Title: [293409] trunk
Revision
293409
Author
drou...@apple.com
Date
2022-04-25 21:07:01 -0700 (Mon, 25 Apr 2022)

Log Message

Web Inspector: add UI for blocking requests
https://bugs.webkit.org/show_bug.cgi?id=239674

Reviewed by Patrick Angle.

Source/WebCore:

Test: http/tests/inspector/network/intercept-request-with-error.html

* inspector/agents/InspectorNetworkAgent.h:
(WebCore::InspectorNetworkAgent::errorDomain): Added.
* inspector/agents/InspectorNetworkAgent.cpp:
(WebCore::InspectorNetworkAgent::toResourceErrorType): Added.
(WebCore::InspectorNetworkAgent::interceptRequestWithError):
Unify the resource error message for consistency.
Mark the resource error as coming from Web Inspector so that it can identified (see below).

* inspector/agents/WebConsoleAgent.cpp:
(WebCore::WebConsoleAgent::didFailLoading):
* loader/DocumentThreadableLoader.cpp:
(WebCore::DocumentThreadableLoader::logErrorAndFail):
* loader/SubresourceLoader.cpp:
(WebCore::SubresourceLoader::didFail):
Don't add console messages for requests that were blocked by Web Inspector.

* inspector/agents/page/PageNetworkAgent.h:
* inspector/agents/page/PageNetworkAgent.cpp:
(WebCore::PageNetworkAgent::addConsoleMessage): Added.
* inspector/agents/worker/WorkerNetworkAgent.h:
* inspector/agents/worker/WorkerNetworkAgent.cpp:
(WebCore::WorkerNetworkAgent::addConsoleMessage): Added.
Add a `virtual` method so that `InspectorNetworkAgent` can log to the console (`PageNetworkAgent`
uses the `Page`, `WorkerNetworkAgent` uses the `WorkerOrWorkletGlobalScope`, etc.).

Source/WebInspectorUI:

* UserInterface/Models/LocalResourceOverride.js:
(WI.LocalResourceOverride):
(WI.LocalResourceOverride.create):
(WI.LocalResourceOverride.displayNameForNetworkStageOfType): Added.
(WI.LocalResourceOverride.displayNameForType):
(WI.LocalResourceOverride.displayNameForResourceErrorType): Added.
(WI.LocalResourceOverride.fromJSON):
(WI.LocalResourceOverride.prototype.toJSON):
(WI.LocalResourceOverride.prototype.get resourceErrorType): Added.
(WI.LocalResourceOverride.prototype.set resourceErrorType): Added.
(WI.LocalResourceOverride.prototype.get canMapToFile):
Add `WI.LocalResourceOverride.ResourceErrorType` (and a corresponding member variable) that
is be used when blocking matching requests.

* UserInterface/Controllers/NetworkManager.js:
(WI.NetworkManager.supportsBlockingRequests): Added.
(WI.NetworkManager.prototype.async requestIntercepted):
(WI.NetworkManager.prototype._handleResourceOverrideResourceErrorTypeChanged): Added.
Make sure to save `WI.LocalResourceOverride.InterceptType.Block` whenever the
`WI.LocalResourceOverride.ResourceErrorType` changes.

* UserInterface/Views/ContextMenuUtilities.js:
(WI.appendContextMenuItemsForSourceCode):
* UserInterface/Views/ResourceContentView.js:
(WI.ResourceContentView):
(WI.ResourceContentView.prototype._populateCreateLocalResourceOverrideContextMenu):
(WI.ResourceContentView.prototype._handleCreateLocalResourceOverride): Deleted.
Add contextmenu items for "Block Request URL".

* UserInterface/Views/LocalResourceOverrideRequestContentView.js:
(WI.LocalResourceOverrideRequestContentView):
(WI.LocalResourceOverrideRequestContentView.prototype.initialLayout):
(WI.LocalResourceOverrideRequestContentView.prototype.initialLayout.addOption): Added.
* UserInterface/Views/LocalResourceOverrideRequestContentView.css:
(.content-view.text.local-resource-override-request > .message-text-view select): Added.
There will be no request or response content for `WI.LocalResourceOverride.InterceptType.Block`
so show a `<select>` for choosing the `WI.LocalResourceOverride.ResourceErrorType`.

* UserInterface/Views/LocalResourceOverridePopover.js:
(WI.LocalResourceOverridePopover.prototype.get serializedData):
(WI.LocalResourceOverridePopover.prototype.show):
* UserInterface/Views/LocalResourceOverridePopover.css:
(.popover .local-resource-override-popover-content:is(.response, .block) .editor.url): Renamed from `.popover .local-resource-override-popover-content.response .editor.url`.
Only show the URL editor for `WI.LocalResourceOverride.InterceptType.Block`.

* UserInterface/Models/Resource.js:
(WI.Resource.classNamesForResource):
* UserInterface/Views/ResourceTreeElement.js:
(WI.ResourceTreeElement.prototype._updateResource):
(WI.ResourceTreeElement.prototype._updateIcon):
(WI.ResourceTreeElement.prototype._loadingDidFail): Added.
* UserInterface/Views/ResourceIcons.css:
(.resource-icon.override.skip-network .icon): Added.
(body:not(.window-inactive, .window-docked-inactive) :is(.table, .data-grid):focus-within .selected .resource-icon.override.skip-network .icon, body:not(.window-inactive, .window-docked-inactive) .tree-outline:focus-within .selected.resource-icon.override.skip-network .icon): Added.
(@media (prefers-color-scheme: dark) .resource-icon.override.skip-network .icon): Added.
* UserInterface/Images/SkipNetwork.svg: Added.
Add a new icon for when requests do not involve any network activity.

* UserInterface/Views/ContentView.js:
(WI.ContentView.createFromRepresentedObject):
(WI.ContentView.resolvedRepresentedObjectForRepresentedObject):
* UserInterface/Views/FontResourceContentView.js:
(WI.FontResourceContentView.prototype.dropZoneShouldAppearForDragEvent):
* UserInterface/Views/ImageResourceContentView.js:
(WI.ImageResourceContentView.prototype.dropZoneShouldAppearForDragEvent):
* UserInterface/Views/LocalResourceOverrideTreeElement.js:
(WI.LocalResourceOverrideTreeElement.prototype.willDismissPopover):
Use positive checks for the desired `WI.LocalResourceOverride.InterceptType` instead of
a negative check for the one value meant to be exlucded in the expectation that no new
enum values would get added (which this patch proves to be false).

* UserInterface/Views/LocalResourceOverrideLabelView.js:
(WI.LocalResourceOverrideLabelView.prototype.initialLayout):
Use the new `WI.LocalResourceOverride.displayNameForNetworkStageOfType` instead of doing
that work here.

* Localizations/en.lproj/localizedStrings.js:

LayoutTests:

* http/tests/inspector/network/intercept-request-with-error.html: Added.
* http/tests/inspector/network/intercept-request-with-error-expected.txt: Added.
* inspector/network/interceptRequestWithError.html: Removed.
* inspector/network/interceptRequestWithError-expected.txt: Removed.
* platform/mac-wk1/TestExpectations:
* platform/mac-wk2/TestExpectations:
Reworked this test to use `WI.LocalResourceOverride` instead of the protocol.

Modified Paths

Added Paths

Removed Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (293408 => 293409)


--- trunk/LayoutTests/ChangeLog	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/LayoutTests/ChangeLog	2022-04-26 04:07:01 UTC (rev 293409)
@@ -1,5 +1,20 @@
 2022-04-25  Devin Rousso  <drou...@apple.com>
 
+        Web Inspector: add UI for blocking requests
+        https://bugs.webkit.org/show_bug.cgi?id=239674
+
+        Reviewed by Patrick Angle.
+
+        * http/tests/inspector/network/intercept-request-with-error.html: Added.
+        * http/tests/inspector/network/intercept-request-with-error-expected.txt: Added.
+        * inspector/network/interceptRequestWithError.html: Removed.
+        * inspector/network/interceptRequestWithError-expected.txt: Removed.
+        * platform/mac-wk1/TestExpectations:
+        * platform/mac-wk2/TestExpectations:
+        Reworked this test to use `WI.LocalResourceOverride` instead of the protocol.
+
+2022-04-25  Devin Rousso  <drou...@apple.com>
+
         Web Inspector: crash when a subresource is intercepted by a skip network local override with an error status code
         https://bugs.webkit.org/show_bug.cgi?id=239675
 

Added: trunk/LayoutTests/http/tests/inspector/network/intercept-request-with-error-expected.txt (0 => 293409)


--- trunk/LayoutTests/http/tests/inspector/network/intercept-request-with-error-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/http/tests/inspector/network/intercept-request-with-error-expected.txt	2022-04-26 04:07:01 UTC (rev 293409)
@@ -0,0 +1,29 @@
+CONSOLE MESSAGE: Web Inspector blocked http://127.0.0.1:8000/inspector/network/resources/override.txt#General from loading
+CONSOLE MESSAGE: Web Inspector blocked http://127.0.0.1:8000/inspector/network/resources/override.txt#AccessControl from loading
+CONSOLE MESSAGE: Fetch API cannot load http://127.0.0.1:8000/inspector/network/resources/override.txt#AccessControl due to access control checks.
+CONSOLE MESSAGE: Web Inspector blocked http://127.0.0.1:8000/inspector/network/resources/override.txt#Cancellation from loading
+CONSOLE MESSAGE: Web Inspector blocked http://127.0.0.1:8000/inspector/network/resources/override.txt#Timeout from loading
+Test request interception with response.
+
+
+== Running test suite: Network.interceptRequestWithError
+-- Running test case: Network.interceptRequestWithError.ResourceErrorType.General
+Creating Local Resource Override for: http://127.0.0.1:8000/inspector/network/resources/override.txt
+Triggering load...
+Resource with url 'http://127.0.0.1:8000/inspector/network/resources/override.txt#General' failed to load with error 'Blocked by Web Inspector'.
+
+-- Running test case: Network.interceptRequestWithError.ResourceErrorType.AccessControl
+Creating Local Resource Override for: http://127.0.0.1:8000/inspector/network/resources/override.txt
+Triggering load...
+Resource with url 'http://127.0.0.1:8000/inspector/network/resources/override.txt#AccessControl' failed to load with error 'Blocked by Web Inspector'.
+
+-- Running test case: Network.interceptRequestWithError.ResourceErrorType.Cancellation
+Creating Local Resource Override for: http://127.0.0.1:8000/inspector/network/resources/override.txt
+Triggering load...
+Resource with url 'http://127.0.0.1:8000/inspector/network/resources/override.txt#Cancellation' failed to load with error 'Blocked by Web Inspector'.
+
+-- Running test case: Network.interceptRequestWithError.ResourceErrorType.Timeout
+Creating Local Resource Override for: http://127.0.0.1:8000/inspector/network/resources/override.txt
+Triggering load...
+Resource with url 'http://127.0.0.1:8000/inspector/network/resources/override.txt#Timeout' failed to load with error 'Blocked by Web Inspector'.
+

Added: trunk/LayoutTests/http/tests/inspector/network/intercept-request-with-error.html (0 => 293409)


--- trunk/LayoutTests/http/tests/inspector/network/intercept-request-with-error.html	                        (rev 0)
+++ trunk/LayoutTests/http/tests/inspector/network/intercept-request-with-error.html	2022-04-26 04:07:01 UTC (rev 293409)
@@ -0,0 +1,97 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<script src=""
+<script>
+function triggerOverrideLoad(urlSuffix) {
+    let url = ""
+    if (urlSuffix)
+        url += urlSuffix;
+    fetch(url).catch(() => {
+        TestPage.dispatchEventToFrontend("LoadComplete");
+    });
+}
+
+function test()
+{
+    let suite = InspectorTest.createAsyncSuite("Network.interceptRequestWithError");
+
+    function addTestCase({name, description, _expression_, overrides}) {
+        suite.addTestCase({
+            name,
+            description,
+            async test() {
+                let localResourceOverrides = overrides.map((override) => {
+                    InspectorTest.log("Creating Local Resource Override for: " + override.url);
+                    let localResourceOverride = WI.LocalResourceOverride.create(override.url, WI.LocalResourceOverride.InterceptType.Block, override);
+                    WI.networkManager.addLocalResourceOverride(localResourceOverride);
+                    return localResourceOverride;
+                });
+
+                InspectorTest.log("Triggering load...");
+                let [resourceWasAddedEvent] = await Promise.all([
+                    WI.Frame.awaitEvent(WI.Frame.Event.ResourceWasAdded),
+                    WI.Resource.awaitEvent(WI.Resource.Event.LoadingDidFail),
+                    InspectorTest.awaitEvent("LoadComplete"),
+                    InspectorTest.evaluateInPage(_expression_),
+                ]);
+
+                let resource = resourceWasAddedEvent.data.resource;
+                InspectorTest.assert(resource.failed, "Should fail to load");
+                InspectorTest.log(`Resource with url '${resource.url}' failed to load with error '${resource.failureReasonText}'.`);
+
+                for (let localResourceOverride of localResourceOverrides)
+                    WI.networkManager.removeLocalResourceOverride(localResourceOverride);
+            }
+        });
+    }
+
+    addTestCase({
+        name: "Network.interceptRequestWithError.ResourceErrorType.General",
+        description: "Block request wth general error.",
+        _expression_: `triggerOverrideLoad("#General")`,
+        overrides: [{
+            url: "http://127.0.0.1:8000/inspector/network/resources/override.txt",
+            resourceErrorType: WI.LocalResourceOverride.ResourceErrorType.General,
+        }],
+    });
+
+    addTestCase({
+        name: "Network.interceptRequestWithError.ResourceErrorType.AccessControl",
+        description: "Block request wth access control error.",
+        _expression_: `triggerOverrideLoad("#AccessControl")`,
+        overrides: [{
+            url: "http://127.0.0.1:8000/inspector/network/resources/override.txt",
+            resourceErrorType: WI.LocalResourceOverride.ResourceErrorType.AccessControl,
+        }],
+    });
+
+    addTestCase({
+        name: "Network.interceptRequestWithError.ResourceErrorType.Cancellation",
+        description: "Block request wth cancellation error.",
+        _expression_: `triggerOverrideLoad("#Cancellation")`,
+        overrides: [{
+            url: "http://127.0.0.1:8000/inspector/network/resources/override.txt",
+            resourceErrorType: WI.LocalResourceOverride.ResourceErrorType.Cancellation,
+        }],
+    });
+
+    addTestCase({
+        name: "Network.interceptRequestWithError.ResourceErrorType.Timeout",
+        description: "Block request wth timeout error.",
+        _expression_: `triggerOverrideLoad("#Timeout")`,
+        overrides: [{
+            url: "http://127.0.0.1:8000/inspector/network/resources/override.txt",
+            resourceErrorType: WI.LocalResourceOverride.ResourceErrorType.Timeout,
+        }],
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body _onload_="runTest()">
+<p>Test request interception with response.</p>
+</body>
+</html>

Deleted: trunk/LayoutTests/inspector/network/interceptRequestWithError-expected.txt (293408 => 293409)


--- trunk/LayoutTests/inspector/network/interceptRequestWithError-expected.txt	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/LayoutTests/inspector/network/interceptRequestWithError-expected.txt	2022-04-26 04:07:01 UTC (rev 293409)
@@ -1,28 +0,0 @@
-CONSOLE MESSAGE: Fetch API cannot load data.json?error=General.
-CONSOLE MESSAGE: Access denied
-CONSOLE MESSAGE: Fetch API cannot load data.json?error=AccessControl due to access control checks.
-CONSOLE MESSAGE: Fetch API cannot load data.json?error=Timeout.
-Tests for Network.interceptRequestWithError.
-
-
-== Running test suite: Network.interceptRequestWithError
--- Running test case: Network.interceptRequestWithError.General
-Triggering load...
-Intercepting with error...
-FAILURE TEXT: Request intercepted
-
--- Running test case: Network.interceptRequestWithError.Access
-Triggering load...
-Intercepting with error...
-FAILURE TEXT: Access denied
-
--- Running test case: Network.interceptRequestWithError.Canceled
-Triggering load...
-Intercepting with error...
-FAILURE TEXT: Request canceled
-
--- Running test case: Network.interceptRequestWithError.Timeout
-Triggering load...
-Intercepting with error...
-FAILURE TEXT: Request timed out
-

Deleted: trunk/LayoutTests/inspector/network/interceptRequestWithError.html (293408 => 293409)


--- trunk/LayoutTests/inspector/network/interceptRequestWithError.html	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/LayoutTests/inspector/network/interceptRequestWithError.html	2022-04-26 04:07:01 UTC (rev 293409)
@@ -1,77 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<meta charset="utf-8">
-<script src=""
-<script>
-function loadSubresource(query) {
-    fetch(`resources/data.json?error=${query}`)
-        .catch(() => {});
-}
-
-function test()
-{
-    let suite = ProtocolTest.createAsyncSuite("Network.interceptRequestWithError");
-
-    InspectorProtocol.sendCommand("Network.enable");
-    InspectorProtocol.sendCommand("Network.setInterceptionEnabled", {enabled: true});
-    InspectorProtocol.sendCommand("Network.addInterception", {
-        url: "resources/data\.json",
-        stage: /* Network.NetworkStage */ "request",
-        isRegex: true,
-    });
-
-    function addTestCase({name, errorType}) {
-        suite.addTestCase({
-            name,
-            async test() {
-                ProtocolTest.log("Triggering load...");
-                let [requestInterceptedEvent] = await Promise.all([
-                    InspectorProtocol.awaitEvent({event: "Network.requestIntercepted"}),
-                    ProtocolTest.evaluateInPage(`loadSubresource("${errorType}")`),
-                ]);
-
-                ProtocolTest.log("Intercepting with error...");
-                let [loadingDidFailEvent] = await Promise.all([
-                    InspectorProtocol.awaitEvent({event: "Network.loadingFailed"}),
-                    InspectorProtocol.awaitCommand({
-                        method: "Network.interceptRequestWithError",
-                        params: {
-                            requestId: requestInterceptedEvent.params.requestId,
-                            errorType,
-                        },
-                    }),
-                ]);
-                ProtocolTest.log("FAILURE TEXT: " + loadingDidFailEvent.params.errorText);
-            },
-        });
-    }
-
-    addTestCase({
-        name: "Network.interceptRequestWithError.General",
-        errorType: /* Network.ResourceErrorType */ "General",
-    });
-
-    addTestCase({
-        name: "Network.interceptRequestWithError.Access",
-        errorType: /* Network.ResourceErrorType */ "AccessControl",
-    });
-
-    addTestCase({
-        name: "Network.interceptRequestWithError.Canceled",
-        errorType: /* Network.ResourceErrorType */ "Cancellation",
-    });
-
-    addTestCase({
-        name: "Network.interceptRequestWithError.Timeout",
-        errorType: /* Network.ResourceErrorType */ "Timeout",
-    });
-
-    suite.runTestCasesAndFinish();
-}
-</script>
-</head>
-<body _onload_="runTest()">
-<p>Tests for Network.interceptRequestWithError.</p>
-</body>
-</html>

Modified: trunk/LayoutTests/platform/mac-wk1/TestExpectations (293408 => 293409)


--- trunk/LayoutTests/platform/mac-wk1/TestExpectations	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/LayoutTests/platform/mac-wk1/TestExpectations	2022-04-26 04:07:01 UTC (rev 293409)
@@ -924,6 +924,7 @@
 http/tests/inspector/network/intercept-request-subresource.html [ Skip ]
 http/tests/inspector/network/intercept-request-subresource-with-response.html [ Skip ]
 http/tests/inspector/network/intercept-request-subresource-with-response-error-status-code.html [ Skip ]
+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 ]
@@ -932,7 +933,6 @@
 http/tests/inspector/network/resource-response-inspector-override.html [ Skip ]
 inspector/network/intercept-aborted-request.html [ Skip ]
 inspector/network/interceptContinue.html [ Skip ]
-inspector/network/interceptRequestWithError.html [ Skip ]
 
 webkit.org/b/164933 http/tests/misc/link-rel-icon-beforeload.html [ Failure ]
 

Modified: trunk/LayoutTests/platform/mac-wk2/TestExpectations (293408 => 293409)


--- trunk/LayoutTests/platform/mac-wk2/TestExpectations	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/LayoutTests/platform/mac-wk2/TestExpectations	2022-04-26 04:07:01 UTC (rev 293409)
@@ -1586,6 +1586,7 @@
 
 webkit.org/b/230117 [ BigSur Debug ] http/tests/inspector/network/fetch-response-body.html [ Pass Failure ]
 webkit.org/b/230117 [ BigSur Debug ] http/tests/inspector/network/intercept-request-properties.html [ Pass Failure ]
+webkit.org/b/230117 [ BigSur Debug ] http/tests/inspector/network/intercept-request-with-error.html [ Pass Failure ]
 webkit.org/b/230117 [ BigSur Debug ] http/tests/inspector/network/intercept-request-with-response.html [ Pass Failure ]
 webkit.org/b/230117 [ BigSur Debug ] http/tests/inspector/target/pause-on-inline-debugger-statement.html [ Pass Failure ]
 

Modified: trunk/Source/WebCore/ChangeLog (293408 => 293409)


--- trunk/Source/WebCore/ChangeLog	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/Source/WebCore/ChangeLog	2022-04-26 04:07:01 UTC (rev 293409)
@@ -1,5 +1,39 @@
 2022-04-25  Devin Rousso  <drou...@apple.com>
 
+        Web Inspector: add UI for blocking requests
+        https://bugs.webkit.org/show_bug.cgi?id=239674
+
+        Reviewed by Patrick Angle.
+
+        Test: http/tests/inspector/network/intercept-request-with-error.html
+
+        * inspector/agents/InspectorNetworkAgent.h:
+        (WebCore::InspectorNetworkAgent::errorDomain): Added.
+        * inspector/agents/InspectorNetworkAgent.cpp:
+        (WebCore::InspectorNetworkAgent::toResourceErrorType): Added.
+        (WebCore::InspectorNetworkAgent::interceptRequestWithError):
+        Unify the resource error message for consistency.
+        Mark the resource error as coming from Web Inspector so that it can identified (see below).
+
+        * inspector/agents/WebConsoleAgent.cpp:
+        (WebCore::WebConsoleAgent::didFailLoading):
+        * loader/DocumentThreadableLoader.cpp:
+        (WebCore::DocumentThreadableLoader::logErrorAndFail):
+        * loader/SubresourceLoader.cpp:
+        (WebCore::SubresourceLoader::didFail):
+        Don't add console messages for requests that were blocked by Web Inspector.
+
+        * inspector/agents/page/PageNetworkAgent.h:
+        * inspector/agents/page/PageNetworkAgent.cpp:
+        (WebCore::PageNetworkAgent::addConsoleMessage): Added.
+        * inspector/agents/worker/WorkerNetworkAgent.h:
+        * inspector/agents/worker/WorkerNetworkAgent.cpp:
+        (WebCore::WorkerNetworkAgent::addConsoleMessage): Added.
+        Add a `virtual` method so that `InspectorNetworkAgent` can log to the console (`PageNetworkAgent`
+        uses the `Page`, `WorkerNetworkAgent` uses the `WorkerOrWorkletGlobalScope`, etc.).
+
+2022-04-25  Devin Rousso  <drou...@apple.com>
+
         Web Inspector: request interception should not be guarded based on service workers
         https://bugs.webkit.org/show_bug.cgi?id=239677
 

Modified: trunk/Source/WebCore/inspector/agents/InspectorNetworkAgent.cpp (293408 => 293409)


--- trunk/Source/WebCore/inspector/agents/InspectorNetworkAgent.cpp	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/Source/WebCore/inspector/agents/InspectorNetworkAgent.cpp	2022-04-26 04:07:01 UTC (rev 293409)
@@ -69,6 +69,7 @@
 #include "SubresourceLoader.h"
 #include "TextResourceDecoder.h"
 #include "ThreadableLoaderClient.h"
+#include "WebConsoleAgent.h"
 #include <wtf/URL.h>
 #include "WebSocket.h"
 #include "WebSocketChannel.h"
@@ -1311,6 +1312,26 @@
     return { };
 }
 
+static ResourceError::Type toResourceErrorType(Protocol::Network::ResourceErrorType protocolResourceErrorType)
+{
+    switch (protocolResourceErrorType) {
+    case Protocol::Network::ResourceErrorType::General:
+        return ResourceError::Type::General;
+
+    case Protocol::Network::ResourceErrorType::AccessControl:
+        return ResourceError::Type::AccessControl;
+
+    case Protocol::Network::ResourceErrorType::Cancellation:
+        return ResourceError::Type::Cancellation;
+
+    case Protocol::Network::ResourceErrorType::Timeout:
+        return ResourceError::Type::Timeout;
+    }
+
+    ASSERT_NOT_REACHED();
+    return ResourceError::Type::Null;
+}
+
 Protocol::ErrorStringOr<void> InspectorNetworkAgent::interceptRequestWithError(const Protocol::Network::RequestId& requestId, Protocol::Network::ResourceErrorType errorType)
 {
     auto pendingRequest = m_pendingInterceptRequests.take(requestId);
@@ -1321,25 +1342,9 @@
     if (loader.reachedTerminalState())
         return makeUnexpected("Unable to abort request, it has already been processed"_s);
 
-    switch (errorType) {
-    case Protocol::Network::ResourceErrorType::General:
-        loader.didFail(ResourceError(errorDomainWebKitInternal, 0, loader.url(), "Request intercepted"_s, ResourceError::Type::General));
-        return { };
+    addConsoleMessage(makeUnique<Inspector::ConsoleMessage>(MessageSource::Network, MessageType::Log, MessageLevel::Info, makeString("Web Inspector blocked ", loader.url().string(), " from loading"), loader.identifier().toUInt64()));
 
-    case Protocol::Network::ResourceErrorType::AccessControl:
-        loader.didFail(ResourceError(errorDomainWebKitInternal, 0, loader.url(), "Access denied"_s, ResourceError::Type::AccessControl));
-        return { };
-
-    case Protocol::Network::ResourceErrorType::Cancellation:
-        loader.didFail(ResourceError(errorDomainWebKitInternal, 0, loader.url(), "Request canceled"_s, ResourceError::Type::Cancellation));
-        return { };
-
-    case Protocol::Network::ResourceErrorType::Timeout:
-        loader.didFail(ResourceError(errorDomainWebKitInternal, 0, loader.url(), "Request timed out"_s, ResourceError::Type::Timeout));
-        return { };
-    }
-
-    ASSERT_NOT_REACHED();
+    loader.didFail(ResourceError(InspectorNetworkAgent::errorDomain(), 0, loader.url(), "Blocked by Web Inspector"_s, toResourceErrorType(errorType)));
     return { };
 }
 

Modified: trunk/Source/WebCore/inspector/agents/InspectorNetworkAgent.h (293408 => 293409)


--- trunk/Source/WebCore/inspector/agents/InspectorNetworkAgent.h	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/Source/WebCore/inspector/agents/InspectorNetworkAgent.h	2022-04-26 04:07:01 UTC (rev 293409)
@@ -43,6 +43,7 @@
 #include <wtf/RobinHoodHashMap.h>
 
 namespace Inspector {
+class ConsoleMessage;
 class InjectedScriptManager;
 }
 
@@ -69,6 +70,8 @@
 public:
     ~InspectorNetworkAgent() override;
 
+    static constexpr ASCIILiteral errorDomain() { return "InspectorNetworkAgent"_s; }
+
     static bool shouldTreatAsText(const String& mimeType);
     static Ref<TextResourceDecoder> createTextDecoder(const String& mimeType, const String& textEncodingName);
     static std::optional<String> textContentForCachedResource(CachedResource&);
@@ -138,6 +141,7 @@
     virtual Vector<WebSocket*> activeWebSockets() WTF_REQUIRES_LOCK(WebSocket::allActiveWebSocketsLock()) = 0;
     virtual void setResourceCachingDisabledInternal(bool) = 0;
     virtual ScriptExecutionContext* scriptExecutionContext(Inspector::Protocol::ErrorString&, const Inspector::Protocol::Network::FrameId&) = 0;
+    virtual void addConsoleMessage(std::unique_ptr<Inspector::ConsoleMessage>&&) = 0;
     virtual bool shouldForceBufferingNetworkResourceData() const = 0;
 
 private:

Modified: trunk/Source/WebCore/inspector/agents/WebConsoleAgent.cpp (293408 => 293409)


--- trunk/Source/WebCore/inspector/agents/WebConsoleAgent.cpp	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/Source/WebCore/inspector/agents/WebConsoleAgent.cpp	2022-04-26 04:07:01 UTC (rev 293409)
@@ -29,6 +29,7 @@
 
 #include "CommandLineAPIHost.h"
 #include "DOMWindow.h"
+#include "InspectorNetworkAgent.h"
 #include "InspectorWebAgentBase.h"
 #include "JSExecState.h"
 #include "Logging.h"
@@ -70,6 +71,9 @@
 
 void WebConsoleAgent::didFailLoading(ResourceLoaderIdentifier requestIdentifier, const ResourceError& error)
 {
+    if (error.domain() == InspectorNetworkAgent::errorDomain())
+        return;
+
     // Report failures only.
     if (error.isCancellation())
         return;

Modified: trunk/Source/WebCore/inspector/agents/page/PageNetworkAgent.cpp (293408 => 293409)


--- trunk/Source/WebCore/inspector/agents/page/PageNetworkAgent.cpp	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/Source/WebCore/inspector/agents/page/PageNetworkAgent.cpp	2022-04-26 04:07:01 UTC (rev 293409)
@@ -31,6 +31,7 @@
 #include "Frame.h"
 #include "InstrumentingAgents.h"
 #include "Page.h"
+#include "PageConsoleClient.h"
 #include "WebSocket.h"
 #include "WebSocketChannel.h"
 
@@ -116,4 +117,9 @@
     return document;
 }
 
+void PageNetworkAgent::addConsoleMessage(std::unique_ptr<Inspector::ConsoleMessage>&& message)
+{
+    m_inspectedPage.console().addMessage(WTFMove(message));
+}
+
 } // namespace WebCore

Modified: trunk/Source/WebCore/inspector/agents/page/PageNetworkAgent.h (293408 => 293409)


--- trunk/Source/WebCore/inspector/agents/page/PageNetworkAgent.h	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/Source/WebCore/inspector/agents/page/PageNetworkAgent.h	2022-04-26 04:07:01 UTC (rev 293409)
@@ -44,6 +44,7 @@
     Vector<WebSocket*> activeWebSockets() WTF_REQUIRES_LOCK(WebSocket::allActiveWebSocketsLock());
     void setResourceCachingDisabledInternal(bool);
     ScriptExecutionContext* scriptExecutionContext(Inspector::Protocol::ErrorString&, const Inspector::Protocol::Network::FrameId&);
+    void addConsoleMessage(std::unique_ptr<Inspector::ConsoleMessage>&&);
     bool shouldForceBufferingNetworkResourceData() const { return false; }
 
     Page& m_inspectedPage;

Modified: trunk/Source/WebCore/inspector/agents/worker/WorkerNetworkAgent.cpp (293408 => 293409)


--- trunk/Source/WebCore/inspector/agents/worker/WorkerNetworkAgent.cpp	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/Source/WebCore/inspector/agents/worker/WorkerNetworkAgent.cpp	2022-04-26 04:07:01 UTC (rev 293409)
@@ -71,4 +71,9 @@
     return &m_globalScope;
 }
 
+void WorkerNetworkAgent::addConsoleMessage(std::unique_ptr<Inspector::ConsoleMessage>&& message)
+{
+    m_globalScope.addConsoleMessage(WTFMove(message));
+}
+
 } // namespace WebCore

Modified: trunk/Source/WebCore/inspector/agents/worker/WorkerNetworkAgent.h (293408 => 293409)


--- trunk/Source/WebCore/inspector/agents/worker/WorkerNetworkAgent.h	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/Source/WebCore/inspector/agents/worker/WorkerNetworkAgent.h	2022-04-26 04:07:01 UTC (rev 293409)
@@ -42,6 +42,7 @@
     Vector<WebSocket*> activeWebSockets() WTF_REQUIRES_LOCK(WebSocket::allActiveWebSocketsLock());
     void setResourceCachingDisabledInternal(bool);
     ScriptExecutionContext* scriptExecutionContext(Inspector::Protocol::ErrorString&, const Inspector::Protocol::Network::FrameId&);
+    void addConsoleMessage(std::unique_ptr<Inspector::ConsoleMessage>&&);
     bool shouldForceBufferingNetworkResourceData() const { return true; }
 
     WorkerOrWorkletGlobalScope& m_globalScope;

Modified: trunk/Source/WebCore/loader/DocumentThreadableLoader.cpp (293408 => 293409)


--- trunk/Source/WebCore/loader/DocumentThreadableLoader.cpp	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/Source/WebCore/loader/DocumentThreadableLoader.cpp	2022-04-26 04:07:01 UTC (rev 293409)
@@ -44,6 +44,7 @@
 #include "Frame.h"
 #include "FrameLoader.h"
 #include "InspectorInstrumentation.h"
+#include "InspectorNetworkAgent.h"
 #include "LegacySchemeRegistry.h"
 #include "LoaderStrategy.h"
 #include "MixedContentChecker.h"
@@ -754,7 +755,7 @@
 void DocumentThreadableLoader::logErrorAndFail(const ResourceError& error)
 {
     if (m_shouldLogError == ShouldLogError::Yes) {
-        if (error.isAccessControl() && !error.localizedDescription().isEmpty())
+        if (error.isAccessControl() && error.domain() != InspectorNetworkAgent::errorDomain() && !error.localizedDescription().isEmpty())
             m_document.addConsoleMessage(MessageSource::Security, MessageLevel::Error, error.localizedDescription());
         logError(m_document, error, m_options.initiator);
     }

Modified: trunk/Source/WebCore/loader/SubresourceLoader.cpp (293408 => 293409)


--- trunk/Source/WebCore/loader/SubresourceLoader.cpp	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/Source/WebCore/loader/SubresourceLoader.cpp	2022-04-26 04:07:01 UTC (rev 293409)
@@ -39,6 +39,7 @@
 #include "Frame.h"
 #include "FrameLoader.h"
 #include "HTTPParsers.h"
+#include "InspectorNetworkAgent.h"
 #include "LinkLoader.h"
 #include "Logging.h"
 #include "MemoryCache.h"
@@ -766,7 +767,7 @@
     ASSERT(!reachedTerminalState());
     LOG(ResourceLoading, "Failed to load '%s'.\n", m_resource->url().string().latin1().data());
 
-    if (m_frame->document() && error.isAccessControl() && m_resource->type() != CachedResource::Type::Ping)
+    if (m_frame->document() && error.isAccessControl() && error.domain() != InspectorNetworkAgent::errorDomain() && m_resource->type() != CachedResource::Type::Ping)
         m_frame->document()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, error.localizedDescription());
 
     Ref<SubresourceLoader> protectedThis(*this);

Modified: trunk/Source/WebInspectorUI/ChangeLog (293408 => 293409)


--- trunk/Source/WebInspectorUI/ChangeLog	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/Source/WebInspectorUI/ChangeLog	2022-04-26 04:07:01 UTC (rev 293409)
@@ -1,5 +1,90 @@
 2022-04-25  Devin Rousso  <drou...@apple.com>
 
+        Web Inspector: add UI for blocking requests
+        https://bugs.webkit.org/show_bug.cgi?id=239674
+
+        Reviewed by Patrick Angle.
+
+        * UserInterface/Models/LocalResourceOverride.js:
+        (WI.LocalResourceOverride):
+        (WI.LocalResourceOverride.create):
+        (WI.LocalResourceOverride.displayNameForNetworkStageOfType): Added.
+        (WI.LocalResourceOverride.displayNameForType):
+        (WI.LocalResourceOverride.displayNameForResourceErrorType): Added.
+        (WI.LocalResourceOverride.fromJSON):
+        (WI.LocalResourceOverride.prototype.toJSON):
+        (WI.LocalResourceOverride.prototype.get resourceErrorType): Added.
+        (WI.LocalResourceOverride.prototype.set resourceErrorType): Added.
+        (WI.LocalResourceOverride.prototype.get canMapToFile):
+        Add `WI.LocalResourceOverride.ResourceErrorType` (and a corresponding member variable) that
+        is be used when blocking matching requests.
+
+        * UserInterface/Controllers/NetworkManager.js:
+        (WI.NetworkManager.supportsBlockingRequests): Added.
+        (WI.NetworkManager.prototype.async requestIntercepted):
+        (WI.NetworkManager.prototype._handleResourceOverrideResourceErrorTypeChanged): Added.
+        Make sure to save `WI.LocalResourceOverride.InterceptType.Block` whenever the
+        `WI.LocalResourceOverride.ResourceErrorType` changes.
+
+        * UserInterface/Views/ContextMenuUtilities.js:
+        (WI.appendContextMenuItemsForSourceCode):
+        * UserInterface/Views/ResourceContentView.js:
+        (WI.ResourceContentView):
+        (WI.ResourceContentView.prototype._populateCreateLocalResourceOverrideContextMenu):
+        (WI.ResourceContentView.prototype._handleCreateLocalResourceOverride): Deleted.
+        Add contextmenu items for "Block Request URL".
+
+        * UserInterface/Views/LocalResourceOverrideRequestContentView.js:
+        (WI.LocalResourceOverrideRequestContentView):
+        (WI.LocalResourceOverrideRequestContentView.prototype.initialLayout):
+        (WI.LocalResourceOverrideRequestContentView.prototype.initialLayout.addOption): Added.
+        * UserInterface/Views/LocalResourceOverrideRequestContentView.css:
+        (.content-view.text.local-resource-override-request > .message-text-view select): Added.
+        There will be no request or response content for `WI.LocalResourceOverride.InterceptType.Block`
+        so show a `<select>` for choosing the `WI.LocalResourceOverride.ResourceErrorType`.
+
+        * UserInterface/Views/LocalResourceOverridePopover.js:
+        (WI.LocalResourceOverridePopover.prototype.get serializedData):
+        (WI.LocalResourceOverridePopover.prototype.show):
+        * UserInterface/Views/LocalResourceOverridePopover.css:
+        (.popover .local-resource-override-popover-content:is(.response, .block) .editor.url): Renamed from `.popover .local-resource-override-popover-content.response .editor.url`.
+        Only show the URL editor for `WI.LocalResourceOverride.InterceptType.Block`.
+
+        * UserInterface/Models/Resource.js:
+        (WI.Resource.classNamesForResource):
+        * UserInterface/Views/ResourceTreeElement.js:
+        (WI.ResourceTreeElement.prototype._updateResource):
+        (WI.ResourceTreeElement.prototype._updateIcon):
+        (WI.ResourceTreeElement.prototype._loadingDidFail): Added.
+        * UserInterface/Views/ResourceIcons.css:
+        (.resource-icon.override.skip-network .icon): Added.
+        (body:not(.window-inactive, .window-docked-inactive) :is(.table, .data-grid):focus-within .selected .resource-icon.override.skip-network .icon, body:not(.window-inactive, .window-docked-inactive) .tree-outline:focus-within .selected.resource-icon.override.skip-network .icon): Added.
+        (@media (prefers-color-scheme: dark) .resource-icon.override.skip-network .icon): Added.
+        * UserInterface/Images/SkipNetwork.svg: Added.
+        Add a new icon for when requests do not involve any network activity.
+
+        * UserInterface/Views/ContentView.js:
+        (WI.ContentView.createFromRepresentedObject):
+        (WI.ContentView.resolvedRepresentedObjectForRepresentedObject):
+        * UserInterface/Views/FontResourceContentView.js:
+        (WI.FontResourceContentView.prototype.dropZoneShouldAppearForDragEvent):
+        * UserInterface/Views/ImageResourceContentView.js:
+        (WI.ImageResourceContentView.prototype.dropZoneShouldAppearForDragEvent):
+        * UserInterface/Views/LocalResourceOverrideTreeElement.js:
+        (WI.LocalResourceOverrideTreeElement.prototype.willDismissPopover):
+        Use positive checks for the desired `WI.LocalResourceOverride.InterceptType` instead of
+        a negative check for the one value meant to be exlucded in the expectation that no new
+        enum values would get added (which this patch proves to be false).
+
+        * UserInterface/Views/LocalResourceOverrideLabelView.js:
+        (WI.LocalResourceOverrideLabelView.prototype.initialLayout):
+        Use the new `WI.LocalResourceOverride.displayNameForNetworkStageOfType` instead of doing
+        that work here.
+
+        * Localizations/en.lproj/localizedStrings.js:
+
+2022-04-25  Devin Rousso  <drou...@apple.com>
+
         Web Inspector: response overrides with an error status code should indicate that somewhere
         https://bugs.webkit.org/show_bug.cgi?id=239676
 

Modified: trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js (293408 => 293409)


--- trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js	2022-04-26 04:07:01 UTC (rev 293409)
@@ -105,6 +105,8 @@
 localizedStrings["1080p"] = "1080p";
 localizedStrings["2D"] = "2D";
 localizedStrings["720p"] = "720p";
+/* Text indicating that the local override intercepts the request phase of network activity. */
+localizedStrings["Access Control @ Local Override Type"] = "Access Control";
 localizedStrings["Accessibility"] = "Accessibility";
 localizedStrings["Action"] = "Action";
 /* Tooltip for a time range bar that represents when a CSS animation/transition is running */
@@ -233,6 +235,10 @@
 localizedStrings["Blackbox script to ignore it when debugging"] = "Blackbox script to ignore it when debugging";
 /* Part of the 'Blackboxed - %d call frames' label shown in the debugger call stack when paused instead of subsequent call frames that have been blackboxed. */
 localizedStrings["Blackboxed @ Debugger Call Stack"] = "Blackboxed";
+/* Text indicating that the local override will always block the network activity. */
+localizedStrings["Block @ Local Override Type"] = "Block";
+localizedStrings["Block Request URL"] = "Block Request URL";
+localizedStrings["Block URL with %s error"] = "Block URL with %s error";
 localizedStrings["Block Variables"] = "Block Variables";
 /* Input label for the blur radius of a CSS box shadow */
 localizedStrings["Blur @ Box Shadow Editor"] = "Blur";
@@ -274,6 +280,8 @@
 localizedStrings["Cancel comparison"] = "Cancel comparison";
 /* Tooltip for a timestamp marker that represents when a CSS animation/transition is canceled */
 localizedStrings["Canceled"] = "Canceled";
+/* Text indicating that the local override will always block the network activity. */
+localizedStrings["Cancellation @ Local Override Type"] = "Cancellation";
 localizedStrings["Canvas"] = "Canvas";
 localizedStrings["Canvas %d"] = "Canvas %d";
 localizedStrings["Canvas %s"] = "Canvas %s";
@@ -724,6 +732,8 @@
 localizedStrings["GIF"] = "GIF";
 localizedStrings["Garbage Collection"] = "Garbage Collection";
 localizedStrings["General"] = "General";
+/* Text indicating that the local override intercepts the response phase of network activity. */
+localizedStrings["General @ Local Override Type"] = "General";
 localizedStrings["Getter"] = "Getter";
 localizedStrings["Global Code"] = "Global Code";
 localizedStrings["Global Lexical Environment"] = "Global Lexical Environment";
@@ -1215,8 +1225,8 @@
 localizedStrings["Request Cookies"] = "Request Cookies";
 localizedStrings["Request Data"] = "Request Data";
 localizedStrings["Request Headers"] = "Request Headers";
-/* Label indicating that the shown content is from a request local override. */
-localizedStrings["Request Override @ Local Override Content View"] = "Request Override";
+/* Text indicating that the local override replaces the request of the network activity. */
+localizedStrings["Request Override @ Local Override Network Stage"] = "Request Override";
 localizedStrings["Requesting: %s"] = "Requesting: %s";
 localizedStrings["Required"] = "Required";
 /* Context menu action for resetting the breakpoint to its initial configuration. */
@@ -1243,8 +1253,8 @@
 localizedStrings["Response @ Local Override Type"] = "Response";
 localizedStrings["Response Cookies"] = "Response Cookies";
 localizedStrings["Response Headers"] = "Response Headers";
-/* Label indicating that the shown content is from a response local override. */
-localizedStrings["Response Override @ Local Override Content View"] = "Response Override";
+/* Text indicating that the local override replaces the response of the network activity. */
+localizedStrings["Response Override @ Local Override Network Stage"] = "Response Override";
 localizedStrings["Response:"] = "Response:";
 localizedStrings["Restart (%s)"] = "Restart (%s)";
 localizedStrings["Restart animation"] = "Restart animation";
@@ -1561,6 +1571,7 @@
 localizedStrings["This object is a root"] = "This object is a root";
 localizedStrings["This object is referenced by internal objects"] = "This object is referenced by internal objects";
 localizedStrings["This resource came from a local override"] = "This resource came from a local override";
+localizedStrings["This resource was blocked by a local override"] = "This resource was blocked by a local override";
 localizedStrings["This resource was loaded from a local override"] = "This resource was loaded from a local override";
 localizedStrings["This resource was loaded from a service worker"] = "This resource was loaded from a service worker";
 localizedStrings["This resource was loaded from the disk cache"] = "This resource was loaded from the disk cache";
@@ -1575,6 +1586,8 @@
 localizedStrings["Timeline Recording Import Error: %s"] = "Timeline Recording Import Error: %s";
 /* Name of Timelines Tab */
 localizedStrings["Timelines Tab Name"] = "Timelines";
+/* Text indicating that the local override will skip all network activity and instead immediately serve the response. */
+localizedStrings["Timeout @ Local Override Type"] = "Timeout";
 localizedStrings["Timer %d Fired"] = "Timer %d Fired";
 localizedStrings["Timer %d Installed"] = "Timer %d Installed";
 localizedStrings["Timer %d Removed"] = "Timer %d Removed";

Modified: trunk/Source/WebInspectorUI/UserInterface/Controllers/NetworkManager.js (293408 => 293409)


--- trunk/Source/WebInspectorUI/UserInterface/Controllers/NetworkManager.js	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/Source/WebInspectorUI/UserInterface/Controllers/NetworkManager.js	2022-04-26 04:07:01 UTC (rev 293409)
@@ -61,6 +61,7 @@
             WI.Resource.addEventListener(WI.SourceCode.Event.ContentDidChange, this._handleResourceContentChangedForLocalResourceOverride, this);
             WI.Resource.addEventListener(WI.Resource.Event.RequestDataDidChange, this._handleResourceContentChangedForLocalResourceOverride, this);
             WI.LocalResourceOverride.addEventListener(WI.LocalResourceOverride.Event.DisabledChanged, this._handleResourceOverrideDisabledChanged, this);
+            WI.LocalResourceOverride.addEventListener(WI.LocalResourceOverride.Event.ResourceErrorTypeChanged, this._handleResourceOverrideResourceErrorTypeChanged, this);
 
             WI.Target.registerInitializationPromise((async () => {
                 let serializedLocalResourceOverrides = await WI.objectStores.localResourceOverrides.getAll();
@@ -71,6 +72,10 @@
 
                     let supported = false;
                     switch (localResourceOverride.type) {
+                    case WI.LocalResourceOverride.InterceptType.Block:
+                        supported = WI.NetworkManager.supportsBlockingRequests();
+                        break;
+
                     case WI.LocalResourceOverride.InterceptType.Request:
                         supported = WI.NetworkManager.supportsOverridingRequests();
                         break;
@@ -115,6 +120,12 @@
             && InspectorBackend.hasCommand("Network.getSerializedCertificate");
     }
 
+    static supportsBlockingRequests()
+    {
+        // COMPATIBILITY (iOS 13.4): Network.interceptRequestWithError did not exist yet.
+        return InspectorBackend.hasCommand("Network.interceptRequestWithError");
+    }
+
     static supportsOverridingRequests()
     {
         // COMPATIBILITY (iOS 13.4): Network.interceptWithRequest did not exist yet.
@@ -959,6 +970,13 @@
             let revision = localResource.currentRevision;
 
             switch (localResourceOverride.type) {
+            case WI.LocalResourceOverride.InterceptType.Block:
+                target.NetworkAgent.interceptRequestWithError.invoke({
+                    requestId,
+                    errorType: localResourceOverride.resourceErrorType,
+                });
+                return;
+
             case WI.LocalResourceOverride.InterceptType.Request: {
                 target.NetworkAgent.interceptWithRequest.invoke({
                     requestId,
@@ -1504,6 +1522,14 @@
             this._addInterception(localResourceOverride);
     }
 
+    _handleResourceOverrideResourceErrorTypeChanged(event)
+    {
+        console.assert(WI.NetworkManager.supportsBlockingRequests());
+
+        let localResourceOverride = event.target;
+        WI.objectStores.localResourceOverrides.putObject(localResourceOverride);
+    }
+
     _handleBootstrapScriptContentDidChange(event)
     {
         let source = this._bootstrapScript.content || "";

Added: trunk/Source/WebInspectorUI/UserInterface/Images/SkipNetwork.svg (0 => 293409)


--- trunk/Source/WebInspectorUI/UserInterface/Images/SkipNetwork.svg	                        (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Images/SkipNetwork.svg	2022-04-26 04:07:01 UTC (rev 293409)
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright © 2022 Apple Inc. All rights reserved. -->
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
+    <defs>
+        <style>
+            g:not(:target) {
+                display: none;
+            }
+
+            #light {
+                --stroke: black;
+            }
+
+            #dark {
+                --stroke: white;
+            }
+        </style>
+    </defs>
+
+    <symbol id="i" viewBox="0 0 16 16" fill="none" stroke="var(--stroke)" stroke-width="1">
+        <circle cx="8" cy="8" r="7.5"/>
+        <path d="M 5.5 1.0 L 5.5 10.5"/>
+        <path d="M 3.0 9.0 L 5.5 11.0 L 8.0 9.0"/>
+        <path d="M 10.5 15.0 L 10.5 5.5"/>
+        <path d="M 13.0 7.0 L 10.5 5.0 L 8.0 7.0"/>
+        <path d="M 13.3 2.7 L 2.7 13.3"/>
+    </symbol>
+
+    <g id="light"><use href=""
+    <g id="dark"><use href=""
+</svg>

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/LocalResourceOverride.js (293408 => 293409)


--- trunk/Source/WebInspectorUI/UserInterface/Models/LocalResourceOverride.js	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/LocalResourceOverride.js	2022-04-26 04:07:01 UTC (rev 293409)
@@ -25,12 +25,13 @@
 
 WI.LocalResourceOverride = class LocalResourceOverride extends WI.Object
 {
-    constructor(url, type, localResource, {isCaseSensitive, isRegex, disabled} = {})
+    constructor(url, type, localResource, {resourceErrorType, isCaseSensitive, isRegex, disabled} = {})
     {
         console.assert(url && typeof url ="" "string", url);
         console.assert(Object.values(WI.LocalResourceOverride.InterceptType).includes(type), type);
         console.assert(localResource instanceof WI.LocalResource, localResource);
         console.assert(!localResource.localResourceOverride, localResource);
+        console.assert(!resourceErrorType || Object.values(WI.LocalResourceOverride.ResourceErrorType).includes(resourceErrorType), resourceErrorType);
         console.assert(isCaseSensitive === undefined || typeof isCaseSensitive === "boolean", isCaseSensitive);
         console.assert(isRegex === undefined || typeof isRegex === "boolean", isRegex);
         console.assert(disabled === undefined || typeof disabled === "boolean", disabled);
@@ -41,6 +42,7 @@
         this._urlComponents = null;
         this._type = type;
         this._localResource = localResource;
+        this._resourceErrorType = resourceErrorType || WI.LocalResourceOverride.ResourceErrorType.General;
         this._isCaseSensitive = isCaseSensitive !== undefined ? isCaseSensitive : true;
         this._isRegex = isRegex !== undefined ? isRegex : false;
         this._disabled = disabled !== undefined ? disabled : false;
@@ -50,11 +52,11 @@
 
     // Static
 
-    static create(url, type, {requestURL, requestMethod, requestHeaders, requestData, responseMIMEType, responseContent, responseBase64Encoded, responseStatusCode, responseStatusText, responseHeaders, isCaseSensitive, isRegex, disabled} = {})
+    static create(url, type, {requestURL, requestMethod, requestHeaders, requestData, responseMIMEType, responseContent, responseBase64Encoded, responseStatusCode, responseStatusText, responseHeaders, resourceErrorType, isCaseSensitive, isRegex, disabled} = {})
     {
         let localResource = new WI.LocalResource({
             request: {
-                url: requestURL || "",
+                url: requestURL || url || "",
                 method: requestMethod,
                 headers: requestHeaders,
                 data: requestData,
@@ -68,16 +70,37 @@
                 base64Encoded: responseBase64Encoded,
             },
         });
-        return new WI.LocalResourceOverride(url, type, localResource, {isCaseSensitive, isRegex, disabled});
+        return new WI.LocalResourceOverride(url, type, localResource, {resourceErrorType, isCaseSensitive, isRegex, disabled});
     }
 
+    static displayNameForNetworkStageOfType(type)
+    {
+        switch (type) {
+        case WI.LocalResourceOverride.InterceptType.Block:
+        case WI.LocalResourceOverride.InterceptType.Request:
+            return WI.UIString("Request Override", "Request Override @ Local Override Network Stage", "Text indicating that the local override replaces the request of the network activity.");
+
+        case WI.LocalResourceOverride.InterceptType.Response:
+        case WI.LocalResourceOverride.InterceptType.ResponseSkippingNetwork:
+            return WI.UIString("Response Override", "Response Override @ Local Override Network Stage", "Text indicating that the local override replaces the response of the network activity.");
+        }
+
+        console.assert(false, "Unknown type: ", type);
+        return "";
+    }
+
     static displayNameForType(type)
     {
         switch (type) {
+        case WI.LocalResourceOverride.InterceptType.Block:
+            return WI.UIString("Block", "Block @ Local Override Type", "Text indicating that the local override will always block the network activity.");
+
         case WI.LocalResourceOverride.InterceptType.Request:
             return WI.UIString("Request", "Request @ Local Override Type", "Text indicating that the local override intercepts the request phase of network activity.");
+
         case WI.LocalResourceOverride.InterceptType.Response:
             return WI.UIString("Response", "Response @ Local Override Type", "Text indicating that the local override intercepts the response phase of network activity.");
+
         case WI.LocalResourceOverride.InterceptType.ResponseSkippingNetwork:
             return WI.UIString("Response (skip network)", "Response (skip network) @ Local Override Type", "Text indicating that the local override will skip all network activity and instead immediately serve the response.");
         }
@@ -86,11 +109,31 @@
         return "";
     }
 
+    static displayNameForResourceErrorType(resourceErrorType)
+    {
+        switch (resourceErrorType) {
+        case WI.LocalResourceOverride.ResourceErrorType.AccessControl:
+            return WI.UIString("Access Control", "Access Control @ Local Override Type", "Text indicating that the local override will block the network activity with an access error.");
+
+        case WI.LocalResourceOverride.ResourceErrorType.Cancellation:
+            return WI.UIString("Cancellation", "Cancellation @ Local Override Type", "Text indicating that the local override will block the network activity with a cancellation error.");
+
+        case WI.LocalResourceOverride.ResourceErrorType.General:
+            return WI.UIString("General", "General @ Local Override Type", "Text indicating that the local override will block the network activity with a general error.");
+
+        case WI.LocalResourceOverride.ResourceErrorType.Timeout:
+            return WI.UIString("Timeout", "Timeout @ Local Override Type", "Text indicating that the local override will block the network activity with an timeout error.");
+        }
+
+        console.assert(false, "Unknown resource error type: ", resourceErrorType);
+        return "";
+    }
+
     // Import / Export
 
     static fromJSON(json)
     {
-        let {url, type, localResource: localResourceJSON, isCaseSensitive, isRegex, disabled} = json;
+        let {url, type, localResource: localResourceJSON, resourceErrorType, isCaseSensitive, isRegex, disabled} = json;
 
         let localResource = WI.LocalResource.fromJSON(localResourceJSON);
 
@@ -98,7 +141,7 @@
         url ??= localResource.url;
         type ??= WI.LocalResourceOverride.InterceptType.Response;
 
-        return new WI.LocalResourceOverride(url, type, localResource, {isCaseSensitive, isRegex, disabled});
+        return new WI.LocalResourceOverride(url, type, localResource, {resourceErrorType, isCaseSensitive, isRegex, disabled});
     }
 
     toJSON(key)
@@ -112,6 +155,9 @@
             disabled: this._disabled,
         };
 
+        if (this._resourceErrorType)
+            json.resourceErrorType = this._resourceErrorType;
+
         if (key === WI.ObjectStore.toJSONSymbol)
             json[WI.objectStores.localResourceOverrides.keyPath] = this._url;
 
@@ -133,6 +179,23 @@
         return this._urlComponents;
     }
 
+    get resourceErrorType()
+    {
+        return this._resourceErrorType;
+    }
+
+    set resourceErrorType(resourceErrorType)
+    {
+        console.assert(Object.values(WI.LocalResourceOverride.ResourceErrorType).includes(resourceErrorType), resourceErrorType);
+
+        if (this._resourceErrorType === resourceErrorType)
+            return;
+
+        this._resourceErrorType = resourceErrorType;
+
+        this.dispatchEventToListeners(WI.LocalResourceOverride.Event.ResourceErrorTypeChanged);
+    }
+
     get disabled()
     {
         return this._disabled;
@@ -170,6 +233,7 @@
             return false;
 
         switch (this._type) {
+        case WI.LocalResourceOverride.InterceptType.Block:
         case WI.LocalResourceOverride.InterceptType.Request:
             return false;
 
@@ -219,11 +283,20 @@
 WI.LocalResourceOverride.TypeIdentifier = "local-resource-override";
 
 WI.LocalResourceOverride.InterceptType = {
+    Block: "block",
     Request: "request",
     Response: "response",
     ResponseSkippingNetwork: "response-skipping-network",
 };
 
+WI.LocalResourceOverride.ResourceErrorType = {
+    AccessControl: "AccessControl",
+    Cancellation: "Cancellation",
+    General: "General",
+    Timeout: "Timeout",
+};
+
 WI.LocalResourceOverride.Event = {
     DisabledChanged: "local-resource-override-disabled-state-did-change",
+    ResourceErrorTypeChanged: "local-resource-override-resource-error-type-changed",
 };

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/Resource.js (293408 => 293409)


--- trunk/Source/WebInspectorUI/UserInterface/Models/Resource.js	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/Resource.js	2022-04-26 04:07:01 UTC (rev 293409)
@@ -184,12 +184,18 @@
     {
         let classes = [];
 
+        let localResourceOverride = resource.localResourceOverride || WI.networkManager.localResourceOverridesForURL(resource.url).filter((localResourceOverride) => !localResourceOverride.disabled)[0];
         let isOverride = !!resource.localResourceOverride;
         let wasOverridden = resource.responseSource === WI.Resource.ResponseSource.InspectorOverride;
-        let shouldBeOverridden = resource.isLoading() && WI.networkManager.localResourceOverridesForURL(resource.url).some((localResourceOverride) => !localResourceOverride.disabled);
-        if (isOverride || wasOverridden || shouldBeOverridden)
+        let shouldBeOverridden = resource.isLoading() && localResourceOverride;
+        let shouldBeBlocked = (resource.failed || isOverride) && localResourceOverride?.type === WI.LocalResourceOverride.InterceptType.Block;
+        if (isOverride || wasOverridden || shouldBeOverridden || shouldBeBlocked) {
             classes.push("override");
 
+            if (shouldBeBlocked || localResourceOverride?.type === WI.LocalResourceOverride.InterceptType.ResponseSkippingNetwork)
+                classes.push("skip-network");
+        }
+
         if (resource.type === WI.Resource.Type.Other) {
             if (resource.requestedByteRange)
                 classes.push("resource-type-range");

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ContentView.js (293408 => 293409)


--- trunk/Source/WebInspectorUI/UserInterface/Views/ContentView.js	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ContentView.js	2022-04-26 04:07:01 UTC (rev 293409)
@@ -108,7 +108,7 @@
         }
 
         if (representedObject instanceof WI.LocalResourceOverride) {
-            if (representedObject.type === WI.LocalResourceOverride.InterceptType.Request)
+            if (representedObject.type === WI.LocalResourceOverride.InterceptType.Block || representedObject.type === WI.LocalResourceOverride.InterceptType.Request)
                 return new WI.LocalResourceOverrideRequestContentView(representedObject);
             return WI.ContentView.createFromRepresentedObject(representedObject.localResource);
         }
@@ -271,7 +271,7 @@
             return representedObject.sourceCode;
 
         if (representedObject instanceof WI.LocalResourceOverride) {
-            if (representedObject.type !== WI.LocalResourceOverride.InterceptType.Request)
+            if (representedObject.type === WI.LocalResourceOverride.InterceptType.Response || representedObject.type === WI.LocalResourceOverride.InterceptType.ResponseSkippingNetwork)
                 return representedObject.localResource;
             return representedObject;
         }

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ContextMenuUtilities.js (293408 => 293409)


--- trunk/Source/WebInspectorUI/UserInterface/Views/ContextMenuUtilities.js	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ContextMenuUtilities.js	2022-04-26 04:07:01 UTC (rev 293409)
@@ -95,6 +95,13 @@
                     initiatorHint: WI.TabBrowser.TabNavigationInitiator.ContextMenu,
                 });
             });
+
+            if (WI.NetworkManager.supportsBlockingRequests()) {
+                contextMenu.appendItem(WI.UIString("Block Request URL"), async () => {
+                    let localResourceOverride = await sourceCode.createLocalResourceOverride(WI.LocalResourceOverride.InterceptType.Block);
+                    WI.networkManager.addLocalResourceOverride(localResourceOverride);
+                });
+            }
         } else {
             let localResourceOverride = WI.networkManager.localResourceOverridesForURL(sourceCode.url)[0];
             if (localResourceOverride) {

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/FontResourceContentView.js (293408 => 293409)


--- trunk/Source/WebInspectorUI/UserInterface/Views/FontResourceContentView.js	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/FontResourceContentView.js	2022-04-26 04:07:01 UTC (rev 293409)
@@ -120,9 +120,8 @@
         if (existingOverrides.length > 1)
             return false;
 
-        // Request overrides cannot be created/updated from a file as files don't have network info.
         let localResourceOverride = this.resource.localResourceOverride || existingOverrides[0];
-        if (localResourceOverride?.type === WI.LocalResourceOverride.InterceptType.Request)
+        if (localResourceOverride && !localResourceOverride.canMapToFile)
             return false;
 
         return event.dataTransfer.types.includes("Files");

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ImageResourceContentView.js (293408 => 293409)


--- trunk/Source/WebInspectorUI/UserInterface/Views/ImageResourceContentView.js	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ImageResourceContentView.js	2022-04-26 04:07:01 UTC (rev 293409)
@@ -178,9 +178,8 @@
         if (existingOverrides.length > 1)
             return false;
 
-        // Request overrides cannot be created/updated from a file as files don't have network info.
         let localResourceOverride = this.resource.localResourceOverride || existingOverrides[0];
-        if (localResourceOverride?.type === WI.LocalResourceOverride.InterceptType.Request)
+        if (localResourceOverride && !localResourceOverride.canMapToFile)
             return false;
 
         // Appear if the drop contains a file.

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideLabelView.js (293408 => 293409)


--- trunk/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideLabelView.js	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideLabelView.js	2022-04-26 04:07:01 UTC (rev 293409)
@@ -42,10 +42,7 @@
     {
         let labelElement = document.createElement("span");
         labelElement.className = "label";
-        if (this._localResourceOverride.type === WI.LocalResourceOverride.InterceptType.Request)
-            labelElement.textContent = WI.UIString("Request Override", "Request Override @ Local Override Content View", "Label indicating that the shown content is from a request local override.");
-        else
-            labelElement.textContent = WI.UIString("Response Override", "Response Override @ Local Override Content View", "Label indicating that the shown content is from a response local override.");
+        labelElement.textContent = WI.LocalResourceOverride.displayNameForNetworkStageOfType(this._localResourceOverride.type);
 
         let urlElement = document.createElement("span");
         urlElement.textContent = this._localResourceOverride.displayURL({full: true});

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverridePopover.css (293408 => 293409)


--- trunk/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverridePopover.css	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverridePopover.css	2022-04-26 04:07:01 UTC (rev 293409)
@@ -69,7 +69,7 @@
     width: 344px;
 }
 
-.popover .local-resource-override-popover-content.response .editor.url {
+.popover .local-resource-override-popover-content:is(.response, .block) .editor.url {
     width: 330px;
 }
 

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverridePopover.js (293408 => 293409)


--- trunk/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverridePopover.js	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverridePopover.js	2022-04-26 04:07:01 UTC (rev 293409)
@@ -76,7 +76,7 @@
             let {name, value} = node.data;
             if (!name || !value)
                 continue;
-            if (data.type !== WI.LocalResourceOverride.InterceptType.Request) {
+            if (data.type === WI.LocalResourceOverride.InterceptType.Response || data.type === WI.LocalResourceOverride.InterceptType.ResponseSkippingNetwork) {
                 if (name.toLowerCase() === "content-type")
                     continue;
                 if (name.toLowerCase() === "set-cookie")
@@ -125,7 +125,7 @@
 
         if (!data.responseMIMEType && data.requestURL) {
             data.responseMIMEType = WI.mimeTypeForFileExtension(WI.fileExtensionForURL(data.requestURL));
-            if (data.type !== WI.LocalResourceOverride.InterceptType.Request)
+            if (data.type === WI.LocalResourceOverride.InterceptType.Response || data.type === WI.LocalResourceOverride.InterceptType.ResponseSkippingNetwork)
                 headers["Content-Type"] = data.responseMIMEType;
         }
 
@@ -199,7 +199,7 @@
         };
 
         this._typeSelectElement = document.createElement("select");
-        for (let type of [WI.LocalResourceOverride.InterceptType.Request, WI.LocalResourceOverride.InterceptType.Response]) {
+        for (let type of [WI.LocalResourceOverride.InterceptType.Request, WI.LocalResourceOverride.InterceptType.Response, WI.LocalResourceOverride.InterceptType.Block]) {
             let optionElement = this._typeSelectElement.appendChild(document.createElement("option"));
             optionElement.textContent = WI.LocalResourceOverride.displayNameForType(type);
             optionElement.value = type;
@@ -501,10 +501,15 @@
         updateURLCodeMirrorMode();
 
         let toggleInputsForType = (initializeHeaders) => {
+            let isBlock = this._typeSelectElement.value === WI.LocalResourceOverride.InterceptType.Block;
             let isRequest = this._typeSelectElement.value === WI.LocalResourceOverride.InterceptType.Request;
+            let isResponse = this._typeSelectElement.value === WI.LocalResourceOverride.InterceptType.Response || this._typeSelectElement.value === WI.LocalResourceOverride.InterceptType.ResponseSkippingNetwork;
+
+            popoverContentElement.classList.toggle("block", isBlock);
             popoverContentElement.classList.toggle("request", isRequest);
-            popoverContentElement.classList.toggle("response", !isRequest);
+            popoverContentElement.classList.toggle("response", isResponse);
 
+            initializeHeaders &&= !isBlock;
             if (initializeHeaders) {
                 let headers = isRequest ? requestHeaders : responseHeaders;
                 for (let name in headers) {
@@ -519,14 +524,14 @@
             }
 
             if (requestURLRow)
-                requestURLRow.element.hidden = !isRequest;
+                requestURLRow.element.hidden = isResponse || isBlock;
             if (methodRowElement)
-                methodRowElement.hidden = !isRequest;
-
-            mimeTypeRow.element.hidden = isRequest;
-            statusCodeRow.element.hidden = isRequest;
+                methodRowElement.hidden = isResponse || isBlock;
+            mimeTypeRow.element.hidden = isRequest || isBlock;
+            statusCodeRow.element.hidden = isRequest || isBlock;
+            headersRow.hidden = isBlock;
             if (optionsRowElement)
-                optionsRowElement.hidden = isRequest;
+                optionsRowElement.hidden = isRequest || isBlock;
 
             if (isRequest) {
                 this._requestURLCodeMirror.refresh();
@@ -533,7 +538,9 @@
 
                 if (contentTypeDataGridNode.parent)
                     this._headersDataGrid.removeChild(contentTypeDataGridNode);
-            } else {
+            }
+
+            if (isResponse) {
                 this._mimeTypeCodeMirror.refresh();
                 this._statusCodeCodeMirror.refresh();
                 this._statusTextCodeMirror.refresh();
@@ -541,6 +548,7 @@
                 if (!contentTypeDataGridNode.parent)
                     this._headersDataGrid.insertChild(contentTypeDataGridNode, 0);
             }
+
             toggleHeadersDataGridVisibility();
         };
         toggleInputsForType(true);

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideRequestContentView.css (293408 => 293409)


--- trunk/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideRequestContentView.css	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideRequestContentView.css	2022-04-26 04:07:01 UTC (rev 293409)
@@ -37,3 +37,8 @@
 .content-view.text.local-resource-override-request > .message-text-view {
     top: var(--navigation-bar-height);
 }
+
+ .content-view.text.local-resource-override-request > .message-text-view select {
+    font-size: inherit;
+    vertical-align: text-bottom;
+ }

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideRequestContentView.js (293408 => 293409)


--- trunk/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideRequestContentView.js	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideRequestContentView.js	2022-04-26 04:07:01 UTC (rev 293409)
@@ -28,8 +28,9 @@
     constructor(localResourceOverride)
     {
         console.assert(localResourceOverride instanceof WI.LocalResourceOverride, localResourceOverride);
-        console.assert(localResourceOverride.type === WI.LocalResourceOverride.InterceptType.Request, localResourceOverride);
-        console.assert(WI.NetworkManager.supportsOverridingRequests());
+        console.assert(localResourceOverride.type === WI.LocalResourceOverride.InterceptType.Block || localResourceOverride.type === WI.LocalResourceOverride.InterceptType.Request, localResourceOverride);
+        console.assert(localResourceOverride.type !== WI.LocalResourceOverride.InterceptType.Block || WI.NetworkManager.supportsBlockingRequests(), localResourceOverride);
+        console.assert(localResourceOverride.type !== WI.LocalResourceOverride.InterceptType.Request || WI.NetworkManager.supportsOverridingRequests(), localResourceOverride);
 
         let localResource = localResourceOverride.localResource;
         let string = localResource.requestData || "";
@@ -68,11 +69,43 @@
         this.textEditor.readOnly = false;
         this.textEditor.addEventListener(WI.TextEditor.Event.ContentDidChange, this._handleTextEditorContentDidChange, this);
 
-        let requestMethod = this.representedObject.localResource.requestMethod;
-        if (!WI.HTTPUtilities.RequestMethodsWithBody.has(requestMethod)) {
-            let message = WI.createMessageTextView(WI.UIString("%s requests do not have a body").format(requestMethod));
-            this.element.appendChild(message);
+        let message = null;
+        switch (this.representedObject.type) {
+        case WI.LocalResourceOverride.InterceptType.Block: {
+            let selectElement = document.createElement("select");
+
+            function addOption(resourceErrorType) {
+                let optionElement = selectElement.appendChild(document.createElement("option"));
+                optionElement.textContent = WI.LocalResourceOverride.displayNameForResourceErrorType(resourceErrorType);
+                optionElement.value = resourceErrorType;
+            }
+            addOption(WI.LocalResourceOverride.ResourceErrorType.General);
+            addOption(WI.LocalResourceOverride.ResourceErrorType.Timeout);
+            addOption(WI.LocalResourceOverride.ResourceErrorType.Cancellation);
+            addOption(WI.LocalResourceOverride.ResourceErrorType.AccessControl);
+
+            selectElement.value = this.representedObject.resourceErrorType;
+            selectElement.addEventListener("change", (event) => {
+                this.representedObject.resourceErrorType = selectElement.value;
+            });
+
+            message = document.createDocumentFragment();
+            String.format(WI.UIString("Block URL with %s error"), [selectElement], String.standardFormatters, message, (a, b) => {
+                a.append(b);
+                return a;
+            });
+            break;
         }
+
+        case WI.LocalResourceOverride.InterceptType.Request: {
+            let requestMethod = this.representedObject.localResource.requestMethod;
+            if (!WI.HTTPUtilities.RequestMethodsWithBody.has(requestMethod))
+                message = WI.UIString("%s requests do not have a body").format(requestMethod);
+            break;
+        }
+        }
+        if (message)
+            this.element.appendChild(WI.createMessageTextView(message));
     }
 
     // Private

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideTreeElement.js (293408 => 293409)


--- trunk/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideTreeElement.js	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideTreeElement.js	2022-04-26 04:07:01 UTC (rev 293409)
@@ -132,7 +132,7 @@
 
         let wasSelected = this.selected;
 
-        if (serializedData.type !== WI.LocalResourceOverride.InterceptType.Request) {
+        if (serializedData.type === WI.LocalResourceOverride.InterceptType.Response || serializedData.type === WI.LocalResourceOverride.InterceptType.ResponseSkippingNetwork) {
             let revision = this._localResourceOverride.localResource.currentRevision;
             serializedData.responseContent = revision.content;
             serializedData.responseBase64Encoded = revision.base64Encoded;

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ResourceContentView.js (293408 => 293409)


--- trunk/Source/WebInspectorUI/UserInterface/Views/ResourceContentView.js	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ResourceContentView.js	2022-04-26 04:07:01 UTC (rev 293409)
@@ -87,7 +87,7 @@
                 this._createLocalResourceOverrideButtonNavigationItem = new WI.ButtonNavigationItem("create-local-resource-override", this.createLocalResourceOverrideTooltip, "Images/NavigationItemNetworkOverride.svg", 13, 14);
                 this._createLocalResourceOverrideButtonNavigationItem.enabled = false; // Enabled when the content is available.
                 this._createLocalResourceOverrideButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
-                if (WI.NetworkManager.supportsOverridingRequests())
+                if (WI.NetworkManager.supportsOverridingRequests() || WI.NetworkManager.supportsBlockingRequests())
                     WI.addMouseDownContextMenuHandlers(this._createLocalResourceOverrideButtonNavigationItem.element, this._populateCreateLocalResourceOverrideContextMenu.bind(this));
                 else
                     this._createLocalResourceOverrideButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleCreateLocalResourceOverride, this);
@@ -334,10 +334,12 @@
         if (!this._createLocalResourceOverrideButtonNavigationItem.enabled)
             return;
 
-        contextMenu.appendItem(WI.UIString("Create Request Local Override"), () => {
-            // Request overrides cannot be created from a file as files don't have network info.
-            this._createAndShowLocalResourceOverride(WI.LocalResourceOverride.InterceptType.Request);
-        });
+        if (WI.NetworkManager.supportsOverridingRequests()) {
+            contextMenu.appendItem(WI.UIString("Create Request Local Override"), () => {
+                // Request overrides cannot be created from a file as files don't have network info.
+                this._createAndShowLocalResourceOverride(WI.LocalResourceOverride.InterceptType.Request);
+            });
+        }
 
         contextMenu.appendItem(WI.UIString("Create Response Local Override"), () => {
             this._createAndShowLocalResourceOverride(WI.LocalResourceOverride.InterceptType.Response, {
@@ -344,6 +346,13 @@
                 requestInitialContent: !event.shiftKey,
             });
         });
+
+        if (WI.NetworkManager.supportsBlockingRequests()) {
+            contextMenu.appendItem(WI.UIString("Block Request URL"), async () => {
+                let localResourceOverride = await this._resource.createLocalResourceOverride(WI.LocalResourceOverride.InterceptType.Block);
+                WI.networkManager.addLocalResourceOverride(localResourceOverride);
+            });
+        }
     }
 
     _handleCreateLocalResourceOverride(event)

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ResourceIcons.css (293408 => 293409)


--- trunk/Source/WebInspectorUI/UserInterface/Views/ResourceIcons.css	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ResourceIcons.css	2022-04-26 04:07:01 UTC (rev 293409)
@@ -108,6 +108,15 @@
     content: url(../Images/ClippingIcons.svg#range-light);
 }
 
+.resource-icon.override.skip-network .icon {
+    content: url(../Images/SkipNetwork.svg#light);
+}
+
+body:not(.window-inactive, .window-docked-inactive) :is(.table, .data-grid):focus-within .selected .resource-icon.override.skip-network .icon,
+body:not(.window-inactive, .window-docked-inactive) .tree-outline:focus-within .selected.resource-icon.override.skip-network .icon {
+    content: url(../Images/SkipNetwork.svg#dark);
+}
+
 @media (prefers-color-scheme: dark) {
     .resource-icon .icon {
         content: url(../Images/DocumentIcons.svg#generic-dark);
@@ -189,4 +198,8 @@
     .resource-icon.resource-type-range .icon {
         content: url(../Images/ClippingIcons.svg#range-dark);
     }
+
+    .resource-icon.override.skip-network .icon {
+        content: url(../Images/SkipNetwork.svg#dark);
+    }
 }
\ No newline at end of file

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ResourceTreeElement.js (293408 => 293409)


--- trunk/Source/WebInspectorUI/UserInterface/Views/ResourceTreeElement.js	2022-04-26 03:53:13 UTC (rev 293408)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ResourceTreeElement.js	2022-04-26 04:07:01 UTC (rev 293409)
@@ -139,7 +139,7 @@
             this._resource.removeEventListener(WI.Resource.Event.URLDidChange, this._urlDidChange, this);
             this._resource.removeEventListener(WI.Resource.Event.TypeDidChange, this._typeDidChange, this);
             this._resource.removeEventListener(WI.Resource.Event.LoadingDidFinish, this.updateStatus, this);
-            this._resource.removeEventListener(WI.Resource.Event.LoadingDidFail, this.updateStatus, this);
+            this._resource.removeEventListener(WI.Resource.Event.LoadingDidFail, this._loadingDidFail, this);
             this._resource.removeEventListener(WI.Resource.Event.ResponseReceived, this._responseReceived, this);
         }
 
@@ -150,7 +150,7 @@
         resource.addEventListener(WI.Resource.Event.URLDidChange, this._urlDidChange, this);
         resource.addEventListener(WI.Resource.Event.TypeDidChange, this._typeDidChange, this);
         resource.addEventListener(WI.Resource.Event.LoadingDidFinish, this.updateStatus, this);
-        resource.addEventListener(WI.Resource.Event.LoadingDidFail, this.updateStatus, this);
+        resource.addEventListener(WI.Resource.Event.LoadingDidFail, this._loadingDidFail, this);
         resource.addEventListener(WI.Resource.Event.ResponseReceived, this._responseReceived, this);
 
         this._updateTitles();
@@ -260,14 +260,27 @@
 
     _updateIcon()
     {
+        let localResourceOverride = this._resource.localResourceOverride || WI.networkManager.localResourceOverridesForURL(this._resource.url).filter((localResourceOverride) => !localResourceOverride.disabled)[0];
         let isOverride = !!this._resource.localResourceOverride;
         let wasOverridden = this._resource.responseSource === WI.Resource.ResponseSource.InspectorOverride;
-        if (isOverride || wasOverridden)
+        let shouldBeOverridden = this._resource.isLoading() && localResourceOverride;
+        let shouldBeBlocked = (this._resource.failed || isOverride) && localResourceOverride?.type === WI.LocalResourceOverride.InterceptType.Block;
+        if (isOverride || wasOverridden || shouldBeOverridden || shouldBeBlocked) {
             this.addClassName("override");
-        else
+
+            if (shouldBeBlocked || localResourceOverride?.type === WI.LocalResourceOverride.InterceptType.ResponseSkippingNetwork)
+                this.addClassName("skip-network");
+        } else {
             this.removeClassName("override");
+            this.removeClassName("skip-network");
+        }
 
-        this.iconElement.title = wasOverridden ? WI.UIString("This resource was loaded from a local override") : "";
+        if (wasOverridden)
+            this.iconElement.title = WI.UIString("This resource was loaded from a local override");
+        else if (shouldBeBlocked)
+            this.iconElement.title = WI.UIString("This resource was blocked by a local override");
+        else
+            this.iconElement.title = "";
     }
 
     _urlDidChange(event)
@@ -284,6 +297,12 @@
         this.callFirstAncestorFunction("descendantResourceTreeElementTypeDidChange", [this, event.data.oldType]);
     }
 
+    _loadingDidFail(event)
+    {
+        this.updateStatus();
+        this._updateIcon();
+    }
+
     _responseReceived(event)
     {
         this._updateIcon();
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to