chrome-devtools-frontend 1.0.1009019 → 1.0.1010492

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/extension-api/ExtensionAPI.d.ts +8 -6
  2. package/front_end/core/host/UserMetrics.ts +3 -1
  3. package/front_end/core/i18n/locales/en-US.json +16 -4
  4. package/front_end/core/i18n/locales/en-XL.json +16 -4
  5. package/front_end/core/sdk/CSSProperty.ts +3 -3
  6. package/front_end/core/sdk/DebuggerModel.ts +12 -3
  7. package/front_end/core/sdk/EmulationModel.ts +9 -3
  8. package/front_end/core/sdk/Script.ts +3 -2
  9. package/front_end/models/bindings/BreakpointManager.ts +58 -22
  10. package/front_end/models/bindings/DebuggerLanguagePlugins.ts +72 -65
  11. package/front_end/models/bindings/ResourceMapping.ts +43 -10
  12. package/front_end/models/bindings/ResourceScriptMapping.ts +13 -1
  13. package/front_end/models/extensions/ExtensionAPI.ts +15 -4
  14. package/front_end/models/extensions/ExtensionServer.ts +3 -3
  15. package/front_end/models/extensions/RecorderExtensionEndpoint.ts +18 -1
  16. package/front_end/models/persistence/Automapping.ts +18 -12
  17. package/front_end/models/persistence/PersistenceImpl.ts +10 -2
  18. package/front_end/models/workspace/WorkspaceImpl.ts +16 -9
  19. package/front_end/panels/application/DOMStorageItemsView.ts +6 -0
  20. package/front_end/panels/console/ConsoleViewMessage.ts +2 -1
  21. package/front_end/panels/sources/CallStackSidebarPane.ts +11 -4
  22. package/front_end/panels/sources/DebuggerPlugin.ts +51 -1
  23. package/front_end/ui/components/data_grid/DataGrid.ts +5 -0
  24. package/front_end/ui/components/data_grid/DataGridController.ts +5 -0
  25. package/front_end/ui/legacy/Infobar.ts +1 -0
  26. package/front_end/ui/legacy/components/data_grid/DataGrid.ts +1 -1
  27. package/front_end/ui/legacy/infobar.css +17 -0
  28. package/package.json +1 -1
@@ -14,7 +14,7 @@ import {DebuggerWorkspaceBinding} from './DebuggerWorkspaceBinding.js';
14
14
  import {NetworkProject} from './NetworkProject.js';
15
15
  import {resourceMetadata} from './ResourceUtils.js';
16
16
 
17
- let resourceMappingInstance: ResourceMapping;
17
+ let resourceMappingInstance: ResourceMapping|undefined;
18
18
 
19
19
  const styleSheetOffsetMap = new WeakMap<SDK.CSSStyleSheetHeader.CSSStyleSheetHeader, TextUtils.TextRange.TextRange>();
20
20
  const scriptOffsetMap = new WeakMap<SDK.Script.Script, TextUtils.TextRange.TextRange>();
@@ -47,6 +47,10 @@ export class ResourceMapping implements SDK.TargetManager.SDKModelObserver<SDK.R
47
47
  return resourceMappingInstance;
48
48
  }
49
49
 
50
+ static removeInstance(): void {
51
+ resourceMappingInstance = undefined;
52
+ }
53
+
50
54
  modelAdded(resourceTreeModel: SDK.ResourceTreeModel.ResourceTreeModel): void {
51
55
  const info = new ModelInfo(this.#workspace, resourceTreeModel);
52
56
  this.#modelToInfo.set(resourceTreeModel, info);
@@ -97,17 +101,27 @@ export class ResourceMapping implements SDK.TargetManager.SDKModelObserver<SDK.R
97
101
  if (!info) {
98
102
  return null;
99
103
  }
100
- const uiSourceCode = info.getProject().uiSourceCodeForURL(script.sourceURL);
104
+ const embedderName = script.embedderName();
105
+ if (!embedderName) {
106
+ return null;
107
+ }
108
+ const uiSourceCode = info.getProject().uiSourceCodeForURL(embedderName);
101
109
  if (!uiSourceCode) {
102
110
  return null;
103
111
  }
104
112
  const offset = scriptOffsetMap.get(script) ||
105
113
  TextUtils.TextRange.TextRange.createFromLocation(script.lineOffset, script.columnOffset);
106
- const lineNumber = jsLocation.lineNumber + offset.startLine - script.lineOffset;
114
+ let lineNumber = jsLocation.lineNumber + offset.startLine - script.lineOffset;
107
115
  let columnNumber = jsLocation.columnNumber;
108
116
  if (jsLocation.lineNumber === script.lineOffset) {
109
117
  columnNumber += offset.startColumn - script.columnOffset;
110
118
  }
119
+ if (script.hasSourceURL) {
120
+ if (lineNumber === 0) {
121
+ columnNumber += script.columnOffset;
122
+ }
123
+ lineNumber += script.lineOffset;
124
+ }
111
125
  return uiSourceCode.uiLocation(lineNumber, columnNumber);
112
126
  }
113
127
 
@@ -124,14 +138,33 @@ export class ResourceMapping implements SDK.TargetManager.SDKModelObserver<SDK.R
124
138
  if (!debuggerModel) {
125
139
  return [];
126
140
  }
127
- const location = debuggerModel.createRawLocationByURL(uiSourceCode.url(), lineNumber, columnNumber);
128
- if (location) {
129
- const script = location.script();
130
- if (script && script.containsLocation(lineNumber, columnNumber)) {
131
- return [location];
141
+ const locations = [];
142
+ for (const script of debuggerModel.scripts()) {
143
+ if (script.embedderName() !== uiSourceCode.url()) {
144
+ continue;
145
+ }
146
+ const {startLine, startColumn} = scriptOffsetMap.get(script) ||
147
+ TextUtils.TextRange.TextRange.createFromLocation(script.lineOffset, script.columnOffset);
148
+ if (lineNumber < startLine || (lineNumber === startLine && columnNumber < startColumn)) {
149
+ continue;
150
+ }
151
+ const endLine = startLine + (script.endLine - script.lineOffset);
152
+ const endColumn =
153
+ startLine === endLine ? startColumn + (script.endColumn - script.columnOffset) : script.endColumn;
154
+ if (lineNumber > endLine || (lineNumber === endLine && columnNumber > endColumn)) {
155
+ continue;
156
+ }
157
+ let scriptLineNumber = lineNumber;
158
+ let scriptColumnNumber = columnNumber;
159
+ if (script.hasSourceURL) {
160
+ scriptLineNumber -= startLine;
161
+ if (scriptLineNumber === 0) {
162
+ scriptColumnNumber -= startColumn;
163
+ }
132
164
  }
165
+ locations.push(debuggerModel.createRawLocation(script, scriptLineNumber, scriptColumnNumber));
133
166
  }
134
- return [];
167
+ return locations;
135
168
  }
136
169
 
137
170
  uiLocationToCSSLocations(uiLocation: Workspace.UISourceCode.UILocation): SDK.CSSModel.CSSLocation[] {
@@ -341,7 +374,7 @@ class Binding implements TextUtils.ContentProvider.ContentProvider {
341
374
  if (!debuggerModel) {
342
375
  return [];
343
376
  }
344
- return debuggerModel.scriptsForSourceURL(this.#uiSourceCode.url());
377
+ return debuggerModel.scripts().filter(script => script.embedderName() === this.#uiSourceCode.url());
345
378
  }
346
379
 
347
380
  async styleSheetChanged(stylesheet: SDK.CSSStyleSheetHeader.CSSStyleSheetHeader, edit: SDK.CSSModel.Edit|null):
@@ -298,7 +298,7 @@ export class ResourceScriptFile extends Common.ObjectWrapper.ObjectWrapper<Resou
298
298
  }
299
299
 
300
300
  // Match ignoring sourceURL.
301
- if (!workingCopy.startsWith(this.#scriptSource.trimRight())) {
301
+ if (!workingCopy.startsWith(this.#scriptSource.trimEnd())) {
302
302
  return true;
303
303
  }
304
304
  const suffix = this.#uiSourceCodeInternal.workingCopy().substr(this.#scriptSource.length);
@@ -423,6 +423,18 @@ export class ResourceScriptFile extends Common.ObjectWrapper.ObjectWrapper<Resou
423
423
  return this.scriptInternal !== undefined && Boolean(this.scriptInternal.sourceMapURL);
424
424
  }
425
425
 
426
+ async missingSymbolFiles(): Promise<string[]|null> {
427
+ if (!this.scriptInternal) {
428
+ return null;
429
+ }
430
+ const {pluginManager} = this.#resourceScriptMapping.debuggerWorkspaceBinding;
431
+ if (!pluginManager) {
432
+ return null;
433
+ }
434
+ const sources = await pluginManager.getSourcesForScript(this.scriptInternal);
435
+ return sources && 'missingSymbolFiles' in sources ? sources.missingSymbolFiles : null;
436
+ }
437
+
426
438
  get script(): SDK.Script.Script|null {
427
439
  return this.scriptInternal || null;
428
440
  }
@@ -111,6 +111,7 @@ export namespace PrivateAPI {
111
111
 
112
112
  export const enum RecorderExtensionPluginCommands {
113
113
  Stringify = 'stringify',
114
+ StringifyStep = 'stringifyStep',
114
115
  }
115
116
 
116
117
  export const enum RecorderExtensionPluginEvents {
@@ -132,6 +133,7 @@ export namespace PrivateAPI {
132
133
  type RegisterRecorderExtensionPluginRequest = {
133
134
  command: Commands.RegisterRecorderExtensionPlugin,
134
135
  pluginName: string,
136
+ mediaType: string,
135
137
  port: MessagePort,
136
138
  };
137
139
  type SubscribeRequest = {command: Commands.Subscribe, type: string};
@@ -276,7 +278,12 @@ export namespace PrivateAPI {
276
278
  parameters: {recording: Record<string, unknown>},
277
279
  };
278
280
 
279
- export type RecorderExtensionRequests = StringifyRequest;
281
+ type StringifyStepRequest = {
282
+ method: RecorderExtensionPluginCommands.StringifyStep,
283
+ parameters: {step: Record<string, unknown>},
284
+ };
285
+
286
+ export type RecorderExtensionRequests = StringifyRequest|StringifyStepRequest;
280
287
  }
281
288
 
282
289
  declare global {
@@ -705,8 +712,8 @@ self.injectedExtensionAPI = function(
705
712
  (RecorderServicesAPIImpl.prototype as
706
713
  Pick<APIImpl.RecorderExtensions, 'registerRecorderExtensionPlugin'|'unregisterRecorderExtensionPlugin'>) = {
707
714
  registerRecorderExtensionPlugin: async function(
708
- this: APIImpl.RecorderExtensions, plugin: PublicAPI.Chrome.DevTools.RecorderExtensionPlugin,
709
- pluginName: string): Promise<void> {
715
+ this: APIImpl.RecorderExtensions, plugin: PublicAPI.Chrome.DevTools.RecorderExtensionPlugin, pluginName: string,
716
+ mediaType: string): Promise<void> {
710
717
  if (this._plugins.has(plugin)) {
711
718
  throw new Error(`Tried to register plugin '${pluginName}' twice`);
712
719
  }
@@ -720,11 +727,14 @@ self.injectedExtensionAPI = function(
720
727
  .catch(error => port.postMessage({requestId, error: {message: error.message}}));
721
728
  };
722
729
 
723
- function dispatchMethodCall(request: PrivateAPI.RecorderExtensionRequests): Promise<unknown> {
730
+ async function dispatchMethodCall(request: PrivateAPI.RecorderExtensionRequests): Promise<unknown> {
724
731
  switch (request.method) {
725
732
  case PrivateAPI.RecorderExtensionPluginCommands.Stringify:
726
733
  return plugin.stringify(request.parameters.recording);
734
+ case PrivateAPI.RecorderExtensionPluginCommands.StringifyStep:
735
+ return plugin.stringifyStep(request.parameters.step);
727
736
  default:
737
+ // @ts-expect-error
728
738
  throw new Error(`'${request.method}' is not recognized`);
729
739
  }
730
740
  }
@@ -734,6 +744,7 @@ self.injectedExtensionAPI = function(
734
744
  {
735
745
  command: PrivateAPI.Commands.RegisterRecorderExtensionPlugin,
736
746
  pluginName,
747
+ mediaType,
737
748
  port: channel.port2,
738
749
  },
739
750
  () => resolve(), [channel.port2]);
@@ -222,10 +222,10 @@ export class ExtensionServer extends Common.ObjectWrapper.ObjectWrapper<EventTyp
222
222
  private registerRecorderExtensionEndpoint(
223
223
  message: PrivateAPI.ExtensionServerRequestMessage, _shared_port: MessagePort): Record {
224
224
  if (message.command !== PrivateAPI.Commands.RegisterRecorderExtensionPlugin) {
225
- return this.status.E_BADARG('command', `expected ${PrivateAPI.Commands.Subscribe}`);
225
+ return this.status.E_BADARG('command', `expected ${PrivateAPI.Commands.RegisterRecorderExtensionPlugin}`);
226
226
  }
227
- const {pluginName, port} = message;
228
- RecorderPluginManager.instance().addPlugin(new RecorderExtensionEndpoint(pluginName, port));
227
+ const {pluginName, mediaType, port} = message;
228
+ RecorderPluginManager.instance().addPlugin(new RecorderExtensionEndpoint(pluginName, mediaType, port));
229
229
  return this.status.OK();
230
230
  }
231
231
 
@@ -8,16 +8,22 @@ import {RecorderPluginManager} from './RecorderPluginManager.js';
8
8
 
9
9
  export class RecorderExtensionEndpoint extends ExtensionEndpoint {
10
10
  private readonly name: string;
11
+ private readonly mediaType: string;
11
12
 
12
- constructor(name: string, port: MessagePort) {
13
+ constructor(name: string, mediaType: string, port: MessagePort) {
13
14
  super(port);
14
15
  this.name = name;
16
+ this.mediaType = mediaType;
15
17
  }
16
18
 
17
19
  getName(): string {
18
20
  return this.name;
19
21
  }
20
22
 
23
+ getMediaType(): string {
24
+ return this.mediaType;
25
+ }
26
+
21
27
  protected handleEvent({event}: {event: string}): void {
22
28
  switch (event) {
23
29
  case PrivateAPI.RecorderExtensionPluginEvents.UnregisteredRecorderExtensionPlugin: {
@@ -40,4 +46,15 @@ export class RecorderExtensionEndpoint extends ExtensionEndpoint {
40
46
  stringify(recording: Object): Promise<string> {
41
47
  return this.sendRequest(PrivateAPI.RecorderExtensionPluginCommands.Stringify, {recording});
42
48
  }
49
+
50
+ /**
51
+ * In practice, `step` is a Step[1], but we avoid defining this type on the
52
+ * API in order to prevent dependencies between Chrome and puppeteer. Extensions
53
+ * are responsible for working out compatibility issues.
54
+ *
55
+ * [1]: https://github.com/puppeteer/replay/blob/main/src/Schema.ts#L243
56
+ */
57
+ stringifyStep(step: Object): Promise<string> {
58
+ return this.sendRequest(PrivateAPI.RecorderExtensionPluginCommands.StringifyStep, {step});
59
+ }
43
60
  }
@@ -88,7 +88,7 @@ export class Automapping {
88
88
  const networkProjects = this.workspace.projectsForType(Workspace.Workspace.projectTypes.Network);
89
89
  for (const networkProject of networkProjects) {
90
90
  for (const uiSourceCode of networkProject.uiSourceCodes()) {
91
- this.computeNetworkStatus(uiSourceCode);
91
+ void this.computeNetworkStatus(uiSourceCode);
92
92
  }
93
93
  }
94
94
  this.onSweepHappenedForTest();
@@ -137,7 +137,7 @@ export class Automapping {
137
137
  this.fileSystemUISourceCodes.add(uiSourceCode);
138
138
  this.scheduleSweep();
139
139
  } else if (project.type() === Workspace.Workspace.projectTypes.Network) {
140
- this.computeNetworkStatus(uiSourceCode);
140
+ void this.computeNetworkStatus(uiSourceCode);
141
141
  }
142
142
  }
143
143
 
@@ -173,20 +173,24 @@ export class Automapping {
173
173
  this.scheduleSweep();
174
174
  }
175
175
 
176
- private computeNetworkStatus(networkSourceCode: Workspace.UISourceCode.UISourceCode): void {
177
- if (this.sourceCodeToProcessingPromiseMap.has(networkSourceCode) ||
178
- this.sourceCodeToAutoMappingStatusMap.has(networkSourceCode)) {
179
- return;
176
+ computeNetworkStatus(networkSourceCode: Workspace.UISourceCode.UISourceCode): Promise<void> {
177
+ const processingPromise = this.sourceCodeToProcessingPromiseMap.get(networkSourceCode);
178
+ if (processingPromise) {
179
+ return processingPromise;
180
+ }
181
+ if (this.sourceCodeToAutoMappingStatusMap.has(networkSourceCode)) {
182
+ return Promise.resolve();
180
183
  }
181
184
  if (this.interceptors.some(interceptor => interceptor(networkSourceCode))) {
182
- return;
185
+ return Promise.resolve();
183
186
  }
184
187
  if (networkSourceCode.url().startsWith('wasm://')) {
185
- return;
188
+ return Promise.resolve();
186
189
  }
187
190
  const createBindingPromise =
188
191
  this.createBinding(networkSourceCode).then(validateStatus.bind(this)).then(onStatus.bind(this));
189
192
  this.sourceCodeToProcessingPromiseMap.set(networkSourceCode, createBindingPromise);
193
+ return createBindingPromise;
190
194
 
191
195
  async function validateStatus(this: Automapping, status: AutomappingStatus|null): Promise<AutomappingStatus|null> {
192
196
  if (!status) {
@@ -242,7 +246,7 @@ export class Automapping {
242
246
  } else {
243
247
  if (networkContent.content) {
244
248
  // Trim trailing whitespaces because V8 adds trailing newline.
245
- isValid = fileContent.trimRight() === networkContent.content.trimRight();
249
+ isValid = fileContent.trimEnd() === networkContent.content.trimEnd();
246
250
  }
247
251
  }
248
252
  if (!isValid) {
@@ -252,18 +256,19 @@ export class Automapping {
252
256
  return status;
253
257
  }
254
258
 
255
- function onStatus(this: Automapping, status: AutomappingStatus|null): void {
259
+ async function onStatus(this: Automapping, status: AutomappingStatus|null): Promise<void> {
256
260
  if (this.sourceCodeToProcessingPromiseMap.get(networkSourceCode) !== createBindingPromise) {
257
261
  return;
258
262
  }
259
- this.sourceCodeToProcessingPromiseMap.delete(networkSourceCode);
260
263
  if (!status) {
261
264
  this.onBindingFailedForTest();
265
+ this.sourceCodeToProcessingPromiseMap.delete(networkSourceCode);
262
266
  return;
263
267
  }
264
268
  // TODO(lushnikov): remove this check once there's a single uiSourceCode per url. @see crbug.com/670180
265
269
  if (this.sourceCodeToAutoMappingStatusMap.has(status.network) ||
266
270
  this.sourceCodeToAutoMappingStatusMap.has(status.fileSystem)) {
271
+ this.sourceCodeToProcessingPromiseMap.delete(networkSourceCode);
267
272
  return;
268
273
  }
269
274
 
@@ -277,7 +282,8 @@ export class Automapping {
277
282
  this.scheduleSweep();
278
283
  }
279
284
  }
280
- void this.onStatusAdded.call(null, status);
285
+ await this.onStatusAdded.call(null, status);
286
+ this.sourceCodeToProcessingPromiseMap.delete(networkSourceCode);
281
287
  }
282
288
  }
283
289
 
@@ -29,6 +29,7 @@ export class PersistenceImpl extends Common.ObjectWrapper.ObjectWrapper<EventTyp
29
29
  super();
30
30
  this.workspace = workspace;
31
31
  this.breakpointManager = breakpointManager;
32
+ this.breakpointManager.addUpdateBindingsCallback(this.#setupBindings.bind(this));
32
33
  this.filePathPrefixesToBindingCount = new FilePathPrefixesBindingCounts();
33
34
 
34
35
  this.subscribedBindingEventListeners = new Platform.MapUtilities.Multimap();
@@ -79,6 +80,13 @@ export class PersistenceImpl extends Common.ObjectWrapper.ObjectWrapper<EventTyp
79
80
  await this.innerRemoveBinding(binding);
80
81
  }
81
82
 
83
+ #setupBindings(networkUISourceCode: Workspace.UISourceCode.UISourceCode): Promise<void> {
84
+ if (networkUISourceCode.project().type() !== Workspace.Workspace.projectTypes.Network) {
85
+ return Promise.resolve();
86
+ }
87
+ return this.mapping.computeNetworkStatus(networkUISourceCode);
88
+ }
89
+
82
90
  private async innerAddBinding(binding: PersistenceBinding): Promise<void> {
83
91
  bindings.set(binding.network, binding);
84
92
  bindings.set(binding.fileSystem, binding);
@@ -140,10 +148,10 @@ export class PersistenceImpl extends Common.ObjectWrapper.ObjectWrapper<EventTyp
140
148
  this.dispatchEventToListeners(Events.BindingRemoved, binding);
141
149
  }
142
150
 
143
- private async onStatusAdded(status: AutomappingStatus): Promise<void> {
151
+ private onStatusAdded(status: AutomappingStatus): Promise<void> {
144
152
  const binding = new PersistenceBinding(status.network, status.fileSystem);
145
153
  statusBindings.set(status, binding);
146
- await this.innerAddBinding(binding);
154
+ return this.innerAddBinding(binding);
147
155
  }
148
156
 
149
157
  private async onStatusRemoved(status: AutomappingStatus): Promise<void> {
@@ -261,8 +261,8 @@ export class WorkspaceImpl extends Common.ObjectWrapper.ObjectWrapper<EventTypes
261
261
 
262
262
  // This method explicitly awaits the UISourceCode if not yet
263
263
  // available.
264
- uiSourceCodeForURLPromise(url: Platform.DevToolsPath.UrlString): Promise<UISourceCode> {
265
- const uiSourceCode = this.uiSourceCodeForURL(url);
264
+ uiSourceCodeForURLPromise(url: Platform.DevToolsPath.UrlString, type?: projectTypes): Promise<UISourceCode> {
265
+ const uiSourceCode = this.uiSourceCodeForURL(url, type);
266
266
  if (uiSourceCode) {
267
267
  return Promise.resolve(uiSourceCode);
268
268
  }
@@ -270,24 +270,31 @@ export class WorkspaceImpl extends Common.ObjectWrapper.ObjectWrapper<EventTypes
270
270
  const descriptor = this.addEventListener(Events.UISourceCodeAdded, event => {
271
271
  const uiSourceCode = event.data;
272
272
  if (uiSourceCode.url() === url) {
273
- this.removeEventListener(Events.UISourceCodeAdded, descriptor.listener);
274
- resolve(uiSourceCode);
273
+ if (!type || type === uiSourceCode.project().type()) {
274
+ this.removeEventListener(Events.UISourceCodeAdded, descriptor.listener);
275
+ resolve(uiSourceCode);
276
+ }
275
277
  }
276
278
  });
277
279
  });
278
280
  }
279
281
 
280
- uiSourceCodeForURL(url: Platform.DevToolsPath.UrlString): UISourceCode|null {
282
+ uiSourceCodeForURL(url: Platform.DevToolsPath.UrlString, type?: projectTypes): UISourceCode|null {
281
283
  for (const project of this.projectsInternal.values()) {
282
- const uiSourceCode = project.uiSourceCodeForURL(url);
283
- if (uiSourceCode) {
284
- return uiSourceCode;
284
+ // For snippets, we may get two different UISourceCodes for the same url (one belonging to
285
+ // the file system project, one belonging to the network project). Allow selecting the UISourceCode
286
+ // for a specific project type.
287
+ if (!type || project.type() === type) {
288
+ const uiSourceCode = project.uiSourceCodeForURL(url);
289
+ if (uiSourceCode) {
290
+ return uiSourceCode;
291
+ }
285
292
  }
286
293
  }
287
294
  return null;
288
295
  }
289
296
 
290
- uiSourceCodesForProjectType(type: string): UISourceCode[] {
297
+ uiSourceCodesForProjectType(type: projectTypes): UISourceCode[] {
291
298
  const result: UISourceCode[] = [];
292
299
  for (const project of this.projectsInternal.values()) {
293
300
  if (project.type() === type) {
@@ -56,6 +56,11 @@ const UIStrings = {
56
56
  *@description Data grid name for DOM Storage Items data grids
57
57
  */
58
58
  domStorageItems: 'DOM Storage Items',
59
+ /**
60
+ *@description Text for announcing number of entries after filtering
61
+ *@example {'DOM Storage Items'} PH1
62
+ */
63
+ domStorageItemsCleared: '{PH1} cleared',
59
64
  /**
60
65
  *@description Text in DOMStorage Items View of the Application panel
61
66
  */
@@ -149,6 +154,7 @@ export class DOMStorageItemsView extends StorageItemsView {
149
154
 
150
155
  this.dataGrid.rootNode().removeChildren();
151
156
  this.dataGrid.addCreationNode(false);
157
+ UI.ARIAUtils.alert(i18nString(UIStrings.domStorageItemsCleared, {PH1: this.dataGrid.displayName}));
152
158
  this.setCanDeleteSelected(false);
153
159
  }
154
160
 
@@ -1397,7 +1397,8 @@ export class ConsoleViewMessage implements ConsoleViewportElement {
1397
1397
  if (scripts.length) {
1398
1398
  const location =
1399
1399
  new SDK.DebuggerModel.Location(debuggerModel, scripts[0].scriptId, lineNumber || 0, columnNumber);
1400
- return await debuggerWorkspaceBinding.pluginManager.getFunctionInfo(scripts[0], location) ?? {frames: []};
1400
+ const functionInfo = await debuggerWorkspaceBinding.pluginManager.getFunctionInfo(scripts[0], location);
1401
+ return functionInfo && 'frames' in functionInfo ? functionInfo : {frames: []};
1401
1402
  }
1402
1403
  }
1403
1404
 
@@ -86,6 +86,11 @@ const UIStrings = {
86
86
  *@description Text in Call Stack Sidebar Pane of the Sources panel when some call frames have warnings
87
87
  */
88
88
  callFrameWarnings: 'Some call frames have warnings',
89
+ /**
90
+ *@description Error message that is displayed in UI when a file needed for debugging information for a call frame is missing
91
+ *@example {src/myapp.debug.wasm.dwp} PH1
92
+ */
93
+ debugFileNotFound: 'Failed to load debug file "{PH1}".',
89
94
  /**
90
95
  * @description A contex menu item in the Call Stack Sidebar Pane. "Restart" is a verb and
91
96
  * "frame" is a noun. "Frame" refers to an individual item in the call stack, i.e. a call frame.
@@ -204,8 +209,8 @@ export class CallStackSidebarPane extends UI.View.SimpleView implements UI.Conte
204
209
  return item;
205
210
  });
206
211
  itemPromises.push(itemPromise);
207
- for (const warning of frame.warnings) {
208
- uniqueWarnings.add(warning);
212
+ if (frame.missingDebugInfoDetails) {
213
+ uniqueWarnings.add(frame.missingDebugInfoDetails.details);
209
214
  }
210
215
  }
211
216
  const items = await Promise.all(itemPromises);
@@ -316,9 +321,11 @@ export class CallStackSidebarPane extends UI.View.SimpleView implements UI.Conte
316
321
  element.appendChild(UI.Icon.Icon.create('smallicon-thick-right-arrow', 'selected-call-frame-icon'));
317
322
  element.tabIndex = item === this.list.selectedItem() ? 0 : -1;
318
323
 
319
- if (callframe && callframe.warnings.length) {
324
+ if (callframe && callframe.missingDebugInfoDetails) {
320
325
  const icon = UI.Icon.Icon.create('smallicon-warning', 'call-frame-warning-icon');
321
- UI.Tooltip.Tooltip.install(icon, callframe.warnings.join('\n'));
326
+ const messages =
327
+ callframe.missingDebugInfoDetails.resources.map(r => i18nString(UIStrings.debugFileNotFound, {PH1: r}));
328
+ UI.Tooltip.Tooltip.install(icon, [callframe.missingDebugInfoDetails.details, ...messages].join('\n'));
322
329
  element.appendChild(icon);
323
330
  }
324
331
  return element;
@@ -141,6 +141,16 @@ const UIStrings = {
141
141
  *@description Text in Debugger Plugin of the Sources panel
142
142
  */
143
143
  theDebuggerWillSkipStepping: 'The debugger will skip stepping through this script, and will not stop on exceptions.',
144
+ /**
145
+ *@description Error message that is displayed in UI when a file needed for debugging information for a call frame is missing
146
+ *@example {src/myapp.debug.wasm.dwp} PH1
147
+ */
148
+ debugFileNotFound: 'Failed to load debug file "{PH1}".',
149
+ /**
150
+ *@description Error message that is displayed when no debug info could be loaded
151
+ *@example {app.wasm} PH1
152
+ */
153
+ debugInfoNotFound: 'Failed to load any debug info for {PH1}.',
144
154
  };
145
155
  const str_ = i18n.i18n.registerUIStrings('panels/sources/DebuggerPlugin.ts', UIStrings);
146
156
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -194,6 +204,7 @@ export class DebuggerPlugin extends Plugin {
194
204
  private prettyPrintInfobar!: UI.Infobar.Infobar|null;
195
205
  private refreshBreakpointsTimeout: undefined|number = undefined;
196
206
  private activeBreakpointDialog: BreakpointEditDialog|null = null;
207
+ private missingDebugInfoBar: UI.Infobar.Infobar|null = null;
197
208
 
198
209
  constructor(
199
210
  uiSourceCode: Workspace.UISourceCode.UISourceCode,
@@ -237,7 +248,6 @@ export class DebuggerPlugin extends Plugin {
237
248
 
238
249
  this.ignoreListInfobar = null;
239
250
  this.showIgnoreListInfobarIfNeeded();
240
-
241
251
  for (const scriptFile of this.scriptFileForDebuggerModel.values()) {
242
252
  scriptFile.checkMapping();
243
253
  }
@@ -332,6 +342,9 @@ export class DebuggerPlugin extends Plugin {
332
342
  editor.dispatch({effects: SourceFrame.SourceFrame.addNonBreakableLines.of(linePositions)});
333
343
  }
334
344
  }, console.error);
345
+ if (this.missingDebugInfoBar) {
346
+ this.attachInfobar(this.missingDebugInfoBar);
347
+ }
335
348
  if (!this.muted) {
336
349
  void this.refreshBreakpoints();
337
350
  }
@@ -1350,6 +1363,42 @@ export class DebuggerPlugin extends Plugin {
1350
1363
  if (newScriptFile.hasSourceMapURL()) {
1351
1364
  this.showSourceMapInfobar();
1352
1365
  }
1366
+
1367
+ void newScriptFile.missingSymbolFiles().then(resources => {
1368
+ if (resources) {
1369
+ const details = i18nString(UIStrings.debugInfoNotFound, {PH1: newScriptFile.uiSourceCode.url()});
1370
+ this.updateMissingDebugInfoInfobar({resources, details});
1371
+ } else {
1372
+ this.updateMissingDebugInfoInfobar(null);
1373
+ }
1374
+ });
1375
+ }
1376
+
1377
+ private updateMissingDebugInfoInfobar(warning: SDK.DebuggerModel.MissingDebugInfoDetails|null): void {
1378
+ if (this.missingDebugInfoBar) {
1379
+ return;
1380
+ }
1381
+ if (warning === null) {
1382
+ this.removeInfobar(this.missingDebugInfoBar);
1383
+ this.missingDebugInfoBar = null;
1384
+ return;
1385
+ }
1386
+ this.missingDebugInfoBar = UI.Infobar.Infobar.create(UI.Infobar.Type.Error, warning.details, []);
1387
+ if (!this.missingDebugInfoBar) {
1388
+ return;
1389
+ }
1390
+ for (const resource of warning.resources) {
1391
+ const detailsRow =
1392
+ this.missingDebugInfoBar?.createDetailsRowMessage(i18nString(UIStrings.debugFileNotFound, {PH1: resource}));
1393
+ if (detailsRow) {
1394
+ detailsRow.classList.add('infobar-selectable');
1395
+ }
1396
+ }
1397
+ this.missingDebugInfoBar.setCloseCallback(() => {
1398
+ this.removeInfobar(this.missingDebugInfoBar);
1399
+ this.missingDebugInfoBar = null;
1400
+ });
1401
+ this.attachInfobar(this.missingDebugInfoBar);
1353
1402
  }
1354
1403
 
1355
1404
  private showSourceMapInfobar(): void {
@@ -1481,6 +1530,7 @@ export class DebuggerPlugin extends Plugin {
1481
1530
  const uiLocation = await liveLocation.uiLocation();
1482
1531
  if (uiLocation && uiLocation.uiSourceCode.url() === this.uiSourceCode.url()) {
1483
1532
  this.setExecutionLocation(uiLocation);
1533
+ this.updateMissingDebugInfoInfobar(callFrame.missingDebugInfoDetails);
1484
1534
  } else {
1485
1535
  this.setExecutionLocation(null);
1486
1536
  }
@@ -53,6 +53,7 @@ export interface DataGridData {
53
53
  rows: Row[];
54
54
  activeSort: SortState|null;
55
55
  contextMenus?: DataGridContextMenusConfiguration;
56
+ label?: string;
56
57
  }
57
58
 
58
59
  const enum UserScrollState {
@@ -76,6 +77,7 @@ export class DataGrid extends HTMLElement {
76
77
  #isRendering = false;
77
78
  #userScrollState: UserScrollState = UserScrollState.NOT_SCROLLED;
78
79
  #contextMenus?: DataGridContextMenusConfiguration = undefined;
80
+ #label?: string = undefined;
79
81
  #currentResize: {
80
82
  rightCellCol: HTMLTableColElement,
81
83
  leftCellCol: HTMLTableColElement,
@@ -132,6 +134,7 @@ export class DataGrid extends HTMLElement {
132
134
  rows: this.#rows as Row[],
133
135
  activeSort: this.#sortState,
134
136
  contextMenus: this.#contextMenus,
137
+ label: this.#label,
135
138
  };
136
139
  }
137
140
 
@@ -143,6 +146,7 @@ export class DataGrid extends HTMLElement {
143
146
  });
144
147
  this.#sortState = data.activeSort;
145
148
  this.#contextMenus = data.contextMenus;
149
+ this.#label = data.label;
146
150
 
147
151
  /**
148
152
  * On first render, now we have data, we can figure out which cell is the
@@ -711,6 +715,7 @@ export class DataGrid extends HTMLElement {
711
715
  })}
712
716
  <div class="wrapping-container" @scroll=${this.#onScroll} @focusout=${this.#onFocusOut}>
713
717
  <table
718
+ aria-label=${LitHtml.Directives.ifDefined(this.#label)}
714
719
  aria-rowcount=${this.#rows.length}
715
720
  aria-colcount=${this.#columns.length}
716
721
  @keydown=${this.#onTableKeyDown}
@@ -23,6 +23,7 @@ export interface DataGridControllerData {
23
23
  */
24
24
  initialSort?: SortState;
25
25
  contextMenus?: DataGridContextMenusConfiguration;
26
+ label?: string;
26
27
  }
27
28
 
28
29
  export class DataGridController extends HTMLElement {
@@ -33,6 +34,7 @@ export class DataGridController extends HTMLElement {
33
34
  #columns: readonly Column[] = [];
34
35
  #rows: Row[] = [];
35
36
  #contextMenus?: DataGridContextMenusConfiguration = undefined;
37
+ #label?: string = undefined;
36
38
 
37
39
  /**
38
40
  * Because the controller will sort data in place (e.g. mutate it) when we get
@@ -56,6 +58,7 @@ export class DataGridController extends HTMLElement {
56
58
  rows: this.#originalRows as Row[],
57
59
  filters: this.#filters,
58
60
  contextMenus: this.#contextMenus,
61
+ label: this.#label,
59
62
  };
60
63
  }
61
64
 
@@ -65,6 +68,7 @@ export class DataGridController extends HTMLElement {
65
68
  this.#contextMenus = data.contextMenus;
66
69
  this.#filters = data.filters || [];
67
70
  this.#contextMenus = data.contextMenus;
71
+ this.#label = data.label;
68
72
 
69
73
  this.#columns = [...this.#originalColumns];
70
74
  this.#rows = this.#cloneAndFilterRows(data.rows, this.#filters);
@@ -210,6 +214,7 @@ export class DataGridController extends HTMLElement {
210
214
  rows: this.#rows,
211
215
  activeSort: this.#sortState,
212
216
  contextMenus: this.#contextMenus,
217
+ label: this.#label,
213
218
  } as DataGridData}
214
219
  @columnheaderclick=${this.#onColumnHeaderClick}
215
220
  @contextmenucolumnsortclick=${this.#onContextMenuColumnSortClick}
@@ -236,4 +236,5 @@ export enum Type {
236
236
  Warning = 'warning',
237
237
  Info = 'info',
238
238
  Issue = 'issue',
239
+ Error = 'error',
239
240
  }