Skip to content

Commit

Permalink
Support world in content_scripts for Web Extension manifests.
Browse files Browse the repository at this point in the history
https://webkit.org/b/270748
rdar://problem/124331541

Reviewed by Brian Weinstein.

Add support for the `world` key in `content_scripts`. Also changed the `forMainWorld`
boolean to be `contentWorldType` and use `WebExtensionContentWorldType` for consistency.

Drive-by fixes to provide missing default values for plain types to avoid random memory.

* Source/WebCore/en.lproj/Localizable.strings: Updated with the script.
* Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js: Ditto.
* Source/WebKit/Shared/Extensions/WebExtensionFrameParameters.h: Add default value.
* Source/WebKit/Shared/Extensions/WebExtensionMessageSenderParameters.h: Ditto.
* Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPIScriptingCocoa.mm:
(WebKit::WebExtensionContext::scriptingExecuteScript): Use toContentWorld().
(WebKit::WebExtensionContext::createInjectedContentForScripts): Use contentWorldType. Also
use value_or() in case the web process does not set the value.
* Source/WebKit/UIProcess/Extensions/Cocoa/WebExtensionCocoa.mm:
(WebKit::WebExtension::populateContentScriptPropertiesIfNeeded): Parse the `world` key.
* Source/WebKit/UIProcess/Extensions/Cocoa/WebExtensionContextCocoa.mm:
(WebKit::WebExtensionContext::toContentWorld const): Added.
(WebKit::WebExtensionContext::addInjectedContent): Use toContentWorld().
* Source/WebKit/UIProcess/Extensions/WebExtension.h:
* Source/WebKit/UIProcess/Extensions/WebExtensionContext.h:
* Tools/TestWebKitAPI/Tests/WebKitCocoa/WKWebExtension.mm:
(TEST(WKWebExtension, ContentScriptsParsing)): Added `world` tests.
* Tools/TestWebKitAPI/Tests/WebKitCocoa/WKWebExtensionAPIScripting.mm:
(TEST(WKWebExtensionAPIScripting, MainWorld)): Added.
(TEST(WKWebExtensionAPIScripting, IsolatedWorld)): Added.
* Tools/TestWebKitAPI/Tests/WebKitCocoa/WKWebExtensionContext.mm:
(TEST(WKWebExtensionContext, ContentScriptsParsing)): Added `world` tests.

Canonical link: https://commits.webkit.org/275909@main
  • Loading branch information
xeenon committed Mar 11, 2024
1 parent ff20e45 commit 3894943
Show file tree
Hide file tree
Showing 12 changed files with 191 additions and 30 deletions.
46 changes: 27 additions & 19 deletions Source/WebCore/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,6 @@
/* window title for a standalone image (uses multiplication symbol, not x) */
"%s %d×%d pixels" = "%s %d×%d pixels";

/* Name of application's single WebCrypto master key in Keychain */
"%s WebCrypto Master Key" = "%s WebCrypto Master Key";

/* Present the number of selected <option> items in a <select multiple> element (iOS only) */
"%zu Items" = "%zu Items";

Expand Down Expand Up @@ -124,9 +121,6 @@
/* 2× media controls context menu playback speed label */
"2× (Media Controls Menu Playback Speed)" = "2×";

/* Name of application's single WebCrypto master key in Keychain */
"<application> WebCrypto Master Key" = "<application> WebCrypto Master Key";

/* window title for a standalone image (uses multiplication symbol, not x) */
"<filename> %d×%d pixels" = "<filename> %d×%d pixels";

Expand Down Expand Up @@ -730,6 +724,9 @@
/* Inspect Element context menu item */
"Inspect Element" = "Inspect Element";

/* Message when a PDF fails to unlock with the given password */
"Invalid Password" = "Invalid Password";

/* WKWebExtensionErrorInvalidManifestEntry description for commands */
"Invalid `commands` manifest entry." = "Invalid `commands` manifest entry.";

Expand All @@ -751,9 +748,6 @@
/* WKWebExtensionErrorInvalidManifestEntry description for web_accessible_resources */
"Invalid `web_accessible_resources` manifest entry." = "Invalid `web_accessible_resources` manifest entry.";

/* Message when a PDF fails to unlock with the given password */
"Invalid Password" = "Invalid Password";

/* Validation message for input form controls with a value not matching type */
"Invalid value" = "Invalid value";

Expand Down Expand Up @@ -967,6 +961,9 @@
/* WKWebExtensionErrorInvalidContentScripts description for unknown 'run_at' value */
"Manifest `content_scripts` entry has unknown `run_at` value." = "Manifest `content_scripts` entry has unknown `run_at` value.";

/* WKWebExtensionErrorInvalidContentScripts description for unknown 'world' value */
"Manifest `content_scripts` entry has unknown `world` value." = "Manifest `content_scripts` entry has unknown `world` value.";

/* _WKWebExtensionErrorInvalidDeclarativeNetRequestEntry description for missing declarativeNetRequest permission */
"Manifest has no `declarativeNetRequest` permission." = "Manifest has no `declarativeNetRequest` permission.";

Expand Down Expand Up @@ -1150,6 +1147,9 @@
/* Playback Speed media controls context menu title */
"Playback Speed (Media Controls Menu)" = "Playback Speed";

/* Subtitle when a PDF needs a password to be unlocked */
"Please enter the password below." = "Please enter the password below.";

/* Label text to be used when a plug-in was blocked from loading because it was too small */
"Plug-In too small" = "Plug-In too small";

Expand Down Expand Up @@ -1396,6 +1396,9 @@
/* Label for the support with Apple Pay button. */
"Support with Apple Pay" = "Support with Apple Pay";

/* Swap characters context menu item */
"Swap characters" = "Swap characters";

/* File Upload alert sheet camera button string for taking only photos */
"Take Photo (file upload action sheet)" = "Take Photo";

Expand Down Expand Up @@ -1795,6 +1798,9 @@
/* accessibility role description for a mark element */
"highlighted" = "highlighted";

/* accessibility label for hour fields. */
"hour" = "hour";

/* accessibility role description for image map */
"image map" = "image map";

Expand Down Expand Up @@ -1825,6 +1831,15 @@
/* An ARIA accessibility group that contains mathematical symbols. */
"math" = "math";

/* accessibility label for meridiem fields. */
"meridiem" = "meridiem";

/* accessibility label for milliseconds fields. */
"milliseconds" = "milliseconds";

/* accessibility label for minutes fields. */
"minutes" = "minutes";

/* accessibility label for a date field month input. */
"month" = "month";

Expand Down Expand Up @@ -1888,9 +1903,6 @@
/* accessibility label for play button */
"play" = "play";

/* Subtitle when a PDF needs a password to be unlocked */
"Please enter the password below." = "Please enter the password below.";

/* Verb stating the action that will occur when a button is pressed, as used by accessibility */
"press" = "press";

Expand Down Expand Up @@ -1918,6 +1930,9 @@
/* An ARIA accessibility group that contains a search feature of a website. */
"search" = "search";

/* accessibility label for seconds fields. */
"seconds" = "seconds";

/* accessibility help text for jump back 30 seconds button */
"seek movie back 30 seconds" = "seek movie back 30 seconds";

Expand Down Expand Up @@ -2017,13 +2032,6 @@
/* accessibility label for a date field year input. */
"year" = "year";

/* Accessibility labels for time fields. */
"hour" = "hour";
"minutes" = "minutes";
"seconds" = "seconds";
"milliseconds" = "milliseconds";
"meridiem" = "meridiem";

/* Message for requesting access to the device motion and orientation */
"“%@” Would Like to Access Motion and Orientation" = "“%@” Would Like to Access Motion and Orientation";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1839,7 +1839,6 @@ localizedStrings["Ungrouped @ Computed Style variables grouping mode"] = "Ungrou
/* Property value for `font-variant-capitals: unicase`. */
localizedStrings["Unicase @ Font Details Sidebar Property Value"] = "Unicase";
localizedStrings["Unique"] = "Unique";
localizedStrings["Unknown"] = "Unknown";
localizedStrings["Unknown Location"] = "Unknown Location";
localizedStrings["Unknown error"] = "Unknown error";
localizedStrings["Unknown node"] = "Unknown node";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
namespace WebKit {

struct WebExtensionFrameParameters {
bool errorOccurred;
bool errorOccurred { false };
std::optional<URL> url;
WebKit::WebExtensionFrameIdentifier parentFrameIdentifier;
std::optional<WebKit::WebExtensionFrameIdentifier> frameIdentifier;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ struct WebExtensionMessageSenderParameters {
std::optional<WebExtensionTabParameters> tabParameters;
std::optional<WebExtensionFrameIdentifier> frameIdentifier;
WebPageProxyIdentifier pageProxyIdentifier;
WebExtensionContentWorldType contentWorldType;
WebExtensionContentWorldType contentWorldType { WebExtensionContentWorldType::ContentScript };
URL url;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
}

auto scriptPairs = getSourcePairsForParameters(parameters, m_extension);
Ref executionWorld = parameters.world == WebExtensionContentWorldType::Main ? API::ContentWorld::pageContentWorld() : *m_contentScriptWorld;
Ref executionWorld = toContentWorld(parameters.world);

executeScript(scriptPairs, webView, executionWorld, tab.get(), parameters, *this, [completionHandler = WTFMove(completionHandler)](InjectionResults&& injectionResults) mutable {
completionHandler(WTFMove(injectionResults));
Expand Down Expand Up @@ -390,9 +390,9 @@
injectedContentData.identifier = parameters.identifier;
injectedContentData.includeMatchPatterns = WTFMove(includeMatchPatterns);
injectedContentData.excludeMatchPatterns = WTFMove(excludeMatchPatterns);
injectedContentData.injectionTime = parameters.injectionTime.value();
injectedContentData.injectsIntoAllFrames = parameters.allFrames.value();
injectedContentData.forMainWorld = parameters.world.value() == WebExtensionContentWorldType::Main;
injectedContentData.injectionTime = parameters.injectionTime.value_or(WebExtension::InjectionTime::DocumentIdle);
injectedContentData.injectsIntoAllFrames = parameters.allFrames.value_or(false);
injectedContentData.contentWorldType = parameters.world.value_or(WebExtensionContentWorldType::ContentScript);
injectedContentData.scriptPaths = scriptPaths;
injectedContentData.styleSheetPaths = styleSheetPaths;

Expand Down
14 changes: 13 additions & 1 deletion Source/WebKit/UIProcess/Extensions/Cocoa/WebExtensionCocoa.mm
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@
static NSString * const contentScriptsAllFramesManifestKey = @"all_frames";
static NSString * const contentScriptsJSManifestKey = @"js";
static NSString * const contentScriptsCSSManifestKey = @"css";
static NSString * const contentScriptsWorldManifestKey = @"world";
static NSString * const contentScriptsIsolatedManifestKey = @"ISOLATED";
static NSString * const contentScriptsMainManifestKey = @"MAIN";

static NSString * const permissionsManifestKey = @"permissions";
static NSString * const optionalPermissionsManifestKey = @"optional_permissions";
Expand Down Expand Up @@ -1950,13 +1953,22 @@ static bool parseCommandShortcut(const String& shortcut, OptionSet<ModifierFlags
else
recordError(createError(Error::InvalidContentScripts, WEB_UI_STRING("Manifest `content_scripts` entry has unknown `run_at` value.", "WKWebExtensionErrorInvalidContentScripts description for unknown 'run_at' value")));

WebExtensionContentWorldType contentWorldType = WebExtensionContentWorldType::ContentScript;
NSString *worldString = objectForKey<NSString>(dictionary, contentScriptsWorldManifestKey);
if (!worldString || [worldString isEqualToString:contentScriptsIsolatedManifestKey])
contentWorldType = WebExtensionContentWorldType::ContentScript;
else if ([worldString isEqualToString:contentScriptsMainManifestKey])
contentWorldType = WebExtensionContentWorldType::Main;
else
recordError(createError(Error::InvalidContentScripts, WEB_UI_STRING("Manifest `content_scripts` entry has unknown `world` value.", "WKWebExtensionErrorInvalidContentScripts description for unknown 'world' value")));

InjectedContentData injectedContentData;
injectedContentData.includeMatchPatterns = WTFMove(includeMatchPatterns);
injectedContentData.excludeMatchPatterns = WTFMove(excludeMatchPatterns);
injectedContentData.injectionTime = injectionTime;
injectedContentData.matchesAboutBlank = matchesAboutBlank;
injectedContentData.injectsIntoAllFrames = injectsIntoAllFrames;
injectedContentData.forMainWorld = false;
injectedContentData.contentWorldType = contentWorldType;
injectedContentData.scriptPaths = scriptPaths;
injectedContentData.styleSheetPaths = styleSheetPaths;
injectedContentData.includeGlobPatternStrings = includeGlobPatternStrings;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3519,6 +3519,25 @@ void effectiveAppearanceDidChange(Inspector::ExtensionAppearance appearance) ove
}
}

API::ContentWorld& WebExtensionContext::toContentWorld(WebExtensionContentWorldType contentWorldType) const
{
ASSERT(isLoaded());

switch (contentWorldType) {
case WebExtensionContentWorldType::Main:
case WebExtensionContentWorldType::WebPage:
#if ENABLE(INSPECTOR_EXTENSIONS)
case WebExtensionContentWorldType::Inspector:
#endif
return API::ContentWorld::pageContentWorld();
case WebExtensionContentWorldType::ContentScript:
return *m_contentScriptWorld;
case WebExtensionContentWorldType::Native:
ASSERT_NOT_REACHED();
return API::ContentWorld::pageContentWorld();
}
}

void WebExtensionContext::addInjectedContent(const InjectedContentVector& injectedContents, WebExtensionMatchPattern& pattern)
{
if (!isLoaded())
Expand Down Expand Up @@ -3604,7 +3623,7 @@ void effectiveAppearanceDidChange(Inspector::ExtensionAppearance appearance) ove
auto injectedFrames = injectedContentData.injectsIntoAllFrames ? WebCore::UserContentInjectedFrames::InjectInAllFrames : WebCore::UserContentInjectedFrames::InjectInTopFrameOnly;
auto injectionTime = toImpl(injectedContentData.injectionTime);
auto waitForNotification = WebCore::WaitForNotificationBeforeInjecting::No;
Ref executionWorld = injectedContentData.forMainWorld ? API::ContentWorld::pageContentWorld() : *m_contentScriptWorld;
Ref executionWorld = toContentWorld(injectedContentData.contentWorldType);

auto scriptID = injectedContentData.identifier;
bool isRegisteredScript = !scriptID.isEmpty();
Expand Down
5 changes: 3 additions & 2 deletions Source/WebKit/UIProcess/Extensions/WebExtension.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

#include "APIObject.h"
#include "CocoaImage.h"
#include "WebExtensionContentWorldType.h"
#include "WebExtensionMatchPattern.h"
#include <wtf/Forward.h>
#include <wtf/HashSet.h>
Expand Down Expand Up @@ -155,7 +156,7 @@ class WebExtension : public API::ObjectImpl<API::Object::Type::WebExtension>, pu

bool matchesAboutBlank { false };
bool injectsIntoAllFrames { false };
bool forMainWorld { false };
WebExtensionContentWorldType contentWorldType { WebExtensionContentWorldType::ContentScript };

RetainPtr<NSArray> scriptPaths;
RetainPtr<NSArray> styleSheetPaths;
Expand All @@ -174,7 +175,7 @@ class WebExtension : public API::ObjectImpl<API::Object::Type::WebExtension>, pu

struct DeclarativeNetRequestRulesetData {
String rulesetID;
bool enabled;
bool enabled { false };
String jsonPath;
};

Expand Down
2 changes: 2 additions & 0 deletions Source/WebKit/UIProcess/Extensions/WebExtensionContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,8 @@ class WebExtensionContext : public API::ObjectImpl<API::Object::Type::WebExtensi
HashSet<Ref<WebProcessProxy>> processes(const API::InspectorExtension&) const;
#endif // ENABLE(INSPECTOR_EXTENSIONS)

API::ContentWorld& toContentWorld(WebExtensionContentWorldType) const;

void addInjectedContent() { addInjectedContent(injectedContents()); }
void addInjectedContent(const InjectedContentVector&);
void addInjectedContent(const InjectedContentVector&, MatchPatternSet&);
Expand Down
13 changes: 13 additions & 0 deletions Tools/TestWebKitAPI/Tests/WebKitCocoa/WKWebExtension.mm
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,12 @@
EXPECT_NS_EQUAL(testExtension.errors, @[ ]);
EXPECT_TRUE(testExtension.hasInjectedContent);

testManifestDictionary[@"content_scripts"] = @[ @{ @"js": @[ @"test.js" ], @"matches": @[ @"*://*.example.com/" ], @"world": @"MAIN" } ];
testExtension = [[_WKWebExtension alloc] _initWithManifestDictionary:testManifestDictionary];

EXPECT_NS_EQUAL(testExtension.errors, @[ ]);
EXPECT_TRUE(testExtension.hasInjectedContent);

// Invalid cases

testManifestDictionary[@"content_scripts"] = @[ ];
Expand Down Expand Up @@ -252,6 +258,13 @@
EXPECT_NE(testExtension.errors.count, 0ul);
EXPECT_NOT_NULL(matchingError(testExtension.errors, _WKWebExtensionErrorInvalidManifestEntry));
EXPECT_TRUE(testExtension.hasInjectedContent);

testManifestDictionary[@"content_scripts"] = @[ @{ @"js": @[ @"test.js" ], @"matches": @[ @"*://*.example.com/" ], @"world": @"INVALID" } ];
testExtension = [[_WKWebExtension alloc] _initWithManifestDictionary:testManifestDictionary];

EXPECT_NE(testExtension.errors.count, 0ul);
EXPECT_NOT_NULL(matchingError(testExtension.errors, _WKWebExtensionErrorInvalidManifestEntry));
EXPECT_TRUE(testExtension.hasInjectedContent);
}

TEST(WKWebExtension, PermissionsParsing)
Expand Down
Loading

0 comments on commit 3894943

Please sign in to comment.