| /* |
| * Copyright (C) 2013-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. |
| */ |
| |
| // FIXME: NetworkManager lacks advanced multi-target support. (Network.loadResource invocations per-target) |
| |
| WI.NetworkManager = class NetworkManager extends WI.Object |
| { |
| constructor() |
| { |
| super(); |
| |
| this._frameIdentifierMap = new Map; |
| this._mainFrame = null; |
| this._resourceRequestIdentifierMap = new Map; |
| this._orphanedResources = new Map; |
| this._webSocketIdentifierToURL = new Map; |
| |
| this._waitingForMainFrameResourceTreePayload = true; |
| this._transitioningPageTarget = false; |
| |
| this._sourceMapURLMap = new Map; |
| this._downloadingSourceMaps = new Set; |
| |
| this._localResourceOverrides = []; |
| this._harImportLocalResourceMap = new Set; |
| |
| this._pendingLocalResourceOverrideSaves = null; |
| this._saveLocalResourceOverridesDebouncer = null; |
| |
| // FIXME: Provide dedicated UI to toggle Network Interception globally? |
| this._interceptionEnabled = true; |
| |
| // COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type |
| WI.notifications.addEventListener(WI.Notification.ExtraDomainsActivated, this._extraDomainsActivated, this); |
| |
| WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._handleFrameMainResourceDidChange, this); |
| |
| if (NetworkManager.supportsOverridingResponses()) { |
| WI.Resource.addEventListener(WI.SourceCode.Event.ContentDidChange, this._handleResourceContentChangedForLocalResourceOverride, this); |
| WI.Resource.addEventListener(WI.Resource.Event.RequestDataDidChange, this._handleResourceContentChangedForLocalResourceOverride, this); |
| WI.LocalResourceOverride.addEventListener(WI.LocalResourceOverride.Event.DisabledChanged, this._handleResourceOverrideDisabledChanged, this); |
| |
| WI.Target.registerInitializationPromise((async () => { |
| let serializedLocalResourceOverrides = await WI.objectStores.localResourceOverrides.getAll(); |
| |
| this._restoringLocalResourceOverrides = true; |
| for (let serializedLocalResourceOverride of serializedLocalResourceOverrides) { |
| let localResourceOverride = WI.LocalResourceOverride.fromJSON(serializedLocalResourceOverride); |
| |
| let supported = false; |
| switch (localResourceOverride.type) { |
| case WI.LocalResourceOverride.InterceptType.Request: |
| supported = WI.NetworkManager.supportsOverridingRequests(); |
| break; |
| |
| case WI.LocalResourceOverride.InterceptType.Response: |
| supported = WI.NetworkManager.supportsOverridingResponses(); |
| break; |
| |
| case WI.LocalResourceOverride.InterceptType.ResponseSkippingNetwork: |
| supported = WI.NetworkManager.supportsOverridingRequestsWithResponses(); |
| break; |
| } |
| if (!supported) |
| continue; |
| |
| const key = null; |
| WI.objectStores.localResourceOverrides.associateObject(localResourceOverride, key, serializedLocalResourceOverride); |
| |
| this.addLocalResourceOverride(localResourceOverride); |
| } |
| this._restoringLocalResourceOverrides = false; |
| })()); |
| } |
| |
| this._bootstrapScript = null; |
| if (NetworkManager.supportsBootstrapScript()) { |
| this._bootstrapScriptEnabledSetting = new WI.Setting("bootstrap-script-enabled", true); |
| |
| WI.Target.registerInitializationPromise((async () => { |
| let bootstrapScriptSource = await WI.objectStores.general.get(NetworkManager.bootstrapScriptSourceObjectStoreKey); |
| if (bootstrapScriptSource !== undefined) |
| this.createBootstrapScript(bootstrapScriptSource); |
| })()); |
| } |
| } |
| |
| // Static |
| |
| static supportsShowCertificate() |
| { |
| return InspectorFrontendHost.supportsShowCertificate |
| && InspectorBackend.hasCommand("Network.getSerializedCertificate"); |
| } |
| |
| static supportsOverridingRequests() |
| { |
| // COMPATIBILITY (iOS 13.4): Network.interceptWithRequest did not exist yet. |
| return InspectorBackend.hasCommand("Network.interceptWithRequest"); |
| } |
| |
| static supportsOverridingRequestsWithResponses() |
| { |
| // COMPATIBILITY (iOS 13.4): Network.interceptRequestWithResponse did not exist yet. |
| return InspectorBackend.hasCommand("Network.interceptRequestWithResponse"); |
| } |
| |
| static supportsOverridingResponses() |
| { |
| // COMPATIBILITY (iOS 13.0): Network.interceptWithResponse did not exist yet. |
| return InspectorBackend.hasCommand("Network.interceptWithResponse"); |
| } |
| |
| static supportsBootstrapScript() |
| { |
| return InspectorBackend.hasCommand("Page.setBootstrapScript"); |
| } |
| |
| static get bootstrapScriptURL() |
| { |
| return "web-inspector://bootstrap.js"; |
| } |
| |
| static get bootstrapScriptSourceObjectStoreKey() |
| { |
| return "bootstrap-script-source"; |
| } |
| |
| static synthesizeImportError(message) |
| { |
| message = WI.UIString("HAR Import Error: %s").format(message); |
| |
| if (window.InspectorTest) { |
| console.error(message); |
| return; |
| } |
| |
| let consoleMessage = new WI.ConsoleMessage(WI.mainTarget, WI.ConsoleMessage.MessageSource.Other, WI.ConsoleMessage.MessageLevel.Error, message); |
| consoleMessage.shouldRevealConsole = true; |
| |
| WI.consoleLogViewController.appendConsoleMessage(consoleMessage); |
| } |
| |
| // Target |
| |
| initializeTarget(target) |
| { |
| if (target.hasDomain("Page")) { |
| target.PageAgent.enable(); |
| target.PageAgent.getResourceTree(this._processMainFrameResourceTreePayload.bind(this)); |
| |
| // COMPATIBILITY (iOS 13.0): Page.setBootstrapScript did not exist yet. |
| if (target.hasCommand("Page.setBootstrapScript") && this._bootstrapScript && this._bootstrapScriptEnabledSetting.value) |
| target.PageAgent.setBootstrapScript(this._bootstrapScript.content); |
| } |
| |
| if (target.hasDomain("ServiceWorker")) |
| target.ServiceWorkerAgent.getInitializationInfo(this._processServiceWorkerConfiguration.bind(this)); |
| |
| if (target.hasDomain("Network")) { |
| target.NetworkAgent.enable(); |
| |
| // COMPATIBILITY (iOS 10.3): Network.setDisableResourceCaching did not exist. |
| if (target.hasCommand("Network.setResourceCachingDisabled")) |
| target.NetworkAgent.setResourceCachingDisabled(WI.settings.resourceCachingDisabled.value); |
| |
| // COMPATIBILITY (iOS 13.0): Network.setInterceptionEnabled did not exist. |
| if (target.hasCommand("Network.setInterceptionEnabled")) { |
| if (this._interceptionEnabled) |
| target.NetworkAgent.setInterceptionEnabled(this._interceptionEnabled); |
| |
| for (let localResourceOverride of this._localResourceOverrides) { |
| if (!localResourceOverride.disabled) |
| this._addInterception(localResourceOverride, target); |
| } |
| } |
| } |
| |
| if (target.type === WI.TargetType.Worker) |
| this.adoptOrphanedResourcesForTarget(target); |
| } |
| |
| transitionPageTarget() |
| { |
| this._transitioningPageTarget = true; |
| this._waitingForMainFrameResourceTreePayload = true; |
| } |
| |
| // Public |
| |
| get mainFrame() { return this._mainFrame; } |
| get localResourceOverrides() { return this._localResourceOverrides; } |
| get bootstrapScript() { return this._bootstrapScript; } |
| |
| get frames() |
| { |
| return Array.from(this._frameIdentifierMap.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.hasCommand("Network.setInterceptionEnabled")) |
| target.NetworkAgent.setInterceptionEnabled(this._interceptionEnabled); |
| } |
| } |
| |
| frameForIdentifier(frameId) |
| { |
| return this._frameIdentifierMap.get(frameId) || null; |
| } |
| |
| resourceForRequestIdentifier(requestIdentifier) |
| { |
| return this._resourceRequestIdentifierMap.get(requestIdentifier) || null; |
| } |
| |
| downloadSourceMap(sourceMapURL, baseURL, originalSourceCode) |
| { |
| if (!WI.settings.sourceMapsEnabled.value) |
| return; |
| |
| // The baseURL could have come from a "//# sourceURL". Attempt to get a |
| // reasonable absolute URL for the base by using the main resource's URL. |
| if (WI.networkManager.mainFrame) |
| baseURL = absoluteURL(baseURL, WI.networkManager.mainFrame.url); |
| |
| if (sourceMapURL.startsWith("data:")) { |
| this._loadAndParseSourceMap(sourceMapURL, baseURL, originalSourceCode); |
| return; |
| } |
| |
| sourceMapURL = absoluteURL(sourceMapURL, baseURL); |
| if (!sourceMapURL) |
| return; |
| |
| console.assert(originalSourceCode.url); |
| if (!originalSourceCode.url) |
| return; |
| |
| // FIXME: <rdar://problem/13265694> Source Maps: Better handle when multiple resources reference the same SourceMap |
| |
| if (this._sourceMapURLMap.has(sourceMapURL) || this._downloadingSourceMaps.has(sourceMapURL)) |
| return; |
| |
| let loadAndParseSourceMap = () => { |
| this._loadAndParseSourceMap(sourceMapURL, baseURL, originalSourceCode); |
| }; |
| |
| if (!WI.networkManager.mainFrame) { |
| // If we don't have a main frame, then we are likely in the middle of building the resource tree. |
| // Delaying until the next runloop is enough in this case to then start loading the source map. |
| setTimeout(loadAndParseSourceMap, 0); |
| return; |
| } |
| |
| loadAndParseSourceMap(); |
| } |
| |
| get bootstrapScriptEnabled() |
| { |
| console.assert(NetworkManager.supportsBootstrapScript()); |
| console.assert(this._bootstrapScript); |
| |
| return this._bootstrapScriptEnabledSetting.value; |
| } |
| |
| set bootstrapScriptEnabled(enabled) |
| { |
| console.assert(NetworkManager.supportsBootstrapScript()); |
| console.assert(this._bootstrapScript); |
| |
| this._bootstrapScriptEnabledSetting.value = !!enabled; |
| |
| let source = this._bootstrapScriptEnabledSetting.value ? this._bootstrapScript.content : undefined; |
| |
| // COMPATIBILITY (iOS 13.0): Page.setBootstrapScript did not exist yet. |
| for (let target of WI.targets) { |
| if (target.hasCommand("Page.setBootstrapScript")) |
| target.PageAgent.setBootstrapScript(source); |
| } |
| |
| this.dispatchEventToListeners(NetworkManager.Event.BootstrapScriptEnabledChanged, {bootstrapScript: this._bootstrapScript}); |
| } |
| |
| async createBootstrapScript(source) |
| { |
| console.assert(NetworkManager.supportsBootstrapScript()); |
| |
| if (this._bootstrapScript) |
| return; |
| |
| if (!arguments.length) |
| source = await WI.objectStores.general.get(NetworkManager.bootstrapScriptSourceObjectStoreKey); |
| |
| if (!source) { |
| source = ` |
| /* |
| * ${WI.UIString("The Inspector Bootstrap Script is guaranteed to be the first script evaluated in any page, as well as any sub-frames.")} |
| * ${WI.UIString("It is evaluated immediately after the global object is created, before any other content has loaded.")} |
| * |
| * ${WI.UIString("Modifications made here will take effect on the next load of any page or sub-frame.")} |
| * ${WI.UIString("The contents and enabled state will be preserved across Web Inspector sessions.")} |
| * |
| * ${WI.UIString("Some examples of ways to use this script include (but are not limited to):")} |
| * - ${WI.UIString("overriding built-in functions to log call traces or add %s statements").format(WI.unlocalizedString("`debugger`"))} |
| * - ${WI.UIString("ensuring that common debugging functions are available on every page via the Console")} |
| * |
| * ${WI.UIString("More information is available at <https://webkit.org/web-inspector/inspector-bootstrap-script/>.")} |
| */ |
| `.trimStart(); |
| } |
| |
| const target = null; |
| const url = null; |
| const sourceURL = NetworkManager.bootstrapScriptURL; |
| this._bootstrapScript = new WI.LocalScript(target, url, sourceURL, WI.Script.SourceType.Program, source, {injected: true, editable: true}); |
| this._bootstrapScript.addEventListener(WI.SourceCode.Event.ContentDidChange, this._handleBootstrapScriptContentDidChange, this); |
| this._handleBootstrapScriptContentDidChange(); |
| |
| this.dispatchEventToListeners(NetworkManager.Event.BootstrapScriptCreated, {bootstrapScript: this._bootstrapScript}); |
| } |
| |
| destroyBootstrapScript() |
| { |
| console.assert(NetworkManager.supportsBootstrapScript()); |
| |
| if (!this._bootstrapScript) |
| return; |
| |
| let bootstrapScript = this._bootstrapScript; |
| |
| this._bootstrapScript = null; |
| WI.objectStores.general.delete(NetworkManager.bootstrapScriptSourceObjectStoreKey); |
| |
| // COMPATIBILITY (iOS 13.0): Page.setBootstrapScript did not exist yet. |
| for (let target of WI.targets) { |
| if (target.hasCommand("Page.setBootstrapScript")) |
| target.PageAgent.setBootstrapScript(); |
| } |
| |
| this.dispatchEventToListeners(NetworkManager.Event.BootstrapScriptDestroyed, {bootstrapScript}); |
| } |
| |
| addLocalResourceOverride(localResourceOverride) |
| { |
| console.assert(localResourceOverride instanceof WI.LocalResourceOverride); |
| |
| console.assert(!this._localResourceOverrides.includes(localResourceOverride)); |
| this._localResourceOverrides.push(localResourceOverride); |
| |
| if (!this._restoringLocalResourceOverrides) |
| WI.objectStores.localResourceOverrides.putObject(localResourceOverride); |
| |
| if (!localResourceOverride.disabled) |
| this._addInterception(localResourceOverride); |
| |
| this.dispatchEventToListeners(WI.NetworkManager.Event.LocalResourceOverrideAdded, {localResourceOverride}); |
| } |
| |
| removeLocalResourceOverride(localResourceOverride) |
| { |
| console.assert(localResourceOverride instanceof WI.LocalResourceOverride); |
| |
| if (!this._localResourceOverrides.remove(localResourceOverride)) { |
| 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) |
| this._removeInterception(localResourceOverride); |
| |
| this.dispatchEventToListeners(WI.NetworkManager.Event.LocalResourceOverrideRemoved, {localResourceOverride}); |
| } |
| |
| localResourceOverridesForURL(url) |
| { |
| // Order local resource overrides based on how closely they match the given URL. As an example, |
| // a regular expression is likely going to match more URLs than a case-insensitive string. |
| const rankFunctions = [ |
| (localResourceOverride) => localResourceOverride.isCaseSensitive && !localResourceOverride.isRegex, // exact match |
| (localResourceOverride) => !localResourceOverride.isCaseSensitive && !localResourceOverride.isRegex, // case-insensitive |
| (localResourceOverride) => localResourceOverride.isCaseSensitive && localResourceOverride.isRegex, // case-sensitive regex |
| (localResourceOverride) => !localResourceOverride.isCaseSensitive && localResourceOverride.isRegex, // case-insensitive regex |
| ]; |
| return this._localResourceOverrides |
| .filter((localResourceOverride) => localResourceOverride.matches(url)) |
| .sort((a, b) => { |
| let aRank = rankFunctions.findIndex((rankFunction) => rankFunction(a)); |
| let bRank = rankFunctions.findIndex((rankFunction) => rankFunction(b)); |
| return aRank - bRank; |
| }); |
| } |
| |
| canBeOverridden(resource) |
| { |
| if (!(resource instanceof WI.Resource)) |
| return false; |
| |
| if (resource instanceof WI.SourceMapResource) |
| return false; |
| |
| if (resource.localResourceOverride) |
| return false; |
| |
| const schemes = ["http:", "https:", "file:"]; |
| if (!schemes.some((scheme) => resource.url.startsWith(scheme))) |
| return false; |
| |
| if (this.localResourceOverridesForURL(resource.url).length) |
| 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; |
| } |
| |
| resourcesForURL(url) |
| { |
| let resources = new Set; |
| if (this._mainFrame) { |
| if (this._mainFrame.mainResource.url === url) |
| resources.add(this._mainFrame.mainResource); |
| |
| const recursivelySearchChildFrames = true; |
| resources.addAll(this._mainFrame.resourcesForURL(url, recursivelySearchChildFrames)); |
| } |
| return resources; |
| } |
| |
| adoptOrphanedResourcesForTarget(target) |
| { |
| let resources = this._orphanedResources.take(target.identifier); |
| if (!resources) |
| return; |
| |
| for (let resource of resources) |
| target.adoptResource(resource); |
| } |
| |
| processHAR({json, error}) |
| { |
| if (error) { |
| WI.NetworkManager.synthesizeImportError(error); |
| return null; |
| } |
| |
| if (typeof json !== "object" || json === null) { |
| WI.NetworkManager.synthesizeImportError(WI.UIString("invalid JSON")); |
| return null; |
| } |
| |
| if (typeof json.log !== "object" || typeof json.log.version !== "string") { |
| WI.NetworkManager.synthesizeImportError(WI.UIString("invalid HAR")); |
| return null; |
| } |
| |
| if (json.log.version !== "1.2") { |
| WI.NetworkManager.synthesizeImportError(WI.UIString("unsupported HAR version")); |
| return null; |
| } |
| |
| if (!Array.isArray(json.log.entries) || !Array.isArray(json.log.pages) || !json.log.pages[0] || !json.log.pages[0].startedDateTime) { |
| WI.NetworkManager.synthesizeImportError(WI.UIString("invalid HAR")); |
| return null; |
| } |
| |
| let mainResourceSentWalltime = WI.HARBuilder.dateFromHARDate(json.log.pages[0].startedDateTime) / 1000; |
| if (isNaN(mainResourceSentWalltime)) { |
| WI.NetworkManager.synthesizeImportError(WI.UIString("invalid HAR")); |
| return null; |
| } |
| |
| let localResources = []; |
| |
| for (let entry of json.log.entries) { |
| let localResource = WI.LocalResource.fromHAREntry(entry, mainResourceSentWalltime); |
| this._harImportLocalResourceMap.add(localResource); |
| localResources.push(localResource); |
| } |
| |
| return localResources; |
| } |
| |
| // PageObserver |
| |
| frameDidNavigate(framePayload) |
| { |
| // Ignore this while waiting for the whole frame/resource tree. |
| if (this._waitingForMainFrameResourceTreePayload) |
| return; |
| |
| var frameWasLoadedInstantly = false; |
| |
| var frame = this.frameForIdentifier(framePayload.id); |
| if (!frame) { |
| // If the frame wasn't known before now, then the main resource was loaded instantly (about:blank, etc.) |
| // Make a new resource (which will make the frame). Mark will mark it as loaded at the end too since we |
| // don't expect any more events about the load finishing for these frames. |
| let resourceOptions = { |
| loaderIdentifier: framePayload.loaderId, |
| }; |
| let frameOptions = { |
| name: framePayload.name, |
| securityOrigin: framePayload.securityOrigin, |
| }; |
| let frameResource = this._addNewResourceToFrameOrTarget(framePayload.url, framePayload.id, resourceOptions, frameOptions); |
| frame = frameResource.parentFrame; |
| frameWasLoadedInstantly = true; |
| |
| console.assert(frame); |
| if (!frame) |
| return; |
| } |
| |
| if (framePayload.loaderId === frame.provisionalLoaderIdentifier) { |
| // There was a provisional load in progress, commit it. |
| frame.commitProvisionalLoad(framePayload.securityOrigin); |
| } else { |
| let mainResource = null; |
| if (frame.mainResource.url !== framePayload.url || frame.loaderIdentifier !== framePayload.loaderId) { |
| // Navigations like back/forward do not have provisional loads, so create a new main resource here. |
| mainResource = new WI.Resource(framePayload.url, { |
| mimeType: framePayload.mimeType, |
| loaderIdentifier: framePayload.loaderId, |
| }); |
| } else { |
| // The main resource is already correct, so reuse it. |
| mainResource = frame.mainResource; |
| } |
| |
| frame.initialize(framePayload.name, framePayload.securityOrigin, framePayload.loaderId, mainResource); |
| } |
| |
| var oldMainFrame = this._mainFrame; |
| |
| if (framePayload.parentId) { |
| var parentFrame = this.frameForIdentifier(framePayload.parentId); |
| console.assert(parentFrame); |
| |
| if (frame === this._mainFrame) |
| this._mainFrame = null; |
| |
| if (frame.parentFrame !== parentFrame) |
| parentFrame.addChildFrame(frame); |
| } else { |
| if (frame.parentFrame) |
| frame.parentFrame.removeChildFrame(frame); |
| this._mainFrame = frame; |
| } |
| |
| if (this._mainFrame !== oldMainFrame) |
| this._mainFrameDidChange(oldMainFrame); |
| |
| if (frameWasLoadedInstantly) |
| frame.mainResource.markAsFinished(); |
| } |
| |
| frameDidDetach(frameId) |
| { |
| // Ignore this while waiting for the whole frame/resource tree. |
| if (this._waitingForMainFrameResourceTreePayload) |
| return; |
| |
| var frame = this.frameForIdentifier(frameId); |
| if (!frame) |
| return; |
| |
| if (frame.parentFrame) |
| frame.parentFrame.removeChildFrame(frame); |
| |
| this._frameIdentifierMap.delete(frame.id); |
| |
| var oldMainFrame = this._mainFrame; |
| |
| if (frame === this._mainFrame) |
| this._mainFrame = null; |
| |
| frame.clearExecutionContexts(); |
| |
| this.dispatchEventToListeners(WI.NetworkManager.Event.FrameWasRemoved, {frame}); |
| |
| if (this._mainFrame !== oldMainFrame) |
| this._mainFrameDidChange(oldMainFrame); |
| } |
| |
| // NetworkObserver |
| |
| resourceRequestWillBeSent(requestIdentifier, frameIdentifier, loaderIdentifier, request, type, redirectResponse, timestamp, walltime, initiator, targetId) |
| { |
| // Ignore this while waiting for the whole frame/resource tree. |
| if (this._waitingForMainFrameResourceTreePayload) |
| return; |
| |
| var elapsedTime = WI.timelineManager.computeElapsedTime(timestamp); |
| let resource = this._resourceRequestIdentifierMap.get(requestIdentifier); |
| if (resource) { |
| // This is an existing request which is being redirected, update the resource. |
| console.assert(resource.parentFrame.id === frameIdentifier); |
| console.assert(resource.loaderIdentifier === loaderIdentifier); |
| console.assert(!targetId); |
| resource.updateForRedirectResponse(request, redirectResponse, elapsedTime, walltime); |
| return; |
| } |
| |
| // This is a new request, make a new resource and add it to the right frame. |
| resource = this._addNewResourceToFrameOrTarget(request.url, frameIdentifier, { |
| type, |
| loaderIdentifier, |
| targetId, |
| requestIdentifier, |
| requestMethod: request.method, |
| requestHeaders: request.headers, |
| requestData: request.postData, |
| requestSentTimestamp: elapsedTime, |
| requestSentWalltime: walltime, |
| initiatorCallFrames: this._initiatorCallFramesFromPayload(initiator), |
| initiatorSourceCodeLocation: this._initiatorSourceCodeLocationFromPayload(initiator), |
| initiatorNode: this._initiatorNodeFromPayload(initiator), |
| }); |
| |
| // Associate the resource with the requestIdentifier so it can be found in future loading events. |
| this._resourceRequestIdentifierMap.set(requestIdentifier, resource); |
| } |
| |
| webSocketCreated(requestId, url) |
| { |
| this._webSocketIdentifierToURL.set(requestId, url); |
| } |
| |
| webSocketWillSendHandshakeRequest(requestId, timestamp, walltime, request) |
| { |
| let url = this._webSocketIdentifierToURL.get(requestId); |
| console.assert(url); |
| if (!url) |
| return; |
| |
| // COMPATIBILITY(iOS 10.3): `walltime` did not exist in 10.3 and earlier. |
| if (!InspectorBackend.hasEvent("Network.webSocketWillSendHandshakeRequest", "walltime")) { |
| request = arguments[2]; |
| walltime = NaN; |
| } |
| |
| // FIXME: <webkit.org/b/168475> Web Inspector: Correctly display iframe's and worker's WebSockets |
| |
| let resource = new WI.WebSocketResource(url, { |
| loaderIdentifier: WI.networkManager.mainFrame.id, |
| requestIdentifier: requestId, |
| requestHeaders: request.headers, |
| timestamp, |
| walltime, |
| requestSentTimestamp: WI.timelineManager.computeElapsedTime(timestamp), |
| }); |
| |
| let frame = this.frameForIdentifier(WI.networkManager.mainFrame.id); |
| frame.addResource(resource); |
| |
| this._resourceRequestIdentifierMap.set(requestId, resource); |
| } |
| |
| webSocketHandshakeResponseReceived(requestId, timestamp, response) |
| { |
| let resource = this._resourceRequestIdentifierMap.get(requestId); |
| console.assert(resource); |
| if (!resource) |
| return; |
| |
| resource.readyState = WI.WebSocketResource.ReadyState.Open; |
| |
| let elapsedTime = WI.timelineManager.computeElapsedTime(timestamp); |
| |
| // FIXME: <webkit.org/b/169166> Web Inspector: WebSockets: Implement timing information |
| let responseTiming = response.timing || null; |
| |
| resource.updateForResponse(resource.url, resource.mimeType, resource.type, response.headers, response.status, response.statusText, elapsedTime, responseTiming); |
| |
| resource.markAsFinished(elapsedTime); |
| } |
| |
| webSocketFrameReceived(requestId, timestamp, response) |
| { |
| this._webSocketFrameReceivedOrSent(requestId, timestamp, response); |
| } |
| |
| webSocketFrameSent(requestId, timestamp, response) |
| { |
| this._webSocketFrameReceivedOrSent(requestId, timestamp, response); |
| } |
| |
| webSocketClosed(requestId, timestamp) |
| { |
| let resource = this._resourceRequestIdentifierMap.get(requestId); |
| console.assert(resource); |
| if (!resource) |
| return; |
| |
| resource.readyState = WI.WebSocketResource.ReadyState.Closed; |
| |
| let elapsedTime = WI.timelineManager.computeElapsedTime(timestamp); |
| resource.markAsFinished(elapsedTime); |
| |
| this._webSocketIdentifierToURL.delete(requestId); |
| this._resourceRequestIdentifierMap.delete(requestId); |
| } |
| |
| _webSocketFrameReceivedOrSent(requestId, timestamp, response) |
| { |
| let resource = this._resourceRequestIdentifierMap.get(requestId); |
| console.assert(resource); |
| if (!resource) |
| return; |
| |
| // Data going from the client to the server is always masked. |
| let isOutgoing = !!response.mask; |
| |
| let {payloadData, payloadLength, opcode} = response; |
| let elapsedTime = WI.timelineManager.computeElapsedTime(timestamp); |
| |
| resource.addFrame(payloadData, payloadLength, isOutgoing, opcode, timestamp, elapsedTime); |
| } |
| |
| markResourceRequestAsServedFromMemoryCache(requestIdentifier) |
| { |
| // Ignore this while waiting for the whole frame/resource tree. |
| if (this._waitingForMainFrameResourceTreePayload) |
| return; |
| |
| let resource = this._resourceRequestIdentifierMap.get(requestIdentifier); |
| |
| // We might not have a resource if the inspector was opened during the page load (after resourceRequestWillBeSent is called). |
| // We don't want to assert in this case since we do likely have the resource, via Page.getResourceTree. The Resource |
| // just doesn't have a requestIdentifier for us to look it up. |
| if (!resource) |
| return; |
| |
| resource.legacyMarkServedFromMemoryCache(); |
| } |
| |
| resourceRequestWasServedFromMemoryCache(requestIdentifier, frameIdentifier, loaderIdentifier, cachedResourcePayload, timestamp, initiator) |
| { |
| // Ignore this while waiting for the whole frame/resource tree. |
| if (this._waitingForMainFrameResourceTreePayload) |
| return; |
| |
| console.assert(!this._resourceRequestIdentifierMap.has(requestIdentifier)); |
| |
| let elapsedTime = WI.timelineManager.computeElapsedTime(timestamp); |
| let response = cachedResourcePayload.response; |
| const responseSource = InspectorBackend.Enum.Network.ResponseSource.MemoryCache; |
| |
| let resource = this._addNewResourceToFrameOrTarget(cachedResourcePayload.url, frameIdentifier, { |
| type: cachedResourcePayload.type, |
| loaderIdentifier, |
| requestIdentifier, |
| requestMethod: "GET", |
| requestSentTimestamp: elapsedTime, |
| initiatorCallFrames: this._initiatorCallFramesFromPayload(initiator), |
| initiatorSourceCodeLocation: this._initiatorSourceCodeLocationFromPayload(initiator), |
| initiatorNode: this._initiatorNodeFromPayload(initiator), |
| }); |
| resource.updateForResponse(cachedResourcePayload.url, response.mimeType, cachedResourcePayload.type, response.headers, response.status, response.statusText, elapsedTime, response.timing, responseSource, response.security); |
| resource.increaseSize(cachedResourcePayload.bodySize, elapsedTime); |
| resource.increaseTransferSize(cachedResourcePayload.bodySize); |
| resource.setCachedResponseBodySize(cachedResourcePayload.bodySize); |
| resource.markAsFinished(elapsedTime); |
| |
| console.assert(resource.cached, "This resource should be classified as cached since it was served from the MemoryCache", resource); |
| |
| if (cachedResourcePayload.sourceMapURL) |
| this.downloadSourceMap(cachedResourcePayload.sourceMapURL, resource.url, resource); |
| |
| // No need to associate the resource with the requestIdentifier, since this is the only event |
| // sent for memory cache resource loads. |
| } |
| |
| resourceRequestDidReceiveResponse(requestIdentifier, frameIdentifier, loaderIdentifier, type, response, timestamp) |
| { |
| // Ignore this while waiting for the whole frame/resource tree. |
| if (this._waitingForMainFrameResourceTreePayload) |
| return; |
| |
| var elapsedTime = WI.timelineManager.computeElapsedTime(timestamp); |
| let resource = this._resourceRequestIdentifierMap.get(requestIdentifier); |
| |
| // We might not have a resource if the inspector was opened during the page load (after resourceRequestWillBeSent is called). |
| // We don't want to assert in this case since we do likely have the resource, via Page.getResourceTree. The Resource |
| // just doesn't have a requestIdentifier for us to look it up, but we can try to look it up by its URL. |
| if (!resource) { |
| var frame = this.frameForIdentifier(frameIdentifier); |
| if (frame) |
| resource = frame.resourcesForURL(response.url).firstValue; |
| |
| // If we find the resource this way we had marked it earlier as finished via Page.getResourceTree. |
| // Associate the resource with the requestIdentifier so it can be found in future loading events. |
| // and roll it back to an unfinished state, we know now it is still loading. |
| if (resource) { |
| this._resourceRequestIdentifierMap.set(requestIdentifier, resource); |
| resource.revertMarkAsFinished(); |
| } |
| } |
| |
| // If we haven't found an existing Resource by now, then it is a resource that was loading when the inspector |
| // opened and we just missed the resourceRequestWillBeSent for it. So make a new resource and add it. |
| if (!resource) { |
| resource = this._addNewResourceToFrameOrTarget(response.url, frameIdentifier, { |
| type, |
| loaderIdentifier, |
| requestIdentifier, |
| requestHeaders: response.requestHeaders, |
| requestSentTimestamp: elapsedTime, |
| }); |
| |
| // Associate the resource with the requestIdentifier so it can be found in future loading events. |
| this._resourceRequestIdentifierMap.set(requestIdentifier, resource); |
| } |
| |
| // COMPATIBILITY (iOS 10.3): `fromDiskCache` is legacy, replaced by `source`. |
| if (response.fromDiskCache) |
| resource.legacyMarkServedFromDiskCache(); |
| |
| resource.updateForResponse(response.url, response.mimeType, type, response.headers, response.status, response.statusText, elapsedTime, response.timing, response.source, response.security); |
| } |
| |
| resourceRequestDidReceiveData(requestIdentifier, dataLength, encodedDataLength, timestamp) |
| { |
| // Ignore this while waiting for the whole frame/resource tree. |
| if (this._waitingForMainFrameResourceTreePayload) |
| return; |
| |
| let resource = this._resourceRequestIdentifierMap.get(requestIdentifier); |
| var elapsedTime = WI.timelineManager.computeElapsedTime(timestamp); |
| |
| // We might not have a resource if the inspector was opened during the page load (after resourceRequestWillBeSent is called). |
| // We don't want to assert in this case since we do likely have the resource, via Page.getResourceTree. The Resource |
| // just doesn't have a requestIdentifier for us to look it up. |
| if (!resource) |
| return; |
| |
| resource.increaseSize(dataLength, elapsedTime); |
| |
| if (encodedDataLength !== -1) |
| resource.increaseTransferSize(encodedDataLength); |
| } |
| |
| resourceRequestDidFinishLoading(requestIdentifier, timestamp, sourceMapURL, metrics) |
| { |
| // Ignore this while waiting for the whole frame/resource tree. |
| if (this._waitingForMainFrameResourceTreePayload) |
| return; |
| |
| // By now we should always have the Resource. Either it was fetched when the inspector first opened with |
| // Page.getResourceTree, or it was a currently loading resource that we learned about in resourceRequestDidReceiveResponse. |
| let resource = this._resourceRequestIdentifierMap.get(requestIdentifier); |
| console.assert(resource); |
| if (!resource) |
| return; |
| |
| if (metrics) |
| resource.updateWithMetrics(metrics); |
| |
| let elapsedTime = WI.timelineManager.computeElapsedTime(timestamp); |
| resource.markAsFinished(elapsedTime); |
| |
| if (sourceMapURL) |
| this.downloadSourceMap(sourceMapURL, resource.url, resource); |
| |
| this._resourceRequestIdentifierMap.delete(requestIdentifier); |
| } |
| |
| resourceRequestDidFailLoading(requestIdentifier, canceled, timestamp, errorText) |
| { |
| // Ignore this while waiting for the whole frame/resource tree. |
| if (this._waitingForMainFrameResourceTreePayload) |
| return; |
| |
| // By now we should always have the Resource. Either it was fetched when the inspector first opened with |
| // Page.getResourceTree, or it was a currently loading resource that we learned about in resourceRequestDidReceiveResponse. |
| let resource = this._resourceRequestIdentifierMap.get(requestIdentifier); |
| console.assert(resource); |
| if (!resource) |
| return; |
| |
| let elapsedTime = WI.timelineManager.computeElapsedTime(timestamp); |
| resource.markAsFailed(canceled, elapsedTime, errorText); |
| |
| if (resource.parentFrame && resource === resource.parentFrame.provisionalMainResource) |
| resource.parentFrame.clearProvisionalLoad(); |
| |
| this._resourceRequestIdentifierMap.delete(requestIdentifier); |
| } |
| |
| requestIntercepted(target, requestId, request) |
| { |
| let url = WI.urlWithoutFragment(request.url); |
| for (let localResourceOverride of this.localResourceOverridesForURL(url)) { |
| if (localResourceOverride.disabled) |
| continue; |
| |
| let localResource = localResourceOverride.localResource; |
| let revision = localResource.currentRevision; |
| |
| switch (localResourceOverride.type) { |
| case WI.LocalResourceOverride.InterceptType.Request: { |
| target.NetworkAgent.interceptWithRequest.invoke({ |
| requestId, |
| url: localResource.url || undefined, |
| method: localResource.requestMethod ?? undefined, |
| headers: localResource.requestHeaders, |
| postData: (WI.HTTPUtilities.RequestMethodsWithBody.has(localResource.requestMethod) && localResource.requestData) ? btoa(localResource.requestData) : undefined, |
| }); |
| return; |
| } |
| |
| case WI.LocalResourceOverride.InterceptType.ResponseSkippingNetwork: |
| console.assert(revision.mimeType === localResource.mimeType); |
| target.NetworkAgent.interceptRequestWithResponse.invoke({ |
| requestId, |
| content: revision.content, |
| base64Encoded: !!revision.base64Encoded, |
| mimeType: revision.mimeType ?? undefined, |
| status: !isNaN(localResource.statusCode) ? localResource.statusCode : 200, |
| statusText: !isNaN(localResource.statusCode) ? (localResource.statusText ?? "") : WI.HTTPUtilities.statusTextForStatusCode(200), |
| headers: localResource.responseHeaders, |
| }); |
| return; |
| } |
| } |
| |
| // It's possible for a response regex override to overlap a request regex override, in |
| // which case we should silently continue the request if the response regex override was |
| // used instead (e.g. it was added first). |
| target.NetworkAgent.interceptContinue.invoke({ |
| requestId, |
| stage: InspectorBackend.Enum.Network.NetworkStage.Request, |
| }); |
| } |
| |
| responseIntercepted(target, requestId, response) |
| { |
| let url = WI.urlWithoutFragment(response.url); |
| for (let localResourceOverride of this.localResourceOverridesForURL(url)) { |
| if (localResourceOverride.disabled) |
| continue; |
| |
| let localResource = localResourceOverride.localResource; |
| let revision = localResource.currentRevision; |
| |
| switch (localResourceOverride.type) { |
| case WI.LocalResourceOverride.InterceptType.Response: |
| console.assert(revision.mimeType === localResource.mimeType); |
| target.NetworkAgent.interceptWithResponse.invoke({ |
| requestId, |
| content: revision.content, |
| base64Encoded: !!revision.base64Encoded, |
| mimeType: revision.mimeType ?? undefined, |
| status: !isNaN(localResource.statusCode) ? localResource.statusCode : undefined, |
| statusText: !isNaN(localResource.statusCode) ? (localResource.statusText ?? "") : undefined, |
| headers: localResource.responseHeaders, |
| }); |
| return; |
| } |
| } |
| |
| // It's possible for a request regex override to overlap a response regex override, in |
| // which case we should silently continue the response if the request regex override was |
| // used instead (e.g. it was added first). |
| target.NetworkAgent.interceptContinue.invoke({ |
| requestId, |
| stage: InspectorBackend.Enum.Network.NetworkStage.Response, |
| }); |
| } |
| |
| // RuntimeObserver |
| |
| executionContextCreated(payload) |
| { |
| let frame = this.frameForIdentifier(payload.frameId); |
| console.assert(frame); |
| if (!frame) |
| return; |
| |
| let type = WI.ExecutionContext.typeFromPayload(payload); |
| let target = frame.mainResource.target; |
| let executionContext = new WI.ExecutionContext(target, payload.id, type, payload.name, frame); |
| frame.addExecutionContext(executionContext); |
| } |
| |
| // Private |
| |
| _addNewResourceToFrameOrTarget(url, frameIdentifier, resourceOptions = {}, frameOptions = {}) |
| { |
| console.assert(!this._waitingForMainFrameResourceTreePayload); |
| |
| let resource = null; |
| |
| if (!frameIdentifier && resourceOptions.targetId) { |
| // This is a new resource for a ServiceWorker target. |
| console.assert(WI.sharedApp.debuggableType === WI.DebuggableType.ServiceWorker); |
| console.assert(resourceOptions.targetId === WI.mainTarget.identifier); |
| resource = new WI.Resource(url, resourceOptions); |
| resource.target.addResource(resource); |
| return resource; |
| } |
| |
| let frame = this.frameForIdentifier(frameIdentifier); |
| if (frame) { |
| if (resourceOptions.type === InspectorBackend.Enum.Page.ResourceType.Document && frame.provisionalMainResource && frame.provisionalMainResource.url === url && frame.provisionalLoaderIdentifier === resourceOptions.loaderIdentifier) |
| resource = frame.provisionalMainResource; |
| else { |
| resource = new WI.Resource(url, resourceOptions); |
| if (resource.target === WI.pageTarget) |
| this._addResourceToFrame(frame, resource); |
| else if (resource.target) |
| resource.target.addResource(resource); |
| else |
| this._addOrphanedResource(resource, resourceOptions.targetId); |
| } |
| } else { |
| // This is a new request for a new frame, which is always the main resource. |
| console.assert(WI.sharedApp.debuggableType !== WI.DebuggableType.ServiceWorker); |
| console.assert(!resourceOptions.targetId); |
| resource = new WI.Resource(url, resourceOptions); |
| frame = new WI.Frame(frameIdentifier, frameOptions.name, frameOptions.securityOrigin, resourceOptions.loaderIdentifier, resource); |
| this._frameIdentifierMap.set(frame.id, frame); |
| |
| // If we don't have a main frame, assume this is it. This can change later in |
| // frameDidNavigate when the parent frame is known. |
| if (!this._mainFrame) { |
| this._mainFrame = frame; |
| this._mainFrameDidChange(null); |
| } |
| |
| this._dispatchFrameWasAddedEvent(frame); |
| } |
| |
| console.assert(resource); |
| |
| return resource; |
| } |
| |
| _addResourceToFrame(frame, resource) |
| { |
| console.assert(!this._waitingForMainFrameResourceTreePayload); |
| if (this._waitingForMainFrameResourceTreePayload) |
| return; |
| |
| console.assert(frame); |
| console.assert(resource); |
| |
| if (resource.loaderIdentifier !== frame.loaderIdentifier && !frame.provisionalLoaderIdentifier) { |
| // This is the start of a provisional load which happens before frameDidNavigate is called. |
| // This resource will be the new mainResource if frameDidNavigate is called. |
| frame.startProvisionalLoad(resource); |
| return; |
| } |
| |
| // This is just another resource, either for the main loader or the provisional loader. |
| console.assert(resource.loaderIdentifier === frame.loaderIdentifier || resource.loaderIdentifier === frame.provisionalLoaderIdentifier); |
| frame.addResource(resource); |
| } |
| |
| _addResourceToTarget(target, resource) |
| { |
| console.assert(target !== WI.pageTarget); |
| console.assert(resource); |
| |
| target.addResource(resource); |
| } |
| |
| _initiatorCallFramesFromPayload(initiatorPayload) |
| { |
| if (!initiatorPayload) |
| return null; |
| |
| let callFrames = initiatorPayload.stackTrace; |
| if (!callFrames) |
| return null; |
| |
| return callFrames.map((payload) => WI.CallFrame.fromPayload(WI.assumingMainTarget(), payload)); |
| } |
| |
| _initiatorSourceCodeLocationFromPayload(initiatorPayload) |
| { |
| if (!initiatorPayload) |
| return null; |
| |
| var url = null; |
| var lineNumber = NaN; |
| var columnNumber = 0; |
| |
| if (initiatorPayload.stackTrace && initiatorPayload.stackTrace.length) { |
| var stackTracePayload = initiatorPayload.stackTrace; |
| for (var i = 0; i < stackTracePayload.length; ++i) { |
| var callFramePayload = stackTracePayload[i]; |
| if (!callFramePayload.url || callFramePayload.url === "[native code]") |
| continue; |
| |
| url = callFramePayload.url; |
| |
| // The lineNumber is 1-based, but we expect 0-based. |
| lineNumber = callFramePayload.lineNumber - 1; |
| |
| columnNumber = callFramePayload.columnNumber; |
| |
| break; |
| } |
| } else if (initiatorPayload.url) { |
| url = initiatorPayload.url; |
| |
| // The lineNumber is 1-based, but we expect 0-based. |
| lineNumber = initiatorPayload.lineNumber - 1; |
| } |
| |
| if (!url || isNaN(lineNumber) || lineNumber < 0) |
| return null; |
| |
| let sourceCode = WI.networkManager.resourcesForURL(url).firstValue; |
| if (!sourceCode) |
| sourceCode = WI.debuggerManager.scriptsForURL(url, WI.mainTarget)[0]; |
| |
| if (!sourceCode) |
| return null; |
| |
| return sourceCode.createSourceCodeLocation(lineNumber, columnNumber); |
| } |
| |
| _initiatorNodeFromPayload(initiatorPayload) |
| { |
| return WI.domManager.nodeForId(initiatorPayload.nodeId); |
| } |
| |
| _processServiceWorkerConfiguration(error, initializationPayload) |
| { |
| console.assert(this._waitingForMainFrameResourceTreePayload); |
| this._waitingForMainFrameResourceTreePayload = false; |
| |
| if (error) { |
| console.error(JSON.stringify(error)); |
| return; |
| } |
| |
| console.assert(initializationPayload.targetId.startsWith("serviceworker:")); |
| |
| WI.mainTarget.identifier = initializationPayload.targetId; |
| WI.mainTarget.name = initializationPayload.url; |
| |
| // Create a main resource with this content in case the content never shows up as a WI.Script. |
| const sourceURL = null; |
| const sourceType = WI.Script.SourceType.Program; |
| let script = new WI.LocalScript(WI.mainTarget, initializationPayload.url, sourceURL, sourceType, initializationPayload.content); |
| WI.mainTarget.mainResource = script; |
| |
| InspectorBackend.runAfterPendingDispatches(() => { |
| if (WI.mainTarget.mainResource === script) { |
| // We've now received all the scripts, if we don't have a better main resource use this LocalScript. |
| WI.debuggerManager.dataForTarget(WI.mainTarget).addScript(script); |
| WI.debuggerManager.dispatchEventToListeners(WI.DebuggerManager.Event.ScriptAdded, {script}); |
| } |
| }); |
| } |
| |
| _processMainFrameResourceTreePayload(error, mainFramePayload) |
| { |
| console.assert(this._waitingForMainFrameResourceTreePayload); |
| this._waitingForMainFrameResourceTreePayload = false; |
| |
| if (error) { |
| console.error(JSON.stringify(error)); |
| return; |
| } |
| |
| console.assert(mainFramePayload); |
| console.assert(mainFramePayload.frame); |
| |
| this._resourceRequestIdentifierMap = new Map; |
| this._frameIdentifierMap = new Map; |
| |
| var oldMainFrame = this._mainFrame; |
| |
| this._mainFrame = this._addFrameTreeFromFrameResourceTreePayload(mainFramePayload, true); |
| |
| if (this._mainFrame !== oldMainFrame) |
| this._mainFrameDidChange(oldMainFrame); |
| |
| // Emulate a main resource change within this page even though we are swapping out main frames. |
| // This is because many managers listen only for main resource change events to perform work, |
| // but they don't listen for main frame changes. |
| if (this._transitioningPageTarget) { |
| this._transitioningPageTarget = false; |
| this._mainFrame._dispatchMainResourceDidChangeEvent(oldMainFrame.mainResource); |
| } |
| } |
| |
| _createFrame(payload) |
| { |
| // If payload.url is missing or empty then this page is likely the special empty page. In that case |
| // we will just say it is "about:blank" so we have a URL, which is required for resources. |
| let mainResource = new WI.Resource(payload.url || "about:blank", { |
| mimeType: payload.mimeType, |
| loaderIdentifier: payload.loaderId, |
| }); |
| var frame = new WI.Frame(payload.id, payload.name, payload.securityOrigin, payload.loaderId, mainResource); |
| |
| this._frameIdentifierMap.set(frame.id, frame); |
| |
| mainResource.markAsFinished(); |
| |
| return frame; |
| } |
| |
| _createResource(payload, framePayload) |
| { |
| let resource = new WI.Resource(payload.url, { |
| mimeType: payload.mimeType, |
| type: payload.type, |
| loaderIdentifier: framePayload.loaderId, |
| targetId: payload.targetId, |
| }); |
| |
| if (payload.sourceMapURL) |
| this.downloadSourceMap(payload.sourceMapURL, resource.url, resource); |
| |
| return resource; |
| } |
| |
| _addFrameTreeFromFrameResourceTreePayload(payload, isMainFrame) |
| { |
| var frame = this._createFrame(payload.frame); |
| if (isMainFrame) |
| frame.markAsMainFrame(); |
| |
| for (var i = 0; payload.childFrames && i < payload.childFrames.length; ++i) |
| frame.addChildFrame(this._addFrameTreeFromFrameResourceTreePayload(payload.childFrames[i], false)); |
| |
| for (var i = 0; payload.resources && i < payload.resources.length; ++i) { |
| var resourcePayload = payload.resources[i]; |
| |
| // The main resource is included as a resource. We can skip it since we already created |
| // a main resource when we created the Frame. The resource payload does not include anything |
| // didn't already get from the frame payload. |
| if (resourcePayload.type === "Document" && resourcePayload.url === payload.frame.url) |
| continue; |
| |
| var resource = this._createResource(resourcePayload, payload); |
| if (resource.target === WI.pageTarget) |
| frame.addResource(resource); |
| else if (resource.target) |
| resource.target.addResource(resource); |
| else |
| this._addOrphanedResource(resource, resourcePayload.targetId); |
| |
| if (resourcePayload.failed || resourcePayload.canceled) |
| resource.markAsFailed(resourcePayload.canceled); |
| else |
| resource.markAsFinished(); |
| } |
| |
| this._dispatchFrameWasAddedEvent(frame); |
| |
| return frame; |
| } |
| |
| _addOrphanedResource(resource, targetId) |
| { |
| let resources = this._orphanedResources.get(targetId); |
| if (!resources) { |
| resources = []; |
| this._orphanedResources.set(targetId, resources); |
| } |
| |
| resources.push(resource); |
| } |
| |
| _commandArgumentsForInterception(localResourceOverride) |
| { |
| console.assert(localResourceOverride instanceof WI.LocalResourceOverride, localResourceOverride); |
| |
| return { |
| url: localResourceOverride.url, |
| stage: localResourceOverride.type === WI.LocalResourceOverride.InterceptType.Response ? InspectorBackend.Enum.Network.NetworkStage.Response : InspectorBackend.Enum.Network.NetworkStage.Request, |
| caseSensitive: localResourceOverride.isCaseSensitive, |
| isRegex: localResourceOverride.isRegex, |
| }; |
| } |
| |
| _addInterception(localResourceOverride, specificTarget) |
| { |
| console.assert(localResourceOverride instanceof WI.LocalResourceOverride, localResourceOverride); |
| console.assert(!localResourceOverride.disabled, localResourceOverride); |
| |
| let targets = specificTarget ? [specificTarget] : WI.targets; |
| for (let target of targets) { |
| // COMPATIBILITY (iOS 13.0): Network.addInterception did not exist yet. |
| if (!target.hasCommand("Network.addInterception")) |
| continue; |
| |
| target.NetworkAgent.addInterception.invoke(this._commandArgumentsForInterception(localResourceOverride)); |
| } |
| } |
| |
| _removeInterception(localResourceOverride, specificTarget) |
| { |
| console.assert(localResourceOverride instanceof WI.LocalResourceOverride, localResourceOverride); |
| |
| let targets = specificTarget ? [specificTarget] : WI.targets; |
| for (let target of targets) { |
| // COMPATIBILITY (iOS 13.0): Network.removeInterception did not exist yet. |
| if (!target.hasCommand("Network.removeInterception")) |
| continue; |
| |
| target.NetworkAgent.removeInterception.invoke(this._commandArgumentsForInterception(localResourceOverride)); |
| } |
| } |
| |
| _dispatchFrameWasAddedEvent(frame) |
| { |
| this.dispatchEventToListeners(WI.NetworkManager.Event.FrameWasAdded, {frame}); |
| } |
| |
| _mainFrameDidChange(oldMainFrame) |
| { |
| if (oldMainFrame) |
| oldMainFrame.unmarkAsMainFrame(); |
| if (this._mainFrame) |
| this._mainFrame.markAsMainFrame(); |
| |
| this.dispatchEventToListeners(WI.NetworkManager.Event.MainFrameDidChange, {oldMainFrame}); |
| } |
| |
| _loadAndParseSourceMap(sourceMapURL, baseURL, originalSourceCode) |
| { |
| this._downloadingSourceMaps.add(sourceMapURL); |
| |
| let sourceMapLoaded = (error, content, mimeType, statusCode) => { |
| if (error || statusCode >= 400) { |
| this._sourceMapLoadAndParseFailed(sourceMapURL); |
| return; |
| } |
| |
| if (content.slice(0, 3) === ")]}") { |
| let firstNewlineIndex = content.indexOf("\n"); |
| if (firstNewlineIndex === -1) { |
| this._sourceMapLoadAndParseFailed(sourceMapURL); |
| return; |
| } |
| |
| content = content.substring(firstNewlineIndex); |
| } |
| |
| try { |
| let payload = JSON.parse(content); |
| let baseURL = sourceMapURL.startsWith("data:") ? originalSourceCode.url : sourceMapURL; |
| let sourceMap = new WI.SourceMap(baseURL, payload, originalSourceCode); |
| this._sourceMapLoadAndParseSucceeded(sourceMapURL, sourceMap); |
| } catch { |
| this._sourceMapLoadAndParseFailed(sourceMapURL); |
| } |
| }; |
| |
| if (sourceMapURL.startsWith("data:")) { |
| let {mimeType, base64, data} = parseDataURL(sourceMapURL); |
| let content = base64 ? atob(data) : data; |
| sourceMapLoaded(null, content, mimeType, 0); |
| return; |
| } |
| |
| let target = WI.assumingMainTarget(); |
| if (!target.hasCommand("Network.loadResource")) { |
| this._sourceMapLoadAndParseFailed(sourceMapURL); |
| return; |
| } |
| |
| let frameIdentifier = null; |
| if (originalSourceCode instanceof WI.Resource && originalSourceCode.parentFrame) |
| frameIdentifier = originalSourceCode.parentFrame.id; |
| |
| if (!frameIdentifier) |
| frameIdentifier = WI.networkManager.mainFrame ? WI.networkManager.mainFrame.id : ""; |
| |
| target.NetworkAgent.loadResource(frameIdentifier, sourceMapURL, sourceMapLoaded); |
| } |
| |
| _sourceMapLoadAndParseFailed(sourceMapURL) |
| { |
| this._downloadingSourceMaps.delete(sourceMapURL); |
| } |
| |
| _sourceMapLoadAndParseSucceeded(sourceMapURL, sourceMap) |
| { |
| if (!this._downloadingSourceMaps.has(sourceMapURL)) |
| return; |
| |
| this._downloadingSourceMaps.delete(sourceMapURL); |
| |
| this._sourceMapURLMap.set(sourceMapURL, sourceMap); |
| |
| for (let source of sourceMap.sources()) |
| sourceMap.addResource(new WI.SourceMapResource(source, sourceMap)); |
| |
| // Associate the SourceMap with the originalSourceCode. |
| sourceMap.originalSourceCode.addSourceMap(sourceMap); |
| |
| // If the originalSourceCode was not a Resource, be sure to also associate with the Resource if one exists. |
| // FIXME: We should try to use the right frame instead of a global lookup by URL. |
| if (!(sourceMap.originalSourceCode instanceof WI.Resource)) { |
| console.assert(sourceMap.originalSourceCode instanceof WI.Script); |
| let resource = sourceMap.originalSourceCode.resource; |
| if (resource) |
| resource.addSourceMap(sourceMap); |
| } |
| } |
| |
| _handleResourceContentChangedForLocalResourceOverride(event) |
| { |
| let localResourceOverride = event.target.localResourceOverride; |
| if (!localResourceOverride) |
| return; |
| |
| 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.supportsOverridingResponses()); |
| |
| let localResourceOverride = event.target; |
| WI.objectStores.localResourceOverrides.putObject(localResourceOverride); |
| |
| if (localResourceOverride.disabled) |
| this._removeInterception(localResourceOverride); |
| else |
| this._addInterception(localResourceOverride); |
| } |
| |
| _handleBootstrapScriptContentDidChange(event) |
| { |
| let source = this._bootstrapScript.content || ""; |
| |
| WI.objectStores.general.put(source, NetworkManager.bootstrapScriptSourceObjectStoreKey); |
| |
| if (!this._bootstrapScriptEnabledSetting.value) |
| return; |
| |
| // COMPATIBILITY (iOS 13.0): Page.setBootstrapScript did not exist yet. |
| for (let target of WI.targets) { |
| if (target.hasCommand("Page.setBootstrapScript")) |
| target.PageAgent.setBootstrapScript(source); |
| } |
| } |
| |
| _extraDomainsActivated(event) |
| { |
| // COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type |
| |
| let target = WI.assumingMainTarget(); |
| if (target.hasDomain("Page") && event.data.domains.includes("Page")) |
| target.PageAgent.getResourceTree(this._processMainFrameResourceTreePayload.bind(this)); |
| } |
| |
| _handleFrameMainResourceDidChange(event) |
| { |
| if (!event.target.isMainFrame()) |
| return; |
| |
| this._sourceMapURLMap.clear(); |
| this._downloadingSourceMaps.clear(); |
| } |
| }; |
| |
| WI.NetworkManager.Event = { |
| FrameWasAdded: "network-manager-frame-was-added", |
| FrameWasRemoved: "network-manager-frame-was-removed", |
| MainFrameDidChange: "network-manager-main-frame-did-change", |
| BootstrapScriptCreated: "network-manager-bootstrap-script-created", |
| BootstrapScriptEnabledChanged: "network-manager-bootstrap-script-enabled-changed", |
| BootstrapScriptDestroyed: "network-manager-bootstrap-script-destroyed", |
| LocalResourceOverrideAdded: "network-manager-local-resource-override-added", |
| LocalResourceOverrideRemoved: "network-manager-local-resource-override-removed", |
| }; |