Skip to content
Permalink
Browse files
Web Inspector: Implement frameURL option for `devtools.inspectedWin…
…dow.eval` command

https://bugs.webkit.org/show_bug.cgi?id=222568

Reviewed by Devin Rousso.

Source/WebInspectorUI:

New test: Tools/TestWebKitAPI/Tests/WebKitCocoa/WKInspectorExtension.mm

Add support for evaluating script from an extension in a specific frame on the page by referring to it by the
frame's URL. Frame URLs are matched in three steps, first looking for an exact URL match, including query
parameters and fragment identifier. If no match is found and the provided `options.frameURL` does not have any
fragment identifier or query parameters, a check is then made against each known frame again, this time
excluding their fragment identifier. If that check still fails to find a frame for the URL, we perform one more
pass, this time excluding the fragment identifier and query parameters for each known frame.

* UserInterface/Controllers/WebInspectorExtensionController.js:
(WI.WebInspectorExtensionController.prototype.evaluateScriptForExtension):
(WI.WebInspectorExtensionController.prototype.reloadForExtension):
- Drive-by removal of trailing spaces.
(WI.WebInspectorExtensionController.prototype._frameForFrameURL):

Source/WebKit:

New test: Tools/TestWebKitAPI/Tests/WebKitCocoa/WKInspectorExtension.mm

Correct the API to indicate that `frameURL` and `contextSecurityOrigin` are nullable parameters.

* UIProcess/API/Cocoa/_WKInspectorExtension.h:

Tools:

Add test coverage for evaluating script on an inspected page from an extension, including evaluating on an inner
frame.

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebKitCocoa/WKInspectorExtension.mm:
(TEST):
* TestWebKitAPI/Tests/WebKitCocoa/WKInspectorExtensionEvaluateScriptOnPage.html: Added.
* TestWebKitAPI/Tests/WebKitCocoa/WKInspectorExtensionEvaluateScriptOnPageInnerFrame.html: Added.



Canonical link: https://commits.webkit.org/246008@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@287979 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
patrickangle committed Jan 13, 2022
1 parent 23258a6 commit ae93002ab336e62b8816f9f3e73d8188d3257330
@@ -1,3 +1,25 @@
2022-01-13 Patrick Angle <pangle@apple.com>

Web Inspector: Implement `frameURL` option for `devtools.inspectedWindow.eval` command
https://bugs.webkit.org/show_bug.cgi?id=222568

Reviewed by Devin Rousso.

New test: Tools/TestWebKitAPI/Tests/WebKitCocoa/WKInspectorExtension.mm

Add support for evaluating script from an extension in a specific frame on the page by referring to it by the
frame's URL. Frame URLs are matched in three steps, first looking for an exact URL match, including query
parameters and fragment identifier. If no match is found and the provided `options.frameURL` does not have any
fragment identifier or query parameters, a check is then made against each known frame again, this time
excluding their fragment identifier. If that check still fails to find a frame for the URL, we perform one more
pass, this time excluding the fragment identifier and query parameters for each known frame.

* UserInterface/Controllers/WebInspectorExtensionController.js:
(WI.WebInspectorExtensionController.prototype.evaluateScriptForExtension):
(WI.WebInspectorExtensionController.prototype.reloadForExtension):
- Drive-by removal of trailing spaces.
(WI.WebInspectorExtensionController.prototype._frameForFrameURL):

2022-01-12 Elliott Williams <emw@apple.com>

[Xcode] Configure each project for the legacy build system
@@ -133,10 +133,10 @@ WI.WebInspectorExtensionController = class WebInspectorExtensionController exten
return WI.WebInspectorExtension.ErrorCode.InvalidRequest;
}

// FIXME: <rdar://problem/74180355> implement execution context selection options
if (frameURL) {
WI.reportInternalError("evaluateScriptForExtension: the 'frameURL' option is not yet implemented.");
return WI.WebInspectorExtension.ErrorCode.NotImplemented;
let frame = this._frameForFrameURL(frameURL);
if (!frame) {
WI.reportInternalError("evaluateScriptForExtension: No frame matched provided frameURL: " + frameURL);
return WI.WebInspectorExtension.ErrorCode.InvalidRequest;
}

if (contextSecurityOrigin) {
@@ -149,7 +149,12 @@ WI.WebInspectorExtensionController = class WebInspectorExtensionController exten
return WI.WebInspectorExtension.ErrorCode.NotImplemented;
}

let evaluationContext = WI.runtimeManager.activeExecutionContext;
let evaluationContext = frame.pageExecutionContext;
if (!evaluationContext) {
WI.reportInternalError("evaluateScriptForExtension: No 'pageExecutionContext' was present for frame with URL: " + frame.url);
return WI.WebInspectorExtension.ErrorCode.ContextDestroyed;
}

return evaluationContext.target.RuntimeAgent.evaluate.invoke({
expression: scriptSource,
objectGroup: "extension-evaluation",
@@ -165,7 +170,7 @@ WI.WebInspectorExtensionController = class WebInspectorExtensionController exten
return wasThrown ? {"error": resultOrError.description} : {"result": value};
}).catch((error) => error.description);
}

reloadForExtension(extensionID, {ignoreCache, userAgent, injectedScript} = {})
{
let extension = this._extensionForExtensionIDMap.get(extensionID);
@@ -184,14 +189,14 @@ WI.WebInspectorExtensionController = class WebInspectorExtensionController exten
WI.reportInternalError("reloadForExtension: the 'injectedScript' option is not yet implemented.");
return WI.WebInspectorExtension.ErrorCode.NotImplemented;
}

let target = WI.assumingMainTarget();
if (!target.hasCommand("Page.reload"))
return WI.WebInspectorExtension.ErrorCode.InvalidRequest;

return target.PageAgent.reload.invoke({ignoreCache});
}

showExtensionTab(extensionTabID, options = {})
{
let tabContentView = this._extensionTabContentViewForExtensionTabIDMap.get(extensionTabID);
@@ -393,6 +398,42 @@ WI.WebInspectorExtensionController = class WebInspectorExtensionController exten
return {anchorTabType, anchorTabIndex, distanceFromAnchorTab};
}

_frameForFrameURL(frameURL)
{
if (!frameURL)
return WI.networkManager.mainFrame;

function findFrame(frameURL, adjustKnownFrameURL) {
return WI.networkManager.frames.find((knownFrame) => {
let knownFrameURL = new URL(knownFrame.url);
adjustKnownFrameURL?.(knownFrameURL);
return knownFrameURL.toString() === frameURL;
});
}

let frame = findFrame(frameURL);
if (frame)
return frame;

let frameURLParts = new URL(frameURL);
if (frameURLParts.hash.length)
return null;

frame = findFrame(frameURL, (knownFrameURL) => {
knownFrameURL.hash = "";
});
if (frame)
return frame;

if (frameURLParts.search.length)
return null;

return findFrame(frameURL, (knownFrameURL) => {
knownFrameURL.hash = "";
knownFrameURL.search = "";
});
}

_handleMainResourceDidChange(event)
{
if (!event.target.isMainFrame())
@@ -1,3 +1,16 @@
2022-01-13 Patrick Angle <pangle@apple.com>

Web Inspector: Implement `frameURL` option for `devtools.inspectedWindow.eval` command
https://bugs.webkit.org/show_bug.cgi?id=222568

Reviewed by Devin Rousso.

New test: Tools/TestWebKitAPI/Tests/WebKitCocoa/WKInspectorExtension.mm

Correct the API to indicate that `frameURL` and `contextSecurityOrigin` are nullable parameters.

* UIProcess/API/Cocoa/_WKInspectorExtension.h:

2022-01-12 John Wilander <wilander@apple.com>

PCM: Same-site triggering events should support ephemeral measurement
@@ -63,7 +63,7 @@ WK_CLASS_AVAILABLE(macos(12.0))
* scriptSource is treated as a top-level evaluation. By default, the script is evaluated in the inspected page's script context.
* The inspected page ultimately controls its execution context and the result of this evaluation. Thus, the result shall be treated as untrusted input.
*/
- (void)evaluateScript:(NSString *)scriptSource frameURL:(NSURL *)frameURL contextSecurityOrigin:(NSURL *)contextSecurityOrigin useContentScriptContext:(BOOL)useContentScriptContext completionHandler:(void(^)(NSError * _Nullable, id result))completionHandler;
- (void)evaluateScript:(NSString *)scriptSource frameURL:(NSURL * _Nullable)frameURL contextSecurityOrigin:(NSURL * _Nullable)contextSecurityOrigin useContentScriptContext:(BOOL)useContentScriptContext completionHandler:(void(^)(NSError * _Nullable, id result))completionHandler;

/**
* @abstract Evaluates JavaScript in the context of a Web Inspector tab created by this _WKInspectorExtension.
@@ -1,3 +1,19 @@
2022-01-13 Patrick Angle <pangle@apple.com>

Web Inspector: Implement `frameURL` option for `devtools.inspectedWindow.eval` command
https://bugs.webkit.org/show_bug.cgi?id=222568

Reviewed by Devin Rousso.

Add test coverage for evaluating script on an inspected page from an extension, including evaluating on an inner
frame.

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebKitCocoa/WKInspectorExtension.mm:
(TEST):
* TestWebKitAPI/Tests/WebKitCocoa/WKInspectorExtensionEvaluateScriptOnPage.html: Added.
* TestWebKitAPI/Tests/WebKitCocoa/WKInspectorExtensionEvaluateScriptOnPageInnerFrame.html: Added.

2022-01-12 Jonathan Bedard <jbedard@apple.com>

[EWS] Load contributors from stand-alone class
@@ -169,6 +169,8 @@
2EB29D5E1F762DB90023A5F1 /* dump-datatransfer-types.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 2EB29D5D1F762DA50023A5F1 /* dump-datatransfer-types.html */; };
2EBD9D0A2134730D002DA758 /* video.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 07CD32F72065B72A0064A4BE /* video.html */; };
2EC7034A26AF5E88002B2D37 /* KeyboardEventTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2EC7034926AF5E88002B2D37 /* KeyboardEventTests.mm */; };
2ED88823277125AB00DB7E99 /* WKInspectorExtensionEvaluateScriptOnPage.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 2ED8882127711F1200DB7E99 /* WKInspectorExtensionEvaluateScriptOnPage.html */; };
2ED88824277125AB00DB7E99 /* WKInspectorExtensionEvaluateScriptOnPageInnerFrame.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 2ED888222771203A00DB7E99 /* WKInspectorExtensionEvaluateScriptOnPageInnerFrame.html */; };
2EFF06C31D88621E0004BB30 /* large-video-offscreen.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 2EFF06C21D8862120004BB30 /* large-video-offscreen.html */; };
2EFF06C51D8867760004BB30 /* change-video-source-on-click.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 2EFF06C41D8867700004BB30 /* change-video-source-on-click.html */; };
2EFF06C71D886A580004BB30 /* change-video-source-on-end.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 2EFF06C61D886A560004BB30 /* change-video-source-on-end.html */; };
@@ -1634,6 +1636,8 @@
CE14F1A4181873B0001C2705 /* WillPerformClientRedirectToURLCrash.html in Copy Resources */,
468F2F942368DAF100F4B864 /* window-open-then-document-open.html in Copy Resources */,
A5E2027515B21F6E00C13E14 /* WindowlessWebViewWithMedia.html in Copy Resources */,
2ED88823277125AB00DB7E99 /* WKInspectorExtensionEvaluateScriptOnPage.html in Copy Resources */,
2ED88824277125AB00DB7E99 /* WKInspectorExtensionEvaluateScriptOnPageInnerFrame.html in Copy Resources */,
);
name = "Copy Resources";
runOnlyForDeploymentPostprocessing = 0;
@@ -1857,6 +1861,8 @@
2EB29D5D1F762DA50023A5F1 /* dump-datatransfer-types.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "dump-datatransfer-types.html"; sourceTree = "<group>"; };
2EC7034926AF5E88002B2D37 /* KeyboardEventTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyboardEventTests.mm; sourceTree = "<group>"; };
2ECFF5541D9B12F800B55394 /* NowPlayingControlsTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = NowPlayingControlsTests.mm; sourceTree = "<group>"; };
2ED8882127711F1200DB7E99 /* WKInspectorExtensionEvaluateScriptOnPage.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = WKInspectorExtensionEvaluateScriptOnPage.html; sourceTree = "<group>"; };
2ED888222771203A00DB7E99 /* WKInspectorExtensionEvaluateScriptOnPageInnerFrame.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = WKInspectorExtensionEvaluateScriptOnPageInnerFrame.html; sourceTree = "<group>"; };
2EFF06C21D8862120004BB30 /* large-video-offscreen.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "large-video-offscreen.html"; sourceTree = "<group>"; };
2EFF06C41D8867700004BB30 /* change-video-source-on-click.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "change-video-source-on-click.html"; sourceTree = "<group>"; };
2EFF06C61D886A560004BB30 /* change-video-source-on-end.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "change-video-source-on-end.html"; sourceTree = "<group>"; };
@@ -4252,6 +4258,8 @@
51714EB31CF8C761004723C4 /* WebProcessKillIDBCleanup-2.html */,
5120C83B1E674E350025B250 /* WebsiteDataStoreCustomPaths.html */,
2E131C171D83A97E001BA36C /* wide-autoplaying-video-with-audio.html */,
2ED8882127711F1200DB7E99 /* WKInspectorExtensionEvaluateScriptOnPage.html */,
2ED888222771203A00DB7E99 /* WKInspectorExtensionEvaluateScriptOnPageInnerFrame.html */,
);
name = Resources;
sourceTree = "<group>";
@@ -30,6 +30,7 @@
#import "DeprecatedGlobalValues.h"
#import "TestCocoa.h"
#import "TestInspectorURLSchemeHandler.h"
#import "TestNavigationDelegate.h"
#import "Utilities.h"
#import <WebKit/WKPreferencesPrivate.h>
#import <WebKit/WKWebViewPrivate.h>
@@ -382,4 +383,127 @@ - (void)inspectorExtension:(_WKInspectorExtension *)extension didHideTabWithIden
TestWebKitAPI::Util::run(&pendingCallbackWasCalled);
}

TEST(WKInspectorExtension, EvaluateScriptOnPage)
{
resetGlobalState();

auto webViewConfiguration = adoptNS([WKWebViewConfiguration new]);
webViewConfiguration.get().preferences._developerExtrasEnabled = YES;
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectMake(0, 0, 800, 600) configuration:webViewConfiguration.get()]);
auto uiDelegate = adoptNS([UIDelegateForTestingInspectorExtension new]);
auto navigationDelegate = adoptNS([[TestNavigationDelegate alloc] init]);

auto *testPageFileURL = [NSBundle.mainBundle URLForResource:@"WKInspectorExtensionEvaluateScriptOnPage" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];

[webView setUIDelegate:uiDelegate.get()];
[webView setNavigationDelegate:navigationDelegate.get()];
[webView loadFileURL:testPageFileURL allowingReadAccessToURL:testPageFileURL.URLByDeletingLastPathComponent];
[navigationDelegate waitForDidFinishNavigation];

[[webView _inspector] show];
TestWebKitAPI::Util::run(&didAttachLocalInspectorCalled);

auto extensionID = [NSUUID UUID].UUIDString;
auto extensionBundleIdentifier = @"org.webkit.TestWebKitAPI.FourthExtension";
auto extensionDisplayName = @"FourthExtension";

// Register the test extension.
pendingCallbackWasCalled = false;
[[webView _inspector] registerExtensionWithID:extensionID extensionBundleIdentifier:extensionBundleIdentifier displayName:extensionDisplayName completionHandler:^(NSError *error, _WKInspectorExtension *extension) {
EXPECT_NULL(error);
EXPECT_NOT_NULL(extension);
sharedInspectorExtension = extension;

pendingCallbackWasCalled = true;
}];
TestWebKitAPI::Util::run(&pendingCallbackWasCalled);

auto extensionDelegate = adoptNS([InspectorExtensionDelegateForTestingInspectorExtension new]);
[sharedInspectorExtension setDelegate:extensionDelegate.get()];

// Create and show an extension tab.
auto iconURL = [NSURL URLWithString:@"test-resource://FourthExtension/InspectorExtension-TabIcon-30x30.png"];
auto sourceURL = [NSURL URLWithString:@"test-resource://FourthExtension/InspectorExtension-basic-tab.html"];

pendingCallbackWasCalled = false;
[sharedInspectorExtension createTabWithName:@"FourthExtension-Tab" tabIconURL:iconURL sourceURL:sourceURL completionHandler:^(NSError *error, NSString *extensionTabIdentifier) {
EXPECT_NULL(error);
EXPECT_NOT_NULL(extensionTabIdentifier);
sharedExtensionTabIdentifier = extensionTabIdentifier;

pendingCallbackWasCalled = true;
}];
TestWebKitAPI::Util::run(&pendingCallbackWasCalled);

pendingCallbackWasCalled = false;
didShowExtensionTabWasCalled = false;
[[webView _inspector] showExtensionTabWithIdentifier:sharedExtensionTabIdentifier.get() completionHandler:^(NSError *error) {
EXPECT_NULL(error);

pendingCallbackWasCalled = true;
}];
TestWebKitAPI::Util::run(&pendingCallbackWasCalled);
TestWebKitAPI::Util::run(&didShowExtensionTabWasCalled);

auto mainFrameSecretString = @"42-mainFrame";
auto innerFrameSecretString = @"42-innerFrame";
auto scriptSource = @"document.getElementById('secret').innerText";

// Test main frame evaluation.
pendingCallbackWasCalled = false;
[sharedInspectorExtension evaluateScript:scriptSource frameURL:nil contextSecurityOrigin:nil useContentScriptContext:false completionHandler:^(NSError *error, NSDictionary *result) {
EXPECT_NULL(error);
EXPECT_NOT_NULL(result);
EXPECT_NS_EQUAL(result, mainFrameSecretString);

pendingCallbackWasCalled = true;
}];
TestWebKitAPI::Util::run(&pendingCallbackWasCalled);

// Test main frame evaluation failure.
pendingCallbackWasCalled = false;
[sharedInspectorExtension evaluateScript:@"[].x.x" frameURL:nil contextSecurityOrigin:nil useContentScriptContext:false completionHandler:^(NSError *error, NSDictionary *result) {
EXPECT_NULL(result);
EXPECT_NOT_NULL(error);

pendingCallbackWasCalled = true;
}];
TestWebKitAPI::Util::run(&pendingCallbackWasCalled);

// Test loose frameURL evaluation.
auto *testPageInnerFrameFileURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@WKInspectorExtensionEvaluateScriptOnPageInnerFrame.html", [[testPageFileURL URLByDeletingLastPathComponent] absoluteString]]];

pendingCallbackWasCalled = false;
[sharedInspectorExtension evaluateScript:scriptSource frameURL:testPageInnerFrameFileURL contextSecurityOrigin:nil useContentScriptContext:false completionHandler:^(NSError *error, NSDictionary *result) {
EXPECT_NULL(error);
EXPECT_NOT_NULL(result);
EXPECT_NS_EQUAL(result, innerFrameSecretString);

pendingCallbackWasCalled = true;
}];
TestWebKitAPI::Util::run(&pendingCallbackWasCalled);

// Test strict frameURL evaluation.
auto *testPageInnerFrameStrictFileURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@?query=param#fragment", [testPageInnerFrameFileURL absoluteString]]];

pendingCallbackWasCalled = false;
[sharedInspectorExtension evaluateScript:scriptSource frameURL:testPageInnerFrameStrictFileURL contextSecurityOrigin:nil useContentScriptContext:false completionHandler:^(NSError *error, NSDictionary *result) {
EXPECT_NULL(error);
EXPECT_NOT_NULL(result);
EXPECT_NS_EQUAL(result, innerFrameSecretString);

pendingCallbackWasCalled = true;
}];
TestWebKitAPI::Util::run(&pendingCallbackWasCalled);

// Unregister the test extension.
pendingCallbackWasCalled = false;
[[webView _inspector] unregisterExtension:sharedInspectorExtension.get() completionHandler:^(NSError * error) {
EXPECT_NULL(error);

pendingCallbackWasCalled = true;
}];
TestWebKitAPI::Util::run(&pendingCallbackWasCalled);
}

#endif // ENABLE(INSPECTOR_EXTENSIONS)
@@ -0,0 +1,8 @@
<html>
<head>Test page to be inspected</head>
<body>
<p>Test page to be inspected.</p>
<p id="secret">42-mainFrame</p>
<iframe src="./WKInspectorExtensionEvaluateScriptOnPageInnerFrame.html?query=param#fragment"></iframe>
</body>
</html>
@@ -0,0 +1,7 @@
<html>
<head>Test inner frame to be inspected</head>
<body>
<p>Test inner frame to be inspected.</p>
<p id="secret">42-innerFrame</p>
</body>
</html>

0 comments on commit ae93002

Please sign in to comment.