@quick-game/cli
Version:
Command line interface for rapid qg development
367 lines • 16.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 '../../core/common/common.js';
import * as SDK from '../../core/sdk/sdk.js';
import { AttributionReportingIssue } from './AttributionReportingIssue.js';
import { BounceTrackingIssue } from './BounceTrackingIssue.js';
import { ClientHintIssue } from './ClientHintIssue.js';
import { ContentSecurityPolicyIssue } from './ContentSecurityPolicyIssue.js';
import { CorsIssue } from './CorsIssue.js';
import { CrossOriginEmbedderPolicyIssue, isCrossOriginEmbedderPolicyIssue } from './CrossOriginEmbedderPolicyIssue.js';
import { DeprecationIssue } from './DeprecationIssue.js';
import { FederatedAuthRequestIssue } from './FederatedAuthRequestIssue.js';
import { FederatedAuthUserInfoRequestIssue } from './FederatedAuthUserInfoRequestIssue.js';
import { GenericIssue } from './GenericIssue.js';
import { HeavyAdIssue } from './HeavyAdIssue.js';
import { LowTextContrastIssue } from './LowTextContrastIssue.js';
import { MixedContentIssue } from './MixedContentIssue.js';
import { QuirksModeIssue } from './QuirksModeIssue.js';
import { CookieIssue } from './CookieIssue.js';
import { SharedArrayBufferIssue } from './SharedArrayBufferIssue.js';
import { SourceFrameIssuesManager } from './SourceFrameIssuesManager.js';
import { StylesheetLoadingIssue } from './StylesheetLoadingIssue.js';
let issuesManagerInstance = null;
function createIssuesForBlockedByResponseIssue(issuesModel, inspectorIssue) {
const blockedByResponseIssueDetails = inspectorIssue.details.blockedByResponseIssueDetails;
if (!blockedByResponseIssueDetails) {
console.warn('BlockedByResponse issue without details received.');
return [];
}
if (isCrossOriginEmbedderPolicyIssue(blockedByResponseIssueDetails.reason)) {
return [new CrossOriginEmbedderPolicyIssue(blockedByResponseIssueDetails, issuesModel)];
}
return [];
}
const issueCodeHandlers = new Map([
[
"CookieIssue" /* Protocol.Audits.InspectorIssueCode.CookieIssue */,
CookieIssue.fromInspectorIssue,
],
[
"MixedContentIssue" /* Protocol.Audits.InspectorIssueCode.MixedContentIssue */,
MixedContentIssue.fromInspectorIssue,
],
[
"HeavyAdIssue" /* Protocol.Audits.InspectorIssueCode.HeavyAdIssue */,
HeavyAdIssue.fromInspectorIssue,
],
[
"ContentSecurityPolicyIssue" /* Protocol.Audits.InspectorIssueCode.ContentSecurityPolicyIssue */,
ContentSecurityPolicyIssue.fromInspectorIssue,
],
["BlockedByResponseIssue" /* Protocol.Audits.InspectorIssueCode.BlockedByResponseIssue */, createIssuesForBlockedByResponseIssue],
[
"SharedArrayBufferIssue" /* Protocol.Audits.InspectorIssueCode.SharedArrayBufferIssue */,
SharedArrayBufferIssue.fromInspectorIssue,
],
[
"LowTextContrastIssue" /* Protocol.Audits.InspectorIssueCode.LowTextContrastIssue */,
LowTextContrastIssue.fromInspectorIssue,
],
[
"CorsIssue" /* Protocol.Audits.InspectorIssueCode.CorsIssue */,
CorsIssue.fromInspectorIssue,
],
[
"QuirksModeIssue" /* Protocol.Audits.InspectorIssueCode.QuirksModeIssue */,
QuirksModeIssue.fromInspectorIssue,
],
[
"AttributionReportingIssue" /* Protocol.Audits.InspectorIssueCode.AttributionReportingIssue */,
AttributionReportingIssue.fromInspectorIssue,
],
[
"GenericIssue" /* Protocol.Audits.InspectorIssueCode.GenericIssue */,
GenericIssue.fromInspectorIssue,
],
[
"DeprecationIssue" /* Protocol.Audits.InspectorIssueCode.DeprecationIssue */,
DeprecationIssue.fromInspectorIssue,
],
[
"ClientHintIssue" /* Protocol.Audits.InspectorIssueCode.ClientHintIssue */,
ClientHintIssue.fromInspectorIssue,
],
[
"FederatedAuthRequestIssue" /* Protocol.Audits.InspectorIssueCode.FederatedAuthRequestIssue */,
FederatedAuthRequestIssue.fromInspectorIssue,
],
[
"BounceTrackingIssue" /* Protocol.Audits.InspectorIssueCode.BounceTrackingIssue */,
BounceTrackingIssue.fromInspectorIssue,
],
[
"StylesheetLoadingIssue" /* Protocol.Audits.InspectorIssueCode.StylesheetLoadingIssue */,
StylesheetLoadingIssue.fromInspectorIssue,
],
[
"FederatedAuthUserInfoRequestIssue" /* Protocol.Audits.InspectorIssueCode.FederatedAuthUserInfoRequestIssue */,
FederatedAuthUserInfoRequestIssue.fromInspectorIssue,
],
]);
/**
* Each issue reported by the backend can result in multiple `Issue` instances.
* Handlers are simple functions hard-coded into a map.
*/
function createIssuesFromProtocolIssue(issuesModel, inspectorIssue) {
const handler = issueCodeHandlers.get(inspectorIssue.code);
if (handler) {
return handler(issuesModel, inspectorIssue);
}
console.warn(`No handler registered for issue code ${inspectorIssue.code}`);
return [];
}
export function defaultHideIssueByCodeSetting() {
const setting = {};
return setting;
}
export function getHideIssueByCodeSetting() {
return Common.Settings.Settings.instance().createSetting('HideIssueByCodeSetting-Experiment-2021', defaultHideIssueByCodeSetting());
}
/**
* The `IssuesManager` is the central storage for issues. It collects issues from all the
* `IssuesModel` instances in the page, and deduplicates them wrt their primary key.
* It also takes care of clearing the issues when it sees a main-frame navigated event.
* Any client can subscribe to the events provided, and/or query the issues via the public
* interface.
*
* Additionally, the `IssuesManager` can filter Issues. All Issues are stored, but only
* Issues that are accepted by the filter cause events to be fired or are returned by
* `IssuesManager#issues()`.
*/
export class IssuesManager extends Common.ObjectWrapper.ObjectWrapper {
showThirdPartyIssuesSetting;
hideIssueSetting;
#eventListeners = new WeakMap();
#allIssues = new Map();
#filteredIssues = new Map();
#issueCounts = new Map();
#hiddenIssueCount = new Map();
#hasSeenPrimaryPageChanged = false;
#issuesById = new Map();
#issuesByOutermostTarget = new Map();
constructor(showThirdPartyIssuesSetting, hideIssueSetting) {
super();
this.showThirdPartyIssuesSetting = showThirdPartyIssuesSetting;
this.hideIssueSetting = hideIssueSetting;
new SourceFrameIssuesManager(this);
SDK.TargetManager.TargetManager.instance().observeModels(SDK.IssuesModel.IssuesModel, this);
SDK.TargetManager.TargetManager.instance().addModelListener(SDK.ResourceTreeModel.ResourceTreeModel, SDK.ResourceTreeModel.Events.PrimaryPageChanged, this.#onPrimaryPageChanged, this);
SDK.FrameManager.FrameManager.instance().addEventListener(SDK.FrameManager.Events.FrameAddedToTarget, this.#onFrameAddedToTarget, this);
// issueFilter uses the 'showThirdPartyIssues' setting. Clients of IssuesManager need
// a full update when the setting changes to get an up-to-date issues list.
this.showThirdPartyIssuesSetting?.addChangeListener(() => this.#updateFilteredIssues());
this.hideIssueSetting?.addChangeListener(() => this.#updateFilteredIssues());
SDK.TargetManager.TargetManager.instance().observeTargets({
targetAdded: (target) => {
if (target.outermostTarget() === target) {
this.#updateFilteredIssues();
}
},
targetRemoved: (_) => { },
}, { scoped: true });
}
static instance(opts = {
forceNew: false,
ensureFirst: false,
}) {
if (issuesManagerInstance && opts.ensureFirst) {
throw new Error('IssuesManager was already created. Either set "ensureFirst" to false or make sure that this invocation is really the first one.');
}
if (!issuesManagerInstance || opts.forceNew) {
issuesManagerInstance = new IssuesManager(opts.showThirdPartyIssuesSetting, opts.hideIssueSetting);
}
return issuesManagerInstance;
}
static removeInstance() {
issuesManagerInstance = null;
}
/**
* Once we have seen at least one `PrimaryPageChanged` event, we can be reasonably sure
* that we also collected issues that were reported during the navigation to the current
* page. If we haven't seen a main frame navigated, we might have missed issues that arose
* during navigation.
*/
reloadForAccurateInformationRequired() {
return !this.#hasSeenPrimaryPageChanged;
}
#onPrimaryPageChanged(event) {
const { frame, type } = event.data;
const keptIssues = new Map();
for (const [key, issue] of this.#allIssues.entries()) {
if (issue.isAssociatedWithRequestId(frame.loaderId)) {
keptIssues.set(key, issue);
// Keep issues for prerendered target alive in case of prerender-activation.
}
else if ((type === "Activation" /* SDK.ResourceTreeModel.PrimaryPageChangeType.Activation */) &&
(frame.resourceTreeModel().target() === issue.model()?.target())) {
keptIssues.set(key, issue);
// Keep BounceTrackingIssues alive for non-user-initiated navigations.
}
else if (issue.code() === "BounceTrackingIssue" /* Protocol.Audits.InspectorIssueCode.BounceTrackingIssue */ ||
issue.code() === "CookieIssue" /* Protocol.Audits.InspectorIssueCode.CookieIssue */) {
const networkManager = frame.resourceTreeModel().target().model(SDK.NetworkManager.NetworkManager);
if (networkManager?.requestForLoaderId(frame.loaderId)?.hasUserGesture() ===
false) {
keptIssues.set(key, issue);
}
}
}
this.#allIssues = keptIssues;
this.#hasSeenPrimaryPageChanged = true;
this.#updateFilteredIssues();
}
#onFrameAddedToTarget(event) {
const { frame } = event.data;
// Determining third-party status usually requires the registered domain of the outermost frame.
// When DevTools is opened after navigation has completed, issues may be received
// before the outermost frame is available. Thus, we trigger a recalcuation of third-party-ness
// when we attach to the outermost frame.
if (frame.isOutermostFrame() && SDK.TargetManager.TargetManager.instance().isInScope(frame.resourceTreeModel())) {
this.#updateFilteredIssues();
}
}
modelAdded(issuesModel) {
const listener = issuesModel.addEventListener("IssueAdded" /* SDK.IssuesModel.Events.IssueAdded */, this.#onIssueAddedEvent, this);
this.#eventListeners.set(issuesModel, listener);
}
modelRemoved(issuesModel) {
const listener = this.#eventListeners.get(issuesModel);
if (listener) {
Common.EventTarget.removeEventListeners([listener]);
}
}
#onIssueAddedEvent(event) {
const { issuesModel, inspectorIssue } = event.data;
const issues = createIssuesFromProtocolIssue(issuesModel, inspectorIssue);
for (const issue of issues) {
this.addIssue(issuesModel, issue);
}
}
addIssue(issuesModel, issue) {
// Ignore issues without proper description; they are invisible to the user and only cause confusion.
if (!issue.getDescription()) {
return;
}
const primaryKey = issue.primaryKey();
if (this.#allIssues.has(primaryKey)) {
return;
}
this.#allIssues.set(primaryKey, issue);
const outermostTarget = issuesModel.target().outermostTarget();
if (outermostTarget) {
let issuesForTarget = this.#issuesByOutermostTarget.get(outermostTarget);
if (!issuesForTarget) {
issuesForTarget = new Set();
this.#issuesByOutermostTarget.set(outermostTarget, issuesForTarget);
}
issuesForTarget.add(issue);
}
if (this.#issueFilter(issue)) {
this.#filteredIssues.set(primaryKey, issue);
this.#issueCounts.set(issue.getKind(), 1 + (this.#issueCounts.get(issue.getKind()) || 0));
const issueId = issue.getIssueId();
if (issueId) {
this.#issuesById.set(issueId, issue);
}
const values = this.hideIssueSetting?.get();
this.#updateIssueHiddenStatus(issue, values);
if (issue.isHidden()) {
this.#hiddenIssueCount.set(issue.getKind(), 1 + (this.#hiddenIssueCount.get(issue.getKind()) || 0));
}
this.dispatchEventToListeners("IssueAdded" /* Events.IssueAdded */, { issuesModel, issue });
}
// Always fire the "count" event even if the issue was filtered out.
// The result of `hasOnlyThirdPartyIssues` could still change.
this.dispatchEventToListeners("IssuesCountUpdated" /* Events.IssuesCountUpdated */);
}
issues() {
return this.#filteredIssues.values();
}
numberOfIssues(kind) {
if (kind) {
return (this.#issueCounts.get(kind) ?? 0) - this.numberOfHiddenIssues(kind);
}
return this.#filteredIssues.size - this.numberOfHiddenIssues();
}
numberOfHiddenIssues(kind) {
if (kind) {
return this.#hiddenIssueCount.get(kind) ?? 0;
}
let count = 0;
for (const num of this.#hiddenIssueCount.values()) {
count += num;
}
return count;
}
numberOfAllStoredIssues() {
return this.#allIssues.size;
}
#issueFilter(issue) {
const scopeTarget = SDK.TargetManager.TargetManager.instance().scopeTarget();
if (!scopeTarget) {
return false;
}
if (!this.#issuesByOutermostTarget.get(scopeTarget)?.has(issue)) {
return false;
}
return this.showThirdPartyIssuesSetting?.get() || !issue.isCausedByThirdParty();
}
#updateIssueHiddenStatus(issue, values) {
const code = issue.code();
// All issues are hidden via their code.
// For hiding we check whether the issue code is present and has a value of IssueStatus.Hidden
// assosciated with it. If all these conditions are met the issue is hidden.
// IssueStatus is set in hidden issues menu.
// In case a user wants to hide a specific issue, the issue code is added to "code" section
// of our setting and its value is set to IssueStatus.Hidden. Then issue then gets hidden.
if (values && values[code]) {
if (values[code] === "Hidden" /* IssueStatus.Hidden */) {
issue.setHidden(true);
return;
}
issue.setHidden(false);
return;
}
}
#updateFilteredIssues() {
this.#filteredIssues.clear();
this.#issueCounts.clear();
this.#issuesById.clear();
this.#hiddenIssueCount.clear();
const values = this.hideIssueSetting?.get();
for (const [key, issue] of this.#allIssues) {
if (this.#issueFilter(issue)) {
this.#updateIssueHiddenStatus(issue, values);
this.#filteredIssues.set(key, issue);
this.#issueCounts.set(issue.getKind(), 1 + (this.#issueCounts.get(issue.getKind()) ?? 0));
if (issue.isHidden()) {
this.#hiddenIssueCount.set(issue.getKind(), 1 + (this.#hiddenIssueCount.get(issue.getKind()) || 0));
}
const issueId = issue.getIssueId();
if (issueId) {
this.#issuesById.set(issueId, issue);
}
}
}
this.dispatchEventToListeners("FullUpdateRequired" /* Events.FullUpdateRequired */);
this.dispatchEventToListeners("IssuesCountUpdated" /* Events.IssuesCountUpdated */);
}
unhideAllIssues() {
for (const issue of this.#allIssues.values()) {
issue.setHidden(false);
}
this.hideIssueSetting?.set(defaultHideIssueByCodeSetting());
}
getIssueById(id) {
return this.#issuesById.get(id);
}
}
// @ts-ignore
globalThis.addIssueForTest = (issue) => {
const mainTarget = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
const issuesModel = mainTarget?.model(SDK.IssuesModel.IssuesModel);
issuesModel?.issueAdded({ issue });
};
//# sourceMappingURL=IssuesManager.js.map