| // 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 * as Bindings from '../../models/bindings/bindings.js'; |
| import * as TextUtils from '../../models/text_utils/text_utils.js'; |
| import * as Workspace from '../../models/workspace/workspace.js'; |
| |
| import { |
| ContentSecurityPolicyIssue, |
| trustedTypesPolicyViolationCode, |
| trustedTypesSinkViolationCode, |
| } from './ContentSecurityPolicyIssue.js'; |
| import type {Issue, IssueKind} from './Issue.js'; |
| import {toZeroBasedLocation} from './Issue.js'; |
| import type {IssueAddedEvent, IssuesManager} from './IssuesManager.js'; |
| import {Events} from './IssuesManagerEvents.js'; |
| import {getIssueTitleFromMarkdownDescription} from './MarkdownIssueDescription.js'; |
| |
| export class SourceFrameIssuesManager { |
| #locationPool = new Bindings.LiveLocation.LiveLocationPool(); |
| #issueMessages = new Array<IssueMessage>(); |
| |
| constructor(private readonly issuesManager: IssuesManager) { |
| this.issuesManager.addEventListener(Events.IssueAdded, this.#onIssueAdded, this); |
| this.issuesManager.addEventListener(Events.FullUpdateRequired, this.#onFullUpdateRequired, this); |
| } |
| |
| #onIssueAdded(event: Common.EventTarget.EventTargetEvent<IssueAddedEvent>): void { |
| const {issue} = event.data; |
| this.#addIssue(issue); |
| } |
| |
| #addIssue(issue: Issue): void { |
| if (!this.#isTrustedTypeIssue(issue)) { |
| return; |
| } |
| const issuesModel = issue.model(); |
| if (!issuesModel) { |
| return; |
| } |
| const debuggerModel = issuesModel.target().model(SDK.DebuggerModel.DebuggerModel); |
| const srcLocation = toZeroBasedLocation(issue.details().sourceCodeLocation); |
| if (srcLocation && debuggerModel) { |
| const rawLocation = |
| debuggerModel.createRawLocationByURL(srcLocation.url, srcLocation.lineNumber, srcLocation.columnNumber); |
| if (rawLocation) { |
| void this.#addIssueMessageToScript(issue, rawLocation); |
| } |
| } |
| } |
| |
| #onFullUpdateRequired(): void { |
| this.#resetMessages(); |
| const issues = this.issuesManager.issues(); |
| for (const issue of issues) { |
| this.#addIssue(issue); |
| } |
| } |
| |
| async #addIssueMessageToScript(issue: Issue, rawLocation: SDK.DebuggerModel.Location): Promise<void> { |
| const description = issue.getDescription(); |
| if (description) { |
| const title = await getIssueTitleFromMarkdownDescription(description); |
| if (title) { |
| const clickHandler = (): void => { |
| void Common.Revealer.reveal(issue); |
| }; |
| this.#issueMessages.push( |
| new IssueMessage(title, issue.getKind(), rawLocation, this.#locationPool, clickHandler)); |
| } |
| } |
| } |
| |
| #isTrustedTypeIssue(issue: Issue): issue is ContentSecurityPolicyIssue { |
| return issue instanceof ContentSecurityPolicyIssue && issue.code() === trustedTypesSinkViolationCode || |
| issue.code() === trustedTypesPolicyViolationCode; |
| } |
| |
| #resetMessages(): void { |
| for (const message of this.#issueMessages) { |
| message.dispose(); |
| } |
| this.#issueMessages = []; |
| this.#locationPool.disposeAll(); |
| } |
| } |
| |
| export class IssueMessage extends Workspace.UISourceCode.Message { |
| #uiSourceCode?: Workspace.UISourceCode.UISourceCode = undefined; |
| #kind: IssueKind; |
| |
| constructor( |
| title: string, kind: IssueKind, rawLocation: SDK.DebuggerModel.Location, |
| locationPool: Bindings.LiveLocation.LiveLocationPool, clickHandler: () => void) { |
| super(Workspace.UISourceCode.Message.Level.Issue, title, clickHandler); |
| this.#kind = kind; |
| void Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance().createLiveLocation( |
| rawLocation, this.#updateLocation.bind(this), locationPool); |
| } |
| |
| async #updateLocation(liveLocation: Bindings.LiveLocation.LiveLocation): Promise<void> { |
| if (this.#uiSourceCode) { |
| this.#uiSourceCode.removeMessage(this); |
| } |
| const uiLocation = await liveLocation.uiLocation(); |
| if (!uiLocation) { |
| return; |
| } |
| this.range = TextUtils.TextRange.TextRange.createFromLocation(uiLocation.lineNumber, uiLocation.columnNumber || 0); |
| this.#uiSourceCode = uiLocation.uiSourceCode; |
| this.#uiSourceCode.addMessage(this); |
| } |
| |
| getIssueKind(): IssueKind { |
| return this.#kind; |
| } |
| |
| dispose(): void { |
| if (this.#uiSourceCode) { |
| this.#uiSourceCode.removeMessage(this); |
| } |
| } |
| } |