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.
- package/AUTHORS +1 -0
- package/config/gni/devtools_grd_files.gni +2 -0
- package/docs/workflows.md +4 -5
- package/front_end/core/i18n/locales/en-US.json +21 -6
- package/front_end/core/i18n/locales/en-XL.json +21 -6
- package/front_end/core/root/Runtime.ts +8 -3
- package/front_end/core/sdk/CSSStyleDeclaration.ts +1 -1
- package/front_end/core/sdk/NetworkManager.ts +21 -5
- package/front_end/core/sdk/NetworkRequest.ts +10 -0
- package/front_end/entrypoints/main/MainImpl.ts +6 -5
- package/front_end/generated/InspectorBackendCommands.js +2 -2
- package/front_end/generated/SupportedCSSProperties.js +8 -4
- package/front_end/generated/protocol.ts +7 -1
- package/front_end/models/persistence/NetworkPersistenceManager.ts +25 -14
- package/front_end/panels/application/components/Prerender2.ts +5 -7
- package/front_end/panels/elements/CSSRuleValidator.ts +1 -1
- package/front_end/panels/elements/ElementsTreeOutline.ts +3 -3
- package/front_end/panels/elements/StylePropertiesSection.ts +12 -0
- package/front_end/panels/elements/StylePropertyTreeElement.ts +1 -1
- package/front_end/panels/elements/StylesSidebarPane.ts +1 -0
- package/front_end/panels/elements/TopLayerContainer.ts +14 -13
- package/front_end/panels/network/NetworkLogView.ts +13 -5
- package/front_end/panels/settings/SettingsScreen.ts +12 -0
- package/front_end/panels/settings/settingsScreen.css +6 -0
- package/front_end/panels/sources/NavigatorView.ts +8 -10
- package/front_end/panels/sources/ScopeChainSidebarPane.ts +4 -3
- package/front_end/panels/sources/SourcesNavigator.ts +0 -37
- package/front_end/panels/sources/SourcesPanel.ts +34 -22
- package/front_end/panels/sources/components/BreakpointsView.ts +206 -0
- package/front_end/panels/sources/components/HeadersView.ts +8 -41
- package/front_end/panels/sources/components/breakpointsView.css +95 -0
- package/front_end/panels/sources/components/components.ts +2 -0
- package/front_end/ui/components/linear_memory_inspector/LinearMemoryHighlightChipList.ts +11 -9
- package/front_end/ui/components/linear_memory_inspector/linearMemoryHighlightChipList.css +48 -30
- package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +2 -2
- 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
|
-
|
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
|
-
|
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.
|
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
|
84
|
-
|
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
|
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
|
-
|
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
|
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
|
-
|
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 =
|
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
|
-
|
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,
|
323
|
+
this.openMemoryInspector.bind(this, expression, target.property.value));
|
323
324
|
}
|
324
325
|
}
|
325
326
|
}
|
326
327
|
|
327
|
-
private async openMemoryInspector(
|
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,
|
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
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
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.
|
595
|
-
|
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
|
+
}
|