/* * 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; this._emulatedCondition = WI.NetworkManager.EmulatedCondition.None; // 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.LocalResourceOverride.addEventListener(WI.LocalResourceOverride.Event.ResourceErrorTypeChanged, this._handleResourceOverrideResourceErrorTypeChanged, 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.Block: supported = WI.NetworkManager.supportsBlockingRequests(); break; 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 supportsBlockingRequests() { // COMPATIBILITY (iOS 13.4): Network.interceptRequestWithError did not exist yet. return InspectorBackend.hasCommand("Network.interceptRequestWithError"); } static supportsOverridingRequests() { // COMPATIBILITY (iOS 13.4): Network.interceptWithRequest did not exist yet. 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(); 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); } } } this._applyEmulatedCondition(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); } } get emulatedCondition() { return this._emulatedCondition; } set emulatedCondition(condition) { console.assert(Object.values(WI.NetworkManager.EmulatedCondition).includes(condition), condition); console.assert(WI.settings.experimentalEnableNetworkEmulatedCondition.value); console.assert(InspectorBackend.hasCommand("Network.setEmulatedConditions")); if (condition === this._emulatedCondition) return; this._emulatedCondition = condition; for (let target of WI.targets) this._applyEmulatedCondition(target); this.dispatchEventToListeners(WI.NetworkManager.Event.EmulatedConditionChanged); } 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: 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 .")} */ `.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.EventSource: 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, referrerPolicy: request.referrerPolicy, integrity: request.integrity, initiatorStackTrace: this._initiatorStackTraceFromPayload(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; // FIXME: 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: 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); } 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, initiatorStackTrace: this._initiatorStackTraceFromPayload(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); } 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); } async requestIntercepted(target, requestId, request) { for (let localResourceOverride of this.localResourceOverridesForURL(request.url)) { if (localResourceOverride.disabled) continue; let isPassthrough = localResourceOverride.isPassthrough; let originalHeaders = isPassthrough ? request.headers : {}; let localResource = localResourceOverride.localResource; await localResource.requestContent(); let revision = localResource.currentRevision; switch (localResourceOverride.type) { case WI.LocalResourceOverride.InterceptType.Block: target.NetworkAgent.interceptRequestWithError.invoke({ requestId, errorType: localResourceOverride.resourceErrorType, }); return; case WI.LocalResourceOverride.InterceptType.Request: { target.NetworkAgent.interceptWithRequest.invoke({ requestId, url: localResourceOverride.generateRequestRedirectURL(request.url) ?? undefined, method: localResource.requestMethod ?? (isPassthrough ? request.method : ""), headers: {...originalHeaders, ...localResource.requestHeaders}, postData: (function() { if (!WI.HTTPUtilities.RequestMethodsWithBody.has(localResource.requestMethod)) return undefined; if (localResource.requestData ?? false) return btoa(localResource.requestData); if (isPassthrough) return request.data; return ""; })(), }); 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 ?? "text/plain", status: !isNaN(localResource.statusCode) ? localResource.statusCode : 200, statusText: (function() { if (localResource.statusText ?? false) return localResource.statusText; if (!isNaN(localResource.statusCode)) return WI.HTTPUtilities.statusTextForStatusCode(localResource.statusCode); return WI.HTTPUtilities.statusTextForStatusCode(200); })(), headers: {...originalHeaders, ...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, }); } async responseIntercepted(target, requestId, response) { for (let localResourceOverride of this.localResourceOverridesForURL(response.url)) { if (localResourceOverride.disabled) continue; let isPassthrough = localResourceOverride.isPassthrough; let originalHeaders = isPassthrough ? response.headers : {}; let localResource = localResourceOverride.localResource; await localResource.requestContent(); 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 ?? (isPassthrough ? response.mimeType : "text/plain"), status: (function() { if (!isNaN(localResource.statusCode)) return localResource.statusCode; if (isPassthrough) return response.statusCode; return 200; })(), statusText: (function() { if (localResource.statusText ?? false) return localResource.statusText; if (isPassthrough) return response.statusText; if (!isNaN(localResource.statusCode)) return WI.HTTPUtilities.statusTextForStatusCode(localResource.statusCode); return WI.HTTPUtilities.statusTextForStatusCode(200); })(), headers: {...originalHeaders, ...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); } _initiatorStackTraceFromPayload(initiatorPayload) { if (!initiatorPayload) return null; let stackTrace = initiatorPayload.stackTrace; if (!stackTrace) return null; // COMPATIBILITY (macOS 13.0, iOS 16.0): `stackTrace` was an array of `Console.CallFrame`. if (Array.isArray(stackTrace)) stackTrace = {callFrames: stackTrace}; return WI.StackTrace.fromPayload(WI.assumingMainTarget(), stackTrace); } _initiatorSourceCodeLocationFromPayload(initiatorPayload) { if (!initiatorPayload) return null; var url = null; var lineNumber = NaN; var columnNumber = 0; // COMPATIBILITY (macOS 13.0, iOS 16.0): `stackTrace` was an array of `Console.CallFrame`. let callFramesPayload = Array.isArray(initiatorPayload.stackTrace) ? initiatorPayload.stackTrace : initiatorPayload.stackTrace?.callFrames; if (callFramesPayload?.length) { for (let callFramePayload of callFramesPayload) { 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)); } } _applyEmulatedCondition(target) { if (!WI.settings.experimentalEnableNetworkEmulatedCondition.value) return; // COMPATIBILITY (macOS 13.0, iOS 16.0): Network.setEmulatedConditions did not exist. if (!target.hasCommand("Network.setEmulatedConditions")) return; target.NetworkAgent.setEmulatedConditions(this._emulatedCondition.bytesPerSecondLimit); } _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); } _handleResourceOverrideResourceErrorTypeChanged(event) { console.assert(WI.NetworkManager.supportsBlockingRequests()); let localResourceOverride = event.target; WI.objectStores.localResourceOverrides.putObject(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.EmulatedCondition = { // Keep this first. None: { id: "none", bytesPerSecondLimit: 0, get displayName() { return WI.UIString("No throttling", "Label indicating that network throttling is inactive."); } }, Mobile3G: { id: "mobile-3g", bytesPerSecondLimit: 780 * 1000 / 8, // 780kbps get displayName() { return WI.UIString("3G", "Label indicating that network activity is being simulated with 3G connectivity."); } }, DSL: { id: "dsl", bytesPerSecondLimit: 2 * 1000 * 1000 / 8, // 2mbps get displayName() { return WI.UIString("DSL", "Label indicating that network activity is being simulated with DSL connectivity."); } }, Edge: { id: "edge", bytesPerSecondLimit: 240 * 1000 / 8, // 240kbps get displayName() { return WI.UIString("Edge", "Label indicating that network activity is being simulated with Edge connectivity."); } }, LTE: { id: "lte", bytesPerSecondLimit: 50 * 1000 * 1000 / 8, // 50mbps get displayName() { return WI.UIString("LTE", "Label indicating that network activity is being simulated with LTE connectivity"); } }, WiFi: { id: "wifi", bytesPerSecondLimit: 40 * 1000 * 1000 / 8, // 40mbps get displayName() { return WI.UIString("Wi-Fi", "Label indicating that network activity is being simulated with Wi-Fi connectivity"); } }, WiFi802_11ac: { id: "wifi-802_11ac", bytesPerSecondLimit: 250 * 1000 * 1000 / 8, // 250mbps get displayName() { return WI.UIString("Wi-Fi 802.11ac", "Label indicating that network activity is being simulated with Wi-Fi 802.11ac connectivity"); } }, }; 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", EmulatedConditionChanged: "network-manager-emulated-condition-changed", };