Skip to content

Commit

Permalink
Add support for externally_connectable
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=268856
rdar://122422972

Authors: Kiara Rose, Timothy Hatcher

Reviewed by Timothy Hatcher.

This patch creates seperate runtime and namespace objects to use for web pages
that wish to send and receive messages from extensions.

Also change how WebExtensionAPIObject tracks the world, and simplify child object creation
by passing the parent object. This touched all the API files, those changed files are elided.

Also change WebExtensionController to have a concept of testMode, so WebPage APIs can
use the browser.test API too, without an extensionContext. This required changing the
UI process WebExtensionContextAPITestCocoa to be WebExtensionControllerAPITestCocoa and
changing the delegates to no longer get a context. This wasn't used, so it is fine to remove.

* Source/WebKit/DerivedSources-input.xcfilelist:
* Source/WebKit/DerivedSources-output.xcfilelist:
* Source/WebKit/DerivedSources.make:

* Source/WebKit/Shared/Extensions/WebExtensionContentWorldType.h:
(WebKit::toDebugString):
* Source/WebKit/Shared/Extensions/WebExtensionContentWorldType.serialization.in:
Add a new WebExtensionContentWorldType::WebPage to distinguish messages being sent to and from web pages.

* Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPIPortCocoa.mm:
(WebKit::WebExtensionContext::portPostMessage):
(WebKit::WebExtensionContext::firePortDisconnectEventIfNeeded):
Notify ports created from webPageRuntime.connect that the port was disconnected.

* Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPIRuntimeCocoa.mm:
(WebKit::WebExtensionContext::runtimeWebPageSendMessage):
(WebKit::WebExtensionContext::runtimeWebPageConnect):

* Source/WebKit/UIProcess/Extensions/Cocoa/WebExtensionControllerCocoa.mm:
(WebKit::WebExtensionController::extensionContext const):
Mehtod to return extension context with a given unique identifier.

* Source/WebKit/UIProcess/Extensions/Cocoa/WebExtensionMatchPatternCocoa.mm:
(WebKit::WebExtensionMatchPattern::matchesPatternSetContainsMatchForURL):
Return true if one of the match pattern in the MatchPatternSet matches the given URL.

* Source/WebKit/UIProcess/Extensions/WebExtensionContext.h:
* Source/WebKit/UIProcess/Extensions/WebExtensionContext.messages.in:
* Source/WebKit/UIProcess/Extensions/WebExtensionController.h:
* Source/WebKit/UIProcess/Extensions/WebExtensionMatchPattern.h:
* Source/WebKit/WebKit.xcodeproj/project.pbxproj:

* Source/WebKit/WebProcess/Extensions/API/Cocoa/WebExtensionAPIRuntimeCocoa.mm:
(WebKit::WebExtensionAPIWebPageRuntime::sendMessage):
(WebKit::WebExtensionAPIWebPageRuntime::connect):

(WebKit::WebExtensionContextProxy::internalDispatchRuntimeMessageEvent):
Use the contentWorldType from the sendersParameters to determine which onConnect listeners
we should call.

(WebKit::WebExtensionContextProxy::dispatchRuntimeMessageEvent):
Event listeners cannot be added for the webPageRuntime namespace object.

(WebKit::WebExtensionContextProxy::internalDispatchRuntimeConnectEvent):
Use the contentWorldType from the sendersParameters to determine which onConnect listeners
we should call.

(WebKit::WebExtensionContextProxy::dispatchRuntimeConnectEvent):
Event listeners cannot be added for the webPageRuntime namespace object.

* Source/WebKit/WebProcess/Extensions/API/Cocoa/WebExtensionAPIWebPageNamespaceCocoa.mm:
Copied from Source/WebKit/Shared/Extensions/WebExtensionContentWorldType.h.
(WebKit::WebExtensionAPIWebPageNamespace::runtime):
* Source/WebKit/WebProcess/Extensions/API/Cocoa/WebExtensionAPIWebPageRuntimeCocoa.mm: Added.
* Source/WebKit/WebProcess/Extensions/API/WebExtensionAPIObject.h:
(WebKit::WebExtensionAPIObject::WebExtensionAPIObject):

* Source/WebKit/WebProcess/Extensions/API/WebExtensionAPIRuntime.h:
* Source/WebKit/WebProcess/Extensions/API/WebExtensionAPIWebPageNamespace.h:
Copied from Source/WebKit/Shared/Extensions/WebExtensionContentWorldType.h.

* Source/WebKit/WebProcess/Extensions/API/WebExtensionAPIWebPageRuntime.h:
Copied from Source/WebKit/Shared/Extensions/WebExtensionContentWorldType.h.

* Source/WebKit/WebProcess/Extensions/Cocoa/WebExtensionContextProxyCocoa.mm:
(WebKit::WebExtensionContextProxy::toDOMWorld):

* Source/WebKit/WebProcess/Extensions/Cocoa/WebExtensionControllerProxyCocoa.mm:
(WebKit::WebExtensionControllerProxy::globalObjectIsAvailableForFrame):
(WebKit::WebExtensionControllerProxy::serviceWorkerGlobalObjectIsAvailableForFrame):
(WebKit::WebExtensionControllerProxy::addBindingsToWebPageFrameIfNecessary):
Inject custom browser namespace object for web pages.

* Source/WebKit/WebProcess/Extensions/Interfaces/WebExtensionAPIWebPageNamespace.idl:
Copied from Source/WebKit/Shared/Extensions/WebExtensionContentWorldType.h.
* Source/WebKit/WebProcess/Extensions/Interfaces/WebExtensionAPIWebPageRuntime.idl:
Copied from Source/WebKit/Shared/Extensions/WebExtensionContentWorldType.h.
* Source/WebKit/WebProcess/Extensions/WebExtensionControllerProxy.h:

Canonical link: https://commits.webkit.org/274898@main
  • Loading branch information
kiaraarose committed Feb 17, 2024
1 parent 6c9a595 commit b69952d
Show file tree
Hide file tree
Showing 66 changed files with 1,092 additions and 238 deletions.
2 changes: 2 additions & 0 deletions Source/WebKit/DerivedSources-input.xcfilelist
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,8 @@ $(PROJECT_DIR)/WebProcess/Extensions/Interfaces/WebExtensionAPITabs.idl
$(PROJECT_DIR)/WebProcess/Extensions/Interfaces/WebExtensionAPITest.idl
$(PROJECT_DIR)/WebProcess/Extensions/Interfaces/WebExtensionAPIWebNavigation.idl
$(PROJECT_DIR)/WebProcess/Extensions/Interfaces/WebExtensionAPIWebNavigationEvent.idl
$(PROJECT_DIR)/WebProcess/Extensions/Interfaces/WebExtensionAPIWebPageNamespace.idl
$(PROJECT_DIR)/WebProcess/Extensions/Interfaces/WebExtensionAPIWebPageRuntime.idl
$(PROJECT_DIR)/WebProcess/Extensions/Interfaces/WebExtensionAPIWebRequest.idl
$(PROJECT_DIR)/WebProcess/Extensions/Interfaces/WebExtensionAPIWebRequestEvent.idl
$(PROJECT_DIR)/WebProcess/Extensions/Interfaces/WebExtensionAPIWindows.idl
Expand Down
4 changes: 4 additions & 0 deletions Source/WebKit/DerivedSources-output.xcfilelist
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ $(BUILT_PRODUCTS_DIR)/DerivedSources/WebKit/JSWebExtensionAPIWebNavigation.h
$(BUILT_PRODUCTS_DIR)/DerivedSources/WebKit/JSWebExtensionAPIWebNavigation.mm
$(BUILT_PRODUCTS_DIR)/DerivedSources/WebKit/JSWebExtensionAPIWebNavigationEvent.h
$(BUILT_PRODUCTS_DIR)/DerivedSources/WebKit/JSWebExtensionAPIWebNavigationEvent.mm
$(BUILT_PRODUCTS_DIR)/DerivedSources/WebKit/JSWebExtensionAPIWebPageNamespace.h
$(BUILT_PRODUCTS_DIR)/DerivedSources/WebKit/JSWebExtensionAPIWebPageNamespace.mm
$(BUILT_PRODUCTS_DIR)/DerivedSources/WebKit/JSWebExtensionAPIWebPageRuntime.h
$(BUILT_PRODUCTS_DIR)/DerivedSources/WebKit/JSWebExtensionAPIWebPageRuntime.mm
$(BUILT_PRODUCTS_DIR)/DerivedSources/WebKit/JSWebExtensionAPIWebRequest.h
$(BUILT_PRODUCTS_DIR)/DerivedSources/WebKit/JSWebExtensionAPIWebRequest.mm
$(BUILT_PRODUCTS_DIR)/DerivedSources/WebKit/JSWebExtensionAPIWebRequestEvent.h
Expand Down
2 changes: 2 additions & 0 deletions Source/WebKit/DerivedSources.make
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,8 @@ EXTENSION_INTERFACES = \
WebExtensionAPITest \
WebExtensionAPIWebNavigation \
WebExtensionAPIWebNavigationEvent \
WebExtensionAPIWebPageNamespace \
WebExtensionAPIWebPageRuntime \
WebExtensionAPIWebRequest \
WebExtensionAPIWebRequestEvent \
WebExtensionAPIWindows \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ enum class WebExtensionContentWorldType : uint8_t {
Main,
ContentScript,
Native,
WebPage,
};

inline String toDebugString(WebExtensionContentWorldType contentWorldType)
Expand All @@ -46,6 +47,8 @@ inline String toDebugString(WebExtensionContentWorldType contentWorldType)
return "content script"_s;
case WebExtensionContentWorldType::Native:
return "native"_s;
case WebExtensionContentWorldType::WebPage:
return "web page"_s;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ enum class WebKit::WebExtensionContentWorldType : uint8_t {
Main,
ContentScript,
Native,
WebPage,
}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ namespace WebKit {
struct WebExtensionControllerParameters {
WebExtensionControllerIdentifier identifier;

bool testingMode { false };

#if PLATFORM(COCOA)
Vector<WebExtensionContextParameters> contextParameters;
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
struct WebKit::WebExtensionControllerParameters {
WebKit::WebExtensionControllerIdentifier identifier;

bool testingMode;

#if PLATFORM(COCOA)
Vector<WebKit::WebExtensionContextParameters> contextParameters;
#endif
Expand Down
19 changes: 19 additions & 0 deletions Source/WebKit/UIProcess/API/Cocoa/_WKWebExtensionController.mm
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,16 @@ - (void)didChangeTabProperties:(_WKWebExtensionTabChangedProperties)properties f
[context->wrapper() didChangeTabProperties:properties forTab:changedTab];
}

- (BOOL)_inTestingMode
{
return _webExtensionController->inTestingMode();
}

- (void)_setTestingMode:(BOOL)testingMode
{
_webExtensionController->setTestingMode(testingMode);
}

#pragma mark WKObject protocol implementation

- (API::Object&)_apiObject
Expand Down Expand Up @@ -390,6 +400,15 @@ - (void)didChangeTabProperties:(_WKWebExtensionTabChangedProperties)properties f
{
}

- (BOOL)_inTestingMode
{
return NO;
}

- (void)_setTestingMode:(BOOL)testingMode
{
}

#endif // ENABLE(WK_WEB_EXTENSIONS)

@end
Original file line number Diff line number Diff line change
Expand Up @@ -35,31 +35,31 @@ WK_API_AVAILABLE(macos(13.3), ios(16.4))
@abstract Delegate for the `browser.test.assertTrue()`, `browser.test.assertFalse()`, `browser.test.assertThrows()`, and `browser.test.assertRejects()` JavaScript testing APIs.
@discussion Default implementation logs a message to the system console when `result` is `NO`.
*/
- (void)_webExtensionController:(_WKWebExtensionController *)controller recordTestAssertionResult:(BOOL)result withMessage:(NSString *)message andSourceURL:(NSString *)sourceURL lineNumber:(unsigned)lineNumber forExtensionContext:(_WKWebExtensionContext *)context;
- (void)_webExtensionController:(_WKWebExtensionController *)controller recordTestAssertionResult:(BOOL)result withMessage:(NSString *)message andSourceURL:(NSString *)sourceURL lineNumber:(unsigned)lineNumber;

/*!
@abstract Delegate for the `browser.test.assertEq()` and `browser.test.assertDeepEq()` JavaScript testing APIs.
@discussion Default implementation logs a message to the system console when `result` is `NO`.
*/
- (void)_webExtensionController:(_WKWebExtensionController *)controller recordTestEqualityResult:(BOOL)result expectedValue:(NSString *)expectedValue actualValue:(NSString *)actualValue withMessage:(NSString *)message andSourceURL:(NSString *)sourceURL lineNumber:(unsigned)lineNumber forExtensionContext:(_WKWebExtensionContext *)context;
- (void)_webExtensionController:(_WKWebExtensionController *)controller recordTestEqualityResult:(BOOL)result expectedValue:(NSString *)expectedValue actualValue:(NSString *)actualValue withMessage:(NSString *)message andSourceURL:(NSString *)sourceURL lineNumber:(unsigned)lineNumber;

/*!
@abstract Delegate for the `browser.test.log()` JavaScript testing API.
@discussion Default implementation always logs the message to the system console.
*/
- (void)_webExtensionController:(_WKWebExtensionController *)controller recordTestMessage:(NSString *)message andSourceURL:(NSString *)sourceURL lineNumber:(unsigned)lineNumber forExtensionContext:(_WKWebExtensionContext *)context;
- (void)_webExtensionController:(_WKWebExtensionController *)controller recordTestMessage:(NSString *)message andSourceURL:(NSString *)sourceURL lineNumber:(unsigned)lineNumber;

/*!
@abstract Delegate for the `browser.test.yield()` JavaScript testing API.
@discussion Default implementation always logs the message to the system console. Test harnesses should use this to exit the run loop to preform other work before resuming extension execution.
*/
- (void)_webExtensionController:(_WKWebExtensionController *)controller recordTestYieldedWithMessage:(NSString *)message andSourceURL:(NSString *)sourceURL lineNumber:(unsigned)lineNumber forExtensionContext:(_WKWebExtensionContext *)context;
- (void)_webExtensionController:(_WKWebExtensionController *)controller recordTestYieldedWithMessage:(NSString *)message andSourceURL:(NSString *)sourceURL lineNumber:(unsigned)lineNumber;

/*!
@abstract Delegate for the `browser.test.notifyPass()` and `browser.test.notifyFail()` JavaScript testing APIs.
@discussion Default implementation logs a message to the system console when `result` is `NO`. Test harnesses should use this to exit the run loop and record a test pass or failure.
*/
- (void)_webExtensionController:(_WKWebExtensionController *)controller recordTestFinishedWithResult:(BOOL)result message:(NSString *)message andSourceURL:(NSString *)sourceURL lineNumber:(unsigned)lineNumber forExtensionContext:(_WKWebExtensionContext *)context;
- (void)_webExtensionController:(_WKWebExtensionController *)controller recordTestFinishedWithResult:(BOOL)result message:(NSString *)message andSourceURL:(NSString *)sourceURL lineNumber:(unsigned)lineNumber;

/*!
@abstract Delegate notification about the creation of the background web view in the web extension context.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@

#import <WebKit/_WKWebExtensionController.h>

NS_ASSUME_NONNULL_BEGIN

@interface _WKWebExtensionController ()

/*!
@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;

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@
if (auto nativePort = m_nativePortMap.get(channelIdentifier))
nativePort->receiveMessage(parseJSON(messageJSON, JSONOptions::FragmentsAllowed), std::nullopt);
return;

case WebExtensionContentWorldType::WebPage:
sendToProcesses(processes(type, WebExtensionContentWorldType::WebPage), Messages::WebExtensionContextProxy::DispatchPortMessageEvent(sendingPageProxyIdentifier, channelIdentifier, messageJSON));
return;
}
}

Expand Down Expand Up @@ -203,6 +207,10 @@
if (auto nativePort = m_nativePortMap.get(channelIdentifier))
nativePort->reportDisconnection(std::nullopt);
return;

case WebExtensionContentWorldType::WebPage:
sendToProcesses(processes(type, WebExtensionContentWorldType::WebPage), Messages::WebExtensionContextProxy::DispatchPortDisconnectEvent(channelIdentifier));
return;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@
void WebExtensionContext::runtimeSendMessage(const String& extensionID, const String& messageJSON, const WebExtensionMessageSenderParameters& senderParameters, CompletionHandler<ReplyCompletionHandlerSignature>&& completionHandler)
{
if (!extensionID.isEmpty() && uniqueIdentifier() != extensionID) {
// FIXME: <https://webkit.org/b/id269299> Add support for externally_connectable:ids.
completionHandler(std::nullopt, toErrorString(@"runtime.sendMessage()", @"extensionID", @"cross-extension messaging is not supported"));
return;
}
Expand Down Expand Up @@ -152,6 +153,7 @@
addPorts(sourceContentWorldType, channelIdentifier, 1);

if (!extensionID.isEmpty() && uniqueIdentifier() != extensionID) {
// FIXME: <https://webkit.org/b/id269299> Add support for externally_connectable:ids.
completionHandler(toErrorString(@"runtime.connect()", @"extensionID", @"cross-extension messaging is not supported"));
return;
}
Expand Down Expand Up @@ -244,6 +246,97 @@
}).get()];
}

void WebExtensionContext::runtimeWebPageSendMessage(const String& extensionID, const String& messageJSON, const WebExtensionMessageSenderParameters& senderParameters, CompletionHandler<ReplyCompletionHandlerSignature>&& completionHandler)
{
RefPtr destinationExtension = extensionController()->extensionContext(extensionID);
if (!destinationExtension) {
completionHandler(std::nullopt, @"extension not found, but do not report this error");
return;
}

RefPtr tab = getTab(senderParameters.pageProxyIdentifier);
if (!tab) {
completionHandler(std::nullopt, @"tab not found");
return;
}

WebExtensionMessageSenderParameters completeSenderParameters = senderParameters;
completeSenderParameters.tabParameters = tab->parameters();

auto url = completeSenderParameters.url;
auto validMatchPatterns = destinationExtension->extension().externallyConnectableMatchPatterns();
if (!hasPermission(url, tab.get()) || !WebExtensionMatchPattern::patternsMatchURL(validMatchPatterns, url)) {
completionHandler(std::nullopt, @"extension does not have access to this page, but do not reprort this error");
return;
}

auto mainWorldProcesses = processes(WebExtensionEventListenerType::RuntimeOnMessageExternal, WebExtensionContentWorldType::Main);
if (mainWorldProcesses.isEmpty()) {
completionHandler(std::nullopt, std::nullopt);
return;
}

auto callbackAggregator = EagerCallbackAggregator<ReplyCompletionHandlerSignature>::create(WTFMove(completionHandler), std::nullopt, std::nullopt);

for (auto& process : mainWorldProcesses) {
process->sendWithAsyncReply(Messages::WebExtensionContextProxy::DispatchRuntimeMessageEvent(WebExtensionContentWorldType::Main, messageJSON, std::nullopt, completeSenderParameters), [callbackAggregator](std::optional<String> replyJSON) {
callbackAggregator.get()(replyJSON, std::nullopt);
}, identifier());
}
}

void WebExtensionContext::runtimeWebPageConnect(const String& extensionID, WebExtensionPortChannelIdentifier channelIdentifier, const String& name, const WebExtensionMessageSenderParameters& senderParameters, CompletionHandler<void(WebExtensionTab::Error)>&& completionHandler)
{
RefPtr destinationExtension = extensionController()->extensionContext(extensionID);
if (!destinationExtension) {
completionHandler(@"extension not found, but do not report this error");
return;
}

RefPtr tab = getTab(senderParameters.pageProxyIdentifier);
if (!tab) {
completionHandler(@"tab not found");
return;
}

WebExtensionMessageSenderParameters completeSenderParameters = senderParameters;
completeSenderParameters.tabParameters = tab->parameters();

auto url = completeSenderParameters.url;
auto validMatchPatterns = destinationExtension->extension().externallyConnectableMatchPatterns();
if (!hasPermission(url, tab.get()) || !WebExtensionMatchPattern::patternsMatchURL(validMatchPatterns, url)) {
completionHandler(@"extension does not have access to this page, but do not reprort this error");
return;
}

constexpr auto sourceContentWorldType = WebExtensionContentWorldType::WebPage;
constexpr auto targetContentWorldType = WebExtensionContentWorldType::Main;

// Add 1 for the starting port here so disconnect will balance with a decrement.
addPorts(sourceContentWorldType, channelIdentifier, 1);

auto mainWorldProcesses = processes(WebExtensionEventListenerType::RuntimeOnConnectExternal, targetContentWorldType);
if (mainWorldProcesses.isEmpty()) {
completionHandler(toErrorString(@"runtime.connect()", nil, @"no runtime.onConnectExternal listeners found"));
return;
}

size_t handledCount = 0;
size_t totalExpected = mainWorldProcesses.size();

for (auto& process : mainWorldProcesses) {
process->sendWithAsyncReply(Messages::WebExtensionContextProxy::DispatchRuntimeConnectEvent(targetContentWorldType, channelIdentifier, name, std::nullopt, completeSenderParameters), [=, &handledCount, protectedThis = Ref { *this }](size_t firedEventCount) mutable {
protectedThis->addPorts(targetContentWorldType, channelIdentifier, firedEventCount);
protectedThis->fireQueuedPortMessageEventsIfNeeded(process, targetContentWorldType, channelIdentifier);
protectedThis->firePortDisconnectEventIfNeeded(sourceContentWorldType, targetContentWorldType, channelIdentifier);
if (++handledCount >= totalExpected)
protectedThis->clearQueuedPortMessages(targetContentWorldType, channelIdentifier);
}, identifier());
}

completionHandler(std::nullopt);
}

void WebExtensionContext::fireRuntimeStartupEventIfNeeded()
{
// The background content is assumed to be loaded for this event.
Expand Down
Loading

0 comments on commit b69952d

Please sign in to comment.