@quick-game/cli
Version:
Command line interface for rapid qg development
423 lines • 19.5 kB
JavaScript
// 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.
import * as Common from '../../core/common/common.js';
import * as i18n from '../../core/i18n/i18n.js';
import * as IssuesManager from '../../models/issues_manager/issues_manager.js';
import * as IssueCounter from '../../ui/components/issue_counter/issue_counter.js';
import * as UI from '../../ui/legacy/legacy.js';
import { HiddenIssuesRow } from './HiddenIssuesRow.js';
import issuesPaneStyles from './issuesPane.css.js';
import issuesTreeStyles from './issuesTree.css.js';
import { IssueAggregator, } from './IssueAggregator.js';
import { IssueView } from './IssueView.js';
import { IssueKindView, getGroupIssuesByKindSetting, issueKindViewSortPriority } from './IssueKindView.js';
const UIStrings = {
/**
* @description Category title for a group of cross origin embedder policy (COEP) issues
*/
crossOriginEmbedderPolicy: 'Cross Origin Embedder Policy',
/**
* @description Category title for a group of mixed content issues
*/
mixedContent: 'Mixed Content',
/**
* @description Category title for a group of SameSite cookie issues
*/
samesiteCookie: 'SameSite Cookie',
/**
* @description Category title for a group of heavy ads issues
*/
heavyAds: 'Heavy Ads',
/**
* @description Category title for a group of content security policy (CSP) issues
*/
contentSecurityPolicy: 'Content Security Policy',
/**
* @description Text for other types of items
*/
other: 'Other',
/**
* @description Category title for the different 'low text contrast' issues. Low text contrast refers
* to the difference between the color of a text and the background color where that text
* appears.
*/
lowTextContrast: 'Low Text Contrast',
/**
* @description Category title for the different 'Cross-Origin Resource Sharing' (CORS) issues. CORS
* refers to one origin (e.g 'a.com') loading resources from another origin (e.g. 'b.com').
*/
cors: 'Cross Origin Resource Sharing',
/**
* @description Title for a checkbox which toggles grouping by category in the issues tab
*/
groupDisplayedIssuesUnder: 'Group displayed issues under associated categories',
/**
* @description Label for a checkbox which toggles grouping by category in the issues tab
*/
groupByCategory: 'Group by category',
/**
* @description Title for a checkbox which toggles grouping by kind in the issues tab
*/
groupDisplayedIssuesUnderKind: 'Group displayed issues as Page errors, Breaking changes and Improvements',
/**
* @description Label for a checkbox which toggles grouping by kind in the issues tab
*/
groupByKind: 'Group by kind',
/**
* @description Title for a checkbox. Whether the issues tab should include third-party issues or not.
*/
includeCookieIssuesCausedBy: 'Include cookie Issues caused by third-party sites',
/**
* @description Label for a checkbox. Whether the issues tab should include third-party issues or not.
*/
includeThirdpartyCookieIssues: 'Include third-party cookie issues',
/**
* @description Label on the issues tab
*/
onlyThirdpartyCookieIssues: 'Only third-party cookie issues detected so far',
/**
* @description Label in the issues panel
*/
noIssuesDetectedSoFar: 'No issues detected so far',
/**
* @description Category title for the different 'Attribution Reporting API' issues. The
* Attribution Reporting API is a newly proposed web API (see https://github.com/WICG/conversion-measurement-api).
*/
attributionReporting: 'Attribution Reporting `API`',
/**
* @description Category title for the different 'Quirks Mode' issues. Quirks Mode refers
* to the legacy browser modes that displays web content according to outdated
* browser behaviors.
*/
quirksMode: 'Quirks Mode',
/**
* @description Category title for the different 'Generic' issues.
*/
generic: 'Generic',
};
const str_ = i18n.i18n.registerUIStrings('panels/issues/IssuesPane.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
class IssueCategoryView extends UI.TreeOutline.TreeElement {
#category;
constructor(category) {
super();
this.#category = category;
this.toggleOnClick = true;
this.listItemElement.classList.add('issue-category');
this.childrenListElement.classList.add('issue-category-body');
}
getCategoryName() {
switch (this.#category) {
case IssuesManager.Issue.IssueCategory.CrossOriginEmbedderPolicy:
return i18nString(UIStrings.crossOriginEmbedderPolicy);
case IssuesManager.Issue.IssueCategory.MixedContent:
return i18nString(UIStrings.mixedContent);
case IssuesManager.Issue.IssueCategory.Cookie:
return i18nString(UIStrings.samesiteCookie);
case IssuesManager.Issue.IssueCategory.HeavyAd:
return i18nString(UIStrings.heavyAds);
case IssuesManager.Issue.IssueCategory.ContentSecurityPolicy:
return i18nString(UIStrings.contentSecurityPolicy);
case IssuesManager.Issue.IssueCategory.LowTextContrast:
return i18nString(UIStrings.lowTextContrast);
case IssuesManager.Issue.IssueCategory.Cors:
return i18nString(UIStrings.cors);
case IssuesManager.Issue.IssueCategory.AttributionReporting:
return i18nString(UIStrings.attributionReporting);
case IssuesManager.Issue.IssueCategory.QuirksMode:
return i18nString(UIStrings.quirksMode);
case IssuesManager.Issue.IssueCategory.Generic:
return i18nString(UIStrings.generic);
case IssuesManager.Issue.IssueCategory.Other:
return i18nString(UIStrings.other);
}
}
onattach() {
this.#appendHeader();
}
#appendHeader() {
const header = document.createElement('div');
header.classList.add('header');
const title = document.createElement('div');
title.classList.add('title');
title.textContent = this.getCategoryName();
header.appendChild(title);
this.listItemElement.appendChild(header);
}
}
export function getGroupIssuesByCategorySetting() {
return Common.Settings.Settings.instance().createSetting('groupIssuesByCategory', false);
}
let issuesPaneInstance;
export class IssuesPane extends UI.Widget.VBox {
#categoryViews;
#issueViews;
#kindViews;
#showThirdPartyCheckbox;
#issuesTree;
#hiddenIssuesRow;
#noIssuesMessageDiv;
#issuesManager;
#aggregator;
#issueViewUpdatePromise = Promise.resolve();
constructor() {
super(true);
this.contentElement.classList.add('issues-pane');
this.#categoryViews = new Map();
this.#kindViews = new Map();
this.#issueViews = new Map();
this.#showThirdPartyCheckbox = null;
this.#createToolbars();
this.#issuesTree = new UI.TreeOutline.TreeOutlineInShadow();
this.#issuesTree.setShowSelectionOnKeyboardFocus(true);
this.#issuesTree.contentElement.classList.add('issues');
this.contentElement.appendChild(this.#issuesTree.element);
this.#hiddenIssuesRow = new HiddenIssuesRow();
this.#issuesTree.appendChild(this.#hiddenIssuesRow);
this.#noIssuesMessageDiv = document.createElement('div');
this.#noIssuesMessageDiv.classList.add('issues-pane-no-issues');
this.contentElement.appendChild(this.#noIssuesMessageDiv);
this.#issuesManager = IssuesManager.IssuesManager.IssuesManager.instance();
this.#aggregator = new IssueAggregator(this.#issuesManager);
this.#aggregator.addEventListener("AggregatedIssueUpdated" /* IssueAggregatorEvents.AggregatedIssueUpdated */, this.#issueUpdated, this);
this.#aggregator.addEventListener("FullUpdateRequired" /* IssueAggregatorEvents.FullUpdateRequired */, this.#onFullUpdate, this);
this.#hiddenIssuesRow.hidden = this.#issuesManager.numberOfHiddenIssues() === 0;
this.#onFullUpdate();
this.#issuesManager.addEventListener("IssuesCountUpdated" /* IssuesManager.IssuesManager.Events.IssuesCountUpdated */, this.#updateCounts, this);
}
static instance(opts = { forceNew: null }) {
const { forceNew } = opts;
if (!issuesPaneInstance || forceNew) {
issuesPaneInstance = new IssuesPane();
}
return issuesPaneInstance;
}
elementsToRestoreScrollPositionsFor() {
return [this.#issuesTree.element];
}
#createToolbars() {
const toolbarContainer = this.contentElement.createChild('div', 'issues-toolbar-container');
new UI.Toolbar.Toolbar('issues-toolbar-left', toolbarContainer);
const rightToolbar = new UI.Toolbar.Toolbar('issues-toolbar-right', toolbarContainer);
const groupByCategorySetting = getGroupIssuesByCategorySetting();
const groupByCategoryCheckbox = new UI.Toolbar.ToolbarSettingCheckbox(groupByCategorySetting, i18nString(UIStrings.groupDisplayedIssuesUnder), i18nString(UIStrings.groupByCategory));
// Hide the option to toggle category grouping for now.
groupByCategoryCheckbox.setVisible(false);
rightToolbar.appendToolbarItem(groupByCategoryCheckbox);
groupByCategorySetting.addChangeListener(() => {
this.#fullUpdate(true);
});
const groupByKindSetting = getGroupIssuesByKindSetting();
const groupByKindSettingCheckbox = new UI.Toolbar.ToolbarSettingCheckbox(groupByKindSetting, i18nString(UIStrings.groupDisplayedIssuesUnderKind), i18nString(UIStrings.groupByKind));
rightToolbar.appendToolbarItem(groupByKindSettingCheckbox);
groupByKindSetting.addChangeListener(() => {
this.#fullUpdate(true);
});
groupByKindSettingCheckbox.setVisible(true);
const thirdPartySetting = IssuesManager.Issue.getShowThirdPartyIssuesSetting();
this.#showThirdPartyCheckbox = new UI.Toolbar.ToolbarSettingCheckbox(thirdPartySetting, i18nString(UIStrings.includeCookieIssuesCausedBy), i18nString(UIStrings.includeThirdpartyCookieIssues));
rightToolbar.appendToolbarItem(this.#showThirdPartyCheckbox);
this.setDefaultFocusedElement(this.#showThirdPartyCheckbox.inputElement);
rightToolbar.appendSeparator();
const issueCounter = new IssueCounter.IssueCounter.IssueCounter();
issueCounter.data = {
tooltipCallback: () => {
const issueEnumeration = IssueCounter.IssueCounter.getIssueCountsEnumeration(IssuesManager.IssuesManager.IssuesManager.instance(), false);
issueCounter.title = issueEnumeration;
},
displayMode: "ShowAlways" /* IssueCounter.IssueCounter.DisplayMode.ShowAlways */,
issuesManager: IssuesManager.IssuesManager.IssuesManager.instance(),
};
issueCounter.id = 'console-issues-counter';
const issuesToolbarItem = new UI.Toolbar.ToolbarItem(issueCounter);
rightToolbar.appendToolbarItem(issuesToolbarItem);
return { toolbarContainer };
}
#issueUpdated(event) {
this.#scheduleIssueViewUpdate(event.data);
}
#scheduleIssueViewUpdate(issue) {
this.#issueViewUpdatePromise = this.#issueViewUpdatePromise.then(() => this.#updateIssueView(issue));
}
/** Don't call directly. Use `scheduleIssueViewUpdate` instead. */
async #updateIssueView(issue) {
let issueView = this.#issueViews.get(issue.aggregationKey());
if (!issueView) {
const description = issue.getDescription();
if (!description) {
console.warn('Could not find description for issue code:', issue.code());
return;
}
const markdownDescription = await IssuesManager.MarkdownIssueDescription.createIssueDescriptionFromMarkdown(description);
issueView = new IssueView(issue, markdownDescription);
this.#issueViews.set(issue.aggregationKey(), issueView);
const parent = this.#getIssueViewParent(issue);
this.appendIssueViewToParent(issueView, parent);
}
else {
issueView.setIssue(issue);
const newParent = this.#getIssueViewParent(issue);
if (issueView.parent !== newParent &&
!(newParent instanceof UI.TreeOutline.TreeOutline && issueView.parent === newParent.rootElement())) {
issueView.parent?.removeChild(issueView);
this.appendIssueViewToParent(issueView, newParent);
}
}
issueView.update();
this.#updateCounts();
}
appendIssueViewToParent(issueView, parent) {
parent.appendChild(issueView, (a, b) => {
if (a instanceof HiddenIssuesRow) {
return 1;
}
if (b instanceof HiddenIssuesRow) {
return -1;
}
if (a instanceof IssueView && b instanceof IssueView) {
return a.getIssueTitle().localeCompare(b.getIssueTitle());
}
console.error('The issues tree should only contain IssueView objects as direct children');
return 0;
});
if (parent instanceof UI.TreeOutline.TreeElement) {
// This is an aggregated view, so we need to update the label for position and size of the treeItem.
this.#updateItemPositionAndSize(parent);
}
}
#updateItemPositionAndSize(parent) {
const childNodes = parent.childrenListNode.children;
let treeItemCount = 0;
for (let i = 0; i < childNodes.length; i++) {
const node = childNodes[i];
if (node.classList.contains('issue')) {
UI.ARIAUtils.setPositionInSet(node, ++treeItemCount);
UI.ARIAUtils.setSetSize(node, childNodes.length / 2); // Each issue has 2 nodes (issue + description).
}
}
}
#getIssueViewParent(issue) {
if (issue.isHidden()) {
return this.#hiddenIssuesRow;
}
if (getGroupIssuesByKindSetting().get()) {
const kind = issue.getKind();
const view = this.#kindViews.get(kind);
if (view) {
return view;
}
const newView = new IssueKindView(kind);
this.#issuesTree.appendChild(newView, (a, b) => {
if (a instanceof IssueKindView && b instanceof IssueKindView) {
return issueKindViewSortPriority(a, b);
}
return 0;
});
this.#kindViews.set(kind, newView);
return newView;
}
if (getGroupIssuesByCategorySetting().get()) {
const category = issue.getCategory();
const view = this.#categoryViews.get(category);
if (view) {
return view;
}
const newView = new IssueCategoryView(category);
this.#issuesTree.appendChild(newView, (a, b) => {
if (a instanceof IssueCategoryView && b instanceof IssueCategoryView) {
return a.getCategoryName().localeCompare(b.getCategoryName());
}
return 0;
});
this.#categoryViews.set(category, newView);
return newView;
}
return this.#issuesTree;
}
#clearViews(views, preservedSet) {
for (const [key, view] of Array.from(views.entries())) {
if (preservedSet?.has(key)) {
continue;
}
view.parent && view.parent.removeChild(view);
views.delete(key);
}
}
#onFullUpdate() {
this.#fullUpdate(false);
}
#fullUpdate(force) {
this.#clearViews(this.#categoryViews, force ? undefined : this.#aggregator.aggregatedIssueCategories());
this.#clearViews(this.#kindViews, force ? undefined : this.#aggregator.aggregatedIssueKinds());
this.#clearViews(this.#issueViews, force ? undefined : this.#aggregator.aggregatedIssueCodes());
if (this.#aggregator) {
for (const issue of this.#aggregator.aggregatedIssues()) {
this.#scheduleIssueViewUpdate(issue);
}
}
this.#updateCounts();
}
#updateIssueKindViewsCount() {
for (const view of this.#kindViews.values()) {
const count = this.#issuesManager.numberOfIssues(view.getKind());
view.update(count);
}
}
#updateCounts() {
this.#showIssuesTreeOrNoIssuesDetectedMessage(this.#issuesManager.numberOfIssues(), this.#issuesManager.numberOfHiddenIssues());
if (getGroupIssuesByKindSetting().get()) {
this.#updateIssueKindViewsCount();
}
}
#showIssuesTreeOrNoIssuesDetectedMessage(issuesCount, hiddenIssueCount) {
if (issuesCount > 0 || hiddenIssueCount > 0) {
this.#hiddenIssuesRow.hidden = hiddenIssueCount === 0;
this.#hiddenIssuesRow.update(hiddenIssueCount);
this.#issuesTree.element.hidden = false;
this.#noIssuesMessageDiv.style.display = 'none';
const firstChild = this.#issuesTree.firstChild();
if (firstChild) {
firstChild.select(/* omitFocus= */ true);
this.setDefaultFocusedElement(firstChild.listItemElement);
}
}
else {
this.#issuesTree.element.hidden = true;
if (this.#showThirdPartyCheckbox) {
this.setDefaultFocusedElement(this.#showThirdPartyCheckbox.inputElement);
}
// We alreay know that issesCount is zero here.
const hasOnlyThirdPartyIssues = this.#issuesManager.numberOfAllStoredIssues() > 0;
this.#noIssuesMessageDiv.textContent = hasOnlyThirdPartyIssues ?
i18nString(UIStrings.onlyThirdpartyCookieIssues) :
i18nString(UIStrings.noIssuesDetectedSoFar);
this.#noIssuesMessageDiv.style.display = 'flex';
}
}
async reveal(issue) {
await this.#issueViewUpdatePromise;
const key = this.#aggregator.keyForIssue(issue);
const issueView = this.#issueViews.get(key);
if (issueView) {
if (issueView.isForHiddenIssue()) {
this.#hiddenIssuesRow.expand();
this.#hiddenIssuesRow.reveal();
}
if (getGroupIssuesByKindSetting().get() && !issueView.isForHiddenIssue()) {
const kindView = this.#kindViews.get(issueView.getIssueKind());
kindView?.expand();
kindView?.reveal();
}
issueView.expand();
issueView.reveal();
issueView.select(false, true);
}
}
wasShown() {
super.wasShown();
this.#issuesTree.registerCSSFiles([issuesTreeStyles]);
this.registerCSSFiles([issuesPaneStyles]);
}
}
//# sourceMappingURL=IssuesPane.js.map