UNPKG

@quick-game/cli

Version:

Command line interface for rapid qg development

241 lines 10.8 kB
// Copyright 2020 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. import * as Common from '../common/common.js'; import * as Host from '../host/host.js'; import * as i18n from '../i18n/i18n.js'; import { FrameManager } from './FrameManager.js'; import { IOModel } from './IOModel.js'; import { MultitargetNetworkManager, NetworkManager } from './NetworkManager.js'; import { Events as ResourceTreeModelEvents, ResourceTreeModel, } from './ResourceTreeModel.js'; import { TargetManager } from './TargetManager.js'; const UIStrings = { /** *@description Error message for canceled source map loads */ loadCanceledDueToReloadOf: 'Load canceled due to reload of inspected page', }; const str_ = i18n.i18n.registerUIStrings('core/sdk/PageResourceLoader.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); let pageResourceLoader = null; /** * The page resource loader is a bottleneck for all DevTools-initiated resource loads. For each such load, it keeps a * `PageResource` object around that holds meta information. This can be as the basis for reporting to the user which * resources were loaded, and whether there was a load error. */ export class PageResourceLoader extends Common.ObjectWrapper.ObjectWrapper { #currentlyLoading; #maxConcurrentLoads; #pageResources; #queuedLoads; #loadOverride; constructor(loadOverride, maxConcurrentLoads) { super(); this.#currentlyLoading = 0; this.#maxConcurrentLoads = maxConcurrentLoads; this.#pageResources = new Map(); this.#queuedLoads = []; TargetManager.instance().addModelListener(ResourceTreeModel, ResourceTreeModelEvents.PrimaryPageChanged, this.onPrimaryPageChanged, this); this.#loadOverride = loadOverride; } static instance({ forceNew, loadOverride, maxConcurrentLoads } = { forceNew: false, loadOverride: null, maxConcurrentLoads: 500, }) { if (!pageResourceLoader || forceNew) { pageResourceLoader = new PageResourceLoader(loadOverride, maxConcurrentLoads); } return pageResourceLoader; } static removeInstance() { pageResourceLoader = null; } onPrimaryPageChanged(event) { const mainFrame = event.data.frame; if (!mainFrame.isOutermostFrame()) { return; } for (const { reject } of this.#queuedLoads) { reject(new Error(i18nString(UIStrings.loadCanceledDueToReloadOf))); } this.#queuedLoads = []; this.#pageResources.clear(); this.dispatchEventToListeners(Events.Update); } getResourcesLoaded() { return this.#pageResources; } /** * Loading is the number of currently loading and queued items. Resources is the total number of resources, * including loading and queued resources, but not including resources that are still loading but scheduled * for cancelation.; */ getNumberOfResources() { return { loading: this.#currentlyLoading, queued: this.#queuedLoads.length, resources: this.#pageResources.size }; } async acquireLoadSlot() { this.#currentlyLoading++; if (this.#currentlyLoading > this.#maxConcurrentLoads) { const entry = { resolve: () => { }, reject: () => { } }; const waitForCapacity = new Promise((resolve, reject) => { entry.resolve = resolve; entry.reject = reject; }); this.#queuedLoads.push(entry); await waitForCapacity; } } releaseLoadSlot() { this.#currentlyLoading--; const entry = this.#queuedLoads.shift(); if (entry) { entry.resolve(); } } static makeKey(url, initiator) { if (initiator.frameId) { return `${url}-${initiator.frameId}`; } if (initiator.target) { return `${url}-${initiator.target.id()}`; } throw new Error('Invalid initiator'); } async loadResource(url, initiator) { const key = PageResourceLoader.makeKey(url, initiator); const pageResource = { success: null, size: null, errorMessage: undefined, url, initiator }; this.#pageResources.set(key, pageResource); this.dispatchEventToListeners(Events.Update); try { await this.acquireLoadSlot(); const resultPromise = this.dispatchLoad(url, initiator); const result = await resultPromise; pageResource.errorMessage = result.errorDescription.message; pageResource.success = result.success; if (result.success) { pageResource.size = result.content.length; return { content: result.content }; } throw new Error(result.errorDescription.message); } catch (e) { if (pageResource.errorMessage === undefined) { pageResource.errorMessage = e.message; } if (pageResource.success === null) { pageResource.success = false; } throw e; } finally { this.releaseLoadSlot(); this.dispatchEventToListeners(Events.Update); } } async dispatchLoad(url, initiator) { let failureReason = null; if (this.#loadOverride) { return this.#loadOverride(url); } const parsedURL = new Common.ParsedURL.ParsedURL(url); const eligibleForLoadFromTarget = getLoadThroughTargetSetting().get() && parsedURL && parsedURL.scheme !== 'file' && parsedURL.scheme !== 'data' && parsedURL.scheme !== 'devtools'; Host.userMetrics.developerResourceScheme(this.getDeveloperResourceScheme(parsedURL)); // if (eligibleForLoadFromTarget) { // try { // if (initiator.target) { // Host.userMetrics.developerResourceLoaded(Host.UserMetrics.DeveloperResourceLoaded.LoadThroughPageViaTarget); // const result = await this.loadFromTarget(initiator.target, initiator.frameId, url); // return result; // } // const frame = FrameManager.instance().getFrame(initiator.frameId); // if (frame) { // Host.userMetrics.developerResourceLoaded(Host.UserMetrics.DeveloperResourceLoaded.LoadThroughPageViaFrame); // const result = await this.loadFromTarget(frame.resourceTreeModel().target(), initiator.frameId, url); // return result; // } // } // catch (e) { // if (e instanceof Error) { // Host.userMetrics.developerResourceLoaded(Host.UserMetrics.DeveloperResourceLoaded.LoadThroughPageFailure); // failureReason = e.message; // } // } // Host.userMetrics.developerResourceLoaded(Host.UserMetrics.DeveloperResourceLoaded.LoadThroughPageFallback); // console.warn('Fallback triggered', url, initiator); // } // else { const code = getLoadThroughTargetSetting().get() ? Host.UserMetrics.DeveloperResourceLoaded.FallbackPerProtocol : Host.UserMetrics.DeveloperResourceLoaded.FallbackPerOverride; Host.userMetrics.developerResourceLoaded(code); // } const result = await MultitargetNetworkManager.instance().loadResource(url); if (eligibleForLoadFromTarget && !result.success) { Host.userMetrics.developerResourceLoaded(Host.UserMetrics.DeveloperResourceLoaded.FallbackFailure); } if (failureReason) { // In case we have a success, add a note about why the load through the target failed. result.errorDescription.message = `Fetch through target failed: ${failureReason}; Fallback: ${result.errorDescription.message}`; } return result; } getDeveloperResourceScheme(parsedURL) { if (!parsedURL || parsedURL.scheme === '') { return Host.UserMetrics.DeveloperResourceScheme.SchemeUnknown; } const isLocalhost = parsedURL.host === 'localhost' || parsedURL.host.endsWith('.localhost'); switch (parsedURL.scheme) { case 'file': return Host.UserMetrics.DeveloperResourceScheme.SchemeFile; case 'data': return Host.UserMetrics.DeveloperResourceScheme.SchemeData; case 'blob': return Host.UserMetrics.DeveloperResourceScheme.SchemeBlob; case 'http': return isLocalhost ? Host.UserMetrics.DeveloperResourceScheme.SchemeHttpLocalhost : Host.UserMetrics.DeveloperResourceScheme.SchemeHttp; case 'https': return isLocalhost ? Host.UserMetrics.DeveloperResourceScheme.SchemeHttpsLocalhost : Host.UserMetrics.DeveloperResourceScheme.SchemeHttps; } return Host.UserMetrics.DeveloperResourceScheme.SchemeOther; } async loadFromTarget(target, frameId, url) { const networkManager = target.model(NetworkManager); const ioModel = target.model(IOModel); const disableCache = Common.Settings.Settings.instance().moduleSetting('cacheDisabled').get(); const resource = await networkManager.loadNetworkResource(frameId, url, { disableCache, includeCredentials: true }); try { const content = resource.stream ? await ioModel.readToString(resource.stream) : ''; return { success: resource.success, content, errorDescription: { statusCode: resource.httpStatusCode || 0, netError: resource.netError, netErrorName: resource.netErrorName, message: Host.ResourceLoader.netErrorToMessage(resource.netError, resource.httpStatusCode, resource.netErrorName) || '', urlValid: undefined, }, }; } finally { if (resource.stream) { void ioModel.close(resource.stream); } } } } export function getLoadThroughTargetSetting() { return Common.Settings.Settings.instance().createSetting('loadThroughTarget', true); } // TODO(crbug.com/1167717): Make this a const enum again // eslint-disable-next-line rulesdir/const_enum export var Events; (function (Events) { Events["Update"] = "Update"; })(Events || (Events = {})); //# sourceMappingURL=PageResourceLoader.js.map