@quick-game/cli
Version:
Command line interface for rapid qg development
228 lines • 10.4 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 Host from '../../core/host/host.js';
import * as i18n from '../../core/i18n/i18n.js';
import * as SDK from '../../core/sdk/sdk.js';
import * as Logs from '../../models/logs/logs.js';
import * as IconButton from '../../ui/components/icon_button/icon_button.js';
import * as Components from '../../ui/legacy/components/utils/utils.js';
import * as UI from '../../ui/legacy/legacy.js';
import * as RequestLinkIcon from '../../ui/components/request_link_icon/request_link_icon.js';
const UIStrings = {
/**
*@description Text in Object Properties Section
*/
unknown: 'unknown',
/**
*@description Tooltip for button linking to the Elements panel
*/
clickToRevealTheFramesDomNodeIn: 'Click to reveal the frame\'s DOM node in the Elements panel',
/**
*@description Replacement text for a link to an HTML element which is not available (anymore).
*/
unavailable: 'unavailable',
};
const str_ = i18n.i18n.registerUIStrings('panels/issues/AffectedResourcesView.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
export const extractShortPath = (path) => {
// 1st regex matches everything after last '/'
// if path ends with '/', 2nd regex returns everything between the last two '/'
return (/[^/]+$/.exec(path) || /[^/]+\/$/.exec(path) || [''])[0];
};
/**
* The base class for all affected resource views. It provides basic scaffolding
* as well as machinery for resolving request and frame ids to SDK objects.
*/
export class AffectedResourcesView extends UI.TreeOutline.TreeElement {
#parentView;
issue;
affectedResourcesCountElement;
affectedResources;
#affectedResourcesCount;
#frameListeners;
#unresolvedFrameIds;
requestResolver;
constructor(parent, issue) {
super();
this.#parentView = parent;
this.issue = issue;
this.toggleOnClick = true;
this.affectedResourcesCountElement = this.createAffectedResourcesCounter();
this.affectedResources = this.createAffectedResources();
this.#affectedResourcesCount = 0;
this.requestResolver = new Logs.RequestResolver.RequestResolver();
this.#frameListeners = [];
this.#unresolvedFrameIds = new Set();
}
/**
* Sets the issue to take the resources from. Does not
* trigger an update, the caller needs to do that explicitly.
*/
setIssue(issue) {
this.issue = issue;
}
createAffectedResourcesCounter() {
const counterLabel = document.createElement('div');
counterLabel.classList.add('affected-resource-label');
this.listItemElement.appendChild(counterLabel);
return counterLabel;
}
createAffectedResources() {
const body = new UI.TreeOutline.TreeElement();
const affectedResources = document.createElement('table');
affectedResources.classList.add('affected-resource-list');
body.listItemElement.appendChild(affectedResources);
this.appendChild(body);
return affectedResources;
}
updateAffectedResourceCount(count) {
this.#affectedResourcesCount = count;
this.affectedResourcesCountElement.textContent = this.getResourceNameWithCount(count);
this.hidden = this.#affectedResourcesCount === 0;
this.#parentView.updateAffectedResourceVisibility();
}
isEmpty() {
return this.#affectedResourcesCount === 0;
}
clear() {
this.affectedResources.textContent = '';
this.requestResolver.clear();
}
expandIfOneResource() {
if (this.#affectedResourcesCount === 1) {
this.expand();
}
}
/**
* This function resolves a frameId to a ResourceTreeFrame. If the frameId does not resolve, or hasn't navigated yet,
* a listener is installed that takes care of updating the view if the frame is added. This is useful if the issue is
* added before the frame gets reported.
*/
#resolveFrameId(frameId) {
const frame = SDK.FrameManager.FrameManager.instance().getFrame(frameId);
if (!frame || !frame.url) {
this.#unresolvedFrameIds.add(frameId);
if (!this.#frameListeners.length) {
const addListener = SDK.FrameManager.FrameManager.instance().addEventListener(SDK.FrameManager.Events.FrameAddedToTarget, this.#onFrameChanged, this);
const navigateListener = SDK.FrameManager.FrameManager.instance().addEventListener(SDK.FrameManager.Events.FrameNavigated, this.#onFrameChanged, this);
this.#frameListeners = [addListener, navigateListener];
}
}
return frame;
}
#onFrameChanged(event) {
const frame = event.data.frame;
if (!frame.url) {
return;
}
const frameWasUnresolved = this.#unresolvedFrameIds.delete(frame.id);
if (this.#unresolvedFrameIds.size === 0 && this.#frameListeners.length) {
// Stop listening once all requests are resolved.
Common.EventTarget.removeEventListeners(this.#frameListeners);
this.#frameListeners = [];
}
if (frameWasUnresolved) {
this.update();
}
}
createFrameCell(frameId, issueCategory) {
const frame = this.#resolveFrameId(frameId);
const url = frame && (frame.unreachableUrl() || frame.url) || i18nString(UIStrings.unknown);
const frameCell = document.createElement('td');
frameCell.classList.add('affected-resource-cell');
if (frame) {
const icon = new IconButton.Icon.Icon();
icon.data = { iconName: 'code-circle', color: 'var(--icon-link)', width: '16px', height: '16px' };
icon.classList.add('link', 'elements-panel');
icon.onclick = async () => {
Host.userMetrics.issuesPanelResourceOpened(issueCategory, "Element" /* AffectedItem.Element */);
const frame = SDK.FrameManager.FrameManager.instance().getFrame(frameId);
if (frame) {
const ownerNode = await frame.getOwnerDOMNodeOrDocument();
if (ownerNode) {
void Common.Revealer.reveal(ownerNode);
}
}
};
icon.title = i18nString(UIStrings.clickToRevealTheFramesDomNodeIn);
frameCell.appendChild(icon);
}
frameCell.appendChild(document.createTextNode(url));
frameCell.onmouseenter = () => {
const frame = SDK.FrameManager.FrameManager.instance().getFrame(frameId);
if (frame) {
void frame.highlight();
}
};
frameCell.onmouseleave = () => SDK.OverlayModel.OverlayModel.hideDOMNodeHighlight();
return frameCell;
}
createRequestCell(affectedRequest, options = {}) {
const requestCell = document.createElement('td');
requestCell.classList.add('affected-resource-cell');
const requestLinkIcon = new RequestLinkIcon.RequestLinkIcon.RequestLinkIcon();
requestLinkIcon.data = { ...options, affectedRequest, requestResolver: this.requestResolver, displayURL: true };
requestCell.appendChild(requestLinkIcon);
return requestCell;
}
async createElementCell({ backendNodeId, nodeName, target }, issueCategory) {
if (!target) {
const cellElement = document.createElement('td');
cellElement.textContent = nodeName || i18nString(UIStrings.unavailable);
return cellElement;
}
function sendTelemetry() {
Host.userMetrics.issuesPanelResourceOpened(issueCategory, "Element" /* AffectedItem.Element */);
}
const deferredDOMNode = new SDK.DOMModel.DeferredDOMNode(target, backendNodeId);
const anchorElement = (await Common.Linkifier.Linkifier.linkify(deferredDOMNode));
anchorElement.textContent = nodeName;
anchorElement.addEventListener('click', () => sendTelemetry());
anchorElement.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
sendTelemetry();
}
});
const cellElement = document.createElement('td');
cellElement.classList.add('affected-resource-element', 'devtools-link');
cellElement.appendChild(anchorElement);
return cellElement;
}
appendSourceLocation(element, sourceLocation, target) {
const sourceCodeLocation = document.createElement('td');
sourceCodeLocation.classList.add('affected-source-location');
if (sourceLocation) {
const maxLengthForDisplayedURLs = 40; // Same as console messages.
// TODO(crbug.com/1108503): Add some mechanism to be able to add telemetry to this element.
const linkifier = new Components.Linkifier.Linkifier(maxLengthForDisplayedURLs);
const sourceAnchor = linkifier.linkifyScriptLocation(target || null, sourceLocation.scriptId || null, sourceLocation.url, sourceLocation.lineNumber, { columnNumber: sourceLocation.columnNumber, inlineFrameIndex: 0 });
sourceCodeLocation.appendChild(sourceAnchor);
}
element.appendChild(sourceCodeLocation);
}
appendColumnTitle(header, title, additionalClass = null) {
const info = document.createElement('td');
info.classList.add('affected-resource-header');
if (additionalClass) {
info.classList.add(additionalClass);
}
info.textContent = title;
header.appendChild(info);
}
createIssueDetailCell(textContent, additionalClass = null) {
const cell = document.createElement('td');
cell.textContent = textContent;
if (additionalClass) {
cell.classList.add(additionalClass);
}
return cell;
}
appendIssueDetailCell(element, textContent, additionalClass = null) {
const cell = this.createIssueDetailCell(textContent, additionalClass);
element.appendChild(cell);
return cell;
}
}
//# sourceMappingURL=AffectedResourcesView.js.map