Skip to content

Commit

Permalink
Add support for runtime.openOptionsPage() and new tab override URLs.
Browse files Browse the repository at this point in the history
https://webkit.org/b/264117
rdar://problem/117879995

Reviewed by Brent Fulgham.

* Source/WebCore/en.lproj/Localizable.strings: Updated.
* Source/WebKit/UIProcess/API/Cocoa/_WKWebExtension.h:
* Source/WebKit/UIProcess/API/Cocoa/_WKWebExtension.mm:
(-[_WKWebExtension hasOptionsPage]): Added.
(-[_WKWebExtension hasOverrideNewTabPage]): Added.
* Source/WebKit/UIProcess/API/Cocoa/_WKWebExtensionContext.h:
* Source/WebKit/UIProcess/API/Cocoa/_WKWebExtensionContext.mm:
(-[_WKWebExtensionContext optionsPageURL]): Added.
(-[_WKWebExtensionContext overrideNewTabPageURL]): Added.
* Source/WebKit/UIProcess/API/Cocoa/_WKWebExtensionContextPrivate.h:
* Source/WebKit/UIProcess/API/Cocoa/_WKWebExtensionControllerDelegate.h:
* Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPIRuntimeCocoa.mm:
(WebKit::WebExtensionContext::runtimeOpenOptionsPage): Added.
* Source/WebKit/UIProcess/Extensions/Cocoa/WebExtensionCocoa.mm:
(WebKit::toAPI):
(WebKit::WebExtension::createError):
(WebKit::WebExtension::errors):
(WebKit::WebExtension::hasOptionsPage): Added.
(WebKit::WebExtension::hasOverrideNewTabPage): Added.
(WebKit::WebExtension::optionsPagePath): Added.
(WebKit::WebExtension::overrideNewTabPagePath): Added.
(WebKit::WebExtension::populatePagePropertiesIfNeeded): Added.
* Source/WebKit/UIProcess/Extensions/Cocoa/WebExtensionContextCocoa.mm:
(WebKit::WebExtensionContext::optionsPageURL const): Added.
(WebKit::WebExtensionContext::overrideNewTabPageURL const): Added.
* Source/WebKit/UIProcess/Extensions/WebExtension.h:
* Source/WebKit/UIProcess/Extensions/WebExtensionContext.h:
* Source/WebKit/UIProcess/Extensions/WebExtensionContext.messages.in:
* Source/WebKit/WebProcess/Extensions/API/Cocoa/WebExtensionAPIRuntimeCocoa.mm:
(WebKit::WebExtensionAPIRuntime::openOptionsPage): Added.
* Source/WebKit/WebProcess/Extensions/API/WebExtensionAPIRuntime.h:
* Source/WebKit/WebProcess/Extensions/Interfaces/WebExtensionAPIRuntime.idl:
* Tools/TestWebKitAPI/Tests/WebKitCocoa/WKWebExtension.mm:
(TestWebKitAPI::TEST):
* Tools/TestWebKitAPI/Tests/WebKitCocoa/WKWebExtensionAPIRuntime.mm:
(TestWebKitAPI::TEST):
* Tools/TestWebKitAPI/Tests/WebKitCocoa/WKWebExtensionContext.mm:
(TestWebKitAPI::TEST):
* Tools/TestWebKitAPI/cocoa/TestWebExtensionsDelegate.h:
* Tools/TestWebKitAPI/cocoa/TestWebExtensionsDelegate.mm:
(-[TestWebExtensionsDelegate webExtensionController:openOptionsPageForExtensionContext:completionHandler:]): Added.

Canonical link: https://commits.webkit.org/270197@main
  • Loading branch information
xeenon committed Nov 3, 2023
1 parent 86dd02d commit 2e41d88
Show file tree
Hide file tree
Showing 21 changed files with 774 additions and 40 deletions.
36 changes: 15 additions & 21 deletions Source/WebCore/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,6 @@
/* Extension's process name that appears in Activity Monitor where the parameter is the name of the extension */
"%@ Web Extension" = "%@ Web Extension";

/* Extension's process name for background content that appears in Activity Monitor where the parameter is the name of the extension */
"%@ Web Extension Background Content" = "%@ Web Extension Background Content";

/* Extension's process name for popups that appears in Activity Monitor where the parameter is the name of the extension */
"%@ Web Extension Popup" = "%@ Web Extension Popup";

/* Extension's process name for tabs that appears in Activity Monitor where the parameter is the name of the extension */
"%@ Web Extension Tab" = "%@ Web Extension Tab";

/* Visible name of Web Inspector's web process. The argument is the application name. */
"%@ Web Inspector" = "%@ Web Inspector";

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

/* Menu item title for KEYGEN pop-up menu */
"2048 (High Grade)" = "2048 (High Grade)";

/* 2× media controls context menu playback speed label */
"2× (Media Controls Menu Playback Speed)" = "2×";

Expand Down Expand Up @@ -481,12 +469,15 @@
/* Display name for easy reader (i.e. 3rd-grade level) text tracks. */
"Easy Reader (text track)" = "Easy Reader";

/* WKWebExtensionErrorInvalidManifestEntry description for URL overrides */
"Empty or invalid URL overrides manifest entry" = "Empty or invalid URL overrides manifest entry";

/* WKWebExtensionErrorInvalidManifestEntry description for background */
"Empty or invalid `background` manifest entry." = "Empty or invalid `background` manifest entry.";

/* WKWebExtensionErrorInvalidManifestEntry description for browser URL overrides */
"Empty or invalid `browser_url_overrides` manifest entry" = "Empty or invalid `browser_url_overrides` manifest entry";

/* WKWebExtensionErrorInvalidManifestEntry description for chrome URL overrides */
"Empty or invalid `chrome_url_overrides` manifest entry" = "Empty or invalid `chrome_url_overrides` manifest entry";

/* WKWebExtensionErrorInvalidManifestEntry description for content_scripts */
"Empty or invalid `content_scripts` manifest entry." = "Empty or invalid `content_scripts` manifest entry.";

Expand All @@ -499,6 +490,15 @@
/* WKWebExtensionErrorInvalidManifestEntry description for externally_connectable */
"Empty or invalid `externally_connectable` manifest entry." = "Empty or invalid `externally_connectable` manifest entry.";

/* WKWebExtensionErrorInvalidManifestEntry description for invalid new tab entry */
"Empty or invalid `newtab` manifest entry." = "Empty or invalid `newtab` manifest entry.";

/* WKWebExtensionErrorInvalidManifestEntry description for options page */
"Empty or invalid `options_page` manifest entry" = "Empty or invalid `options_page` manifest entry";

/* WKWebExtensionErrorInvalidManifestEntry description for options UI */
"Empty or invalid `options_ui` manifest entry" = "Empty or invalid `options_ui` manifest entry";

/* Video Enter Full Screen context menu item */
"Enter Full Screen" = "Enter Full Screen";

Expand Down Expand Up @@ -688,9 +688,6 @@
/* Undo action name */
"Justify (Undo action name)" = "Justify";

/* Name of keychain key generated by the KEYGEN tag */
"Key from %@" = "Key from %@";

/* Languages media controls context menu title */
"Languages (Media Controls Menu)" = "Languages";

Expand Down Expand Up @@ -952,9 +949,6 @@
/* Description of the primary type supported by the PDF pseudo plug-in. Visible in the Installed Plug-ins page in Safari. */
"Portable Document Format (Safari)" = "Portable Document Format";

/* Description of the PostScript type supported by the PDF pseudo plug-in. Visible in the Installed Plug-ins page in Safari. */
"PostScript" = "PostScript";

/* Title for file upload progress view */
"Preparing (file upload)" = "Preparing…";

Expand Down
14 changes: 14 additions & 0 deletions Source/WebKit/UIProcess/API/Cocoa/_WKWebExtension.h
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,20 @@ WK_CLASS_AVAILABLE(macos(13.3), ios(16.4))
*/
@property (nonatomic, readonly) BOOL backgroundContentIsPersistent;

/*!
@abstract A Boolean value indicating whether the extension has an options page.
@discussion If this property is `YES`, the extension includes a dedicated options page where users can customize settings.
The app should provide access to this page through a user interface element, which can be accessed via `optionsPageURL` on an extension context.
*/
@property (nonatomic, readonly) BOOL hasOptionsPage;

/*!
@abstract A Boolean value indicating whether the extension provides an alternative to the default new tab page.
@discussion If this property is `YES`, the extension can specify a custom page that can be displayed when a new tab is opened in the app, instead of the default new tab page.
The app should prompt the user for permission to use the extension's new tab page as the default, which can be accessed via `overrideNewTabPageURL` on an extension context.
*/
@property (nonatomic, readonly) BOOL hasOverrideNewTabPage;

@end

NS_ASSUME_NONNULL_END
20 changes: 20 additions & 0 deletions Source/WebKit/UIProcess/API/Cocoa/_WKWebExtension.mm
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,16 @@ - (BOOL)backgroundContentIsPersistent
return _webExtension->backgroundContentIsPersistent();
}

- (BOOL)hasOptionsPage
{
return _webExtension->hasOptionsPage();
}

- (BOOL)hasOverrideNewTabPage
{
return _webExtension->hasOverrideNewTabPage();
}

- (BOOL)_backgroundContentIsServiceWorker
{
return _webExtension->backgroundContentIsServiceWorker();
Expand Down Expand Up @@ -421,6 +431,16 @@ - (BOOL)backgroundContentIsPersistent
return NO;
}

- (BOOL)hasOptionsPage
{
return NO;
}

- (BOOL)hasOverrideNewTabPage
{
return NO;
}

- (BOOL)_backgroundContentIsServiceWorker
{
return NO;
Expand Down
18 changes: 18 additions & 0 deletions Source/WebKit/UIProcess/API/Cocoa/_WKWebExtensionContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,24 @@ WK_CLASS_AVAILABLE(macos(13.3), ios(16.4))
*/
@property (nonatomic, readonly, copy, nullable) WKWebViewConfiguration *webViewConfiguration;

/*!
@abstract The URL of the extension's options page, if the extension has one.
@discussion This property holds the URL for the dedicated options page, if provided by the extension; otherwise `nil` if no page is defined.
The app should provide access to this page through a user interface element.
@note Navigation to the options page is only possible after this extension has been loaded.
@seealso webViewConfiguration
*/
@property (nonatomic, readonly, copy, nullable) NSURL *optionsPageURL;

/*!
@abstract The URL to use as an alternative to the default new tab page, if the extension has one.
@discussion This property holds the URL for a new tab page, if provided by the extension; otherwise `nil` if no page is defined.
The app should prompt the user for permission to use the extension's new tab page as the default.
@note Navigation to the override new tab page is only possible after this extension has been loaded.
@seealso webViewConfiguration
*/
@property (nonatomic, readonly, copy, nullable) NSURL *overrideNewTabPageURL;

/*!
@abstract The currently granted permissions and their expiration dates.
@discussion Permissions that don't expire will have a distant future date. This will never include expired entries at time of access.
Expand Down
20 changes: 20 additions & 0 deletions Source/WebKit/UIProcess/API/Cocoa/_WKWebExtensionContext.mm
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,16 @@ - (WKWebViewConfiguration *)webViewConfiguration
return _webExtensionContext->webViewConfiguration(WebKit::WebExtensionContext::WebViewPurpose::Tab);
}

- (NSURL *)optionsPageURL
{
return _webExtensionContext->optionsPageURL();
}

- (NSURL *)overrideNewTabPageURL
{
return _webExtensionContext->overrideNewTabPageURL();
}

static inline WallTime toImpl(NSDate *date)
{
NSCParameterAssert(!date || [date isKindOfClass:NSDate.class]);
Expand Down Expand Up @@ -822,6 +832,16 @@ - (WKWebViewConfiguration *)webViewConfiguration
return nil;
}

- (NSURL *)optionsPageURL
{
return nil;
}

- (NSURL *)overrideNewTabPageURL
{
return nil;
}

- (NSDictionary<_WKWebExtensionPermission, NSDate *> *)grantedPermissions
{
return nil;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ NS_ASSUME_NONNULL_BEGIN
@interface _WKWebExtensionContext ()

/*!
@abstract Enables extra `browser.test` JavaScript APIs for unit testing.
@abstract Enables extra `browser.test` APIs and unsupported API stubs for testing.
@discussion Defaults to `YES` in debug builds.
*/
@property (nonatomic, getter=_inTestingMode, setter=_setTestingMode:) BOOL _testingMode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ WK_API_AVAILABLE(macos(13.3), ios(16.4))
@param completionHandler A block to be called with the newly created window or \c nil if the window wasn't created. An error should be
provided if any errors occurred.
@discussion This method should be implemented by the app to handle requests to open new windows. The app can decide how to handle
the creation based on the provided options and existing windows. Once handled, the app should call the completion block with the created window
the creation based on the provided options and existing windows. Once handled, the app should call the completion handler with the created window
or `nil` if the creation was declined or failed. If not implemented, the extension can't open new windows.
*/
- (void)webExtensionController:(_WKWebExtensionController *)controller openNewWindowWithOptions:(_WKWebExtensionWindowCreationOptions *)options forExtensionContext:(_WKWebExtensionContext *)extensionContext completionHandler:(void (^)(id <_WKWebExtensionWindow> WK_NULLABLE_RESULT newWindow, NSError * _Nullable error))completionHandler;
Expand All @@ -92,20 +92,32 @@ WK_API_AVAILABLE(macos(13.3), ios(16.4))
@param completionHandler A block to be called with the newly created tab or \c nil if the tab wasn't created. An error should be
provided if any errors occurred.
@discussion This method should be implemented by the app to handle requests to open new tabs. The app can decide how to handle
the creation based on the provided options and existing tabs. Once handled, the app should call the completion block with the created tab
the creation based on the provided options and existing tabs. Once handled, the app should call the completion handler with the created tab
or `nil` if the creation was declined or failed. If not implemented, the extension can't open new tabs.
*/
- (void)webExtensionController:(_WKWebExtensionController *)controller openNewTabWithOptions:(_WKWebExtensionTabCreationOptions *)options forExtensionContext:(_WKWebExtensionContext *)extensionContext completionHandler:(void (^)(id <_WKWebExtensionTab> WK_NULLABLE_RESULT newTab, NSError * _Nullable error))completionHandler;

/*!
@abstract Called when an extension context requests its options page to be opened.
@param controller The web extension controller that is managing the extension.
@param extensionContext The context in which the web extension is running.
@param completionHandler A block to be called once the options page has been displayed or with an error if the page could not be shown.
@discussion This method should be implemented by the app to handle requests to display the extension's options page. The app can decide
how and where to display the options page (e.g., in a new tab or a separate window). The app should call the completion handler once the options
page is visible to the user, or with an error if the operation was declined or failed. If not implemented, the options page will be opened in a new tab
using the `webExtensionController:openNewTabWithOptions:forExtensionContext:completionHandler:` delegate method.
*/
- (void)webExtensionController:(_WKWebExtensionController *)controller openOptionsPageForExtensionContext:(_WKWebExtensionContext *)extensionContext completionHandler:(void (^)(NSError * _Nullable error))completionHandler;

/*!
@abstract Called when an extension context requests permissions.
@param controller The web extension controller that is managing the extension.
@param permissions The set of permissions being requested by the extension.
@param tab The tab in which the extension is running, or \c nil if the request are not specific to a tab.
@param extensionContext The context in which the web extension is running.
@param completionHandler A block to be called with the set of allowed permissions.
@discussion This method should be implemented by the app to prompt the user for permission and call the completion block with the
set of permissions that were granted. If not implemented or the completion block is not called within a reasonable amount of time, the
@discussion This method should be implemented by the app to prompt the user for permission and call the completion handler with the
set of permissions that were granted. If not implemented or the completion handler is not called within a reasonable amount of time, the
request is assumed to have been denied.
*/
- (void)webExtensionController:(_WKWebExtensionController *)controller promptForPermissions:(NSSet<_WKWebExtensionPermission> *)permissions inTab:(nullable id <_WKWebExtensionTab>)tab forExtensionContext:(_WKWebExtensionContext *)extensionContext completionHandler:(void (^)(NSSet<_WKWebExtensionPermission> *allowedPermissions))completionHandler NS_SWIFT_NAME(webExtensionController(_:promptForPermissions:in:for:completionHandler:));
Expand All @@ -117,8 +129,8 @@ WK_API_AVAILABLE(macos(13.3), ios(16.4))
@param tab The tab in which the extension is running, or \c nil if the request is not specific to a tab.
@param extensionContext The context in which the web extension is running.
@param completionHandler A block to be called with the set of allowed URLs.
@discussion This method should be implemented by the app to prompt the user for permission and call the completion block with the
set of URLs that were granted access to. If not implemented or the completion block is not called within a reasonable amount of time, the
@discussion This method should be implemented by the app to prompt the user for permission and call the completion handler with the
set of URLs that were granted access to. If not implemented or the completion handler is not called within a reasonable amount of time, the
request is assumed to have been denied.
*/
- (void)webExtensionController:(_WKWebExtensionController *)controller promptForPermissionToAccessURLs:(NSSet<NSURL *> *)urls inTab:(nullable id <_WKWebExtensionTab>)tab forExtensionContext:(_WKWebExtensionContext *)extensionContext completionHandler:(void (^)(NSSet<NSURL *> *allowedURLs))completionHandler NS_SWIFT_NAME(webExtensionController(_:promptForPermissionToAccess:in:for:completionHandler:));
Expand All @@ -130,8 +142,8 @@ WK_API_AVAILABLE(macos(13.3), ios(16.4))
@param tab The tab in which the extension is running, or \c nil if the request is not specific to a tab.
@param extensionContext The context in which the web extension is running.
@param completionHandler A block to be called with the set of allowed match patterns.
@discussion This method should be implemented by the app to prompt the user for permission and call the completion block with the
set of match patterns that were granted access to. If not implemented or the completion block is not called within a reasonable amount of time,
@discussion This method should be implemented by the app to prompt the user for permission and call the completion handler with the
set of match patterns that were granted access to. If not implemented or the completion handler is not called within a reasonable amount of time,
the request is assumed to have been denied.
*/
- (void)webExtensionController:(_WKWebExtensionController *)controller promptForPermissionMatchPatterns:(NSSet<_WKWebExtensionMatchPattern *> *)matchPatterns inTab:(nullable id <_WKWebExtensionTab>)tab forExtensionContext:(_WKWebExtensionContext *)extensionContext completionHandler:(void (^)(NSSet<_WKWebExtensionMatchPattern *> *allowedMatchPatterns))completionHandler NS_SWIFT_NAME(webExtensionController(_:promptForPermissionMatchPatterns:in:for:completionHandler:));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#import "WebExtensionUtilities.h"
#import "WebPageProxy.h"
#import "_WKWebExtensionControllerDelegatePrivate.h"
#import "_WKWebExtensionTabCreationOptionsInternal.h"
#import <wtf/BlockPtr.h>

namespace WebKit {
Expand All @@ -51,6 +52,65 @@
});
}

void WebExtensionContext::runtimeOpenOptionsPage(CompletionHandler<void(std::optional<String> error)>&& completionHandler)
{
if (!optionsPageURL().isValid()) {
completionHandler(toErrorString(@"runtime.openOptionsPage()", nil, @"no options page is specified in the manifest"));
return;
}

auto delegate = extensionController()->delegate();

bool respondsToOpenOptionsPage = [delegate respondsToSelector:@selector(webExtensionController:openOptionsPageForExtensionContext:completionHandler:)];
bool respondsToOpenNewTab = [delegate respondsToSelector:@selector(webExtensionController:openNewTabWithOptions:forExtensionContext:completionHandler:)];
if (!respondsToOpenOptionsPage && !respondsToOpenNewTab) {
completionHandler(toErrorString(@"runtime.openOptionsPage()", nil, @"it is not implemented"));
return;
}

if (respondsToOpenOptionsPage) {
[delegate webExtensionController:extensionController()->wrapper() openOptionsPageForExtensionContext:wrapper() completionHandler:makeBlockPtr([completionHandler = WTFMove(completionHandler)](NSError *error) mutable {
if (error) {
RELEASE_LOG_ERROR(Extensions, "Error opening options page: %{private}@", error);
completionHandler(error.localizedDescription);
return;
}

completionHandler(std::nullopt);
}).get()];

return;
}

ASSERT(respondsToOpenNewTab);

auto frontmostWindow = this->frontmostWindow();

auto *creationOptions = [[_WKWebExtensionTabCreationOptions alloc] _init];
creationOptions.shouldActivate = YES;
creationOptions.shouldSelect = YES;
creationOptions.desiredWindow = frontmostWindow ? frontmostWindow->delegate() : nil;
creationOptions.desiredIndex = frontmostWindow ? frontmostWindow->tabs().size() : 0;
creationOptions.desiredURL = optionsPageURL();

[delegate webExtensionController:extensionController()->wrapper() openNewTabWithOptions:creationOptions forExtensionContext:wrapper() completionHandler:makeBlockPtr([completionHandler = WTFMove(completionHandler)](id<_WKWebExtensionTab> newTab, NSError *error) mutable {
if (error) {
RELEASE_LOG_ERROR(Extensions, "Error opening options page in new tab: %{private}@", error);
completionHandler(error.localizedDescription);
return;
}

if (!newTab) {
completionHandler(toErrorString(@"runtime.openOptionsPage()", nil, @"the options page cound not be opened"));
return;
}

THROW_UNLESS([newTab conformsToProtocol:@protocol(_WKWebExtensionTab)], @"Object returned by webExtensionController:openNewTabWithOptions:forExtensionContext:completionHandler: does not conform to the _WKWebExtensionTab protocol");

completionHandler(std::nullopt);
}).get()];
}

void WebExtensionContext::runtimeSendMessage(const String& extensionID, const String& messageJSON, const WebExtensionMessageSenderParameters& senderParameters, CompletionHandler<void(std::optional<String> replyJSON, std::optional<String> error)>&& completionHandler)
{
if (!extensionID.isEmpty() && uniqueIdentifier() != extensionID) {
Expand Down
Loading

0 comments on commit 2e41d88

Please sign in to comment.