@quick-game/cli
Version:
Command line interface for rapid qg development
241 lines • 10.8 kB
JavaScript
// 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