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
+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
--- /dev/null
+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.
+
--- /dev/null
+<!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>
--- /dev/null
+ALERT: ORIGINAL HTML CONTENT
+ALERT: REPLACED HTML CONTENT
+Overridden page content
--- /dev/null
+<!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>
--- /dev/null
+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
+
--- /dev/null
+<!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 <script> load.</p>
+<script src="resources/override.js"></script>
+</body>
+</html>
--- /dev/null
+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)
+
--- /dev/null
+<!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>
--- /dev/null
+alert("DEFAULT override.js TEXT");
--- /dev/null
+default override.txt content
}
function test() {
- ProtocolTest.debug();
-
let documentNode = null;
let pseudoElement = null;
--- /dev/null
+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.
+
--- /dev/null
+<!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>
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
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'.
}
testInvalid("a");
+ testInvalid("__WebInspectorInternal__");
+ testInvalid("__WebTest__");
testInvalid("/http://example.com");
testValid("http://example.com", {
},
});
+ 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() {
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 ]
+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().
import glob
import json
import os
+import re
import sys
if len(sys.argv) < 2:
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:
{ "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.",
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))
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))
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)
+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
inspector/InspectorFrontendClient.h
inspector/InspectorFrontendClientLocal.h
inspector/InspectorFrontendHost.h
+ inspector/InspectorInstrumentationPublic.h
+ inspector/InspectorInstrumentationWebKit.h
inspector/InspectorOverlay.h
inspector/InspectorWebAgentBase.h
inspector/PageScriptDebugServer.h
inspector/InspectorHistory.cpp
inspector/InspectorInstrumentation.cpp
inspector/InspectorInstrumentationCookie.cpp
+inspector/InspectorInstrumentationPublic.cpp
+inspector/InspectorInstrumentationWebKit.cpp
inspector/InspectorNodeFinder.cpp
inspector/InspectorOverlay.cpp
inspector/InspectorShaderProgram.cpp
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 */,
static HashSet<InstrumentingAgents*>* s_instrumentingAgentsSet = nullptr;
}
-int InspectorInstrumentation::s_frontendCounter = 0;
-
void InspectorInstrumentation::firstFrontendCreated()
{
platformStrategies()->loaderStrategy()->setCaptureExtraNetworkLoadMetricsEnabled(true);
}
}
+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)
{
#include "HitTestResult.h"
#include "InspectorController.h"
#include "InspectorInstrumentationCookie.h"
+#include "InspectorInstrumentationPublic.h"
#include "OffscreenCanvas.h"
#include "Page.h"
#include "StorageArea.h"
#include "WorkerInspectorController.h"
#include <JavaScriptCore/JSCInlines.h>
#include <initializer_list>
+#include <wtf/CompletionHandler.h>
#include <wtf/MemoryPressureHandler.h>
#include <wtf/RefPtr.h>
class ScriptExecutionContext;
class SecurityOrigin;
class ShadowRoot;
+class SharedBuffer;
class TimerBase;
#if ENABLE(WEBGL)
class WebGLProgram;
struct WebSocketFrame;
-#define FAST_RETURN_IF_NO_FRONTENDS(value) if (LIKELY(!InspectorInstrumentation::hasFrontends())) return value;
-
class InspectorInstrumentation {
public:
static void didClearWindowObjectInWorld(Frame&, DOMWrapperWorld&);
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>);
static void frontendCreated();
static void frontendDeleted();
- static bool hasFrontends() { return s_frontendCounter; }
+ static bool hasFrontends() { return InspectorInstrumentationPublic::hasFrontends(); }
static void firstFrontendCreated();
static void lastFrontendDeleted();
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);
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&);
static InstrumentingAgents* instrumentingAgentsForWorkerGlobalScope(WorkerGlobalScope*);
static InspectorTimelineAgent* retrieveTimelineAgent(const InspectorInstrumentationCookie&);
-
- WEBCORE_EXPORT static int s_frontendCounter;
};
inline void InspectorInstrumentation::didClearWindowObjectInWorld(Frame& frame, DOMWrapperWorld& world)
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());
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());
}
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();
}
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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;
+};
+
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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));
+}
+
+}
/*
* 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
#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>
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();
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) {
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/")
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();
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>>&);
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);
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
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();
case ResourceResponse::Source::MemoryCache:
case ResourceResponse::Source::MemoryCacheAfterValidation:
case ResourceResponse::Source::ApplicationCache:
+ case ResourceResponse::Source::InspectorOverride:
case ResourceResponse::Source::Unknown:
return;
}
bool reachedTerminalState() const { return m_reachedTerminalState; }
-
const ResourceRequest& request() const { return m_request; }
void setRequest(ResourceRequest&& request) { m_request = WTFMove(request); }
#include "HTMLElement.h"
#include "HTMLFrameOwnerElement.h"
#include "HTTPHeaderField.h"
+#include "InspectorInstrumentation.h"
#include "LoaderStrategy.h"
#include "LocalizedStrings.h"
#include "Logging.h"
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();
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());
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);
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:
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)
{
return "Memory cache after validation";
case ResourceResponse::Source::ApplicationCache:
return "Application cache";
+ case ResourceResponse::Source::InspectorOverride:
+ return "Inspector override";
}
ASSERT_NOT_REACHED();
return "Error";
+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
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)";
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";
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";
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)";
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";
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";
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";
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";
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";
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";
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";
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";
--- /dev/null
+<!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>
--- /dev/null
+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;
+}
--- /dev/null
+/*
+ * 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";
+ }
+};
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);
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) => {
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"}),
};
return result;
}
+ // Internal sourceURLs will fail in URL constructor anyways.
+ if (isWebKitInternalScript(url))
+ return result;
+
let parsed = null;
try {
parsed = new URL(url);
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.
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;
return "Disk Cache";
case WI.Resource.ResponseSource.ServiceWorker:
return "Service Worker";
+ case WI.Resource.ResponseSource.InspectorOverride:
+ return "Inspector Override";
}
console.assert();
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)
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
WI.consoleLogViewController.appendConsoleMessage(consoleMessage);
}
+ static supportsLocalResourceOverrides()
+ {
+ return window.NetworkAgent && InspectorBackend.domains.Network && InspectorBackend.domains.Network.setInterceptionEnabled;
+ }
+
// Target
initializeTarget(target)
// 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)
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;
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)
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);
}
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)
}
}
+ _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)
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",
};
--- /dev/null
+<?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>
<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>
// 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");
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
});
}
+ // 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;
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()
--- /dev/null
+/*
+ * 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",
+};
}
}
- 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)
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;
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);
return this._parentFrame ? this._parentFrame.mainResource === this : false;
}
+ get isLocalResourceOverride()
+ {
+ return false;
+ }
+
addInitiatedResource(resource)
{
if (!(resource instanceof WI.Resource))
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) {
MemoryCache: Symbol("memory-cache"),
DiskCache: Symbol("disk-cache"),
ServiceWorker: Symbol("service-worker"),
+ InspectorOverride: Symbol("inspector-override"),
};
WI.Resource.NetworkPriority = {
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,
{
// FIXME: Not implemented.
}
+
+ responseIntercepted(requestId, response)
+ {
+ WI.networkManager.responseIntercepted(this.target, requestId, response);
+ }
};
<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>
}
.popover .edit-breakpoint-popover-content > table > tr > th {
- width: 1px; /* Shrink to fit. */
font-weight: bold;
line-height: 23px;
text-align: end;
.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);
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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");
}
.console-warning-level {
- background-color: hsl(50, 100%, 94%);
+ background-color: var(--warning-color);
border-color: hsl(40, 100%, 90%);
}
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);
}
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);
if (representedObject instanceof WI.SourceCodeSearchMatchObject)
return representedObject.sourceCode;
+ if (representedObject instanceof WI.LocalResourceOverride)
+ return representedObject.localResource;
+
return representedObject;
}
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)
_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);
+ }
}
};
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());
InspectorFrontendHost.copyText(sourceCode.stringifyHTTPResponse());
});
}
-
- contextMenu.appendSeparator();
}
}
+ contextMenu.appendSeparator();
+
contextMenu.appendItem(WI.UIString("Save File"), () => {
sourceCode.requestContent().then(() => {
const forceSaveAs = true;
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"), () => {
});
}
}
+
if (!WI.isShowingNetworkTab()) {
contextMenu.appendItem(WI.UIString("Reveal in Network Tab"), () => {
showResourceWithOptions({preferredTabType: WI.NetworkTabContentView.Type});
}
}
+ startEditingNode(node)
+ {
+ console.assert(this._editCallback);
+ if (this._editing || this._editingNode)
+ return;
+
+ this._startEditingNodeAtColumnIndex(node, 0);
+ }
+
_updateScrollListeners()
{
if (this._inline || this._variableHeightRows) {
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;
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"];
}
}
- 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));
}
this._hidden = false;
this._selected = false;
this._copyable = true;
+ this._editable = true;
this._shouldRefreshChildren = true;
this._data = data || {};
this.hasChildren = hasChildren || false;
this._copyable = x;
}
+ get editable()
+ {
+ return this._editable;
+ }
+
+ set editable(x)
+ {
+ this._editable = x;
+ }
+
get element()
{
if (this._element)
.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);
- }
-}
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;
.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;
--- /dev/null
+/*
+ * 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%);
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+};
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+};
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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();
+ }
+};
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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();
+ }
+};
// 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);
}
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);
}
cell.title = resource.url;
- cell.classList.add(WI.Resource.classNameForResource(resource));
+ cell.classList.add(...WI.Resource.classNamesForResource(resource));
}
_populateDomainCell(cell, entry)
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);
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)
transferSizeB = -10;
else if (sourceB === WI.Resource.ResponseSource.ServiceWorker)
transferSizeB = -5;
+ else if (sourceB === WI.Resource.ResponseSource.InspectorOverride)
+ transferSizeB = -3;
return transferSizeA - transferSizeB;
};
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;
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);
}
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;
iconClassNames()
{
- return [WI.ResourceTreeElement.ResourceIconStyleClassName, this.resource.type];
+ return [WI.ResourceTreeElement.ResourceIconStyleClassName, ...WI.Resource.classNamesForResource(this.resource)];
}
appendContextMenuItems(contextMenu)
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;
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);
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
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)
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();
this.callFirstAncestorFunction("descendantResourceTreeElementTypeDidChange", [this, event.data.oldType]);
}
+
+ _responseReceived(event)
+ {
+ this._updateIcon();
+ }
};
WI.ResourceTreeElement.ResourceIconStyleClassName = "resource-icon";
.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;
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);
}
.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);
- }
-}
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
}
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;
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);
- }
-}
-
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;
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);
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);
{
// 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;
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;
// 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)
{
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;
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
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)
this._addResourcesRecursivelyForFrame(frame);
}
+ _handleLocalResourceOverrideAdded(event)
+ {
+ this._addLocalResourceOverride(event.data.localResourceOverride);
+ }
+
+ _handleLocalResourceOverrideRemoved(event)
+ {
+ this._removeLocalResourceOverride(event.data.localResourceOverride);
+ }
+
_handleDebuggerBreakpointAdded(event)
{
this._addBreakpoint(event.data.breakpoint);
|| representedObject instanceof WI.ScriptCollection
|| representedObject instanceof WI.CSSStyleSheet
|| representedObject instanceof WI.CSSStyleSheetCollection
+ || representedObject instanceof WI.LocalResourceOverride
|| super.canShowRepresentedObject(representedObject);
}
.text-editor {
position: relative;
-
+ width: 100%;
+ height: 100%;
overflow: hidden;
}
* 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%;
}
{
constructor(resource)
{
+ console.assert(resource instanceof WI.Resource || resource instanceof WI.CSSStyleSheet);
+
super(resource, "text");
resource.addEventListener(WI.SourceCode.Event.ContentDidChange, this._sourceCodeContentDidChange, this);
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);
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()
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);
}
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();
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;
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)
{
this._ignoreSourceCodeContentDidChangeEvent = true;
WI.branchManager.currentBranch.revisionForRepresentedObject(this.resource).content = this._textEditor.string;
- delete this._ignoreSourceCodeContentDidChangeEvent;
+ this._ignoreSourceCodeContentDidChangeEvent = false;
}
_executionLineNumberDidChange(event)
if (this.resource.urlComponents.scheme === "file")
return true;
+ if (this._showingLocalResourceOverride)
+ return true;
+
return false;
}
};
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);
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);
.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);
});
this._codeMirror.addKeyMap({
- "Enter": () => {
- this.dismiss();
- },
- "Esc": () => {
- this.dismiss();
- },
+ "Enter": () => { this.dismiss(); },
+ "Esc": () => { this.dismiss(); },
});
this._updateEditor();
--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;
+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.
WebProcess/Network/NetworkProcessConnection.cpp
WebProcess/Network/WebLoaderStrategy.cpp
+WebProcess/Network/WebResourceInterceptController.cpp
WebProcess/Network/WebResourceLoader.cpp
WebProcess/Network/WebSocketChannel.cpp
WebProcess/Network/WebSocketChannelManager.cpp
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 */,
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:
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
#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>
};
}
+ 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));
}
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);
}
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);
}
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))
#include "MessageSender.h"
#include "ShareableResource.h"
#include "WebPageProxyIdentifier.h"
+#include "WebResourceInterceptController.h"
#include <WebCore/FrameIdentifier.h>
#include <WebCore/PageIdentifier.h>
#include <wtf/RefCounted.h>
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();
RefPtr<WebCore::ResourceLoader> m_coreLoader;
TrackingParameters m_trackingParameters;
+ WebResourceInterceptController m_interceptController;
size_t m_numBytesReceived { 0 };
#if !ASSERT_DISABLED