Web Inspector: Local Overrides - Provide substitution content for resource loads...
authorjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 4 Sep 2019 23:35:03 +0000 (23:35 +0000)
committerjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 4 Sep 2019 23:35:03 +0000 (23:35 +0000)
https://bugs.webkit.org/show_bug.cgi?id=201262
<rdar://problem/13108764>

Reviewed by Devin Rousso.

Source/JavaScriptCore:

When interception is enabled, Network requests that match any of the configured
interception patterns will be paused on the backend and allowed to be modified
by the frontend.

Currently the only time a network request can be intercepted is during the
HTTP response. However, this intercepting interface is mean to extend to
HTTP requests as well.

When a response is to be intercepted a new event is sent to the frontend:

  `Network.responseIntercepted` event

With a `requestId` to identify that network request. The frontend
must respond with one of the following commands to continue:

  `Network.interceptContinue`     - proceed with the response unmodified
  `Network.interceptWithResponse` - provide a response

The response is paused in the meantime.

* inspector/protocol/Network.json:
New interfaces for intercepting network responses and suppling override content.

* Scripts/generate-combined-inspector-json.py:
* inspector/scripts/generate-inspector-protocol-bindings.py:
(generate_from_specification.load_specification):
Complete allowing comments in JSON protocol files.

* inspector/scripts/codegen/generate_objc_backend_dispatcher_implementation.py:
(ObjCBackendDispatcherImplementationGenerator._generate_invocation_for_command):
* inspector/scripts/tests/generic/expected/commands-with-optional-call-return-parameters.json-result:
Allow optional enums in ObjC interfaces.

Source/WebCore:

Tests: http/tests/inspector/network/local-resource-override-basic.html
       http/tests/inspector/network/local-resource-override-main-resource.html
       http/tests/inspector/network/local-resource-override-script-tag.html
       http/tests/inspector/network/resource-response-inspector-override.html

* Sources.txt:
* WebCore.xcodeproj/project.pbxproj:
* Headers.cmake:
New files.

* inspector/InspectorInstrumentation.cpp:
(WebCore::InspectorInstrumentation::willInterceptRequestImpl):
(WebCore::InspectorInstrumentation::shouldInterceptResponseImpl):
(WebCore::InspectorInstrumentation::interceptResponseImpl):
* inspector/InspectorInstrumentation.h:
(WebCore::InspectorInstrumentation::hasFrontends):
(WebCore::InspectorInstrumentation::willInterceptRequest):
(WebCore::InspectorInstrumentation::shouldInterceptResponse):
(WebCore::InspectorInstrumentation::interceptResponse):
(WebCore::InspectorInstrumentation::frontendCreated):
(WebCore::InspectorInstrumentation::frontendDeleted):
* inspector/InspectorInstrumentationPublic.cpp:
* inspector/InspectorInstrumentationPublic.h:
* inspector/InspectorInstrumentationWebKit.cpp:
(WebCore::InspectorInstrumentationWebKit::shouldInterceptResponseInternal):
(WebCore::InspectorInstrumentationWebKit::interceptResponseInternal):
* inspector/InspectorInstrumentationWebKit.h: Added.
(WebCore::InspectorInstrumentationWebKit::shouldInterceptResponse):
(WebCore::InspectorInstrumentationWebKit::interceptResponse):
Provide a slim InspectorInstrumentation API that can be used in the WebKit
layer without a ton of includes.

* inspector/agents/InspectorNetworkAgent.cpp:
(WebCore::responseSource):
(WebCore::InspectorNetworkAgent::disable):
(WebCore::InspectorNetworkAgent::continuePendingResponses):
(WebCore::InspectorNetworkAgent::setInterceptionEnabled):
(WebCore::InspectorNetworkAgent::addInterception):
(WebCore::InspectorNetworkAgent::removeInterception):
(WebCore::InspectorNetworkAgent::willInterceptRequest):
(WebCore::InspectorNetworkAgent::shouldInterceptResponse):
(WebCore::InspectorNetworkAgent::interceptResponse):
(WebCore::InspectorNetworkAgent::interceptContinue):
(WebCore::InspectorNetworkAgent::interceptWithResponse):
Manage a list of URLs that will be intercepted and send
intercepts to an active frontend for response content.

* inspector/agents/InspectorNetworkAgent.h:
(WebCore::InspectorNetworkAgent::PendingInterceptResponse::PendingInterceptResponse):
(WebCore::InspectorNetworkAgent::PendingInterceptResponse::~PendingInterceptResponse):
(WebCore::InspectorNetworkAgent::PendingInterceptResponse::originalResponse):
(WebCore::InspectorNetworkAgent::PendingInterceptResponse::respondWithOriginalResponse):
(WebCore::InspectorNetworkAgent::PendingInterceptResponse::respond):
Callback for an eventual intercept response.

* platform/network/ResourceResponseBase.h:
New ResponseSource - Inspector Override.

* loader/DocumentLoader.cpp:
(WebCore::logResourceResponseSource):
* testing/Internals.cpp:
(WebCore::responseSourceToString):
Handle new response sources.

* loader/cache/CachedResourceLoader.cpp:
(WebCore::CachedResourceLoader::requestResource):
(WebCore::CachedResourceLoader::preload):
Avoid preloading or using the cache for URLs that would be intercepted
by an active Inspector frontend.

* loader/cache/MemoryCache.cpp:
(WebCore::MemoryCache::remove):
Assertion to help detect if we ever get override content into the MemoryCache.

* loader/ResourceLoader.h:
(WebCore::DocumentLoader::responseReceived):
* loader/ResourceLoader.cpp:
Fix typos.

Source/WebInspectorUI:

This adds a new "Local Overrides" section to the Sources tab sidebar
which will allow users to provide their own resource content for text
resources. Users can clone a resource, and provide their own content
(by editing in Web Inspector) and new requests for those particular
URLs will get the substitute content.

Overrides are based on a particular URL (ignoring fragment). They
can override: status code, status text, response headers, content,
and MIME Type (Content-Type).

* Tools/CodeMirrorModes/index.html: Added.
* Tools/CodeMirrorModes/styles.css: Added.
Debug tool for CodeMirror editors and our custom CodeMirror modes.

* UserInterface/Main.html:
* UserInterface/Test.html:
* Localizations/en.lproj/localizedStrings.js:
New files and strings.

* UserInterface/Base/HTTPUtilities.js: Added.
(WI.httpStatusTextForStatusCode):
Translate between typical status codes and status text.

* UserInterface/Base/ObjectStore.js:
(WI.ObjectStore._open):
New persistent store for local resource overrides.

* UserInterface/Base/Main.js:
(WI.showLocalResourceOverride):
Convenience for showing an override file.

* UserInterface/Base/URLUtilities.js:
(parseURL):
Avoid uncaught exceptions with the URL constructor for common WebKit internal sourceURL strings.

(WI.urlWithoutFragment):
Strip a fragment from a URL.

* UserInterface/Controllers/HARBuilder.js:
(WI.HARBuilder.fetchType):
(WI.HARBuilder.responseSourceFromHARFetchType):
Handle new custom response types.

* UserInterface/Protocol/NetworkObserver.js:
(WI.NetworkObserver.prototype.responseIntercepted):
(WI.NetworkObserver):
New events.

* UserInterface/Controllers/NetworkManager.js:
(WI.NetworkManager):
(WI.NetworkManager.supportsLocalResourceOverrides):
(WI.NetworkManager.prototype.initializeTarget):
(WI.NetworkManager.prototype.get localResourceOverrides):
(WI.NetworkManager.prototype.get interceptionEnabled):
(WI.NetworkManager.prototype.set interceptionEnabled):
(WI.NetworkManager.prototype.addLocalResourceOverride):
(WI.NetworkManager.prototype.removeLocalResourceOverride):
(WI.NetworkManager.prototype.localResourceOverrideForURL):
(WI.NetworkManager.prototype.canBeOverridden):
(WI.NetworkManager.prototype.responseIntercepted):
(WI.NetworkManager.prototype._handleResourceContentDidChange):
(WI.NetworkManager.prototype._persistLocalResourceOverrideSoonAfterContentChange):
(WI.NetworkManager.prototype._saveLocalResourceOverrides):
(WI.NetworkManager.prototype._extraDomainsActivated):
(WI.NetworkManager.prototype.localResourceForURL): Deleted.
Handle saving and restoring local resource overrides.
Handle responding to a `responseIntercepted` Network protocol event.

* UserInterface/Models/LocalResource.js:
(WI.LocalResource.fromJSON):
(WI.LocalResource.prototype.toJSON):
(WI.LocalResource.prototype.get localContent):
(WI.LocalResource.prototype.get localContentIsBase64Encoded):
(WI.LocalResource.prototype.isLocalResourceOverride):
(WI.LocalResource.prototype.updateOverrideContent):
Allow a LocalResource to identify itself as an "override".

* UserInterface/Models/LocalResourceOverride.js: Added.
(WI.LocalResourceOverride.prototype.create):
(WI.LocalResourceOverride.fromJSON):
(WI.LocalResourceOverride.prototype.toJSON):
(WI.LocalResourceOverride.prototype.get url):
(WI.LocalResourceOverride.prototype.get localResource):
(WI.LocalResourceOverride.prototype.get disabled):
(WI.LocalResourceOverride.prototype.set disabled):
(WI.LocalResourceOverride.prototype.saveIdentityToCookie):
(WI.LocalResourceOverride):
Model object for a LocalResourceOverride. This has LocalResource content
and an enabled/disabled state.

* UserInterface/Models/Resource.js:
(WI.Resource.classNamesForResource):
(WI.Resource.responseSourceFromPayload):
(WI.Resource.prototype.isLocalResourceOverride):
(WI.Resource.prototype.async.createLocalResourceOverride):
(WI.Resource.classNameForResource): Deleted.
Convenience functions and icon updates.

* UserInterface/Views/SourcesTabContentView.js:
(WI.SourcesTabContentView.prototype.canShowRepresentedObject):
* UserInterface/Views/ContentView.js:
(WI.ContentView.createFromRepresentedObject):
(WI.ContentView.resolvedRepresentedObjectForRepresentedObject):
(WI.ContentView.isViewable):
Handle new represented object type.

* UserInterface/Views/SourcesNavigationSidebarPanel.js:
(WI.SourcesNavigationSidebarPanel):
(WI.SourcesNavigationSidebarPanel.prototype.createContentTreeOutline):
(WI.SourcesNavigationSidebarPanel.prototype.willDismissPopover):
(WI.SourcesNavigationSidebarPanel.prototype._willDismissLocalOverridePopover):
(WI.SourcesNavigationSidebarPanel.prototype._willDismissEventBreakpointPopover):
(WI.SourcesNavigationSidebarPanel.prototype._willDismissURLBreakpointPopover):
(WI.SourcesNavigationSidebarPanel.prototype._addLocalResourceOverride):
(WI.SourcesNavigationSidebarPanel.prototype._removeLocalResourceOverride):
(WI.SourcesNavigationSidebarPanel.prototype._handleTreeSelectionDidChange):
(WI.SourcesNavigationSidebarPanel.prototype._populateCreateBreakpointContextMenu):
(WI.SourcesNavigationSidebarPanel.prototype._handleLocalResourceOverrideAdded):
(WI.SourcesNavigationSidebarPanel.prototype._handleLocalResourceOverrideRemoved):
* UserInterface/Views/SourcesNavigationSidebarPanel.css:
(.sidebar > .panel.navigation.sources > .content > .warning-banner):
(.sidebar > .panel.navigation.sources > .content > :matches(.pause-reason-container, .call-stack-container, .breakpoints-container, .local-overrides)):
(.sidebar > .panel.navigation.sources > .content > .local-overrides):
(.sidebar > .panel.navigation.sources > .content > :matches(.pause-reason-container, .call-stack-container, .breakpoints-container)): Deleted.
Hide and show Local Overrides section.

* UserInterface/Views/LocalResourceOverrideTreeElement.css:
(.item.resource.override .status > div):
* UserInterface/Views/LocalResourceOverrideTreeElement.js: Added.
(WI.LocalResourceOverrideTreeElement):
(WI.LocalResourceOverrideTreeElement.prototype.canSelectOnMouseDown):
(WI.LocalResourceOverrideTreeElement.prototype.populateContextMenu):
(WI.LocalResourceOverrideTreeElement.prototype.willDismissPopover):
TreeElement for a Local Resource Override.

* UserInterface/Views/CodeMirrorLocalOverrideURLMode.css:
(.cm-s-default .cm-local-override-url-bad-scheme):
(.cm-s-default .cm-local-override-url-fragment):
* UserInterface/Views/CodeMirrorLocalOverrideURLMode.js: Added.
(tokenBase):
(return.startState):
(return.token):
* UserInterface/Views/ContentBrowserTabContentView.js:
(WI.ContentBrowserTabContentView.prototype._revealAndSelectRepresentedObject):

* UserInterface/Views/ContextMenu.js:
(WI.ContextMenu.prototype._itemSelected):
(WI.ContextMenu):
Better debugging for exceptions in context menu handlers.

* UserInterface/Views/ContextMenuUtilities.js:
(WI.appendContextMenuItemsForSourceCode):
(WI.appendContextMenuItemsForURL):
Context menu items for Local Resource Overrides.

* UserInterface/Views/DataGrid.js:
(WI.DataGrid.prototype.startEditingNode):
(WI.DataGrid.prototype._startEditingNodeAtColumnIndex):
(WI.DataGrid.prototype._startEditing):
(WI.DataGrid.prototype._contextMenuInDataTable):
* UserInterface/Views/DataGridNode.js:
(WI.DataGridNode):
(WI.DataGridNode.prototype.get editable):
(WI.DataGridNode.prototype.set editable):
Improve DataGrid editing functionality.
Allow a node to not be editable.
Allow adding a new node and starting to edit in one action.

* UserInterface/Views/DebuggerSidebarPanel.js:
(WI.DebuggerSidebarPanel.prototype.treeElementForRepresentedObject):
Do not provide overrides in the Debugger tab.

* UserInterface/Views/LocalResourceOverrideLabelView.css:
(.local-resource-override-label-view):
(.local-resource-override-label-view > div):
(.local-resource-override-label-view > div > .label):
(.local-resource-override-label-view > div > .url):
(@media (prefers-color-scheme: dark)):
* UserInterface/Views/LocalResourceOverrideLabelView.js:
(WI.LocalResourceOverrideLabelView):
(WI.LocalResourceOverrideLabelView.prototype.initialLayout):
* UserInterface/Views/LocalResourceOverridePopover.css: Added.
(.popover .local-resource-override-popover-content):
(.popover .local-resource-override-popover-content > label.toggle):
(.popover .local-resource-override-popover-content > table):
(.popover .local-resource-override-popover-content > table > tr > th):
(.popover .local-resource-override-popover-content > table > tr > td):
(.popover .local-resource-override-popover-content .editor):
(.popover .local-resource-override-popover-content .editor > .CodeMirror):
(.popover .local-resource-override-popover-content .editor.url):
(.popover .local-resource-override-popover-content .editor.mime):
(.popover .local-resource-override-popover-content .editor.status):
(.popover .local-resource-override-popover-content .editor.status-text):
(.popover .local-resource-override-popover-content .add-header):
(@media (prefers-color-scheme: dark)):
New banner view for a local resource override itself.
Shows the URL being overriden.

* UserInterface/Views/LocalResourceOverrideWarningView.css:
(.local-resource-override-warning-view):
(.local-resource-override-warning-view[hidden]):
(.local-resource-override-warning-view > div):
(.local-resource-override-warning-view > div > button):
(@media (prefers-color-scheme: dark)):
* UserInterface/Views/LocalResourceOverrideWarningView.js: Added.
(WI.LocalResourceOverrideWarningView):
(WI.LocalResourceOverrideWarningView.prototype.attached):
(WI.LocalResourceOverrideWarningView.prototype.detached):
(WI.LocalResourceOverrideWarningView.prototype._updateContent):
(WI.LocalResourceOverrideWarningView.prototype._handleLocalResourceOverrideChanged):
* UserInterface/Views/NavigationSidebarPanel.js:
(WI.NavigationSidebarPanel.prototype.pruneStaleResourceTreeElements):
New banner view for a resource that has been overridden.
Allows jumping to the override itself.

* UserInterface/Views/LocalResourceOverridePopover.js: Added.
(WI.LocalResourceOverridePopover):
(WI.LocalResourceOverridePopover.prototype.get serializedData):
(WI.LocalResourceOverridePopover.prototype.show.addDataGridNodeForHeader):
(WI.LocalResourceOverridePopover.prototype.show):
(WI.LocalResourceOverridePopover.prototype._createEditor):
(WI.LocalResourceOverridePopover.prototype._defaultURL):
(WI.LocalResourceOverridePopover.prototype._presentOverTargetElement):
New popover for creating or editing a Local Resource Override.

* UserInterface/Views/SearchSidebarPanel.js:
(WI.SearchSidebarPanel.prototype.performSearch):
Consider searching overrides.

* UserInterface/Views/Variables.css:
(:root):
* UserInterface/Views/SearchSidebarPanel.css:
(.sidebar > .panel.navigation.search.changed > .banner):
* UserInterface/Views/DebuggerSidebarPanel.css:
(.sidebar > .panel.navigation.debugger .warning-banner):
* UserInterface/Views/ConsoleMessageView.css:
(.console-warning-level):
Use a new variable for a common warning color.

* UserInterface/Images/NavigationItemNetworkOverride.svg: Added.
* UserInterface/Views/SourceCodeTextEditor.js:
(WI.SourceCodeTextEditor.prototype.canBeFormatted):
(WI.SourceCodeTextEditor.prototype.get _supportsDebugging):
* UserInterface/Views/SourcesNavigationSidebarPanel.css:
(.sidebar > .panel.navigation.sources > .content > .warning-banner):
(.sidebar > .panel.navigation.sources > .content > :matches(.pause-reason-container, .call-stack-container, .breakpoints-container, .local-overrides)):
(.sidebar > .panel.navigation.sources > .content > .local-overrides):
(.sidebar > .panel.navigation.sources > .content > :matches(.pause-reason-container, .call-stack-container, .breakpoints-container)): Deleted.
* UserInterface/Views/TextEditor.css:
(.text-editor):
* UserInterface/Views/TextResourceContentView.css:
(.content-view.resource.text):
(.content-view.resource.text > .text-editor):
* UserInterface/Views/TextResourceContentView.js:
(WI.TextResourceContentView):
(WI.TextResourceContentView.prototype.get navigationItems):
(WI.TextResourceContentView.prototype.closed):
(WI.TextResourceContentView.prototype._contentWillPopulate):
(WI.TextResourceContentView.prototype._contentDidPopulate):
(WI.TextResourceContentView.prototype.async._handleCreateLocalResourceOverride):
(WI.TextResourceContentView.prototype._handleRemoveLocalResourceOverride):
(WI.TextResourceContentView.prototype._handleLocalResourceOverrideChanged):
(WI.TextResourceContentView.prototype._textEditorContentDidChange):
(WI.TextResourceContentView.prototype._shouldBeEditable):
Allow Text resources to create a local resource override.
Support for Image resources will come separately.

* UserInterface/Views/ResourceHeadersContentView.js:
(WI.ResourceHeadersContentView.prototype._responseSourceDisplayString):
Handle new response type.

* UserInterface/Controllers/CSSManager.js:
Avoid extra handling for Local Resource Overrides.

* UserInterface/Views/ResourceIcons.css:
(.resource-icon.override .icon):
* UserInterface/Views/ResourceSizesContentView.js:
(WI.ResourceSizesContentView.prototype.initialLayout):
* UserInterface/Views/ResourceTimelineDataGridNode.js:
(WI.ResourceTimelineDataGridNode.prototype.iconClassNames):
* UserInterface/Views/ResourceTreeElement.js:
(WI.ResourceTreeElement.prototype._updateResource):
(WI.ResourceTreeElement.prototype._updateIcon):
(WI.ResourceTreeElement.prototype._responseReceived):
(WI.ResourceTreeElement):
* UserInterface/Views/TimelineDataGridNode.js:
(WI.TimelineDataGridNode.prototype.createCellContent):
* UserInterface/Views/NetworkTableContentView.js:
(WI.NetworkTableContentView.prototype._populateNameCell):
(WI.NetworkTableContentView.prototype._populateTransferSizeCell):
(WI.NetworkTableContentView.prototype._generateSortComparator):
Better Resource icons all over for overrides.

* UserInterface/Views/URLBreakpointPopover.js:
(WI.URLBreakpointPopover.prototype._createEditor):
Code cleanup.

Source/WebKit:

* Sources.txt:
* WebKit.xcodeproj/project.pbxproj:
New sources.

* WebProcess/Network/WebResourceLoader.h:
* WebProcess/Network/WebResourceLoader.cpp:
(WebKit::WebResourceLoader::didReceiveResponse):
(WebKit::WebResourceLoader::didReceiveData):
(WebKit::WebResourceLoader::didFinishResourceLoad):
(WebKit::WebResourceLoader::didFailResourceLoad):
On receiving a response, check with the inspector if an active
frontend will override the response content.

* WebProcess/Network/WebResourceInterceptController.h:
* WebProcess/Network/WebResourceInterceptController.cpp:
(WebKit::WebResourceInterceptController::isIntercepting const):
(WebKit::WebResourceInterceptController::beginInterceptingResponse):
(WebKit::WebResourceInterceptController::continueResponse):
(WebKit::WebResourceInterceptController::interceptedResponse):
(WebKit::WebResourceInterceptController::defer):
Buffer networking callbacks for an ongoing intercept.

* WebProcess/Network/WebLoaderStrategy.cpp:
(WebKit::WebLoaderStrategy::havePerformedSecurityChecks const):
Handle new response source.

LayoutTests:

* http/tests/inspector/network/local-resource-override-basic-expected.txt: Added.
* http/tests/inspector/network/local-resource-override-basic.html: Added.
* http/tests/inspector/network/local-resource-override-main-resource-expected.txt: Added.
* http/tests/inspector/network/local-resource-override-main-resource.html: Added.
* http/tests/inspector/network/local-resource-override-script-tag-expected.txt: Added.
* http/tests/inspector/network/local-resource-override-script-tag.html: Added.
* http/tests/inspector/network/resource-response-inspector-override-expected.txt: Added.
* http/tests/inspector/network/resource-response-inspector-override.html: Added.
* http/tests/inspector/network/resources/override.js: Added.
* http/tests/inspector/network/resources/override.txt: Added.
* inspector/network/local-resource-override-continue-response-expected.txt: Added.
* inspector/network/local-resource-override-continue-response.html: Added.
Tests for overrides.

* inspector/unit-tests/url-utilities-expected.txt:
* inspector/unit-tests/url-utilities.html:
Test WI.urlWithoutFragment.

* platform/mac-wk1/TestExpectations:
WebKitLegacy does not support overrides.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@249504 268f45cc-cd09-0410-ab3c-d52691b4dbfc

110 files changed:
LayoutTests/ChangeLog
LayoutTests/http/tests/inspector/network/local-resource-override-basic-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/inspector/network/local-resource-override-basic.html [new file with mode: 0644]
LayoutTests/http/tests/inspector/network/local-resource-override-main-resource-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/inspector/network/local-resource-override-main-resource.html [new file with mode: 0644]
LayoutTests/http/tests/inspector/network/local-resource-override-script-tag-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/inspector/network/local-resource-override-script-tag.html [new file with mode: 0644]
LayoutTests/http/tests/inspector/network/resource-response-inspector-override-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/inspector/network/resource-response-inspector-override.html [new file with mode: 0644]
LayoutTests/http/tests/inspector/network/resources/override.js [new file with mode: 0644]
LayoutTests/http/tests/inspector/network/resources/override.txt [new file with mode: 0644]
LayoutTests/inspector/css/pseudo-creation.html
LayoutTests/inspector/network/local-resource-override-continue-response-expected.txt [new file with mode: 0644]
LayoutTests/inspector/network/local-resource-override-continue-response.html [new file with mode: 0644]
LayoutTests/inspector/unit-tests/url-utilities-expected.txt
LayoutTests/inspector/unit-tests/url-utilities.html
LayoutTests/platform/mac-wk1/TestExpectations
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/Scripts/generate-combined-inspector-json.py
Source/JavaScriptCore/inspector/protocol/Network.json
Source/JavaScriptCore/inspector/scripts/codegen/generate_objc_backend_dispatcher_implementation.py
Source/JavaScriptCore/inspector/scripts/generate-inspector-protocol-bindings.py
Source/JavaScriptCore/inspector/scripts/tests/generic/expected/commands-with-optional-call-return-parameters.json-result
Source/WebCore/ChangeLog
Source/WebCore/Headers.cmake
Source/WebCore/Sources.txt
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/inspector/InspectorInstrumentation.cpp
Source/WebCore/inspector/InspectorInstrumentation.h
Source/WebCore/inspector/InspectorInstrumentationPublic.cpp [new file with mode: 0644]
Source/WebCore/inspector/InspectorInstrumentationPublic.h [new file with mode: 0644]
Source/WebCore/inspector/InspectorInstrumentationWebKit.cpp [new file with mode: 0644]
Source/WebCore/inspector/InspectorInstrumentationWebKit.h [new file with mode: 0644]
Source/WebCore/inspector/agents/InspectorNetworkAgent.cpp
Source/WebCore/inspector/agents/InspectorNetworkAgent.h
Source/WebCore/loader/DocumentLoader.cpp
Source/WebCore/loader/ResourceLoader.cpp
Source/WebCore/loader/ResourceLoader.h
Source/WebCore/loader/cache/CachedResourceLoader.cpp
Source/WebCore/loader/cache/MemoryCache.cpp
Source/WebCore/platform/network/ResourceResponseBase.h
Source/WebCore/testing/Internals.cpp
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
Source/WebInspectorUI/Tools/CodeMirrorModes/index.html [new file with mode: 0644]
Source/WebInspectorUI/Tools/CodeMirrorModes/styles.css [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Base/HTTPUtilities.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Base/Main.js
Source/WebInspectorUI/UserInterface/Base/ObjectStore.js
Source/WebInspectorUI/UserInterface/Base/URLUtilities.js
Source/WebInspectorUI/UserInterface/Controllers/CSSManager.js
Source/WebInspectorUI/UserInterface/Controllers/HARBuilder.js
Source/WebInspectorUI/UserInterface/Controllers/NetworkManager.js
Source/WebInspectorUI/UserInterface/Images/NavigationItemNetworkOverride.svg [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Main.html
Source/WebInspectorUI/UserInterface/Models/LocalResource.js
Source/WebInspectorUI/UserInterface/Models/LocalResourceOverride.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Models/Resource.js
Source/WebInspectorUI/UserInterface/Protocol/NetworkObserver.js
Source/WebInspectorUI/UserInterface/Test.html
Source/WebInspectorUI/UserInterface/Views/BreakpointPopoverController.css
Source/WebInspectorUI/UserInterface/Views/CodeMirrorLocalOverrideURLMode.css [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/CodeMirrorLocalOverrideURLMode.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/ConsoleMessageView.css
Source/WebInspectorUI/UserInterface/Views/ContentBrowserTabContentView.js
Source/WebInspectorUI/UserInterface/Views/ContentView.js
Source/WebInspectorUI/UserInterface/Views/ContextMenu.js
Source/WebInspectorUI/UserInterface/Views/ContextMenuUtilities.js
Source/WebInspectorUI/UserInterface/Views/DataGrid.js
Source/WebInspectorUI/UserInterface/Views/DataGridNode.js
Source/WebInspectorUI/UserInterface/Views/DebuggerSidebarPanel.css
Source/WebInspectorUI/UserInterface/Views/DebuggerSidebarPanel.js
Source/WebInspectorUI/UserInterface/Views/InputPopover.css
Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideLabelView.css [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideLabelView.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/LocalResourceOverridePopover.css [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/LocalResourceOverridePopover.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideTreeElement.css [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideTreeElement.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideWarningView.css [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideWarningView.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/NavigationSidebarPanel.js
Source/WebInspectorUI/UserInterface/Views/NetworkTableContentView.js
Source/WebInspectorUI/UserInterface/Views/ResourceHeadersContentView.js
Source/WebInspectorUI/UserInterface/Views/ResourceIcons.css
Source/WebInspectorUI/UserInterface/Views/ResourceSizesContentView.js
Source/WebInspectorUI/UserInterface/Views/ResourceTimelineDataGridNode.js
Source/WebInspectorUI/UserInterface/Views/ResourceTreeElement.js
Source/WebInspectorUI/UserInterface/Views/ScopeChainDetailsSidebarPanel.css
Source/WebInspectorUI/UserInterface/Views/SearchSidebarPanel.css
Source/WebInspectorUI/UserInterface/Views/SearchSidebarPanel.js
Source/WebInspectorUI/UserInterface/Views/SourceCodeTextEditor.js
Source/WebInspectorUI/UserInterface/Views/SourcesNavigationSidebarPanel.css
Source/WebInspectorUI/UserInterface/Views/SourcesNavigationSidebarPanel.js
Source/WebInspectorUI/UserInterface/Views/SourcesTabContentView.js
Source/WebInspectorUI/UserInterface/Views/TextEditor.css
Source/WebInspectorUI/UserInterface/Views/TextResourceContentView.css
Source/WebInspectorUI/UserInterface/Views/TextResourceContentView.js
Source/WebInspectorUI/UserInterface/Views/TimelineDataGridNode.js
Source/WebInspectorUI/UserInterface/Views/URLBreakpointPopover.css
Source/WebInspectorUI/UserInterface/Views/URLBreakpointPopover.js
Source/WebInspectorUI/UserInterface/Views/Variables.css
Source/WebKit/ChangeLog
Source/WebKit/Sources.txt
Source/WebKit/WebKit.xcodeproj/project.pbxproj
Source/WebKit/WebProcess/Network/WebLoaderStrategy.cpp
Source/WebKit/WebProcess/Network/WebResourceInterceptController.cpp [new file with mode: 0644]
Source/WebKit/WebProcess/Network/WebResourceInterceptController.h [new file with mode: 0644]
Source/WebKit/WebProcess/Network/WebResourceLoader.cpp
Source/WebKit/WebProcess/Network/WebResourceLoader.h

index 0e2042499475b632adbcbfc39d223a7d48ca6b25..d673cc0fb5f72a6d5b92ef3f3a7c7c66c52f14ac 100644 (file)
@@ -1,3 +1,32 @@
+2019-09-04  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: Local Overrides - Provide substitution content for resource loads (URL based)
+        https://bugs.webkit.org/show_bug.cgi?id=201262
+        <rdar://problem/13108764>
+
+        Reviewed by Devin Rousso.
+
+        * http/tests/inspector/network/local-resource-override-basic-expected.txt: Added.
+        * http/tests/inspector/network/local-resource-override-basic.html: Added.
+        * http/tests/inspector/network/local-resource-override-main-resource-expected.txt: Added.
+        * http/tests/inspector/network/local-resource-override-main-resource.html: Added.
+        * http/tests/inspector/network/local-resource-override-script-tag-expected.txt: Added.
+        * http/tests/inspector/network/local-resource-override-script-tag.html: Added.
+        * http/tests/inspector/network/resource-response-inspector-override-expected.txt: Added.
+        * http/tests/inspector/network/resource-response-inspector-override.html: Added.
+        * http/tests/inspector/network/resources/override.js: Added.
+        * http/tests/inspector/network/resources/override.txt: Added.
+        * inspector/network/local-resource-override-continue-response-expected.txt: Added.
+        * inspector/network/local-resource-override-continue-response.html: Added.
+        Tests for overrides.
+
+        * inspector/unit-tests/url-utilities-expected.txt:
+        * inspector/unit-tests/url-utilities.html:
+        Test WI.urlWithoutFragment.
+
+        * platform/mac-wk1/TestExpectations:
+        WebKitLegacy does not support overrides.
+
 2019-09-04  Said Abou-Hallawa  <sabouhallawa@apple.com>
 
         Address review comments after r249364
diff --git a/LayoutTests/http/tests/inspector/network/local-resource-override-basic-expected.txt b/LayoutTests/http/tests/inspector/network/local-resource-override-basic-expected.txt
new file mode 100644 (file)
index 0000000..f4563d8
--- /dev/null
@@ -0,0 +1,150 @@
+Test for some LocalResourceOverride loads.
+
+
+== Running test suite: LocalResourceOverride
+-- Running test case: LocalResourceOverride.None
+Triggering load...
+Resource Loaded:
+URL: http://127.0.0.1:8000/inspector/network/resources/override.txt
+MIME Type: text/plain
+Status: 200 OK
+Response Source: <not-InspectorOverride>
+Response Headers:
+  Accept-Ranges: <filtered>
+  Connection: <filtered>
+  Content-Length: 29
+  Content-Type: text/plain
+  Date: <filtered>
+  ETag: <filtered>
+  Keep-Alive: <filtered>
+  Last-Modified: <filtered>
+  Server: <filtered>
+Content: default override.txt content
+
+
+-- Running test case: LocalResourceOverride.Text
+Creating Local Resource Override for: http://127.0.0.1:8000/inspector/network/resources/override.txt
+Triggering load...
+Resource Loaded:
+URL: http://127.0.0.1:8000/inspector/network/resources/override.txt
+MIME Type: text/plain
+Status: 987 Override Status Text
+Response Source: Symbol(inspector-override)
+Response Headers:
+  Content-Type: text/plain
+  X-Override-Header-1: Override-Header-Value-1
+  X-Override-Header-2: Override-Header-Value-2
+Content: PASS - OVERRIDDEN TEXT
+
+-- Running test case: LocalResourceOverride.JavaScript
+Creating Local Resource Override for: http://127.0.0.1:8000/inspector/network/resources/override.txt
+Triggering load...
+Resource Loaded:
+URL: http://127.0.0.1:8000/inspector/network/resources/override.txt
+MIME Type: application/javascript
+Status: 200 Super OK
+Response Source: Symbol(inspector-override)
+Response Headers:
+  Content-Type: application/javascript
+  X-Custom-Header: Header value
+Content: /* PASS */ (function() { /* OVERRIDDEN */ })();
+
+-- Running test case: LocalResourceOverride.Image
+Creating Local Resource Override for: http://127.0.0.1:8000/inspector/network/resources/override.txt
+Triggering load...
+Resource Loaded:
+URL: http://127.0.0.1:8000/inspector/network/resources/override.txt
+MIME Type: image/png
+Status: 200 OK
+Response Source: Symbol(inspector-override)
+Response Headers:
+  Content-Type: image/png
+Content: [base64] PGRhdGE+
+
+-- Running test case: LocalResourceOverride.URL.QueryString
+Creating Local Resource Override for: http://127.0.0.1:8000/inspector/network/resources/override.txt?s=1
+Creating Local Resource Override for: http://127.0.0.1:8000/inspector/network/resources/override.txt?s=2
+Triggering load...
+Resource Loaded:
+URL: http://127.0.0.1:8000/inspector/network/resources/override.txt?s=2
+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.Fragment
+Creating Local Resource Override for: http://127.0.0.1:8000/inspector/network/resources/override.txt
+Triggering load...
+Resource Loaded:
+URL: http://127.0.0.1:8000/inspector/network/resources/override.txt#frag
+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.Enabled
+Creating Local Resource Override for: http://127.0.0.1:8000/inspector/network/resources/override.txt
+Triggering load...
+Resource Loaded:
+URL: http://127.0.0.1:8000/inspector/network/resources/override.txt
+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.Disabled
+Creating Local Resource Override for: http://127.0.0.1:8000/inspector/network/resources/override.txt
+Triggering load...
+Resource Loaded:
+URL: http://127.0.0.1:8000/inspector/network/resources/override.txt
+MIME Type: text/plain
+Status: 200 OK
+Response Source: <not-InspectorOverride>
+Response Headers:
+  Accept-Ranges: <filtered>
+  Connection: <filtered>
+  Content-Length: 29
+  Content-Type: text/plain
+  Date: <filtered>
+  ETag: <filtered>
+  Keep-Alive: <filtered>
+  Last-Modified: <filtered>
+  Server: <filtered>
+Content: default override.txt content
+
+
+-- Running test setup.
+-- Running test case: LocalResourceOverride.GlobalDisabled
+Creating Local Resource Override for: http://127.0.0.1:8000/inspector/network/resources/override.txt
+Triggering load...
+Resource Loaded:
+URL: http://127.0.0.1:8000/inspector/network/resources/override.txt
+MIME Type: text/plain
+Status: 200 OK
+Response Source: <not-InspectorOverride>
+Response Headers:
+  Accept-Ranges: <filtered>
+  Connection: <filtered>
+  Content-Length: 29
+  Content-Type: text/plain
+  Date: <filtered>
+  ETag: <filtered>
+  Keep-Alive: <filtered>
+  Last-Modified: <filtered>
+  Server: <filtered>
+Content: default override.txt content
+
+-- Running test teardown.
+
+-- Running test case: LocalResourceOverride.URL.Fragment
+PASS: LocalResourceOverride creation should strip fragments.
+
diff --git a/LayoutTests/http/tests/inspector/network/local-resource-override-basic.html b/LayoutTests/http/tests/inspector/network/local-resource-override-basic.html
new file mode 100644 (file)
index 0000000..f762c1c
--- /dev/null
@@ -0,0 +1,241 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<script src="../resources/inspector-test.js"></script>
+<script>
+function triggerOverrideLoad(urlSuffix) {
+    let url = "http://127.0.0.1:8000/inspector/network/resources/override.txt";
+    if (urlSuffix)
+        url += urlSuffix;
+    fetch(url).then(() => {
+        TestPage.dispatchEventToFrontend("LoadComplete");
+    });
+}
+
+function test()
+{
+    let suite = InspectorTest.createAsyncSuite("LocalResourceOverride");
+
+    async function logResource(resource) {
+        let responseSource = resource.responseSource === WI.Resource.ResponseSource.InspectorOverride ? String(resource.responseSource) : "<not-InspectorOverride>";
+        InspectorTest.log(`URL: ${resource.url}`);
+        InspectorTest.log(`MIME Type: ${resource.mimeType}`);
+        InspectorTest.log(`Status: ${resource.statusCode} ${resource.statusText}`);
+        InspectorTest.log(`Response Source: ${responseSource}`);
+        InspectorTest.log(`Response Headers:`);
+        let keys = Object.keys(resource.responseHeaders);
+        keys.sort();
+        for (let name of keys) {
+            let value = resource.responseHeaders[name];
+            if (!name.startsWith("X-") && !name.startsWith("Content-"))
+                value = "<filtered>";
+            InspectorTest.log(`  ${name}: ${value}`);
+        }
+
+        let {rawContent, rawBase64Encoded} = await resource.requestContent();
+        InspectorTest.log(`Content: ${rawBase64Encoded ? "[base64] " : ""}${rawContent}`);
+    }
+
+    function addTestCase({name, description, setup, teardown, expression, overrides}) {
+        suite.addTestCase({
+            name, description, setup, teardown,
+            async test() {
+                // Create overrides.
+                let localResourceOverrides = [];
+                for (let override of overrides) {
+                    InspectorTest.log("Creating Local Resource Override for: " + override.url);
+                    let localResourceOverride = WI.LocalResourceOverride.create(override);
+                    WI.networkManager.addLocalResourceOverride(localResourceOverride);
+                    localResourceOverrides.push(localResourceOverride);
+                }
+
+                InspectorTest.log("Triggering load...");
+                let [resourceWasAddedEvent, responseReceivedEvent, loadCompleteEvent] = await Promise.all([
+                    WI.Frame.awaitEvent(WI.Frame.Event.ResourceWasAdded),
+                    WI.Resource.awaitEvent(WI.Resource.Event.ResponseReceived),
+                    InspectorTest.awaitEvent("LoadComplete"),
+                    InspectorTest.evaluateInPage(expression),
+                ]);
+
+                InspectorTest.log("Resource Loaded:");
+                let resource = resourceWasAddedEvent.data.resource;
+                await logResource(resource);
+
+                // Remove overrides.
+                for (let localResourceOverride of localResourceOverrides)
+                    WI.networkManager.removeLocalResourceOverride(localResourceOverride);
+            }
+        });
+    }
+
+    addTestCase({
+        name: "LocalResourceOverride.None",
+        description: "Load without an override.",
+        expression: `triggerOverrideLoad()`,
+        overrides: [],
+    });
+
+    addTestCase({
+        name: "LocalResourceOverride.Text",
+        description: "Load an override with text content.",
+        expression: `triggerOverrideLoad()`,
+        overrides: [{
+            url: "http://127.0.0.1:8000/inspector/network/resources/override.txt",
+            mimeType: "text/plain",
+            content: `PASS - OVERRIDDEN TEXT`,
+            base64Encoded: false,
+            statusCode: 987,
+            statusText: "Override Status Text",
+            headers: {
+                "X-Override-Header-1": "Override-Header-Value-1",
+                "X-Override-Header-2": "Override-Header-Value-2",
+            },
+        }]
+    });
+
+    addTestCase({
+        name: "LocalResourceOverride.JavaScript",
+        description: "Load an override with javascript content.",
+        expression: `triggerOverrideLoad()`,
+        overrides: [{
+            url: "http://127.0.0.1:8000/inspector/network/resources/override.txt",
+            mimeType: "application/javascript",
+            content: `/* PASS */ (function() { /* OVERRIDDEN */ })();`,
+            base64Encoded: false,
+            statusCode: 200,
+            statusText: "Super OK",
+            headers: {
+                "X-Custom-Header": "Header value",
+            },
+        }]
+    });
+
+    addTestCase({
+        name: "LocalResourceOverride.Image",
+        description: "Load an override with image content.",
+        expression: `triggerOverrideLoad()`,
+        overrides: [{
+            url: "http://127.0.0.1:8000/inspector/network/resources/override.txt",
+            mimeType: "image/png",
+            content: btoa("<data>"),
+            base64Encoded: true,
+            statusCode: 200,
+            statusText: "OK",
+            headers: {},
+        }]
+    });
+
+    addTestCase({
+        name: "LocalResourceOverride.URL.QueryString",
+        description: "Test overrides with different query strings.",
+        expression: `triggerOverrideLoad("?s=2")`,
+        overrides: [{
+            url: "http://127.0.0.1:8000/inspector/network/resources/override.txt?s=1",
+            mimeType: "text/plain",
+            content: "FAIL",
+            base64Encoded: false,
+            statusCode: 500,
+            statusText: "FAIL",
+            headers: {"X-Expected": "FAIL"},
+        }, {
+            url: "http://127.0.0.1:8000/inspector/network/resources/override.txt?s=2",
+            mimeType: "text/plain",
+            content: "PASS",
+            base64Encoded: false,
+            statusCode: 200,
+            statusText: "OK",
+            headers: {"X-Expected": "PASS"},
+        }]
+    });
+
+    addTestCase({
+        name: "LocalResourceOverride.URL.Fragment",
+        description: "Test override for a load with a fragment.",
+        expression: `triggerOverrideLoad("#frag")`,
+        overrides: [{
+            url: "http://127.0.0.1:8000/inspector/network/resources/override.txt",
+            mimeType: "text/plain",
+            content: "PASS",
+            base64Encoded: false,
+            statusCode: 200,
+            statusText: "OK",
+            headers: {"X-Expected": "PASS"},
+        }]
+    });
+
+    addTestCase({
+        name: "LocalResourceOverride.Enabled",
+        description: "Test for an enabled override.",
+        expression: `triggerOverrideLoad()`,
+        overrides: [{
+            url: "http://127.0.0.1:8000/inspector/network/resources/override.txt",
+            mimeType: "text/plain",
+            content: "PASS",
+            base64Encoded: false,
+            statusCode: 200,
+            statusText: "OK",
+            headers: {"X-Expected": "PASS"},
+            disabled: false,
+        }]
+    });
+
+    addTestCase({
+        name: "LocalResourceOverride.Disabled",
+        description: "Test for a disabled override.",
+        expression: `triggerOverrideLoad()`,
+        overrides: [{
+            url: "http://127.0.0.1:8000/inspector/network/resources/override.txt",
+            mimeType: "text/plain",
+            content: "FAIL",
+            base64Encoded: false,
+            statusCode: 500,
+            statusText: "FAIL",
+            headers: {"X-Expected": "FAIL"},
+            disabled: true,
+        }]
+    });
+
+    addTestCase({
+        name: "LocalResourceOverride.GlobalDisabled",
+        description: "Test for an override when they are globally disabled.",
+        expression: `triggerOverrideLoad()`,
+        async setup() { WI.networkManager.interceptionEnabled = false; },
+        async teardown() { WI.networkManager.interceptionEnabled = true; },
+        overrides: [{
+            url: "http://127.0.0.1:8000/inspector/network/resources/override.txt",
+            mimeType: "text/plain",
+            content: "FAIL",
+            base64Encoded: false,
+            statusCode: 500,
+            statusText: "FAIL",
+            headers: {"X-Expected": "FAIL"},
+        }]
+    });
+
+    suite.addTestCase({
+        name: "LocalResourceOverride.URL.Fragment",
+        description: "LocalResourceOverride creation strips a fragment",
+        async test() {
+            let localResourceOverride = WI.LocalResourceOverride.create({
+                url: "http://127.0.0.1:8000/inspector/network/resources/override.txt#test",
+                mimeType: "text/plain",
+                content: "OVERRIDDEN TEXT",
+                base64Encoded: false,
+                statusCode: 200,
+                statusText: "OK",
+                headers: {},
+            });
+
+            InspectorTest.expectEqual(localResourceOverride.localResource.url, "http://127.0.0.1:8000/inspector/network/resources/override.txt", "LocalResourceOverride creation should strip fragments.");
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+<p>Test for some LocalResourceOverride loads.</p>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/inspector/network/local-resource-override-main-resource-expected.txt b/LayoutTests/http/tests/inspector/network/local-resource-override-main-resource-expected.txt
new file mode 100644 (file)
index 0000000..4212590
--- /dev/null
@@ -0,0 +1,3 @@
+ALERT: ORIGINAL HTML CONTENT
+ALERT: REPLACED HTML CONTENT
+Overridden page content
diff --git a/LayoutTests/http/tests/inspector/network/local-resource-override-main-resource.html b/LayoutTests/http/tests/inspector/network/local-resource-override-main-resource.html
new file mode 100644 (file)
index 0000000..2dc1aa8
--- /dev/null
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<script src="../resources/inspector-test.js"></script>
+<script>
+function test()
+{
+    let suite = InspectorTest.createAsyncSuite("LocalResourceOverride.MainResource");
+
+    suite.addTestCase({
+        name: "LocalResourceOverride.MainResource",
+        description: "Main resource uses override content on next page load",
+        async test() {
+            WI.networkManager.addLocalResourceOverride(WI.LocalResourceOverride.create({
+                url: "http://127.0.0.1:8000/inspector/network/local-resource-override-main-resource.html",
+                mimeType: "text/html",
+                content: `<!DOCTYPE html><html><head><script src="../resources/inspector-test.js"></`+`script></head><body><p>Overridden page content</p><script>alert("REPLACED HTML CONTENT"); TestPage.completeTest();</`+`script></body></html>`,
+                base64Encoded: false,
+                statusCode: 200,
+                statusText: "OK",
+                headers: {},
+            }));
+
+            await InspectorTest.reloadPage({ignoreCache: false, revalidateAllResources: true});
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+<p>Test a LocalResourceOverride overriding a main resource load.</p>
+<script>alert("ORIGINAL HTML CONTENT");</script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/inspector/network/local-resource-override-script-tag-expected.txt b/LayoutTests/http/tests/inspector/network/local-resource-override-script-tag-expected.txt
new file mode 100644 (file)
index 0000000..31225f8
--- /dev/null
@@ -0,0 +1,8 @@
+ALERT: DEFAULT override.js TEXT
+ALERT: OVERRIDDEN override.js TEXT
+Test a LocalResourceOverride overriding a <script> load.
+
+
+== Running test suite: LocalResourceOverride.Script
+-- Running test case: LocalResourceOverride.Script.Tag
+
diff --git a/LayoutTests/http/tests/inspector/network/local-resource-override-script-tag.html b/LayoutTests/http/tests/inspector/network/local-resource-override-script-tag.html
new file mode 100644 (file)
index 0000000..11a9362
--- /dev/null
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<script src="../resources/inspector-test.js"></script>
+<script>
+function test()
+{
+    let suite = InspectorTest.createAsyncSuite("LocalResourceOverride.Script");
+
+    suite.addTestCase({
+        name: "LocalResourceOverride.Script.Tag",
+        description: "<script> load uses override content on next page load",
+        async test() {
+            WI.networkManager.addLocalResourceOverride(WI.LocalResourceOverride.create({
+                url: "http://127.0.0.1:8000/inspector/network/resources/override.js",
+                mimeType: "text/javascript",
+                content: `alert("OVERRIDDEN override.js TEXT"); TestPage.dispatchEventToFrontend("OverrideContentDidLoad");`,
+                base64Encoded: false,
+                statusCode: 200,
+                statusText: "OK",
+                headers: {},
+            }));
+
+            await InspectorTest.reloadPage({ignoreCache: false, revalidateAllResources: true});
+            await InspectorTest.awaitEvent("OverrideContentDidLoad");
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+<p>Test a LocalResourceOverride overriding a &lt;script&gt; load.</p>
+<script src="resources/override.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/inspector/network/resource-response-inspector-override-expected.txt b/LayoutTests/http/tests/inspector/network/resource-response-inspector-override-expected.txt
new file mode 100644 (file)
index 0000000..515251a
--- /dev/null
@@ -0,0 +1,11 @@
+Test for `Resource.ResponseSource.InspectorOverride`.
+
+
+== Running test suite: Resource.ResponseSource.InspectorOverride
+-- Running test setup.
+-- Running test case: Resource.ResponseSource.InspectorOverride
+PASS: Resource should be created.
+PASS: Resource should receive a Response.
+PASS: statusCode should be 987
+PASS: responseSource should be Symbol(inspector-override)
+
diff --git a/LayoutTests/http/tests/inspector/network/resource-response-inspector-override.html b/LayoutTests/http/tests/inspector/network/resource-response-inspector-override.html
new file mode 100644 (file)
index 0000000..7a40ea8
--- /dev/null
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<script src="../resources/inspector-test.js"></script>
+<script>
+function triggerOverrideLoad() {
+    let url = "http://127.0.0.1:8000/inspector/network/resources/override.txt";
+    fetch(url).then(() => {
+        TestPage.dispatchEventToFrontend("LoadComplete");
+    });
+}
+
+function test()
+{
+    let suite = InspectorTest.createAsyncSuite("Resource.ResponseSource.InspectorOverride");
+
+    function addTestCase({name, description, expression, statusCode, responseSource, setup}) {
+        suite.addTestCase({
+            name, description, setup,
+            test(resolve, reject) {
+                InspectorTest.evaluateInPage(expression);
+                Promise.all([
+                    WI.Frame.awaitEvent(WI.Frame.Event.ResourceWasAdded),
+                    WI.Resource.awaitEvent(WI.Resource.Event.ResponseReceived),
+                    InspectorTest.awaitEvent("LoadComplete"),
+                ]).then(([resourceWasAddedEvent, responseReceivedEvent, loadCompleteEvent]) => {
+                    let resource = resourceWasAddedEvent.data.resource;
+                    InspectorTest.expectThat(resource instanceof WI.Resource, "Resource should be created.");
+                    InspectorTest.expectEqual(resource, responseReceivedEvent.target, "Resource should receive a Response.");
+                    InspectorTest.expectEqual(resource.statusCode, statusCode, `statusCode should be ${statusCode}`);
+                    InspectorTest.expectEqual(resource.responseSource, responseSource, `responseSource should be ${String(responseSource)}`);
+                }).then(resolve, reject);
+            }
+        });
+    }
+
+    addTestCase({
+        name: "Resource.ResponseSource.InspectorOverride",
+        description: "Load a an override resource.",
+        expression: `triggerOverrideLoad()`,
+        responseSource: WI.Resource.ResponseSource.InspectorOverride,
+        statusCode: 987,
+        async setup() {
+            WI.networkManager.addLocalResourceOverride(WI.LocalResourceOverride.create({
+                url: "http://127.0.0.1:8000/inspector/network/resources/override.txt",
+                mimeType: "text/plain",
+                content: "Overridden Text",
+                base64Encoded: false,
+                statusCode: 987,
+                statusText: "Status Text",
+                headers: {},
+            }));
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+<p>Test for `Resource.ResponseSource.InspectorOverride`.</p>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/inspector/network/resources/override.js b/LayoutTests/http/tests/inspector/network/resources/override.js
new file mode 100644 (file)
index 0000000..3175cc5
--- /dev/null
@@ -0,0 +1 @@
+alert("DEFAULT override.js TEXT");
diff --git a/LayoutTests/http/tests/inspector/network/resources/override.txt b/LayoutTests/http/tests/inspector/network/resources/override.txt
new file mode 100644 (file)
index 0000000..3e09ca6
--- /dev/null
@@ -0,0 +1 @@
+default override.txt content
index d4632a3a90fad18c4280c0c962cced52dad981e6..a01ce0d3645d38f47e5267eac9d8e0f486d1a646 100644 (file)
@@ -25,8 +25,6 @@ function removeElementWithClass(className) {
 }
 
 function test() {
-    ProtocolTest.debug();
-
     let documentNode = null;
     let pseudoElement = null;
 
diff --git a/LayoutTests/inspector/network/local-resource-override-continue-response-expected.txt b/LayoutTests/inspector/network/local-resource-override-continue-response-expected.txt
new file mode 100644 (file)
index 0000000..d3404e5
--- /dev/null
@@ -0,0 +1,8 @@
+Test an override continue response to let the original load complete.
+
+
+== Running test suite: LocalResourceOverride.ContinueResponse
+-- Running test case: LocalResourceOverride.ContinueResponse
+PASS: Intercepted response.
+PASS: Response received.
+
diff --git a/LayoutTests/inspector/network/local-resource-override-continue-response.html b/LayoutTests/inspector/network/local-resource-override-continue-response.html
new file mode 100644 (file)
index 0000000..4480642
--- /dev/null
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<script src="../../http/tests/inspector/resources/protocol-test.js"></script>
+<script>
+function getURL() {
+    TestPage.dispatchEventToFrontend("TestURL", {
+        url: document.URL.substring(0, document.URL.lastIndexOf("/")) + "/resources/data.json"
+    });
+}
+function triggerOverrideLoad() {
+    fetch("resources/data.json").then(() => {
+        TestPage.dispatchEventToFrontend("LoadComplete");
+    });
+}
+
+function test()
+{
+    let suite = ProtocolTest.createAsyncSuite("LocalResourceOverride.ContinueResponse");
+
+    suite.addTestCase({
+        name: "LocalResourceOverride.ContinueResponse",
+        description: "Test an override continue response to let the original load complete.",
+        async test() {
+            // Setup interception.
+            await Promise.all([
+                InspectorProtocol.awaitCommand({method: "Network.enable", params: {}}),
+                InspectorProtocol.awaitCommand({method: "Network.setInterceptionEnabled", params: {enabled: true}}),
+            ]);
+
+            // Get URL to override and override it.
+            let [event] = await Promise.all([
+                ProtocolTest.awaitEvent("TestURL"),
+                ProtocolTest.evaluateInPage(`getURL()`),
+            ]);
+            await InspectorProtocol.awaitCommand({method: "Network.addInterception", params: {url: event.data.url, stage: "response"}});
+
+            // Fetch URL and intercept it.
+            let [messageObject] = await Promise.all([
+                InspectorProtocol.awaitEvent({event: "Network.responseIntercepted"}),
+                ProtocolTest.evaluateInPage(`triggerOverrideLoad()`),
+            ]);
+            ProtocolTest.pass("Intercepted response.");
+
+            // Let the load continue.
+            await Promise.all([
+                InspectorProtocol.awaitEvent({event: "Network.responseReceived"}),
+                InspectorProtocol.awaitCommand({method: "Network.interceptContinue", params: {requestId: messageObject.params.requestId}}),
+            ]);
+            ProtocolTest.pass("Response received.");
+
+            // Remove override.
+            await InspectorProtocol.awaitCommand({method: "Network.removeInterception", params: {url: event.data.url, stage: "response"}});
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+<p>Test an override continue response to let the original load complete.</p>
+</body>
+</html>
index a26681d87da413bc11c432edac0babd69eac991a..decdf94d011db088132864590cc264594cb6af89 100644 (file)
@@ -6,6 +6,14 @@ Test Invalid: a
 PASS: Should not be a complete URL
 PASS: URL constructor thinks this is invalid
 
+Test Invalid: __WebInspectorInternal__
+PASS: Should not be a complete URL
+PASS: URL constructor thinks this is invalid
+
+Test Invalid: __WebTest__
+PASS: Should not be a complete URL
+PASS: URL constructor thinks this is invalid
+
 Test Invalid: /http://example.com
 PASS: Should not be a complete URL
 PASS: URL constructor thinks this is invalid
@@ -460,6 +468,40 @@ PASS: Display name of 'http://' should be 'http://'.
 PASS: Display name of 'http://example.com:65537' should be 'http://example.com:65537'.
 PASS: Display name of 'http://user@pass:example.com/' should be 'http://user@pass:example.com/'.
 
+-- Running test case: WI.urlWithoutFragment
+PASS: Removing fragment of 'http://example.com' should be 'http://example.com/'.
+PASS: Removing fragment of 'http://example.com#frag' should be 'http://example.com/'.
+PASS: Removing fragment of 'https://example.com' should be 'https://example.com/'.
+PASS: Removing fragment of 'https://example.com#frag' should be 'https://example.com/'.
+PASS: Removing fragment of 'ftp://example.com' should be 'ftp://example.com/'.
+PASS: Removing fragment of 'ftp://example.com#frag' should be 'ftp://example.com/'.
+PASS: Removing fragment of 'http://example.com/' should be 'http://example.com/'.
+PASS: Removing fragment of 'http://example.com/#frag' should be 'http://example.com/'.
+PASS: Removing fragment of 'http://example.com/path' should be 'http://example.com/path'.
+PASS: Removing fragment of 'http://example.com/path#frag' should be 'http://example.com/path'.
+PASS: Removing fragment of 'http://example.com/path/a/b/' should be 'http://example.com/path/a/b/'.
+PASS: Removing fragment of 'http://example.com/path/a/b/#frag' should be 'http://example.com/path/a/b/'.
+PASS: Removing fragment of 'http://example.com/path/a/b/?' should be 'http://example.com/path/a/b/?'.
+PASS: Removing fragment of 'http://example.com/path/a/b/?#frag' should be 'http://example.com/path/a/b/?'.
+PASS: Removing fragment of 'http://example.com/path/a/b/?s=1' should be 'http://example.com/path/a/b/?s=1'.
+PASS: Removing fragment of 'http://example.com/path/a/b/?s=1#frag' should be 'http://example.com/path/a/b/?s=1'.
+PASS: Removing fragment of 'http://example.com/path/a/b/?s=1&t=2' should be 'http://example.com/path/a/b/?s=1&t=2'.
+PASS: Removing fragment of 'http://example.com/path/a/b/?s=1&t=2#frag' should be 'http://example.com/path/a/b/?s=1&t=2'.
+PASS: Removing fragment of 'http://example.com?' should be 'http://example.com/?'.
+PASS: Removing fragment of 'http://example.com?#frag' should be 'http://example.com/?'.
+PASS: Removing fragment of 'http://example.com?s=1' should be 'http://example.com/?s=1'.
+PASS: Removing fragment of 'http://example.com?s=1#frag' should be 'http://example.com/?s=1'.
+PASS: Removing fragment of 'http://example.com?s=1&t=2' should be 'http://example.com/?s=1&t=2'.
+PASS: Removing fragment of 'http://example.com?s=1&t=2#frag' should be 'http://example.com/?s=1&t=2'.
+PASS: Removing fragment of 'http://example.com#' should be 'http://example.com/'.
+PASS: Removing fragment of 'http://example.com/#' should be 'http://example.com/'.
+PASS: Removing fragment of 'http://example.com/path#' should be 'http://example.com/path'.
+PASS: Removing fragment of 'http://example.com/path/#' should be 'http://example.com/path/'.
+PASS: Removing fragment of 'http://example.com/path?#' should be 'http://example.com/path?'.
+PASS: Removing fragment of 'http://example.com/path/?#' should be 'http://example.com/path/?'.
+PASS: Removing fragment of '#hash' should be 'about:blank'.
+PASS: Removing fragment of 'invalid' should be 'invalid'.
+
 -- Running test case: WI.h2Authority
 PASS: HTTP/2 :authority of 'http://example.com' should be 'example.com'.
 PASS: HTTP/2 :authority of 'https://example.com' should be 'example.com'.
index ecbad3260a2573975c37658b0d925843025a58a6..a41e8b038e910d619628f344692919b434c60118 100644 (file)
@@ -46,6 +46,8 @@ function test()
             }
 
             testInvalid("a");
+            testInvalid("__WebInspectorInternal__");
+            testInvalid("__WebTest__");
             testInvalid("/http://example.com");
 
             testValid("http://example.com", {
@@ -537,6 +539,47 @@ function test()
         },
     });
 
+    suite.addTestCase({
+        name: "WI.urlWithoutFragment",
+        test() {
+            function test(url, expected) {
+                InspectorTest.expectEqual(WI.urlWithoutFragment(url), expected, `Removing fragment of '${url}' should be '${expected}'.`);
+            }
+
+            function testBoth(url, expected) {
+                const urlWithFrag = url + "#frag";
+                InspectorTest.expectEqual(WI.urlWithoutFragment(url), expected, `Removing fragment of '${url}' should be '${expected}'.`);
+                InspectorTest.expectEqual(WI.urlWithoutFragment(urlWithFrag), expected, `Removing fragment of '${urlWithFrag}' should be '${expected}'.`);
+            }
+
+            testBoth("http://example.com", "http://example.com/");
+            testBoth("https://example.com", "https://example.com/");
+            testBoth("ftp://example.com", "ftp://example.com/");
+            
+            testBoth("http://example.com/", "http://example.com/");
+            testBoth("http://example.com/path", "http://example.com/path");
+            testBoth("http://example.com/path/a/b/", "http://example.com/path/a/b/");
+            testBoth("http://example.com/path/a/b/?", "http://example.com/path/a/b/?");
+            testBoth("http://example.com/path/a/b/?s=1", "http://example.com/path/a/b/?s=1");
+            testBoth("http://example.com/path/a/b/?s=1&t=2", "http://example.com/path/a/b/?s=1&t=2");
+            testBoth("http://example.com?", "http://example.com/?");
+            testBoth("http://example.com?s=1", "http://example.com/?s=1");
+            testBoth("http://example.com?s=1&t=2", "http://example.com/?s=1&t=2");
+
+            test("http://example.com#", "http://example.com/");
+            test("http://example.com/#", "http://example.com/");
+            test("http://example.com/path#", "http://example.com/path");
+            test("http://example.com/path/#", "http://example.com/path/");
+            test("http://example.com/path?#", "http://example.com/path?");
+            test("http://example.com/path/?#", "http://example.com/path/?");
+
+            test("#hash", "about:blank");
+            test("invalid", "invalid");
+
+            return true;
+        }
+    });
+
     suite.addTestCase({
         name: "WI.h2Authority",
         test() {
index 1c9fa9418f03aec80d365d13133d1aa4ae217fc7..67266a9ca1ebc22274ead7e76909374b925d8c7c 100644 (file)
@@ -495,6 +495,13 @@ http/tests/inspector/network/resource-sizes-network.html [ Failure ]
 http/tests/inspector/network/resource-sizes-memory-cache.html [ Failure ]
 http/tests/inspector/network/har/har-page.html [ Failure ]
 
+# Local Overrides not available in WebKit1
+http/tests/inspector/network/local-resource-override-basic.html [ Failure ]
+http/tests/inspector/network/local-resource-override-main-resource.html [ Failure ]
+http/tests/inspector/network/local-resource-override-script-tag.html [ Failure ]
+http/tests/inspector/network/resource-response-inspector-override.html [ Failure ]
+inspector/network/local-resource-override-continue-response.html [ Skip ]
+
 webkit.org/b/164933 http/tests/misc/link-rel-icon-beforeload.html [ Failure ]
 
 webkit.org/b/165541 compositing/layer-creation/fixed-overlap-extent-rtl.html [ Failure ]
index 59ff489367ec40fd9067af65f6ba4298b1b8bb5c..509ccbbfbdccead3658b74270d16d83bcf0cd355 100644 (file)
@@ -1,3 +1,44 @@
+2019-09-04  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: Local Overrides - Provide substitution content for resource loads (URL based)
+        https://bugs.webkit.org/show_bug.cgi?id=201262
+        <rdar://problem/13108764>
+
+        Reviewed by Devin Rousso.
+
+        When interception is enabled, Network requests that match any of the configured
+        interception patterns will be paused on the backend and allowed to be modified
+        by the frontend.
+
+        Currently the only time a network request can be intercepted is during the
+        HTTP response. However, this intercepting interface is mean to extend to
+        HTTP requests as well.
+
+        When a response is to be intercepted a new event is sent to the frontend:
+
+          `Network.responseIntercepted` event
+
+        With a `requestId` to identify that network request. The frontend
+        must respond with one of the following commands to continue:
+
+          `Network.interceptContinue`     - proceed with the response unmodified
+          `Network.interceptWithResponse` - provide a response
+
+        The response is paused in the meantime.
+
+        * inspector/protocol/Network.json:
+        New interfaces for intercepting network responses and suppling override content.
+
+        * Scripts/generate-combined-inspector-json.py:
+        * inspector/scripts/generate-inspector-protocol-bindings.py:
+        (generate_from_specification.load_specification):
+        Complete allowing comments in JSON protocol files.
+
+        * inspector/scripts/codegen/generate_objc_backend_dispatcher_implementation.py:
+        (ObjCBackendDispatcherImplementationGenerator._generate_invocation_for_command):
+        * inspector/scripts/tests/generic/expected/commands-with-optional-call-return-parameters.json-result:
+        Allow optional enums in ObjC interfaces.
+
 2019-09-03  Mark Lam  <mark.lam@apple.com>
 
         Structure::storedPrototype() and storedPrototypeObject() should assert with isCompilationThread(), not !isMainThread().
index 67ad4a3f0193949bcd69798d6848a91c0d71456b..f0c632f26e2421b09e0228414d98cf8235bbd771 100755 (executable)
@@ -26,6 +26,7 @@
 import glob
 import json
 import os
+import re
 import sys
 
 if len(sys.argv) < 2:
@@ -57,7 +58,8 @@ for file in files:
     string = open(file).read()
 
     try:
-        dictionary = json.loads(string)
+        regex = re.compile(r"\/\*.*?\*\/", re.DOTALL)
+        dictionary = json.loads(re.sub(regex, "", string))
         if not "domain" in dictionary:
             raise Exception("File \"%s\" does not contains a \"domain\" key." % file)
     except ValueError:
index 1ed44713f615ba0737c9954af6717443163ddadb..b712107de61a302e635bf5033c043e9aa0bcdc9b 100644 (file)
@@ -73,7 +73,7 @@
                 { "name": "statusText", "type": "string", "description": "HTTP response status text." },
                 { "name": "headers", "$ref": "Headers", "description": "HTTP response headers." },
                 { "name": "mimeType", "type": "string", "description": "Resource mimeType as determined by the browser." },
-                { "name": "source", "type": "string", "enum": ["unknown", "network", "memory-cache", "disk-cache", "service-worker"], "description": "Specifies where the response came from." },
+                { "name": "source", "type": "string", "enum": ["unknown", "network", "memory-cache", "disk-cache", "service-worker", "inspector-override"], "description": "Specifies where the response came from." },
                 { "name": "requestHeaders", "$ref": "Headers", "optional": true, "description": "Refined HTTP request headers that were actually transmitted over the network." },
                 { "name": "timing", "$ref": "ResourceTiming", "optional": true, "description": "Timing information for the given request." },
                 { "name": "security", "$ref": "Security.Security", "optional": true, "description": "The security information for the given request." }
                 { "name": "lineNumber", "type": "number", "optional": true, "description": "Initiator line number, set for Parser type only." },
                 { "name": "nodeId", "$ref": "DOM.NodeId", "optional": true, "description": "Set if the load was triggered by a DOM node, in addition to the other initiator information." }
             ]
+        },
+        {
+            "id": "NetworkStage",
+            "type": "string",
+            "description": "Different stages of a network request.",
+            "enum": ["response"]
         }
     ],
     "commands": [
             "returns": [
                 { "name": "object", "$ref": "Runtime.RemoteObject", "description": "JavaScript object wrapper for given node." }
             ]
+        },
+        {
+            "name": "setInterceptionEnabled",
+            "description": "Enable interception of network requests.",
+            "parameters": [
+                { "name": "enabled", "type": "boolean" }
+            ]
+        },
+        {
+            "name": "addInterception",
+            "description": "Add an interception.",
+            "parameters": [
+                { "name": "url", "type": "string" },
+                { "name": "stage", "$ref": "NetworkStage", "optional": true, "description": "If not present this applies to all network stages." }
+            ]
+        },
+        {
+            "name": "removeInterception",
+            "description": "Remove an interception.",
+            "parameters": [
+                { "name": "url", "type": "string" },
+                { "name": "stage", "$ref": "NetworkStage", "optional": true, "description": "If not present this applies to all network stages." }
+            ]
+        },
+        {
+            "name": "interceptContinue",
+            "description": "Continue an interception with no modifications.",
+            "parameters": [
+                { "name": "requestId", "$ref": "RequestId", "description": "Identifier for the intercepted Network request or response to continue." }
+            ]
+        },
+        {
+            "name": "interceptWithResponse",
+            "description": "Provide response content for an intercepted response.",
+            "parameters": [
+                { "name": "requestId", "$ref": "RequestId", "description": "Identifier for the intercepted Network response to modify." },
+                { "name": "content", "type": "string" },
+                { "name": "base64Encoded", "type": "boolean", "description": "True, if content was sent as base64." },
+                { "name": "mimeType", "type": "string", "optional": true, "description": "MIME Type for the data." },
+                { "name": "status", "type": "integer", "optional": true, "description": "HTTP response status code. Pass through original values if unmodified." },
+                { "name": "statusText", "type": "string", "optional": true, "description": "HTTP response status text. Pass through original values if unmodified." },
+                { "name": "headers", "$ref": "Headers", "optional": true, "description": "HTTP response headers. Pass through original values if unmodified." }
+            ]
         }
     ],
     "events": [
                 { "name": "resource", "$ref": "CachedResource", "description": "Cached resource data." }
             ]
         },
+        {
+            "name": "responseIntercepted",
+            "description": "Fired when HTTP response has been intercepted. The frontend must response with <code>Network.interceptContinue</code> or <code>Network.interceptWithRespons</code>` to continue this response.",
+            "parameters": [
+                { "name": "requestId", "$ref": "RequestId", "description": "Identifier for this intercepted network. Corresponds with an earlier <code>Network.requestWillBeSent</code>." },
+                { "name": "response", "$ref": "Response", "description": "Original response content that would proceed if this is continued." }
+            ]
+        },
         {
             "name": "webSocketWillSendHandshakeRequest",
             "description": "Fired when WebSocket is about to initiate handshake.",
index cc0ebdaeff64d8ab1dca8be98adefdfd91473e3b..90626a034ff27c8ec4844d0bcfe582092dcbae65 100755 (executable)
@@ -205,13 +205,14 @@ class ObjCBackendDispatcherImplementationGenerator(ObjCGenerator):
             in_param_name = 'in_%s' % parameter.parameter_name
             objc_in_param_expression = 'o_%s' % in_param_name
             if not parameter.is_optional:
-                # FIXME: we don't handle optional enum values in commands here because it isn't used anywhere yet.
-                # We'd need to change the delegate's signature to take Optional for optional enum values.
                 if isinstance(parameter.type, EnumType):
                     objc_in_param_expression = '%s.value()' % objc_in_param_expression
 
                 pairs.append('%s:%s' % (parameter.parameter_name, objc_in_param_expression))
             else:
+                if isinstance(parameter.type, EnumType):
+                    objc_in_param_expression = '%s.value()' % objc_in_param_expression
+
                 optional_expression = '(%s ? &%s : nil)' % (in_param_name, objc_in_param_expression)
                 pairs.append('%s:%s' % (parameter.parameter_name, optional_expression))
         return '    [m_delegate %s%s];' % (command.command_name, ' '.join(pairs))
index 8b310f6911de7240a8744a0e0a1b1a8179967bd5..2b1d8782cb2a7e92c82bcb5733b19add495d29d5 100755 (executable)
@@ -124,7 +124,8 @@ def generate_from_specification(primary_specification_filepath=None,
     def load_specification(protocol, filepath, isSupplemental=False):
         try:
             with open(filepath, "r") as input_file:
-                parsed_json = json.loads(re.sub(r"/\*.+?\*/", "", input_file.read()))
+                regex = re.compile(r"\/\*.*?\*\/", re.DOTALL)
+                parsed_json = json.loads(re.sub(regex, "", input_file.read()))
                 protocol.parse_specification(parsed_json, isSupplemental)
         except ValueError as e:
             raise Exception("Error parsing valid JSON in file: " + filepath + "\nParse error: " + str(e))
index dfff54226c9177d8a011300c486fa4fb4195c8b0..16131ddf808eec4832d3ef35e77895508c9bb1ae 100644 (file)
@@ -898,7 +898,7 @@ void ObjCInspectorDatabaseBackendDispatcher::executeAllOptionalParameters(long r
     if (in_printColor)
         o_in_printColor = fromProtocolString<TestProtocolDatabaseExecuteAllOptionalParametersPrintColor>(*in_printColor);
 
-    [m_delegate executeAllOptionalParametersWithErrorCallback:errorCallback successCallback:successCallback columnNames:(in_columnNames ? &o_in_columnNames : nil) notes:(in_notes ? &o_in_notes : nil) timestamp:(in_timestamp ? &o_in_timestamp : nil) values:(in_values ? &o_in_values : nil) payload:(in_payload ? &o_in_payload : nil) databaseId:(in_databaseId ? &o_in_databaseId : nil) sqlError:(in_sqlError ? &o_in_sqlError : nil) screenColor:(in_screenColor ? &o_in_screenColor : nil) alternateColors:(in_alternateColors ? &o_in_alternateColors : nil) printColor:(in_printColor ? &o_in_printColor : nil)];
+    [m_delegate executeAllOptionalParametersWithErrorCallback:errorCallback successCallback:successCallback columnNames:(in_columnNames ? &o_in_columnNames : nil) notes:(in_notes ? &o_in_notes : nil) timestamp:(in_timestamp ? &o_in_timestamp : nil) values:(in_values ? &o_in_values : nil) payload:(in_payload ? &o_in_payload : nil) databaseId:(in_databaseId ? &o_in_databaseId : nil) sqlError:(in_sqlError ? &o_in_sqlError : nil) screenColor:(in_screenColor ? &o_in_screenColor.value() : nil) alternateColors:(in_alternateColors ? &o_in_alternateColors : nil) printColor:(in_printColor ? &o_in_printColor.value() : nil)];
 }
 
 void ObjCInspectorDatabaseBackendDispatcher::executeNoOptionalParameters(long requestId, const JSON::Array& in_columnNames, const String& in_notes, double in_timestamp, const JSON::Object& in_values, JSON::Value in_payload, int in_databaseId, const JSON::Object& in_sqlError, const String& in_screenColor, const JSON::Array& in_alternateColors, const String& in_printColor)
index ea829f1a216a137334b695c224dd43a26e8670f8..3fe9e3de302a959bca1c73342ebeed1832ac03bf 100644 (file)
@@ -1,3 +1,90 @@
+2019-09-04  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: Local Overrides - Provide substitution content for resource loads (URL based)
+        https://bugs.webkit.org/show_bug.cgi?id=201262
+        <rdar://problem/13108764>
+
+        Reviewed by Devin Rousso.
+
+        Tests: http/tests/inspector/network/local-resource-override-basic.html
+               http/tests/inspector/network/local-resource-override-main-resource.html
+               http/tests/inspector/network/local-resource-override-script-tag.html
+               http/tests/inspector/network/resource-response-inspector-override.html
+
+        * Sources.txt:
+        * WebCore.xcodeproj/project.pbxproj:
+        * Headers.cmake:
+        New files.
+
+        * inspector/InspectorInstrumentation.cpp:
+        (WebCore::InspectorInstrumentation::willInterceptRequestImpl):
+        (WebCore::InspectorInstrumentation::shouldInterceptResponseImpl):
+        (WebCore::InspectorInstrumentation::interceptResponseImpl):
+        * inspector/InspectorInstrumentation.h:
+        (WebCore::InspectorInstrumentation::hasFrontends):
+        (WebCore::InspectorInstrumentation::willInterceptRequest):
+        (WebCore::InspectorInstrumentation::shouldInterceptResponse):
+        (WebCore::InspectorInstrumentation::interceptResponse):
+        (WebCore::InspectorInstrumentation::frontendCreated):
+        (WebCore::InspectorInstrumentation::frontendDeleted):
+        * inspector/InspectorInstrumentationPublic.cpp:
+        * inspector/InspectorInstrumentationPublic.h:
+        * inspector/InspectorInstrumentationWebKit.cpp:
+        (WebCore::InspectorInstrumentationWebKit::shouldInterceptResponseInternal):
+        (WebCore::InspectorInstrumentationWebKit::interceptResponseInternal):
+        * inspector/InspectorInstrumentationWebKit.h: Added.
+        (WebCore::InspectorInstrumentationWebKit::shouldInterceptResponse):
+        (WebCore::InspectorInstrumentationWebKit::interceptResponse):
+        Provide a slim InspectorInstrumentation API that can be used in the WebKit
+        layer without a ton of includes.
+
+        * inspector/agents/InspectorNetworkAgent.cpp:
+        (WebCore::responseSource):
+        (WebCore::InspectorNetworkAgent::disable):
+        (WebCore::InspectorNetworkAgent::continuePendingResponses):
+        (WebCore::InspectorNetworkAgent::setInterceptionEnabled):
+        (WebCore::InspectorNetworkAgent::addInterception):
+        (WebCore::InspectorNetworkAgent::removeInterception):
+        (WebCore::InspectorNetworkAgent::willInterceptRequest):
+        (WebCore::InspectorNetworkAgent::shouldInterceptResponse):
+        (WebCore::InspectorNetworkAgent::interceptResponse):
+        (WebCore::InspectorNetworkAgent::interceptContinue):
+        (WebCore::InspectorNetworkAgent::interceptWithResponse):
+        Manage a list of URLs that will be intercepted and send
+        intercepts to an active frontend for response content.
+
+        * inspector/agents/InspectorNetworkAgent.h:
+        (WebCore::InspectorNetworkAgent::PendingInterceptResponse::PendingInterceptResponse):
+        (WebCore::InspectorNetworkAgent::PendingInterceptResponse::~PendingInterceptResponse):
+        (WebCore::InspectorNetworkAgent::PendingInterceptResponse::originalResponse):
+        (WebCore::InspectorNetworkAgent::PendingInterceptResponse::respondWithOriginalResponse):
+        (WebCore::InspectorNetworkAgent::PendingInterceptResponse::respond):
+        Callback for an eventual intercept response.
+
+        * platform/network/ResourceResponseBase.h:
+        New ResponseSource - Inspector Override.
+
+        * loader/DocumentLoader.cpp:
+        (WebCore::logResourceResponseSource):
+        * testing/Internals.cpp:
+        (WebCore::responseSourceToString):
+        Handle new response sources.
+
+        * loader/cache/CachedResourceLoader.cpp:
+        (WebCore::CachedResourceLoader::requestResource):
+        (WebCore::CachedResourceLoader::preload):
+        Avoid preloading or using the cache for URLs that would be intercepted
+        by an active Inspector frontend.
+
+        * loader/cache/MemoryCache.cpp:
+        (WebCore::MemoryCache::remove):
+        Assertion to help detect if we ever get override content into the MemoryCache.
+
+        * loader/ResourceLoader.h:
+        (WebCore::DocumentLoader::responseReceived):
+        * loader/ResourceLoader.cpp:
+        Fix typos.
+
 2019-09-04  Chris Dumez  <cdumez@apple.com>
 
         Expose WebPageProxy identifier to the Network Process
index 98604cfcdbec4a511b24d2f8acdda198bc7cac8d..30d092962af5f32f26580cf0f86cb1dc09402258 100644 (file)
@@ -656,6 +656,8 @@ set(WebCore_PRIVATE_FRAMEWORK_HEADERS
     inspector/InspectorFrontendClient.h
     inspector/InspectorFrontendClientLocal.h
     inspector/InspectorFrontendHost.h
+    inspector/InspectorInstrumentationPublic.h
+    inspector/InspectorInstrumentationWebKit.h
     inspector/InspectorOverlay.h
     inspector/InspectorWebAgentBase.h
     inspector/PageScriptDebugServer.h
index c9b113c1d80f3f3f4ba9f977540365ba747e1411..ae67a5bdb4d27d82f16dc8695884dc71f32eb27f 100644 (file)
@@ -1338,6 +1338,8 @@ inspector/InspectorFrontendHost.cpp
 inspector/InspectorHistory.cpp
 inspector/InspectorInstrumentation.cpp
 inspector/InspectorInstrumentationCookie.cpp
+inspector/InspectorInstrumentationPublic.cpp
+inspector/InspectorInstrumentationWebKit.cpp
 inspector/InspectorNodeFinder.cpp
 inspector/InspectorOverlay.cpp
 inspector/InspectorShaderProgram.cpp
index 394155a10add925ead761ba517e4d8e8acf1a846..3c8ba99b869724fa0b142723a8c162bb8faa0757 100644 (file)
                A584FE301864CB8400843B10 /* WebInjectedScriptManager.h in Headers */ = {isa = PBXBuildFile; fileRef = A584FE2E1864CB8400843B10 /* WebInjectedScriptManager.h */; };
                A584FE351864D5AF00843B10 /* CommandLineAPIHost.h in Headers */ = {isa = PBXBuildFile; fileRef = A584FE321864D5AF00843B10 /* CommandLineAPIHost.h */; };
                A584FE3C1864E2D800843B10 /* JSCommandLineAPIHost.h in Headers */ = {isa = PBXBuildFile; fileRef = A584FE3A1864E2D800843B10 /* JSCommandLineAPIHost.h */; };
+               A5860E6D230DE30300461AAE /* InspectorInstrumentationPublic.h in Headers */ = {isa = PBXBuildFile; fileRef = A5860E6B230DE2E100461AAE /* InspectorInstrumentationPublic.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               A5860E6E230DE30A00461AAE /* InspectorInstrumentationWebKit.h in Headers */ = {isa = PBXBuildFile; fileRef = A5860E68230DE2E000461AAE /* InspectorInstrumentationWebKit.h */; settings = {ATTRIBUTES = (Private, ); }; };
                A58C59D11E382EAE0047859C /* JSPerformanceMark.h in Headers */ = {isa = PBXBuildFile; fileRef = A58C59CD1E382EA90047859C /* JSPerformanceMark.h */; };
                A58C59D31E382EB20047859C /* JSPerformanceMeasure.h in Headers */ = {isa = PBXBuildFile; fileRef = A58C59CF1E382EA90047859C /* JSPerformanceMeasure.h */; };
                A593CF8B1840535200BFCE27 /* InspectorWebAgentBase.h in Headers */ = {isa = PBXBuildFile; fileRef = A593CF8A1840535200BFCE27 /* InspectorWebAgentBase.h */; settings = {ATTRIBUTES = (Private, ); }; };
                A584FE331864D5AF00843B10 /* CommandLineAPIHost.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CommandLineAPIHost.idl; sourceTree = "<group>"; };
                A584FE391864E2D800843B10 /* JSCommandLineAPIHost.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSCommandLineAPIHost.cpp; sourceTree = "<group>"; };
                A584FE3A1864E2D800843B10 /* JSCommandLineAPIHost.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSCommandLineAPIHost.h; sourceTree = "<group>"; };
+               A5860E68230DE2E000461AAE /* InspectorInstrumentationWebKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = InspectorInstrumentationWebKit.h; sourceTree = "<group>"; };
+               A5860E6A230DE2E100461AAE /* InspectorInstrumentationPublic.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = InspectorInstrumentationPublic.cpp; sourceTree = "<group>"; };
+               A5860E6B230DE2E100461AAE /* InspectorInstrumentationPublic.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = InspectorInstrumentationPublic.h; sourceTree = "<group>"; };
+               A5860E6C230DE2E200461AAE /* InspectorInstrumentationWebKit.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = InspectorInstrumentationWebKit.cpp; sourceTree = "<group>"; };
                A58C59CC1E382EA90047859C /* JSPerformanceMark.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSPerformanceMark.cpp; sourceTree = "<group>"; };
                A58C59CD1E382EA90047859C /* JSPerformanceMark.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSPerformanceMark.h; sourceTree = "<group>"; };
                A58C59CE1E382EA90047859C /* JSPerformanceMeasure.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSPerformanceMeasure.cpp; sourceTree = "<group>"; };
                                20D629251253690B00081543 /* InspectorInstrumentation.h */,
                                A5840E1A187B74D500843B10 /* InspectorInstrumentationCookie.cpp */,
                                A5840E1B187B74D500843B10 /* InspectorInstrumentationCookie.h */,
+                               A5860E6A230DE2E100461AAE /* InspectorInstrumentationPublic.cpp */,
+                               A5860E6B230DE2E100461AAE /* InspectorInstrumentationPublic.h */,
+                               A5860E6C230DE2E200461AAE /* InspectorInstrumentationWebKit.cpp */,
+                               A5860E68230DE2E000461AAE /* InspectorInstrumentationWebKit.h */,
                                504AACCB1834455900E3D9BC /* InspectorNodeFinder.cpp */,
                                504AACCC1834455900E3D9BC /* InspectorNodeFinder.h */,
                                7C522D4915B477E8009B7C95 /* InspectorOverlay.cpp */,
                                07D60928214C5BFD00E7396C /* WindowDisplayCaptureSourceMac.h in Headers */,
                                BC8243E90D0CFD7500460C8F /* WindowFeatures.h in Headers */,
                                7E99AF530B13846468FB01A5 /* WindowFocusAllowedIndicator.h in Headers */,
+                               A5860E6E230DE30A00461AAE /* InspectorInstrumentationWebKit.h in Headers */,
                                463521AD2081092A00C28922 /* WindowProxy.h in Headers */,
                                E1E1BF00115FF6FB006F52CA /* WindowsKeyboardCodes.h in Headers */,
                                501BAAA913950E2C00F7ACEB /* WindRule.h in Headers */,
                                A14832BE187F64CC00DA63A6 /* WKContentObservation.h in Headers */,
+                               A5860E6D230DE30300461AAE /* InspectorInstrumentationPublic.h in Headers */,
                                A14832BF187F652C00DA63A6 /* WKGraphics.h in Headers */,
                                A14832C1187F657A00DA63A6 /* WKTypes.h in Headers */,
                                A14832C3187F65DF00DA63A6 /* WKUtilities.h in Headers */,
index 5d38a55e523916850355e645a44d20cdc852c47e..3513c43171afcf60c82fc41ae65c6795b15ac9d2 100644 (file)
@@ -81,8 +81,6 @@ namespace {
 static HashSet<InstrumentingAgents*>* s_instrumentingAgentsSet = nullptr;
 }
 
-int InspectorInstrumentation::s_frontendCounter = 0;
-
 void InspectorInstrumentation::firstFrontendCreated()
 {
     platformStrategies()->loaderStrategy()->setCaptureExtraNetworkLoadMetricsEnabled(true);
@@ -828,6 +826,26 @@ void InspectorInstrumentation::willDestroyCachedResourceImpl(CachedResource& cac
     }
 }
 
+bool InspectorInstrumentation::willInterceptRequestImpl(InstrumentingAgents& instrumentingAgents, const ResourceRequest& request)
+{
+    if (auto* networkAgent = instrumentingAgents.inspectorNetworkAgent())
+        return networkAgent->willInterceptRequest(request);
+    return false;
+}
+
+bool InspectorInstrumentation::shouldInterceptResponseImpl(InstrumentingAgents& instrumentingAgents, const ResourceResponse& response)
+{
+    if (auto* networkAgent = instrumentingAgents.inspectorNetworkAgent())
+        return networkAgent->shouldInterceptResponse(response);
+    return false;
+}
+
+void InspectorInstrumentation::interceptResponseImpl(InstrumentingAgents& instrumentingAgents, const ResourceResponse& response, unsigned long identifier, CompletionHandler<void(const ResourceResponse&, RefPtr<SharedBuffer>)>&& handler)
+{
+    if (auto* networkAgent = instrumentingAgents.inspectorNetworkAgent())
+        networkAgent->interceptResponse(response, identifier, WTFMove(handler));
+}
+
 // JavaScriptCore InspectorDebuggerAgent should know Console MessageTypes.
 static bool isConsoleAssertMessage(MessageSource source, MessageType type)
 {
index 1fd419d2e8aa8f4be69e97c76cf940c7c9ddb7b6..b44f930a4de1c54fee53682f955fc8800555659e 100644 (file)
@@ -44,6 +44,7 @@
 #include "HitTestResult.h"
 #include "InspectorController.h"
 #include "InspectorInstrumentationCookie.h"
+#include "InspectorInstrumentationPublic.h"
 #include "OffscreenCanvas.h"
 #include "Page.h"
 #include "StorageArea.h"
@@ -51,6 +52,7 @@
 #include "WorkerInspectorController.h"
 #include <JavaScriptCore/JSCInlines.h>
 #include <initializer_list>
+#include <wtf/CompletionHandler.h>
 #include <wtf/MemoryPressureHandler.h>
 #include <wtf/RefPtr.h>
 
@@ -88,6 +90,7 @@ class ResourceResponse;
 class ScriptExecutionContext;
 class SecurityOrigin;
 class ShadowRoot;
+class SharedBuffer;
 class TimerBase;
 #if ENABLE(WEBGL)
 class WebGLProgram;
@@ -99,8 +102,6 @@ enum class StorageType;
 
 struct WebSocketFrame;
 
-#define FAST_RETURN_IF_NO_FRONTENDS(value) if (LIKELY(!InspectorInstrumentation::hasFrontends())) return value;
-
 class InspectorInstrumentation {
 public:
     static void didClearWindowObjectInWorld(Frame&, DOMWrapperWorld&);
@@ -219,6 +220,10 @@ public:
     static void defaultAppearanceDidChange(Page&, bool useDarkAppearance);
     static void willDestroyCachedResource(CachedResource&);
 
+    static bool willInterceptRequest(const Frame*, const ResourceRequest&);
+    static bool shouldInterceptResponse(const Frame&, const ResourceResponse&);
+    static void interceptResponse(const Frame&, const ResourceResponse&, unsigned long identifier, CompletionHandler<void(const ResourceResponse&, RefPtr<SharedBuffer>)>&&);
+
     static void addMessageToConsole(Page&, std::unique_ptr<Inspector::ConsoleMessage>);
     static void addMessageToConsole(WorkerGlobalScope&, std::unique_ptr<Inspector::ConsoleMessage>);
 
@@ -288,7 +293,7 @@ public:
 
     static void frontendCreated();
     static void frontendDeleted();
-    static bool hasFrontends() { return s_frontendCounter; }
+    static bool hasFrontends() { return InspectorInstrumentationPublic::hasFrontends(); }
 
     static void firstFrontendCreated();
     static void lastFrontendDeleted();
@@ -404,6 +409,10 @@ private:
     static void defaultAppearanceDidChangeImpl(InstrumentingAgents&, bool useDarkAppearance);
     static void willDestroyCachedResourceImpl(CachedResource&);
 
+    static bool willInterceptRequestImpl(InstrumentingAgents&, const ResourceRequest&);
+    static bool shouldInterceptResponseImpl(InstrumentingAgents&, const ResourceResponse&);
+    static void interceptResponseImpl(InstrumentingAgents&, const ResourceResponse&, unsigned long identifier, CompletionHandler<void(const ResourceResponse&, RefPtr<SharedBuffer>)>&&);
+
     static void addMessageToConsoleImpl(InstrumentingAgents&, std::unique_ptr<Inspector::ConsoleMessage>);
 
     static void consoleCountImpl(InstrumentingAgents&, JSC::ExecState*, const String& label);
@@ -469,8 +478,8 @@ private:
     static InstrumentingAgents& instrumentingAgentsForPage(Page&);
     static InstrumentingAgents& instrumentingAgentsForWorkerGlobalScope(WorkerGlobalScope&);
 
-    static InstrumentingAgents* instrumentingAgentsForFrame(Frame&);
-    static InstrumentingAgents* instrumentingAgentsForFrame(Frame*);
+    static InstrumentingAgents* instrumentingAgentsForFrame(const Frame&);
+    static InstrumentingAgents* instrumentingAgentsForFrame(const Frame*);
     static InstrumentingAgents* instrumentingAgentsForContext(ScriptExecutionContext*);
     static InstrumentingAgents* instrumentingAgentsForContext(ScriptExecutionContext&);
     static InstrumentingAgents* instrumentingAgentsForDocument(Document&);
@@ -479,8 +488,6 @@ private:
     static InstrumentingAgents* instrumentingAgentsForWorkerGlobalScope(WorkerGlobalScope*);
 
     static InspectorTimelineAgent* retrieveTimelineAgent(const InspectorInstrumentationCookie&);
-
-    WEBCORE_EXPORT static int s_frontendCounter;
 };
 
 inline void InspectorInstrumentation::didClearWindowObjectInWorld(Frame& frame, DOMWrapperWorld& world)
@@ -1199,6 +1206,29 @@ inline void InspectorInstrumentation::willDestroyCachedResource(CachedResource&
     willDestroyCachedResourceImpl(cachedResource);
 }
 
+inline bool InspectorInstrumentation::willInterceptRequest(const Frame* frame, const ResourceRequest& request)
+{
+    FAST_RETURN_IF_NO_FRONTENDS(false);
+    if (auto* instrumentingAgents = instrumentingAgentsForFrame(frame))
+        return willInterceptRequestImpl(*instrumentingAgents, request);
+    return false;
+}
+
+inline bool InspectorInstrumentation::shouldInterceptResponse(const Frame& frame, const ResourceResponse& response)
+{
+    ASSERT(InspectorInstrumentationPublic::hasFrontends());
+    if (auto* instrumentingAgents = instrumentingAgentsForFrame(frame))
+        return shouldInterceptResponseImpl(*instrumentingAgents, response);
+    return false;
+}
+
+inline void InspectorInstrumentation::interceptResponse(const Frame& frame, const ResourceResponse& response, unsigned long identifier, CompletionHandler<void(const ResourceResponse&, RefPtr<SharedBuffer>)>&& handler)
+{
+    ASSERT(InspectorInstrumentation::shouldInterceptResponse(frame, response));
+    if (auto* instrumentingAgents = instrumentingAgentsForFrame(frame))
+        interceptResponseImpl(*instrumentingAgents, response, identifier, WTFMove(handler));
+}
+
 inline void InspectorInstrumentation::didOpenDatabase(Database& database)
 {
     FAST_RETURN_IF_NO_FRONTENDS(void());
@@ -1546,12 +1576,12 @@ inline InstrumentingAgents* InspectorInstrumentation::instrumentingAgentsForCont
     return nullptr;
 }
 
-inline InstrumentingAgents* InspectorInstrumentation::instrumentingAgentsForFrame(Frame* frame)
+inline InstrumentingAgents* InspectorInstrumentation::instrumentingAgentsForFrame(const Frame* frame)
 {
     return frame ? instrumentingAgentsForFrame(*frame) : nullptr;
 }
 
-inline InstrumentingAgents* InspectorInstrumentation::instrumentingAgentsForFrame(Frame& frame)
+inline InstrumentingAgents* InspectorInstrumentation::instrumentingAgentsForFrame(const Frame& frame)
 {
     return instrumentingAgentsForPage(frame.page());
 }
@@ -1593,18 +1623,18 @@ inline InstrumentingAgents& InspectorInstrumentation::instrumentingAgentsForWork
 inline void InspectorInstrumentation::frontendCreated()
 {
     ASSERT(isMainThread());
-    s_frontendCounter++;
+    ++InspectorInstrumentationPublic::s_frontendCounter;
 
-    if (s_frontendCounter == 1)
+    if (InspectorInstrumentationPublic::s_frontendCounter == 1)
         InspectorInstrumentation::firstFrontendCreated();
 }
 
 inline void InspectorInstrumentation::frontendDeleted()
 {
     ASSERT(isMainThread());
-    s_frontendCounter--;
+    --InspectorInstrumentationPublic::s_frontendCounter;
 
-    if (!s_frontendCounter)
+    if (!InspectorInstrumentationPublic::s_frontendCounter)
         InspectorInstrumentation::lastFrontendDeleted();
 }
 
diff --git a/Source/WebCore/inspector/InspectorInstrumentationPublic.cpp b/Source/WebCore/inspector/InspectorInstrumentationPublic.cpp
new file mode 100644 (file)
index 0000000..8cc6706
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "InspectorInstrumentationPublic.h"
+
+namespace WebCore {
+
+int InspectorInstrumentationPublic::s_frontendCounter = 0;
+
+} // namespace WebCore
diff --git a/Source/WebCore/inspector/InspectorInstrumentationPublic.h b/Source/WebCore/inspector/InspectorInstrumentationPublic.h
new file mode 100644 (file)
index 0000000..59246a4
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+namespace WebCore {
+
+#define FAST_RETURN_IF_NO_FRONTENDS(value)                       \
+    if (LIKELY(!InspectorInstrumentationPublic::hasFrontends())) \
+        return value;
+
+class WEBCORE_EXPORT InspectorInstrumentationPublic {
+public:
+    static bool hasFrontends() { return s_frontendCounter; }
+    static int s_frontendCounter;
+};
+
+}
diff --git a/Source/WebCore/inspector/InspectorInstrumentationWebKit.cpp b/Source/WebCore/inspector/InspectorInstrumentationWebKit.cpp
new file mode 100644 (file)
index 0000000..954aaf1
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "InspectorInstrumentationWebKit.h"
+
+#include "InspectorInstrumentation.h"
+
+namespace WebCore {
+
+bool InspectorInstrumentationWebKit::shouldInterceptResponseInternal(const Frame& frame, const ResourceResponse& response)
+{
+    return InspectorInstrumentation::shouldInterceptResponse(frame, response);
+}
+
+void InspectorInstrumentationWebKit::interceptResponseInternal(const Frame& frame, const ResourceResponse& response, unsigned long identifier, CompletionHandler<void(const ResourceResponse&, RefPtr<SharedBuffer>)>&& handler)
+{
+    InspectorInstrumentation::interceptResponse(frame, response, identifier, WTFMove(handler));
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/inspector/InspectorInstrumentationWebKit.h b/Source/WebCore/inspector/InspectorInstrumentationWebKit.h
new file mode 100644 (file)
index 0000000..b67e89b
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "InspectorInstrumentationPublic.h"
+#include <wtf/CompletionHandler.h>
+
+namespace WebCore {
+
+class Frame;
+class ResourceResponse;
+class SharedBuffer;
+
+class WEBCORE_EXPORT InspectorInstrumentationWebKit {
+public:
+    static bool shouldInterceptResponse(const Frame*, const ResourceResponse&);
+    static void interceptResponse(const Frame*, const ResourceResponse&, unsigned long identifier, CompletionHandler<void(const ResourceResponse&, RefPtr<SharedBuffer>)>&&);
+
+private:
+    static bool shouldInterceptResponseInternal(const Frame&, const ResourceResponse&);
+    static void interceptResponseInternal(const Frame&, const ResourceResponse&, unsigned long identifier, CompletionHandler<void(const ResourceResponse&, RefPtr<SharedBuffer>)>&&);
+};
+
+inline bool InspectorInstrumentationWebKit::shouldInterceptResponse(const Frame* frame, const ResourceResponse& response)
+{
+    FAST_RETURN_IF_NO_FRONTENDS(false);
+    if (!frame)
+        return false;
+
+    return shouldInterceptResponseInternal(*frame, response);
+}
+
+inline void InspectorInstrumentationWebKit::interceptResponse(const Frame* frame, const ResourceResponse& response, unsigned long identifier, CompletionHandler<void(const ResourceResponse&, RefPtr<SharedBuffer>)>&& handler)
+{
+    ASSERT(InspectorInstrumentationWebKit::shouldInterceptResponse(frame, response));
+    interceptResponseInternal(*frame, response, identifier, WTFMove(handler));
+}
+
+}
index 85ee4e2beb74ef855cc70105f97162db34c8dc9b..ca664e9c47a1cb46f461346b18d5c9f8cf2d04ec 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2011 Google Inc. All rights reserved.
- * Copyright (C) 2015-2018 Apple Inc. All rights reserved.
+ * Copyright (C) 2015-2019 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are
@@ -77,6 +77,7 @@
 #include <JavaScriptCore/IdentifiersFactory.h>
 #include <JavaScriptCore/InjectedScript.h>
 #include <JavaScriptCore/InjectedScriptManager.h>
+#include <JavaScriptCore/InspectorProtocolObjects.h>
 #include <JavaScriptCore/JSCInlines.h>
 #include <JavaScriptCore/ScriptCallStack.h>
 #include <JavaScriptCore/ScriptCallStackFactory.h>
@@ -312,6 +313,8 @@ static Inspector::Protocol::Network::Response::Source responseSource(ResourceRes
         return Inspector::Protocol::Network::Response::Source::DiskCache;
     case ResourceResponse::Source::ServiceWorker:
         return Inspector::Protocol::Network::Response::Source::ServiceWorker;
+    case ResourceResponse::Source::InspectorOverride:
+        return Inspector::Protocol::Network::Response::Source::InspectorOverride;
     }
 
     ASSERT_NOT_REACHED();
@@ -827,13 +830,24 @@ void InspectorNetworkAgent::enable()
 void InspectorNetworkAgent::disable(ErrorString&)
 {
     m_enabled = false;
+    m_interceptionEnabled = false;
+    m_interceptResponseURLs.clear();
     m_instrumentingAgents.setInspectorNetworkAgent(nullptr);
     m_resourcesData->clear();
     m_extraRequestHeaders.clear();
 
+    continuePendingResponses();
+
     setResourceCachingDisabled(false);
 }
 
+void InspectorNetworkAgent::continuePendingResponses()
+{
+    for (auto& pendingInterceptResponse : m_pendingInterceptResponses.values())
+        pendingInterceptResponse->respondWithOriginalResponse();
+    m_pendingInterceptResponses.clear();
+}
+
 void InspectorNetworkAgent::setExtraHTTPHeaders(ErrorString&, const JSON::Object& headers)
 {
     for (auto& entry : headers) {
@@ -983,6 +997,152 @@ void InspectorNetworkAgent::resolveWebSocket(ErrorString& errorString, const Str
     result = injectedScript.wrapObject(webSocketAsScriptValue(state, webSocket), objectGroupName);
 }
 
+void InspectorNetworkAgent::setInterceptionEnabled(ErrorString& errorString, bool enabled)
+{
+    if (m_interceptionEnabled == enabled) {
+        errorString = m_interceptionEnabled ? "Interception already enabled"_s : "Interception already disabled"_s;
+        return;
+    }
+
+    m_interceptionEnabled = enabled;
+
+    if (!m_interceptionEnabled)
+        continuePendingResponses();
+}
+
+void InspectorNetworkAgent::addInterception(ErrorString& errorString, const String& url, const String* networkStageString)
+{
+    if (networkStageString) {
+        auto networkStage = Inspector::Protocol::InspectorHelpers::parseEnumValueFromString<Inspector::Protocol::Network::NetworkStage>(*networkStageString);
+        if (!networkStage) {
+            errorString = makeString("Unknown networkStage: "_s, *networkStageString);
+            return;
+        }
+    }
+
+    // FIXME: Support intercepting requests.
+
+    if (!m_interceptResponseURLs.add(url).isNewEntry)
+        errorString = "Intercept for given url already exists"_s;
+}
+
+void InspectorNetworkAgent::removeInterception(ErrorString& errorString, const String& url, const String* networkStageString)
+{
+    if (networkStageString) {
+        auto networkStage = Inspector::Protocol::InspectorHelpers::parseEnumValueFromString<Inspector::Protocol::Network::NetworkStage>(*networkStageString);
+        if (!networkStage) {
+            errorString = makeString("Unknown networkStage: "_s, *networkStageString);
+            return;
+        }
+    }
+
+    // FIXME: Support intercepting requests.
+
+    if (!m_interceptResponseURLs.remove(url))
+        errorString = "Missing intercept for given url"_s;
+}
+
+bool InspectorNetworkAgent::willInterceptRequest(const ResourceRequest& request)
+{
+    if (!m_interceptionEnabled)
+        return false;
+
+    URL requestURL = request.url();
+    requestURL.removeFragmentIdentifier();
+
+    String url = requestURL.string();
+    if (url.isEmpty())
+        return false;
+
+    return m_interceptResponseURLs.contains(url);
+}
+
+bool InspectorNetworkAgent::shouldInterceptResponse(const ResourceResponse& response)
+{
+    if (!m_interceptionEnabled)
+        return false;
+
+    URL responseURL = response.url();
+    responseURL.removeFragmentIdentifier();
+
+    String url = responseURL.string();
+    if (url.isEmpty())
+        return false;
+
+    return m_interceptResponseURLs.contains(url);
+}
+
+void InspectorNetworkAgent::interceptResponse(const ResourceResponse& response, unsigned long identifier, CompletionHandler<void(const ResourceResponse&, RefPtr<SharedBuffer>)>&& handler)
+{
+    ASSERT(m_enabled);
+    ASSERT(m_interceptionEnabled);
+
+    String requestId = IdentifiersFactory::requestId(identifier);
+    if (m_pendingInterceptResponses.contains(requestId)) {
+        ASSERT_NOT_REACHED();
+        handler(response, nullptr);
+        return;
+    }
+
+    m_pendingInterceptResponses.set(requestId, makeUnique<PendingInterceptResponse>(response, WTFMove(handler)));
+
+    m_frontendDispatcher->responseIntercepted(requestId, buildObjectForResourceResponse(response, nullptr));
+}
+
+void InspectorNetworkAgent::interceptContinue(ErrorString& errorString, const String& requestId)
+{
+    auto pendingInterceptResponse = m_pendingInterceptResponses.take(requestId);
+    if (!pendingInterceptResponse) {
+        errorString = "Missing pending intercept response for given requestId"_s;
+        return;
+    }
+
+    pendingInterceptResponse->respondWithOriginalResponse();
+}
+
+void InspectorNetworkAgent::interceptWithResponse(ErrorString& errorString, const String& requestId, const String& content, bool base64Encoded, const String* mimeType, const int* status, const String* statusText, const JSON::Object* headers)
+{
+    auto pendingInterceptResponse = m_pendingInterceptResponses.take(requestId);
+    if (!pendingInterceptResponse) {
+        errorString = "Missing pending intercept response for given requestId"_s;
+        return;
+    }
+
+    ResourceResponse overrideResponse(pendingInterceptResponse->originalResponse());
+    overrideResponse.setSource(ResourceResponse::Source::InspectorOverride);
+
+    if (status)
+        overrideResponse.setHTTPStatusCode(*status);
+    if (statusText)
+        overrideResponse.setHTTPStatusText(*statusText);
+    if (mimeType)
+        overrideResponse.setMimeType(*mimeType);
+    if (headers) {
+        HTTPHeaderMap explicitHeaders;
+        for (auto& [key, value] : *headers) {
+            String headerValue;
+            if (value->asString(headerValue))
+                explicitHeaders.add(key, headerValue);
+        }
+        overrideResponse.setHTTPHeaderFields(WTFMove(explicitHeaders));
+        overrideResponse.setHTTPHeaderField(HTTPHeaderName::ContentType, overrideResponse.mimeType());
+    }
+
+    RefPtr<SharedBuffer> overrideData;
+    if (base64Encoded) {
+        Vector<uint8_t> buffer;
+        if (!base64Decode(content, buffer)) {
+            errorString = "Unable to decode given content"_s;
+            pendingInterceptResponse->respondWithOriginalResponse();
+            return;
+        }
+        overrideData = SharedBuffer::create(WTFMove(buffer));
+    } else
+        overrideData = SharedBuffer::create(content.utf8().data(), content.utf8().length());
+
+    pendingInterceptResponse->respond(overrideResponse, overrideData);
+}
+
 bool InspectorNetworkAgent::shouldTreatAsText(const String& mimeType)
 {
     return startsWithLettersIgnoringASCIICase(mimeType, "text/")
index 5298b091e008f31959c58df30486e8a874beb1a7..0b9d9dbe119e2a3127b247d37f53744ad9788a77 100644 (file)
@@ -88,6 +88,11 @@ public:
     void loadResource(const String& frameId, const String& url, Ref<LoadResourceCallback>&&) final;
     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 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;
 
     // InspectorInstrumentation
     void willRecalculateStyle();
@@ -114,6 +119,9 @@ public:
     void mainFrameNavigated(DocumentLoader&);
     void setInitialScriptContent(unsigned long identifier, const String& sourceString);
     void didScheduleStyleRecalculation(Document&);
+    bool willInterceptRequest(const ResourceRequest&);
+    bool shouldInterceptResponse(const ResourceResponse&);
+    void interceptResponse(const ResourceResponse&, unsigned long identifier, CompletionHandler<void(const ResourceResponse&, RefPtr<SharedBuffer>)>&&);
 
     void searchOtherRequests(const JSC::Yarr::RegularExpression&, RefPtr<JSON::ArrayOf<Inspector::Protocol::Page::SearchResult>>&);
     void searchInRequest(ErrorString&, const String& requestId, const String& query, bool caseSensitive, bool isRegex, RefPtr<JSON::ArrayOf<Inspector::Protocol::GenericTypes::SearchMatch>>&);
@@ -133,6 +141,8 @@ private:
 
     void willSendRequest(unsigned long identifier, DocumentLoader*, ResourceRequest&, const ResourceResponse& redirectResponse, InspectorPageAgent::ResourceType);
 
+    void continuePendingResponses();
+
     WebSocket* webSocketForRequestId(const String& requestId);
 
     RefPtr<Inspector::Protocol::Network::Initiator> buildInitiatorObject(Document*, Optional<const ResourceRequest&> = WTF::nullopt);
@@ -143,19 +153,63 @@ private:
 
     double timestamp();
 
+    class PendingInterceptResponse {
+        WTF_MAKE_NONCOPYABLE(PendingInterceptResponse);
+        WTF_MAKE_FAST_ALLOCATED;
+    public:
+        PendingInterceptResponse(const ResourceResponse& originalResponse, CompletionHandler<void(const ResourceResponse&, RefPtr<SharedBuffer>)>&& completionHandler)
+            : m_originalResponse(originalResponse)
+            , m_completionHandler(WTFMove(completionHandler))
+        { }
+
+        ~PendingInterceptResponse()
+        {
+            ASSERT(m_responded);
+        }
+
+        ResourceResponse originalResponse() { return m_originalResponse; }
+
+        void respondWithOriginalResponse()
+        {
+            respond(m_originalResponse, nullptr);
+        }
+
+        void respond(const ResourceResponse& response, RefPtr<SharedBuffer> data)
+        {
+            ASSERT(!m_responded);
+            if (m_responded)
+                return;
+
+            m_responded = true;
+
+            m_completionHandler(response, data);
+        }
+
+    private:
+        ResourceResponse m_originalResponse;
+        CompletionHandler<void(const ResourceResponse&, RefPtr<SharedBuffer>)> m_completionHandler;
+        bool m_responded { false };
+    };
+
     std::unique_ptr<Inspector::NetworkFrontendDispatcher> m_frontendDispatcher;
     RefPtr<Inspector::NetworkBackendDispatcher> m_backendDispatcher;
     Inspector::InjectedScriptManager& m_injectedScriptManager;
 
+    std::unique_ptr<NetworkResourcesData> m_resourcesData;
+
+    HashMap<String, String> m_extraRequestHeaders;
+    HashSet<unsigned long> m_hiddenRequestIdentifiers;
+
+    HashSet<String> m_interceptResponseURLs;
+    HashMap<String, std::unique_ptr<PendingInterceptResponse>> m_pendingInterceptResponses;
+
     // FIXME: InspectorNetworkAgent should not be aware of style recalculation.
     RefPtr<Inspector::Protocol::Network::Initiator> m_styleRecalculationInitiator;
     bool m_isRecalculatingStyle { false };
 
-    std::unique_ptr<NetworkResourcesData> m_resourcesData;
     bool m_enabled { false };
     bool m_loadingXHRSynchronously { false };
-    HashMap<String, String> m_extraRequestHeaders;
-    HashSet<unsigned long> m_hiddenRequestIdentifiers;
+    bool m_interceptionEnabled { false };
 };
 
 } // namespace WebCore
index 0455cafc59ff7b6ea1f9a2cd3931a69e9f694455..c76b7091f5fcdf87460dc3bf5270b2df46b513e6 100644 (file)
@@ -882,8 +882,8 @@ void DocumentLoader::responseReceived(const ResourceResponse& response, Completi
         mainResourceLoader->markInAsyncResponsePolicyCheck();
     auto requestIdentifier = PolicyCheckIdentifier::create();
     frameLoader()->checkContentPolicy(m_response, requestIdentifier, [this, protectedThis = makeRef(*this), mainResourceLoader = WTFMove(mainResourceLoader),
-        completionHandler = completionHandlerCaller.release(), requestIdentifier] (PolicyAction policy, PolicyCheckIdentifier responseIdentifeir) mutable {
-        RELEASE_ASSERT(responseIdentifeir.isValidFor(requestIdentifier));
+        completionHandler = completionHandlerCaller.release(), requestIdentifier] (PolicyAction policy, PolicyCheckIdentifier responseIdentifier) mutable {
+        RELEASE_ASSERT(responseIdentifier.isValidFor(requestIdentifier));
         continueAfterContentPolicy(policy);
         if (mainResourceLoader)
             mainResourceLoader->didReceiveResponsePolicy();
index a76656c03e7125285388ab5d65d8dc9df53e2a86..c66e789c8e43e1ec7a05f9f9db21daae31057b95 100644 (file)
@@ -458,6 +458,7 @@ static void logResourceResponseSource(Frame* frame, ResourceResponse::Source sou
     case ResourceResponse::Source::MemoryCache:
     case ResourceResponse::Source::MemoryCacheAfterValidation:
     case ResourceResponse::Source::ApplicationCache:
+    case ResourceResponse::Source::InspectorOverride:
     case ResourceResponse::Source::Unknown:
         return;
     }
index d17e58d6bd0449225c13b1a338fd1010f572eec6..5c5c8df751a26e88b256db232961575c7e52d314 100644 (file)
@@ -130,7 +130,6 @@ public:
 
     bool reachedTerminalState() const { return m_reachedTerminalState; }
 
-
     const ResourceRequest& request() const { return m_request; }
     void setRequest(ResourceRequest&& request) { m_request = WTFMove(request); }
 
index b34b8fd35c1077a5452abb3432361b4cc3d11c98..fe12ec1961a7daa38d5721019a69ec2b22549d1d 100644 (file)
@@ -55,6 +55,7 @@
 #include "HTMLElement.h"
 #include "HTMLFrameOwnerElement.h"
 #include "HTTPHeaderField.h"
+#include "InspectorInstrumentation.h"
 #include "LoaderStrategy.h"
 #include "LocalizedStrings.h"
 #include "Logging.h"
@@ -799,6 +800,9 @@ ResourceErrorOr<CachedResourceHandle<CachedResource>> CachedResourceLoader::requ
     if (Document* document = this->document())
         request.upgradeInsecureRequestIfNeeded(*document);
 
+    if (InspectorInstrumentation::willInterceptRequest(frame(), request.resourceRequest()))
+        request.setCachingPolicy(CachingPolicy::DisallowCaching);
+
     request.updateReferrerPolicy(document() ? document()->referrerPolicy() : ReferrerPolicy::NoReferrerWhenDowngrade);
     URL url = request.resourceRequest().url();
 
@@ -1379,6 +1383,9 @@ void CachedResourceLoader::decrementRequestCount(const CachedResource& resource)
 
 ResourceErrorOr<CachedResourceHandle<CachedResource>> CachedResourceLoader::preload(CachedResource::Type type, CachedResourceRequest&& request)
 {
+    if (InspectorInstrumentation::willInterceptRequest(frame(), request.resourceRequest()))
+        return makeUnexpected(ResourceError { errorDomainWebKitInternal, 0, request.resourceRequest().url(), "Inspector intercept"_s });
+
     if (request.charset().isEmpty() && (type == CachedResource::Type::Script || type == CachedResource::Type::CSSStyleSheet))
         request.setCharset(m_document->charset());
 
index 1d4a078da919d3fec08431e4027d4b6b39557797..907fdd60c9d6c9db584b9df910626612da99b013 100644 (file)
@@ -396,6 +396,8 @@ void MemoryCache::remove(CachedResource& resource)
         auto key = std::make_pair(resource.url(), resource.cachePartition());
 
         if (resource.inCache()) {
+            ASSERT_WITH_MESSAGE(resource.response().source() != ResourceResponse::Source::InspectorOverride, "InspectorOverride responses should not get into the MemoryCache");
+
             // Remove resource from the resource map.
             resources->remove(key);
             resource.setInCache(false);
index 9f1b3b2e7fc67935bf788d4977190dee5305855d..cb17b595487d10b773c4e4853543024a50f1a97f 100644 (file)
@@ -41,7 +41,7 @@ class ResourceResponse;
 
 bool isScriptAllowedByNosniff(const ResourceResponse&);
 
-// Do not use this class directly, use the class ResponseResponse instead
+// Do not use this class directly, use the class ResourceResponse instead
 class ResourceResponseBase {
     WTF_MAKE_FAST_ALLOCATED;
 public:
@@ -142,7 +142,7 @@ public:
     WEBCORE_EXPORT Optional<WallTime> lastModified() const;
     const ParsedContentRange& contentRange() const;
 
-    enum class Source : uint8_t { Unknown, Network, DiskCache, DiskCacheAfterValidation, MemoryCache, MemoryCacheAfterValidation, ServiceWorker, ApplicationCache };
+    enum class Source : uint8_t { Unknown, Network, DiskCache, DiskCacheAfterValidation, MemoryCache, MemoryCacheAfterValidation, ServiceWorker, ApplicationCache, InspectorOverride };
     WEBCORE_EXPORT Source source() const;
     void setSource(Source source)
     {
index b471de5b904f1bcc91d2710e228f958ed2610a9d..ba431c7357e73e374154d4f92ce6a3c8b34529e9 100644 (file)
@@ -704,6 +704,8 @@ static String responseSourceToString(const ResourceResponse& response)
         return "Memory cache after validation";
     case ResourceResponse::Source::ApplicationCache:
         return "Application cache";
+    case ResourceResponse::Source::InspectorOverride:
+        return "Inspector override";
     }
     ASSERT_NOT_REACHED();
     return "Error";
index 72a2228df850f477659ba2539a8a609128950d86..7c90d011cf77e4bc3647582eba0d0f74000125e0 100644 (file)
@@ -1,3 +1,308 @@
+2019-09-04  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: Local Overrides - Provide substitution content for resource loads (URL based)
+        https://bugs.webkit.org/show_bug.cgi?id=201262
+        <rdar://problem/13108764>
+
+        Reviewed by Devin Rousso.
+
+        This adds a new "Local Overrides" section to the Sources tab sidebar
+        which will allow users to provide their own resource content for text
+        resources. Users can clone a resource, and provide their own content
+        (by editing in Web Inspector) and new requests for those particular
+        URLs will get the substitute content.
+
+        Overrides are based on a particular URL (ignoring fragment). They
+        can override: status code, status text, response headers, content,
+        and MIME Type (Content-Type).
+
+        * Tools/CodeMirrorModes/index.html: Added.
+        * Tools/CodeMirrorModes/styles.css: Added.
+        Debug tool for CodeMirror editors and our custom CodeMirror modes.
+
+        * UserInterface/Main.html:
+        * UserInterface/Test.html:
+        * Localizations/en.lproj/localizedStrings.js:
+        New files and strings.
+
+        * UserInterface/Base/HTTPUtilities.js: Added.
+        (WI.httpStatusTextForStatusCode):
+        Translate between typical status codes and status text.
+
+        * UserInterface/Base/ObjectStore.js:
+        (WI.ObjectStore._open):
+        New persistent store for local resource overrides.
+
+        * UserInterface/Base/Main.js:
+        (WI.showLocalResourceOverride):
+        Convenience for showing an override file.
+
+        * UserInterface/Base/URLUtilities.js:
+        (parseURL):
+        Avoid uncaught exceptions with the URL constructor for common WebKit internal sourceURL strings.
+        
+        (WI.urlWithoutFragment):
+        Strip a fragment from a URL.
+
+        * UserInterface/Controllers/HARBuilder.js:
+        (WI.HARBuilder.fetchType):
+        (WI.HARBuilder.responseSourceFromHARFetchType):
+        Handle new custom response types.
+
+        * UserInterface/Protocol/NetworkObserver.js:
+        (WI.NetworkObserver.prototype.responseIntercepted):
+        (WI.NetworkObserver):
+        New events.
+
+        * UserInterface/Controllers/NetworkManager.js:
+        (WI.NetworkManager):
+        (WI.NetworkManager.supportsLocalResourceOverrides):
+        (WI.NetworkManager.prototype.initializeTarget):
+        (WI.NetworkManager.prototype.get localResourceOverrides):
+        (WI.NetworkManager.prototype.get interceptionEnabled):
+        (WI.NetworkManager.prototype.set interceptionEnabled):
+        (WI.NetworkManager.prototype.addLocalResourceOverride):
+        (WI.NetworkManager.prototype.removeLocalResourceOverride):
+        (WI.NetworkManager.prototype.localResourceOverrideForURL):
+        (WI.NetworkManager.prototype.canBeOverridden):
+        (WI.NetworkManager.prototype.responseIntercepted):
+        (WI.NetworkManager.prototype._handleResourceContentDidChange):
+        (WI.NetworkManager.prototype._persistLocalResourceOverrideSoonAfterContentChange):
+        (WI.NetworkManager.prototype._saveLocalResourceOverrides):
+        (WI.NetworkManager.prototype._extraDomainsActivated):
+        (WI.NetworkManager.prototype.localResourceForURL): Deleted.
+        Handle saving and restoring local resource overrides.
+        Handle responding to a `responseIntercepted` Network protocol event.
+
+        * UserInterface/Models/LocalResource.js:
+        (WI.LocalResource.fromJSON):
+        (WI.LocalResource.prototype.toJSON):
+        (WI.LocalResource.prototype.get localContent):
+        (WI.LocalResource.prototype.get localContentIsBase64Encoded):
+        (WI.LocalResource.prototype.isLocalResourceOverride):
+        (WI.LocalResource.prototype.updateOverrideContent):
+        Allow a LocalResource to identify itself as an "override".
+
+        * UserInterface/Models/LocalResourceOverride.js: Added.
+        (WI.LocalResourceOverride.prototype.create):
+        (WI.LocalResourceOverride.fromJSON):
+        (WI.LocalResourceOverride.prototype.toJSON):
+        (WI.LocalResourceOverride.prototype.get url):
+        (WI.LocalResourceOverride.prototype.get localResource):
+        (WI.LocalResourceOverride.prototype.get disabled):
+        (WI.LocalResourceOverride.prototype.set disabled):
+        (WI.LocalResourceOverride.prototype.saveIdentityToCookie):
+        (WI.LocalResourceOverride):
+        Model object for a LocalResourceOverride. This has LocalResource content
+        and an enabled/disabled state.
+
+        * UserInterface/Models/Resource.js:
+        (WI.Resource.classNamesForResource):
+        (WI.Resource.responseSourceFromPayload):
+        (WI.Resource.prototype.isLocalResourceOverride):
+        (WI.Resource.prototype.async.createLocalResourceOverride):
+        (WI.Resource.classNameForResource): Deleted.
+        Convenience functions and icon updates.
+
+        * UserInterface/Views/SourcesTabContentView.js:
+        (WI.SourcesTabContentView.prototype.canShowRepresentedObject):
+        * UserInterface/Views/ContentView.js:
+        (WI.ContentView.createFromRepresentedObject):
+        (WI.ContentView.resolvedRepresentedObjectForRepresentedObject):
+        (WI.ContentView.isViewable):
+        Handle new represented object type.
+
+        * UserInterface/Views/SourcesNavigationSidebarPanel.js:
+        (WI.SourcesNavigationSidebarPanel):
+        (WI.SourcesNavigationSidebarPanel.prototype.createContentTreeOutline):
+        (WI.SourcesNavigationSidebarPanel.prototype.willDismissPopover):
+        (WI.SourcesNavigationSidebarPanel.prototype._willDismissLocalOverridePopover):
+        (WI.SourcesNavigationSidebarPanel.prototype._willDismissEventBreakpointPopover):
+        (WI.SourcesNavigationSidebarPanel.prototype._willDismissURLBreakpointPopover):
+        (WI.SourcesNavigationSidebarPanel.prototype._addLocalResourceOverride):
+        (WI.SourcesNavigationSidebarPanel.prototype._removeLocalResourceOverride):
+        (WI.SourcesNavigationSidebarPanel.prototype._handleTreeSelectionDidChange):
+        (WI.SourcesNavigationSidebarPanel.prototype._populateCreateBreakpointContextMenu):
+        (WI.SourcesNavigationSidebarPanel.prototype._handleLocalResourceOverrideAdded):
+        (WI.SourcesNavigationSidebarPanel.prototype._handleLocalResourceOverrideRemoved):
+        * UserInterface/Views/SourcesNavigationSidebarPanel.css:
+        (.sidebar > .panel.navigation.sources > .content > .warning-banner):
+        (.sidebar > .panel.navigation.sources > .content > :matches(.pause-reason-container, .call-stack-container, .breakpoints-container, .local-overrides)):
+        (.sidebar > .panel.navigation.sources > .content > .local-overrides):
+        (.sidebar > .panel.navigation.sources > .content > :matches(.pause-reason-container, .call-stack-container, .breakpoints-container)): Deleted.
+        Hide and show Local Overrides section.
+
+        * UserInterface/Views/LocalResourceOverrideTreeElement.css:
+        (.item.resource.override .status > div):
+        * UserInterface/Views/LocalResourceOverrideTreeElement.js: Added.
+        (WI.LocalResourceOverrideTreeElement):
+        (WI.LocalResourceOverrideTreeElement.prototype.canSelectOnMouseDown):
+        (WI.LocalResourceOverrideTreeElement.prototype.populateContextMenu):
+        (WI.LocalResourceOverrideTreeElement.prototype.willDismissPopover):
+        TreeElement for a Local Resource Override.
+
+        * UserInterface/Views/CodeMirrorLocalOverrideURLMode.css:
+        (.cm-s-default .cm-local-override-url-bad-scheme):
+        (.cm-s-default .cm-local-override-url-fragment):
+        * UserInterface/Views/CodeMirrorLocalOverrideURLMode.js: Added.
+        (tokenBase):
+        (return.startState):
+        (return.token):
+        * UserInterface/Views/ContentBrowserTabContentView.js:
+        (WI.ContentBrowserTabContentView.prototype._revealAndSelectRepresentedObject):
+
+        * UserInterface/Views/ContextMenu.js:
+        (WI.ContextMenu.prototype._itemSelected):
+        (WI.ContextMenu):
+        Better debugging for exceptions in context menu handlers.
+
+        * UserInterface/Views/ContextMenuUtilities.js:
+        (WI.appendContextMenuItemsForSourceCode):
+        (WI.appendContextMenuItemsForURL):
+        Context menu items for Local Resource Overrides.
+
+        * UserInterface/Views/DataGrid.js:
+        (WI.DataGrid.prototype.startEditingNode):
+        (WI.DataGrid.prototype._startEditingNodeAtColumnIndex):
+        (WI.DataGrid.prototype._startEditing):
+        (WI.DataGrid.prototype._contextMenuInDataTable):
+        * UserInterface/Views/DataGridNode.js:
+        (WI.DataGridNode):
+        (WI.DataGridNode.prototype.get editable):
+        (WI.DataGridNode.prototype.set editable):
+        Improve DataGrid editing functionality.
+        Allow a node to not be editable.
+        Allow adding a new node and starting to edit in one action.
+
+        * UserInterface/Views/DebuggerSidebarPanel.js:
+        (WI.DebuggerSidebarPanel.prototype.treeElementForRepresentedObject):
+        Do not provide overrides in the Debugger tab.
+
+        * UserInterface/Views/LocalResourceOverrideLabelView.css:
+        (.local-resource-override-label-view):
+        (.local-resource-override-label-view > div):
+        (.local-resource-override-label-view > div > .label):
+        (.local-resource-override-label-view > div > .url):
+        (@media (prefers-color-scheme: dark)):
+        * UserInterface/Views/LocalResourceOverrideLabelView.js:
+        (WI.LocalResourceOverrideLabelView):
+        (WI.LocalResourceOverrideLabelView.prototype.initialLayout):
+        * UserInterface/Views/LocalResourceOverridePopover.css: Added.
+        (.popover .local-resource-override-popover-content):
+        (.popover .local-resource-override-popover-content > label.toggle):
+        (.popover .local-resource-override-popover-content > table):
+        (.popover .local-resource-override-popover-content > table > tr > th):
+        (.popover .local-resource-override-popover-content > table > tr > td):
+        (.popover .local-resource-override-popover-content .editor):
+        (.popover .local-resource-override-popover-content .editor > .CodeMirror):
+        (.popover .local-resource-override-popover-content .editor.url):
+        (.popover .local-resource-override-popover-content .editor.mime):
+        (.popover .local-resource-override-popover-content .editor.status):
+        (.popover .local-resource-override-popover-content .editor.status-text):
+        (.popover .local-resource-override-popover-content .add-header):
+        (@media (prefers-color-scheme: dark)):
+        New banner view for a local resource override itself.
+        Shows the URL being overriden.
+
+        * UserInterface/Views/LocalResourceOverrideWarningView.css:
+        (.local-resource-override-warning-view):
+        (.local-resource-override-warning-view[hidden]):
+        (.local-resource-override-warning-view > div):
+        (.local-resource-override-warning-view > div > button):
+        (@media (prefers-color-scheme: dark)):
+        * UserInterface/Views/LocalResourceOverrideWarningView.js: Added.
+        (WI.LocalResourceOverrideWarningView):
+        (WI.LocalResourceOverrideWarningView.prototype.attached):
+        (WI.LocalResourceOverrideWarningView.prototype.detached):
+        (WI.LocalResourceOverrideWarningView.prototype._updateContent):
+        (WI.LocalResourceOverrideWarningView.prototype._handleLocalResourceOverrideChanged):
+        * UserInterface/Views/NavigationSidebarPanel.js:
+        (WI.NavigationSidebarPanel.prototype.pruneStaleResourceTreeElements):
+        New banner view for a resource that has been overridden.
+        Allows jumping to the override itself.
+
+        * UserInterface/Views/LocalResourceOverridePopover.js: Added.
+        (WI.LocalResourceOverridePopover):
+        (WI.LocalResourceOverridePopover.prototype.get serializedData):
+        (WI.LocalResourceOverridePopover.prototype.show.addDataGridNodeForHeader):
+        (WI.LocalResourceOverridePopover.prototype.show):
+        (WI.LocalResourceOverridePopover.prototype._createEditor):
+        (WI.LocalResourceOverridePopover.prototype._defaultURL):
+        (WI.LocalResourceOverridePopover.prototype._presentOverTargetElement):
+        New popover for creating or editing a Local Resource Override.
+
+        * UserInterface/Views/SearchSidebarPanel.js:
+        (WI.SearchSidebarPanel.prototype.performSearch):
+        Consider searching overrides.
+
+        * UserInterface/Views/Variables.css:
+        (:root):
+        * UserInterface/Views/SearchSidebarPanel.css:
+        (.sidebar > .panel.navigation.search.changed > .banner):
+        * UserInterface/Views/DebuggerSidebarPanel.css:
+        (.sidebar > .panel.navigation.debugger .warning-banner):
+        * UserInterface/Views/ConsoleMessageView.css:
+        (.console-warning-level):
+        Use a new variable for a common warning color.
+
+        * UserInterface/Images/NavigationItemNetworkOverride.svg: Added.
+        * UserInterface/Views/SourceCodeTextEditor.js:
+        (WI.SourceCodeTextEditor.prototype.canBeFormatted):
+        (WI.SourceCodeTextEditor.prototype.get _supportsDebugging):
+        * UserInterface/Views/SourcesNavigationSidebarPanel.css:
+        (.sidebar > .panel.navigation.sources > .content > .warning-banner):
+        (.sidebar > .panel.navigation.sources > .content > :matches(.pause-reason-container, .call-stack-container, .breakpoints-container, .local-overrides)):
+        (.sidebar > .panel.navigation.sources > .content > .local-overrides):
+        (.sidebar > .panel.navigation.sources > .content > :matches(.pause-reason-container, .call-stack-container, .breakpoints-container)): Deleted.
+        * UserInterface/Views/TextEditor.css:
+        (.text-editor):
+        * UserInterface/Views/TextResourceContentView.css:
+        (.content-view.resource.text):
+        (.content-view.resource.text > .text-editor):
+        * UserInterface/Views/TextResourceContentView.js:
+        (WI.TextResourceContentView):
+        (WI.TextResourceContentView.prototype.get navigationItems):
+        (WI.TextResourceContentView.prototype.closed):
+        (WI.TextResourceContentView.prototype._contentWillPopulate):
+        (WI.TextResourceContentView.prototype._contentDidPopulate):
+        (WI.TextResourceContentView.prototype.async._handleCreateLocalResourceOverride):
+        (WI.TextResourceContentView.prototype._handleRemoveLocalResourceOverride):
+        (WI.TextResourceContentView.prototype._handleLocalResourceOverrideChanged):
+        (WI.TextResourceContentView.prototype._textEditorContentDidChange):
+        (WI.TextResourceContentView.prototype._shouldBeEditable):
+        Allow Text resources to create a local resource override.
+        Support for Image resources will come separately.
+
+        * UserInterface/Views/ResourceHeadersContentView.js:
+        (WI.ResourceHeadersContentView.prototype._responseSourceDisplayString):
+        Handle new response type.
+
+        * UserInterface/Controllers/CSSManager.js:
+        Avoid extra handling for Local Resource Overrides.
+
+        * UserInterface/Views/ResourceIcons.css:
+        (.resource-icon.override .icon):
+        * UserInterface/Views/ResourceSizesContentView.js:
+        (WI.ResourceSizesContentView.prototype.initialLayout):
+        * UserInterface/Views/ResourceTimelineDataGridNode.js:
+        (WI.ResourceTimelineDataGridNode.prototype.iconClassNames):
+        * UserInterface/Views/ResourceTreeElement.js:
+        (WI.ResourceTreeElement.prototype._updateResource):
+        (WI.ResourceTreeElement.prototype._updateIcon):
+        (WI.ResourceTreeElement.prototype._responseReceived):
+        (WI.ResourceTreeElement):
+        * UserInterface/Views/TimelineDataGridNode.js:
+        (WI.TimelineDataGridNode.prototype.createCellContent):
+        * UserInterface/Views/NetworkTableContentView.js:
+        (WI.NetworkTableContentView.prototype._populateNameCell):
+        (WI.NetworkTableContentView.prototype._populateTransferSizeCell):
+        (WI.NetworkTableContentView.prototype._generateSortComparator):
+        Better Resource icons all over for overrides.
+
+        * UserInterface/Views/URLBreakpointPopover.js:
+        (WI.URLBreakpointPopover.prototype._createEditor):
+        Code cleanup.
+
 2019-09-03  Devin Rousso  <drousso@apple.com>
 
         Web Inspector: provide a way to view XML/HTML/SVG resource responses as a DOM tree
index aa527a8733fc4efbe59aa2bbadf5c2772dfeb10b..c5e976cb72bc06a9a6e58873b75e1dfde6558870 100644 (file)
@@ -69,6 +69,7 @@ localizedStrings["(Tail Call)"] = "(Tail Call)";
 localizedStrings["(anonymous function)"] = "(anonymous function)";
 localizedStrings["(async)"] = "(async)";
 localizedStrings["(disk)"] = "(disk)";
+localizedStrings["(inspector override)"] = "(inspector override)";
 localizedStrings["(many)"] = "(many)";
 localizedStrings["(memory)"] = "(memory)";
 localizedStrings["(multiple)"] = "(multiple)";
@@ -86,6 +87,7 @@ localizedStrings["Add %s Rule"] = "Add %s Rule";
 localizedStrings["Add Action"] = "Add Action";
 localizedStrings["Add Breakpoint"] = "Add Breakpoint";
 localizedStrings["Add Breakpoints"] = "Add Breakpoints";
+localizedStrings["Add Header"] = "Add Header";
 localizedStrings["Add New"] = "Add New";
 localizedStrings["Add New Class"] = "Add New Class";
 localizedStrings["Add New Probe Expression"] = "Add New Probe Expression";
@@ -304,6 +306,7 @@ localizedStrings["Could not fetch properties. Object may no longer exist."] = "C
 localizedStrings["Count"] = "Count";
 localizedStrings["Create %s Rule"] = "Create %s Rule";
 localizedStrings["Create Breakpoint"] = "Create Breakpoint";
+localizedStrings["Create Local Override"] = "Create Local Override";
 localizedStrings["Create Resource"] = "Create Resource";
 localizedStrings["Create a new tab"] = "Create a new tab";
 localizedStrings["Cross-Origin Restrictions"] = "Cross-Origin Restrictions";
@@ -352,6 +355,7 @@ localizedStrings["Disable Encryption"] = "Disable Encryption";
 localizedStrings["Disable Event Listener"] = "Disable Event Listener";
 localizedStrings["Disable Event Listeners"] = "Disable Event Listeners";
 localizedStrings["Disable ICE Candidate Restrictions"] = "Disable ICE Candidate Restrictions";
+localizedStrings["Disable Local Override"] = "Disable Local Override";
 localizedStrings["Disable Program"] = "Disable Program";
 localizedStrings["Disable Rule"] = "Disable Rule";
 localizedStrings["Disable all breakpoints (%s)"] = "Disable all breakpoints (%s)";
@@ -386,6 +390,7 @@ localizedStrings["Dynamically calculated for the selected element"] = "Dynamical
 localizedStrings["Dynamically calculated for the selected element and did not match"] = "Dynamically calculated for the selected element and did not match";
 localizedStrings["Edit"] = "Edit";
 localizedStrings["Edit Breakpoint\u2026"] = "Edit Breakpoint\u2026";
+localizedStrings["Edit Local Override\u2026"] = "Edit Local Override\u2026";
 localizedStrings["Edit \u201C%s\u201D"] = "Edit \u201C%s\u201D";
 localizedStrings["Edit \u201Ccubic-bezier\u201D function"] = "Edit \u201Ccubic-bezier\u201D function";
 localizedStrings["Edit \u201Cspring\u201D function"] = "Edit \u201Cspring\u201D function";
@@ -429,6 +434,7 @@ localizedStrings["Enable Descendant Breakpoints"] = "Enable Descendant Breakpoin
 localizedStrings["Enable Event Listener"] = "Enable Event Listener";
 localizedStrings["Enable Event Listeners"] = "Enable Event Listeners";
 localizedStrings["Enable Layers Tab"] = "Enable Layers Tab";
+localizedStrings["Enable Local Override"] = "Enable Local Override";
 localizedStrings["Enable New Tab Bar"] = "Enable New Tab Bar";
 localizedStrings["Enable Program"] = "Enable Program";
 localizedStrings["Enable Rule"] = "Enable Rule";
@@ -612,6 +618,7 @@ localizedStrings["Initial Velocity"] = "Initial Velocity";
 localizedStrings["Initiated"] = "Initiated";
 localizedStrings["Initiator"] = "Initiator";
 localizedStrings["Input: "] = "Input: ";
+localizedStrings["Inspector Override"] = "Inspector Override";
 localizedStrings["Inspector Style Sheet"] = "Inspector Style Sheet";
 localizedStrings["Instances"] = "Instances";
 localizedStrings["Invalid"] = "Invalid";
@@ -648,6 +655,8 @@ localizedStrings["Live Size"] = "Live Size";
 localizedStrings["Load \u2014 %s"] = "Load \u2014 %s";
 localizedStrings["Load cancelled"] = "Load cancelled";
 localizedStrings["Local File"] = "Local File";
+localizedStrings["Local Override\u2026"] = "Local Override\u2026";
+localizedStrings["Local Overrides"] = "Local Overrides";
 localizedStrings["Local Storage"] = "Local Storage";
 localizedStrings["Local Variables"] = "Local Variables";
 localizedStrings["Located at %s"] = "Located at %s";
@@ -776,6 +785,7 @@ localizedStrings["Outgoing message"] = "Outgoing message";
 localizedStrings["Output: "] = "Output: ";
 localizedStrings["Over 1 ms"] = "Over 1 ms";
 localizedStrings["Over 15 ms"] = "Over 15 ms";
+localizedStrings["Override"] = "Override";
 localizedStrings["Overview"] = "Overview";
 localizedStrings["Owns"] = "Owns";
 localizedStrings["PDF"] = "PDF";
@@ -865,6 +875,7 @@ localizedStrings["Regular Expression @ Settings"] = "Regular Expression";
 localizedStrings["Reload Web Inspector"] = "Reload Web Inspector";
 localizedStrings["Reload page (%s)\nReload page ignoring cache (%s)"] = "Reload page (%s)\nReload page ignoring cache (%s)";
 localizedStrings["Removals"] = "Removals";
+localizedStrings["Remove Local Override"] = "Remove Local Override";
 localizedStrings["Remove Watch Expression"] = "Remove Watch Expression";
 localizedStrings["Remove probe"] = "Remove probe";
 localizedStrings["Remove this breakpoint action"] = "Remove this breakpoint action";
@@ -910,6 +921,7 @@ localizedStrings["Return string must be one of %s"] = "Return string must be one
 localizedStrings["Return type for anonymous function"] = "Return type for anonymous function";
 localizedStrings["Return type for function: %s"] = "Return type for function: %s";
 localizedStrings["Return value is not an object, string, or boolean"] = "Return value is not an object, string, or boolean";
+localizedStrings["Reveal"] = "Reveal";
 localizedStrings["Reveal Descendant Breakpoints"] = "Reveal Descendant Breakpoints";
 /* Open Elements tab and select this node in DOM tree */
 localizedStrings["Reveal in DOM Tree"] = "Reveal in DOM Tree";
@@ -1112,6 +1124,7 @@ localizedStrings["These tests demonstrate how to use %s to get information about
 localizedStrings["These tests demonstrate how to use %s to get information about loaded resources."] = "These tests demonstrate how to use %s to get information about loaded resources.";
 localizedStrings["These tests demonstrate how to use %s to get information about the accessibility tree."] = "These tests demonstrate how to use %s to get information about the accessibility tree.";
 localizedStrings["These tests serve as a demonstration of the functionality and structure of audits."] = "These tests serve as a demonstration of the functionality and structure of audits.";
+localizedStrings["This Resource came from a Local Resource Override"] = "This Resource came from a Local Resource Override";
 localizedStrings["This action causes no visual change"] = "This action causes no visual change";
 localizedStrings["This action moves the path outside the visible area"] = "This action moves the path outside the visible area";
 localizedStrings["This audit is not supported"] = "This audit is not supported";
diff --git a/Source/WebInspectorUI/Tools/CodeMirrorModes/index.html b/Source/WebInspectorUI/Tools/CodeMirrorModes/index.html
new file mode 100644 (file)
index 0000000..b0bce32
--- /dev/null
@@ -0,0 +1,123 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CodeMirrorModes Tool</title>
+
+    <style>:root { color-scheme: light dark; }</style>
+    <link rel="stylesheet" href="../../UserInterface/External/CodeMirror/codemirror.css">
+    <link rel="stylesheet" href="../../UserInterface/Views/Variables.css">
+    <link rel="stylesheet" href="../../UserInterface/Views/CodeMirrorOverrides.css">
+    <link rel="stylesheet" href="../../UserInterface/Views/SyntaxHighlightingDefaultTheme.css">
+    <link rel="stylesheet" href="../../UserInterface/Views/CodeMirrorLocalOverrideURLMode.css">
+    <link rel="stylesheet" href="../../UserInterface/Views/CodeMirrorRegexMode.css">
+    <link rel="stylesheet" href="styles.css">
+
+    <script src="../../UserInterface/External/CodeMirror/codemirror.js"></script>
+    <script src="../../UserInterface/External/CodeMirror/clike.js"></script>
+    <script src="../../UserInterface/External/CodeMirror/clojure.js"></script>
+    <script src="../../UserInterface/External/CodeMirror/coffeescript.js"></script>
+    <script src="../../UserInterface/External/CodeMirror/css.js"></script>
+    <script src="../../UserInterface/External/CodeMirror/css.js"></script>
+    <script src="../../UserInterface/External/CodeMirror/htmlmixed.js"></script>
+    <script src="../../UserInterface/External/CodeMirror/javascript.js"></script>
+    <script src="../../UserInterface/External/CodeMirror/javascript.js"></script>
+    <script src="../../UserInterface/External/CodeMirror/jsx.js"></script>
+    <script src="../../UserInterface/External/CodeMirror/livescript.js"></script>
+    <script src="../../UserInterface/External/CodeMirror/sass.js"></script>
+    <script src="../../UserInterface/External/CodeMirror/sql.js"></script>
+    <script src="../../UserInterface/External/CodeMirror/xml.js"></script>
+    <script src="../../UserInterface/Views/CodeMirrorLocalOverrideURLMode.js"></script>
+    <script src="../../UserInterface/Views/CodeMirrorRegexMode.js"></script>
+</head>
+<body>
+    <h1>Debug CodeMirrorModes</h1>
+
+    <!-- Controls -->
+    <select id="mode">
+        <optgroup label="Web Inspector">
+            <option>text/x-local-override-url</option>
+            <option>text/x-regex</option>
+        </optgroup>
+        <optgroup label="Web">
+            <option>text/css</option>
+            <option>text/html</option>
+            <option>text/javascript</option>
+            <option>text/plain</option>
+            <option>text/xml</option>
+        </optgroup>
+        <optgroup id="extra-group" label="Extra Modes"></optgroup>
+    </select>
+    <button id="save-as-url">Save URL</button>
+    <br><br>
+
+    <!-- Editor -->
+    <textarea id="code" name="code"></textarea>
+
+    <script>
+    const modeSelect = document.getElementById("mode");
+    let cm = CodeMirror.fromTextArea(document.getElementById("code"), {lineNumbers: true});
+
+    function update() {
+        let mode = modeSelect.value;
+        cm.setOption("mode", mode);
+
+        let content;
+        switch (mode) {
+        case "text/x-local-override-url":
+            content = "http://example.com/path/?query#frag";
+            break;
+        case "text/x-regex":
+            content = "^\\d{2,3}.\\.*?(abc|[A-Z_])+\\1zzz$";
+            break;
+        default:
+            content = cm.getValue() || "";
+            break;
+        }
+        cm.setValue(content);
+    }
+
+    // Mode picker.
+    modeSelect.addEventListener("change", (event) => { update(); });
+
+    // Fill in additional options dynamically.
+    const optgroup = document.getElementById("extra-group");
+    let knownModes = new Set(Array.from(modeSelect.options).map((x) => x.value));
+    let extraModes = Object.keys(CodeMirror.mimeModes).sort();
+    for (let mode of extraModes) {
+        if (knownModes.has(mode))
+            continue;
+        knownModes.add(mode);
+        optgroup.appendChild(document.createElement("option")).textContent = mode;
+    }
+
+    // Save as URL button.
+    document.getElementById("save-as-url").addEventListener("click", function(event) {
+        let mode = modeSelect.value;
+        let content = cm.getValue();
+        window.location.search = `?mode=${encodeURIComponent(mode)}&content=${encodeURIComponent(content)}`;
+    });
+
+    // Initial content.
+    update();
+
+    // Restore better initial value from query string.
+    (function() {
+        let queryParams = {};
+        if (window.location.search.length > 0) {
+            let searchString = window.location.search.substring(1);
+            let groups = searchString.split("&");
+            for (let i = 0; i < groups.length; ++i) {
+                let pair = groups[i].split("=");
+                queryParams[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
+            }
+        }
+        if (queryParams.mode) {
+            modeSelect.value = queryParams.mode;
+            cm.setOption("mode", queryParams.mode);
+        }
+        if (queryParams.content)
+            cm.setValue(queryParams.content);
+    })();
+    </script>
+</body>
+</html>
diff --git a/Source/WebInspectorUI/Tools/CodeMirrorModes/styles.css b/Source/WebInspectorUI/Tools/CodeMirrorModes/styles.css
new file mode 100644 (file)
index 0000000..0204530
--- /dev/null
@@ -0,0 +1,22 @@
+body {
+    font-family: Arial, sans-serif;
+    line-height: 1.5;
+    max-width: 64em;
+    margin: 2em auto;
+    padding: 0 1em;
+}
+
+h1 {
+    letter-spacing: -3px;
+    font-size: 3.23em;
+    font-weight: bold;
+    margin: 0;
+}
+
+.CodeMirror {
+    height: auto;
+}
+
+.CodeMirror .CodeMirror-lines {
+    padding: 4px 0;
+}
diff --git a/Source/WebInspectorUI/UserInterface/Base/HTTPUtilities.js b/Source/WebInspectorUI/UserInterface/Base/HTTPUtilities.js
new file mode 100644 (file)
index 0000000..d44d4c1
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WI.HTTPUtilities = class HTTPUtilities {
+    static statusTextForStatusCode(code)
+    {
+        console.assert(typeof code === "number");
+
+        switch (code) {
+        case 0:   return "OK";
+        case 100: return "Continue";
+        case 101: return "Switching Protocols";
+        case 200: return "OK";
+        case 201: return "Created";
+        case 202: return "Accepted";
+        case 203: return "Non-Authoritative Information";
+        case 204: return "No Content";
+        case 205: return "Reset Content";
+        case 206: return "Partial Content";
+        case 207: return "Multi-Status";
+        case 300: return "Multiple Choices";
+        case 301: return "Moved Permanently";
+        case 302: return "Found";
+        case 303: return "See Other";
+        case 304: return "Not Modified";
+        case 305: return "Use Proxy";
+        case 307: return "Temporary Redirect";
+        case 308: return "Permanent Redirect";
+        case 400: return "Bad Request";
+        case 401: return "Unauthorized";
+        case 402: return "Payment Required";
+        case 403: return "Forbidden";
+        case 404: return "Not Found";
+        case 405: return "Method Not Allowed";
+        case 406: return "Not Acceptable";
+        case 407: return "Proxy Authentication Required";
+        case 408: return "Request Time-out";
+        case 409: return "Conflict";
+        case 410: return "Gone";
+        case 411: return "Length Required";
+        case 412: return "Precondition Failed";
+        case 413: return "Request Entity Too Large";
+        case 414: return "Request-URI Too Large";
+        case 415: return "Unsupported Media Type";
+        case 416: return "Requested range not satisfiable";
+        case 417: return "Expectation Failed";
+        case 500: return "Internal Server Error";
+        case 501: return "Not Implemented";
+        case 502: return "Bad Gateway";
+        case 503: return "Service Unavailable";
+        case 504: return "Gateway Time-out";
+        case 505: return "HTTP Version not supported";
+        }
+
+        if (code < 200)
+            return "Continue";
+        if (code < 300)
+            return "OK";
+        if (code < 400)
+            return "Multiple Choices";
+        if (code < 500)
+            return "Bad Request";
+
+        return "Internal Server Error";
+    }
+};
index f907ea4160119dc493716ac870bd52bd010d057a..f95c9cf77a7b68b80f5934805e5dc029bedacf5a 100644 (file)
@@ -1331,6 +1331,14 @@ WI.showRepresentedObject = function(representedObject, cookie, options = {})
     tabContentView.showRepresentedObject(representedObject, cookie);
 };
 
+WI.showLocalResourceOverride = function(localResourceOverride)
+{
+    console.assert(localResourceOverride instanceof WI.LocalResourceOverride);
+    const cookie = null;
+    const options = {ignoreNetworkTab: true, ignoreSearchTab: true};
+    WI.showRepresentedObject(localResourceOverride, cookie, options);
+};
+
 WI.showMainFrameDOMTree = function(nodeToSelect, options = {})
 {
     console.assert(WI.networkManager.mainFrame);
index 3f4e94454324d23560002ec4caf4f723493006b1..e0a27c89b86c7e3d576a1a180d950b7040f4dff3 100644 (file)
@@ -67,7 +67,7 @@ WI.ObjectStore = class ObjectStore
 
         WI.ObjectStore._databaseCallbacks = [callback];
 
-        const version = 3; // Increment this for every edit to `WI.objectStores`.
+        const version = 4; // Increment this for every edit to `WI.objectStores`.
 
         let databaseRequest = window.indexedDB.open(WI.ObjectStore._databaseName, version);
         databaseRequest.addEventListener("upgradeneeded", (event) => {
@@ -245,4 +245,5 @@ WI.objectStores = {
     domBreakpoints: new WI.ObjectStore("dom-debugger-dom-breakpoints", {keyPath: "__id"}),
     eventBreakpoints: new WI.ObjectStore("dom-debugger-event-breakpoints", {keyPath: "__id"}),
     urlBreakpoints: new WI.ObjectStore("dom-debugger-url-breakpoints", {keyPath: "__id"}),
+    localResourceOverrides: new WI.ObjectStore("local-resource-overrides", {keyPath: "__id"}),
 };
index 816ead69c247b263971dbaebcdf267966ac070b7..b59519e59908db0dad18c7f77699fbcd305f5cf1 100644 (file)
@@ -109,6 +109,10 @@ function parseURL(url)
         return result;
     }
 
+    // Internal sourceURLs will fail in URL constructor anyways.
+    if (isWebKitInternalScript(url))
+        return result;
+
     let parsed = null;
     try {
         parsed = new URL(url);
@@ -285,6 +289,26 @@ WI.truncateURL = function(url, multiline = false, dataURIMaxSize = 6)
     return header + firstChunk + middleChunk + lastChunk;
 };
 
+WI.urlWithoutFragment = function(urlString)
+{
+    try {
+        let url = new URL(urlString);
+        if (url.hash) {
+            url.hash = "";
+            return url.toString();
+        }
+
+        // URL.toString with an empty hash leaves the hash symbol, so we strip it.
+        let result = url.toString();
+        if (result.endsWith("#"))
+            return result.substring(0, result.length - 1);
+
+        return result;
+    } catch { }
+
+    return urlString;
+};
+
 WI.displayNameForHost = function(host)
 {
     // FIXME <rdar://problem/11237413>: This should decode punycode hostnames.
index fd0d236e27dd81da2fa9e90042b09ee8f327f939..ec92dd655407738bf6c8e7fb0231e0682b74f181 100644 (file)
@@ -633,6 +633,10 @@ WI.CSSManager = class CSSManager extends WI.Object
         if (resource === this._ignoreResourceContentDidChangeEventForResource)
             return;
 
+        // Ignore changes to resource overrides, those are not live on the page.
+        if (resource.isLocalResourceOverride)
+            return;
+
         // Ignore if it isn't a CSS style sheet.
         if (resource.type !== WI.Resource.Type.StyleSheet || resource.syntheticMIMEType !== "text/css")
             return;
index 80c4115450eeab1dd6b493fb44d57591edd236f9..65e4065c9063954b1f54e05c69cd18c5fe8a6303 100644 (file)
@@ -338,6 +338,8 @@ WI.HARBuilder = class HARBuilder
             return "Disk Cache";
         case WI.Resource.ResponseSource.ServiceWorker:
             return "Service Worker";
+        case WI.Resource.ResponseSource.InspectorOverride:
+            return "Inspector Override";
         }
 
         console.assert();
@@ -397,6 +399,10 @@ WI.HARBuilder = class HARBuilder
             return WI.Resource.ResponseSource.MemoryCache;
         case "Disk Cache":
             return WI.Resource.ResponseSource.DiskCache;
+        case "Service Worker":
+            return WI.Resource.ResponseSource.ServiceWorker;
+        case "Inspector Override":
+            return WI.Resource.ResponseSource.InspectorOverride;
         }
 
         if (fetchType)
index f82a987a8d4a3c5270a0166b44c1a944cc787f65..e432e40ccfb597b2f2dbdcf2adf4a3d6975b7a22 100644 (file)
@@ -43,10 +43,37 @@ WI.NetworkManager = class NetworkManager extends WI.Object
         this._sourceMapURLMap = new Map;
         this._downloadingSourceMaps = new Set;
 
-        this._localResourcesMap = new Map;
+        this._localResourceOverrideMap = new Map;
+        this._harImportLocalResourceMap = new Set;
+
+        this._pendingLocalResourceOverrideSaves = null;
+        this._saveLocalResourceOverridesDebouncer = null;
+
+        // FIXME: Provide dedicated UI to toggle Network Interception globally?
+        this._interceptionEnabled = WI.settings.experimentalEnableSourcesTab.value || !!window.InspectorTest;
 
         WI.notifications.addEventListener(WI.Notification.ExtraDomainsActivated, this._extraDomainsActivated, this);
         WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._handleFrameMainResourceDidChange, this);
+
+        if (NetworkManager.supportsLocalResourceOverrides()) {
+            WI.Resource.addEventListener(WI.SourceCode.Event.ContentDidChange, this._handleResourceContentDidChange, this);
+            WI.LocalResourceOverride.addEventListener(WI.LocalResourceOverride.Event.DisabledChanged, this._handleResourceOverrideDisabledChanged, this);
+
+            WI.Target.registerInitializationPromise((async () => {
+                let serializedLocalResourceOverrides = await WI.objectStores.localResourceOverrides.getAll();
+
+                this._restoringLocalResourceOverrides = true;
+                for (let serializedLocalResourceOverride of serializedLocalResourceOverrides) {
+                    let localResourceOverride = WI.LocalResourceOverride.fromJSON(serializedLocalResourceOverride);
+
+                    const key = null;
+                    WI.objectStores.localResourceOverrides.associateObject(localResourceOverride, key, serializedLocalResourceOverride);
+
+                    this.addLocalResourceOverride(localResourceOverride);
+                }
+                this._restoringLocalResourceOverrides = false;
+            })());            
+        }
     }
 
     // Static
@@ -71,6 +98,11 @@ WI.NetworkManager = class NetworkManager extends WI.Object
         WI.consoleLogViewController.appendConsoleMessage(consoleMessage);
     }
 
+    static supportsLocalResourceOverrides()
+    {
+        return window.NetworkAgent && InspectorBackend.domains.Network && InspectorBackend.domains.Network.setInterceptionEnabled;
+    }
+
     // Target
 
     initializeTarget(target)
@@ -89,6 +121,17 @@ WI.NetworkManager = class NetworkManager extends WI.Object
             // COMPATIBILITY (iOS 10.3): Network.setDisableResourceCaching did not exist.
             if (target.NetworkAgent.setResourceCachingDisabled)
                 target.NetworkAgent.setResourceCachingDisabled(WI.settings.resourceCachingDisabled.value);
+
+            // COMPATIBILITY (iOS 13.0): Network.setInterceptionEnabled did not exist.
+            if (target.NetworkAgent.setInterceptionEnabled) {
+                if (this._interceptionEnabled)
+                    target.NetworkAgent.setInterceptionEnabled(this._interceptionEnabled);
+
+                for (let [url, localResourceOverride] of this._localResourceOverrideMap) {
+                    if (!localResourceOverride.disabled)
+                        target.NetworkAgent.addInterception(localResourceOverride.url, InspectorBackend.domains.Network.NetworkStage.Response);
+                }
+            }
         }
 
         if (target.type === WI.Target.Type.Worker)
@@ -113,6 +156,30 @@ WI.NetworkManager = class NetworkManager extends WI.Object
         return Array.from(this._frameIdentifierMap.values());
     }
 
+    get localResourceOverrides()
+    {
+        return Array.from(this._localResourceOverrideMap.values());
+    }
+
+    get interceptionEnabled()
+    {
+        return this._interceptionEnabled;
+    }
+
+    set interceptionEnabled(enabled)
+    {
+        if (this._interceptionEnabled === enabled)
+            return;
+
+        this._interceptionEnabled = enabled;
+
+        for (let target of WI.targets) {
+            // COMPATIBILITY (iOS 13.0): Network.setInterceptionEnabled did not exist.
+            if (target.NetworkAgent && target.NetworkAgent.setInterceptionEnabled)
+                target.NetworkAgent.setInterceptionEnabled(this._interceptionEnabled);
+        }
+    }
+
     frameForIdentifier(frameId)
     {
         return this._frameIdentifierMap.get(frameId) || null;
@@ -165,9 +232,98 @@ WI.NetworkManager = class NetworkManager extends WI.Object
         loadAndParseSourceMap();
     }
 
-    localResourceForURL(url)
+    addLocalResourceOverride(localResourceOverride)
+    {
+        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);
+
+        if (!this._restoringLocalResourceOverrides)
+            WI.objectStores.localResourceOverrides.putObject(localResourceOverride);
+
+        if (!localResourceOverride.disabled) {
+            // COMPATIBILITY (iOS 13.0): Network.addInterception did not exist.
+            for (let target of WI.targets) {
+                if (target.NetworkAgent && target.NetworkAgent.addInterception)
+                    target.NetworkAgent.addInterception(localResourceOverride.url, InspectorBackend.domains.Network.NetworkStage.Response);
+            }
+        }
+
+        this.dispatchEventToListeners(WI.NetworkManager.Event.LocalResourceOverrideAdded, {localResourceOverride});
+    }
+
+    removeLocalResourceOverride(localResourceOverride)
+    {
+        console.assert(localResourceOverride instanceof WI.LocalResourceOverride);
+
+        if (!this._localResourceOverrideMap.delete(localResourceOverride.url)) {
+            console.assert(false, "Attempted to remove a local resource override that was not known.");
+            return;
+        }
+
+        if (this._pendingLocalResourceOverrideSaves)
+            this._pendingLocalResourceOverrideSaves.delete(localResourceOverride);
+
+        if (!this._restoringLocalResourceOverrides)
+            WI.objectStores.localResourceOverrides.deleteObject(localResourceOverride);
+
+        if (!localResourceOverride.disabled) {
+            // COMPATIBILITY (iOS 13.0): Network.removeInterception did not exist.
+            for (let target of WI.targets) {
+                if (target.NetworkAgent && target.NetworkAgent.removeInterception)
+                    target.NetworkAgent.removeInterception(localResourceOverride.url, InspectorBackend.domains.Network.NetworkStage.Response);
+            }
+        }
+
+        this.dispatchEventToListeners(WI.NetworkManager.Event.LocalResourceOverrideRemoved, {localResourceOverride});
+    }
+
+    localResourceOverrideForURL(url)
     {
-        return this._localResourcesMap.get(url);
+        return this._localResourceOverrideMap.get(url);
+    }
+
+    canBeOverridden(resource)
+    {
+        if (!(resource instanceof WI.Resource))
+            return false;
+
+        if (resource instanceof WI.SourceMapResource)
+            return false;
+
+        if (resource.isLocalResourceOverride)
+            return false;
+
+        const schemes = ["http:", "https:", "file:"];
+        if (!schemes.some((scheme) => resource.url.startsWith(scheme)))
+            return false;
+
+        let existingOverride = this.localResourceOverrideForURL(resource.url);
+        if (existingOverride)
+            return false;
+
+        switch (resource.type) {
+        case WI.Resource.Type.Document:
+        case WI.Resource.Type.StyleSheet:
+        case WI.Resource.Type.Script:
+        case WI.Resource.Type.XHR:
+        case WI.Resource.Type.Fetch:
+        case WI.Resource.Type.Image:
+        case WI.Resource.Type.Font:
+        case WI.Resource.Type.Other:
+            break;
+        case WI.Resource.Type.Ping:
+        case WI.Resource.Type.Beacon:
+            // Responses aren't really expected for Ping/Beacon.
+            return false;
+        case WI.Resource.Type.WebSocket:
+            // Non-HTTP traffic.
+            console.assert(false, "Scheme check above should have been sufficient.");
+            return false;
+        }
+
+        return true;
     }
 
     resourceForURL(url)
@@ -228,7 +384,7 @@ WI.NetworkManager = class NetworkManager extends WI.Object
 
         for (let entry of json.log.entries) {
             let localResource = WI.LocalResource.fromHAREntry(entry, mainResourceSentWalltime);
-            this._localResourcesMap.set(localResource.url, localResource);
+            this._harImportLocalResourceMap.add(localResource);
             localResources.push(localResource);
         }
 
@@ -650,6 +806,30 @@ WI.NetworkManager = class NetworkManager extends WI.Object
         this._resourceRequestIdentifierMap.delete(requestIdentifier);
     }
 
+    responseIntercepted(target, requestId, response)
+    {
+        let url = WI.urlWithoutFragment(response.url);
+        let localResourceOverride = this._localResourceOverrideMap.get(url);
+        if (!localResourceOverride || localResourceOverride.disabled) {
+            target.NetworkAgent.interceptContinue(requestId);
+            return;
+        }
+
+        let localResource = localResourceOverride.localResource;
+        let content = localResource.localContent;
+        let base64Encoded = localResource.localContentIsBase64Encoded;
+        let {mimeType, statusCode, statusText, responseHeaders} = localResource;
+
+        if (isNaN(statusCode))
+            statusCode = undefined;
+        if (!statusText)
+            statusText = undefined;
+        if (!responseHeaders)
+            responseHeaders = undefined;
+
+        target.NetworkAgent.interceptWithResponse(requestId, content, base64Encoded, mimeType, statusCode, statusText, responseHeaders);
+    }
+
     // RuntimeObserver
 
     executionContextCreated(contextPayload)
@@ -1050,6 +1230,63 @@ WI.NetworkManager = class NetworkManager extends WI.Object
         }
     }
 
+    _handleResourceContentDidChange(event)
+    {
+        let resource = event.target;
+        if (!(resource instanceof WI.Resource))
+            return;
+
+        if (!resource.isLocalResourceOverride)
+            return;
+
+        let localResourceOverride = this.localResourceOverrideForURL(resource.url);
+        console.assert(localResourceOverride);
+        if (!localResourceOverride)
+            return;
+
+        let localResource = localResourceOverride.localResource;
+        let content = localResource.content;
+        let base64Encoded = localResource.localContentIsBase64Encoded;
+        let mimeType = localResource.mimeType;
+        localResource.updateOverrideContent(content, base64Encoded, mimeType, {suppressEvent: true});
+
+        this._persistLocalResourceOverrideSoonAfterContentChange(localResourceOverride);
+    }
+
+    _persistLocalResourceOverrideSoonAfterContentChange(localResourceOverride)
+    {
+        if (!this._saveLocalResourceOverridesDebouncer) {
+            this._pendingLocalResourceOverrideSaves = new Set;
+            this._saveLocalResourceOverridesDebouncer = new Debouncer(() => {
+                for (let localResourceOverride of this._pendingLocalResourceOverrideSaves) {
+                    console.assert(localResourceOverride instanceof WI.LocalResourceOverride);
+                    WI.objectStores.localResourceOverrides.putObject(localResourceOverride);
+                }
+            });
+        }
+
+        this._pendingLocalResourceOverrideSaves.add(localResourceOverride);
+        this._saveLocalResourceOverridesDebouncer.delayForTime(500);
+    }
+
+    _handleResourceOverrideDisabledChanged(event)
+    {
+        console.assert(WI.NetworkManager.supportsLocalResourceOverrides());
+
+        let localResourceOverride = event.target;
+        WI.objectStores.localResourceOverrides.putObject(localResourceOverride);
+
+        // COMPATIBILITY (iOS 13.0): Network.addInterception / Network.removeInterception did not exist.
+        for (let target of WI.targets) {
+            if (target.NetworkAgent) {
+                if (localResourceOverride.disabled)
+                    target.NetworkAgent.removeInterception(localResourceOverride.url, InspectorBackend.domains.Network.NetworkStage.Response);
+                else
+                    target.NetworkAgent.addInterception(localResourceOverride.url, InspectorBackend.domains.Network.NetworkStage.Response);
+            }
+        }
+    }
+
     _extraDomainsActivated(event)
     {
         if (event.data.domains.includes("Page") && window.PageAgent)
@@ -1070,4 +1307,6 @@ WI.NetworkManager.Event = {
     FrameWasAdded: "network-manager-frame-was-added",
     FrameWasRemoved: "network-manager-frame-was-removed",
     MainFrameDidChange: "network-manager-main-frame-did-change",
+    LocalResourceOverrideAdded: "network-manager-local-resource-override-added",
+    LocalResourceOverrideRemoved: "network-manager-local-resource-override-removed",
 };
diff --git a/Source/WebInspectorUI/UserInterface/Images/NavigationItemNetworkOverride.svg b/Source/WebInspectorUI/UserInterface/Images/NavigationItemNetworkOverride.svg
new file mode 100644 (file)
index 0000000..d60aeaf
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<!-- Copyright © 2019 Apple Inc. All rights reserved. -->
+<svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 13 14">
+    <path fill="none" stroke="currentColor" d="M 9.0 3.0 C 9.0 1.33 8.17 0.5 6.5 0.5 C 6.17 0.5 5.17 0.5 3.5 0.5 C 1.83 0.5 1.0 1.33 1.0 3.0 C 1.0 4.67 1.0 6.33 1.0 8.0 C 1.0 9.67 1.83 10.5 3.5 10.5"/>
+    <path fill="currentColor" stroke="currentColor" d="M 9.5 3.5 C 9.5 3.5 9.0 3.5 6.5 3.5 C 4.83 3.5 4.0 4.33 4.0 6.0 C 4.0 7.67 4.0 9.33 4.0 11.0 C 4.0 12.67 4.83 13.5 6.5 13.5 C 6.83 13.5 7.83 13.5 9.5 13.5 C 11.17 13.5 12.0 12.67 12.0 11.0 C 12.0 9.33 12.0 7.67 12.0 6.0 C 12.0 3.5 9.5 3.5 9.5 3.5 Z"/>
+</svg>
index 44f3c78d8ae7b0b87d4e25f9aac50f43e80bcc17..94d144e9f461ac8d3b1f8e95ec55890a84d3b3f5 100644 (file)
@@ -60,6 +60,7 @@
     <link rel="stylesheet" href="Views/CheckboxNavigationItem.css">
     <link rel="stylesheet" href="Views/CircleChart.css">
     <link rel="stylesheet" href="Views/ClusterContentView.css">
+    <link rel="stylesheet" href="Views/CodeMirrorLocalOverrideURLMode.css">
     <link rel="stylesheet" href="Views/CodeMirrorOverrides.css">
     <link rel="stylesheet" href="Views/CodeMirrorRegexMode.css">
     <link rel="stylesheet" href="Views/CollectionContentView.css">
     <link rel="stylesheet" href="Views/LayoutTimelineOverviewGraph.css">
     <link rel="stylesheet" href="Views/LayoutTimelineView.css">
     <link rel="stylesheet" href="Views/LocalRemoteObjectContentView.css">
+    <link rel="stylesheet" href="Views/LocalResourceOverrideLabelView.css">
+    <link rel="stylesheet" href="Views/LocalResourceOverridePopover.css">
+    <link rel="stylesheet" href="Views/LocalResourceOverrideTreeElement.css">
+    <link rel="stylesheet" href="Views/LocalResourceOverrideWarningView.css">
     <link rel="stylesheet" href="Views/LogContentView.css">
     <link rel="stylesheet" href="Views/LogIcon.css">
     <link rel="stylesheet" href="Views/MediaTimelineOverviewGraph.css">
     <script src="Base/EventListener.js"></script>
     <script src="Base/EventListenerSet.js"></script>
     <script src="Base/FileUtilities.js"></script>
+    <script src="Base/HTTPUtilities.js"></script>
     <script src="Base/ImageUtilities.js"></script>
     <script src="Base/LoadLocalizedStrings.js"></script>
     <script src="Base/MIMETypeUtilities.js"></script>
     <script src="Models/LayoutTimelineRecord.js"></script>
     <script src="Models/LazySourceCodeLocation.js"></script>
     <script src="Models/LineWidget.js"></script>
+    <script src="Models/LocalResourceOverride.js"></script>
     <script src="Models/LogObject.js"></script>
     <script src="Models/LoggingChannel.js"></script>
     <script src="Models/MediaInstrument.js"></script>
     <script src="Views/CodeMirrorAdditions.js"></script>
     <script src="Views/CodeMirrorEditor.js"></script>
     <script src="Views/CodeMirrorFormatters.js"></script>
+    <script src="Views/CodeMirrorLocalOverrideURLMode.js"></script>
     <script src="Views/CodeMirrorRegexMode.js"></script>
     <script src="Views/CodeMirrorTextMarkers.js"></script>
     <script src="Views/ColorPicker.js"></script>
     <script src="Views/LayoutTimelineView.js"></script>
     <script src="Views/LocalDOMContentView.js"></script>
     <script src="Views/LocalJSONContentView.js"></script>
+    <script src="Views/LocalResourceOverrideLabelView.js"></script>
+    <script src="Views/LocalResourceOverridePopover.js"></script>
+    <script src="Views/LocalResourceOverrideTreeElement.js"></script>
+    <script src="Views/LocalResourceOverrideWarningView.js"></script>
     <script src="Views/LogContentView.js"></script>
     <script src="Views/MediaTimelineDataGridNode.js"></script>
     <script src="Views/MediaTimelineOverviewGraph.js"></script>
index fce4198bd28255650beb47435d205117585c3dc0..666f0dbeb07170b8c9e9611964bdf06e56502fb2 100644 (file)
 //     response: { mimeType, headers, statusCode, statusText, failureReasonText, content, base64Encoded }
 //     metrics: { responseSource, protocol, priority, remoteAddress, connectionIdentifier, sizes }
 //     timing: { startTime, domainLookupStart, domainLookupEnd, connectStart, connectEnd, secureConnectionStart, requestStart, responseStart, responseEnd }
+//     isLocalResourceOverride: <boolean>
 
 WI.LocalResource = class LocalResource extends WI.Resource
 {
-    constructor({request, response, metrics, timing})
+    constructor({request, response, metrics, timing, isLocalResourceOverride})
     {
         console.assert(request);
         console.assert(typeof request.url === "string");
@@ -77,10 +78,17 @@ WI.LocalResource = class LocalResource extends WI.Resource
         this._localContent = response.content;
         this._localContentIsBase64Encoded = response.base64Encoded;
 
+        // LocalResource specific.
+        this._isLocalResourceOverride = isLocalResourceOverride || false;
+
         // Finalize WI.Resource.
         this._finished = true;
         this._failed = false; // FIXME: How should we denote a failure? Assume from status code / failure reason?
         this._cached = false; // FIXME: How should we denote cached? Assume from response source?
+
+        // Finalize WI.SourceCode.
+        this._originalRevision = new WI.SourceCodeRevision(this, this._localContent);
+        this._currentRevision = this._originalRevision;
     }
 
     // Static
@@ -197,8 +205,41 @@ WI.LocalResource = class LocalResource extends WI.Resource
         });
     }
 
+    // Import / Export
+
+    static fromJSON(json)
+    {
+        return new WI.LocalResource(json);
+    }
+
+    toJSON(key)
+    {
+        return {
+            request: {
+                url: this.url,
+            },
+            response: {
+                headers: this.responseHeaders,
+                mimeType: this.mimeType,
+                statusCode: this.statusCode,
+                statusText: this.statusText,
+                content: this._localContent,
+                base64Encoded: this._localContentIsBase64Encoded,
+            },
+            isLocalResourceOverride: this._isLocalResourceOverride,
+        };
+    }
+
     // Public
 
+    get localContent() { return this._localContent; }
+    get localContentIsBase64Encoded() { return this._localContentIsBase64Encoded; }
+
+    get isLocalResourceOverride()
+    {
+        return this._isLocalResourceOverride;
+    }
+
     hasContent()
     {
         return !!this._localContent;
@@ -219,6 +260,23 @@ WI.LocalResource = class LocalResource extends WI.Resource
         this._localContentIsBase64Encoded = base64Encoded;
     }
 
+    updateOverrideContent(content, base64Encoded, mimeType, options = {})
+    {
+        console.assert(this._isLocalResourceOverride);
+
+        if (content !== undefined && this._localContent !== content)
+            this._localContent = content;
+
+        if (base64Encoded !== undefined && this._localContentIsBase64Encoded !== base64Encoded)
+            this._localContentIsBase64Encoded = base64Encoded;
+
+        if (mimeType !== undefined && mimeType !== this._mimeType) {
+            let oldMIMEType = this._mimeType;
+            this._mimeType = mimeType;
+            this.dispatchEventToListeners(WI.Resource.Event.MIMETypeDidChange, {oldMIMEType});
+        }
+    }
+
     // Protected
 
     requestContentFromBackend()
diff --git a/Source/WebInspectorUI/UserInterface/Models/LocalResourceOverride.js b/Source/WebInspectorUI/UserInterface/Models/LocalResourceOverride.js
new file mode 100644 (file)
index 0000000..51fb453
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WI.LocalResourceOverride = class LocalResourceOverride extends WI.Object
+{
+    constructor(localResource, {disabled} = {})
+    {
+        console.assert(localResource instanceof WI.LocalResource);
+        console.assert(localResource.isLocalResourceOverride);
+        console.assert(localResource.url);
+        console.assert(!disabled || typeof disabled === "boolean");
+
+        super();
+
+        this._localResource = localResource;
+        this._disabled = disabled || false;
+    }
+
+    // Static
+
+    static create({url, mimeType, content, base64Encoded, statusCode, statusText, headers, disabled})
+    {
+        let localResource = new WI.LocalResource({
+            request: {
+                url: WI.urlWithoutFragment(url),
+            },
+            response: {
+                headers,
+                mimeType,
+                statusCode,
+                statusText,
+                content,
+                base64Encoded,
+            },
+            isLocalResourceOverride: true,
+        });
+
+        return new WI.LocalResourceOverride(localResource, {disabled});
+    }
+
+    // Import / Export
+
+    static fromJSON(json)
+    {
+        let {localResource, disabled} = json;
+        return new WI.LocalResourceOverride(WI.LocalResource.fromJSON(localResource), {disabled});
+    }
+
+    toJSON(key)
+    {
+        let json = {
+            localResource: this._localResource.toJSON(key),
+            disabled: this._disabled,
+        };
+
+        if (key === WI.ObjectStore.toJSONSymbol)
+            json[WI.objectStores.localResourceOverrides.keyPath] = this._localResource.url;
+
+        return json;
+    }
+
+    // Public
+
+    get url() { return this._localResource.url; }
+    get localResource() { return this._localResource; }
+
+    get disabled()
+    {
+        return this._disabled;
+    }
+
+    set disabled(disabled)
+    {
+        if (this._disabled === disabled)
+            return;
+
+        this._disabled = !!disabled;
+
+        this.dispatchEventToListeners(WI.LocalResourceOverride.Event.DisabledChanged);
+    }
+
+    // Protected
+
+    saveIdentityToCookie(cookie)
+    {
+        cookie["local-resource-override-url"] = this._localResource.url;
+        cookie["local-resource-override-disabled"] = this._disabled;
+    }
+};
+
+WI.LocalResourceOverride.TypeIdentifier = "local-resource-override";
+
+WI.LocalResourceOverride.Event = {
+    DisabledChanged: "local-resource-override-disabled-state-did-change",
+};
index 31aed8b63668d2a3eea2495baa5798e222e0f573..61d659fad5a8662db8efbbf15f5ab5f27e72b163 100644 (file)
@@ -178,13 +178,23 @@ WI.Resource = class Resource extends WI.SourceCode
         }
     }
 
-    static classNameForResource(resource)
+    static classNamesForResource(resource)
     {
+        let classes = [];
+
+        let isOverride = resource.isLocalResourceOverride;
+        let wasOverridden = resource.responseSource === WI.Resource.ResponseSource.InspectorOverride;
+        let shouldBeOverridden = resource.isLoading() && WI.networkManager.localResourceOverrideForURL(resource.url);
+        if (isOverride || wasOverridden || shouldBeOverridden)
+            classes.push("override");
+
         if (resource.type === WI.Resource.Type.Other) {
             if (resource.requestedByteRange)
-                return "resource-type-range";
-        }
-        return resource.type;
+                classes.push("resource-type-range");
+        } else
+            classes.push(resource.type)
+
+        return classes;
     }
 
     static displayNameForProtocol(protocol)
@@ -254,6 +264,8 @@ WI.Resource = class Resource extends WI.SourceCode
             return WI.Resource.ResponseSource.DiskCache;
         case NetworkAgent.ResponseSource.ServiceWorker:
             return WI.Resource.ResponseSource.ServiceWorker;
+        case NetworkAgent.ResponseSource.InspectorOverride:
+            return WI.Resource.ResponseSource.InspectorOverride;
         default:
             console.error("Unknown response source type", source);
             return WI.Resource.ResponseSource.Unknown;
@@ -357,6 +369,13 @@ WI.Resource = class Resource extends WI.SourceCode
         return this._type === Resource.Type.Script;
     }
 
+    get supportsScriptBlackboxing()
+    {
+        if (this.isLocalResourceOverride)
+            return false;
+        return super.supportsScriptBlackboxing;
+    }
+
     get displayName()
     {
         return WI.displayNameForURL(this._url, this.urlComponents);
@@ -423,6 +442,11 @@ WI.Resource = class Resource extends WI.SourceCode
         return this._parentFrame ? this._parentFrame.mainResource === this : false;
     }
 
+    get isLocalResourceOverride()
+    {
+        return false;
+    }
+
     addInitiatedResource(resource)
     {
         if (!(resource instanceof WI.Resource))
@@ -1039,6 +1063,25 @@ WI.Resource = class Resource extends WI.SourceCode
         cookie[WI.Resource.MainResourceCookieKey] = this.isMainResource();
     }
 
+    async createLocalResourceOverride(initialContent)
+    {
+        console.assert(!this.isLocalResourceOverride)
+        console.assert(WI.NetworkManager.supportsLocalResourceOverrides());
+
+        let {rawContent, rawBase64Encoded} = await this.requestContent();
+        let content = initialContent !== undefined ? initialContent : rawContent;
+
+        return WI.LocalResourceOverride.create({
+            url: this.url,
+            mimeType: this.mimeType,
+            content,
+            base64Encoded: rawBase64Encoded,
+            statusCode: this.statusCode,
+            statusText: this.statusText,
+            headers: this.responseHeaders,
+        });
+    }
+
     generateCURLCommand()
     {
         function escapeStringPosix(str) {
@@ -1190,6 +1233,7 @@ WI.Resource.ResponseSource = {
     MemoryCache: Symbol("memory-cache"),
     DiskCache: Symbol("disk-cache"),
     ServiceWorker: Symbol("service-worker"),
+    InspectorOverride: Symbol("inspector-override"),
 };
 
 WI.Resource.NetworkPriority = {
@@ -1209,9 +1253,10 @@ WI.settings.resourceGroupingMode = new WI.Setting("resource-grouping-mode", WI.R
 WI.Resource._mimeTypeMap = {
     "text/html": WI.Resource.Type.Document,
     "text/xml": WI.Resource.Type.Document,
-    "text/plain": WI.Resource.Type.Document,
     "application/xhtml+xml": WI.Resource.Type.Document,
 
+    "text/plain": WI.Resource.Type.Other,
+
     "text/css": WI.Resource.Type.StyleSheet,
     "text/xsl": WI.Resource.Type.StyleSheet,
     "text/x-less": WI.Resource.Type.StyleSheet,
index e927888154897ece56ad4237d62b8c5d6967e9e9..c742a06a1abe79819bdd1a2b117c07c422161ea9 100644 (file)
@@ -106,4 +106,9 @@ WI.NetworkObserver = class NetworkObserver
     {
         // FIXME: Not implemented.
     }
+
+    responseIntercepted(requestId, response)
+    {
+        WI.networkManager.responseIntercepted(this.target, requestId, response);
+    }
 };
index 92c7e98dd44773241fdb6e66870d68270ac4aaf3..06db7615880d7ffa678f292baec7e44ea88bf8fd 100644 (file)
     <script src="Models/LayoutInstrument.js"></script>
     <script src="Models/LayoutTimelineRecord.js"></script>
     <script src="Models/LazySourceCodeLocation.js"></script>
+    <script src="Models/LocalResourceOverride.js"></script>
     <script src="Models/LoggingChannel.js"></script>
     <script src="Models/MediaInstrument.js"></script>
     <script src="Models/MediaTimelineRecord.js"></script>
index ff02a32480e118ce6604691234ac07f18b9370ea..0db62e169800ec77fce4b087b0a997e64fa99f18 100644 (file)
@@ -43,7 +43,6 @@
 }
 
 .popover .edit-breakpoint-popover-content > table > tr > th {
-    width: 1px; /* Shrink to fit. */
     font-weight: bold;
     line-height: 23px;
     text-align: end;
@@ -58,7 +57,7 @@
 
 .edit-breakpoint-popover-condition {
     width: 100%;
-    padding: 4px 0 2px 0;
+    padding: 4px 0 2px;
     -webkit-appearance: textfield;
     border: 1px solid hsl(0, 0%, 78%);
     background: var(--background-color-code);
diff --git a/Source/WebInspectorUI/UserInterface/Views/CodeMirrorLocalOverrideURLMode.css b/Source/WebInspectorUI/UserInterface/Views/CodeMirrorLocalOverrideURLMode.css
new file mode 100644 (file)
index 0000000..53e9584
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+.cm-s-default .cm-local-override-url-bad-scheme {
+    color: red;
+}
+
+.cm-s-default .cm-local-override-url-fragment {
+    color: red;
+}
diff --git a/Source/WebInspectorUI/UserInterface/Views/CodeMirrorLocalOverrideURLMode.js b/Source/WebInspectorUI/UserInterface/Views/CodeMirrorLocalOverrideURLMode.js
new file mode 100644 (file)
index 0000000..caf860c
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+CodeMirror.defineMode("local-override-url", function() {
+    function tokenBase(stream, state) {
+        // Parse only the first line, ignore all other lines.
+        if (state.parsedURL && stream.sol()) {
+            stream.skipToEnd();
+            return null;
+        }
+
+        // Parse once per tokenize.
+        let url = stream.string;
+        if (!state.parsedURL) {
+            try {
+                state.parsedURL = new URL(url);
+
+                let indexOfFragment = -1;
+                if (state.parsedURL.hash)
+                    indexOfFragment = url.lastIndexOf(state.parsedURL.hash);
+                else if (url.endsWith("#"))
+                    indexOfFragment = url.length - 1;
+                state.indexOfFragment = indexOfFragment;
+            } catch {
+                stream.skipToEnd();
+                return null;
+            }
+        }
+
+        // Scheme
+        if (stream.pos < state.parsedURL.protocol.length) {
+            if (state.parsedURL.protocol !== "http:" && state.parsedURL.protocol !== "https:") {
+                while (stream.pos < state.parsedURL.protocol.length && !stream.eol())
+                    stream.next();
+                return "local-override-url-bad-scheme";
+            }
+        }
+
+        // Pre-fragment string.
+        let indexOfFragment = state.indexOfFragment;
+        if (indexOfFragment === -1) {
+            stream.skipToEnd();
+            return null;
+        }
+        if (stream.pos < indexOfFragment) {
+            while (stream.pos < indexOfFragment && !stream.eol())
+                stream.next();
+            return null;
+        }
+
+        // Fragment.
+        stream.skipToEnd();
+        return "local-override-url-fragment";
+    }
+
+    return {
+        startState: function() {
+            return {
+                parsedURL: null,
+                tokenize: tokenBase,
+            };
+        },
+        token: function(stream, state) {
+            return state.tokenize(stream, state);
+        }
+    };
+});
+
+CodeMirror.defineMIME("text/x-local-override-url", "local-override-url");
index c5cf46a297f8b3451fe45e0740433cca5fc84878..babd36ad117ce2e9f3ba25e7abb7159e90921eff 100644 (file)
 }
 
 .console-warning-level {
-    background-color: hsl(50, 100%, 94%);
+    background-color: var(--warning-color);
     border-color: hsl(40, 100%, 90%);
 }
 
index 54acaaf6d7d0e0bc79414705d035b8ab1626b452..3ec9a72e837978542f5307de6bab90651952efeb 100644 (file)
@@ -315,9 +315,12 @@ WI.ContentBrowserTabContentView = class ContentBrowserTabContentView extends WI.
 
         let treeElement = this.treeElementForRepresentedObject(representedObject);
 
-        if (treeElement)
-            treeElement.revealAndSelect(true, false, true);
-        else if (this.navigationSidebarPanel && this.navigationSidebarPanel.contentTreeOutline.selectedTreeElement)
+        if (treeElement) {
+            const omitFocus = true;
+            const selectedByUser = false;
+            const suppressNotification = false;
+            treeElement.revealAndSelect(omitFocus, selectedByUser, suppressNotification);
+        } else if (this.navigationSidebarPanel && this.navigationSidebarPanel.contentTreeOutline.selectedTreeElement)
             this.navigationSidebarPanel.contentTreeOutline.selectedTreeElement.deselect(true);
     }
 
index 558e45e0246b394a2bfa02fc6353927d5b05c1a4..df912dd56fa07df948f0d7f7af0d7c6d1b79a2f6 100644 (file)
@@ -101,6 +101,9 @@ WI.ContentView = class ContentView extends WI.View
                 return WI.ContentView.createFromRepresentedObject(representedObject.sourceCodeLocation.displaySourceCode, extraArguments);
         }
 
+        if (representedObject instanceof WI.LocalResourceOverride)
+            return WI.ContentView.createFromRepresentedObject(representedObject.localResource);
+
         if (representedObject instanceof WI.DOMStorageObject)
             return new WI.DOMStorageContentView(representedObject, extraArguments);
 
@@ -252,6 +255,9 @@ WI.ContentView = class ContentView extends WI.View
         if (representedObject instanceof WI.SourceCodeSearchMatchObject)
             return representedObject.sourceCode;
 
+        if (representedObject instanceof WI.LocalResourceOverride)
+            return representedObject.localResource;
+
         return representedObject;
     }
 
@@ -277,6 +283,8 @@ WI.ContentView = class ContentView extends WI.View
             return true;
         if (representedObject instanceof WI.Breakpoint || representedObject instanceof WI.IssueMessage)
             return representedObject.sourceCodeLocation;
+        if (representedObject instanceof WI.LocalResourceOverride)
+            return true;
         if (representedObject instanceof WI.DOMStorageObject)
             return true;
         if (representedObject instanceof WI.CookieStorageObject)
index 95f7bee62c80b7219e924e34797180ea08c96e21..3a4c8104c3c1f3da56bd50c9b1ca6c09dfe6ba86 100644 (file)
@@ -248,8 +248,12 @@ WI.ContextMenu = class ContextMenu extends WI.ContextSubMenuItem
 
     _itemSelected(id)
     {
-        if (this._handlers[id])
-            this._handlers[id].call(this);
+        try {
+            if (this._handlers[id])
+                this._handlers[id].call(this);
+        } catch (e) {
+            WI.reportInternalError(e);
+        }
     }
 };
 
index afbf301ea7f792f3d425a458c60db295b0f2a8e5..4015cd89b9c3f985bee897d9915a7614ea91a258 100644 (file)
@@ -70,11 +70,23 @@ WI.appendContextMenuItemsForSourceCode = function(contextMenu, sourceCodeOrLocat
     if (!(sourceCode instanceof WI.SourceCode))
         return;
 
+    if (WI.NetworkManager.supportsLocalResourceOverrides()) {
+        if (WI.networkManager.canBeOverridden(sourceCode)) {
+            contextMenu.appendSeparator();
+            contextMenu.appendItem(WI.UIString("Create Local Override"), async () => {
+                let resource = sourceCode;
+                let localResourceOverride = await resource.createLocalResourceOverride();
+                WI.networkManager.addLocalResourceOverride(localResourceOverride);
+                WI.showLocalResourceOverride(localResourceOverride);
+            });
+        }
+    }
+
     contextMenu.appendSeparator();
 
     WI.appendContextMenuItemsForURL(contextMenu, sourceCode.url, {sourceCode, location});
 
-    if (sourceCode instanceof WI.Resource) {
+    if (sourceCode instanceof WI.Resource && !sourceCode.isLocalResourceOverride) {
         if (sourceCode.urlComponents.scheme !== "data") {
             contextMenu.appendItem(WI.UIString("Copy as cURL"), () => {
                 InspectorFrontendHost.copyText(sourceCode.generateCURLCommand());
@@ -91,11 +103,11 @@ WI.appendContextMenuItemsForSourceCode = function(contextMenu, sourceCodeOrLocat
                     InspectorFrontendHost.copyText(sourceCode.stringifyHTTPResponse());
                 });
             }
-
-            contextMenu.appendSeparator();
         }
     }
 
+    contextMenu.appendSeparator();
+
     contextMenu.appendItem(WI.UIString("Save File"), () => {
         sourceCode.requestContent().then(() => {
             const forceSaveAs = true;
@@ -108,7 +120,7 @@ WI.appendContextMenuItemsForSourceCode = function(contextMenu, sourceCodeOrLocat
 
     contextMenu.appendSeparator();
 
-    if (location && (sourceCode instanceof WI.Script || (sourceCode instanceof WI.Resource && sourceCode.type === WI.Resource.Type.Script))) {
+    if (location && (sourceCode instanceof WI.Script || (sourceCode instanceof WI.Resource && sourceCode.type === WI.Resource.Type.Script && !sourceCode.isLocalResourceOverride))) {
         let existingBreakpoint = WI.debuggerManager.breakpointForSourceCodeLocation(location);
         if (existingBreakpoint) {
             contextMenu.appendItem(WI.UIString("Delete Breakpoint"), () => {
@@ -159,6 +171,7 @@ WI.appendContextMenuItemsForURL = function(contextMenu, url, options = {})
                 });
             }
         }
+
         if (!WI.isShowingNetworkTab()) {
             contextMenu.appendItem(WI.UIString("Reveal in Network Tab"), () => {
                 showResourceWithOptions({preferredTabType: WI.NetworkTabContentView.Type});
index 547381dd787b86125f9f6ad6dca247275d052c77..3f4819dd0a9beaaad5baeabb79166925f0e6cf57 100644 (file)
@@ -389,6 +389,15 @@ WI.DataGrid = class DataGrid extends WI.View
         }
     }
 
+    startEditingNode(node)
+    {
+        console.assert(this._editCallback);
+        if (this._editing || this._editingNode)
+            return;
+
+        this._startEditingNodeAtColumnIndex(node, 0);
+    }
+
     _updateScrollListeners()
     {
         if (this._inline || this._variableHeightRows) {
@@ -524,16 +533,21 @@ WI.DataGrid = class DataGrid extends WI.View
 
         var element = this._editingNode.element.children[columnIndex];
         WI.startEditing(element, this._startEditingConfig(element));
+
         window.getSelection().setBaseAndExtent(element, 0, element, 1);
     }
 
     _startEditing(target)
     {
-        var element = target.closest("td");
+        let element = target.closest("td");
         if (!element)
             return;
 
-        this._editingNode = this.dataGridNodeFromNode(target);
+        let node = this.dataGridNodeFromNode(target);
+        if (!node.editable)
+            return;
+
+        this._editingNode = node;
         if (!this._editingNode) {
             if (!this.placeholderNode)
                 return;
@@ -1651,7 +1665,7 @@ WI.DataGrid = class DataGrid extends WI.View
                 if (this.dataGrid._editCallback) {
                     if (gridNode === this.placeholderNode)
                         contextMenu.appendItem(WI.UIString("Add New"), this._startEditing.bind(this, event.target));
-                    else {
+                    else if (gridNode.editable) {
                         let element = event.target.closest("td");
                         let columnIdentifier = element.__columnIdentifier;
                         let columnTitle = this.dataGrid.columns.get(columnIdentifier)["title"];
@@ -1659,7 +1673,7 @@ WI.DataGrid = class DataGrid extends WI.View
                     }
                 }
 
-                if (this.dataGrid._deleteCallback && gridNode !== this.placeholderNode)
+                if (this.dataGrid._deleteCallback && gridNode !== this.placeholderNode && gridNode.editable)
                     contextMenu.appendItem(WI.UIString("Delete"), this._deleteCallback.bind(this, gridNode));
             }
 
index ae9563e2800599fc8ec4d028d8ea36ded708a8b3..02b70242baaa40d59a3a0c64302aa6b17843c626 100644 (file)
@@ -33,6 +33,7 @@ WI.DataGridNode = class DataGridNode extends WI.Object
         this._hidden = false;
         this._selected = false;
         this._copyable = true;
+        this._editable = true;
         this._shouldRefreshChildren = true;
         this._data = data || {};
         this.hasChildren = hasChildren || false;
@@ -80,6 +81,16 @@ WI.DataGridNode = class DataGridNode extends WI.Object
         this._copyable = x;
     }
 
+    get editable()
+    {
+        return this._editable;
+    }
+
+    set editable(x)
+    {
+        this._editable = x;
+    }
+
     get element()
     {
         if (this._element)
index e872ef693714a9058d95b39eef22afe3c765321f..40994931a415962993e41b46a46d220eeac1bcf0 100644 (file)
 .sidebar > .panel.navigation.debugger .warning-banner {
     text-align: center;
     font-size: 11px;
-
     padding: 11px 6px;
-
+    color: var(--yellow-highlight-text-color);
+    background-color: var(--yellow-highlight-background-color);
     border-bottom: 1px solid var(--border-color);
-    background-color: hsl(50, 100%, 94%);
 }
 
 .sidebar > .panel.navigation.debugger .warning-banner + .warning-banner {
 .sidebar > .panel.navigation.debugger > .content > .breakpoints .tree-outline .item.event-target-window .icon {
     content: url(../Images/TypeObject.svg);
 }
-
-@media (prefers-color-scheme: dark) {
-    .sidebar > .panel.navigation.debugger .warning-banner {
-        background-color: var(--yellow-highlight-background-color);
-        color: var(--yellow-highlight-text-color);
-    }
-}
index efc1486802c766b81db985ce9291561a3e7439ca..09efa62b09447802d48e871a6713e988c77d05b8 100644 (file)
@@ -315,6 +315,9 @@ WI.DebuggerSidebarPanel = class DebuggerSidebarPanel extends WI.NavigationSideba
 
     treeElementForRepresentedObject(representedObject)
     {
+        if (representedObject instanceof WI.LocalResource)
+            return null;
+
         // The main resource is used as the representedObject instead of Frame in our tree.
         if (representedObject instanceof WI.Frame)
             representedObject = representedObject.mainResource;
index 5e1665d905eed4859f3c00e3dda70ceaeb5a5f54..58709635a074cbfb389f0e58686639cba2c57260 100644 (file)
@@ -31,7 +31,7 @@
 .popover .input-popover-content > .editor {
     width: 200px;
     margin-top: 5px;
-    padding: 4px 0 2px 0;
+    padding: 4px 0 2px;
     -webkit-appearance: textfield;
     border: 1px solid hsl(0, 0%, 78%);
     background: white;
diff --git a/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideLabelView.css b/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideLabelView.css
new file mode 100644 (file)
index 0000000..859486d
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+.local-resource-override-label-view {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    width: 100%;
+    height: var(--navigation-bar-height);
+    padding: 0 4px;
+    color: var(--yellow-highlight-text-color);
+    background-color: var(--yellow-highlight-background-color);
+    border-bottom: 1px solid var(--border-color);
+}
+
+.local-resource-override-label-view > div {
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    overflow: hidden;
+}
+
+.local-resource-override-label-view > div > .label {
+    display: inline-block;
+    margin: 0 4px;
+    padding: 0px 5px 1px;
+    font-size: 11px;
+    line-height: 15px;
+    color: var(--gray-foreground-color);
+    background-color: hsl(44, 83%, 49%);
+    border-radius: 2px;
+}
+
+.local-resource-override-label-view > div > .url {
+    -webkit-user-select: text;
+}
+
+@media (prefers-color-scheme: dark) {
+    .local-resource-override-label-view > div > .label {
+        background-color: hsl(41, 74%, 38%);
+    }
+}
diff --git a/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideLabelView.js b/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideLabelView.js
new file mode 100644 (file)
index 0000000..ff52a5c
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WI.LocalResourceOverrideLabelView = class LocalResourceOverrideLabelView extends WI.View
+{
+    constructor(localResource)
+    {
+        console.assert(localResource.isLocalResourceOverride);
+
+        super();
+
+        this._localResource = localResource;
+
+        this.element.classList.add("local-resource-override-label-view");
+    }
+
+    // Protected
+
+    initialLayout()
+    {
+        this._labelElement = document.createElement("span");
+        this._labelElement.className = "label";
+        this._labelElement.textContent = WI.UIString("Override");
+
+        this._urlElement = document.createElement("span");
+        this._urlElement.textContent = this._localResource.url;
+        this._urlElement.className = "url";
+
+        let container = this.element.appendChild(document.createElement("div"));
+        container.append(this._labelElement, " ", this._urlElement);
+    }
+};
diff --git a/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverridePopover.css b/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverridePopover.css
new file mode 100644 (file)
index 0000000..feb3001
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+.popover .local-resource-override-popover-content {
+    max-width: 420px;
+    padding: 4px 8px;
+}
+
+.popover .local-resource-override-popover-content > label.toggle {
+    font-weight: bold;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+}
+
+.popover .local-resource-override-popover-content > table {
+    width: 100%;
+}
+
+.popover .local-resource-override-popover-content > table > tr > th {
+    font-weight: bold;
+    line-height: 23px;
+    text-align: end;
+    vertical-align: top;
+    white-space: nowrap;
+    color: hsl(0, 0%, 34%);
+}
+
+.popover .local-resource-override-popover-content > table > tr > td {
+    padding-left: 4px;
+}
+
+.popover .local-resource-override-popover-content .editor {
+    display: inline-block;
+    width: 100%;
+    padding: 4px 0 2px;
+    -webkit-appearance: unset;
+    border: 1px solid hsl(0, 0%, 78%);
+    background: var(--background-color-code);
+    border-color: var(--text-color-quaternary);
+}
+
+.popover .local-resource-override-popover-content .editor > .CodeMirror {
+    height: auto;
+}
+
+.popover .local-resource-override-popover-content .editor.url {
+    width: 324px;
+}
+
+.popover .local-resource-override-popover-content .editor.mime {
+    width: 240px;
+}
+
+.popover .local-resource-override-popover-content .editor.status {
+    width: 36px;
+}
+
+.popover .local-resource-override-popover-content .editor.status-text {
+    width: 168px;
+    -webkit-margin-start: 12px;
+}
+
+.popover .local-resource-override-popover-content .add-header {
+    margin-top: 8px;
+}
+
+@media (prefers-color-scheme: dark) {
+    .popover .local-resource-override-popover-content > table > tr > th {
+        color: var(--text-color-secondary);
+    }
+}
diff --git a/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverridePopover.js b/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverridePopover.js
new file mode 100644 (file)
index 0000000..6a0a48e
--- /dev/null
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WI.LocalResourceOverridePopover = class LocalResourceOverridePopover extends WI.Popover
+{
+    constructor(delegate)
+    {
+        super(delegate);
+
+        this._urlCodeMirror = null;
+        this._mimeTypeCodeMirror = null;
+        this._statusCodeCodeMirror = null;
+        this._statusTextCodeMirror = null;
+        this._headersDataGrid = null;
+
+        this._serializedDataWhenShown = null;
+
+        this.windowResizeHandler = this._presentOverTargetElement.bind(this);
+    }
+
+    // Public
+
+    get serializedData()
+    {
+        if (!this._targetElement)
+            return null;
+
+        let url = this._urlCodeMirror.getValue();
+        if (!url)
+            return null;
+
+        const schemes = ["http:", "https:", "file:"];
+        if (!schemes.some((scheme) => url.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
+        // the popover doesn't have to have an additional state for "pass through".
+
+        let mimeType = this._mimeTypeCodeMirror.getValue();
+        if (!mimeType)
+            return null;
+
+        let statusCode = parseInt(this._statusCodeCodeMirror.getValue());
+        if (isNaN(statusCode) || statusCode < 0)
+            return null;
+
+        let statusText = this._statusTextCodeMirror.getValue();
+        if (!statusText)
+            return null;
+
+        let headers = {};
+        for (let node of this._headersDataGrid.children) {
+            let {name, value} = node.data;
+            if (!name || !value)
+                continue;
+            if (name.toLowerCase() === "content-type")
+                continue;
+            if (name.toLowerCase() === "set-cookie")
+                continue;
+            headers[name] = value;
+        }
+
+        let data = {
+            url,
+            mimeType,
+            statusCode,
+            statusText,
+            headers,
+        };
+
+        // No change.
+        let oldSerialized = JSON.stringify(this._serializedDataWhenShown);
+        let newSerialized = JSON.stringify(data);
+        if (oldSerialized === newSerialized)
+            return null;
+
+        return data;
+    }
+
+    show(localResource, targetElement, preferredEdges)
+    {
+        this._targetElement = targetElement;
+        this._preferredEdges = preferredEdges;
+
+        let url = localResource ? localResource.url : "";
+        let mimeType = localResource ? localResource.mimeType : "";
+        let statusCode = localResource ? String(localResource.statusCode) : "";
+        let statusText = localResource ? localResource.statusText : "";
+        let responseHeaders = localResource ? localResource.responseHeaders : {};
+
+        if (!url)
+            url = this._defaultURL();
+        if (!mimeType)
+            mimeType = "text/javascript";
+        if (!statusCode || statusCode === "NaN")
+            statusCode = "200";
+        if (!statusText)
+            statusText = WI.HTTPUtilities.statusTextForStatusCode(statusCode);
+
+        let popoverContentElement = document.createElement("div");
+        popoverContentElement.className = "local-resource-override-popover-content";
+
+        let table = popoverContentElement.appendChild(document.createElement("table"));
+
+        let createRow = (label, id, text) => {
+            let row = table.appendChild(document.createElement("tr"));
+            let headerElement = row.appendChild(document.createElement("th"));
+            let dataElement = row.appendChild(document.createElement("td"));
+
+            let labelElement = headerElement.appendChild(document.createElement("label"));
+            labelElement.textContent = label;
+
+            let editorElement = dataElement.appendChild(document.createElement("div"));
+            editorElement.classList.add("editor", id);
+
+            let codeMirror = this._createEditor(editorElement, text);
+            let inputField = codeMirror.getInputField();
+            inputField.id = `local-resource-override-popover-${id}-input-field`;
+            labelElement.setAttribute("for", inputField.id);
+
+            return {codeMirror, dataElement}
+        };
+
+        let urlRow = createRow(WI.UIString("URL"), "url", url);
+        this._urlCodeMirror = urlRow.codeMirror;
+        this._urlCodeMirror.setOption("mode", "text/x-local-override-url");
+
+        let mimeTypeRow = createRow(WI.UIString("MIME Type"), "mime", mimeType);
+        this._mimeTypeCodeMirror = mimeTypeRow.codeMirror;
+
+        let statusCodeRow = createRow(WI.UIString("Status"), "status", statusCode);
+        this._statusCodeCodeMirror = statusCodeRow.codeMirror;
+
+        let statusTextEditorElement = statusCodeRow.dataElement.appendChild(document.createElement("div"));
+        statusTextEditorElement.className = "editor status-text";
+        this._statusTextCodeMirror = this._createEditor(statusTextEditorElement, statusText);
+
+        let editCallback = () => {};
+        let deleteCallback = (node) => {
+            if (node === contentTypeDataGridNode)
+                return;
+
+            let siblingToSelect = node.nextSibling || node.previousSibling;
+            dataGrid.removeChild(node);
+            if (siblingToSelect)
+                siblingToSelect.select();
+
+            dataGrid.updateLayoutIfNeeded();
+            this.update();
+        };
+
+        let columns = {
+            name: {
+                title: WI.UIString("Name"),
+                width: "30%",
+            },
+            value: {
+                title: WI.UIString("Value"),
+            },
+        };
+
+        let dataGrid = this._headersDataGrid = new WI.DataGrid(columns, {editCallback, deleteCallback});
+        dataGrid.inline = true;
+        dataGrid.variableHeightRows = true;
+        dataGrid.copyTextDelimiter = ": ";
+
+        function addDataGridNodeForHeader(name, value) {
+            let node = new WI.DataGridNode({name, value});
+            dataGrid.appendChild(node);
+            return node;
+        }
+
+        let contentTypeDataGridNode = addDataGridNodeForHeader("Content-Type", mimeType);
+        contentTypeDataGridNode.editable = false;
+
+        for (let name in responseHeaders) {
+            if (name.toLowerCase() === "content-type")
+                continue;
+            if (name.toLowerCase() === "set-cookie")
+                continue;
+            addDataGridNodeForHeader(name, responseHeaders[name]);
+        }
+
+        let headersRow = table.appendChild(document.createElement("tr"));
+        let headersHeader = headersRow.appendChild(document.createElement("th"));
+        let headersData = headersRow.appendChild(document.createElement("td"));
+        let headersLabel = headersHeader.appendChild(document.createElement("label"));
+        headersLabel.textContent = WI.UIString("Headers");
+        headersData.appendChild(dataGrid.element);
+        dataGrid.updateLayoutIfNeeded();
+
+        let addHeaderButton = headersData.appendChild(document.createElement("button"));
+        addHeaderButton.className = "add-header";
+        addHeaderButton.textContent = WI.UIString("Add Header");
+        addHeaderButton.addEventListener("click", (event) => {
+            let newNode = new WI.DataGridNode({name: "Header", value: "value"});
+            dataGrid.appendChild(newNode);
+            dataGrid.updateLayoutIfNeeded();            
+            this.update();
+            dataGrid.startEditingNode(newNode);
+        });
+
+        let incrementStatusCode = () => {
+            let x = parseInt(this._statusCodeCodeMirror.getValue());
+            if (isNaN(x) || x >= 999)
+                return;
+
+            if (WI.modifierKeys.shiftKey) {
+                // 200 => 300 and 211 => 300
+                x = (x - (x % 100)) + 100;
+            } else
+                x += 1;
+
+            if (x > 999)
+                x = 999;
+
+            this._statusCodeCodeMirror.setValue(`${x}`);
+            this._statusCodeCodeMirror.setCursor(this._statusCodeCodeMirror.lineCount(), 0);
+        };
+
+        let decrementStatusCode = () => {
+            let x = parseInt(this._statusCodeCodeMirror.getValue());
+            if (isNaN(x) || x <= 0)
+                return;
+
+            if (WI.modifierKeys.shiftKey) {
+                // 311 => 300 and 300 => 200
+                let original = x;
+                x = (x - (x % 100));
+                if (original === x)
+                    x -= 100;
+            } else
+                x -= 1;
+
+            if (x < 0)
+                x = 0;
+
+            this._statusCodeCodeMirror.setValue(`${x}`);
+            this._statusCodeCodeMirror.setCursor(this._statusCodeCodeMirror.lineCount(), 0);
+        };
+
+        this._statusCodeCodeMirror.addKeyMap({
+            "Up": incrementStatusCode,
+            "Shift-Up": incrementStatusCode,
+            "Down": decrementStatusCode,
+            "Shift-Down": decrementStatusCode,
+        });
+
+        // Update statusText when statusCode changes.
+        this._statusCodeCodeMirror.on("change", (cm) => {
+            let statusCode = parseInt(cm.getValue());
+            let statusText = WI.HTTPUtilities.statusTextForStatusCode(statusCode);
+            this._statusTextCodeMirror.setValue(statusText);
+        });
+
+        // Update mimeType when URL gets a file extension.
+        this._urlCodeMirror.on("change", (cm) => {
+            let extension = WI.fileExtensionForURL(cm.getValue());
+            if (!extension)
+                return;
+
+            let mimeType = WI.mimeTypeForFileExtension(extension);
+            if (!mimeType)
+                return;
+
+            this._mimeTypeCodeMirror.setValue(mimeType);
+            contentTypeDataGridNode.data = {name: "Content-Type", value: mimeType};
+        });
+
+        // Update Content-Type header when mimeType changes.
+        this._mimeTypeCodeMirror.on("change", (cm) => {
+            let mimeType = cm.getValue();
+            contentTypeDataGridNode.data = {name: "Content-Type", value: mimeType};
+        });
+
+        this._serializedDataWhenShown = this.serializedData;
+
+        this.content = popoverContentElement;
+        this._presentOverTargetElement();
+
+        // CodeMirror needs a refresh after the popover displays, to layout, otherwise it doesn't appear.
+        setTimeout(() => {
+            this._urlCodeMirror.refresh();
+            this._mimeTypeCodeMirror.refresh();
+            this._statusCodeCodeMirror.refresh();
+            this._statusTextCodeMirror.refresh();
+
+            this._urlCodeMirror.focus();
+            this._urlCodeMirror.setCursor(this._urlCodeMirror.lineCount(), 0);
+        });
+    }
+
+    // Private
+
+    _createEditor(element, value)
+    {
+        let codeMirror = WI.CodeMirrorEditor.create(element, {
+            extraKeys: {"Tab": false, "Shift-Tab": false},
+            lineWrapping: false,
+            mode: "text/plain",
+            matchBrackets: true,
+            placeholder: "http://example.com/index.html",
+            scrollbarStyle: null,
+            value,
+        });
+
+        codeMirror.addKeyMap({
+            "Enter": () => { this.dismiss(); },
+            "Shift-Enter": () => { this.dismiss(); },
+            "Esc": () => { this.dismiss(); },
+        });
+
+        return codeMirror;
+    }
+
+    _defaultURL()
+    {
+        // We avoid just doing "http://example.com/" here because users can
+        // accidentally override the main resource, even though the popover
+        // typically prevents no-edit cases.
+        let mainFrame = WI.networkManager.mainFrame;
+        if (mainFrame && mainFrame.securityOrigin.startsWith("http"))
+            return mainFrame.securityOrigin + "/path";
+
+        return "https://";
+    }
+
+    _presentOverTargetElement()
+    {
+        if (!this._targetElement)
+            return;
+
+        let targetFrame = WI.Rect.rectFromClientRect(this._targetElement.getBoundingClientRect());
+        this.present(targetFrame, this._preferredEdges);
+    }
+};
diff --git a/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideTreeElement.css b/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideTreeElement.css
new file mode 100644 (file)
index 0000000..846e8da
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+.item.resource.override .status > div {
+    width: 14px;
+    height: 16px;
+    margin-top: 1px;
+}
diff --git a/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideTreeElement.js b/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideTreeElement.js
new file mode 100644 (file)
index 0000000..33af26e
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WI.LocalResourceOverrideTreeElement = class LocalResourceOverrideTreeElement extends WI.ResourceTreeElement
+{
+    constructor(localResource, representedObject, options)
+    {
+        console.assert(localResource instanceof WI.LocalResource);
+        console.assert(localResource.isLocalResourceOverride);
+        console.assert(representedObject instanceof WI.LocalResourceOverride);
+
+        super(localResource, representedObject, options);
+
+        this._localResourceOverride = representedObject;
+
+        this._popover = null;
+    }
+
+    // Protected
+
+    onattach()
+    {
+        super.onattach();
+
+        this._localResourceOverride.addEventListener(WI.LocalResourceOverride.Event.DisabledChanged, this._handleLocalResourceOverrideDisabledChanged, this);
+        WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._handleFrameMainResourceDidChange, this);
+
+        this._updateStatusCheckbox();
+    }
+
+    ondetach()
+    {
+        this._localResourceOverride.removeEventListener(WI.LocalResourceOverride.Event.DisabledChanged, this._handleLocalResourceOverrideDisabledChanged, this);
+
+        WI.Frame.removeEventListener(WI.Frame.Event.MainResourceDidChange, this._handleFrameMainResourceDidChange, this);
+
+        super.ondetach();
+    }
+
+    ondelete()
+    {
+        WI.networkManager.removeLocalResourceOverride(this._localResourceOverride);
+
+        return true;
+    }
+
+    onspace()
+    {
+        this._localResourceOverride.disabled = !this._localResourceOverride.disabled;
+
+        return true;
+    }
+
+    canSelectOnMouseDown(event)
+    {
+        if (this.status.contains(event.target))
+            return false;
+
+        return super.canSelectOnMouseDown(event);
+    }
+
+    populateContextMenu(contextMenu, event)
+    {
+        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]);
+        });
+
+        let toggleEnabledString = this._localResourceOverride.disabled ? WI.UIString("Enable Local Override") : WI.UIString("Disable Local Override");
+        contextMenu.appendItem(toggleEnabledString, () => {
+            this._localResourceOverride.disabled = !this._localResourceOverride.disabled;
+        });            
+
+        contextMenu.appendItem(WI.UIString("Remove Local Override"), () => {
+            WI.networkManager.removeLocalResourceOverride(this._localResourceOverride);
+        });
+
+        super.populateContextMenu(contextMenu, event);
+    }
+
+    updateStatus()
+    {
+        // Do nothing. Do not allow ResourceTreeElement / SourceCodeTreeElement to modify our status element.
+    }
+
+    // Popover delegate
+
+    willDismissPopover(popover)
+    {
+        let serializedData = popover.serializedData;
+        if (!serializedData)
+            return;
+
+        let {url, mimeType, statusCode, statusText, headers} = serializedData;
+
+        // Do not conflict with an existing override unless we are modifying ourselves.
+        let existingOverride = WI.networkManager.localResourceOverrideForURL(url);
+        if (existingOverride && existingOverride !== this._localResourceOverride) {
+            InspectorFrontendHost.beep();
+            return;
+        }
+
+        let wasSelected = this.selected;
+
+        let newLocalResourceOverride = WI.LocalResourceOverride.create({
+            url,
+            mimeType,
+            statusCode,
+            statusText,
+            headers,
+            content: this._localResourceOverride.localResource.localContent,
+            base64Encoded: this._localResourceOverride.localResource.localContentIsBase64Encoded,
+        });
+
+        WI.networkManager.removeLocalResourceOverride(this._localResourceOverride);
+        WI.networkManager.addLocalResourceOverride(newLocalResourceOverride);
+
+        if (wasSelected) {
+            const cookie = null;
+            const options = {ignoreNetworkTab: true, ignoreSearchTab: true};
+            WI.showRepresentedObject(newLocalResourceOverride, cookie, options);
+        }
+    }
+
+    // Private
+
+    _updateStatusCheckbox()
+    {
+        this.status = document.createElement("input");
+        this.status.type = "checkbox";
+        this.status.checked = !this._localResourceOverride.disabled;
+        this.status.addEventListener("change", (event) => {
+            this._localResourceOverride.disabled = !event.target.checked;
+        });
+    }
+
+    _handleLocalResourceOverrideDisabledChanged(event)
+    {
+        this.status.checked = !this._localResourceOverride.disabled;
+    }
+
+    _handleFrameMainResourceDidChange(event)
+    {
+        if (!event.target.isMainFrame())
+            return;
+
+        this._updateTitles();
+    }
+};
diff --git a/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideWarningView.css b/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideWarningView.css
new file mode 100644 (file)
index 0000000..f75b6e7
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+.local-resource-override-warning-view {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    width: 100%;
+    height: var(--navigation-bar-height);
+    padding: 0 4px;
+    color: var(--yellow-highlight-text-color);
+    background-color: var(--yellow-highlight-background-color);
+    border-bottom: 1px solid var(--border-color);
+}
+
+.local-resource-override-warning-view[hidden] {
+    display: none;
+}
+
+.local-resource-override-warning-view > div {
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    overflow: hidden;
+}
+
+.local-resource-override-warning-view > div > button {
+    margin: 0 4px;
+}
diff --git a/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideWarningView.js b/Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideWarningView.js
new file mode 100644 (file)
index 0000000..50c7001
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WI.LocalResourceOverrideWarningView = class LocalResourceOverrideWarningView extends WI.View
+{
+    constructor(resource)
+    {
+        console.assert(!resource.isLocalResourceOverride);
+
+        super();
+
+        this._resource = resource;
+
+        this.element.classList.add("local-resource-override-warning-view");
+    }
+
+    // Protected
+
+    attached()
+    {
+        super.attached();
+
+        WI.networkManager.addEventListener(WI.NetworkManager.Event.LocalResourceOverrideAdded, this._handleLocalResourceOverrideAddedOrRemoved, this);
+        WI.networkManager.addEventListener(WI.NetworkManager.Event.LocalResourceOverrideRemoved, this._handleLocalResourceOverrideAddedOrRemoved, this);
+
+        this._updateContent();
+    }
+
+    detached()
+    {
+        WI.networkManager.removeEventListener(null, null, this);
+
+        super.detached();
+    }
+
+    initialLayout()
+    {
+        this._revealButton = document.createElement("button");
+        this._revealButton.textContent = WI.UIString("Reveal");
+        this._revealButton.addEventListener("click", (event) => {
+            const cookie = null;
+            const options = {ignoreNetworkTab: true, ignoreSearchTab: true};
+            let overrideResource = WI.networkManager.localResourceOverrideForURL(this._resource.url);
+            WI.showRepresentedObject(overrideResource, cookie, options);
+        });
+
+        let container = this.element.appendChild(document.createElement("div"));
+        container.append(this._revealButton, WI.UIString("This Resource came from a Local Resource Override"));
+
+        this._updateContent();
+    }
+
+    // Private
+
+    _updateContent()
+    {
+        if (!this._revealButton)
+            return;
+
+        let hasLocalResourceOverride = !!WI.networkManager.localResourceOverrideForURL(this._resource.url);
+        this._revealButton.hidden = !hasLocalResourceOverride;
+
+        let resourceShowingOverrideContent = this._resource.responseSource === WI.Resource.ResponseSource.InspectorOverride;
+        this.element.hidden = !resourceShowingOverrideContent;
+    }
+
+    _handleLocalResourceOverrideAddedOrRemoved(event)
+    {
+        if (this._resource.url !== event.data.localResourceOverride.url)
+            return;
+
+        this._updateContent();
+    }
+};
index edc33a8caa76fd250afe52a70505bec8877fc5ab..22ed89658d700ff83505d4a4db042b846408cffc 100644 (file)
@@ -492,12 +492,16 @@ WI.NavigationSidebarPanel = class NavigationSidebarPanel extends WI.SidebarPanel
             // Check all the ResourceTreeElements at the top level to make sure their Resource still has a parentFrame in the frame hierarchy.
             // If the parentFrame is no longer in the frame hierarchy we know it was removed due to a navigation or some other page change and
             // we should remove the issues for that resource.
-            for (var i = contentTreeOutline.children.length - 1; i >= 0; --i) {
-                var treeElement = contentTreeOutline.children[i];
+            for (let i = contentTreeOutline.children.length - 1; i >= 0; --i) {
+                let treeElement = contentTreeOutline.children[i];
                 if (!(treeElement instanceof WI.ResourceTreeElement))
                     continue;
 
-                var resource = treeElement.resource;
+                // Local Overrides are never stale resources.
+                let resource = treeElement.resource;
+                if (resource.isLocalResourceOverride)
+                    continue;
+
                 if (!resource.parentFrame || resource.parentFrame.isDetached())
                     contentTreeOutline.removeChildAtIndex(i, true, true);
             }
index 38840b1dddbfa567f0e2cfd6064187dc97367744..2796713308c4f57e5baf22aa8627b9fa4b57b88f 100644 (file)
@@ -638,7 +638,7 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
 
         createIconElement();
 
-        cell.classList.add(WI.ResourceTreeElement.ResourceIconStyleClassName);
+        cell.classList.add(WI.ResourceTreeElement.ResourceIconStyleClassName, ...WI.Resource.classNamesForResource(resource));
 
         if (WI.settings.groupMediaRequestsByDOMNode.value && resource.initiatorNode) {
             let nodeEntry = this._domNodeEntries.get(resource.initiatorNode);
@@ -657,7 +657,7 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
         }
 
         cell.title = resource.url;
-        cell.classList.add(WI.Resource.classNameForResource(resource));
+        cell.classList.add(...WI.Resource.classNamesForResource(resource));
     }
 
     _populateDomainCell(cell, entry)
@@ -754,6 +754,11 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
             cell.textContent = WI.UIString("(service worker)");
             return;
         }
+        if (responseSource === WI.Resource.ResponseSource.InspectorOverride) {
+            cell.classList.add("cache-type");
+            cell.textContent = WI.UIString("(inspector override)");
+            return;
+        }
 
         let transferSize = entry.transferSize;
         cell.textContent = isNaN(transferSize) ? emDash : Number.bytesToString(transferSize);
@@ -1030,6 +1035,8 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
                     transferSizeA = -10;
                 else if (sourceA === WI.Resource.ResponseSource.ServiceWorker)
                     transferSizeA = -5;
+                else if (sourceA === WI.Resource.ResponseSource.InspectorOverride)
+                    transferSizeA = -3;
 
                 let sourceB = b.resource.responseSource;
                 if (sourceB === WI.Resource.ResponseSource.MemoryCache)
@@ -1038,6 +1045,8 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
                     transferSizeB = -10;
                 else if (sourceB === WI.Resource.ResponseSource.ServiceWorker)
                     transferSizeB = -5;
+                else if (sourceB === WI.Resource.ResponseSource.InspectorOverride)
+                    transferSizeB = -3;
 
                 return transferSizeA - transferSizeB;
             };
index cabb4376ff065a1e6c845d64ef9a82ee766efeb5..0b0bf0ebe9c2641e4c7b2fa763b5700fedb191d2 100644 (file)
@@ -232,6 +232,8 @@ WI.ResourceHeadersContentView = class ResourceHeadersContentView extends WI.Cont
             return WI.UIString("Disk Cache");
         case WI.Resource.ResponseSource.ServiceWorker:
             return WI.UIString("Service Worker");
+        case WI.Resource.ResponseSource.InspectorOverride:
+            return WI.UIString("Inspector Override");
         case WI.Resource.ResponseSource.Unknown:
         default:
             return null;
index 9a072bc70bd4a837c0def551be3db1e06824751d..68a2e1fa46b79f3064d8b8ff7326045471120936 100644 (file)
     content: image-set(url(../Images/DocumentGeneric.png) 1x, url(../Images/DocumentGeneric@2x.png) 2x);
 }
 
+.resource-icon.override .icon {
+    filter: invert() hue-rotate(260deg);
+}
+
 .resource-icon.resource-type-document .icon {
     content: image-set(url(../Images/DocumentMarkup.png) 1x, url(../Images/DocumentMarkup@2x.png) 2x);
 }
index ff05a8127fad8a86d171637be53920feb995294d..ae55b102cd5fb018abb1e776f069c5c6f6f34081 100644 (file)
@@ -129,7 +129,7 @@ WI.ResourceSizesContentView = class ResourceSizesContentView extends WI.ContentV
         resourceSection.className = "subsection large";
 
         let resourceComponents = createSizeComponents(resourceSection, WI.UIString("Resource Size"), null, WI.UIString("Compression:"), WI.UIString("MIME Type:"));
-        resourceComponents.container.classList.add(WI.ResourceTreeElement.ResourceIconStyleClassName, this._resource.type);
+        resourceComponents.container.classList.add(WI.ResourceTreeElement.ResourceIconStyleClassName, ...WI.Resource.classNamesForResource(this._resource));
         resourceComponents.imageElement.classList.add("icon");
         this._resourceBytesElement = resourceComponents.bytesElement;
         this._resourceBytesSuffixElement = resourceComponents.suffixElement;
index c431da8dad04cf4b79d220bcefe9c0218b511346..695d4bedbb5caec84bddc0e75d6e3f5cb0def7af 100644 (file)
@@ -162,7 +162,7 @@ WI.ResourceTimelineDataGridNode = class ResourceTimelineDataGridNode extends WI.
 
     iconClassNames()
     {
-        return [WI.ResourceTreeElement.ResourceIconStyleClassName, this.resource.type];
+        return [WI.ResourceTreeElement.ResourceIconStyleClassName, ...WI.Resource.classNamesForResource(this.resource)];
     }
 
     appendContextMenuItems(contextMenu)
index 85a90de30075a4ea6a3761212d2390f8e9c9239f..b354b56aa0d1d9540b2dd138b495ecb9f564e51e 100644 (file)
@@ -31,7 +31,8 @@ WI.ResourceTreeElement = class ResourceTreeElement extends WI.SourceCodeTreeElem
 
         const title = null;
         const subtitle = null;
-        super(resource, ["resource", WI.ResourceTreeElement.ResourceIconStyleClassName, WI.Resource.classNameForResource(resource)], title, subtitle, representedObject || resource);
+        let styleClassNames = ["resource", WI.ResourceTreeElement.ResourceIconStyleClassName, ...WI.Resource.classNamesForResource(resource)];
+        super(resource, styleClassNames, title, subtitle, representedObject || resource);
 
         if (allowDirectoryAsName)
             this._allowDirectoryAsName = allowDirectoryAsName;
@@ -120,6 +121,7 @@ WI.ResourceTreeElement = class ResourceTreeElement extends WI.SourceCodeTreeElem
             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.ResponseReceived, this._responseReceived, this);
         }
 
         this._updateSourceCode(resource);
@@ -130,10 +132,12 @@ WI.ResourceTreeElement = class ResourceTreeElement extends WI.SourceCodeTreeElem
         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.ResponseReceived, this._responseReceived, this);
 
         this._updateTitles();
         this.updateStatus();
         this._updateToolTip();
+        this._updateIcon();
     }
 
     // Protected
@@ -166,9 +170,17 @@ WI.ResourceTreeElement = class ResourceTreeElement extends WI.SourceCodeTreeElem
         this.mainTitle = this.mainTitleText;
 
         if (!this._hideOrigin) {
-            // Show the host as the subtitle if it is different from the main resource or if this is the main frame's main resource.
-            var subtitle = parentResourceHost !== urlComponents.host || frame && frame.isMainFrame() && isMainResource ? WI.displayNameForHost(urlComponents.host) : null;
-            this.subtitle = this.mainTitle !== subtitle ? subtitle : null;
+            if (this._resource.isLocalResourceOverride) {
+                // Show the host for a local resource override if it is different from the main frame.
+                let localResourceOverrideHost = urlComponents.host;
+                let mainFrameHost = (WI.networkManager.mainFrame && WI.networkManager.mainFrame.mainResource) ? WI.networkManager.mainFrame.mainResource.urlComponents.host : null;
+                let subtitle = localResourceOverrideHost !== mainFrameHost ? localResourceOverrideHost : null;
+                this.subtitle = this.mainTitle !== subtitle ? subtitle : null;
+            } else {
+                // Show the host as the subtitle if it is different from the main resource or if this is the main frame's main resource.
+                let subtitle = (parentResourceHost !== urlComponents.host || (frame && frame.isMainFrame() && isMainResource)) ? WI.displayNameForHost(urlComponents.host) : null;
+                this.subtitle = this.mainTitle !== subtitle ? subtitle : null;
+            }
         }
 
         if (oldMainTitle !== this.mainTitle)
@@ -222,6 +234,17 @@ WI.ResourceTreeElement = class ResourceTreeElement extends WI.SourceCodeTreeElem
         this.tooltip = this._resource.displayURL;
     }
 
+    _updateIcon()
+    {
+        let isOverride = this._resource.isLocalResourceOverride;
+        let wasOverridden = this._resource.responseSource === WI.Resource.ResponseSource.InspectorOverride;
+
+        if (isOverride || wasOverridden)
+            this.addClassName("override");
+        else
+            this.removeClassName("override");
+    }
+
     _urlDidChange(event)
     {
         this._updateTitles();
@@ -235,6 +258,11 @@ WI.ResourceTreeElement = class ResourceTreeElement extends WI.SourceCodeTreeElem
 
         this.callFirstAncestorFunction("descendantResourceTreeElementTypeDidChange", [this, event.data.oldType]);
     }
+
+    _responseReceived(event)
+    {
+        this._updateIcon();
+    }
 };
 
 WI.ResourceTreeElement.ResourceIconStyleClassName = "resource-icon";
index 46835289886641e91cc12768d4220e7df1acd1a9..d4d4d56912942241d8f960ec544ea2ab1ea0acaa 100644 (file)
@@ -31,7 +31,7 @@
 .watch-expression-editor {
     width: 200px;
     margin-top: 5px;
-    padding: 4px 0 2px 0;
+    padding: 4px 0 2px;
     -webkit-appearance: textfield;
     border: 1px solid hsl(0, 0%, 78%);
     background: white;
index 3e3a7d7719b5ae1f34fdd4e1d7d9de1dc00c3dc6..00bd8b29bd9dbee6f774dd4eba7976997793ef5a 100644 (file)
@@ -60,7 +60,8 @@ body[dir=rtl] .sidebar > .panel.navigation.search .item.source-code-match {
     padding: 6px;
     text-align: center;
     font-size: 11px;
-    background-color: hsl(50, 100%, 94%);
+    color: var(--yellow-highlight-text-color);
+    background-color: var(--yellow-highlight-background-color);
     border-top: 1px solid var(--border-color);
     border-bottom: 1px solid var(--border-color);
 }
@@ -73,10 +74,3 @@ body[dir=rtl] .sidebar > .panel.navigation.search .item.source-code-match {
 .sidebar > .panel.navigation.search.changed > :matches(.content, .message-text-view) {
     top: calc(var(--navigation-bar-height) + 40px - 1px);
 }
-
-@media (prefers-color-scheme: dark) {
-    .sidebar > .panel.navigation.search.changed > .banner {
-        background-color: var(--yellow-highlight-background-color);
-        color: var(--yellow-highlight-text-color);
-    }
-}
index fb1b3d12f5412895d49fa51b28b151508fda81ac..b4a26c7a287bd50ab2bceef0b8afd3d0ebd002cd 100644 (file)
@@ -311,6 +311,8 @@ WI.SearchSidebarPanel = class SearchSidebarPanel extends WI.NavigationSidebarPan
             countPromise(DOMAgent.performSearch.invoke(commandArguments), domCallback);
         }
 
+        // FIXME: Resource search should work with Local Overrides if enabled.
+
         // FIXME: Resource search should work in JSContext inspection.
         // <https://webkit.org/b/131252> Web Inspector: JSContext inspection Resource search does not work
     }
index 5d3aa8a6a7a716ac690ee6c26efc8d1e8a016d9c..5a415a11a7a8942d37de7e31133f2de91acf3f3a 100644 (file)
@@ -1175,8 +1175,11 @@ WI.SourceCodeTextEditor = class SourceCodeTextEditor extends WI.TextEditor
 
     get _supportsDebugging()
     {
-        if (this._sourceCode instanceof WI.Resource)
+        if (this._sourceCode instanceof WI.Resource) {
+            if (this._sourceCode.isLocalResourceOverride)
+                return false;
             return this._sourceCode.type === WI.Resource.Type.Document || this._sourceCode.type === WI.Resource.Type.Script;
+        }
         if (this._sourceCode instanceof WI.Script)
             return !(this._sourceCode instanceof WI.LocalScript);
         return false;
index 3af9d0b3a7797b24f5905755a53cff5303487a3a..96c0d654af34324ba780769ee82e7ffedd49e924 100644 (file)
@@ -57,7 +57,8 @@
     padding: 11px 6px;
     font-size: 11px;
     text-align: center;
-    background-color: hsl(50, 100%, 94%);
+    color: var(--yellow-highlight-text-color);
+    background-color: var(--yellow-highlight-background-color);
     border-bottom: 1px solid var(--border-color);
 }
 
     display: none;
 }
 
-.sidebar > .panel.navigation.sources > .content > :matches(.pause-reason-container, .call-stack-container, .breakpoints-container) {
+.sidebar > .panel.navigation.sources > .content > :matches(.pause-reason-container, .call-stack-container, .breakpoints-container, .local-overrides-container) {
     border-bottom: 1px solid var(--border-color);
 }
 
+.sidebar > .panel.navigation.sources > .content > .local-overrides {
+    border-width: 1px !important;
+}
+
 .sidebar > .panel.navigation.sources > .content .details-section {
     font-size: 11px;
     --details-section-border-bottom: none;
         --details-section-header-top: 0;
     }
 
-    .sidebar > .panel.navigation.sources > .content > :matches(.call-stack-container, .breakpoints-container, .resources-container) {
+    .sidebar > .panel.navigation.sources > .content > :matches(.call-stack-container, .breakpoints-container, .resources-container, .local-overrides-container) {
         height: 100%;
         overflow-y: auto;
     }
         max-height: fit-content;
     }
 
+    .sidebar > .panel.navigation.sources > .content > .local-overrides-container {
+        flex-grow: 1;
+        flex-shrink: 3;
+        max-height: fit-content;
+    }
+
     .sidebar > .panel.navigation.sources > .content > .resources-container {
         flex-grow: 1;
         flex-shrink: 3;
 .sidebar > .panel.navigation.sources > .content > .breakpoints-container .tree-outline .item.event-target-window .icon {
     content: url(../Images/TypeObject.svg);
 }
-
-@media (prefers-color-scheme: dark) {
-    .sidebar > .panel.navigation.sources > .content > .warning-banner {
-        color: var(--yellow-highlight-text-color);
-        background-color: var(--yellow-highlight-background-color);
-    }
-}
-
index e245ad6d68b6c7aacc1791b3e94be58e041e51c5..ff150a291c86be851042a8a0a11b840f7f642724 100644 (file)
@@ -130,6 +130,19 @@ WI.SourcesNavigationSidebarPanel = class SourcesNavigationSidebarPanel extends W
         this._callStackContainer.classList.add("call-stack-container");
         this._callStackContainer.appendChild(this._callStackSection.element);
 
+        this._localResourceOverridesTreeOutline = this.createContentTreeOutline({suppressFiltering: true});
+        this._localResourceOverridesTreeOutline.addEventListener(WI.TreeOutline.Event.SelectionDidChange, this._handleTreeSelectionDidChange, this);
+
+        let localResourceOverridesRow = new WI.DetailsSectionRow;
+        localResourceOverridesRow.element.appendChild(this._localResourceOverridesTreeOutline.element);
+
+        let localResourceOverridesGroup = new WI.DetailsSectionGroup([localResourceOverridesRow]);
+        this._localResourceOverridesSection = new WI.DetailsSection("local-overrides", WI.UIString("Local Overrides"), [localResourceOverridesGroup]);
+
+        this._localResourceOverridesContainer = document.createElement("div");
+        this._localResourceOverridesContainer.classList.add("local-overrides-container");
+        this._localResourceOverridesContainer.appendChild(this._localResourceOverridesSection.element);
+
         this._mainTargetTreeElement = null;
         this._activeCallFrameTreeElement = null;
 
@@ -259,6 +272,11 @@ WI.SourcesNavigationSidebarPanel = class SourcesNavigationSidebarPanel extends W
 
         WI.networkManager.addEventListener(WI.NetworkManager.Event.FrameWasAdded, this._handleFrameWasAdded, this);
 
+        if (WI.NetworkManager.supportsLocalResourceOverrides()) {
+            WI.networkManager.addEventListener(WI.NetworkManager.Event.LocalResourceOverrideAdded, this._handleLocalResourceOverrideAdded, this);
+            WI.networkManager.addEventListener(WI.NetworkManager.Event.LocalResourceOverrideRemoved, this._handleLocalResourceOverrideRemoved, this);
+        }
+
         WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.BreakpointAdded, this._handleDebuggerBreakpointAdded, this);
         WI.domDebuggerManager.addEventListener(WI.DOMDebuggerManager.Event.DOMBreakpointAdded, this._handleDebuggerBreakpointAdded, this);
         WI.domDebuggerManager.addEventListener(WI.DOMDebuggerManager.Event.EventBreakpointAdded, this._handleDebuggerBreakpointAdded, this);
@@ -324,6 +342,11 @@ WI.SourcesNavigationSidebarPanel = class SourcesNavigationSidebarPanel extends W
 
         this._handleResourceGroupingModeChanged();
 
+        if (WI.NetworkManager.supportsLocalResourceOverrides()) {
+            for (let localResourceOverride of WI.networkManager.localResourceOverrides)
+                this._addLocalResourceOverride(localResourceOverride);
+        }
+
         if (WI.domDebuggerManager.supported) {
             if (WI.settings.showAllAnimationFramesBreakpoint.value)
                 WI.domDebuggerManager.addEventBreakpoint(WI.domDebuggerManager.allAnimationFramesBreakpoint);
@@ -419,6 +442,14 @@ WI.SourcesNavigationSidebarPanel = class SourcesNavigationSidebarPanel extends W
     {
         // A custom implementation is needed for this since the frames are populated lazily.
 
+        if (representedObject instanceof WI.LocalResourceOverride)
+            return this._localResourceOverridesTreeOutline.findTreeElement(localResource);
+
+        if (representedObject instanceof WI.LocalResource) {
+            let localResourceOverride = WI.networkManager.localResourceOverrideForURL(representedObject.url);
+            return this._localResourceOverridesTreeOutline.findTreeElement(localResourceOverride);
+        }
+
         if (!this._mainFrameTreeElement && (representedObject instanceof WI.Resource || representedObject instanceof WI.Frame || representedObject instanceof WI.Collection)) {
             // All resources are under the main frame, so we need to return early if we don't have the main frame yet.
             return null;
@@ -514,7 +545,7 @@ WI.SourcesNavigationSidebarPanel = class SourcesNavigationSidebarPanel extends W
 
         treeOutline.addEventListener(WI.TreeOutline.Event.ElementRevealed, (event) => {
             let treeElement = event.data.element;
-            let detailsSections = [this._pauseReasonSection, this._callStackSection, this._breakpointsSection];
+            let detailsSections = [this._pauseReasonSection, this._callStackSection, this._breakpointsSection, this._localResourceOverridesSection];
             let detailsSection = detailsSections.find((detailsSection) => detailsSection.element.contains(treeElement.listItemElement));
             if (!detailsSection)
                 return;
@@ -592,18 +623,75 @@ WI.SourcesNavigationSidebarPanel = class SourcesNavigationSidebarPanel extends W
     // Popover delegate
 
     willDismissPopover(popover)
+    {
+        if (popover instanceof WI.LocalResourceOverridePopover) {
+            this._willDismissLocalOverridePopover(popover);
+            return;
+        }
+
+        if (popover instanceof WI.EventBreakpointPopover) {
+            this._willDismissEventBreakpointPopover(popover);
+            return;
+        }
+
+        if (popover instanceof WI.URLBreakpointPopover) {
+            this._willDismissURLBreakpointPopover(popover);
+            return;
+        }
+
+        console.assert();
+    }
+
+    // Private
+
+    _willDismissLocalOverridePopover(popover)
+    {
+        let serializedData = popover.serializedData;
+        if (!serializedData) {
+            InspectorFrontendHost.beep();
+            return;
+        }
+
+        let {url, mimeType, statusCode, statusText, headers} = serializedData;
+
+        // Do not conflict with an existing override.
+        let existingOverride = WI.networkManager.localResourceOverrideForURL(url);
+        if (existingOverride) {
+            InspectorFrontendHost.beep();
+            return;
+        }
+
+        let localResourceOverride = WI.LocalResourceOverride.create({
+            url,
+            mimeType,
+            statusCode,
+            statusText,
+            headers,
+            content: "",
+            base64Encoded: false,
+        });
+
+        WI.networkManager.addLocalResourceOverride(localResourceOverride);
+        WI.showLocalResourceOverride(localResourceOverride);
+    }
+
+    _willDismissEventBreakpointPopover(popover)
     {
         let breakpoint = popover.breakpoint;
         if (!breakpoint)
             return;
 
-        if (breakpoint instanceof WI.EventBreakpoint)
-            WI.domDebuggerManager.addEventBreakpoint(breakpoint);
-        else if (breakpoint instanceof WI.URLBreakpoint)
-            WI.domDebuggerManager.addURLBreakpoint(breakpoint);
+        WI.domDebuggerManager.addEventBreakpoint(breakpoint);
     }
 
-    // Private
+    _willDismissURLBreakpointPopover(popover)
+    {
+        let breakpoint = popover.breakpoint;
+        if (!breakpoint)
+            return;
+
+        WI.domDebuggerManager.addURLBreakpoint(breakpoint);
+    }
 
     _filterByResourcesWithIssues(treeElement)
     {
@@ -1138,6 +1226,43 @@ WI.SourcesNavigationSidebarPanel = class SourcesNavigationSidebarPanel extends W
             this._addIssue(issue, sourceCode);
     }
 
+    _addLocalResourceOverride(localResourceOverride)
+    {
+        console.assert(WI.NetworkManager.supportsLocalResourceOverrides());
+
+        if (this._localResourceOverridesTreeOutline.findTreeElement(localResourceOverride))
+            return;
+
+        let parentTreeElement = this._localResourceOverridesTreeOutline;
+        let resourceTreeElement = new WI.LocalResourceOverrideTreeElement(localResourceOverride.localResource, localResourceOverride);
+        let index = insertionIndexForObjectInListSortedByFunction(resourceTreeElement, parentTreeElement.children, this._boundCompareTreeElements);
+        parentTreeElement.insertChild(resourceTreeElement, index);
+
+        if (!this._localResourceOverridesContainer.parentNode)
+            this.contentView.element.insertBefore(this._localResourceOverridesContainer, this._resourcesNavigationBar.element);
+    }
+
+    _removeLocalResourceOverride(localResourceOverride)
+    {
+        console.assert(WI.NetworkManager.supportsLocalResourceOverrides());
+
+        let resourceTreeElement = this._localResourceOverridesTreeOutline.findTreeElement(localResourceOverride);
+        if (!resourceTreeElement)
+            return;
+
+        let wasSelected = resourceTreeElement.selected;
+
+        let parentTreeElement = this._localResourceOverridesTreeOutline;
+        parentTreeElement.removeChild(resourceTreeElement);
+
+        if (!parentTreeElement.children.length) {
+            this._localResourceOverridesContainer.remove();
+
+            if (wasSelected && WI.networkManager.mainFrame && WI.networkManager.mainFrame.mainResource)
+                WI.showRepresentedObject(WI.networkManager.mainFrame.mainResource);
+        }
+    }
+
     _updateTemporarilyDisabledBreakpointsButtons()
     {
         let breakpointsDisabledTemporarily = WI.debuggerManager.breakpointsDisabledTemporarily;
@@ -1542,6 +1667,11 @@ WI.SourcesNavigationSidebarPanel = class SourcesNavigationSidebarPanel extends W
         if (treeElement.representedObject === SourcesNavigationSidebarPanel.__windowEventTargetRepresentedObject)
             return;
 
+        if (treeElement instanceof WI.LocalResourceOverrideTreeElement) {
+            WI.showRepresentedObject(treeElement.representedObject.localResource);
+            return;
+        }
+
         if (treeElement instanceof WI.FolderTreeElement
             || treeElement instanceof WI.OriginTreeElement
             || treeElement instanceof WI.ResourceTreeElement
@@ -1720,6 +1850,14 @@ WI.SourcesNavigationSidebarPanel = class SourcesNavigationSidebarPanel extends W
                 popover.show(this._createBreakpointButton.element, [WI.RectEdge.MAX_Y, WI.RectEdge.MIN_Y, WI.RectEdge.MAX_X]);
             });
         }
+
+        if (WI.NetworkManager.supportsLocalResourceOverrides()) {
+            contextMenu.appendSeparator();
+            contextMenu.appendItem(WI.UIString("Local Override\u2026"), () => {
+                let popover = new WI.LocalResourceOverridePopover(this);
+                popover.show(null, this._createBreakpointButton.element, [WI.RectEdge.MAX_Y, WI.RectEdge.MIN_Y, WI.RectEdge.MAX_X]);
+            });
+        }
     }
 
     _populateCreateResourceContextMenu(contextMenu)
@@ -1827,6 +1965,16 @@ WI.SourcesNavigationSidebarPanel = class SourcesNavigationSidebarPanel extends W
         this._addResourcesRecursivelyForFrame(frame);
     }
 
+    _handleLocalResourceOverrideAdded(event)
+    {
+        this._addLocalResourceOverride(event.data.localResourceOverride);
+    }
+
+    _handleLocalResourceOverrideRemoved(event)
+    {
+        this._removeLocalResourceOverride(event.data.localResourceOverride);
+    }
+
     _handleDebuggerBreakpointAdded(event)
     {
         this._addBreakpoint(event.data.breakpoint);
index 2f895d7fecd9c2ee8d4ac2fa9e73257ddbb41bc0..aded1ae4696adb22685256518bc75e0363bd8507 100644 (file)
@@ -69,6 +69,7 @@ WI.SourcesTabContentView = class SourcesTabContentView extends WI.ContentBrowser
             || representedObject instanceof WI.ScriptCollection
             || representedObject instanceof WI.CSSStyleSheet
             || representedObject instanceof WI.CSSStyleSheetCollection
+            || representedObject instanceof WI.LocalResourceOverride
             || super.canShowRepresentedObject(representedObject);
     }
 
index 95318b6e64f3da76bdada4ef805cb76ccda347de..905110e23cdd635dae5e24ea2f93d8261ca7f97a 100644 (file)
@@ -25,7 +25,8 @@
 
 .text-editor {
     position: relative;
-
+    width: 100%;
+    height: 100%;
     overflow: hidden;
 }
 
index 9921f533280a1eb6cac82eb1ff8caf3ba11784d7..40043af4dad9745f03c34747f9653bebd5ebfe06 100644 (file)
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+.content-view.resource.text {
+    display: flex;
+    flex-direction: column;
+}
+
 .content-view.resource.text > .text-editor {
-    position: absolute;
-    top: 0;
-    left: 0;
-    right: 0;
-    bottom: 0;
+    position: relative;
+    width: 100%;
+    height: 100%;
 }
index f04ad01314a2e9f7bdb69a38b317121634592e7c..7b951abbac427409f849228e666997fd12734a2c 100644 (file)
@@ -27,6 +27,8 @@ WI.TextResourceContentView = class TextResourceContentView extends WI.ResourceCo
 {
     constructor(resource)
     {
+        console.assert(resource instanceof WI.Resource || resource instanceof WI.CSSStyleSheet);
+
         super(resource, "text");
 
         resource.addEventListener(WI.SourceCode.Event.ContentDidChange, this._sourceCodeContentDidChange, this);
@@ -54,6 +56,27 @@ WI.TextResourceContentView = class TextResourceContentView extends WI.ResourceCo
         this._codeCoverageButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
         WI.settings.enableControlFlowProfiler.addEventListener(WI.Setting.Event.Changed, this._enableControlFlowProfilerSettingChanged, this);
 
+        this._showingLocalResourceOverride = false;
+
+        if (WI.NetworkManager.supportsLocalResourceOverrides()) {
+            if (resource instanceof WI.Resource && resource.isLocalResourceOverride) {
+                this._showingLocalResourceOverride = true;
+                this._localResourceOverrideBannerView = new WI.LocalResourceOverrideLabelView(resource);
+
+                this._removeLocalResourceOverrideButtonNavigationItem = new WI.ButtonNavigationItem("remove-local-resource-override", WI.UIString("Remove Local Override"), "Images/NavigationItemTrash.svg", 15, 15);
+                this._removeLocalResourceOverrideButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleRemoveLocalResourceOverride, this);
+                this._removeLocalResourceOverrideButtonNavigationItem.enabled = true;
+                this._removeLocalResourceOverrideButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;                
+            } else {
+                this._localResourceOverrideBannerView = new WI.LocalResourceOverrideWarningView(resource);
+
+                this._createLocalResourceOverrideButtonNavigationItem = new WI.ButtonNavigationItem("create-local-resource-override", WI.UIString("Create Local Override"), "Images/NavigationItemNetworkOverride.svg", 13, 14);
+                this._createLocalResourceOverrideButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleCreateLocalResourceOverride, this);
+                this._createLocalResourceOverrideButtonNavigationItem.enabled = false; // Enabled when the text editor is populated with content.
+                this._createLocalResourceOverrideButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;            
+            }
+        }
+
         this._textEditor = new WI.SourceCodeTextEditor(resource);
         this._textEditor.addEventListener(WI.TextEditor.Event.ExecutionLineNumberDidChange, this._executionLineNumberDidChange, this);
         this._textEditor.addEventListener(WI.TextEditor.Event.NumberOfSearchResultsDidChange, this._numberOfSearchResultsDidChange, this);
@@ -66,13 +89,30 @@ WI.TextResourceContentView = class TextResourceContentView extends WI.ResourceCo
 
         WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.ProbeSetAdded, this._probeSetsChanged, this);
         WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.ProbeSetRemoved, this._probeSetsChanged, this);
+
+        if (WI.NetworkManager.supportsLocalResourceOverrides()) {
+            WI.networkManager.addEventListener(WI.NetworkManager.Event.LocalResourceOverrideAdded, this._handleLocalResourceOverrideChanged, this);
+            WI.networkManager.addEventListener(WI.NetworkManager.Event.LocalResourceOverrideRemoved, this._handleLocalResourceOverrideChanged, this);
+        }
     }
 
     // Public
 
     get navigationItems()
     {
-        return [this._prettyPrintButtonNavigationItem, this._showTypesButtonNavigationItem, this._codeCoverageButtonNavigationItem];
+        let items = [];
+
+        if (this._removeLocalResourceOverrideButtonNavigationItem)
+            items.push(this._removeLocalResourceOverrideButtonNavigationItem);
+        if (this._createLocalResourceOverrideButtonNavigationItem)
+            items.push(this._createLocalResourceOverrideButtonNavigationItem);
+
+        items.push(this._prettyPrintButtonNavigationItem);
+
+        if (!this._showingLocalResourceOverride)
+            items.push(this._showTypesButtonNavigationItem, this._codeCoverageButtonNavigationItem);
+
+        return items;
     }
 
     get managesOwnIssues()
@@ -125,6 +165,7 @@ WI.TextResourceContentView = class TextResourceContentView extends WI.ResourceCo
 
         this.resource.removeEventListener(null, null, this);
         WI.debuggerManager.removeEventListener(null, null, this);
+        WI.networkManager.removeEventListener(null, null, this);
         WI.settings.showJavaScriptTypeInformation.removeEventListener(null, null, this);
         WI.settings.enableControlFlowProfiler.removeEventListener(null, null, this);
     }
@@ -202,11 +243,17 @@ WI.TextResourceContentView = class TextResourceContentView extends WI.ResourceCo
 
         this.removeLoadingIndicator();
 
+        if (this._localResourceOverrideBannerView)
+            this.addSubview(this._localResourceOverrideBannerView);
+
         this.addSubview(this._textEditor);
     }
 
     _contentDidPopulate(event)
     {
+        if (this._createLocalResourceOverrideButtonNavigationItem)
+            this._createLocalResourceOverrideButtonNavigationItem.enabled = WI.networkManager.canBeOverridden(this.resource);
+
         this._prettyPrintButtonNavigationItem.enabled = this._textEditor.canBeFormatted();
 
         this._showTypesButtonNavigationItem.enabled = this._textEditor.canShowTypeAnnotations();
@@ -216,6 +263,21 @@ WI.TextResourceContentView = class TextResourceContentView extends WI.ResourceCo
         this._codeCoverageButtonNavigationItem.activated = WI.settings.enableControlFlowProfiler.value;
     }
 
+    async _handleCreateLocalResourceOverride(event)
+    {
+        let localResourceOverride = await this.resource.createLocalResourceOverride(this._textEditor.string);
+        WI.networkManager.addLocalResourceOverride(localResourceOverride);
+        WI.showLocalResourceOverride(localResourceOverride);
+    }
+
+    _handleRemoveLocalResourceOverride(event)
+    {
+        console.assert(this._showingLocalResourceOverride);
+
+        let localResourceOverride = WI.networkManager.localResourceOverrideForURL(this.resource.url);
+        WI.networkManager.removeLocalResourceOverride(localResourceOverride);
+    }
+
     _togglePrettyPrint(event)
     {
         var activated = !this._prettyPrintButtonNavigationItem.activated;
@@ -258,6 +320,15 @@ WI.TextResourceContentView = class TextResourceContentView extends WI.ResourceCo
         this._prettyPrintButtonNavigationItem.enabled = this._textEditor.canBeFormatted();
     }
 
+    _handleLocalResourceOverrideChanged(event)
+    {
+        if (this.resource.url !== event.data.localResourceOverride.url)
+            return;
+
+        if (this._createLocalResourceOverrideButtonNavigationItem)
+            this._createLocalResourceOverrideButtonNavigationItem.enabled = WI.networkManager.canBeOverridden(this.resource);
+    }
+
     _sourceCodeContentDidChange(event)
     {
         if (this._ignoreSourceCodeContentDidChangeEvent)
@@ -270,7 +341,7 @@ WI.TextResourceContentView = class TextResourceContentView extends WI.ResourceCo
     {
         this._ignoreSourceCodeContentDidChangeEvent = true;
         WI.branchManager.currentBranch.revisionForRepresentedObject(this.resource).content = this._textEditor.string;
-        delete this._ignoreSourceCodeContentDidChangeEvent;
+        this._ignoreSourceCodeContentDidChangeEvent = false;
     }
 
     _executionLineNumberDidChange(event)
@@ -303,6 +374,9 @@ WI.TextResourceContentView = class TextResourceContentView extends WI.ResourceCo
         if (this.resource.urlComponents.scheme === "file")
             return true;
 
+        if (this._showingLocalResourceOverride)
+            return true;
+
         return false;
     }
 };
index 5a09168c1660b2942afac2f4392008f83bd3ffb5..f4b39c5670b59b3ff6b5bd59361b8dca4a764a1d 100644 (file)
@@ -115,8 +115,7 @@ WI.TimelineDataGridNode = class TimelineDataGridNode extends WI.DataGridNode
 
         if (value instanceof WI.SourceCodeLocation) {
             if (value.sourceCode instanceof WI.Resource) {
-                cell.classList.add(WI.ResourceTreeElement.ResourceIconStyleClassName);
-                cell.classList.add(value.sourceCode.type);
+                cell.classList.add(WI.ResourceTreeElement.ResourceIconStyleClassName, ...WI.Resource.classNamesForResource(value.sourceCode));
             } else if (value.sourceCode instanceof WI.Script) {
                 if (value.sourceCode.url) {
                     cell.classList.add(WI.ResourceTreeElement.ResourceIconStyleClassName);
@@ -162,8 +161,7 @@ WI.TimelineDataGridNode = class TimelineDataGridNode extends WI.DataGridNode
                 if (isAnonymousFunction) {
                     // For anonymous functions we show the resource or script icon and name.
                     if (callFrame.sourceCodeLocation.sourceCode instanceof WI.Resource) {
-                        cell.classList.add(WI.ResourceTreeElement.ResourceIconStyleClassName);
-                        cell.classList.add(callFrame.sourceCodeLocation.sourceCode.type);
+                        cell.classList.add(WI.ResourceTreeElement.ResourceIconStyleClassName, ...WI.Resource.classNamesForResource(callFrame.sourceCodeLocation.sourceCode));
                     } else if (callFrame.sourceCodeLocation.sourceCode instanceof WI.Script) {
                         if (callFrame.sourceCodeLocation.sourceCode.url) {
                             cell.classList.add(WI.ResourceTreeElement.ResourceIconStyleClassName);
index c607ee39970aa77b6b799115f48c259f9a4d3fe1..89c09613a124ba41862213ce4bf49e763fc957d3 100644 (file)
@@ -37,7 +37,7 @@
 .popover .url-breakpoint-content > .editor-wrapper > .editor {
     width: 180px;
     -webkit-margin-start: 4px;
-    padding: 4px 0 2px 0;
+    padding: 4px 0 2px;
     -webkit-appearance: textfield;
     border: 1px solid hsl(0, 0%, 78%);
     background: var(--background-color-code);
index 3dd4cafab246adf785d2fb7e90872b1d181c3c11..da5230dcee59e5621aa2e75c837caadf99625449 100644 (file)
@@ -107,12 +107,8 @@ WI.URLBreakpointPopover = class URLBreakpointPopover extends WI.Popover
         });
 
         this._codeMirror.addKeyMap({
-            "Enter": () => {
-                this.dismiss();
-            },
-            "Esc": () => {
-                this.dismiss();
-            },
+            "Enter": () => { this.dismiss(); },
+            "Esc": () => { this.dismiss(); },
         });
 
         this._updateEditor();
index 404bb5c23ac6483c8381fe85ba2a1a0e3a479d01..f96960a34dba7310ed9555730a69467a8dd17215 100644 (file)
     --panel-background-color: hsl(0, 0%, 93%);
     --panel-background-color-light: hsl(0, 0%, 96%);
 
+    --warning-color: hsl(50, 100%, 94%);
     --warning-background-color: hsl(43, 97%, 84%);
     --warning-background-color-secondary: hsl(51, 87%, 93%);
+
     --error-background-color: hsl(11, 100%, 80%);
     --error-background-color-secondary: hsl(15, 100%, 90%);
 
+    --yellow-highlight-background-color: var(--warning-color);
+    --yellow-highlight-text-color: var(--text-color);
+
     --console-secondary-text-color: hsla(0, 0%, 0%, 0.33);
     --console-prompt-min-height: 30px;
 
index ee738f35b36612e8d5aedfe87dd8df9d23447ba7..683a7bd3a732fa5b19b5e7020dc286ef5e8ad313 100644 (file)
@@ -1,3 +1,37 @@
+2019-09-04  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: Local Overrides - Provide substitution content for resource loads (URL based)
+        https://bugs.webkit.org/show_bug.cgi?id=201262
+        <rdar://problem/13108764>
+
+        Reviewed by Devin Rousso.
+
+        * Sources.txt:
+        * WebKit.xcodeproj/project.pbxproj:
+        New sources.
+
+        * WebProcess/Network/WebResourceLoader.h:
+        * WebProcess/Network/WebResourceLoader.cpp:
+        (WebKit::WebResourceLoader::didReceiveResponse):
+        (WebKit::WebResourceLoader::didReceiveData):
+        (WebKit::WebResourceLoader::didFinishResourceLoad):
+        (WebKit::WebResourceLoader::didFailResourceLoad):
+        On receiving a response, check with the inspector if an active
+        frontend will override the response content.
+
+        * WebProcess/Network/WebResourceInterceptController.h:
+        * WebProcess/Network/WebResourceInterceptController.cpp:
+        (WebKit::WebResourceInterceptController::isIntercepting const):
+        (WebKit::WebResourceInterceptController::beginInterceptingResponse):
+        (WebKit::WebResourceInterceptController::continueResponse):
+        (WebKit::WebResourceInterceptController::interceptedResponse):
+        (WebKit::WebResourceInterceptController::defer):
+        Buffer networking callbacks for an ongoing intercept.
+
+        * WebProcess/Network/WebLoaderStrategy.cpp:
+        (WebKit::WebLoaderStrategy::havePerformedSecurityChecks const):
+        Handle new response source.
+
 2019-09-04  Chris Dumez  <cdumez@apple.com>
 
         Unreviewed minor follow-up fix after r249501 to address crashes in debug.
index 7814faae3a260b383fd910d180aa89d6c4994f78..313ec0af3b7f62bfc75ee9be8c8e9bf890485754 100644 (file)
@@ -487,6 +487,7 @@ WebProcess/MediaStream/UserMediaPermissionRequestManager.cpp
 
 WebProcess/Network/NetworkProcessConnection.cpp
 WebProcess/Network/WebLoaderStrategy.cpp
+WebProcess/Network/WebResourceInterceptController.cpp
 WebProcess/Network/WebResourceLoader.cpp
 WebProcess/Network/WebSocketChannel.cpp
 WebProcess/Network/WebSocketChannelManager.cpp
index 46736040ce1880781562eef4741db1753d5f6532..24a43fb71aec8563152093d10978d08261d90ce7 100644 (file)
                A55BA8261BA25CFD007CD33D /* RemoteWebInspectorProxyMessageReceiver.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A55BA8211BA25BB8007CD33D /* RemoteWebInspectorProxyMessageReceiver.cpp */; };
                A55BA82B1BA38E61007CD33D /* WebInspectorUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = A55BA8281BA38E1E007CD33D /* WebInspectorUtilities.h */; };
                A55BA8351BA3E70A007CD33D /* WebInspectorFrontendAPIDispatcher.h in Headers */ = {isa = PBXBuildFile; fileRef = A55BA8331BA3E6FA007CD33D /* WebInspectorFrontendAPIDispatcher.h */; };
+               A5860E71230F67FC00461AAE /* WebResourceInterceptController.h in Headers */ = {isa = PBXBuildFile; fileRef = A5860E70230F67DE00461AAE /* WebResourceInterceptController.h */; };
                A58B6F0818FCA733008CBA53 /* WKFileUploadPanel.h in Headers */ = {isa = PBXBuildFile; fileRef = A58B6F0618FCA733008CBA53 /* WKFileUploadPanel.h */; };
                A5C0F0A72000654D00536536 /* _WKNSWindowExtras.h in Headers */ = {isa = PBXBuildFile; fileRef = A5C0F0A62000654400536536 /* _WKNSWindowExtras.h */; settings = {ATTRIBUTES = (Private, ); }; };
                A5C0F0AB2000658200536536 /* _WKInspectorWindow.h in Headers */ = {isa = PBXBuildFile; fileRef = A5C0F0AA2000656E00536536 /* _WKInspectorWindow.h */; settings = {ATTRIBUTES = (Private, ); }; };
                A55BA8281BA38E1E007CD33D /* WebInspectorUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebInspectorUtilities.h; sourceTree = "<group>"; };
                A55BA8321BA3E6FA007CD33D /* WebInspectorFrontendAPIDispatcher.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WebInspectorFrontendAPIDispatcher.cpp; sourceTree = "<group>"; };
                A55BA8331BA3E6FA007CD33D /* WebInspectorFrontendAPIDispatcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebInspectorFrontendAPIDispatcher.h; sourceTree = "<group>"; };
+               A5860E6F230F67DE00461AAE /* WebResourceInterceptController.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = WebResourceInterceptController.cpp; path = Network/WebResourceInterceptController.cpp; sourceTree = "<group>"; };
+               A5860E70230F67DE00461AAE /* WebResourceInterceptController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = WebResourceInterceptController.h; path = Network/WebResourceInterceptController.h; sourceTree = "<group>"; };
                A58B6F0618FCA733008CBA53 /* WKFileUploadPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = WKFileUploadPanel.h; path = ios/forms/WKFileUploadPanel.h; sourceTree = "<group>"; };
                A58B6F0718FCA733008CBA53 /* WKFileUploadPanel.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = WKFileUploadPanel.mm; path = ios/forms/WKFileUploadPanel.mm; sourceTree = "<group>"; };
                A5C0F0A52000654400536536 /* _WKNSWindowExtras.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _WKNSWindowExtras.mm; sourceTree = "<group>"; };
                                51FB0902163A3B1C00EC324A /* NetworkProcessConnection.messages.in */,
                                51ABF65616392F1500132A7A /* WebLoaderStrategy.cpp */,
                                51ABF65716392F1500132A7A /* WebLoaderStrategy.h */,
+                               A5860E6F230F67DE00461AAE /* WebResourceInterceptController.cpp */,
+                               A5860E70230F67DE00461AAE /* WebResourceInterceptController.h */,
                                510AFFB716542048001BA05E /* WebResourceLoader.cpp */,
                                510AFFB816542048001BA05E /* WebResourceLoader.h */,
                                510AFFCE16542CBD001BA05E /* WebResourceLoader.messages.in */,
                                0F3C725B196F604E00AEDD0C /* WKInspectorHighlightView.h in Headers */,
                                A54293A4195A43DA002782C7 /* WKInspectorNodeSearchGestureRecognizer.h in Headers */,
                                6EE849C81368D9390038D481 /* WKInspectorPrivateMac.h in Headers */,
+                               A5860E71230F67FC00461AAE /* WebResourceInterceptController.h in Headers */,
                                994BADF41F7D781400B571E7 /* WKInspectorViewController.h in Headers */,
                                A518B5D21FE1D55B00F9FA28 /* WKInspectorWKWebView.h in Headers */,
                                2DD5E129210ADC7B00DB6012 /* WKKeyboardScrollingAnimator.h in Headers */,
index 2391d1b623df04e4205fdd347cab4cb73ac50fd2..034a663dc0d0ed645f85cd00f9b9bb46116b131b 100644 (file)
@@ -757,6 +757,7 @@ bool WebLoaderStrategy::havePerformedSecurityChecks(const ResourceResponse& resp
     case ResourceResponse::Source::MemoryCache:
     case ResourceResponse::Source::MemoryCacheAfterValidation:
     case ResourceResponse::Source::ServiceWorker:
+    case ResourceResponse::Source::InspectorOverride:
         return false;
     case ResourceResponse::Source::DiskCache:
     case ResourceResponse::Source::DiskCacheAfterValidation:
diff --git a/Source/WebKit/WebProcess/Network/WebResourceInterceptController.cpp b/Source/WebKit/WebProcess/Network/WebResourceInterceptController.cpp
new file mode 100644 (file)
index 0000000..d364e70
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "WebResourceInterceptController.h"
+
+namespace WebKit {
+
+bool WebResourceInterceptController::isIntercepting(unsigned long identifier) const
+{
+    return m_interceptedResponseQueue.contains(identifier);
+}
+
+void WebResourceInterceptController::beginInterceptingResponse(unsigned long identifier)
+{
+    m_interceptedResponseQueue.set(identifier, Deque<Function<void()>>());
+}
+
+void WebResourceInterceptController::continueResponse(unsigned long identifier)
+{
+    auto queue = m_interceptedResponseQueue.take(identifier);
+    for (auto& callback : queue)
+        callback();
+}
+
+void WebResourceInterceptController::interceptedResponse(unsigned long identifier)
+{
+    m_interceptedResponseQueue.remove(identifier);
+}
+
+void WebResourceInterceptController::defer(unsigned long identifier, Function<void()>&& function)
+{
+    ASSERT(isIntercepting(identifier));
+
+    auto iterator = m_interceptedResponseQueue.find(identifier);
+    if (iterator != m_interceptedResponseQueue.end())
+        iterator->value.append(WTFMove(function));
+}
+
+} // namespace WebKit
diff --git a/Source/WebKit/WebProcess/Network/WebResourceInterceptController.h b/Source/WebKit/WebProcess/Network/WebResourceInterceptController.h
new file mode 100644 (file)
index 0000000..d71b907
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <wtf/Deque.h>
+#include <wtf/Function.h>
+#include <wtf/HashMap.h>
+
+namespace WebKit {
+
+class WebResourceInterceptController {
+public:
+    bool isIntercepting(unsigned long identifier) const;
+
+    // Start intercepting a response.
+    void beginInterceptingResponse(unsigned long identifier);
+
+    // Stop intercepting a response. An intercept response was not supplied. Send deferred networking callbacks.
+    void continueResponse(unsigned long identifier);
+
+    // Stop intercepting a response. An intercept response was supplied. Send no deferred networking callbacks.
+    void interceptedResponse(unsigned long identifier);
+
+    void defer(unsigned long identifier, Function<void()>&&);
+
+private:
+    HashMap<unsigned long, Deque<Function<void()>>> m_interceptedResponseQueue;
+};
+
+} // namespace WebKit
index fa53925eb68f042fec79bf38860b2760325fb51c..0d189a6cc083400204e0888bdf6e3c458aa5be61 100644 (file)
@@ -39,6 +39,8 @@
 #include <WebCore/DiagnosticLoggingKeys.h>
 #include <WebCore/DocumentLoader.h>
 #include <WebCore/Frame.h>
+#include <WebCore/InspectorInstrumentationWebKit.h>
+#include <WebCore/NetworkLoadMetrics.h>
 #include <WebCore/Page.h>
 #include <WebCore/ResourceError.h>
 #include <WebCore/ResourceLoader.h>
@@ -139,6 +141,40 @@ void WebResourceLoader::didReceiveResponse(const ResourceResponse& response, boo
         };
     }
 
+    if (InspectorInstrumentationWebKit::shouldInterceptResponse(m_coreLoader->frame(), response)) {
+        unsigned long interceptedRequestIdentifier = m_coreLoader->identifier();
+        m_interceptController.beginInterceptingResponse(interceptedRequestIdentifier);
+        InspectorInstrumentationWebKit::interceptResponse(m_coreLoader->frame(), response, interceptedRequestIdentifier, [this, interceptedRequestIdentifier, policyDecisionCompletionHandler = WTFMove(policyDecisionCompletionHandler)](const ResourceResponse& inspectorResponse, RefPtr<SharedBuffer> overrideData) mutable {
+            if (!m_coreLoader || !m_coreLoader->identifier()) {
+                RELEASE_LOG_IF_ALLOWED("didReceiveResponse: not continuing intercept load because no coreLoader or no ID (pageID = %" PRIu64 ", frameID = %" PRIu64 ", resourceID = %" PRIu64 ")", m_trackingParameters.pageID.toUInt64(), m_trackingParameters.frameID.toUInt64(), m_trackingParameters.resourceID);
+                m_interceptController.continueResponse(interceptedRequestIdentifier);
+                return;
+            }
+
+            m_coreLoader->didReceiveResponse(inspectorResponse, [this, interceptedRequestIdentifier, policyDecisionCompletionHandler = WTFMove(policyDecisionCompletionHandler), overrideData = WTFMove(overrideData)]() mutable {
+                if (policyDecisionCompletionHandler)
+                    policyDecisionCompletionHandler();
+
+                if (!m_coreLoader || !m_coreLoader->identifier()) {
+                    m_interceptController.continueResponse(interceptedRequestIdentifier);
+                    return;
+                }
+
+                RefPtr<WebCore::ResourceLoader> protectedCoreLoader = m_coreLoader;
+                if (!overrideData)
+                    m_interceptController.continueResponse(interceptedRequestIdentifier);
+                else {
+                    m_interceptController.interceptedResponse(interceptedRequestIdentifier);
+                    if (unsigned bufferSize = overrideData->size())
+                        protectedCoreLoader->didReceiveBuffer(overrideData.releaseNonNull(), bufferSize, DataPayloadWholeResource);
+                    WebCore::NetworkLoadMetrics emptyMetrics;
+                    protectedCoreLoader->didFinishLoading(emptyMetrics);
+                }
+            });
+        });
+        return;
+    }
+
     m_coreLoader->didReceiveResponse(response, WTFMove(policyDecisionCompletionHandler));
 }
 
@@ -147,6 +183,13 @@ void WebResourceLoader::didReceiveData(const IPC::DataReference& data, int64_t e
     LOG(Network, "(WebProcess) WebResourceLoader::didReceiveData of size %lu for '%s'", data.size(), m_coreLoader->url().string().latin1().data());
     ASSERT_WITH_MESSAGE(!m_isProcessingNetworkResponse, "Network process should not send data until we've validated the response");
 
+    if (UNLIKELY(m_interceptController.isIntercepting(m_coreLoader->identifier()))) {
+        m_interceptController.defer(m_coreLoader->identifier(), [this, protectedThis = makeRef(*this), data, encodedDataLength]() mutable {
+            didReceiveData(data, encodedDataLength);
+        });
+        return;
+    }
+
     if (!m_numBytesReceived) {
         RELEASE_LOG_IF_ALLOWED("didReceiveData: Started receiving data (pageID = %" PRIu64 ", frameID = %" PRIu64 ", resourceID = %" PRIu64 ")", m_trackingParameters.pageID.toUInt64(), m_trackingParameters.frameID.toUInt64(), m_trackingParameters.resourceID);
     }
@@ -160,6 +203,13 @@ void WebResourceLoader::didFinishResourceLoad(const NetworkLoadMetrics& networkL
     LOG(Network, "(WebProcess) WebResourceLoader::didFinishResourceLoad for '%s'", m_coreLoader->url().string().latin1().data());
     RELEASE_LOG_IF_ALLOWED("didFinishResourceLoad: (pageID = %" PRIu64 ", frameID = %" PRIu64 ", resourceID = %" PRIu64 ", length = %zd)", m_trackingParameters.pageID.toUInt64(), m_trackingParameters.frameID.toUInt64(), m_trackingParameters.resourceID, m_numBytesReceived);
 
+    if (UNLIKELY(m_interceptController.isIntercepting(m_coreLoader->identifier()))) {
+        m_interceptController.defer(m_coreLoader->identifier(), [this, protectedThis = makeRef(*this), networkLoadMetrics]() mutable {
+            didFinishResourceLoad(networkLoadMetrics);
+        });
+        return;
+    }
+
     ASSERT_WITH_MESSAGE(!m_isProcessingNetworkResponse, "Load should not be able to finish before we've validated the response");
     m_coreLoader->didFinishLoading(networkLoadMetrics);
 }
@@ -169,6 +219,13 @@ void WebResourceLoader::didFailResourceLoad(const ResourceError& error)
     LOG(Network, "(WebProcess) WebResourceLoader::didFailResourceLoad for '%s'", m_coreLoader->url().string().latin1().data());
     RELEASE_LOG_IF_ALLOWED("didFailResourceLoad: (pageID = %" PRIu64 ", frameID = %" PRIu64 ", resourceID = %" PRIu64 ")", m_trackingParameters.pageID.toUInt64(), m_trackingParameters.frameID.toUInt64(), m_trackingParameters.resourceID);
 
+    if (UNLIKELY(m_interceptController.isIntercepting(m_coreLoader->identifier()))) {
+        m_interceptController.defer(m_coreLoader->identifier(), [this, protectedThis = makeRef(*this), error]() mutable {
+            didFailResourceLoad(error);
+        });
+        return;
+    }
+
     ASSERT_WITH_MESSAGE(!m_isProcessingNetworkResponse, "Load should not be able to finish before we've validated the response");
 
     if (m_coreLoader->documentLoader()->applicationCacheHost().maybeLoadFallbackForError(m_coreLoader.get(), error))
index 3ce15200901b06c10bbbb89fc4b9eb2a8fb42a9f..8ea1aedbeb9f596497ab674af4362167a1cf29aa 100644 (file)
@@ -29,6 +29,7 @@
 #include "MessageSender.h"
 #include "ShareableResource.h"
 #include "WebPageProxyIdentifier.h"
+#include "WebResourceInterceptController.h"
 #include <WebCore/FrameIdentifier.h>
 #include <WebCore/PageIdentifier.h>
 #include <wtf/RefCounted.h>
@@ -82,7 +83,6 @@ private:
     void didSendData(uint64_t bytesSent, uint64_t totalBytesToBeSent);
     void didReceiveResponse(const WebCore::ResourceResponse&, bool needsContinueDidReceiveResponseMessage);
     void didReceiveData(const IPC::DataReference&, int64_t encodedDataLength);
-    void didRetrieveDerivedData(const String& type, const IPC::DataReference&);
     void didFinishResourceLoad(const WebCore::NetworkLoadMetrics&);
     void didFailResourceLoad(const WebCore::ResourceError&);
     void didBlockAuthenticationChallenge();
@@ -95,6 +95,7 @@ private:
 
     RefPtr<WebCore::ResourceLoader> m_coreLoader;
     TrackingParameters m_trackingParameters;
+    WebResourceInterceptController m_interceptController;
     size_t m_numBytesReceived { 0 };
 
 #if !ASSERT_DISABLED