Skip to content

Commit

Permalink
Add support for browser.tabs script APIs.
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=262816

Reviewed by Timothy Hatcher.

This patch implements the tabs.executeScript(), tabs.insertCSS(), and tabs.removeCSS() APIs.

Testing:
- Calls to methods with an invalid tab results in an error.
- A result is returned from for each frame for scripting.executeScript().
- Injections execute in the top frame only if allFrames isn't specified or is false.

Will add more extensive tests in a follow up PR.

* Source/WebKit/Shared/Extensions/WebExtensionDynamicScripts.serialization.in:
* Source/WebKit/Shared/Extensions/WebExtensionScriptInjectionParameters.h:
* Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPITabsCocoa.mm:
(WebKit::WebExtensionContext::tabsExecuteScript):
(WebKit::WebExtensionContext::tabsInsertCSS):
(WebKit::WebExtensionContext::tabsRemoveCSS):
* Source/WebKit/UIProcess/Extensions/Cocoa/WebExtensionDynamicScriptsCocoa.mm:
(WebKit::WebExtensionDynamicScripts::toInjectionResultParameters):
* Source/WebKit/UIProcess/Extensions/WebExtensionContext.h:
* Source/WebKit/UIProcess/Extensions/WebExtensionContext.messages.in:
* Source/WebKit/UIProcess/Extensions/WebExtensionDynamicScripts.h:
(WebKit::WebExtensionDynamicScripts::InjectionResultHolder::create):
(WebKit::WebExtensionDynamicScripts::InjectionResultHolder::InjectionResultHolder):
Created a ref counted class to hold the results of the script injections so that the data is still
valid when the callback aggregator is called.
* Source/WebKit/WebProcess/Extensions/API/Cocoa/WebExtensionAPITabsCocoa.mm:
(WebKit::toWebAPI):
(WebKit::WebExtensionAPITabs::parseScriptOptions):
(WebKit::WebExtensionAPITabs::executeScript):
(WebKit::WebExtensionAPITabs::insertCSS):
(WebKit::WebExtensionAPITabs::removeCSS):
* Source/WebKit/WebProcess/Extensions/API/WebExtensionAPITabs.h:
* Source/WebKit/WebProcess/Extensions/Interfaces/WebExtensionAPITabs.idl:

Canonical link: https://commits.webkit.org/269028@main
  • Loading branch information
kiaraarose authored and xeenon committed Oct 7, 2023
1 parent 4a0b130 commit ea26ecd
Show file tree
Hide file tree
Showing 10 changed files with 307 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ struct WebKit::WebExtensionScriptInjectionParameters {
std::optional<Vector<String>> files;
std::optional<Vector<WebKit::WebExtensionFrameIdentifier>> frameIDs;

std::optional<String> code;
std::optional<String> css;
std::optional<String> function;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ struct WebExtensionScriptInjectionParameters {
std::optional<Vector<String>> files;
std::optional<Vector<WebExtensionFrameIdentifier>> frameIDs;

std::optional<String> code;
std::optional<String> css;
std::optional<String> function;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,28 @@
#if ENABLE(WK_WEB_EXTENSIONS)

#import "CocoaHelpers.h"
#import "WKContentWorld.h"
#import "WKWebViewInternal.h"
#import "WKWebViewPrivate.h"
#import "WebExtensionContextProxy.h"
#import "WebExtensionContextProxyMessages.h"
#import "WebExtensionScriptInjectionParameters.h"
#import "WebExtensionScriptInjectionResultParameters.h"
#import "WebExtensionTabIdentifier.h"
#import "WebExtensionUtilities.h"
#import "WebExtensionWindowIdentifier.h"
#import "WebPageProxy.h"
#import "_WKFrameHandle.h"
#import "_WKFrameTreeNode.h"
#import "_WKWebExtensionControllerDelegatePrivate.h"
#import "_WKWebExtensionTabCreationOptionsInternal.h"
#import <WebCore/ImageBufferUtilitiesCG.h>
#import <wtf/CallbackAggregator.h>

namespace WebKit {

using namespace WebExtensionDynamicScripts;

void WebExtensionContext::tabsCreate(WebPageProxyIdentifier webPageProxyIdentifier, const WebExtensionTabParameters& parameters, CompletionHandler<void(std::optional<WebExtensionTabParameters>, WebExtensionTab::Error)>&& completionHandler)
{
ASSERT(!parameters.audible);
Expand Down Expand Up @@ -542,6 +551,119 @@ static inline String toMIMEType(WebExtensionTab::ImageFormat format)
}
}

void WebExtensionContext::tabsExecuteScript(WebPageProxyIdentifier webPageProxyIdentifier, std::optional<WebExtensionTabIdentifier> tabIdentifier, const WebExtensionScriptInjectionParameters& parameters, CompletionHandler<void(std::optional<InjectionResults>, WebExtensionTab::Error)>&& completionHandler)
{
auto tab = getTab(webPageProxyIdentifier, tabIdentifier);
if (!tab) {
completionHandler(std::nullopt, toErrorString(@"tabs.executeScript()", nil, @"tab not found"));
return;
}

auto *webView = tab->mainWebView();
if (!webView) {
completionHandler(std::nullopt, toErrorString(@"tabs.executeScript()", nil, @"could not execute script in tab"));
return;
}

if (!hasPermission(webView.URL, tab.get())) {
completionHandler(std::nullopt, toErrorString(@"tabs.executeScript()", nil, @"this extension does not have access to this tab"));
return;
}

std::optional<SourcePair> scriptData;
if (parameters.code)
scriptData = SourcePair { parameters.code.value(), std::nullopt };
else {
NSString *filePath = parameters.files.value().first();
scriptData = sourcePairForResource(filePath, m_extension);
if (!scriptData) {
completionHandler(std::nullopt, toErrorString(@"tabs.executeScript()", nil, @"Invalid resource: %@", filePath));
return;
}
}

auto injectionResults = InjectionResultHolder::create();
auto aggregator = MainRunLoopCallbackAggregator::create([injectionResults, completionHandler = WTFMove(completionHandler)]() mutable {
completionHandler(injectionResults->results, std::nullopt);
});

[webView _frames:makeBlockPtr([this, protectedThis = Ref { *this }, webView = RetainPtr { webView }, tab, injectionResults, aggregator, parameters](_WKFrameTreeNode *mainFrame) mutable {
SourcePairs scriptPairs = getSourcePairsForResource(parameters.files, parameters.code, m_extension);
Vector<RetainPtr<_WKFrameTreeNode>> frames = getFrames(mainFrame, parameters.frameIDs);

for (auto& frame : frames) {
WKFrameInfo *info = frame.get().info;
NSURL *frameURL = info.request.URL;
if (!hasPermission(frameURL, tab.get())) {
injectionResults->results.append(toInjectionResultParameters(nil, nil, nil));
continue;
}

for (auto& script : scriptPairs) {
[webView _evaluateJavaScript:script.value().first withSourceURL:script.value().second.value_or(URL { }) inFrame:info inContentWorld:m_contentScriptWorld.get()->wrapper() completionHandler:makeBlockPtr([injectionResults, aggregator](id resultOfExecution, NSError *error) mutable {
injectionResults->results.append(toInjectionResultParameters(resultOfExecution, nil, error.localizedDescription));
}).get()];
}
}
}).get()];
}

void WebExtensionContext::tabsInsertCSS(WebPageProxyIdentifier webPageProxyIdentifier, std::optional<WebExtensionTabIdentifier> tabIdentifier, const WebExtensionScriptInjectionParameters& parameters, CompletionHandler<void(WebExtensionTab::Error)>&& completionHandler)
{
auto tab = getTab(webPageProxyIdentifier, tabIdentifier);
if (!tab) {
completionHandler(toErrorString(@"tabs.insertCSS()", nil, @"tab not found"));
return;
}

auto *webView = tab->mainWebView();
if (!webView) {
completionHandler(toErrorString(@"tabs.insertCSS()", nil, @"could not inject stylesheet on this tab"));
return;
}

if (!hasPermission(webView.URL, tab.get())) {
completionHandler(toErrorString(@"tabs.insertCSS()", nil, @"this extension does not have access to this tab"));
return;
}

// FIXME: <https://webkit.org/b/262491> There is currently no way to inject CSS in specific frames based on ID's. If 'frameIds' is passed, default to the main frame.
auto injectedFrames = parameters.frameIDs ? WebCore::UserContentInjectedFrames::InjectInTopFrameOnly : WebCore::UserContentInjectedFrames::InjectInAllFrames;

SourcePairs styleSheetPairs = getSourcePairsForResource(parameters.files, parameters.code, m_extension);
injectStyleSheets(styleSheetPairs, webView, *m_contentScriptWorld, injectedFrames, *this);

completionHandler(std::nullopt);
}

void WebExtensionContext::tabsRemoveCSS(WebPageProxyIdentifier webPageProxyIdentifier, std::optional<WebExtensionTabIdentifier> tabIdentifier, const WebExtensionScriptInjectionParameters& parameters, CompletionHandler<void(WebExtensionTab::Error)>&& completionHandler)
{
auto tab = getTab(webPageProxyIdentifier, tabIdentifier);
if (!tab) {
completionHandler(toErrorString(@"tabs.removeCSS()", nil, @"tab not found"));
return;
}

auto *webView = tab->mainWebView();
if (!webView) {
completionHandler(toErrorString(@"tabs.removeCSS()", nil, @"could not remove stylesheet on this tab"));
return;
}

if (!hasPermission(webView.URL, tab.get())) {
completionHandler(toErrorString(@"tabs.removeCSS()", nil, @"this extension does not have access to this tab"));
return;
}

// FIXME: <https://webkit.org/b/262491> There is currently no way to inject CSS in specific frames based on ID's. If 'frameIds' is passed, default to the main frame.
auto injectedFrames = parameters.frameIDs ? WebCore::UserContentInjectedFrames::InjectInTopFrameOnly : WebCore::UserContentInjectedFrames::InjectInAllFrames;

SourcePairs styleSheetPairs = getSourcePairsForResource(parameters.files, parameters.code, m_extension);
removeStyleSheets(styleSheetPairs, injectedFrames, *this);

completionHandler(std::nullopt);
}

void WebExtensionContext::fireTabsCreatedEventIfNeeded(const WebExtensionTabParameters& parameters)
{
constexpr auto type = WebExtensionEventListenerType::TabsOnCreated;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,22 @@ void removeStyleSheets(SourcePairs styleSheetPairs, WebCore::UserContentInjected
}
}

WebExtensionScriptInjectionResultParameters toInjectionResultParameters(id resultOfExecution, WKFrameInfo *info, NSString *errorMessage)
{
WebExtensionScriptInjectionResultParameters parameters;

if (resultOfExecution)
parameters.result = resultOfExecution;

if (info)
parameters.frameID = toWebExtensionFrameIdentifier(info);

if (errorMessage)
parameters.error = errorMessage;

return parameters;
}

} // namespace WebExtensionDynamicScripts

} // namespace WebKit
Expand Down
3 changes: 3 additions & 0 deletions Source/WebKit/UIProcess/Extensions/WebExtensionContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,9 @@ class WebExtensionContext : public API::ObjectImpl<API::Object::Type::WebExtensi
void tabsGetZoom(WebPageProxyIdentifier, std::optional<WebExtensionTabIdentifier>, CompletionHandler<void(std::optional<double>, WebExtensionTab::Error)>&&);
void tabsSetZoom(WebPageProxyIdentifier, std::optional<WebExtensionTabIdentifier>, double, CompletionHandler<void(WebExtensionTab::Error)>&&);
void tabsRemove(Vector<WebExtensionTabIdentifier>, CompletionHandler<void(WebExtensionTab::Error)>&&);
void tabsExecuteScript(WebPageProxyIdentifier, std::optional<WebExtensionTabIdentifier>, const WebExtensionScriptInjectionParameters&, CompletionHandler<void(std::optional<Vector<WebExtensionScriptInjectionResultParameters>>, WebExtensionTab::Error)>&&);
void tabsInsertCSS(WebPageProxyIdentifier, std::optional<WebExtensionTabIdentifier>, const WebExtensionScriptInjectionParameters&, CompletionHandler<void(WebExtensionTab::Error)>&&);
void tabsRemoveCSS(WebPageProxyIdentifier, std::optional<WebExtensionTabIdentifier>, const WebExtensionScriptInjectionParameters&, CompletionHandler<void(WebExtensionTab::Error)>&&);
void fireTabsCreatedEventIfNeeded(const WebExtensionTabParameters&);
void fireTabsUpdatedEventIfNeeded(const WebExtensionTabParameters&, const WebExtensionTabParameters& changedParameters);
void fireTabsReplacedEventIfNeeded(WebExtensionTabIdentifier replacedTabIdentifier, WebExtensionTabIdentifier newTabIdentifier);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ messages -> WebExtensionContext {
TabsGetZoom(WebKit::WebPageProxyIdentifier webPageProxyIdentifier, std::optional<WebKit::WebExtensionTabIdentifier> tabIdentifier) -> (std::optional<double> zoomFactor, WebKit::WebExtensionTab::Error error);
TabsSetZoom(WebKit::WebPageProxyIdentifier webPageProxyIdentifier, std::optional<WebKit::WebExtensionTabIdentifier> tabIdentifier, double zoomFactor) -> (WebKit::WebExtensionTab::Error error);
TabsRemove(Vector<WebKit::WebExtensionTabIdentifier> tabIdentifiers) -> (WebKit::WebExtensionWindow::Error error);
TabsExecuteScript(WebKit::WebPageProxyIdentifier webPageProxyIdentifier, std::optional<WebKit::WebExtensionTabIdentifier> tabIdentifier, WebKit::WebExtensionScriptInjectionParameters parameters) -> (std::optional<Vector<WebKit::WebExtensionScriptInjectionResultParameters>> results, WebKit::WebExtensionTab::Error error);
TabsInsertCSS(WebKit::WebPageProxyIdentifier webPageProxyIdentifier, std::optional<WebKit::WebExtensionTabIdentifier> tabIdentifier, WebKit::WebExtensionScriptInjectionParameters parameters) -> (WebKit::WebExtensionTab::Error error);
TabsRemoveCSS(WebKit::WebPageProxyIdentifier webPageProxyIdentifier, std::optional<WebKit::WebExtensionTabIdentifier> tabIdentifier, WebKit::WebExtensionScriptInjectionParameters parameters) -> (WebKit::WebExtensionTab::Error error);

// Test APIs
TestResult(bool result, String message, String sourceURL, unsigned lineNumber);
Expand Down
23 changes: 20 additions & 3 deletions Source/WebKit/UIProcess/Extensions/WebExtensionDynamicScripts.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

#include "APIContentWorld.h"
#include "WebExtensionFrameIdentifier.h"
#include "WebExtensionScriptInjectionResultParameters.h"
#include <wtf/Forward.h>

OBJC_CLASS WKWebView;
Expand All @@ -39,7 +40,6 @@ namespace WebKit {

class WebExtension;
class WebExtensionContext;
struct WebExtensionScriptInjectionResultParameters;

namespace WebExtensionDynamicScripts {

Expand All @@ -49,14 +49,31 @@ using Error = std::optional<String>;
using SourcePair = std::pair<String, std::optional<URL>>;
using SourcePairs = Vector<std::optional<SourcePair>>;

SourcePairs getSourcePairsForResource(std::optional<Vector<String>> files, std::optional<String> code, RefPtr<WebExtension>);
Vector<RetainPtr<_WKFrameTreeNode>> getFrames(_WKFrameTreeNode *, std::optional<Vector<WebExtensionFrameIdentifier>>);
class InjectionResultHolder : public RefCounted<InjectionResultHolder> {
WTF_MAKE_NONCOPYABLE(InjectionResultHolder);
WTF_MAKE_FAST_ALLOCATED;

public:
template<typename... Args>
static Ref<InjectionResultHolder> create(Args&&... args)
{
return adoptRef(*new InjectionResultHolder(std::forward<Args>(args)...));
}

InjectionResultHolder() { };

InjectionResults results;
};

std::optional<SourcePair> sourcePairForResource(String path, RefPtr<WebExtension>);
SourcePairs getSourcePairsForResource(std::optional<Vector<String>> files, std::optional<String> code, RefPtr<WebExtension>);
Vector<RetainPtr<_WKFrameTreeNode>> getFrames(_WKFrameTreeNode *, std::optional<Vector<WebExtensionFrameIdentifier>>);

void injectStyleSheets(SourcePairs, WKWebView *, API::ContentWorld&, WebCore::UserContentInjectedFrames, WebExtensionContext&);
void removeStyleSheets(SourcePairs, WebCore::UserContentInjectedFrames, WebExtensionContext&);

WebExtensionScriptInjectionResultParameters toInjectionResultParameters(id resultOfExecution, WKFrameInfo *, NSString *errorMessage);

} // namespace WebExtensionDynamicScripts

} // namespace WebKit
Expand Down
Loading

0 comments on commit ea26ecd

Please sign in to comment.