chrome-devtools-frontend 1.0.1035409 → 1.0.1036726

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 (36) hide show
  1. package/AUTHORS +1 -0
  2. package/config/gni/devtools_grd_files.gni +2 -0
  3. package/docs/workflows.md +4 -5
  4. package/front_end/core/i18n/locales/en-US.json +21 -6
  5. package/front_end/core/i18n/locales/en-XL.json +21 -6
  6. package/front_end/core/root/Runtime.ts +8 -3
  7. package/front_end/core/sdk/CSSStyleDeclaration.ts +1 -1
  8. package/front_end/core/sdk/NetworkManager.ts +21 -5
  9. package/front_end/core/sdk/NetworkRequest.ts +10 -0
  10. package/front_end/entrypoints/main/MainImpl.ts +6 -5
  11. package/front_end/generated/InspectorBackendCommands.js +2 -2
  12. package/front_end/generated/SupportedCSSProperties.js +8 -4
  13. package/front_end/generated/protocol.ts +7 -1
  14. package/front_end/models/persistence/NetworkPersistenceManager.ts +25 -14
  15. package/front_end/panels/application/components/Prerender2.ts +5 -7
  16. package/front_end/panels/elements/CSSRuleValidator.ts +1 -1
  17. package/front_end/panels/elements/ElementsTreeOutline.ts +3 -3
  18. package/front_end/panels/elements/StylePropertiesSection.ts +12 -0
  19. package/front_end/panels/elements/StylePropertyTreeElement.ts +1 -1
  20. package/front_end/panels/elements/StylesSidebarPane.ts +1 -0
  21. package/front_end/panels/elements/TopLayerContainer.ts +14 -13
  22. package/front_end/panels/network/NetworkLogView.ts +13 -5
  23. package/front_end/panels/settings/SettingsScreen.ts +12 -0
  24. package/front_end/panels/settings/settingsScreen.css +6 -0
  25. package/front_end/panels/sources/NavigatorView.ts +8 -10
  26. package/front_end/panels/sources/ScopeChainSidebarPane.ts +4 -3
  27. package/front_end/panels/sources/SourcesNavigator.ts +0 -37
  28. package/front_end/panels/sources/SourcesPanel.ts +34 -22
  29. package/front_end/panels/sources/components/BreakpointsView.ts +206 -0
  30. package/front_end/panels/sources/components/HeadersView.ts +8 -41
  31. package/front_end/panels/sources/components/breakpointsView.css +95 -0
  32. package/front_end/panels/sources/components/components.ts +2 -0
  33. package/front_end/ui/components/linear_memory_inspector/LinearMemoryHighlightChipList.ts +11 -9
  34. package/front_end/ui/components/linear_memory_inspector/linearMemoryHighlightChipList.css +48 -30
  35. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +2 -2
  36. package/package.json +1 -1
@@ -298,6 +298,18 @@ export class StylePropertiesSection {
298
298
  this.parentsComputedStyles = parentsComputedStyles;
299
299
  }
300
300
 
301
+ updateAuthoringHint(): void {
302
+ let child = this.propertiesTreeOutline.firstChild();
303
+ while (child) {
304
+ if (child instanceof StylePropertyTreeElement) {
305
+ child.setComputedStyles(this.computedStyles);
306
+ child.setParentsComputedStyles(this.parentsComputedStyles);
307
+ child.updateAuthoringHint();
308
+ }
309
+ child = child.nextSibling;
310
+ }
311
+ }
312
+
301
313
  setSectionIdx(sectionIdx: number): void {
302
314
  this.sectionIdx = sectionIdx;
303
315
  this.onpopulate();
@@ -761,7 +761,7 @@ export class StylePropertyTreeElement extends UI.TreeOutline.TreeElement {
761
761
  }
762
762
  }
763
763
 
764
- private updateAuthoringHint(): void {
764
+ updateAuthoringHint(): void {
765
765
  if (!Root.Runtime.experiments.isEnabled(Root.Runtime.ExperimentName.CSS_AUTHORING_HINTS)) {
766
766
  return;
767
767
  }
@@ -785,6 +785,7 @@ export class StylesSidebarPane extends Common.ObjectWrapper.eventMixin<EventType
785
785
  for (const section of this.allSections()) {
786
786
  section.setComputedStyles(computedStyles);
787
787
  section.setParentsComputedStyles(parentsComputedStyles);
788
+ section.updateAuthoringHint();
788
789
  }
789
790
  }
790
791
 
@@ -25,14 +25,13 @@ const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
25
25
  export class TopLayerContainer extends UI.TreeOutline.TreeElement {
26
26
  tree: ElementsTreeOutline.ElementsTreeOutline;
27
27
  domModel: SDK.DOMModel.DOMModel;
28
- currentTopLayerElements: Set<ElementsTreeElement>;
28
+ currentTopLayerDOMNodes: Set<SDK.DOMModel.DOMNode> = new Set();
29
29
  topLayerUpdateThrottler: Common.Throttler.Throttler;
30
30
 
31
31
  constructor(tree: ElementsTreeOutline.ElementsTreeOutline, domModel: SDK.DOMModel.DOMModel) {
32
32
  super('#top-layer');
33
33
  this.tree = tree;
34
34
  this.domModel = domModel;
35
- this.currentTopLayerElements = new Set();
36
35
  this.topLayerUpdateThrottler = new Common.Throttler.Throttler(1);
37
36
  }
38
37
 
@@ -43,7 +42,7 @@ export class TopLayerContainer extends UI.TreeOutline.TreeElement {
43
42
  async updateTopLayerElements(): Promise<void> {
44
43
  this.removeChildren();
45
44
  this.removeCurrentTopLayerElementsAdorners();
46
- this.currentTopLayerElements = new Set();
45
+ this.currentTopLayerDOMNodes = new Set();
47
46
 
48
47
  const newTopLayerElementsIDs = await this.domModel.getTopLayerElements();
49
48
  if (!newTopLayerElementsIDs || newTopLayerElementsIDs.length === 0) {
@@ -57,15 +56,9 @@ export class TopLayerContainer extends UI.TreeOutline.TreeElement {
57
56
  const topLayerElementShortcut = new SDK.DOMModel.DOMNodeShortcut(
58
57
  this.domModel.target(), topLayerDOMNode.backendNodeId(), 0, topLayerDOMNode.nodeName());
59
58
  const topLayerElementRepresentation = new ElementsTreeOutline.ShortcutTreeElement(topLayerElementShortcut);
60
- const topLayerTreeElement = this.tree.treeElementByNode.get(topLayerDOMNode);
61
- if (!topLayerTreeElement) {
62
- continue;
63
- }
64
-
65
- topLayerElementIndex++;
66
- this.addTopLayerAdorner(topLayerTreeElement, topLayerElementRepresentation, topLayerElementIndex);
67
- this.currentTopLayerElements.add(topLayerTreeElement);
68
59
  this.appendChild(topLayerElementRepresentation);
60
+ this.currentTopLayerDOMNodes.add(topLayerDOMNode);
61
+
69
62
  // Add the element's backdrop if previous top layer element is a backdrop.
70
63
  const previousTopLayerDOMNode =
71
64
  (i > 0) ? this.domModel.idToDOMNode.get(newTopLayerElementsIDs[i - 1]) : undefined;
@@ -75,13 +68,21 @@ export class TopLayerContainer extends UI.TreeOutline.TreeElement {
75
68
  const backdropElementRepresentation = new ElementsTreeOutline.ShortcutTreeElement(backdropElementShortcut);
76
69
  topLayerElementRepresentation.appendChild(backdropElementRepresentation);
77
70
  }
71
+
72
+ // TODO(changhaohan): store not-yet-inserted DOMNodes and adorn them when inserted.
73
+ const topLayerTreeElement = this.tree.treeElementByNode.get(topLayerDOMNode);
74
+ if (topLayerTreeElement) {
75
+ this.addTopLayerAdorner(topLayerTreeElement, topLayerElementRepresentation, ++topLayerElementIndex);
76
+ }
78
77
  }
79
78
  }
80
79
  }
81
80
 
82
81
  private removeCurrentTopLayerElementsAdorners(): void {
83
- for (const topLayerElement of this.currentTopLayerElements) {
84
- topLayerElement.removeAllAdorners();
82
+ for (const node of this.currentTopLayerDOMNodes) {
83
+ const topLayerTreeElement = this.tree.treeElementByNode.get(node);
84
+ // TODO(changhaohan): remove only top layer adorner.
85
+ topLayerTreeElement?.removeAllAdorners();
85
86
  }
86
87
  }
87
88
 
@@ -1662,7 +1662,7 @@ export class NetworkLogView extends Common.ObjectWrapper.eventMixin<EventTypes,
1662
1662
  }
1663
1663
 
1664
1664
  private async copyCurlCommand(request: SDK.NetworkRequest.NetworkRequest, platform: string): Promise<void> {
1665
- const command = await this.generateCurlCommand(request, platform);
1665
+ const command = await NetworkLogView.generateCurlCommand(request, platform);
1666
1666
  Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(command);
1667
1667
  }
1668
1668
 
@@ -2092,7 +2092,7 @@ export class NetworkLogView extends Common.ObjectWrapper.eventMixin<EventTypes,
2092
2092
  return commands.join(' ;\n');
2093
2093
  }
2094
2094
 
2095
- private async generateCurlCommand(request: SDK.NetworkRequest.NetworkRequest, platform: string): Promise<string> {
2095
+ static async generateCurlCommand(request: SDK.NetworkRequest.NetworkRequest, platform: string): Promise<string> {
2096
2096
  let command: string[] = [];
2097
2097
  // Most of these headers are derived from the URL and are automatically added by cURL.
2098
2098
  // The |Accept-Encoding| header is ignored to prevent decompression errors. crbug.com/1015321
@@ -2115,7 +2115,7 @@ export class NetworkLogView extends Common.ObjectWrapper.eventMixin<EventTypes,
2115
2115
  gets to MS Crt parser safely.
2116
2116
 
2117
2117
  The % character is special because MS Crt parser will try and look for
2118
- ENV variables and fill them in it's place. We cannot escape them with %
2118
+ ENV variables and fill them in its place. We cannot escape them with %
2119
2119
  and cannot escape them with ^ (because it's cmd.exe's escape not MS Crt
2120
2120
  parser); So we can get cmd.exe parser to escape the character after it,
2121
2121
  if it is followed by a valid beginning character of an ENV variable.
@@ -2191,7 +2191,14 @@ export class NetworkLogView extends Common.ObjectWrapper.eventMixin<EventTypes,
2191
2191
  if (ignoredHeaders.has(name.toLowerCase())) {
2192
2192
  continue;
2193
2193
  }
2194
- command.push('-H ' + escapeString(name + ': ' + header.value));
2194
+ if (header.value.trim()) {
2195
+ command.push('-H ' + escapeString(name + ': ' + header.value));
2196
+ } else {
2197
+ // A header passed with -H with no value or only whitespace as its
2198
+ // value tells curl to not set the header at all. To post an empty
2199
+ // header, you have to terminate it with a semicolon.
2200
+ command.push('-H ' + escapeString(name + ';'));
2201
+ }
2195
2202
  }
2196
2203
  command = command.concat(data);
2197
2204
  command.push('--compressed');
@@ -2205,7 +2212,8 @@ export class NetworkLogView extends Common.ObjectWrapper.eventMixin<EventTypes,
2205
2212
  private async generateAllCurlCommand(requests: SDK.NetworkRequest.NetworkRequest[], platform: string):
2206
2213
  Promise<string> {
2207
2214
  const nonBlobRequests = this.filterOutBlobRequests(requests);
2208
- const commands = await Promise.all(nonBlobRequests.map(request => this.generateCurlCommand(request, platform)));
2215
+ const commands =
2216
+ await Promise.all(nonBlobRequests.map(request => NetworkLogView.generateCurlCommand(request, platform)));
2209
2217
  if (platform === 'win') {
2210
2218
  return commands.join(' &\r\n');
2211
2219
  }
@@ -93,6 +93,10 @@ const UIStrings = {
93
93
  *@description Text that is usually a hyperlink to more documentation
94
94
  */
95
95
  learnMore: 'Learn more',
96
+ /**
97
+ *@description Text that is usually a hyperlink to a feedback form
98
+ */
99
+ sendFeedback: 'Send feedback',
96
100
  };
97
101
  const str_ = i18n.i18n.registerUIStrings('panels/settings/SettingsScreen.ts', UIStrings);
98
102
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -489,6 +493,14 @@ export class ExperimentsSettingsTab extends SettingsTab {
489
493
  p.appendChild(link);
490
494
  }
491
495
 
496
+ if (experiment.feedbackLink) {
497
+ const link = UI.XLink.XLink.create(experiment.feedbackLink);
498
+ link.textContent = i18nString(UIStrings.sendFeedback);
499
+ link.classList.add('feedback-link');
500
+
501
+ p.appendChild(link);
502
+ }
503
+
492
504
  return p;
493
505
  }
494
506
  }
@@ -199,6 +199,12 @@ label {
199
199
  color: var(--color-text-secondary);
200
200
  }
201
201
 
202
+ .settings-experiment .feedback-link {
203
+ color: var(--color-primary);
204
+ text-decoration-line: underline;
205
+ margin-left: 4px;
206
+ }
207
+
202
208
  @media (forced-colors: active) {
203
209
  .settings-window-title {
204
210
  color: canvastext;
@@ -594,12 +594,17 @@ export class NavigatorView extends UI.Widget.VBox implements SDK.TargetManager.O
594
594
  project: Workspace.Workspace.Project, target: SDK.Target.Target|null,
595
595
  frame: SDK.ResourceTreeModel.ResourceTreeFrame|null, projectOrigin: string, isFromSourceMap: boolean,
596
596
  path: Platform.DevToolsPath.EncodedPathString): string {
597
- let targetId = target && !(this.groupByAuthored && isFromSourceMap) ? target.id() : '';
598
597
  const projectId = project.type() === Workspace.Workspace.projectTypes.FileSystem ? project.id() : '';
598
+ let targetId = target && !(this.groupByAuthored && isFromSourceMap) ? target.id() : '';
599
+ let frameId = this.groupByFrame && frame ? frame.id : '';
599
600
  if (this.groupByAuthored) {
600
- targetId = isFromSourceMap ? 'Authored' : 'Deployed:' + targetId;
601
+ if (isFromSourceMap) {
602
+ targetId = 'Authored';
603
+ frameId = '';
604
+ } else {
605
+ targetId = 'Deployed:' + targetId;
606
+ }
601
607
  }
602
- const frameId = this.groupByFrame && frame ? frame.id : '';
603
608
  return targetId + ':' + projectId + ':' + frameId + ':' + projectOrigin + ':' + path;
604
609
  }
605
610
 
@@ -1034,17 +1039,10 @@ export class NavigatorView extends UI.Widget.VBox implements SDK.TargetManager.O
1034
1039
  }
1035
1040
  }
1036
1041
 
1037
- /**
1038
- * Subclasses can override to listen to grouping changes.
1039
- */
1040
- onGroupingChanged(): void {
1041
- }
1042
-
1043
1042
  private groupingChanged(): void {
1044
1043
  this.reset(true);
1045
1044
  this.initGrouping();
1046
1045
  // Reset the workspace to repopulate filesystem folders.
1047
- this.onGroupingChanged();
1048
1046
  this.resetWorkspace(Workspace.Workspace.WorkspaceImpl.instance());
1049
1047
  this.workspaceInternal.uiSourceCodes().forEach(this.addUISourceCode.bind(this));
1050
1048
  }
@@ -317,16 +317,17 @@ export class OpenLinearMemoryInspector extends UI.Widget.VBox implements UI.Cont
317
317
  if (target instanceof ObjectUI.ObjectPropertiesSection.ObjectPropertyTreeElement) {
318
318
  if (target.property && target.property.value &&
319
319
  LinearMemoryInspector.LinearMemoryInspectorController.isMemoryObjectProperty(target.property.value)) {
320
+ const expression = target.path();
320
321
  contextMenu.debugSection().appendItem(
321
322
  i18nString(UIStrings.revealInMemoryInspectorPanel),
322
- this.openMemoryInspector.bind(this, target.property.name, target.property.value));
323
+ this.openMemoryInspector.bind(this, expression, target.property.value));
323
324
  }
324
325
  }
325
326
  }
326
327
 
327
- private async openMemoryInspector(name: string, obj: SDK.RemoteObject.RemoteObject): Promise<void> {
328
+ private async openMemoryInspector(expression: string, obj: SDK.RemoteObject.RemoteObject): Promise<void> {
328
329
  const controller = LinearMemoryInspector.LinearMemoryInspectorController.LinearMemoryInspectorController.instance();
329
330
  Host.userMetrics.linearMemoryInspectorRevealedFrom(Host.UserMetrics.LinearMemoryInspectorRevealedFrom.ContextMenu);
330
- void controller.openInspectorView(obj, undefined /* address */, name);
331
+ void controller.openInspectorView(obj, /* address */ undefined, expression);
331
332
  }
332
333
  }
@@ -32,11 +32,9 @@ import * as Common from '../../core/common/common.js';
32
32
  import * as Host from '../../core/host/host.js';
33
33
  import * as i18n from '../../core/i18n/i18n.js';
34
34
  import * as Platform from '../../core/platform/platform.js';
35
- import * as Root from '../../core/root/root.js';
36
35
  import * as SDK from '../../core/sdk/sdk.js';
37
36
  import * as Persistence from '../../models/persistence/persistence.js';
38
37
  import * as Workspace from '../../models/workspace/workspace.js';
39
- import * as Feedback from '../../ui/components/panel_feedback/panel_feedback.js';
40
38
  import * as UI from '../../ui/legacy/legacy.js';
41
39
  import * as Snippets from '../snippets/snippets.js';
42
40
 
@@ -102,61 +100,26 @@ const UIStrings = {
102
100
  *@description Text to save content as a specific file type
103
101
  */
104
102
  saveAs: 'Save as...',
105
- /**
106
- *@description Description of the new experimental Authored/Deployed view
107
- */
108
- authoredDescription: 'Group files by Authored/Deployed',
109
103
  };
110
104
  const str_ = i18n.i18n.registerUIStrings('panels/sources/SourcesNavigator.ts', UIStrings);
111
105
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
112
106
  let networkNavigatorViewInstance: NetworkNavigatorView;
113
107
 
114
108
  export class NetworkNavigatorView extends NavigatorView {
115
- private previewToggle: Feedback.PreviewToggle.PreviewToggle;
116
109
  private constructor() {
117
110
  super(true);
118
111
  SDK.TargetManager.TargetManager.instance().addEventListener(
119
112
  SDK.TargetManager.Events.InspectedURLChanged, this.inspectedURLChanged, this);
120
113
 
121
- this.previewToggle = new Feedback.PreviewToggle.PreviewToggle();
122
- this.onGroupingChanged();
123
-
124
- const div = UI.Fragment.html`<div class="border-container"></div>`;
125
- div.append(this.previewToggle);
126
- this.contentElement.prepend(div);
127
-
128
114
  // Record the sources tool load time after the file navigator has loaded.
129
115
  Host.userMetrics.panelLoaded('sources', 'DevTools.Launch.Sources');
130
116
  }
131
117
 
132
- onGroupingChanged(): void {
133
- // Setting the data will re-render it.
134
- this.previewToggle.data = {
135
- name: i18nString(UIStrings.authoredDescription),
136
- helperText: null,
137
- experiment: Root.Runtime.ExperimentName.AUTHORED_DEPLOYED_GROUPING,
138
- learnMoreURL: 'https://goo.gle/authored-deployed',
139
- feedbackURL: 'https://goo.gle/authored-deployed-feedback',
140
- onChangeCallback: this.onAuthoredDeployedChanged,
141
- };
142
- }
143
-
144
118
  wasShown(): void {
145
119
  this.registerCSSFiles([sourcesNavigatorStyles]);
146
120
  super.wasShown();
147
121
  }
148
122
 
149
- private onAuthoredDeployedChanged(checked: boolean): void {
150
- Host.userMetrics.experimentChanged(Root.Runtime.ExperimentName.AUTHORED_DEPLOYED_GROUPING, checked);
151
- // Need to signal to the NavigatorView that grouping has changed. Unfortunately,
152
- // it can't listen to an experiment, and this class doesn't directly interact
153
- // with it, so we will convince it a different grouping setting changed. When we switch
154
- // from using an experiment to a setting, it will listen to that setting and we
155
- // won't need to do this.
156
- const groupByFolderSetting = Common.Settings.Settings.instance().moduleSetting('navigatorGroupByFolder');
157
- groupByFolderSetting.set(groupByFolderSetting.get());
158
- }
159
-
160
123
  static instance(opts: {
161
124
  forceNew: boolean|null,
162
125
  } = {forceNew: null}): NetworkNavigatorView {
@@ -112,6 +112,10 @@ const UIStrings = {
112
112
  */
113
113
  groupByAuthored: 'Group by Authored/Deployed',
114
114
  /**
115
+ *@description Text in Sources Panel of the Sources panel
116
+ */
117
+ hideIgnoreListed: 'Hide ignore-listed sources',
118
+ /**
115
119
  *@description Text for pausing the debugger on exceptions
116
120
  */
117
121
  pauseOnExceptions: 'Pause on exceptions',
@@ -563,36 +567,44 @@ export class SourcesPanel extends UI.Panel.Panel implements UI.ContextMenu.Provi
563
567
  }
564
568
  }
565
569
 
566
- private toggleAuthoredDeployedExperiment(): void {
567
- const experiment = Root.Runtime.ExperimentName.AUTHORED_DEPLOYED_GROUPING;
568
- const checked = Root.Runtime.experiments.isEnabled(experiment);
569
- Root.Runtime.experiments.setEnabled(experiment, !checked);
570
- Host.userMetrics.experimentChanged(experiment, checked);
571
- // Need to signal to the NavigatorView that grouping has changed. Unfortunately,
572
- // it can't listen to an experiment, and this class doesn't directly interact
573
- // with it, so we will convince it a different grouping setting changed. When we switch
574
- // from using an experiment to a setting, it will listen to that setting and we
575
- // won't need to do this.
576
- const groupByFolderSetting = Common.Settings.Settings.instance().moduleSetting('navigatorGroupByFolder');
577
- groupByFolderSetting.set(groupByFolderSetting.get());
578
- }
570
+ private addExperimentMenuItem(
571
+ menuSection: UI.ContextMenu.Section, experiment: string, menuItem: Common.UIString.LocalizedString): void {
572
+ // menu handler
573
+ function toggleExperiment(): void {
574
+ const checked = Root.Runtime.experiments.isEnabled(experiment);
575
+ Root.Runtime.experiments.setEnabled(experiment, !checked);
576
+ Host.userMetrics.experimentChanged(experiment, checked);
577
+ // Need to signal to the NavigatorView that grouping has changed. Unfortunately,
578
+ // it can't listen to an experiment, and this class doesn't directly interact
579
+ // with it, so we will convince it a different grouping setting changed. When we switch
580
+ // from using an experiment to a setting, it will listen to that setting and we
581
+ // won't need to do this.
582
+ const groupByFolderSetting = Common.Settings.Settings.instance().moduleSetting('navigatorGroupByFolder');
583
+ groupByFolderSetting.set(groupByFolderSetting.get());
584
+ }
579
585
 
580
- private populateNavigatorMenu(contextMenu: UI.ContextMenu.ContextMenu): void {
581
- const groupByFolderSetting = Common.Settings.Settings.instance().moduleSetting('navigatorGroupByFolder');
582
- contextMenu.appendItemsAtLocation('navigatorMenu');
583
- contextMenu.viewSection().appendCheckboxItem(
584
- i18nString(UIStrings.groupByFolder), () => groupByFolderSetting.set(!groupByFolderSetting.get()),
585
- groupByFolderSetting.get());
586
586
  const previewIcon = new IconButton.Icon.Icon();
587
- const experiment = Root.Runtime.ExperimentName.AUTHORED_DEPLOYED_GROUPING;
588
587
  previewIcon.data = {
589
588
  iconName: 'ic_preview_feature',
590
589
  color: 'var(--icon-color)',
591
590
  width: '14px',
592
591
  };
592
+ menuSection.appendCheckboxItem(
593
+ menuItem, toggleExperiment, Root.Runtime.experiments.isEnabled(experiment), false, previewIcon);
594
+ }
595
+
596
+ private populateNavigatorMenu(contextMenu: UI.ContextMenu.ContextMenu): void {
597
+ const groupByFolderSetting = Common.Settings.Settings.instance().moduleSetting('navigatorGroupByFolder');
598
+ contextMenu.appendItemsAtLocation('navigatorMenu');
593
599
  contextMenu.viewSection().appendCheckboxItem(
594
- i18nString(UIStrings.groupByAuthored), this.toggleAuthoredDeployedExperiment,
595
- Root.Runtime.experiments.isEnabled(experiment), false, previewIcon);
600
+ i18nString(UIStrings.groupByFolder), () => groupByFolderSetting.set(!groupByFolderSetting.get()),
601
+ groupByFolderSetting.get());
602
+
603
+ this.addExperimentMenuItem(
604
+ contextMenu.viewSection(), Root.Runtime.ExperimentName.AUTHORED_DEPLOYED_GROUPING,
605
+ i18nString(UIStrings.groupByAuthored));
606
+ this.addExperimentMenuItem(
607
+ contextMenu.viewSection(), Root.Runtime.ExperimentName.JUST_MY_CODE, i18nString(UIStrings.hideIgnoreListed));
596
608
  }
597
609
 
598
610
  setIgnoreExecutionLineEvents(ignoreExecutionLineEvents: boolean): void {
@@ -0,0 +1,206 @@
1
+ // Copyright (c) 2022 The Chromium Authors. All rights reserved.
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+
5
+ import * as i18n from '../../../core/i18n/i18n.js';
6
+ import * as Platform from '../../../core/platform/platform.js';
7
+ import * as ComponentHelpers from '../../../ui/components/helpers/helpers.js';
8
+ import * as IconButton from '../../../ui/components/icon_button/icon_button.js';
9
+ import * as LitHtml from '../../../ui/lit-html/lit-html.js';
10
+
11
+ import breakpointsViewStyles from './breakpointsView.css.js';
12
+
13
+ const UIStrings = {
14
+ /**
15
+ *@description Text exposed to screen readers on checked items.
16
+ */
17
+ checked: 'checked',
18
+ /**
19
+ *@description Accessible text exposed to screen readers when the screen reader encounters an unchecked checkbox.
20
+ */
21
+ unchecked: 'unchecked',
22
+ /**
23
+ *@description Accessible text for a breakpoint collection with a combination of checked states.
24
+ */
25
+ indeterminate: 'mixed',
26
+ /**
27
+ *@description Accessibility label for hit breakpoints in the Sources panel.
28
+ *@example {checked} PH1
29
+ */
30
+ breakpointHit: '{PH1} breakpoint hit',
31
+
32
+ };
33
+ const str_ = i18n.i18n.registerUIStrings('panels/sources/components/BreakpointsView.ts', UIStrings);
34
+ const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
35
+
36
+ const MAX_SNIPPET_LENGTH = 200;
37
+
38
+ export interface BreakpointsViewData {
39
+ groups: BreakpointGroup[];
40
+ }
41
+
42
+ export interface BreakpointGroup {
43
+ name: string;
44
+ url: Platform.DevToolsPath.UrlString;
45
+ expanded: boolean;
46
+ breakpointItems: BreakpointItem[];
47
+ }
48
+
49
+ export interface BreakpointItem {
50
+ location: string;
51
+ codeSnippet: string;
52
+ isHit: boolean;
53
+ status: BreakpointStatus;
54
+ hoverText?: string;
55
+ }
56
+
57
+ export const enum BreakpointStatus {
58
+ ENABLED = 'ENABLED',
59
+ DISABLED = 'DISABLED',
60
+ INDETERMINATE = 'INDETERMINATE',
61
+ }
62
+
63
+ export class CheckboxToggledEvent extends Event {
64
+ static readonly eventName = 'checkboxtoggled';
65
+ data: {breakpointItem: BreakpointItem, checked: boolean};
66
+
67
+ constructor(breakpointItem: BreakpointItem, checked: boolean) {
68
+ super(CheckboxToggledEvent.eventName);
69
+ this.data = {breakpointItem: breakpointItem, checked};
70
+ }
71
+ }
72
+
73
+ export class ExpandedStateChangedEvent extends Event {
74
+ static readonly eventName = 'expandedstatechanged';
75
+ data: {url: Platform.DevToolsPath.UrlString, expanded: boolean};
76
+
77
+ constructor(url: Platform.DevToolsPath.UrlString, expanded: boolean) {
78
+ super(ExpandedStateChangedEvent.eventName);
79
+ this.data = {url, expanded};
80
+ }
81
+ }
82
+
83
+ export class BreakpointSelectedEvent extends Event {
84
+ static readonly eventName = 'breakpointselected';
85
+ data: {breakpointItem: BreakpointItem};
86
+
87
+ constructor(breakpointItem: BreakpointItem) {
88
+ super(BreakpointSelectedEvent.eventName);
89
+ this.data = {breakpointItem: breakpointItem};
90
+ }
91
+ }
92
+
93
+ export class BreakpointsView extends HTMLElement {
94
+ static readonly litTagName = LitHtml.literal`devtools-breakpoint-view`;
95
+ readonly #shadow = this.attachShadow({mode: 'open'});
96
+ readonly #boundRender = this.#render.bind(this);
97
+
98
+ #breakpointGroups: BreakpointGroup[] = [];
99
+
100
+ set data(data: BreakpointsViewData) {
101
+ this.#breakpointGroups = data.groups;
102
+ void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
103
+ }
104
+
105
+ connectedCallback(): void {
106
+ this.#shadow.adoptedStyleSheets = [breakpointsViewStyles];
107
+ void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
108
+ }
109
+
110
+ #render(): void {
111
+ const renderedGroups = this.#breakpointGroups.map((group, index) => {
112
+ const out = this.#renderBreakpointGroup(group);
113
+ if (index === this.#breakpointGroups.length - 1) {
114
+ return out;
115
+ }
116
+ return LitHtml.html`${out}<div class='divider'></div>`;
117
+ });
118
+ LitHtml.render(LitHtml.html`${renderedGroups}`, this.#shadow, {host: this});
119
+ }
120
+
121
+ #renderBreakpointGroup(group: BreakpointGroup): LitHtml.TemplateResult {
122
+ const groupClassMap = {
123
+ 'expanded': group.expanded,
124
+ };
125
+ // clang-format off
126
+ return LitHtml.html`
127
+ <div data-group='true' class=${LitHtml.Directives.classMap(groupClassMap)}>
128
+ <div class='group-header' @click=${(): void => this.#onGroupExpandToggled(group)}>
129
+ <span class='triangle'></span>
130
+ ${this.#renderFileIcon()}
131
+ <span class='group-header-title'>${group.name}</span>
132
+ </div>
133
+ ${group.expanded? LitHtml.html`
134
+ ${group.breakpointItems.map(entry => this.#renderBreakpointEntry(entry))}` : LitHtml.nothing}
135
+ </div>
136
+ `;
137
+ // clang-format on
138
+ }
139
+
140
+ #renderFileIcon(): LitHtml.TemplateResult {
141
+ return LitHtml.html`
142
+ <${IconButton.Icon.Icon.litTagName} .data=${
143
+ {iconName: 'ic_file_script', color: 'var(--color-ic-file-script)', width: '16px', height: '16px'} as
144
+ IconButton.Icon.IconWithName}></${IconButton.Icon.Icon.litTagName}>
145
+ `;
146
+ }
147
+
148
+ #renderBreakpointEntry(breakpointItem: BreakpointItem): LitHtml.TemplateResult {
149
+ const classMap = {
150
+ 'breakpoint-item': true,
151
+ 'hit': breakpointItem.isHit,
152
+ };
153
+ const breakpointItemDescription = this.#getBreakpointItemDescription(breakpointItem);
154
+ const codeSnippet = Platform.StringUtilities.trimEndWithMaxLength(breakpointItem.codeSnippet, MAX_SNIPPET_LENGTH);
155
+
156
+ // clang-format off
157
+ return LitHtml.html`
158
+ <div class=${LitHtml.Directives.classMap(classMap)} aria-label=${breakpointItemDescription} tabIndex=${breakpointItem.isHit ? 0 : 1}>
159
+ <label class='checkbox-label'>
160
+ <input type='checkbox' aria-label=${breakpointItem.location} ?indeterminate=${breakpointItem.status === BreakpointStatus.INDETERMINATE} ?checked=${breakpointItem.status === BreakpointStatus.ENABLED} @change=${(e: Event): void => this.#onCheckboxToggled(e, breakpointItem)}>
161
+ </label>
162
+ <span class='code-snippet' @click=${():void => {this.dispatchEvent(new BreakpointSelectedEvent(breakpointItem));}}>${codeSnippet}</span>
163
+ <span class='location'>${breakpointItem.location}</span>
164
+ </div>
165
+ `;
166
+ // clang-format on
167
+ }
168
+
169
+ #getBreakpointItemDescription(breakpointItem: BreakpointItem): Platform.UIString.LocalizedString {
170
+ let checkboxDescription;
171
+ switch (breakpointItem.status) {
172
+ case BreakpointStatus.ENABLED:
173
+ checkboxDescription = i18nString(UIStrings.checked);
174
+ break;
175
+ case BreakpointStatus.DISABLED:
176
+ checkboxDescription = i18nString(UIStrings.unchecked);
177
+ break;
178
+ case BreakpointStatus.INDETERMINATE:
179
+ checkboxDescription = i18nString(UIStrings.indeterminate);
180
+ break;
181
+ }
182
+ if (!breakpointItem.isHit) {
183
+ return checkboxDescription;
184
+ }
185
+ return i18nString(UIStrings.breakpointHit, {PH1: checkboxDescription});
186
+ }
187
+
188
+ #onGroupExpandToggled(group: BreakpointGroup): void {
189
+ group.expanded = !group.expanded;
190
+ this.dispatchEvent(new ExpandedStateChangedEvent(group.url, group.expanded));
191
+ void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
192
+ }
193
+
194
+ #onCheckboxToggled(e: Event, item: BreakpointItem): void {
195
+ const element = e.target as HTMLInputElement;
196
+ this.dispatchEvent(new CheckboxToggledEvent(item, element.checked));
197
+ }
198
+ }
199
+
200
+ ComponentHelpers.CustomElements.defineComponent('devtools-breakpoint-view', BreakpointsView);
201
+
202
+ declare global {
203
+ interface HTMLElementTagNameMap {
204
+ 'devtools-breakpoint-view': BreakpointsView;
205
+ }
206
+ }