UNPKG

8.8 kBPlain TextView Raw
1/*
2 * Copyright (C) 2012 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31import type * as Common from '../../core/common/common.js';
32import * as SDK from '../../core/sdk/sdk.js';
33import * as TextUtils from '../text_utils/text_utils.js';
34import * as Workspace from '../workspace/workspace.js';
35import * as Protocol from '../../generated/protocol.js';
36
37import {DebuggerWorkspaceBinding} from './DebuggerWorkspaceBinding.js';
38import type {LiveLocation} from './LiveLocation.js';
39import {LiveLocationPool} from './LiveLocation.js';
40
41const debuggerModelToMessageHelperMap =
42 new WeakMap<SDK.DebuggerModel.DebuggerModel, PresentationConsoleMessageHelper>();
43
44export class PresentationConsoleMessageManager implements
45 SDK.TargetManager.SDKModelObserver<SDK.DebuggerModel.DebuggerModel> {
46 constructor() {
47 SDK.TargetManager.TargetManager.instance().observeModels(SDK.DebuggerModel.DebuggerModel, this);
48
49 SDK.ConsoleModel.ConsoleModel.instance().addEventListener(
50 SDK.ConsoleModel.Events.ConsoleCleared, this.consoleCleared, this);
51 SDK.ConsoleModel.ConsoleModel.instance().addEventListener(
52 SDK.ConsoleModel.Events.MessageAdded, event => this.consoleMessageAdded(event.data));
53 SDK.ConsoleModel.ConsoleModel.instance().messages().forEach(this.consoleMessageAdded, this);
54 }
55
56 modelAdded(debuggerModel: SDK.DebuggerModel.DebuggerModel): void {
57 debuggerModelToMessageHelperMap.set(debuggerModel, new PresentationConsoleMessageHelper(debuggerModel));
58 }
59
60 modelRemoved(debuggerModel: SDK.DebuggerModel.DebuggerModel): void {
61 const helper = debuggerModelToMessageHelperMap.get(debuggerModel);
62 if (helper) {
63 helper.consoleCleared();
64 }
65 }
66
67 private consoleMessageAdded(message: SDK.ConsoleModel.ConsoleMessage): void {
68 const runtimeModel = message.runtimeModel();
69 if (!message.isErrorOrWarning() || !message.runtimeModel() ||
70 message.source === Protocol.Log.LogEntrySource.Violation || !runtimeModel) {
71 return;
72 }
73 const helper = debuggerModelToMessageHelperMap.get(runtimeModel.debuggerModel());
74 if (helper) {
75 helper.consoleMessageAdded(message);
76 }
77 }
78
79 private consoleCleared(): void {
80 for (const debuggerModel of SDK.TargetManager.TargetManager.instance().models(SDK.DebuggerModel.DebuggerModel)) {
81 const helper = debuggerModelToMessageHelperMap.get(debuggerModel);
82 if (helper) {
83 helper.consoleCleared();
84 }
85 }
86 }
87}
88
89export class PresentationConsoleMessageHelper {
90 readonly #debuggerModel: SDK.DebuggerModel.DebuggerModel;
91 #pendingConsoleMessages: Map<string, SDK.ConsoleModel.ConsoleMessage[]>;
92 #presentationConsoleMessages: PresentationConsoleMessage[];
93 readonly #locationPool: LiveLocationPool;
94
95 constructor(debuggerModel: SDK.DebuggerModel.DebuggerModel) {
96 this.#debuggerModel = debuggerModel;
97
98 this.#pendingConsoleMessages = new Map();
99
100 this.#presentationConsoleMessages = [];
101
102 // TODO(dgozman): queueMicrotask because we race with DebuggerWorkspaceBinding on ParsedScriptSource event delivery.
103 debuggerModel.addEventListener(SDK.DebuggerModel.Events.ParsedScriptSource, event => {
104 queueMicrotask(() => {
105 this.parsedScriptSource(event);
106 });
107 });
108 debuggerModel.addEventListener(SDK.DebuggerModel.Events.GlobalObjectCleared, this.debuggerReset, this);
109
110 this.#locationPool = new LiveLocationPool();
111 }
112
113 consoleMessageAdded(message: SDK.ConsoleModel.ConsoleMessage): void {
114 const rawLocation = this.rawLocation(message);
115 if (rawLocation) {
116 this.addConsoleMessageToScript(message, rawLocation);
117 } else {
118 this.addPendingConsoleMessage(message);
119 }
120 }
121
122 private rawLocation(message: SDK.ConsoleModel.ConsoleMessage): SDK.DebuggerModel.Location|null {
123 if (message.scriptId) {
124 return this.#debuggerModel.createRawLocationByScriptId(message.scriptId, message.line, message.column);
125 }
126 const callFrame = message.stackTrace && message.stackTrace.callFrames ? message.stackTrace.callFrames[0] : null;
127 if (callFrame) {
128 return this.#debuggerModel.createRawLocationByScriptId(
129 callFrame.scriptId, callFrame.lineNumber, callFrame.columnNumber);
130 }
131 if (message.url) {
132 return this.#debuggerModel.createRawLocationByURL(message.url, message.line, message.column);
133 }
134 return null;
135 }
136
137 private addConsoleMessageToScript(message: SDK.ConsoleModel.ConsoleMessage, rawLocation: SDK.DebuggerModel.Location):
138 void {
139 this.#presentationConsoleMessages.push(new PresentationConsoleMessage(message, rawLocation, this.#locationPool));
140 }
141
142 private addPendingConsoleMessage(message: SDK.ConsoleModel.ConsoleMessage): void {
143 if (!message.url) {
144 return;
145 }
146 const pendingMessages = this.#pendingConsoleMessages.get(message.url);
147 if (!pendingMessages) {
148 this.#pendingConsoleMessages.set(message.url, [message]);
149 } else {
150 pendingMessages.push(message);
151 }
152 }
153
154 private parsedScriptSource(event: Common.EventTarget.EventTargetEvent<SDK.Script.Script>): void {
155 const script = event.data;
156
157 const messages = this.#pendingConsoleMessages.get(script.sourceURL);
158 if (!messages) {
159 return;
160 }
161
162 const pendingMessages = [];
163 for (const message of messages) {
164 const rawLocation = this.rawLocation(message);
165 if (rawLocation && script.scriptId === rawLocation.scriptId) {
166 this.addConsoleMessageToScript(message, rawLocation);
167 } else {
168 pendingMessages.push(message);
169 }
170 }
171
172 if (pendingMessages.length) {
173 this.#pendingConsoleMessages.set(script.sourceURL, pendingMessages);
174 } else {
175 this.#pendingConsoleMessages.delete(script.sourceURL);
176 }
177 }
178
179 consoleCleared(): void {
180 this.#pendingConsoleMessages = new Map();
181 this.debuggerReset();
182 }
183
184 private debuggerReset(): void {
185 for (const message of this.#presentationConsoleMessages) {
186 message.dispose();
187 }
188 this.#presentationConsoleMessages = [];
189 this.#locationPool.disposeAll();
190 }
191}
192
193export class PresentationConsoleMessage extends Workspace.UISourceCode.Message {
194 #uiSourceCode?: Workspace.UISourceCode.UISourceCode;
195
196 constructor(
197 message: SDK.ConsoleModel.ConsoleMessage, rawLocation: SDK.DebuggerModel.Location,
198 locationPool: LiveLocationPool) {
199 const level = message.level === Protocol.Log.LogEntryLevel.Error ? Workspace.UISourceCode.Message.Level.Error :
200 Workspace.UISourceCode.Message.Level.Warning;
201 super(level, message.messageText);
202 DebuggerWorkspaceBinding.instance().createLiveLocation(rawLocation, this.updateLocation.bind(this), locationPool);
203 }
204
205 private async updateLocation(liveLocation: LiveLocation): Promise<void> {
206 if (this.#uiSourceCode) {
207 this.#uiSourceCode.removeMessage(this);
208 }
209 const uiLocation = await liveLocation.uiLocation();
210 if (!uiLocation) {
211 return;
212 }
213 this.range = TextUtils.TextRange.TextRange.createFromLocation(uiLocation.lineNumber, uiLocation.columnNumber || 0);
214 this.#uiSourceCode = uiLocation.uiSourceCode;
215 this.#uiSourceCode.addMessage(this);
216 }
217
218 dispose(): void {
219 if (this.#uiSourceCode) {
220 this.#uiSourceCode.removeMessage(this);
221 }
222 }
223}