mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-13 01:54:52 +08:00
This change extracts the IssuesManager Events enum into a separate file to break the circular dependency of values between IssuesManager.ts and SourceFrameIssuesManager.ts. This helps reduce existing circular dependencies, in theory moving us slightly closer to being able to warn about such cycles at build-time to avoid unexpected errors. This particular cycle recently led to a runtime error when attempting to subclass IssuesManager to introduce additional functionality. This subclass was included in the cycle as SourceFrameIssuesManager was updated to import the subclass instead of IssuesManager. At the time the Events enum for IssuesManager was non-const, causing the circular dependency to exist post-build. The error occurred because the extends relationship of the subclass was evaluated during module initialization. Since then the Events enum has been converted to const which eliminates the runtime error. However, eliminating even this cycle is still beneficial. The compile-time only relationship is not obvious and cannot otherwise be communicated explicitly until values from const enums can be used with `import type` per https://github.com/microsoft/TypeScript/issues/40344. Bug: 1229328 Change-Id: I033080c77f22584aa0527bcd0559698341d200f5 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/3040178 Commit-Queue: Tony Ross <tross@microsoft.com> Reviewed-by: Sigurd Schneider <sigurds@chromium.org> Reviewed-by: Tim van der Lippe <tvanderlippe@chromium.org>
122 lines
4.4 KiB
TypeScript
122 lines
4.4 KiB
TypeScript
// 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 {
|
|
private locationPool = new Bindings.LiveLocation.LiveLocationPool();
|
|
private 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);
|
|
}
|
|
|
|
private onIssueAdded(event: Common.EventTarget.EventTargetEvent<IssueAddedEvent>): void {
|
|
const {issue} = event.data;
|
|
this.addIssue(issue);
|
|
}
|
|
|
|
private 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) {
|
|
this.addIssueMessageToScript(issue, rawLocation);
|
|
}
|
|
}
|
|
}
|
|
|
|
private onFullUpdateRequired(): void {
|
|
this.resetMessages();
|
|
const issues = this.issuesManager.issues();
|
|
for (const issue of issues) {
|
|
this.addIssue(issue);
|
|
}
|
|
}
|
|
|
|
private 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 => {
|
|
Common.Revealer.reveal(issue);
|
|
};
|
|
this.issueMessages.push(new IssueMessage(title, issue.getKind(), rawLocation, this.locationPool, clickHandler));
|
|
}
|
|
}
|
|
}
|
|
|
|
private isTrustedTypeIssue(issue: Issue): issue is ContentSecurityPolicyIssue {
|
|
return issue instanceof ContentSecurityPolicyIssue && issue.code() === trustedTypesSinkViolationCode ||
|
|
issue.code() === trustedTypesPolicyViolationCode;
|
|
}
|
|
|
|
private resetMessages(): void {
|
|
for (const message of this.issueMessages) {
|
|
message.dispose();
|
|
}
|
|
this.issueMessages = [];
|
|
this.locationPool.disposeAll();
|
|
}
|
|
}
|
|
|
|
export class IssueMessage extends Workspace.UISourceCode.Message {
|
|
private uiSourceCode?: Workspace.UISourceCode.UISourceCode = undefined;
|
|
private 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;
|
|
Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance().createLiveLocation(
|
|
rawLocation, this.updateLocation.bind(this), locationPool);
|
|
}
|
|
|
|
private 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);
|
|
}
|
|
}
|
|
}
|