Skip to content

Commit

Permalink
Web Inspector: Sources: allow Response Local Overrides to map to a di…
Browse files Browse the repository at this point in the history
…rectory on disk

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

Reviewed by Alexey Proskuryakov.

This allows for developers who are trying to do the "simple" thing of "I have a local copy of my website on my computer and want to use those files instead" to do it with a single Local Override instead of having to create one for each file they want to replace.

* Source/WebInspectorUI/UserInterface/Models/LocalResourceOverride.js:
(WI.LocalResourceOverride.create):
(WI.LocalResourceOverride.displayNameForNetworkStageOfType):
(WI.LocalResourceOverride.displayNameForType):
(WI.LocalResourceOverride.prototype.get networkStage): Added.
(WI.LocalResourceOverride.prototype.get displayType): Added.
(WI.LocalResourceOverride.prototype.generateSubpathForMappedDirectory): Added.
(WI.LocalResourceOverride.prototype.get canMapToFile):
Introduce a new `WI.LocalResourceOverride.InterceptType.ResponseMappedDirectory` in order to make the logic of creating a Local Override that's mapped to a directory on disk easier.
This is because in order to do directory mapping, we cannot have per-request response MIME type, status code, status text, or headers. Instead, we derive what we can from the ultimate file loaded from disk (see below) and pass through everything else (unless the file on disk doesn't exist, in which case everything is passed through). As such, we also cannot support skipping the network.

* Source/WebInspectorUI/UserInterface/Controllers/NetworkManager.js:
(WI.NetworkManager):
(WI.NetworkManager.prototype.async requestIntercepted):
(WI.NetworkManager.prototype.async responseIntercepted):
(WI.NetworkManager.prototype._commandArgumentsForInterception):
(WI.NetworkManager.prototype._handleFrameMainResourceDidChange):
When intercepting requests using `WI.LocalResourceOverride.InterceptType.ResponseMappedDirectory`, attempt to load the corresponding file at
```
mappedFilePath + response.url.replace(localResourceOverride.url, localResourceOverride.localResource.url)
```
where the `response.url` is the intercepted URL, the `localResourceOverride.url` is the regex used to define the interception, and `localResourceOverride.localResource.url` is the subpath that uses capture groups from the `localResourceOverride.url`. If no file exists at that location, don't override the response. This allows for very general directory mapping without having to worry about every possible file needing to be defined inside that folder (and subfolders).
Drive-by: Fix incorrect `Network.Response.statusCode` to be `Network.Response.status`.

* Source/WebInspectorUI/UserInterface/Models/LocalResource.js:
(WI.LocalResource):
(WI.LocalResource.resetPathsThatFailedToLoadFromFileSystem): Added.
(WI.LocalResource.prototype.set mappedFilePath):
(WI.LocalResource.prototype.get isMappedToDirectory): Added.
(WI.LocalResource.prototype.async requestContentFromMappedDirectory): Added.
(WI.LocalResource.prototype.async requestContent):
(WI.LocalResource.prototype.async _loadFromFileSystem):
(WI.LocalResource.prototype.async _updateContentFromFileSystem): Added.
Expose a way to manually load content from a file on disk using the `mappedFilePath` (with a required `subpath` if it's a directory).

* Source/WebInspectorUI/UserInterface/Views/LocalResourceOverridePopover.js:
(WI.LocalResourceOverridePopover):
(WI.LocalResourceOverridePopover.prototype.get serializedData):
(WI.LocalResourceOverridePopover.prototype.show):
(WI.LocalResourceOverridePopover.prototype.show.createEditorId): Added.
(WI.LocalResourceOverridePopover.prototype.show.updateMappedDirectoryPath): Added.
(WI.LocalResourceOverridePopover.prototype.show.updateMappedFilePath): Added.
* Source/WebInspectorUI/UserInterface/Views/LocalResourceOverridePopover.css:
(.popover .local-resource-override-popover-content.request .editor:is(.url, .redirect), .popover .local-resource-override-popover-content.response-mapped-directory :is(.editor:is(.url, .mapped-directory-subpath), .mapped-directory-path)): Renamed from `.popover .local-resource-override-popover-content.request .editor:is(.url, .redirect)`.
(.popover .local-resource-override-popover-content.block .editor.url, .popover .local-resource-override-popover-content.response :is(.editor.url, .mapped-file-path)): Renamed from `(.popover .local-resource-override-popover-content:is(.response, .block) .editor.url`.
(.popover .local-resource-override-popover-content :is(.mapped-directory-path, .mapped-file-path)): Added.
(.popover .local-resource-override-popover-content :is(.mapped-directory-path, .mapped-file-path) > .value:not(:empty) + .placeholder): Added.
(.popover .local-resource-override-popover-content :is(.mapped-directory-path, .mapped-file-path) > button): Added.
Rework the "Type" dropdown to have "Response" be a group name containing "File" and "Directory", the former being the existing "Response" and the latter being the way to create a `WI.LocalResourceOverride.InterceptType.ResponseMappedDirectory`.
In order to successfully create a `WI.LocalResourceOverride.InterceptType.ResponseMappedDirectory` we must have a interception regex (with capture groups), a subpath string (using the capture groups), and an input to choose the mapped directory path.
Drive-by: Also add an input to choose a mapped file path when creating a "File" Local Override.

* Source/WebInspectorUI/UserInterface/Base/Main.js:
* Source/WebInspectorUI/UserInterface/Views/ContentView.js:
(WI.ContentView.resolvedRepresentedObjectForRepresentedObject):
* Source/WebInspectorUI/UserInterface/Views/ResourceContentView.js:
(WI.ResourceContentView):
(WI.ResourceContentView.prototype.async _handleMapLocalResourceOverrideToFile):
Handle the new `WI.LocalResourceOverride.InterceptType.ResponseMappedDirectory`.

* Source/WebInspectorUI/UserInterface/Models/Resource.js:
(WI.Resource.classNamesForResource):
* Source/WebInspectorUI/UserInterface/Views/ResourceTreeElement.js:
(WI.ResourceTreeElement.prototype._updateTitles):
(WI.ResourceTreeElement.prototype._updateIcon):
* Source/WebInspectorUI/UserInterface/Views/ResourceIcons.css:
(.resource-icon.override.mapped-file .icon): Added.
(body:not(.window-inactive, .window-docked-inactive) :is(.table, .data-grid):focus-within .selected .resource-icon.override.mapped-file .icon, body:not(.window-inactive, .window-docked-inactive) .tree-outline:focus-within .selected.resource-icon.override.mapped-file .icon): Added.
(@media (prefers-color-scheme: dark) .resource-icon.override.mapped-file .icon): Added.
Add an icon for when the Local Override is mapped to a file/folder.

* Source/WebInspectorUI/UserInterface/Base/FileUtilities.js:
(WI.FileUtilities.prototype.longestCommonPrefix): Added.
(WI.FileUtilities.prototype.import):
Add a way to set `webkitdirectory` in order to allow selecting a directory.

* Source/WebKit/UIProcess/Inspector/mac/WKInspectorViewController.mm:
(-[WKInspectorViewController webView:runOpenPanelWithParameters:initiatedByFrame:completionHandler:]):
Allow Web Inspector to upload directories if requested, such as for `<input type="file" multiple webkitdirectory>`.

* Source/WebInspectorUI/UserInterface/Images/Disk.svg:
Add light and dark variants.

* Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js:

* LayoutTests/TestExpectations:
* LayoutTests/http/tests/inspector/network/local-resource-override-mapped-to-directory.html: Added.
* LayoutTests/http/tests/inspector/network/local-resource-override-mapped-to-directory-expected.txt: Added.
* LayoutTests/http/tests/inspector/network/local-resource-override-mapped-to-file.html:
* LayoutTests/http/tests/inspector/network/local-resource-override-mapped-to-file-expected.txt:
* LayoutTests/platform/mac-wk1/TestExpectations:
* LayoutTests/platform/mac-wk2/TestExpectations:

Canonical link: https://commits.webkit.org/266821@main
  • Loading branch information
dcrousso committed Aug 11, 2023
1 parent f02cdaa commit d2d000b
Show file tree
Hide file tree
Showing 22 changed files with 712 additions and 68 deletions.
1 change: 1 addition & 0 deletions LayoutTests/TestExpectations
Original file line number Diff line number Diff line change
Expand Up @@ -1426,6 +1426,7 @@ inspector/page/overrideSetting-PrivateClickMeasurementDebugModeEnabled.html [ Sk
inspector/page/overrideSetting-ITPDebugModeEnabled.html [ Skip ]

# Network interception with a mapped file is only mac-wk2 for now.
http/tests/inspector/network/local-resource-override-mapped-to-directory.html [ Skip ]
http/tests/inspector/network/local-resource-override-mapped-to-file.html [ Skip ]

# Network throttling is currently only enabled on Cocoa platforms.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
Tests for LocalResourceOverride being mapped to a directory.


== Running test suite: LocalResourceOverride
-- Running test case: LocalResourceOverride.MappedToDirectory.Valid
Creating Local Resource Override for: http://127.0.0.1:8000/inspector/network/resources/(.+)
Linking to file...
Triggering load...
PASS: Resource Loaded.
Resource status code: 200
Resource status text: OK
Resource MIME: text/plain
Resource Content: 'PASS: Should load from file.'

-- Running test case: LocalResourceOverride.MappedToDirectory.MappedFileChanged
Creating Local Resource Override for: http://127.0.0.1:8000/inspector/network/resources/(.+)
Linking to file...
Triggering load...
PASS: Resource Loaded.
Resource status code: 200
Resource status text: OK
Resource MIME: text/plain
Resource Content: 'PASS: Should load from file.'

-- Running test case: LocalResourceOverride.MappedToDirectory.MappedFileDeleted
Creating Local Resource Override for: http://127.0.0.1:8000/inspector/network/resources/(.+)
Linking to file...
Triggering load...
WARN: Local Override: could not load “DOES NOT EXIST”
PASS: Resource Loaded.
Resource status code: 200
Resource status text: OK
Resource MIME: text/plain
Resource Content: 'default override.txt content
'

-- Running test case: LocalResourceOverride.MappedToDirectory.Invalid
Creating Local Resource Override for: http://127.0.0.1:8000/inspector/network/resources/(.+)
Linking to file...
Triggering load...
WARN: Local Override: could not load “DOES NOT EXIST”
PASS: Resource Loaded.
Resource status code: 200
Resource status text: OK
Resource MIME: text/plain
Resource Content: 'default override.txt content
'

Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="../resources/inspector-test.js"></script>
<script>
function createTemporaryFile(name, contents) {
if (!window.internals)
return "";

let path = window.internals.createTemporaryFile(name, contents);
if (!path) {
TestPage.addResult(`FAIL: could not create file`);
return "";
}

return path;
}

function triggerOverrideLoad() {
fetch("http://127.0.0.1:8000/inspector/network/resources/override.txt").then(() => {
TestPage.dispatchEventToFrontend("LoadComplete");
});
}

function test()
{
let suite = InspectorTest.createAsyncSuite("LocalResourceOverride");

suite.addTestCase({
name: "LocalResourceOverride.MappedToDirectory.Valid",
description: "Test that `WI.LocalResource.prototype.mapToFile` correctly loads a file that exists.",
async test() {
const filename = "override.txt";
const mappedFilePath = await InspectorTest.evaluateInPage(`createTemporaryFile("LocalResourceOverride_MappedToDirectory_Valid-${filename}", "PASS: Should load from file.")`);
const url = `http://127.0.0.1:8000/inspector/network/resources/(.+)`;

InspectorTest.log("Creating Local Resource Override for: " + url);
let localResourceOverride = WI.LocalResourceOverride.create(url, WI.LocalResourceOverride.InterceptType.ResponseMappedDirectory, {
requestURL: mappedFilePath.substring(mappedFilePath.lastIndexOf("/") + 1).replaceAll(filename, "$1"),
responseMIMEType: "FAIL: Should be derived from file.",
responseContent: "FAIL: Should load from file.",
responseStatusCode: 500,
responseStatusText: "FAIL: Should succeed.",
isRegex: true,
isPassthrough: true,
});
WI.networkManager.addLocalResourceOverride(localResourceOverride);

InspectorTest.log("Linking to file...");
localResourceOverride.localResource.mappedFilePath = mappedFilePath.substring(0, mappedFilePath.lastIndexOf("/") + 1);

InspectorTest.log("Triggering load...");
let [resourceWasAddedEvent, responseReceivedEvent, loadCompleteEvent] = await Promise.all([
WI.Frame.awaitEvent(WI.Frame.Event.ResourceWasAdded),
WI.Resource.awaitEvent(WI.Resource.Event.ResponseReceived),
InspectorTest.awaitEvent("LoadComplete"),
InspectorTest.evaluateInPage(`triggerOverrideLoad()`),
]);

InspectorTest.pass("Resource Loaded.");
let resource = resourceWasAddedEvent.data.resource;
let {rawContent, rawBase64Encoded} = await resource.requestContent();
InspectorTest.log(`Resource status code: ${resource.statusCode}`);
InspectorTest.log(`Resource status text: ${resource.statusText}`);
InspectorTest.log(`Resource MIME: ${resource.mimeType}`);
InspectorTest.log(`Resource Content: '${rawBase64Encoded ? "[base64] " : ""}${rawContent}'`);

WI.networkManager.removeLocalResourceOverride(localResourceOverride);
WI.LocalResource.resetPathsThatFailedToLoadFromFileSystem();
},
});

suite.addTestCase({
name: "LocalResourceOverride.MappedToDirectory.MappedFileChanged",
description: "Test that `WI.LocalResource.prototype.mapToFile` correctly re-loads the mapped file.",
async test() {
const filename = "override.txt";
const mappedFilePath = await InspectorTest.evaluateInPage(`createTemporaryFile("LocalResourceOverride_MappedToDirectory_MappedFileChanged-${filename}", "PASS: Should load from file.")`);
const url = `http://127.0.0.1:8000/inspector/network/resources/(.+)`;

InspectorTest.log("Creating Local Resource Override for: " + url);
let localResourceOverride = WI.LocalResourceOverride.create(url, WI.LocalResourceOverride.InterceptType.ResponseMappedDirectory, {
requestURL: mappedFilePath.substring(mappedFilePath.lastIndexOf("/") + 1).replaceAll(filename, "$1"),
responseMIMEType: "FAIL: Should be derived from file.",
responseContent: "FAIL: Should load from file.",
responseStatusCode: 500,
responseStatusText: "FAIL: Should succeed.",
isRegex: true,
isPassthrough: true,
});
WI.networkManager.addLocalResourceOverride(localResourceOverride);

InspectorTest.log("Linking to file...");
// Simulate the file contents changing after the frontend has already loaded it, as `set mappedFilePath` will immediately attempt to load the file's contents.
localResourceOverride.localResource._mappedFilePath = mappedFilePath.substring(0, mappedFilePath.lastIndexOf("/") + 1);

InspectorTest.log("Triggering load...");
let [resourceWasAddedEvent, responseReceivedEvent, loadCompleteEvent] = await Promise.all([
WI.Frame.awaitEvent(WI.Frame.Event.ResourceWasAdded),
WI.Resource.awaitEvent(WI.Resource.Event.ResponseReceived),
InspectorTest.awaitEvent("LoadComplete"),
InspectorTest.evaluateInPage(`triggerOverrideLoad()`),
]);

InspectorTest.pass("Resource Loaded.");
let resource = resourceWasAddedEvent.data.resource;
let {rawContent, rawBase64Encoded} = await resource.requestContent();
InspectorTest.log(`Resource status code: ${resource.statusCode}`);
InspectorTest.log(`Resource status text: ${resource.statusText}`);
InspectorTest.log(`Resource MIME: ${resource.mimeType}`);
InspectorTest.log(`Resource Content: '${rawBase64Encoded ? "[base64] " : ""}${rawContent}'`);

WI.networkManager.removeLocalResourceOverride(localResourceOverride);
WI.LocalResource.resetPathsThatFailedToLoadFromFileSystem();
}
});

suite.addTestCase({
name: "LocalResourceOverride.MappedToDirectory.MappedFileDeleted",
description: "Test that `WI.LocalResource.prototype.mapToFile` does not crash if the previously mapped file no longer exists.",
async test() {
const url = `http://127.0.0.1:8000/inspector/network/resources/(.+)`;

InspectorTest.log("Creating Local Resource Override for: " + url);
let localResourceOverride = WI.LocalResourceOverride.create(url, WI.LocalResourceOverride.InterceptType.ResponseMappedDirectory, {
requestURL: "$1",
responseMIMEType: "FAIL: Should passthrough.",
responseContent: "FAIL: Should passthrough.",
responseStatusCode: 500,
responseStatusText: "FAIL: Should passthrough.",
isRegex: true,
isPassthrough: true,
});
WI.networkManager.addLocalResourceOverride(localResourceOverride);

InspectorTest.log("Linking to file...");
// Simulate the file being deleted after the frontend has already loaded it, as `set mappedFilePath` will immediately attempt to load the file's contents.
localResourceOverride.localResource._mappedFilePath = "DOES NOT EXIST";

InspectorTest.log("Triggering load...");
let [resourceWasAddedEvent, responseReceivedEvent, loadCompleteEvent] = await Promise.all([
WI.Frame.awaitEvent(WI.Frame.Event.ResourceWasAdded),
WI.Resource.awaitEvent(WI.Resource.Event.ResponseReceived),
InspectorTest.awaitEvent("LoadComplete"),
InspectorTest.evaluateInPage(`triggerOverrideLoad()`),
]);

InspectorTest.pass("Resource Loaded.");
let resource = resourceWasAddedEvent.data.resource;
let {rawContent, rawBase64Encoded} = await resource.requestContent();
InspectorTest.log(`Resource status code: ${resource.statusCode}`);
InspectorTest.log(`Resource status text: ${resource.statusText}`);
InspectorTest.log(`Resource MIME: ${resource.mimeType}`);
InspectorTest.log(`Resource Content: '${rawBase64Encoded ? "[base64] " : ""}${rawContent}'`);

WI.networkManager.removeLocalResourceOverride(localResourceOverride);
WI.LocalResource.resetPathsThatFailedToLoadFromFileSystem();
}
});

suite.addTestCase({
name: "LocalResourceOverride.MappedToDirectory.Invalid",
description: "Test that `WI.LocalResource.prototype.mapToFile` does not crash when attempting to load a file that does not exist.",
async test() {
const url = `http://127.0.0.1:8000/inspector/network/resources/(.+)`;

InspectorTest.log("Creating Local Resource Override for: " + url);
let localResourceOverride = WI.LocalResourceOverride.create(url, WI.LocalResourceOverride.InterceptType.ResponseMappedDirectory, {
requestURL: "$1",
responseMIMEType: "FAIL: Should passthrough.",
responseContent: "FAIL: Should passthrough.",
responseStatusCode: 500,
responseStatusText: "FAIL: Should passthrough.",
isRegex: true,
isPassthrough: true,
});
WI.networkManager.addLocalResourceOverride(localResourceOverride);

InspectorTest.log("Linking to file...");
localResourceOverride.localResource.mappedFilePath = "DOES NOT EXIST";

InspectorTest.log("Triggering load...");
let [resourceWasAddedEvent, responseReceivedEvent, loadCompleteEvent] = await Promise.all([
WI.Frame.awaitEvent(WI.Frame.Event.ResourceWasAdded),
WI.Resource.awaitEvent(WI.Resource.Event.ResponseReceived),
InspectorTest.awaitEvent("LoadComplete"),
InspectorTest.evaluateInPage(`triggerOverrideLoad()`),
]);

InspectorTest.pass("Resource Loaded.");
let resource = resourceWasAddedEvent.data.resource;
let {rawContent, rawBase64Encoded} = await resource.requestContent();
InspectorTest.log(`Resource status code: ${resource.statusCode}`);
InspectorTest.log(`Resource status text: ${resource.statusText}`);
InspectorTest.log(`Resource MIME: ${resource.mimeType}`);
InspectorTest.log(`Resource Content: '${rawBase64Encoded ? "[base64] " : ""}${rawContent}'`);

WI.networkManager.removeLocalResourceOverride(localResourceOverride);
WI.LocalResource.resetPathsThatFailedToLoadFromFileSystem();
}
});

suite.runTestCasesAndFinish();
}
</script>
</head>
<body onload="runTest()">
<p>Tests for LocalResourceOverride being mapped to a directory.</p>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ Creating Local Resource Override for: http://127.0.0.1:8000/inspector/network/re
Linking to file...
Triggering load...
WARN: Local Override: could not load “DOES NOT EXIST”
WARN: Local Override: could not load “DOES NOT EXIST”
PASS: Resource Loaded.
Resource Content: 'PASS: Should not load from file.'
Local Resource Override Content: 'PASS: Should not load from file.'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
InspectorTest.log(`Local Resource Override Content: '${localResourceCurrentRevision.base64Encoded ? "[base64] " : ""}${localResourceCurrentRevision.content}'`)

WI.networkManager.removeLocalResourceOverride(localResourceOverride);
WI.LocalResource.resetPathsThatFailedToLoadFromFileSystem();
}
});

Expand Down Expand Up @@ -97,6 +98,7 @@
InspectorTest.log(`Local Resource Override Content: '${localResourceCurrentRevision.base64Encoded ? "[base64] " : ""}${localResourceCurrentRevision.content}'`)

WI.networkManager.removeLocalResourceOverride(localResourceOverride);
WI.LocalResource.resetPathsThatFailedToLoadFromFileSystem();
}
});

Expand Down Expand Up @@ -134,6 +136,7 @@
InspectorTest.log(`Local Resource Override Content: '${localResourceCurrentRevision.base64Encoded ? "[base64] " : ""}${localResourceCurrentRevision.content}'`)

WI.networkManager.removeLocalResourceOverride(localResourceOverride);
WI.LocalResource.resetPathsThatFailedToLoadFromFileSystem();
}
});

Expand Down Expand Up @@ -170,6 +173,7 @@
InspectorTest.log(`Local Resource Override Content: '${localResourceCurrentRevision.base64Encoded ? "[base64] " : ""}${localResourceCurrentRevision.content}'`)

WI.networkManager.removeLocalResourceOverride(localResourceOverride);
WI.LocalResource.resetPathsThatFailedToLoadFromFileSystem();
}
});

Expand Down
1 change: 1 addition & 0 deletions LayoutTests/platform/mac-wk1/TestExpectations
Original file line number Diff line number Diff line change
Expand Up @@ -1582,6 +1582,7 @@ http/tests/inspector/network/intercept-request-with-error.html [ Skip ]
http/tests/inspector/network/intercept-request-with-response.html [ Skip ]
http/tests/inspector/network/local-resource-override-basic.html [ Skip ]
http/tests/inspector/network/local-resource-override-main-resource.html [ Skip ]
http/tests/inspector/network/local-resource-override-mapped-to-directory.html [ Skip ]
http/tests/inspector/network/local-resource-override-mapped-to-file.html [ Skip ]
http/tests/inspector/network/local-resource-override-script-tag.html [ Skip ]
http/tests/inspector/network/resource-response-inspector-override.html [ Skip ]
Expand Down
1 change: 1 addition & 0 deletions LayoutTests/platform/mac-wk2/TestExpectations
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,7 @@ webkit.org/b/141085 http/tests/media/video-query-url.html [ Pass Timeout ]
########################################

# Network interception with a mapped file is only mac-wk2 for now.
http/tests/inspector/network/local-resource-override-mapped-to-directory.html [ Pass ]
http/tests/inspector/network/local-resource-override-mapped-to-file.html [ Pass ]

# Network throttling is currently only enabled on Cocoa platforms.
Expand Down

0 comments on commit d2d000b

Please sign in to comment.