UNPKG

@quick-game/cli

Version:

Command line interface for rapid qg development

1,017 lines 42.1 kB
// Copyright 2021 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /* * Copyright (C) 2011 Google 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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the #name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT * OWNER OR 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. */ import * as Common from '../common/common.js'; import * as i18n from '../i18n/i18n.js'; import * as Platform from '../platform/platform.js'; import { DOMModel } from './DOMModel.js'; import { Events as NetworkManagerEvents, NetworkManager } from './NetworkManager.js'; import { Resource } from './Resource.js'; import { ExecutionContext, RuntimeModel } from './RuntimeModel.js'; import { Capability, Type } from './Target.js'; import { SDKModel } from './SDKModel.js'; import { TargetManager } from './TargetManager.js'; import { SecurityOriginManager } from './SecurityOriginManager.js'; import { StorageKeyManager } from './StorageKeyManager.js'; import { FrameManager } from './FrameManager.js'; export class ResourceTreeModel extends SDKModel { agent; storageAgent; #securityOriginManager; #storageKeyManager; framesInternal; #cachedResourcesProcessed; #pendingReloadOptions; #reloadSuspensionCount; isInterstitialShowing; mainFrame; #pendingBackForwardCacheNotUsedEvents; #pendingPrerenderAttemptCompletedEvents; constructor(target) { super(target); const networkManager = target.model(NetworkManager); if (networkManager) { networkManager.addEventListener(NetworkManagerEvents.RequestFinished, this.onRequestFinished, this); networkManager.addEventListener(NetworkManagerEvents.RequestUpdateDropped, this.onRequestUpdateDropped, this); } this.agent = target.pageAgent(); this.storageAgent = target.storageAgent(); void this.agent.invoke_enable(); this.#securityOriginManager = target.model(SecurityOriginManager); this.#storageKeyManager = target.model(StorageKeyManager); this.#pendingBackForwardCacheNotUsedEvents = new Set(); this.#pendingPrerenderAttemptCompletedEvents = new Set(); target.registerPageDispatcher(new PageDispatcher(this)); target.registerPreloadDispatcher(new PreloadDispatcher(this)); this.framesInternal = new Map(); this.#cachedResourcesProcessed = false; this.#pendingReloadOptions = null; this.#reloadSuspensionCount = 0; this.isInterstitialShowing = false; this.mainFrame = null; void this.#buildResourceTree(); } async #buildResourceTree() { return this.agent.invoke_getResourceTree().then(event => { this.processCachedResources(event.getError() ? null : event.frameTree); if (this.mainFrame) { this.processPendingEvents(this.mainFrame); } }); } static frameForRequest(request) { const networkManager = NetworkManager.forRequest(request); const resourceTreeModel = networkManager ? networkManager.target().model(ResourceTreeModel) : null; if (!resourceTreeModel) { return null; } return request.frameId ? resourceTreeModel.frameForId(request.frameId) : null; } static frames() { const result = []; for (const resourceTreeModel of TargetManager.instance().models(ResourceTreeModel)) { result.push(...resourceTreeModel.frames()); } return result; } static resourceForURL(url) { for (const resourceTreeModel of TargetManager.instance().models(ResourceTreeModel)) { const mainFrame = resourceTreeModel.mainFrame; const result = mainFrame ? mainFrame.resourceForURL(url) : null; if (result) { return result; } } return null; } static reloadAllPages(bypassCache, scriptToEvaluateOnLoad) { for (const resourceTreeModel of TargetManager.instance().models(ResourceTreeModel)) { if (resourceTreeModel.target().parentTarget()?.type() !== Type.Frame) { resourceTreeModel.reloadPage(bypassCache, scriptToEvaluateOnLoad); } } } async storageKeyForFrame(frameId) { if (!this.framesInternal.has(frameId)) { return null; } const response = await this.storageAgent.invoke_getStorageKeyForFrame({ frameId: frameId }); if (response.getError() === 'Frame tree node for given frame not found') { return null; } return response.storageKey; } domModel() { return this.target().model(DOMModel); } processCachedResources(mainFramePayload) { // TODO(caseq): the url check below is a mergeable, conservative // workaround for a problem caused by us requesting resources from a // subtarget frame before it has committed. The proper fix is likely // to be too complicated to be safely merged. // See https://crbug.com/1081270 for details. if (mainFramePayload && mainFramePayload.frame.url !== ':') { this.dispatchEventToListeners(Events.WillLoadCachedResources); this.addFramesRecursively(null, mainFramePayload); this.target().setInspectedURL(mainFramePayload.frame.url); } this.#cachedResourcesProcessed = true; const runtimeModel = this.target().model(RuntimeModel); if (runtimeModel) { runtimeModel.setExecutionContextComparator(this.executionContextComparator.bind(this)); runtimeModel.fireExecutionContextOrderChanged(); } this.dispatchEventToListeners(Events.CachedResourcesLoaded, this); } cachedResourcesLoaded() { return this.#cachedResourcesProcessed; } addFrame(frame, _aboutToNavigate) { this.framesInternal.set(frame.id, frame); if (frame.isMainFrame()) { this.mainFrame = frame; } this.dispatchEventToListeners(Events.FrameAdded, frame); this.updateSecurityOrigins(); void this.updateStorageKeys(); } frameAttached(frameId, parentFrameId, stackTrace) { const sameTargetParentFrame = parentFrameId ? (this.framesInternal.get(parentFrameId) || null) : null; // Do nothing unless cached resource tree is processed - it will overwrite everything. if (!this.#cachedResourcesProcessed && sameTargetParentFrame) { return null; } if (this.framesInternal.has(frameId)) { return null; } const frame = new ResourceTreeFrame(this, sameTargetParentFrame, frameId, null, stackTrace || null); if (parentFrameId && !sameTargetParentFrame) { frame.crossTargetParentFrameId = parentFrameId; } if (frame.isMainFrame() && this.mainFrame) { // Navigation to the new backend process. this.frameDetached(this.mainFrame.id, false); } this.addFrame(frame, true); return frame; } frameNavigated(framePayload, type) { const sameTargetParentFrame = framePayload.parentId ? (this.framesInternal.get(framePayload.parentId) || null) : null; // Do nothing unless cached resource tree is processed - it will overwrite everything. if (!this.#cachedResourcesProcessed && sameTargetParentFrame) { return; } let frame = this.framesInternal.get(framePayload.id) || null; if (!frame) { // Simulate missed "frameAttached" for a main frame navigation to the new backend process. frame = this.frameAttached(framePayload.id, framePayload.parentId || null); console.assert(Boolean(frame)); if (!frame) { return; } } this.dispatchEventToListeners(Events.FrameWillNavigate, frame); frame.navigate(framePayload); if (type) { frame.backForwardCacheDetails.restoredFromCache = type === "BackForwardCacheRestore" /* Protocol.Page.NavigationType.BackForwardCacheRestore */; } this.dispatchEventToListeners(Events.FrameNavigated, frame); if (frame.isPrimaryFrame()) { this.primaryPageChanged(frame, "Navigation" /* PrimaryPageChangeType.Navigation */); } // Fill frame with retained resources (the ones loaded using new loader). const resources = frame.resources(); for (let i = 0; i < resources.length; ++i) { this.dispatchEventToListeners(Events.ResourceAdded, resources[i]); } if (frame.isMainFrame()) { this.target().setInspectedURL(frame.url); } this.updateSecurityOrigins(); void this.updateStorageKeys(); if (frame.backForwardCacheDetails.restoredFromCache) { FrameManager.instance().modelRemoved(this); FrameManager.instance().modelAdded(this); void this.#buildResourceTree(); } } primaryPageChanged(frame, type) { this.processPendingEvents(frame); this.dispatchEventToListeners(Events.PrimaryPageChanged, { frame, type }); const networkManager = this.target().model(NetworkManager); if (networkManager && frame.isOutermostFrame()) { networkManager.clearRequests(); } } documentOpened(framePayload) { this.frameNavigated(framePayload, undefined); const frame = this.framesInternal.get(framePayload.id); if (frame && !frame.getResourcesMap().get(framePayload.url)) { const frameResource = this.createResourceFromFramePayload(framePayload, framePayload.url, Common.ResourceType.resourceTypes.Document, framePayload.mimeType, null, null); frameResource.isGenerated = true; frame.addResource(frameResource); } } frameDetached(frameId, isSwap) { // Do nothing unless cached resource tree is processed - it will overwrite everything. if (!this.#cachedResourcesProcessed) { return; } const frame = this.framesInternal.get(frameId); if (!frame) { return; } const sameTargetParentFrame = frame.sameTargetParentFrame(); if (sameTargetParentFrame) { sameTargetParentFrame.removeChildFrame(frame, isSwap); } else { frame.remove(isSwap); } this.updateSecurityOrigins(); void this.updateStorageKeys(); } onRequestFinished(event) { if (!this.#cachedResourcesProcessed) { return; } const request = event.data; if (request.failed) { return; } const frame = request.frameId ? this.framesInternal.get(request.frameId) : null; if (frame) { frame.addRequest(request); } } onRequestUpdateDropped(event) { if (!this.#cachedResourcesProcessed) { return; } const data = event.data; const frameId = data.frameId; if (!frameId) { return; } const frame = this.framesInternal.get(frameId); if (!frame) { return; } const url = data.url; if (frame.getResourcesMap().get(url)) { return; } const resource = new Resource(this, null, url, frame.url, frameId, data.loaderId, Common.ResourceType.resourceTypes[data.resourceType], data.mimeType, data.lastModified, null); frame.addResource(resource); } frameForId(frameId) { return this.framesInternal.get(frameId) || null; } forAllResources(callback) { if (this.mainFrame) { return this.mainFrame.callForFrameResources(callback); } return false; } frames() { return [...this.framesInternal.values()]; } resourceForURL(url) { // Workers call into this with no #frames available. return this.mainFrame ? this.mainFrame.resourceForURL(url) : null; } addFramesRecursively(sameTargetParentFrame, frameTreePayload) { const framePayload = frameTreePayload.frame; let frame = this.framesInternal.get(framePayload.id); if (!frame) { frame = new ResourceTreeFrame(this, sameTargetParentFrame, framePayload.id, framePayload, null); } if (!sameTargetParentFrame && framePayload.parentId) { frame.crossTargetParentFrameId = framePayload.parentId; } this.addFrame(frame); for (const childFrame of frameTreePayload.childFrames || []) { this.addFramesRecursively(frame, childFrame); } for (let i = 0; i < frameTreePayload.resources.length; ++i) { const subresource = frameTreePayload.resources[i]; const resource = this.createResourceFromFramePayload(framePayload, subresource.url, Common.ResourceType.resourceTypes[subresource.type], subresource.mimeType, subresource.lastModified || null, subresource.contentSize || null); frame.addResource(resource); } if (!frame.getResourcesMap().get(framePayload.url)) { const frameResource = this.createResourceFromFramePayload(framePayload, framePayload.url, Common.ResourceType.resourceTypes.Document, framePayload.mimeType, null, null); frame.addResource(frameResource); } } createResourceFromFramePayload(frame, url, type, mimeType, lastModifiedTime, contentSize) { const lastModified = typeof lastModifiedTime === 'number' ? new Date(lastModifiedTime * 1000) : null; return new Resource(this, null, url, frame.url, frame.id, frame.loaderId, type, mimeType, lastModified, contentSize); } suspendReload() { this.#reloadSuspensionCount++; } resumeReload() { this.#reloadSuspensionCount--; console.assert(this.#reloadSuspensionCount >= 0, 'Unbalanced call to ResourceTreeModel.resumeReload()'); if (!this.#reloadSuspensionCount && this.#pendingReloadOptions) { const { ignoreCache, scriptToEvaluateOnLoad } = this.#pendingReloadOptions; this.reloadPage(ignoreCache, scriptToEvaluateOnLoad); } } reloadPage(ignoreCache, scriptToEvaluateOnLoad) { // Only dispatch PageReloadRequested upon first reload request to simplify client logic. if (!this.#pendingReloadOptions) { this.dispatchEventToListeners(Events.PageReloadRequested, this); } if (this.#reloadSuspensionCount) { this.#pendingReloadOptions = { ignoreCache, scriptToEvaluateOnLoad }; return; } this.#pendingReloadOptions = null; const networkManager = this.target().model(NetworkManager); if (networkManager) { networkManager.clearRequests(); } this.dispatchEventToListeners(Events.WillReloadPage); void this.agent.invoke_reload({ ignoreCache, scriptToEvaluateOnLoad }); } // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration // eslint-disable-next-line @typescript-eslint/no-explicit-any navigate(url) { return this.agent.invoke_navigate({ url }); } async navigationHistory() { const response = await this.agent.invoke_getNavigationHistory(); if (response.getError()) { return null; } return { currentIndex: response.currentIndex, entries: response.entries }; } navigateToHistoryEntry(entry) { void this.agent.invoke_navigateToHistoryEntry({ entryId: entry.id }); } setLifecycleEventsEnabled(enabled) { return this.agent.invoke_setLifecycleEventsEnabled({ enabled }); } async fetchAppManifest() { const response = await this.agent.invoke_getAppManifest(); if (response.getError()) { return { url: response.url, data: null, errors: [] }; } return { url: response.url, data: response.data || null, errors: response.errors }; } async getInstallabilityErrors() { const response = await this.agent.invoke_getInstallabilityErrors(); return response.installabilityErrors || []; } async getAppId() { return this.agent.invoke_getAppId(); } executionContextComparator(a, b) { function framePath(frame) { let currentFrame = frame; const parents = []; while (currentFrame) { parents.push(currentFrame); currentFrame = currentFrame.sameTargetParentFrame(); } return parents.reverse(); } if (a.target() !== b.target()) { return ExecutionContext.comparator(a, b); } const framesA = a.frameId ? framePath(this.frameForId(a.frameId)) : []; const framesB = b.frameId ? framePath(this.frameForId(b.frameId)) : []; let frameA; let frameB; for (let i = 0;; i++) { if (!framesA[i] || !framesB[i] || (framesA[i] !== framesB[i])) { frameA = framesA[i]; frameB = framesB[i]; break; } } if (!frameA && frameB) { return -1; } if (!frameB && frameA) { return 1; } if (frameA && frameB) { return frameA.id.localeCompare(frameB.id); } return ExecutionContext.comparator(a, b); } getSecurityOriginData() { const securityOrigins = new Set(); let mainSecurityOrigin = null; let unreachableMainSecurityOrigin = null; for (const frame of this.framesInternal.values()) { const origin = frame.securityOrigin; if (!origin) { continue; } securityOrigins.add(origin); if (frame.isMainFrame()) { mainSecurityOrigin = origin; if (frame.unreachableUrl()) { const unreachableParsed = new Common.ParsedURL.ParsedURL(frame.unreachableUrl()); unreachableMainSecurityOrigin = unreachableParsed.securityOrigin(); } } } return { securityOrigins: securityOrigins, mainSecurityOrigin: mainSecurityOrigin, unreachableMainSecurityOrigin: unreachableMainSecurityOrigin, }; } async getStorageKeyData() { const storageKeys = new Set(); let mainStorageKey = null; for (const { isMainFrame, storageKey } of await Promise.all([...this.framesInternal.values()].map(f => f.getStorageKey(/* forceFetch */ false).then(k => ({ isMainFrame: f.isMainFrame(), storageKey: k, }))))) { if (isMainFrame) { mainStorageKey = storageKey; } if (storageKey) { storageKeys.add(storageKey); } } return { storageKeys: storageKeys, mainStorageKey: mainStorageKey }; } updateSecurityOrigins() { const data = this.getSecurityOriginData(); this.#securityOriginManager.setMainSecurityOrigin(data.mainSecurityOrigin || '', data.unreachableMainSecurityOrigin || ''); this.#securityOriginManager.updateSecurityOrigins(data.securityOrigins); } async updateStorageKeys() { const data = await this.getStorageKeyData(); this.#storageKeyManager.setMainStorageKey(data.mainStorageKey || ''); this.#storageKeyManager.updateStorageKeys(data.storageKeys); } async getMainStorageKey() { return this.mainFrame ? this.mainFrame.getStorageKey(/* forceFetch */ false) : null; } getMainSecurityOrigin() { const data = this.getSecurityOriginData(); return data.mainSecurityOrigin || data.unreachableMainSecurityOrigin; } onBackForwardCacheNotUsed(event) { if (this.mainFrame && this.mainFrame.id === event.frameId && this.mainFrame.loaderId === event.loaderId) { this.mainFrame.setBackForwardCacheDetails(event); this.dispatchEventToListeners(Events.BackForwardCacheDetailsUpdated, this.mainFrame); } else { this.#pendingBackForwardCacheNotUsedEvents.add(event); } } onPrerenderAttemptCompleted(event) { if (this.mainFrame && this.mainFrame.id === event.initiatingFrameId) { this.mainFrame.setPrerenderFinalStatus(event.finalStatus); this.dispatchEventToListeners(Events.PrerenderingStatusUpdated, this.mainFrame); if (event.disallowedApiMethod) { this.mainFrame.setPrerenderDisallowedApiMethod(event.disallowedApiMethod); } } else { this.#pendingPrerenderAttemptCompletedEvents.add(event); } this.dispatchEventToListeners(Events.PrerenderAttemptCompleted, event); } processPendingEvents(frame) { if (!frame.isMainFrame()) { return; } for (const event of this.#pendingBackForwardCacheNotUsedEvents) { if (frame.id === event.frameId && frame.loaderId === event.loaderId) { frame.setBackForwardCacheDetails(event); this.#pendingBackForwardCacheNotUsedEvents.delete(event); break; } } for (const event of this.#pendingPrerenderAttemptCompletedEvents) { if (frame.id === event.initiatingFrameId) { frame.setPrerenderFinalStatus(event.finalStatus); if (event.disallowedApiMethod) { frame.setPrerenderDisallowedApiMethod(event.disallowedApiMethod); } this.#pendingPrerenderAttemptCompletedEvents.delete(event); break; } } // No need to dispatch events here as this method call is followed by a `PrimaryPageChanged` event. } } // TODO(crbug.com/1167717): Make this a const enum again // eslint-disable-next-line rulesdir/const_enum export var Events; (function (Events) { Events["FrameAdded"] = "FrameAdded"; Events["FrameNavigated"] = "FrameNavigated"; Events["FrameDetached"] = "FrameDetached"; Events["FrameResized"] = "FrameResized"; Events["FrameWillNavigate"] = "FrameWillNavigate"; // Primary page changes can be either main frame navigations or activations of a background frame. // TODO(crbug.com/1393057): Let frame activations trigger this event. Events["PrimaryPageChanged"] = "PrimaryPageChanged"; Events["ResourceAdded"] = "ResourceAdded"; Events["WillLoadCachedResources"] = "WillLoadCachedResources"; Events["CachedResourcesLoaded"] = "CachedResourcesLoaded"; Events["DOMContentLoaded"] = "DOMContentLoaded"; Events["LifecycleEvent"] = "LifecycleEvent"; Events["Load"] = "Load"; Events["PageReloadRequested"] = "PageReloadRequested"; Events["WillReloadPage"] = "WillReloadPage"; Events["InterstitialShown"] = "InterstitialShown"; Events["InterstitialHidden"] = "InterstitialHidden"; Events["BackForwardCacheDetailsUpdated"] = "BackForwardCacheDetailsUpdated"; Events["PrerenderingStatusUpdated"] = "PrerenderingStatusUpdated"; Events["PrerenderAttemptCompleted"] = "PrerenderAttemptCompleted"; Events["JavaScriptDialogOpening"] = "JavaScriptDialogOpening"; })(Events || (Events = {})); export class ResourceTreeFrame { #model; #sameTargetParentFrameInternal; #idInternal; crossTargetParentFrameId; #loaderIdInternal; #nameInternal; #urlInternal; #domainAndRegistryInternal; #securityOriginInternal; #storageKeyInternal; #unreachableUrlInternal; #adFrameStatusInternal; #secureContextType; #crossOriginIsolatedContextType; #gatedAPIFeatures; #creationStackTrace; #creationStackTraceTarget; #childFramesInternal; resourcesMap; backForwardCacheDetails = { restoredFromCache: undefined, explanations: [], explanationsTree: undefined, }; prerenderFinalStatus; prerenderDisallowedApiMethod; constructor(model, parentFrame, frameId, payload, creationStackTrace) { this.#model = model; this.#sameTargetParentFrameInternal = parentFrame; this.#idInternal = frameId; this.crossTargetParentFrameId = null; this.#loaderIdInternal = (payload && payload.loaderId) || ''; this.#nameInternal = payload && payload.name; this.#urlInternal = payload && payload.url || Platform.DevToolsPath.EmptyUrlString; this.#domainAndRegistryInternal = (payload && payload.domainAndRegistry) || ''; this.#securityOriginInternal = payload && payload.securityOrigin; this.#unreachableUrlInternal = (payload && payload.unreachableUrl) || Platform.DevToolsPath.EmptyUrlString; this.#adFrameStatusInternal = payload?.adFrameStatus; this.#secureContextType = payload && payload.secureContextType; this.#crossOriginIsolatedContextType = payload && payload.crossOriginIsolatedContextType; this.#gatedAPIFeatures = payload && payload.gatedAPIFeatures; this.#creationStackTrace = creationStackTrace; this.#creationStackTraceTarget = null; this.#childFramesInternal = new Set(); this.resourcesMap = new Map(); this.prerenderFinalStatus = null; this.prerenderDisallowedApiMethod = null; if (this.#sameTargetParentFrameInternal) { this.#sameTargetParentFrameInternal.#childFramesInternal.add(this); } } isSecureContext() { return this.#secureContextType !== null && this.#secureContextType.startsWith('Secure'); } getSecureContextType() { return this.#secureContextType; } isCrossOriginIsolated() { return this.#crossOriginIsolatedContextType !== null && this.#crossOriginIsolatedContextType.startsWith('Isolated'); } getCrossOriginIsolatedContextType() { return this.#crossOriginIsolatedContextType; } getGatedAPIFeatures() { return this.#gatedAPIFeatures; } getCreationStackTraceData() { return { creationStackTrace: this.#creationStackTrace, creationStackTraceTarget: this.#creationStackTraceTarget || this.resourceTreeModel().target(), }; } navigate(framePayload) { this.#loaderIdInternal = framePayload.loaderId; this.#nameInternal = framePayload.name; this.#urlInternal = framePayload.url; this.#domainAndRegistryInternal = framePayload.domainAndRegistry; this.#securityOriginInternal = framePayload.securityOrigin; void this.getStorageKey(/* forceFetch */ true); this.#unreachableUrlInternal = framePayload.unreachableUrl || Platform.DevToolsPath.EmptyUrlString; this.#adFrameStatusInternal = framePayload?.adFrameStatus; this.#secureContextType = framePayload.secureContextType; this.#crossOriginIsolatedContextType = framePayload.crossOriginIsolatedContextType; this.#gatedAPIFeatures = framePayload.gatedAPIFeatures; this.backForwardCacheDetails = { restoredFromCache: undefined, explanations: [], explanationsTree: undefined, }; const mainResource = this.resourcesMap.get(this.#urlInternal); this.resourcesMap.clear(); this.removeChildFrames(); if (mainResource && mainResource.loaderId === this.#loaderIdInternal) { this.addResource(mainResource); } } resourceTreeModel() { return this.#model; } get id() { return this.#idInternal; } get name() { return this.#nameInternal || ''; } get url() { return this.#urlInternal; } domainAndRegistry() { return this.#domainAndRegistryInternal; } async getAdScriptId(frameId) { const res = await this.#model.agent.invoke_getAdScriptId({ frameId }); return res.adScriptId || null; } get securityOrigin() { return this.#securityOriginInternal; } getStorageKey(forceFetch) { if (!this.#storageKeyInternal || forceFetch) { this.#storageKeyInternal = this.#model.storageKeyForFrame(this.#idInternal); } return this.#storageKeyInternal; } unreachableUrl() { return this.#unreachableUrlInternal; } get loaderId() { return this.#loaderIdInternal; } adFrameType() { return this.#adFrameStatusInternal?.adFrameType || "none" /* Protocol.Page.AdFrameType.None */; } adFrameStatus() { return this.#adFrameStatusInternal; } get childFrames() { return [...this.#childFramesInternal]; } /** * Returns the parent frame if both #frames are part of the same process/target. */ sameTargetParentFrame() { return this.#sameTargetParentFrameInternal; } /** * Returns the parent frame if both #frames are part of different processes/targets (child is an OOPIF). */ crossTargetParentFrame() { if (!this.crossTargetParentFrameId) { return null; } const parentTarget = this.#model.target().parentTarget(); if (parentTarget?.type() !== Type.Frame) { return null; } const parentModel = parentTarget.model(ResourceTreeModel); if (!parentModel) { return null; } // Note that parent #model has already processed cached resources: // - when parent target was created, we issued getResourceTree call; // - strictly after we issued setAutoAttach call; // - both of them were handled in renderer in the same order; // - cached resource tree got processed on parent #model; // - child target was created as a result of setAutoAttach call. return parentModel.framesInternal.get(this.crossTargetParentFrameId) || null; } /** * Returns the parent frame. There is only 1 parent and it's either in the * same target or it's cross-target. */ parentFrame() { return this.sameTargetParentFrame() || this.crossTargetParentFrame(); } /** * Returns true if this is the main frame of its target. A main frame is the root of the frame tree i.e. a frame without * a parent, but the whole frame tree could be embedded in another frame tree (e.g. OOPIFs, fenced frames, portals). * https://chromium.googlesource.com/chromium/src/+/HEAD/docs/frame_trees.md */ isMainFrame() { return !this.#sameTargetParentFrameInternal; } /** * Returns true if this is a main frame which is not embedded in another frame tree. With MPArch features such as * back/forward cache or prerender there can be multiple outermost frames. * https://chromium.googlesource.com/chromium/src/+/HEAD/docs/frame_trees.md */ isOutermostFrame() { return this.#model.target().parentTarget()?.type() !== Type.Frame && !this.#sameTargetParentFrameInternal && !this.crossTargetParentFrameId; } /** * Returns true is this is the primary frame of the browser tab. There can only be one primary frame for each * browser tab. It is the outermost frame being actively displayed in the browser tab. * https://chromium.googlesource.com/chromium/src/+/HEAD/docs/frame_trees.md */ isPrimaryFrame() { return !this.#sameTargetParentFrameInternal && this.#model.target() === TargetManager.instance().primaryPageTarget(); } removeChildFrame(frame, isSwap) { this.#childFramesInternal.delete(frame); frame.remove(isSwap); } removeChildFrames() { const frames = this.#childFramesInternal; this.#childFramesInternal = new Set(); for (const frame of frames) { frame.remove(false); } } remove(isSwap) { this.removeChildFrames(); this.#model.framesInternal.delete(this.id); this.#model.dispatchEventToListeners(Events.FrameDetached, { frame: this, isSwap }); } addResource(resource) { if (this.resourcesMap.get(resource.url) === resource) { // Already in the tree, we just got an extra update. return; } this.resourcesMap.set(resource.url, resource); this.#model.dispatchEventToListeners(Events.ResourceAdded, resource); } addRequest(request) { let resource = this.resourcesMap.get(request.url()); if (resource && resource.request === request) { // Already in the tree, we just got an extra update. return; } resource = new Resource(this.#model, request, request.url(), request.documentURL, request.frameId, request.loaderId, request.resourceType(), request.mimeType, null, null); this.resourcesMap.set(resource.url, resource); this.#model.dispatchEventToListeners(Events.ResourceAdded, resource); } resources() { return Array.from(this.resourcesMap.values()); } resourceForURL(url) { const resource = this.resourcesMap.get(url); if (resource) { return resource; } for (const frame of this.#childFramesInternal) { const resource = frame.resourceForURL(url); if (resource) { return resource; } } return null; } callForFrameResources(callback) { for (const resource of this.resourcesMap.values()) { if (callback(resource)) { return true; } } for (const frame of this.#childFramesInternal) { if (frame.callForFrameResources(callback)) { return true; } } return false; } displayName() { if (this.isOutermostFrame()) { return i18n.i18n.lockedString('top'); } const subtitle = new Common.ParsedURL.ParsedURL(this.#urlInternal).displayName; if (subtitle) { if (!this.#nameInternal) { return subtitle; } return this.#nameInternal + ' (' + subtitle + ')'; } return i18n.i18n.lockedString('iframe'); } async getOwnerDeferredDOMNode() { const parentFrame = this.parentFrame(); if (!parentFrame) { return null; } return parentFrame.resourceTreeModel().domModel().getOwnerNodeForFrame(this.#idInternal); } async getOwnerDOMNodeOrDocument() { const deferredNode = await this.getOwnerDeferredDOMNode(); if (deferredNode) { return deferredNode.resolvePromise(); } if (this.isOutermostFrame()) { return this.resourceTreeModel().domModel().requestDocument(); } return null; } async highlight() { const parentFrame = this.parentFrame(); const parentTarget = this.resourceTreeModel().target().parentTarget(); const highlightFrameOwner = async (domModel) => { const deferredNode = await domModel.getOwnerNodeForFrame(this.#idInternal); if (deferredNode) { domModel.overlayModel().highlightInOverlay({ deferredNode, selectorList: '' }, 'all', true); } }; if (parentFrame) { return highlightFrameOwner(parentFrame.resourceTreeModel().domModel()); } // Portals. if (parentTarget?.type() === Type.Frame) { const domModel = parentTarget.model(DOMModel); if (domModel) { return highlightFrameOwner(domModel); } } // For the outermost frame there is no owner node. Highlight the whole #document instead. const document = await this.resourceTreeModel().domModel().requestDocument(); if (document) { this.resourceTreeModel().domModel().overlayModel().highlightInOverlay({ node: document, selectorList: '' }, 'all', true); } } async getPermissionsPolicyState() { const response = await this.resourceTreeModel().target().pageAgent().invoke_getPermissionsPolicyState({ frameId: this.#idInternal }); if (response.getError()) { return null; } return response.states; } async getOriginTrials() { const response = await this.resourceTreeModel().target().pageAgent().invoke_getOriginTrials({ frameId: this.#idInternal }); if (response.getError()) { return []; } return response.originTrials; } setCreationStackTrace(creationStackTraceData) { this.#creationStackTrace = creationStackTraceData.creationStackTrace; this.#creationStackTraceTarget = creationStackTraceData.creationStackTraceTarget; } setBackForwardCacheDetails(event) { this.backForwardCacheDetails.restoredFromCache = false; this.backForwardCacheDetails.explanations = event.notRestoredExplanations; this.backForwardCacheDetails.explanationsTree = event.notRestoredExplanationsTree; } getResourcesMap() { return this.resourcesMap; } setPrerenderFinalStatus(status) { this.prerenderFinalStatus = status; } setPrerenderDisallowedApiMethod(disallowedApiMethod) { this.prerenderDisallowedApiMethod = disallowedApiMethod; } } export class PageDispatcher { #resourceTreeModel; constructor(resourceTreeModel) { this.#resourceTreeModel = resourceTreeModel; } backForwardCacheNotUsed(params) { this.#resourceTreeModel.onBackForwardCacheNotUsed(params); } domContentEventFired({ timestamp }) { this.#resourceTreeModel.dispatchEventToListeners(Events.DOMContentLoaded, timestamp); } loadEventFired({ timestamp }) { this.#resourceTreeModel.dispatchEventToListeners(Events.Load, { resourceTreeModel: this.#resourceTreeModel, loadTime: timestamp }); } lifecycleEvent({ frameId, name }) { this.#resourceTreeModel.dispatchEventToListeners(Events.LifecycleEvent, { frameId, name }); } frameAttached({ frameId, parentFrameId, stack }) { this.#resourceTreeModel.frameAttached(frameId, parentFrameId, stack); } frameNavigated({ frame, type }) { this.#resourceTreeModel.frameNavigated(frame, type); } documentOpened({ frame }) { this.#resourceTreeModel.documentOpened(frame); } frameDetached({ frameId, reason }) { this.#resourceTreeModel.frameDetached(frameId, reason === "swap" /* Protocol.Page.FrameDetachedEventReason.Swap */); } frameStartedLoading({}) { } frameStoppedLoading({}) { } frameRequestedNavigation({}) { } frameScheduledNavigation({}) { } frameClearedScheduledNavigation({}) { } navigatedWithinDocument({}) { } frameResized() { this.#resourceTreeModel.dispatchEventToListeners(Events.FrameResized); } javascriptDialogOpening(event) { this.#resourceTreeModel.dispatchEventToListeners(Events.JavaScriptDialogOpening, event); if (!event.hasBrowserHandler) { void this.#resourceTreeModel.agent.invoke_handleJavaScriptDialog({ accept: false }); } } javascriptDialogClosed({}) { } screencastFrame({}) { } screencastVisibilityChanged({}) { } interstitialShown() { this.#resourceTreeModel.isInterstitialShowing = true; this.#resourceTreeModel.dispatchEventToListeners(Events.InterstitialShown); } interstitialHidden() { this.#resourceTreeModel.isInterstitialShowing = false; this.#resourceTreeModel.dispatchEventToListeners(Events.InterstitialHidden); } windowOpen({}) { } compilationCacheProduced({}) { } fileChooserOpened({}) { } downloadWillBegin({}) { } downloadProgress() { } } class PreloadDispatcher { #resourceTreeModel; constructor(resourceTreeModel) { this.#resourceTreeModel = resourceTreeModel; } ruleSetUpdated(_event) { } ruleSetRemoved(_event) { } prerenderAttemptCompleted(params) { this.#resourceTreeModel.onPrerenderAttemptCompleted(params); } prefetchStatusUpdated(_event) { } prerenderStatusUpdated(_event) { } preloadEnabledStateUpdated(_event) { } preloadingAttemptSourcesUpdated() { } } SDKModel.register(ResourceTreeModel, { capabilities: Capability.DOM, autostart: true, early: true }); //# sourceMappingURL=ResourceTreeModel.js.map