Title: [252614] trunk
Revision
252614
Author
[email protected]
Date
2019-11-18 17:30:51 -0800 (Mon, 18 Nov 2019)

Log Message

Web Inspector: Local Resource Overrides: allow substitution based on a url pattern
https://bugs.webkit.org/show_bug.cgi?id=202375

Reviewed by Brian Burg.

Source/_javascript_Core:

Often, websites will load content with a timestamp-based URL query parameter for
cache-busting or logging purposes. If a developer is trying to override these resources (or
is trying to have an existing override also match these resources), they'd need to edit the
local override's URL to match in addition to editing the resource that loads it (e.g. change
the <script> in an HTML file), which can sometimes be tricky of the content is dynamically
loaded (e.g. an XHR with a non-hardcoded URL).

Allowing for local overrides to be set using a regular _expression_ pattern would help resolve
this limitation.

* inspector/protocol/Network.json:
Add `isRegex` parameter to `Network.addInterception` and `Network.removeInterception`.

Source/WebCore:

Test: http/tests/inspector/network/local-resource-override-isRegex.html

Often, websites will load content with a timestamp-based URL query parameter for
cache-busting or logging purposes. If a developer is trying to override these resources (or
is trying to have an existing override also match these resources), they'd need to edit the
local override's URL to match in addition to editing the resource that loads it (e.g. change
the <script> in an HTML file), which can sometimes be tricky of the content is dynamically
loaded (e.g. an XHR with a non-hardcoded URL).

Allowing for local overrides to be set using a regular _expression_ pattern would help resolve
this limitation.

* inspector/agents/InspectorNetworkAgent.h:
(WebCore::InspectorNetworkAgent::Intercept): Added.
(WebCore::InspectorNetworkAgent::Intercept::operator== const): Added.
* inspector/agents/InspectorNetworkAgent.cpp:
(WebCore::InspectorNetworkAgent::disable):
(WebCore::InspectorNetworkAgent::shouldIntercept): Added.
(WebCore::InspectorNetworkAgent::addInterception):
(WebCore::InspectorNetworkAgent::removeInterception):
(WebCore::InspectorNetworkAgent::willInterceptRequest):
(WebCore::InspectorNetworkAgent::shouldInterceptResponse):

Source/WebInspectorUI:

Often, websites will load content with a timestamp-based URL query parameter for
cache-busting or logging purposes. If a developer is trying to override these resources (or
is trying to have an existing override also match these resources), they'd need to edit the
local override's URL to match in addition to editing the resource that loads it (e.g. change
the <script> in an HTML file), which can sometimes be tricky of the content is dynamically
loaded (e.g. an XHR with a non-hardcoded URL).

Allowing for local overrides to be set using a regular _expression_ pattern would help resolve
this limitation.

* UserInterface/Models/LocalResourceOverride.js:
(WI.LocalResourceOverride):
(WI.LocalResourceOverride.create):
(WI.LocalResourceOverride.fromJSON):
(WI.LocalResourceOverride.prototype.toJSON):
(WI.LocalResourceOverride.prototype.get isCaseSensitive): Added.
(WI.LocalResourceOverride.prototype.get isRegex): Added.
(WI.LocalResourceOverride.prototype.matches): Added.
(WI.LocalResourceOverride.prototype.saveIdentityToCookie):

* UserInterface/Controllers/NetworkManager.js:
(WI.NetworkManager):
(WI.NetworkManager.prototype.initializeTarget):
(WI.NetworkManager.prototype.get localResourceOverrides):
(WI.NetworkManager.prototype.addLocalResourceOverride):
(WI.NetworkManager.prototype.removeLocalResourceOverride):
(WI.NetworkManager.prototype.localResourceOverrideForURL):
(WI.NetworkManager.prototype.responseIntercepted):

* UserInterface/Views/LocalResourceOverridePopover.js:
(WI.LocalResourceOverridePopover):
(WI.LocalResourceOverridePopover.prototype.get serializedData):
(WI.LocalResourceOverridePopover.prototype.show):
(WI.LocalResourceOverridePopover.prototype._createEditor):

* UserInterface/Views/LocalResourceOverrideTreeElement.js:
(WI.LocalResourceOverrideTreeElement.prototype.get mainTitleText): Added.
(WI.LocalResourceOverrideTreeElement.prototype.onattach):
(WI.LocalResourceOverrideTreeElement.prototype.ondetach):
(WI.LocalResourceOverrideTreeElement.prototype.populateContextMenu):
(WI.LocalResourceOverrideTreeElement.prototype.willDismissPopover):
* UserInterface/Views/SourcesNavigationSidebarPanel.js:
(WI.SourcesNavigationSidebarPanel.prototype._willDismissLocalOverridePopover):

LayoutTests:

* http/tests/inspector/network/local-resource-override-basic.html:
* http/tests/inspector/network/local-resource-override-basic-expected.txt:

Modified Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (252613 => 252614)


--- trunk/LayoutTests/ChangeLog	2019-11-19 01:12:57 UTC (rev 252613)
+++ trunk/LayoutTests/ChangeLog	2019-11-19 01:30:51 UTC (rev 252614)
@@ -1,3 +1,13 @@
+2019-11-18  Devin Rousso  <[email protected]>
+
+        Web Inspector: Local Resource Overrides: allow substitution based on a url pattern
+        https://bugs.webkit.org/show_bug.cgi?id=202375
+
+        Reviewed by Brian Burg.
+
+        * http/tests/inspector/network/local-resource-override-basic.html:
+        * http/tests/inspector/network/local-resource-override-basic-expected.txt:
+
 2019-11-18  Megan Gardner  <[email protected]>
 
         Update dismiss-picker-using-keyboard.html test to work on iPad correctly

Modified: trunk/LayoutTests/http/tests/inspector/network/local-resource-override-basic-expected.txt (252613 => 252614)


--- trunk/LayoutTests/http/tests/inspector/network/local-resource-override-basic-expected.txt	2019-11-19 01:12:57 UTC (rev 252613)
+++ trunk/LayoutTests/http/tests/inspector/network/local-resource-override-basic-expected.txt	2019-11-19 01:30:51 UTC (rev 252614)
@@ -88,6 +88,45 @@
   X-Expected: PASS
 Content: PASS
 
+-- Running test case: LocalResourceOverride.URL.CaseSensitive
+Creating Local Resource Override for: http://127.0.0.1:8000/inspector/network/resources/override.txt?case=sensitive
+Triggering load...
+Resource Loaded:
+URL: http://127.0.0.1:8000/inspector/network/resources/override.txt?CaSe=SeNsItIvE
+MIME Type: text/plain
+Status: 200 OK
+Response Source: Symbol(inspector-override)
+Response Headers:
+  Content-Type: text/plain
+  X-Expected: PASS
+Content: PASS
+
+-- Running test case: LocalResourceOverride.URL.IsRegex
+Creating Local Resource Override for: \/override\.txt\?t=\d+
+Triggering load...
+Resource Loaded:
+URL: http://127.0.0.1:8000/inspector/network/resources/override.txt?t=123456789
+MIME Type: text/plain
+Status: 200 OK
+Response Source: Symbol(inspector-override)
+Response Headers:
+  Content-Type: text/plain
+  X-Expected: PASS
+Content: PASS
+
+-- Running test case: LocalResourceOverride.URL.IsCaseSensitiveRegex
+Creating Local Resource Override for: \/OvErRiDe\.TxT\?t=\d+
+Triggering load...
+Resource Loaded:
+URL: http://127.0.0.1:8000/inspector/network/resources/override.txt?t=123456789
+MIME Type: text/plain
+Status: 200 OK
+Response Source: Symbol(inspector-override)
+Response Headers:
+  Content-Type: text/plain
+  X-Expected: PASS
+Content: PASS
+
 -- Running test case: LocalResourceOverride.404
 Creating Local Resource Override for: http://127.0.0.1:8000/inspector/network/resources/override.txt
 Triggering load...

Modified: trunk/LayoutTests/http/tests/inspector/network/local-resource-override-basic.html (252613 => 252614)


--- trunk/LayoutTests/http/tests/inspector/network/local-resource-override-basic.html	2019-11-19 01:12:57 UTC (rev 252613)
+++ trunk/LayoutTests/http/tests/inspector/network/local-resource-override-basic.html	2019-11-19 01:30:51 UTC (rev 252614)
@@ -165,6 +165,55 @@
     });
 
     addTestCase({
+        name: "LocalResourceOverride.URL.CaseSensitive",
+        description: "Test override for a load with a fragment.",
+        _expression_: `triggerOverrideLoad("?CaSe=SeNsItIvE")`,
+        overrides: [{
+            url: "http://127.0.0.1:8000/inspector/network/resources/override.txt?case=sensitive",
+            mimeType: "text/plain",
+            content: "PASS",
+            base64Encoded: false,
+            statusCode: 200,
+            statusText: "OK",
+            headers: {"X-Expected": "PASS"},
+            isCaseSensitive: false,
+        }]
+    });
+
+    addTestCase({
+        name: "LocalResourceOverride.URL.IsRegex",
+        description: "Test override for a load with a fragment.",
+        _expression_: `triggerOverrideLoad("?t=123456789")`,
+        overrides: [{
+            url: "\\/override\\.txt\\?t=\\d+",
+            mimeType: "text/plain",
+            content: "PASS",
+            base64Encoded: false,
+            statusCode: 200,
+            statusText: "OK",
+            headers: {"X-Expected": "PASS"},
+            isRegex: true,
+        }]
+    });
+
+    addTestCase({
+        name: "LocalResourceOverride.URL.IsCaseSensitiveRegex",
+        description: "Test override for a load with a fragment.",
+        _expression_: `triggerOverrideLoad("?t=123456789")`,
+        overrides: [{
+            url: "\\/OvErRiDe\\.TxT\\?t=\\d+",
+            mimeType: "text/plain",
+            content: "PASS",
+            base64Encoded: false,
+            statusCode: 200,
+            statusText: "OK",
+            headers: {"X-Expected": "PASS"},
+            isCaseSensitive: false,
+            isRegex: true,
+        }]
+    });
+
+    addTestCase({
         name: "LocalResourceOverride.404",
         description: "Test for a 404 override.",
         _expression_: `triggerOverrideLoad()`,

Modified: trunk/Source/_javascript_Core/ChangeLog (252613 => 252614)


--- trunk/Source/_javascript_Core/ChangeLog	2019-11-19 01:12:57 UTC (rev 252613)
+++ trunk/Source/_javascript_Core/ChangeLog	2019-11-19 01:30:51 UTC (rev 252614)
@@ -1,3 +1,23 @@
+2019-11-18  Devin Rousso  <[email protected]>
+
+        Web Inspector: Local Resource Overrides: allow substitution based on a url pattern
+        https://bugs.webkit.org/show_bug.cgi?id=202375
+
+        Reviewed by Brian Burg.
+
+        Often, websites will load content with a timestamp-based URL query parameter for
+        cache-busting or logging purposes. If a developer is trying to override these resources (or
+        is trying to have an existing override also match these resources), they'd need to edit the
+        local override's URL to match in addition to editing the resource that loads it (e.g. change
+        the <script> in an HTML file), which can sometimes be tricky of the content is dynamically
+        loaded (e.g. an XHR with a non-hardcoded URL).
+
+        Allowing for local overrides to be set using a regular _expression_ pattern would help resolve
+        this limitation.
+
+        * inspector/protocol/Network.json:
+        Add `isRegex` parameter to `Network.addInterception` and `Network.removeInterception`.
+
 2019-11-18  Keith Rollin  <[email protected]>
 
         Move jsc from Resources to Helpers

Modified: trunk/Source/_javascript_Core/inspector/protocol/Network.json (252613 => 252614)


--- trunk/Source/_javascript_Core/inspector/protocol/Network.json	2019-11-19 01:12:57 UTC (rev 252613)
+++ trunk/Source/_javascript_Core/inspector/protocol/Network.json	2019-11-19 01:30:51 UTC (rev 252614)
@@ -239,6 +239,8 @@
             "description": "Add an interception.",
             "parameters": [
                 { "name": "url", "type": "string" },
+                { "name": "caseSensitive", "type": "boolean", "optional": true, "description": "If false, ignores letter casing of `url` parameter." },
+                { "name": "isRegex", "type": "boolean", "optional": true, "description": "If true, treats `url` parameter as a regular _expression_." },
                 { "name": "stage", "$ref": "NetworkStage", "optional": true, "description": "If not present this applies to all network stages." }
             ]
         },
@@ -247,6 +249,8 @@
             "description": "Remove an interception.",
             "parameters": [
                 { "name": "url", "type": "string" },
+                { "name": "caseSensitive", "type": "boolean", "optional": true, "description": "If false, ignores letter casing of `url` parameter." },
+                { "name": "isRegex", "type": "boolean", "optional": true, "description": "If true, treats `url` parameter as a regular _expression_." },
                 { "name": "stage", "$ref": "NetworkStage", "optional": true, "description": "If not present this applies to all network stages." }
             ]
         },

Modified: trunk/Source/WebCore/ChangeLog (252613 => 252614)


--- trunk/Source/WebCore/ChangeLog	2019-11-19 01:12:57 UTC (rev 252613)
+++ trunk/Source/WebCore/ChangeLog	2019-11-19 01:30:51 UTC (rev 252614)
@@ -1,3 +1,33 @@
+2019-11-18  Devin Rousso  <[email protected]>
+
+        Web Inspector: Local Resource Overrides: allow substitution based on a url pattern
+        https://bugs.webkit.org/show_bug.cgi?id=202375
+
+        Reviewed by Brian Burg.
+
+        Test: http/tests/inspector/network/local-resource-override-isRegex.html
+
+        Often, websites will load content with a timestamp-based URL query parameter for
+        cache-busting or logging purposes. If a developer is trying to override these resources (or
+        is trying to have an existing override also match these resources), they'd need to edit the
+        local override's URL to match in addition to editing the resource that loads it (e.g. change
+        the <script> in an HTML file), which can sometimes be tricky of the content is dynamically
+        loaded (e.g. an XHR with a non-hardcoded URL).
+
+        Allowing for local overrides to be set using a regular _expression_ pattern would help resolve
+        this limitation.
+
+        * inspector/agents/InspectorNetworkAgent.h:
+        (WebCore::InspectorNetworkAgent::Intercept): Added.
+        (WebCore::InspectorNetworkAgent::Intercept::operator== const): Added.
+        * inspector/agents/InspectorNetworkAgent.cpp:
+        (WebCore::InspectorNetworkAgent::disable):
+        (WebCore::InspectorNetworkAgent::shouldIntercept): Added.
+        (WebCore::InspectorNetworkAgent::addInterception):
+        (WebCore::InspectorNetworkAgent::removeInterception):
+        (WebCore::InspectorNetworkAgent::willInterceptRequest):
+        (WebCore::InspectorNetworkAgent::shouldInterceptResponse):
+
 2019-11-18  Andres Gonzalez  <[email protected]>
 
         Run AccessibilityController::rootElement on secondary thread to simulate HIServices during LayoutTests.

Modified: trunk/Source/WebCore/inspector/agents/InspectorNetworkAgent.cpp (252613 => 252614)


--- trunk/Source/WebCore/inspector/agents/InspectorNetworkAgent.cpp	2019-11-19 01:12:57 UTC (rev 252613)
+++ trunk/Source/WebCore/inspector/agents/InspectorNetworkAgent.cpp	2019-11-19 01:30:51 UTC (rev 252614)
@@ -80,6 +80,8 @@
 #include <_javascript_Core/JSCInlines.h>
 #include <_javascript_Core/ScriptCallStack.h>
 #include <_javascript_Core/ScriptCallStackFactory.h>
+#include <wtf/HashMap.h>
+#include <wtf/HashSet.h>
 #include <wtf/JSONValues.h>
 #include <wtf/Lock.h>
 #include <wtf/RefPtr.h>
@@ -87,6 +89,7 @@
 #include <wtf/persistence/PersistentEncoder.h>
 #include <wtf/text/Base64.h>
 #include <wtf/text/StringBuilder.h>
+#include <wtf/text/WTFString.h>
 
 typedef Inspector::NetworkBackendDispatcherHandler::LoadResourceCallback LoadResourceCallback;
 
@@ -830,7 +833,7 @@
 {
     m_enabled = false;
     m_interceptionEnabled = false;
-    m_interceptResponseURLs.clear();
+    m_intercepts.clear();
     m_instrumentingAgents.setInspectorNetworkAgent(nullptr);
     m_resourcesData->clear();
     m_extraRequestHeaders.clear();
@@ -840,6 +843,23 @@
     setResourceCachingDisabled(false);
 }
 
+bool InspectorNetworkAgent::shouldIntercept(URL url)
+{
+    url.removeFragmentIdentifier();
+
+    String urlString = url.string();
+    if (urlString.isEmpty())
+        return false;
+
+    for (auto& intercept : m_intercepts) {
+        auto regex = ContentSearchUtilities::createSearchRegex(intercept.url, intercept.caseSensitive, intercept.isRegex);
+        if (regex.match(urlString) != -1)
+            return true;
+    }
+
+    return false;
+}
+
 void InspectorNetworkAgent::continuePendingResponses()
 {
     for (auto& pendingInterceptResponse : m_pendingInterceptResponses.values())
@@ -1009,7 +1029,7 @@
         continuePendingResponses();
 }
 
-void InspectorNetworkAgent::addInterception(ErrorString& errorString, const String& url, const String* networkStageString)
+void InspectorNetworkAgent::addInterception(ErrorString& errorString, const String& url, const bool* optionalCaseSensitive, const bool* optionalIsRegex, const String* networkStageString)
 {
     if (networkStageString) {
         auto networkStage = Inspector::Protocol::InspectorHelpers::parseEnumValueFromString<Inspector::Protocol::Network::NetworkStage>(*networkStageString);
@@ -1019,13 +1039,20 @@
         }
     }
 
+    Intercept intercept;
+    intercept.url = ""
+    if (optionalCaseSensitive)
+        intercept.caseSensitive = *optionalCaseSensitive;
+    if (optionalIsRegex)
+        intercept.isRegex = *optionalIsRegex;
+
     // FIXME: Support intercepting requests.
 
-    if (!m_interceptResponseURLs.add(url).isNewEntry)
-        errorString = "Intercept for given url already exists"_s;
+    if (!m_intercepts.appendIfNotContains(intercept))
+        errorString = "Intercept for given url and given isRegex already exists"_s;
 }
 
-void InspectorNetworkAgent::removeInterception(ErrorString& errorString, const String& url, const String* networkStageString)
+void InspectorNetworkAgent::removeInterception(ErrorString& errorString, const String& url, const bool* optionalCaseSensitive, const bool* optionalIsRegex, const String* networkStageString)
 {
     if (networkStageString) {
         auto networkStage = Inspector::Protocol::InspectorHelpers::parseEnumValueFromString<Inspector::Protocol::Network::NetworkStage>(*networkStageString);
@@ -1035,10 +1062,17 @@
         }
     }
 
+    Intercept intercept;
+    intercept.url = ""
+    if (optionalCaseSensitive)
+        intercept.caseSensitive = *optionalCaseSensitive;
+    if (optionalIsRegex)
+        intercept.isRegex = *optionalIsRegex;
+
     // FIXME: Support intercepting requests.
 
-    if (!m_interceptResponseURLs.remove(url))
-        errorString = "Missing intercept for given url"_s;
+    if (!m_intercepts.removeAll(intercept))
+        errorString = "Missing intercept for given url and given isRegex"_s;
 }
 
 bool InspectorNetworkAgent::willInterceptRequest(const ResourceRequest& request)
@@ -1046,14 +1080,7 @@
     if (!m_interceptionEnabled)
         return false;
 
-    URL requestURL = request.url();
-    requestURL.removeFragmentIdentifier();
-
-    String url = ""
-    if (url.isEmpty())
-        return false;
-
-    return m_interceptResponseURLs.contains(url);
+    return shouldIntercept(request.url());
 }
 
 bool InspectorNetworkAgent::shouldInterceptResponse(const ResourceResponse& response)
@@ -1061,14 +1088,7 @@
     if (!m_interceptionEnabled)
         return false;
 
-    URL responseURL = response.url();
-    responseURL.removeFragmentIdentifier();
-
-    String url = ""
-    if (url.isEmpty())
-        return false;
-
-    return m_interceptResponseURLs.contains(url);
+    return shouldIntercept(response.url());
 }
 
 void InspectorNetworkAgent::interceptResponse(const ResourceResponse& response, unsigned long identifier, CompletionHandler<void(const ResourceResponse&, RefPtr<SharedBuffer>)>&& handler)

Modified: trunk/Source/WebCore/inspector/agents/InspectorNetworkAgent.h (252613 => 252614)


--- trunk/Source/WebCore/inspector/agents/InspectorNetworkAgent.h	2019-11-19 01:12:57 UTC (rev 252613)
+++ trunk/Source/WebCore/inspector/agents/InspectorNetworkAgent.h	2019-11-19 01:30:51 UTC (rev 252614)
@@ -37,9 +37,8 @@
 #include <_javascript_Core/InspectorBackendDispatchers.h>
 #include <_javascript_Core/InspectorFrontendDispatchers.h>
 #include <_javascript_Core/RegularExpression.h>
-#include <wtf/HashSet.h>
+#include <wtf/Forward.h>
 #include <wtf/JSONValues.h>
-#include <wtf/text/WTFString.h>
 
 namespace Inspector {
 class InjectedScriptManager;
@@ -89,8 +88,8 @@
     void getSerializedCertificate(ErrorString&, const String& requestId, String* serializedCertificate) final;
     void resolveWebSocket(ErrorString&, const String& requestId, const String* objectGroup, RefPtr<Inspector::Protocol::Runtime::RemoteObject>&) final;
     void setInterceptionEnabled(ErrorString&, bool enabled) final;
-    void addInterception(ErrorString&, const String& url, const String* networkStageString) final;
-    void removeInterception(ErrorString&, const String& url, const String* networkStageString) final;
+    void addInterception(ErrorString&, const String& url, const bool* caseSensitive, const bool* isRegex, const String* networkStageString) final;
+    void removeInterception(ErrorString&, const String& url, const bool* caseSensitive, const bool* isRegex, const String* networkStageString) final;
     void interceptContinue(ErrorString&, const String& requestId) final;
     void interceptWithResponse(ErrorString&, const String& requestId, const String& content, bool base64Encoded, const String* mimeType, const int* status, const String* statusText, const JSON::Object* headers) final;
 
@@ -141,6 +140,7 @@
 
     void willSendRequest(unsigned long identifier, DocumentLoader*, ResourceRequest&, const ResourceResponse& redirectResponse, InspectorPageAgent::ResourceType);
 
+    bool shouldIntercept(URL);
     void continuePendingResponses();
 
     WebSocket* webSocketForRequestId(const String& requestId);
@@ -200,7 +200,19 @@
     HashMap<String, String> m_extraRequestHeaders;
     HashSet<unsigned long> m_hiddenRequestIdentifiers;
 
-    HashSet<String> m_interceptResponseURLs;
+    struct Intercept {
+        String url;
+        bool caseSensitive { true };
+        bool isRegex { false };
+
+        inline bool operator==(const Intercept& other) const
+        {
+            return url == other.url
+                && caseSensitive == other.caseSensitive
+                && isRegex == other.isRegex;
+        }
+    };
+    Vector<Intercept> m_intercepts;
     HashMap<String, std::unique_ptr<PendingInterceptResponse>> m_pendingInterceptResponses;
 
     // FIXME: InspectorNetworkAgent should not be aware of style recalculation.

Modified: trunk/Source/WebInspectorUI/ChangeLog (252613 => 252614)


--- trunk/Source/WebInspectorUI/ChangeLog	2019-11-19 01:12:57 UTC (rev 252613)
+++ trunk/Source/WebInspectorUI/ChangeLog	2019-11-19 01:30:51 UTC (rev 252614)
@@ -1,3 +1,54 @@
+2019-11-18  Devin Rousso  <[email protected]>
+
+        Web Inspector: Local Resource Overrides: allow substitution based on a url pattern
+        https://bugs.webkit.org/show_bug.cgi?id=202375
+
+        Reviewed by Brian Burg.
+
+        Often, websites will load content with a timestamp-based URL query parameter for
+        cache-busting or logging purposes. If a developer is trying to override these resources (or
+        is trying to have an existing override also match these resources), they'd need to edit the
+        local override's URL to match in addition to editing the resource that loads it (e.g. change
+        the <script> in an HTML file), which can sometimes be tricky of the content is dynamically
+        loaded (e.g. an XHR with a non-hardcoded URL).
+
+        Allowing for local overrides to be set using a regular _expression_ pattern would help resolve
+        this limitation.
+
+        * UserInterface/Models/LocalResourceOverride.js:
+        (WI.LocalResourceOverride):
+        (WI.LocalResourceOverride.create):
+        (WI.LocalResourceOverride.fromJSON):
+        (WI.LocalResourceOverride.prototype.toJSON):
+        (WI.LocalResourceOverride.prototype.get isCaseSensitive): Added.
+        (WI.LocalResourceOverride.prototype.get isRegex): Added.
+        (WI.LocalResourceOverride.prototype.matches): Added.
+        (WI.LocalResourceOverride.prototype.saveIdentityToCookie):
+
+        * UserInterface/Controllers/NetworkManager.js:
+        (WI.NetworkManager):
+        (WI.NetworkManager.prototype.initializeTarget):
+        (WI.NetworkManager.prototype.get localResourceOverrides):
+        (WI.NetworkManager.prototype.addLocalResourceOverride):
+        (WI.NetworkManager.prototype.removeLocalResourceOverride):
+        (WI.NetworkManager.prototype.localResourceOverrideForURL):
+        (WI.NetworkManager.prototype.responseIntercepted):
+
+        * UserInterface/Views/LocalResourceOverridePopover.js:
+        (WI.LocalResourceOverridePopover):
+        (WI.LocalResourceOverridePopover.prototype.get serializedData):
+        (WI.LocalResourceOverridePopover.prototype.show):
+        (WI.LocalResourceOverridePopover.prototype._createEditor):
+
+        * UserInterface/Views/LocalResourceOverrideTreeElement.js:
+        (WI.LocalResourceOverrideTreeElement.prototype.get mainTitleText): Added.
+        (WI.LocalResourceOverrideTreeElement.prototype.onattach):
+        (WI.LocalResourceOverrideTreeElement.prototype.ondetach):
+        (WI.LocalResourceOverrideTreeElement.prototype.populateContextMenu):
+        (WI.LocalResourceOverrideTreeElement.prototype.willDismissPopover):
+        * UserInterface/Views/SourcesNavigationSidebarPanel.js:
+        (WI.SourcesNavigationSidebarPanel.prototype._willDismissLocalOverridePopover):
+
 2019-11-15  Devin Rousso  <[email protected]>
 
         Web Inspector: Elements: Styles: support multiline CSS property values

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


--- trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js	2019-11-19 01:12:57 UTC (rev 252613)
+++ trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js	2019-11-19 01:30:51 UTC (rev 252614)
@@ -46,6 +46,7 @@
 localizedStrings["%dpx\u00B2"] = "%dpx\u00B2";
 localizedStrings["%s (%s)"] = "%s (%s)";
 localizedStrings["%s (%s, %s)"] = "%s (%s, %s)";
+localizedStrings["%s (Case Insensitive)"] = "%s (Case Insensitive)";
 localizedStrings["%s (default)"] = "%s (default)";
 localizedStrings["%s (hidden)"] = "%s (hidden)";
 localizedStrings["%s Callback"] = "%s Callback";

Modified: trunk/Source/WebInspectorUI/UserInterface/Controllers/NetworkManager.js (252613 => 252614)


--- trunk/Source/WebInspectorUI/UserInterface/Controllers/NetworkManager.js	2019-11-19 01:12:57 UTC (rev 252613)
+++ trunk/Source/WebInspectorUI/UserInterface/Controllers/NetworkManager.js	2019-11-19 01:30:51 UTC (rev 252614)
@@ -43,7 +43,7 @@
         this._sourceMapURLMap = new Map;
         this._downloadingSourceMaps = new Set;
 
-        this._localResourceOverrideMap = new Map;
+        this._localResourceOverrides = new Set;
         this._harImportLocalResourceMap = new Set;
 
         this._pendingLocalResourceOverrideSaves = null;
@@ -158,9 +158,15 @@
                 if (this._interceptionEnabled)
                     target.NetworkAgent.setInterceptionEnabled(this._interceptionEnabled);
 
-                for (let [url, localResourceOverride] of this._localResourceOverrideMap) {
-                    if (!localResourceOverride.disabled)
-                        target.NetworkAgent.addInterception(localResourceOverride.url, InspectorBackend.Enum.Network.NetworkStage.Response);
+                for (let localResourceOverride of this._localResourceOverrides) {
+                    if (!localResourceOverride.disabled) {
+                        target.NetworkAgent.addInterception.invoke({
+                            url: localResourceOverride.url,
+                            caseSensitive: localResourceOverride.isCaseSensitive,
+                            isRegex: localResourceOverride.isRegex,
+                            networkStage: InspectorBackend.Enum.Network.NetworkStage.Response,
+                        });
+                    }
                 }
             }
         }
@@ -187,7 +193,7 @@
 
     get localResourceOverrides()
     {
-        return Array.from(this._localResourceOverrideMap.values());
+        return Array.from(this._localResourceOverrides);
     }
 
     get interceptionEnabled()
@@ -350,17 +356,24 @@
     {
         console.assert(localResourceOverride instanceof WI.LocalResourceOverride);
 
-        console.assert(!this._localResourceOverrideMap.get(localResourceOverride.url), "Already had an existing local resource override.");
-        this._localResourceOverrideMap.set(localResourceOverride.url, localResourceOverride);
+        console.assert(!this._localResourceOverrides.has(localResourceOverride), "Already had an existing local resource override.");
+        this._localResourceOverrides.add(localResourceOverride);
 
         if (!this._restoringLocalResourceOverrides)
             WI.objectStores.localResourceOverrides.putObject(localResourceOverride);
 
         if (!localResourceOverride.disabled) {
+            let commandArguments = {
+                url: localResourceOverride.url,
+                caseSensitive: localResourceOverride.isCaseSensitive,
+                isRegex: localResourceOverride.isRegex,
+                networkStage: InspectorBackend.Enum.Network.NetworkStage.Response,
+            };
+
             // COMPATIBILITY (iOS 13.0): Network.addInterception did not exist.
             for (let target of WI.targets) {
                 if (target.hasCommand("Network.addInterception"))
-                    target.NetworkAgent.addInterception(localResourceOverride.url, InspectorBackend.Enum.Network.NetworkStage.Response);
+                    target.NetworkAgent.addInterception.invoke(commandArguments);
             }
         }
 
@@ -371,7 +384,7 @@
     {
         console.assert(localResourceOverride instanceof WI.LocalResourceOverride);
 
-        if (!this._localResourceOverrideMap.delete(localResourceOverride.url)) {
+        if (!this._localResourceOverrides.delete(localResourceOverride)) {
             console.assert(false, "Attempted to remove a local resource override that was not known.");
             return;
         }
@@ -383,10 +396,17 @@
             WI.objectStores.localResourceOverrides.deleteObject(localResourceOverride);
 
         if (!localResourceOverride.disabled) {
+            let commandArguments = {
+                url: localResourceOverride.url,
+                caseSensitive: localResourceOverride.isCaseSensitive,
+                isRegex: localResourceOverride.isRegex,
+                networkStage: InspectorBackend.Enum.Network.NetworkStage.Response,
+            };
+
             // COMPATIBILITY (iOS 13.0): Network.removeInterception did not exist.
             for (let target of WI.targets) {
                 if (target.hasCommand("Network.removeInterception"))
-                    target.NetworkAgent.removeInterception(localResourceOverride.url, InspectorBackend.Enum.Network.NetworkStage.Response);
+                    target.NetworkAgent.removeInterception.invoke(commandArguments);
             }
         }
 
@@ -395,7 +415,11 @@
 
     localResourceOverrideForURL(url)
     {
-        return this._localResourceOverrideMap.get(url);
+        for (let localResourceOverride of this._localResourceOverrides) {
+            if (localResourceOverride.matches(url))
+                return localResourceOverride;
+        }
+        return null;
     }
 
     canBeOverridden(resource)
@@ -923,7 +947,7 @@
     responseIntercepted(target, requestId, response)
     {
         let url = ""
-        let localResourceOverride = this._localResourceOverrideMap.get(url);
+        let localResourceOverride = this.localResourceOverrideForURL(url);
         if (!localResourceOverride || localResourceOverride.disabled) {
             target.NetworkAgent.interceptContinue(requestId);
             return;
@@ -1387,13 +1411,20 @@
         let localResourceOverride = event.target;
         WI.objectStores.localResourceOverrides.putObject(localResourceOverride);
 
+        let commandArguments = {
+            url: localResourceOverride.url,
+            caseSensitive: localResourceOverride.isCaseSensitive,
+            isRegex: localResourceOverride.isRegex,
+            networkStage: InspectorBackend.Enum.Network.NetworkStage.Response,
+        };
+
         // COMPATIBILITY (iOS 13.0): Network.addInterception / Network.removeInterception did not exist.
         for (let target of WI.targets) {
             if (target.hasDomain("Network")) {
                 if (localResourceOverride.disabled)
-                    target.NetworkAgent.removeInterception(localResourceOverride.url, InspectorBackend.Enum.Network.NetworkStage.Response);
+                    target.NetworkAgent.removeInterception.invoke(commandArguments);
                 else
-                    target.NetworkAgent.addInterception(localResourceOverride.url, InspectorBackend.Enum.Network.NetworkStage.Response);
+                    target.NetworkAgent.addInterception.invoke(commandArguments);
             }
         }
     }

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/LocalResourceOverride.js (252613 => 252614)


--- trunk/Source/WebInspectorUI/UserInterface/Models/LocalResourceOverride.js	2019-11-19 01:12:57 UTC (rev 252613)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/LocalResourceOverride.js	2019-11-19 01:30:51 UTC (rev 252614)
@@ -25,26 +25,30 @@
 
 WI.LocalResourceOverride = class LocalResourceOverride extends WI.Object
 {
-    constructor(localResource, {disabled} = {})
+    constructor(localResource, {isCaseSensitive, isRegex, disabled} = {})
     {
         console.assert(localResource instanceof WI.LocalResource);
         console.assert(localResource.isLocalResourceOverride);
         console.assert(localResource.url);
-        console.assert(!disabled || typeof disabled === "boolean");
+        console.assert(isCaseSensitive === undefined || typeof isCaseSensitive === "boolean");
+        console.assert(isRegex === undefined || typeof isRegex === "boolean");
+        console.assert(disabled === undefined || typeof disabled === "boolean");
 
         super();
 
         this._localResource = localResource;
-        this._disabled = disabled || false;
+        this._isCaseSensitive = isCaseSensitive !== undefined ? isCaseSensitive : true;
+        this._isRegex = isRegex !== undefined ? isRegex : false;
+        this._disabled = disabled !== undefined ? disabled : false;
     }
 
     // Static
 
-    static create({url, mimeType, content, base64Encoded, statusCode, statusText, headers, disabled})
+    static create({url, mimeType, content, base64Encoded, statusCode, statusText, headers, isCaseSensitive, isRegex, disabled})
     {
         let localResource = new WI.LocalResource({
             request: {
-                url: WI.urlWithoutFragment(url),
+                url: isRegex ? url : WI.urlWithoutFragment(url),
             },
             response: {
                 headers,
@@ -57,7 +61,7 @@
             isLocalResourceOverride: true,
         });
 
-        return new WI.LocalResourceOverride(localResource, {disabled});
+        return new WI.LocalResourceOverride(localResource, {isCaseSensitive, isRegex, disabled});
     }
 
     // Import / Export
@@ -64,8 +68,8 @@
 
     static fromJSON(json)
     {
-        let {localResource, disabled} = json;
-        return new WI.LocalResourceOverride(WI.LocalResource.fromJSON(localResource), {disabled});
+        let {localResource, isCaseSensitive, isRegex, disabled} = json;
+        return new WI.LocalResourceOverride(WI.LocalResource.fromJSON(localResource), {isCaseSensitive, isRegex, disabled});
     }
 
     toJSON(key)
@@ -72,6 +76,8 @@
     {
         let json = {
             localResource: this._localResource.toJSON(key),
+            isCaseSensitive: this._isCaseSensitive,
+            isRegex: this._isRegex,
             disabled: this._disabled,
         };
 
@@ -85,6 +91,8 @@
 
     get url() { return this._localResource.url; }
     get localResource() { return this._localResource; }
+    get isCaseSensitive() { return this._isCaseSensitive; }
+    get isRegex() { return this._isRegex; }
 
     get disabled()
     {
@@ -101,11 +109,26 @@
         this.dispatchEventToListeners(WI.LocalResourceOverride.Event.DisabledChanged);
     }
 
+    matches(url)
+    {
+        if (this._isRegex) {
+            let regex = new RegExp(this.url, !this._isCaseSensitive ? "i" : "");
+            return regex.test(url);
+        }
+
+        if (!this._isCaseSensitive)
+            return String(url).toLowerCase() === this.url.toLowerCase();
+
+        return url ="" this.url;
+    }
+
     // Protected
 
     saveIdentityToCookie(cookie)
     {
         cookie["local-resource-override-url"] = this._localResource.url;
+        cookie["local-resource-override-is-case-sensitive"] = this._isCaseSensitive;
+        cookie["local-resource-override-is-regex"] = this._isRegex;
         cookie["local-resource-override-disabled"] = this._disabled;
     }
 };

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverridePopover.css (252613 => 252614)


--- trunk/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverridePopover.css	2019-11-19 01:12:57 UTC (rev 252613)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverridePopover.css	2019-11-19 01:30:51 UTC (rev 252614)
@@ -69,6 +69,14 @@
     width: 324px;
 }
 
+.popover .local-resource-override-popover-content label:matches(.is-case-sensitive, .is-regex) {
+    display: inline-block;
+}
+
+.popover .local-resource-override-popover-content label.is-case-sensitive {
+    -webkit-margin-end: 8px;
+}
+
 .popover .local-resource-override-popover-content .editor.mime {
     width: 240px;
 }

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverridePopover.js (252613 => 252614)


--- trunk/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverridePopover.js	2019-11-19 01:12:57 UTC (rev 252613)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverridePopover.js	2019-11-19 01:30:51 UTC (rev 252614)
@@ -30,6 +30,8 @@
         super(delegate);
 
         this._urlCodeMirror = null;
+        this._isCaseSensitiveCheckbox = null;
+        this._isRegexCheckbox = null;
         this._mimeTypeCodeMirror = null;
         this._statusCodeCodeMirror = null;
         this._statusTextCodeMirror = null;
@@ -51,9 +53,12 @@
         if (!url)
             return null;
 
-        const schemes = ["http:", "https:", "file:"];
-        if (!schemes.some((scheme) => url.startsWith(scheme)))
-            return null;
+        let isRegex = this._isRegexCheckbox && this._isRegexCheckbox.checked;
+        if (!isRegex) {
+            const schemes = ["http:", "https:", "file:"];
+            if (!schemes.some((scheme) => url.toLowerCase().startsWith(scheme)))
+                return null;
+        }
 
         // NOTE: We can allow an empty mimeType / statusCode / statusText to pass
         // network values through, but lets require them for overrides so that
@@ -91,6 +96,12 @@
             headers,
         };
 
+        if (this._isCaseSensitiveCheckbox)
+            data.isCaseSensitive = this._isCaseSensitiveCheckbox.checked;
+
+        if (this._isRegexCheckbox)
+            data.isRegex = this._isRegexCheckbox.checked;
+
         // No change.
         let oldSerialized = JSON.stringify(this._serializedDataWhenShown);
         let newSerialized = JSON.stringify(data);
@@ -100,11 +111,13 @@
         return data;
     }
 
-    show(localResource, targetElement, preferredEdges)
+    show(localResourceOverride, targetElement, preferredEdges)
     {
         this._targetElement = targetElement;
         this._preferredEdges = preferredEdges;
 
+        let localResource = localResourceOverride ? localResourceOverride.localResource : null;
+
         let url = "" ? localResource.url : "";
         let mimeType = localResource ? localResource.mimeType : "";
         let statusCode = localResource ? String(localResource.statusCode) : "";
@@ -125,7 +138,7 @@
 
         let table = popoverContentElement.appendChild(document.createElement("table"));
 
-        let createRow = (label, id, text) => {
+        let createRow = (label, id, text, placeholder) => {
             let row = table.appendChild(document.createElement("tr"));
             let headerElement = row.appendChild(document.createElement("th"));
             let dataElement = row.appendChild(document.createElement("td"));
@@ -136,7 +149,7 @@
             let editorElement = dataElement.appendChild(document.createElement("div"));
             editorElement.classList.add("editor", id);
 
-            let codeMirror = this._createEditor(editorElement, text);
+            let codeMirror = this._createEditor(editorElement, text, placeholder);
             let inputField = codeMirror.getInputField();
             inputField.id = `local-resource-override-popover-${id}-input-field`;
             labelElement.setAttribute("for", inputField.id);
@@ -144,19 +157,56 @@
             return {codeMirror, dataElement};
         };
 
-        let urlRow = createRow(WI.UIString("URL"), "url", url);
+        let urlRow = createRow(WI.UIString("URL"), "url", url, url || "http://example.com/index.html");
         this._urlCodeMirror = urlRow.codeMirror;
-        this._urlCodeMirror.setOption("mode", "text/x-local-override-url");
 
-        let mimeTypeRow = createRow(WI.UIString("MIME Type"), "mime", mimeType);
+        let updateURLCodeMirrorMode = () => {
+            let isRegex = this._isRegexCheckbox && this._isRegexCheckbox.checked;
+
+            this._urlCodeMirror.setOption("mode", isRegex ? "text/x-regex" : "text/x-local-override-url");
+
+            if (!isRegex) {
+                let url = ""
+                const schemes = ["http:", "https:", "file:"];
+                if (!schemes.some((scheme) => url.toLowerCase().startsWith(scheme)))
+                    this._urlCodeMirror.setValue("http://" + url);
+            }
+        };
+
+        if (InspectorBackend.hasCommand("Network.addInterception", "caseSensitive")) {
+            let isCaseSensitiveLabel = urlRow.dataElement.appendChild(document.createElement("label"));
+            isCaseSensitiveLabel.className = "is-case-sensitive";
+
+            this._isCaseSensitiveCheckbox = isCaseSensitiveLabel.appendChild(document.createElement("input"));
+            this._isCaseSensitiveCheckbox.type = "checkbox";
+            this._isCaseSensitiveCheckbox.checked = localResourceOverride ? localResourceOverride.isCaseSensitive : true;
+
+            isCaseSensitiveLabel.append(WI.UIString("Case Sensitive"));
+        }
+
+        if (InspectorBackend.hasCommand("Network.addInterception", "isRegex")) {
+            let isRegexLabel = urlRow.dataElement.appendChild(document.createElement("label"));
+            isRegexLabel.className = "is-regex";
+
+            this._isRegexCheckbox = isRegexLabel.appendChild(document.createElement("input"));
+            this._isRegexCheckbox.type = "checkbox";
+            this._isRegexCheckbox.checked = localResourceOverride ? localResourceOverride.isRegex : false;
+            this._isRegexCheckbox.addEventListener("change", (event) => {
+                updateURLCodeMirrorMode();
+            });
+
+            isRegexLabel.append(WI.UIString("Regular _expression_"));
+        }
+
+        let mimeTypeRow = createRow(WI.UIString("MIME Type"), "mime", mimeType, mimeType || "text/html");
         this._mimeTypeCodeMirror = mimeTypeRow.codeMirror;
 
-        let statusCodeRow = createRow(WI.UIString("Status"), "status", statusCode);
+        let statusCodeRow = createRow(WI.UIString("Status"), "status", statusCode, statusCode || "200");
         this._statusCodeCodeMirror = statusCodeRow.codeMirror;
 
         let statusTextEditorElement = statusCodeRow.dataElement.appendChild(document.createElement("div"));
         statusTextEditorElement.className = "editor status-text";
-        this._statusTextCodeMirror = this._createEditor(statusTextEditorElement, statusText);
+        this._statusTextCodeMirror = this._createEditor(statusTextEditorElement, statusText, statusText || "OK");
 
         let editCallback = () => {};
         let deleteCallback = (node) => {
@@ -278,6 +328,9 @@
 
         // Update mimeType when URL gets a file extension.
         this._urlCodeMirror.on("change", (cm) => {
+            if (this._isRegexCheckbox && this._isRegexCheckbox.checked)
+                return;
+
             let extension = WI.fileExtensionForURL(cm.getValue());
             if (!extension)
                 return;
@@ -296,6 +349,8 @@
             contentTypeDataGridNode.data = "" "Content-Type", value: mimeType};
         });
 
+        updateURLCodeMirrorMode();
+
         this._serializedDataWhenShown = this.serializedData;
 
         this.content = popoverContentElement;
@@ -315,7 +370,7 @@
 
     // Private
 
-    _createEditor(element, value)
+    _createEditor(element, value, placeholder)
     {
         let codeMirror = WI.CodeMirrorEditor.create(element, {
             extraKeys: {"Tab": false, "Shift-Tab": false},
@@ -322,7 +377,7 @@
             lineWrapping: false,
             mode: "text/plain",
             matchBrackets: true,
-            placeholder: "http://example.com/index.html",
+            placeholder,
             scrollbarStyle: null,
             value,
         });

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideTreeElement.js (252613 => 252614)


--- trunk/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideTreeElement.js	2019-11-19 01:12:57 UTC (rev 252613)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideTreeElement.js	2019-11-19 01:30:51 UTC (rev 252614)
@@ -40,6 +40,21 @@
 
     // Protected
 
+    get mainTitleText()
+    {
+        let text;
+        if (this.representedObject.isRegex) {
+            text = "/" + this.resource.url + "/";
+            if (!this.representedObject.isCaseSensitive)
+                text += "i";
+        } else {
+            text = super.mainTitleText;
+            if (!this.representedObject.isCaseSensitive)
+                text = WI.UIString("%s (Case Insensitive)").format(text);
+        }
+        return text;
+    }
+
     onattach()
     {
         super.onattach();
@@ -86,7 +101,7 @@
     {
         contextMenu.appendItem(WI.UIString("Edit Local Override\u2026"), (event) => {
             let popover = new WI.LocalResourceOverridePopover(this);
-            popover.show(this._localResourceOverride.localResource, this.status, [WI.RectEdge.MAX_X, WI.RectEdge.MIN_X]);
+            popover.show(this._localResourceOverride, this.status, [WI.RectEdge.MAX_X, WI.RectEdge.MIN_X]);
         });
 
         let toggleEnabledString = this._localResourceOverride.disabled ? WI.UIString("Enable Local Override") : WI.UIString("Disable Local Override");
@@ -114,7 +129,7 @@
         if (!serializedData)
             return;
 
-        let {url, mimeType, statusCode, statusText, headers} = serializedData;
+        let {url, isCaseSensitive, isRegex, mimeType, statusCode, statusText, headers} = serializedData;
 
         // Do not conflict with an existing override unless we are modifying ourselves.
         let existingOverride = WI.networkManager.localResourceOverrideForURL(url);
@@ -128,6 +143,8 @@
         let revision = this._localResourceOverride.localResource.currentRevision;
         let newLocalResourceOverride = WI.LocalResourceOverride.create({
             url,
+            isCaseSensitive,
+            isRegex,
             mimeType,
             statusCode,
             statusText,

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/SourcesNavigationSidebarPanel.js (252613 => 252614)


--- trunk/Source/WebInspectorUI/UserInterface/Views/SourcesNavigationSidebarPanel.js	2019-11-19 01:12:57 UTC (rev 252613)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/SourcesNavigationSidebarPanel.js	2019-11-19 01:30:51 UTC (rev 252614)
@@ -667,7 +667,7 @@
             return;
         }
 
-        let {url, mimeType, statusCode, statusText, headers} = serializedData;
+        let {url, isCaseSensitive, isRegex, mimeType, statusCode, statusText, headers} = serializedData;
 
         // Do not conflict with an existing override.
         let existingOverride = WI.networkManager.localResourceOverrideForURL(url);
@@ -678,6 +678,8 @@
 
         let localResourceOverride = WI.LocalResourceOverride.create({
             url,
+            isCaseSensitive,
+            isRegex,
             mimeType,
             statusCode,
             statusText,
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to