chrome-devtools-frontend 1.0.1153166 → 1.0.1155899

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 (61) hide show
  1. package/config/gni/devtools_grd_files.gni +3 -2
  2. package/front_end/core/common/Revealer.ts +1 -1
  3. package/front_end/core/host/InspectorFrontendHostAPI.ts +2 -0
  4. package/front_end/core/host/UserMetrics.ts +32 -12
  5. package/front_end/core/platform/array-utilities.ts +25 -9
  6. package/front_end/core/sdk/ChildTargetManager.ts +2 -1
  7. package/front_end/core/sdk/FilmStripModel.ts +35 -25
  8. package/front_end/devtools_compatibility.js +2 -0
  9. package/front_end/entrypoints/lighthouse_worker/LighthouseWorkerService.ts +8 -2
  10. package/front_end/entrypoints/worker_app/worker_app.ts +0 -1
  11. package/front_end/generated/InspectorBackendCommands.js +2 -1
  12. package/front_end/generated/protocol.ts +36 -0
  13. package/front_end/models/bindings/PresentationConsoleMessageHelper.ts +90 -73
  14. package/front_end/models/issues_manager/IssuesManager.ts +5 -0
  15. package/front_end/models/issues_manager/SourceFrameIssuesManager.ts +31 -61
  16. package/front_end/models/issues_manager/StylesheetLoadingIssue.ts +69 -0
  17. package/front_end/models/issues_manager/descriptions/stylesheetLateImport.md +4 -0
  18. package/front_end/models/issues_manager/descriptions/stylesheetRequestFailed.md +3 -0
  19. package/front_end/models/issues_manager/issues_manager.ts +2 -0
  20. package/front_end/models/timeline_model/TimelineModel.ts +4 -0
  21. package/front_end/models/trace/ModelImpl.ts +1 -0
  22. package/front_end/models/trace/README.md +73 -17
  23. package/front_end/models/trace/handlers/NetworkRequestsHandler.ts +1 -1
  24. package/front_end/models/trace/handlers/RendererHandler.ts +33 -143
  25. package/front_end/models/trace/handlers/UserTimings.md +1 -1
  26. package/front_end/models/trace/types/TraceEvents.ts +3 -2
  27. package/front_end/panels/application/ApplicationPanelSidebar.ts +9 -6
  28. package/front_end/panels/application/PreloadingTreeElement.ts +25 -7
  29. package/front_end/panels/application/preloading/PreloadingView.ts +64 -31
  30. package/front_end/panels/application/preloading/components/UsedPreloadingView.ts +19 -9
  31. package/front_end/panels/console/ConsoleViewMessage.ts +14 -2
  32. package/front_end/panels/elements/ElementsPanel.ts +2 -3
  33. package/front_end/panels/elements/components/LayoutPane.ts +256 -60
  34. package/front_end/panels/elements/elements-legacy.ts +0 -3
  35. package/front_end/panels/elements/elements-meta.ts +10 -2
  36. package/front_end/panels/elements/elements.ts +0 -2
  37. package/front_end/panels/network/NetworkDataGridNode.ts +8 -0
  38. package/front_end/panels/network/NetworkLogView.ts +2 -1
  39. package/front_end/panels/network/NetworkPanel.ts +12 -1
  40. package/front_end/panels/recorder/components/ExtensionView.ts +1 -1
  41. package/front_end/panels/sources/DebuggerPlugin.ts +7 -4
  42. package/front_end/panels/sources/SourcesPanel.ts +1 -1
  43. package/front_end/panels/sources/components/BreakpointsView.ts +406 -89
  44. package/front_end/panels/sources/sources-meta.ts +13 -4
  45. package/front_end/panels/sources/sources.ts +0 -2
  46. package/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +1 -1
  47. package/front_end/panels/timeline/TimelineFlameChartNetworkDataProvider.ts +106 -95
  48. package/front_end/panels/timeline/TimelineFlameChartView.ts +1 -1
  49. package/front_end/panels/timeline/TimelinePaintProfilerView.ts +5 -0
  50. package/front_end/panels/timeline/TimelinePanel.ts +8 -8
  51. package/front_end/ui/legacy/UIUtils.ts +1 -1
  52. package/front_end/ui/legacy/components/perf_ui/.eslintrc.js +18 -0
  53. package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +5 -1
  54. package/front_end/ui/legacy/components/perf_ui/TimelineOverviewPane.ts +5 -2
  55. package/front_end/ui/legacy/components/utils/Linkifier.ts +10 -0
  56. package/package.json +1 -1
  57. package/scripts/build/generate_deprecations.py +3 -0
  58. package/front_end/panels/elements/LayoutSidebarPane.ts +0 -249
  59. package/front_end/panels/sources/BreakpointsSidebarPane.ts +0 -480
  60. package/front_end/ui/components/docs/layout_pane/basic.html +0 -25
  61. package/front_end/ui/components/docs/layout_pane/basic.ts +0 -78
@@ -2,14 +2,20 @@
2
2
  // Use of this source code is governed by a BSD-style license that can be
3
3
  // found in the LICENSE file.
4
4
 
5
+ import * as Common from '../../../core/common/common.js';
5
6
  import * as Host from '../../../core/host/host.js';
6
7
  import * as i18n from '../../../core/i18n/i18n.js';
7
8
  import * as Platform from '../../../core/platform/platform.js';
8
9
  import {assertNotNullOrUndefined} from '../../../core/platform/platform.js';
9
10
  import * as SDK from '../../../core/sdk/sdk.js';
11
+ import * as Bindings from '../../../models/bindings/bindings.js';
12
+ import * as Breakpoints from '../../../models/breakpoints/breakpoints.js';
13
+ import * as TextUtils from '../../../models/text_utils/text_utils.js';
14
+ import * as Workspace from '../../../models/workspace/workspace.js';
10
15
  import * as ComponentHelpers from '../../../ui/components/helpers/helpers.js';
11
16
  import * as IconButton from '../../../ui/components/icon_button/icon_button.js';
12
17
  import * as Input from '../../../ui/components/input/input.js';
18
+ import * as LegacyWrapper from '../../../ui/components/legacy_wrapper/legacy_wrapper.js';
13
19
  import * as Coordinator from '../../../ui/components/render_coordinator/render_coordinator.js';
14
20
  import * as UI from '../../../ui/legacy/legacy.js';
15
21
  import * as LitHtml from '../../../ui/lit-html/lit-html.js';
@@ -130,77 +136,408 @@ export const enum BreakpointStatus {
130
136
  INDETERMINATE = 'INDETERMINATE',
131
137
  }
132
138
 
133
- export class CheckboxToggledEvent extends Event {
134
- static readonly eventName = 'checkboxtoggled';
135
- data: {breakpointItem: BreakpointItem, checked: boolean};
139
+ let breakpointsViewInstance: LegacyWrapper.LegacyWrapper.LegacyWrapper<UI.Widget.Widget, BreakpointsView>|null;
140
+ let breakpointsViewControllerInstance: BreakpointsSidebarController|null;
141
+
142
+ export class BreakpointsSidebarController implements UI.ContextFlavorListener.ContextFlavorListener {
143
+ readonly #breakpointManager: Breakpoints.BreakpointManager.BreakpointManager;
144
+ readonly #breakpointItemToLocationMap =
145
+ new WeakMap<BreakpointItem, Breakpoints.BreakpointManager.BreakpointLocation[]>();
146
+ readonly #breakpointsActiveSetting: Common.Settings.Setting<boolean>;
147
+ readonly #pauseOnUncaughtExceptionSetting: Common.Settings.Setting<boolean>;
148
+ readonly #pauseOnCaughtExceptionSetting: Common.Settings.Setting<boolean>;
149
+
150
+ readonly #collapsedFilesSettings: Common.Settings.Setting<Platform.DevToolsPath.UrlString[]>;
151
+ readonly #collapsedFiles: Set<Platform.DevToolsPath.UrlString>;
152
+
153
+ // This is used to keep track of outstanding edits to breakpoints that were initiated
154
+ // by the breakpoint edit button (for UMA).
155
+ #outstandingBreakpointEdited: Breakpoints.BreakpointManager.Breakpoint|undefined;
156
+ #updateScheduled = false;
157
+ #updateRunning = false;
158
+
159
+ private constructor(
160
+ breakpointManager: Breakpoints.BreakpointManager.BreakpointManager, settings: Common.Settings.Settings) {
161
+ this.#collapsedFilesSettings = Common.Settings.Settings.instance().createSetting('collapsedFiles', []);
162
+ this.#collapsedFiles = new Set(this.#collapsedFilesSettings.get());
163
+ this.#breakpointManager = breakpointManager;
164
+ this.#breakpointManager.addEventListener(
165
+ Breakpoints.BreakpointManager.Events.BreakpointAdded, this.#onBreakpointAdded, this);
166
+ this.#breakpointManager.addEventListener(
167
+ Breakpoints.BreakpointManager.Events.BreakpointRemoved, this.#onBreakpointRemoved, this);
168
+ this.#breakpointsActiveSetting = settings.moduleSetting('breakpointsActive');
169
+ this.#breakpointsActiveSetting.addChangeListener(this.update, this);
170
+ this.#pauseOnUncaughtExceptionSetting = settings.moduleSetting('pauseOnUncaughtException');
171
+ this.#pauseOnUncaughtExceptionSetting.addChangeListener(this.update, this);
172
+ this.#pauseOnCaughtExceptionSetting = settings.moduleSetting('pauseOnCaughtException');
173
+ this.#pauseOnCaughtExceptionSetting.addChangeListener(this.update, this);
174
+ }
136
175
 
137
- constructor(breakpointItem: BreakpointItem, checked: boolean) {
138
- super(CheckboxToggledEvent.eventName);
139
- this.data = {breakpointItem: breakpointItem, checked};
176
+ static instance({forceNew, breakpointManager, settings}: {
177
+ forceNew: boolean|null,
178
+ breakpointManager: Breakpoints.BreakpointManager.BreakpointManager,
179
+ settings: Common.Settings.Settings,
180
+ } = {
181
+ forceNew: null,
182
+ breakpointManager: Breakpoints.BreakpointManager.BreakpointManager.instance(),
183
+ settings: Common.Settings.Settings.instance(),
184
+ }): BreakpointsSidebarController {
185
+ if (!breakpointsViewControllerInstance || forceNew) {
186
+ breakpointsViewControllerInstance = new BreakpointsSidebarController(breakpointManager, settings);
187
+ }
188
+ return breakpointsViewControllerInstance;
140
189
  }
141
- }
142
190
 
143
- export class PauseOnUncaughtExceptionsStateChangedEvent extends Event {
144
- static readonly eventName = 'pauseonuncaughtexceptionsstatechanged';
145
- data: {checked: boolean};
191
+ static removeInstance(): void {
192
+ breakpointsViewControllerInstance = null;
193
+ }
146
194
 
147
- constructor(checked: boolean) {
148
- super(PauseOnUncaughtExceptionsStateChangedEvent.eventName);
149
- this.data = {checked};
195
+ static targetSupportsIndependentPauseOnExceptionToggles(): boolean {
196
+ const hasNodeTargets =
197
+ SDK.TargetManager.TargetManager.instance().targets().some(target => target.type() === SDK.Target.Type.Node);
198
+ return !hasNodeTargets;
150
199
  }
151
- }
152
200
 
153
- export class PauseOnCaughtExceptionsStateChangedEvent extends Event {
154
- static readonly eventName = 'pauseoncaughtexceptionsstatechanged';
155
- data: {checked: boolean};
201
+ flavorChanged(_object: Object|null): void {
202
+ void this.update();
203
+ }
156
204
 
157
- constructor(checked: boolean) {
158
- super(PauseOnCaughtExceptionsStateChangedEvent.eventName);
159
- this.data = {checked};
205
+ breakpointEditFinished(breakpoint: Breakpoints.BreakpointManager.Breakpoint|null, edited: boolean): void {
206
+ if (this.#outstandingBreakpointEdited && this.#outstandingBreakpointEdited === breakpoint) {
207
+ if (edited) {
208
+ Host.userMetrics.actionTaken(Host.UserMetrics.Action.BreakpointConditionEditedFromSidebar);
209
+ }
210
+ this.#outstandingBreakpointEdited = undefined;
211
+ }
160
212
  }
161
- }
162
213
 
163
- export class ExpandedStateChangedEvent extends Event {
164
- static readonly eventName = 'expandedstatechanged';
165
- data: {url: Platform.DevToolsPath.UrlString, expanded: boolean};
214
+ breakpointStateChanged(breakpointItem: BreakpointItem, checked: boolean): void {
215
+ const locations = this.#getLocationsForBreakpointItem(breakpointItem);
216
+ locations.forEach((value: Breakpoints.BreakpointManager.BreakpointLocation) => {
217
+ const breakpoint = value.breakpoint;
218
+ breakpoint.setEnabled(checked);
219
+ });
220
+ }
166
221
 
167
- constructor(url: Platform.DevToolsPath.UrlString, expanded: boolean) {
168
- super(ExpandedStateChangedEvent.eventName);
169
- this.data = {url, expanded};
222
+ async breakpointEdited(breakpointItem: BreakpointItem, editButtonClicked: boolean): Promise<void> {
223
+ const locations = this.#getLocationsForBreakpointItem(breakpointItem);
224
+ let location: Breakpoints.BreakpointManager.BreakpointLocation|undefined;
225
+ for (const locationCandidate of locations) {
226
+ if (!location || locationCandidate.uiLocation.compareTo(location.uiLocation) < 0) {
227
+ location = locationCandidate;
228
+ }
229
+ }
230
+ if (location) {
231
+ if (editButtonClicked) {
232
+ this.#outstandingBreakpointEdited = location.breakpoint;
233
+ }
234
+ await Common.Revealer.reveal(location);
235
+ }
170
236
  }
171
- }
172
237
 
173
- export class BreakpointSelectedEvent extends Event {
174
- static readonly eventName = 'breakpointselected';
175
- data: {breakpointItem: BreakpointItem};
238
+ breakpointsRemoved(breakpointItems: BreakpointItem[]): void {
239
+ const locations = breakpointItems.flatMap(breakpointItem => this.#getLocationsForBreakpointItem(breakpointItem));
240
+ locations.forEach(location => location?.breakpoint.remove(false /* keepInStorage */));
241
+ }
242
+
243
+ expandedStateChanged(url: Platform.DevToolsPath.UrlString, expanded: boolean): void {
244
+ if (expanded) {
245
+ this.#collapsedFiles.delete(url);
246
+ } else {
247
+ this.#collapsedFiles.add(url);
248
+ }
176
249
 
177
- constructor(breakpointItem: BreakpointItem) {
178
- super(BreakpointSelectedEvent.eventName);
179
- this.data = {breakpointItem: breakpointItem};
250
+ this.#saveSettings();
180
251
  }
181
- }
182
252
 
183
- export class BreakpointEditedEvent extends Event {
184
- static readonly eventName = 'breakpointedited';
185
- data: {breakpointItem: BreakpointItem, editButtonClicked: boolean};
253
+ async jumpToSource(breakpointItem: BreakpointItem): Promise<void> {
254
+ const uiLocations = this.#getLocationsForBreakpointItem(breakpointItem).map(location => location.uiLocation);
255
+ let uiLocation: Workspace.UISourceCode.UILocation|undefined;
256
+ for (const uiLocationCandidate of uiLocations) {
257
+ if (!uiLocation || uiLocationCandidate.compareTo(uiLocation) < 0) {
258
+ uiLocation = uiLocationCandidate;
259
+ }
260
+ }
261
+ if (uiLocation) {
262
+ await Common.Revealer.reveal(uiLocation);
263
+ }
264
+ }
186
265
 
187
- constructor(breakpointItem: BreakpointItem, editButtonClicked: boolean) {
188
- super(BreakpointEditedEvent.eventName);
189
- this.data = {breakpointItem, editButtonClicked};
266
+ setPauseOnUncaughtExceptions(value: boolean): void {
267
+ this.#pauseOnUncaughtExceptionSetting.set(value);
268
+ }
269
+
270
+ setPauseOnCaughtExceptions(value: boolean): void {
271
+ this.#pauseOnCaughtExceptionSetting.set(value);
272
+ }
273
+
274
+ async update(): Promise<void> {
275
+ this.#updateScheduled = true;
276
+ if (this.#updateRunning) {
277
+ return;
278
+ }
279
+ this.#updateRunning = true;
280
+ while (this.#updateScheduled) {
281
+ this.#updateScheduled = false;
282
+ const data = await this.getUpdatedBreakpointViewData();
283
+ BreakpointsView.instance().data = data;
284
+ }
285
+ this.#updateRunning = false;
286
+ }
287
+
288
+ async getUpdatedBreakpointViewData(): Promise<BreakpointsViewData> {
289
+ const breakpointsActive = this.#breakpointsActiveSetting.get();
290
+ const independentPauseToggles = BreakpointsSidebarController.targetSupportsIndependentPauseOnExceptionToggles();
291
+ const pauseOnUncaughtExceptions = this.#pauseOnUncaughtExceptionSetting.get();
292
+ const pauseOnCaughtExceptions = this.#pauseOnCaughtExceptionSetting.get();
293
+
294
+ const breakpointLocations = this.#getBreakpointLocations();
295
+ if (!breakpointLocations.length) {
296
+ return {
297
+ breakpointsActive,
298
+ pauseOnCaughtExceptions,
299
+ pauseOnUncaughtExceptions,
300
+ independentPauseToggles,
301
+ groups: [],
302
+ };
303
+ }
304
+
305
+ const locationsGroupedById = this.#groupBreakpointLocationsById(breakpointLocations);
306
+ const locationIdsByLineId = this.#getLocationIdsByLineId(breakpointLocations);
307
+
308
+ const [content, selectedUILocation] = await Promise.all([
309
+ this.#getContent(locationsGroupedById),
310
+ this.#getHitUILocation(),
311
+ ]);
312
+
313
+ const scriptIdToGroup = new Map<string, BreakpointGroup>();
314
+
315
+ for (let idx = 0; idx < locationsGroupedById.length; idx++) {
316
+ const locations = locationsGroupedById[idx];
317
+ const fstLocation = locations[0];
318
+ const sourceURL = fstLocation.uiLocation.uiSourceCode.url();
319
+ const scriptId = fstLocation.uiLocation.uiSourceCode.canononicalScriptId();
320
+ const uiLocation = fstLocation.uiLocation;
321
+
322
+ const isHit = selectedUILocation !== null &&
323
+ locations.some(location => location.uiLocation.id() === selectedUILocation.id());
324
+
325
+ const numBreakpointsOnLine = locationIdsByLineId.get(uiLocation.lineId()).size;
326
+ const showColumn = numBreakpointsOnLine > 1;
327
+ const locationText = uiLocation.lineAndColumnText(showColumn) as string;
328
+
329
+ const text = content[idx];
330
+ const codeSnippet = text instanceof TextUtils.Text.Text ?
331
+ text.lineAt(uiLocation.lineNumber) :
332
+ text.lines[text.bytecodeOffsetToLineNumber(uiLocation.columnNumber ?? 0)] ?? '';
333
+
334
+ if (isHit && this.#collapsedFiles.has(sourceURL)) {
335
+ this.#collapsedFiles.delete(sourceURL);
336
+ this.#saveSettings();
337
+ }
338
+ const expanded = !this.#collapsedFiles.has(sourceURL);
339
+
340
+ const status: BreakpointStatus = this.#getBreakpointState(locations);
341
+ const {type, hoverText} = this.#getBreakpointTypeAndDetails(locations);
342
+ const item = {
343
+ id: fstLocation.breakpoint.breakpointStorageId(),
344
+ location: locationText,
345
+ codeSnippet,
346
+ isHit,
347
+ status,
348
+ type,
349
+ hoverText,
350
+ };
351
+ this.#breakpointItemToLocationMap.set(item, locations);
352
+
353
+ let group = scriptIdToGroup.get(scriptId);
354
+ if (group) {
355
+ group.breakpointItems.push(item);
356
+ group.expanded ||= expanded;
357
+ } else {
358
+ const editable = this.#breakpointManager.supportsConditionalBreakpoints(uiLocation.uiSourceCode);
359
+ group = {
360
+ url: sourceURL,
361
+ name: uiLocation.uiSourceCode.displayName(),
362
+ editable,
363
+ expanded,
364
+ breakpointItems: [item],
365
+ };
366
+ scriptIdToGroup.set(scriptId, group);
367
+ }
368
+ }
369
+ return {
370
+ breakpointsActive,
371
+ pauseOnCaughtExceptions,
372
+ pauseOnUncaughtExceptions,
373
+ independentPauseToggles,
374
+ groups: Array.from(scriptIdToGroup.values()),
375
+ };
376
+ }
377
+
378
+ #onBreakpointAdded(event: Common.EventTarget.EventTargetEvent<Breakpoints.BreakpointManager.BreakpointLocation>):
379
+ Promise<void> {
380
+ const breakpoint = event.data.breakpoint;
381
+ if (breakpoint.origin === Breakpoints.BreakpointManager.BreakpointOrigin.USER_ACTION &&
382
+ this.#collapsedFiles.has(breakpoint.url())) {
383
+ // Auto-expand if a new breakpoint was added to a collapsed group.
384
+ this.#collapsedFiles.delete(breakpoint.url());
385
+ this.#saveSettings();
386
+ }
387
+ return this.update();
388
+ }
389
+
390
+ #onBreakpointRemoved(event: Common.EventTarget.EventTargetEvent<Breakpoints.BreakpointManager.BreakpointLocation>):
391
+ Promise<void> {
392
+ const breakpoint = event.data.breakpoint;
393
+ if (this.#collapsedFiles.has(breakpoint.url())) {
394
+ const locations = Breakpoints.BreakpointManager.BreakpointManager.instance().allBreakpointLocations();
395
+ const otherBreakpointsOnSameFileExist =
396
+ locations.some(location => location.breakpoint.url() === breakpoint.url());
397
+ if (!otherBreakpointsOnSameFileExist) {
398
+ // Clear up the #collapsedFiles set from this url if no breakpoint is left in this group.
399
+ this.#collapsedFiles.delete(breakpoint.url());
400
+ this.#saveSettings();
401
+ }
402
+ }
403
+ return this.update();
404
+ }
405
+
406
+ #saveSettings(): void {
407
+ this.#collapsedFilesSettings.set(Array.from(this.#collapsedFiles.values()));
408
+ }
409
+
410
+ #getBreakpointTypeAndDetails(locations: Breakpoints.BreakpointManager.BreakpointLocation[]):
411
+ {type: SDK.DebuggerModel.BreakpointType, hoverText?: string} {
412
+ const breakpointWithCondition = locations.find(location => Boolean(location.breakpoint.condition()));
413
+ const breakpoint = breakpointWithCondition?.breakpoint;
414
+ if (!breakpoint || !breakpoint.condition()) {
415
+ return {type: SDK.DebuggerModel.BreakpointType.REGULAR_BREAKPOINT};
416
+ }
417
+
418
+ const condition = breakpoint.condition();
419
+ if (breakpoint.isLogpoint()) {
420
+ return {type: SDK.DebuggerModel.BreakpointType.LOGPOINT, hoverText: condition};
421
+ }
422
+
423
+ return {type: SDK.DebuggerModel.BreakpointType.CONDITIONAL_BREAKPOINT, hoverText: condition};
424
+ }
425
+
426
+ #getLocationsForBreakpointItem(breakpointItem: BreakpointItem): Breakpoints.BreakpointManager.BreakpointLocation[] {
427
+ const locations = this.#breakpointItemToLocationMap.get(breakpointItem);
428
+ assertNotNullOrUndefined(locations);
429
+ return locations;
430
+ }
431
+
432
+ async #getHitUILocation(): Promise<Workspace.UISourceCode.UILocation|null> {
433
+ const details = UI.Context.Context.instance().flavor(SDK.DebuggerModel.DebuggerPausedDetails);
434
+ if (details && details.callFrames.length) {
435
+ return await Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance().rawLocationToUILocation(
436
+ details.callFrames[0].location());
437
+ }
438
+ return null;
439
+ }
440
+
441
+ #getBreakpointLocations(): Breakpoints.BreakpointManager.BreakpointLocation[] {
442
+ const locations = this.#breakpointManager.allBreakpointLocations().filter(
443
+ breakpointLocation =>
444
+ breakpointLocation.uiLocation.uiSourceCode.project().type() !== Workspace.Workspace.projectTypes.Debugger);
445
+
446
+ locations.sort((item1, item2) => item1.uiLocation.compareTo(item2.uiLocation));
447
+
448
+ const result = [];
449
+ let lastBreakpoint: Breakpoints.BreakpointManager.Breakpoint|null = null;
450
+ let lastLocation: Workspace.UISourceCode.UILocation|null = null;
451
+ for (const location of locations) {
452
+ if (location.breakpoint !== lastBreakpoint || (lastLocation && location.uiLocation.compareTo(lastLocation))) {
453
+ result.push(location);
454
+ lastBreakpoint = location.breakpoint;
455
+ lastLocation = location.uiLocation;
456
+ }
457
+ }
458
+ return result;
459
+ }
460
+
461
+ #groupBreakpointLocationsById(breakpointLocations: Breakpoints.BreakpointManager.BreakpointLocation[]):
462
+ Breakpoints.BreakpointManager.BreakpointLocation[][] {
463
+ const map = new Platform.MapUtilities.Multimap<string, Breakpoints.BreakpointManager.BreakpointLocation>();
464
+ for (const breakpointLocation of breakpointLocations) {
465
+ const uiLocation = breakpointLocation.uiLocation;
466
+ map.set(uiLocation.id(), breakpointLocation);
467
+ }
468
+ const arr: Breakpoints.BreakpointManager.BreakpointLocation[][] = [];
469
+ for (const id of map.keysArray()) {
470
+ const locations = Array.from(map.get(id));
471
+ if (locations.length) {
472
+ arr.push(locations);
473
+ }
474
+ }
475
+ return arr;
190
476
  }
191
- }
192
477
 
193
- export class BreakpointsRemovedEvent extends Event {
194
- static readonly eventName = 'breakpointsremoved';
195
- data: {breakpointItems: BreakpointItem[]};
478
+ #getLocationIdsByLineId(breakpointLocations: Breakpoints.BreakpointManager.BreakpointLocation[]):
479
+ Platform.MapUtilities.Multimap<string, string> {
480
+ const result = new Platform.MapUtilities.Multimap<string, string>();
196
481
 
197
- constructor(breakpointItems: BreakpointItem[]) {
198
- super(BreakpointsRemovedEvent.eventName);
199
- this.data = {breakpointItems};
482
+ for (const breakpointLocation of breakpointLocations) {
483
+ const uiLocation = breakpointLocation.uiLocation;
484
+ result.set(uiLocation.lineId(), uiLocation.id());
485
+ }
486
+
487
+ return result;
488
+ }
489
+
490
+ #getBreakpointState(locations: Breakpoints.BreakpointManager.BreakpointLocation[]): BreakpointStatus {
491
+ const hasEnabled = locations.some(location => location.breakpoint.enabled());
492
+ const hasDisabled = locations.some(location => !location.breakpoint.enabled());
493
+ let status: BreakpointStatus;
494
+ if (hasEnabled) {
495
+ status = hasDisabled ? BreakpointStatus.INDETERMINATE : BreakpointStatus.ENABLED;
496
+ } else {
497
+ status = BreakpointStatus.DISABLED;
498
+ }
499
+ return status;
500
+ }
501
+
502
+ #getContent(locations: Breakpoints.BreakpointManager.BreakpointLocation[][]):
503
+ Promise<Array<TextUtils.Text.Text|Common.WasmDisassembly.WasmDisassembly>> {
504
+ // Use a cache to share the Text objects between all breakpoints. This way
505
+ // we share the cached line ending information that Text calculates. This
506
+ // was very slow to calculate with a lot of breakpoints in the same very
507
+ // large source file.
508
+ const contentToTextMap = new Map<string, TextUtils.Text.Text>();
509
+
510
+ return Promise.all(locations.map(async ([{uiLocation: {uiSourceCode}}]) => {
511
+ const deferredContent = await uiSourceCode.requestContent({cachedWasmOnly: true});
512
+ if ('wasmDisassemblyInfo' in deferredContent && deferredContent.wasmDisassemblyInfo) {
513
+ return deferredContent.wasmDisassemblyInfo;
514
+ }
515
+ const contentText = deferredContent.content || '';
516
+ if (contentToTextMap.has(contentText)) {
517
+ return contentToTextMap.get(contentText) as TextUtils.Text.Text;
518
+ }
519
+ const text = new TextUtils.Text.Text(contentText);
520
+ contentToTextMap.set(contentText, text);
521
+ return text;
522
+ }));
200
523
  }
201
524
  }
202
525
 
203
- export class BreakpointsView extends HTMLElement {
526
+ export class BreakpointsView extends LegacyWrapper.LegacyWrapper.WrappableComponent {
527
+ readonly #controller: BreakpointsSidebarController;
528
+
529
+ static instance(): BreakpointsView {
530
+ if (!breakpointsViewInstance) {
531
+ breakpointsViewInstance = LegacyWrapper.LegacyWrapper.legacyWrapper(UI.Widget.Widget, new BreakpointsView());
532
+ }
533
+ return breakpointsViewInstance.getComponent();
534
+ }
535
+
536
+ constructor() {
537
+ super();
538
+ this.#controller = BreakpointsSidebarController.instance();
539
+ }
540
+
204
541
  static readonly litTagName = LitHtml.literal`devtools-breakpoint-view`;
205
542
  readonly #shadow = this.attachShadow({mode: 'open'});
206
543
 
@@ -214,9 +551,6 @@ export class BreakpointsView extends HTMLElement {
214
551
  #breakpointGroups: BreakpointGroup[] = [];
215
552
  #urlToDifferentiatingPath: Map<Platform.DevToolsPath.UrlString, string> = new Map();
216
553
 
217
- #scheduledRender = false;
218
- #enqueuedRender = false;
219
-
220
554
  set data(data: BreakpointsViewData) {
221
555
  this.#pauseOnUncaughtExceptions = data.pauseOnUncaughtExceptions;
222
556
  this.#pauseOnCaughtExceptions = data.pauseOnCaughtExceptions;
@@ -230,22 +564,14 @@ export class BreakpointsView extends HTMLElement {
230
564
  }
231
565
  this.#urlToDifferentiatingPath = getDifferentiatingPathMap(titleInfos);
232
566
 
233
- void this.#render();
567
+ void this.render();
234
568
  }
235
569
 
236
570
  connectedCallback(): void {
237
571
  this.#shadow.adoptedStyleSheets = [Input.checkboxStyles, breakpointsViewStyles];
238
572
  }
239
573
 
240
- async #render(): Promise<void> {
241
- if (this.#scheduledRender) {
242
- // If we are already rendering, don't render again immediately, but
243
- // enqueue it to be run after we're done on our current render.
244
- this.#enqueuedRender = true;
245
- return;
246
- }
247
-
248
- this.#scheduledRender = true;
574
+ override async render(): Promise<void> {
249
575
  await coordinator.write('BreakpointsView render', () => {
250
576
  const clickHandler = async(event: Event): Promise<void> => {
251
577
  const currentTarget = event.currentTarget as HTMLElement;
@@ -296,15 +622,6 @@ export class BreakpointsView extends HTMLElement {
296
622
  element?.setAttribute('tabindex', '0');
297
623
  }
298
624
  });
299
-
300
- this.#scheduledRender = false;
301
-
302
- // If render() was called when we were already mid-render, let's re-render
303
- // to ensure we're not rendering any stale UI.
304
- if (this.#enqueuedRender) {
305
- this.#enqueuedRender = false;
306
- return this.#render();
307
- }
308
625
  }
309
626
 
310
627
  async #keyDownHandler(event: KeyboardEvent): Promise<void> {
@@ -388,7 +705,7 @@ export class BreakpointsView extends HTMLElement {
388
705
  const clickHandler = (event: Event): void => {
389
706
  Host.userMetrics.breakpointEditDialogRevealedFrom(
390
707
  Host.UserMetrics.BreakpointEditDialogRevealedFrom.BreakpointSidebarEditButton);
391
- this.dispatchEvent(new BreakpointEditedEvent(breakpointItem, true /* editButtonClicked */));
708
+ void this.#controller.breakpointEdited(breakpointItem, true /* editButtonClicked */);
392
709
  event.consume();
393
710
  };
394
711
  const title = breakpointItem.type === SDK.DebuggerModel.BreakpointType.LOGPOINT ?
@@ -414,7 +731,7 @@ export class BreakpointsView extends HTMLElement {
414
731
  breakpointItems: BreakpointItem[], tooltipText: string, action: Host.UserMetrics.Action): LitHtml.TemplateResult {
415
732
  const clickHandler = (event: Event): void => {
416
733
  Host.userMetrics.actionTaken(action);
417
- this.dispatchEvent(new BreakpointsRemovedEvent(breakpointItems));
734
+ void this.#controller.breakpointsRemoved(breakpointItems);
418
735
  event.consume();
419
736
  };
420
737
  // clang-format off
@@ -439,16 +756,16 @@ export class BreakpointsView extends HTMLElement {
439
756
 
440
757
  menu.defaultSection().appendItem(i18nString(UIStrings.removeAllBreakpointsInFile), () => {
441
758
  Host.userMetrics.actionTaken(Host.UserMetrics.Action.BreakpointsInFileRemovedFromContextMenu);
442
- this.dispatchEvent(new BreakpointsRemovedEvent(breakpointItems));
759
+ void this.#controller.breakpointsRemoved(breakpointItems);
443
760
  });
444
761
  const otherGroups = this.#breakpointGroups.filter(group => group !== breakpointGroup);
445
762
  menu.defaultSection().appendItem(i18nString(UIStrings.removeOtherBreakpoints), () => {
446
763
  const breakpointItems = otherGroups.map(({breakpointItems}) => breakpointItems).flat();
447
- this.dispatchEvent(new BreakpointsRemovedEvent(breakpointItems));
764
+ void this.#controller.breakpointsRemoved(breakpointItems);
448
765
  }, otherGroups.length === 0);
449
766
  menu.defaultSection().appendItem(i18nString(UIStrings.removeAllBreakpoints), () => {
450
767
  const breakpointItems = this.#breakpointGroups.map(({breakpointItems}) => breakpointItems).flat();
451
- this.dispatchEvent(new BreakpointsRemovedEvent(breakpointItems));
768
+ void this.#controller.breakpointsRemoved(breakpointItems);
452
769
  });
453
770
 
454
771
  const notEnabledItems =
@@ -456,7 +773,7 @@ export class BreakpointsView extends HTMLElement {
456
773
  menu.debugSection().appendItem(i18nString(UIStrings.enableAllBreakpointsInFile), () => {
457
774
  Host.userMetrics.actionTaken(Host.UserMetrics.Action.BreakpointsInFileEnabledDisabledFromContextMenu);
458
775
  for (const breakpointItem of notEnabledItems) {
459
- this.dispatchEvent(new CheckboxToggledEvent(breakpointItem, true));
776
+ this.#controller.breakpointStateChanged(breakpointItem, true);
460
777
  }
461
778
  }, notEnabledItems.length === 0);
462
779
  const notDisabledItems =
@@ -464,7 +781,7 @@ export class BreakpointsView extends HTMLElement {
464
781
  menu.debugSection().appendItem(i18nString(UIStrings.disableAllBreakpointsInFile), () => {
465
782
  Host.userMetrics.actionTaken(Host.UserMetrics.Action.BreakpointsInFileEnabledDisabledFromContextMenu);
466
783
  for (const breakpointItem of notDisabledItems) {
467
- this.dispatchEvent(new CheckboxToggledEvent(breakpointItem, false));
784
+ this.#controller.breakpointStateChanged(breakpointItem, false);
468
785
  }
469
786
  }, notDisabledItems.length === 0);
470
787
 
@@ -479,7 +796,7 @@ export class BreakpointsView extends HTMLElement {
479
796
  const toggleHandler = (event: Event): void => {
480
797
  const htmlDetails = event.target as HTMLDetailsElement;
481
798
  group.expanded = htmlDetails.open;
482
- this.dispatchEvent(new ExpandedStateChangedEvent(group.url, group.expanded));
799
+ void this.#controller.expandedStateChanged(group.url, group.expanded);
483
800
  };
484
801
  const clickHandler = async(event: Event): Promise<void> => {
485
802
  const selected = event.currentTarget as HTMLElement;
@@ -528,7 +845,7 @@ export class BreakpointsView extends HTMLElement {
528
845
  const updatedStatus = element.checked ? BreakpointStatus.ENABLED : BreakpointStatus.DISABLED;
529
846
  const itemsToUpdate = group.breakpointItems.filter(item => item.status !== updatedStatus);
530
847
  itemsToUpdate.forEach(item => {
531
- this.dispatchEvent(new CheckboxToggledEvent(item, element.checked));
848
+ this.#controller.breakpointStateChanged(item, element.checked);
532
849
  });
533
850
  e.consume();
534
851
  };
@@ -559,26 +876,26 @@ export class BreakpointsView extends HTMLElement {
559
876
  menu.revealSection().appendItem(editBreakpointText, () => {
560
877
  Host.userMetrics.breakpointEditDialogRevealedFrom(
561
878
  Host.UserMetrics.BreakpointEditDialogRevealedFrom.BreakpointSidebarContextMenu);
562
- this.dispatchEvent(new BreakpointEditedEvent(breakpointItem, false /* editButtonClicked */));
879
+ void this.#controller.breakpointEdited(breakpointItem, false /* editButtonClicked */);
563
880
  }, !editable);
564
881
 
565
882
  menu.defaultSection().appendItem(i18nString(UIStrings.removeBreakpoint), () => {
566
883
  Host.userMetrics.actionTaken(Host.UserMetrics.Action.BreakpointRemovedFromContextMenu);
567
- this.dispatchEvent(new BreakpointsRemovedEvent([breakpointItem]));
884
+ void this.#controller.breakpointsRemoved([breakpointItem]);
568
885
  });
569
886
  const otherItems = this.#breakpointGroups.map(({breakpointItems}) => breakpointItems)
570
887
  .flat()
571
888
  .filter(item => item !== breakpointItem);
572
889
  menu.defaultSection().appendItem(i18nString(UIStrings.removeOtherBreakpoints), () => {
573
- this.dispatchEvent(new BreakpointsRemovedEvent(otherItems));
890
+ void this.#controller.breakpointsRemoved(otherItems);
574
891
  }, otherItems.length === 0);
575
892
  menu.defaultSection().appendItem(i18nString(UIStrings.removeAllBreakpoints), () => {
576
893
  const breakpointItems = this.#breakpointGroups.map(({breakpointItems}) => breakpointItems).flat();
577
- this.dispatchEvent(new BreakpointsRemovedEvent(breakpointItems));
894
+ void this.#controller.breakpointsRemoved(breakpointItems);
578
895
  });
579
896
 
580
897
  menu.editSection().appendItem(i18nString(UIStrings.revealLocation), () => {
581
- this.dispatchEvent(new BreakpointSelectedEvent(breakpointItem));
898
+ void this.#controller.jumpToSource(breakpointItem);
582
899
  });
583
900
 
584
901
  void menu.show();
@@ -588,7 +905,7 @@ export class BreakpointsView extends HTMLElement {
588
905
  breakpointItem: BreakpointItem, editable: boolean, groupIndex: number,
589
906
  breakpointItemIndex: number): LitHtml.TemplateResult {
590
907
  const codeSnippetClickHandler = (event: Event): void => {
591
- this.dispatchEvent(new BreakpointSelectedEvent(breakpointItem));
908
+ void this.#controller.jumpToSource(breakpointItem);
592
909
  event.consume();
593
910
  };
594
911
  const breakpointItemClickHandler = async(event: Event): Promise<void> => {
@@ -676,12 +993,12 @@ export class BreakpointsView extends HTMLElement {
676
993
 
677
994
  #onCheckboxToggled(e: Event, item: BreakpointItem): void {
678
995
  const element = e.target as HTMLInputElement;
679
- this.dispatchEvent(new CheckboxToggledEvent(item, element.checked));
996
+ this.#controller.breakpointStateChanged(item, element.checked);
680
997
  }
681
998
 
682
999
  #onPauseOnCaughtExceptionsStateChanged(e: Event): void {
683
1000
  const {checked} = e.target as HTMLInputElement;
684
- this.dispatchEvent(new PauseOnCaughtExceptionsStateChangedEvent(checked));
1001
+ this.#controller.setPauseOnCaughtExceptions(checked);
685
1002
  }
686
1003
 
687
1004
  #onPauseOnUncaughtExceptionsStateChanged(e: Event): void {
@@ -705,7 +1022,7 @@ export class BreakpointsView extends HTMLElement {
705
1022
  }
706
1023
  });
707
1024
  }
708
- this.dispatchEvent(new PauseOnUncaughtExceptionsStateChangedEvent(checked));
1025
+ this.#controller.setPauseOnUncaughtExceptions(checked);
709
1026
  }
710
1027
  }
711
1028