chrome-devtools-frontend 1.0.944903 → 1.0.945884
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/config/gni/devtools_grd_files.gni +1 -0
- package/config/gni/devtools_image_files.gni +1 -0
- package/front_end/Images/src/circled_exclamation_icon.svg +3 -0
- package/front_end/core/host/InspectorFrontendHostAPI.ts +5 -5
- package/front_end/core/host/UserMetrics.ts +3 -1
- package/front_end/core/i18n/i18nImpl.ts +7 -4
- package/front_end/core/i18n/locales/en-US.json +9 -45
- package/front_end/core/i18n/locales/en-XL.json +9 -45
- package/front_end/core/sdk/CSSMetadata.ts +0 -1
- package/front_end/core/sdk/sdk-meta.ts +20 -8
- package/front_end/entrypoints/main/MainImpl.ts +6 -0
- package/front_end/generated/protocol.d.ts +0 -4
- package/front_end/models/emulation/EmulatedDevices.ts +2 -4
- package/front_end/models/persistence/IsolatedFileSystemManager.ts +6 -10
- package/front_end/models/timeline_model/TimelineJSProfile.ts +16 -3
- package/front_end/models/timeline_model/TimelineModel.ts +1 -0
- package/front_end/models/workspace_diff/WorkspaceDiff.ts +20 -6
- package/front_end/panels/animation/AnimationTimeline.ts +1 -1
- package/front_end/panels/application/BackForwardCacheStrings.ts +15 -75
- package/front_end/panels/application/BackForwardCacheView.ts +8 -1
- package/front_end/panels/changes/ChangesView.ts +8 -7
- package/front_end/panels/elements/StyleEditorWidget.ts +7 -7
- package/front_end/panels/elements/StylePropertyTreeElement.ts +8 -15
- package/front_end/panels/elements/StylesSidebarPane.ts +35 -9
- package/front_end/panels/emulation/DeviceModeView.ts +3 -0
- package/front_end/panels/help/ReleaseNoteText.ts +3 -1
- package/front_end/panels/network/NetworkItemView.ts +7 -1
- package/front_end/panels/profiler/heapProfiler.css +2 -5
- package/front_end/panels/timeline/TimelineController.ts +3 -0
- package/front_end/panels/webauthn/WebauthnPane.ts +31 -32
- package/front_end/third_party/acorn/README.chromium +2 -2
- package/front_end/third_party/acorn/acorn.ts +1 -1
- package/front_end/third_party/acorn/package/CHANGELOG.md +31 -1
- package/front_end/third_party/acorn/package/README.md +1 -1
- package/front_end/third_party/acorn/package/dist/acorn.d.ts +3 -0
- package/front_end/third_party/acorn/package/dist/acorn.js +772 -708
- package/front_end/third_party/acorn/package/dist/acorn.mjs +767 -703
- package/front_end/third_party/acorn/package/dist/bin.js +47 -21
- package/front_end/third_party/acorn/package/package.json +1 -1
- package/front_end/third_party/acorn-loose/README.chromium +2 -2
- package/front_end/third_party/acorn-loose/package/CHANGELOG.md +12 -0
- package/front_end/third_party/acorn-loose/package/dist/acorn-loose.js +27 -7
- package/front_end/third_party/acorn-loose/package/dist/acorn-loose.mjs +28 -8
- package/front_end/third_party/acorn-loose/package/package.json +2 -2
- package/front_end/third_party/i18n/i18n-impl.ts +1 -1
- package/front_end/ui/components/adorners/Adorner.ts +14 -14
- package/front_end/ui/components/buttons/Button.ts +133 -42
- package/front_end/ui/components/buttons/button.css +31 -0
- package/front_end/ui/components/data_grid/DataGrid.ts +131 -122
- package/front_end/ui/components/data_grid/DataGridController.ts +42 -42
- package/front_end/ui/components/diff_view/DiffView.ts +4 -4
- package/front_end/ui/components/docs/button/basic.html +3 -0
- package/front_end/ui/components/docs/button/basic.ts +58 -0
- package/front_end/ui/components/expandable_list/ExpandableList.ts +11 -11
- package/front_end/ui/components/icon_button/Icon.ts +24 -21
- package/front_end/ui/components/icon_button/IconButton.ts +31 -31
- package/front_end/ui/components/issue_counter/IssueCounter.ts +52 -52
- package/front_end/ui/components/issue_counter/IssueLinkIcon.ts +42 -42
- package/front_end/ui/components/linear_memory_inspector/LinearMemoryInspector.ts +67 -67
- package/front_end/ui/components/linear_memory_inspector/LinearMemoryInspectorController.ts +22 -22
- package/front_end/ui/components/linear_memory_inspector/LinearMemoryInspectorPane.ts +36 -36
- package/front_end/ui/components/linear_memory_inspector/LinearMemoryNavigator.ts +19 -19
- package/front_end/ui/components/linear_memory_inspector/LinearMemoryValueInterpreter.ts +24 -32
- package/front_end/ui/components/linear_memory_inspector/LinearMemoryViewer.ts +52 -52
- package/front_end/ui/components/linear_memory_inspector/ValueInterpreterDisplay.ts +21 -21
- package/front_end/ui/components/linear_memory_inspector/ValueInterpreterSettings.ts +6 -6
- package/front_end/ui/components/markdown_view/MarkdownImage.ts +14 -14
- package/front_end/ui/components/markdown_view/MarkdownLink.ts +8 -8
- package/front_end/ui/components/markdown_view/MarkdownView.ts +6 -6
- package/front_end/ui/components/render_coordinator/RenderCoordinator.ts +33 -33
- package/front_end/ui/components/report_view/ReportView.ts +18 -18
- package/front_end/ui/components/request_link_icon/RequestLinkIcon.ts +53 -53
- package/front_end/ui/components/settings/SettingCheckbox.ts +15 -15
- package/front_end/ui/components/survey_link/SurveyLink.ts +28 -28
- package/front_end/ui/components/text_editor/TextEditor.ts +55 -52
- package/front_end/ui/components/text_editor/javascript.ts +6 -6
- package/front_end/ui/components/text_prompt/TextPrompt.ts +19 -19
- package/front_end/ui/components/tree_outline/TreeOutline.ts +56 -56
- package/front_end/ui/legacy/Infobar.ts +9 -0
- package/front_end/ui/legacy/InspectorView.ts +1 -1
- package/front_end/ui/legacy/ListWidget.ts +2 -2
- package/front_end/ui/legacy/tabbedPane.css +1 -1
- package/inspector_overlay/main.ts +3 -0
- package/package.json +1 -1
- package/scripts/eslint_rules/lib/l10n_filename_matches.js +17 -4
- package/scripts/eslint_rules/tests/l10n_filename_matches_test.js +21 -0
|
@@ -21,35 +21,36 @@ declare global {
|
|
|
21
21
|
export class TextEditor extends HTMLElement {
|
|
22
22
|
static readonly litTagName = LitHtml.literal`devtools-text-editor`;
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (this
|
|
33
|
-
this
|
|
34
|
-
this
|
|
35
|
-
if (this
|
|
36
|
-
CodeMirror.repositionTooltips(this
|
|
24
|
+
readonly #shadow = this.attachShadow({mode: 'open'});
|
|
25
|
+
#activeEditor: CodeMirror.EditorView|undefined = undefined;
|
|
26
|
+
#dynamicSettings: readonly DynamicSetting<unknown>[] = DynamicSetting.none;
|
|
27
|
+
#activeSettingListeners: [Common.Settings.Setting<unknown>, (event: {data: unknown}) => void][] = [];
|
|
28
|
+
#pendingState: CodeMirror.EditorState|undefined;
|
|
29
|
+
#lastScrollPos = {left: 0, top: 0};
|
|
30
|
+
#resizeTimeout = -1;
|
|
31
|
+
#resizeListener = (): void => {
|
|
32
|
+
if (this.#resizeTimeout < 0) {
|
|
33
|
+
this.#resizeTimeout = window.setTimeout(() => {
|
|
34
|
+
this.#resizeTimeout = -1;
|
|
35
|
+
if (this.#activeEditor) {
|
|
36
|
+
CodeMirror.repositionTooltips(this.#activeEditor);
|
|
37
37
|
}
|
|
38
38
|
}, 50);
|
|
39
39
|
}
|
|
40
|
-
}
|
|
40
|
+
};
|
|
41
|
+
#devtoolsResizeObserver = new ResizeObserver(this.#resizeListener);
|
|
41
42
|
|
|
42
43
|
constructor(pendingState?: CodeMirror.EditorState) {
|
|
43
44
|
super();
|
|
44
|
-
this
|
|
45
|
-
this
|
|
45
|
+
this.#pendingState = pendingState;
|
|
46
|
+
this.#shadow.adoptedStyleSheets = [CodeHighlighter.Style.default];
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
private createEditor(): CodeMirror.EditorView {
|
|
49
|
-
this
|
|
50
|
+
this.#activeEditor = new CodeMirror.EditorView({
|
|
50
51
|
state: this.state,
|
|
51
|
-
parent: this
|
|
52
|
-
root: this
|
|
52
|
+
parent: this.#shadow,
|
|
53
|
+
root: this.#shadow,
|
|
53
54
|
dispatch: (tr: CodeMirror.Transaction): void => {
|
|
54
55
|
this.editor.update([tr]);
|
|
55
56
|
if (tr.reconfigured) {
|
|
@@ -57,19 +58,19 @@ export class TextEditor extends HTMLElement {
|
|
|
57
58
|
}
|
|
58
59
|
},
|
|
59
60
|
});
|
|
60
|
-
this
|
|
61
|
-
this
|
|
62
|
-
this
|
|
63
|
-
this
|
|
64
|
-
this
|
|
61
|
+
this.#activeEditor.scrollDOM.scrollTop = this.#lastScrollPos.top;
|
|
62
|
+
this.#activeEditor.scrollDOM.scrollLeft = this.#lastScrollPos.left;
|
|
63
|
+
this.#activeEditor.scrollDOM.addEventListener('scroll', (event): void => {
|
|
64
|
+
this.#lastScrollPos.left = (event.target as HTMLElement).scrollLeft;
|
|
65
|
+
this.#lastScrollPos.top = (event.target as HTMLElement).scrollTop;
|
|
65
66
|
});
|
|
66
67
|
this.ensureSettingListeners();
|
|
67
68
|
this.startObservingResize();
|
|
68
|
-
return this
|
|
69
|
+
return this.#activeEditor;
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
get editor(): CodeMirror.EditorView {
|
|
72
|
-
return this
|
|
73
|
+
return this.#activeEditor || this.createEditor();
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
dispatch(spec: CodeMirror.TransactionSpec): void {
|
|
@@ -77,68 +78,69 @@ export class TextEditor extends HTMLElement {
|
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
get state(): CodeMirror.EditorState {
|
|
80
|
-
if (this
|
|
81
|
-
return this
|
|
81
|
+
if (this.#activeEditor) {
|
|
82
|
+
return this.#activeEditor.state;
|
|
82
83
|
}
|
|
83
|
-
if (!this
|
|
84
|
-
this
|
|
84
|
+
if (!this.#pendingState) {
|
|
85
|
+
this.#pendingState = CodeMirror.EditorState.create({extensions: baseConfiguration('')});
|
|
85
86
|
}
|
|
86
|
-
return this
|
|
87
|
+
return this.#pendingState;
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
set state(state: CodeMirror.EditorState) {
|
|
90
|
-
if (this
|
|
91
|
-
this
|
|
91
|
+
if (this.#activeEditor) {
|
|
92
|
+
this.#activeEditor.setState(state);
|
|
92
93
|
} else {
|
|
93
|
-
this
|
|
94
|
+
this.#pendingState = state;
|
|
94
95
|
}
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
connectedCallback(): void {
|
|
98
|
-
if (!this
|
|
99
|
+
if (!this.#activeEditor) {
|
|
99
100
|
this.createEditor();
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
disconnectedCallback(): void {
|
|
104
|
-
if (this
|
|
105
|
-
this
|
|
106
|
-
this
|
|
107
|
-
|
|
108
|
-
this.
|
|
105
|
+
if (this.#activeEditor) {
|
|
106
|
+
this.#pendingState = this.#activeEditor.state;
|
|
107
|
+
this.#devtoolsResizeObserver.disconnect();
|
|
108
|
+
window.removeEventListener('resize', this.#resizeListener);
|
|
109
|
+
this.#activeEditor.destroy();
|
|
110
|
+
this.#activeEditor = undefined;
|
|
109
111
|
this.ensureSettingListeners();
|
|
110
112
|
}
|
|
111
113
|
}
|
|
112
114
|
|
|
113
115
|
focus(): void {
|
|
114
|
-
if (this
|
|
115
|
-
this
|
|
116
|
+
if (this.#activeEditor) {
|
|
117
|
+
this.#activeEditor.focus();
|
|
116
118
|
}
|
|
117
119
|
}
|
|
118
120
|
|
|
119
121
|
private ensureSettingListeners(): void {
|
|
120
|
-
const dynamicSettings = this
|
|
121
|
-
if (dynamicSettings === this
|
|
122
|
+
const dynamicSettings = this.#activeEditor ? this.#activeEditor.state.facet(dynamicSetting) : DynamicSetting.none;
|
|
123
|
+
if (dynamicSettings === this.#dynamicSettings) {
|
|
122
124
|
return;
|
|
123
125
|
}
|
|
124
|
-
this
|
|
126
|
+
this.#dynamicSettings = dynamicSettings;
|
|
125
127
|
|
|
126
|
-
for (const [setting, listener] of this
|
|
128
|
+
for (const [setting, listener] of this.#activeSettingListeners) {
|
|
127
129
|
setting.removeChangeListener(listener);
|
|
128
130
|
}
|
|
129
|
-
this
|
|
131
|
+
this.#activeSettingListeners = [];
|
|
130
132
|
|
|
131
133
|
const settings = Common.Settings.Settings.instance();
|
|
132
134
|
for (const dynamicSetting of dynamicSettings) {
|
|
133
135
|
const handler = ({data}: {data: unknown}): void => {
|
|
134
136
|
const change = dynamicSetting.sync(this.state, data);
|
|
135
|
-
if (change && this
|
|
136
|
-
this
|
|
137
|
+
if (change && this.#activeEditor) {
|
|
138
|
+
this.#activeEditor.dispatch({effects: change});
|
|
137
139
|
}
|
|
138
140
|
};
|
|
139
141
|
const setting = settings.moduleSetting(dynamicSetting.settingName);
|
|
140
142
|
setting.addChangeListener(handler);
|
|
141
|
-
this
|
|
143
|
+
this.#activeSettingListeners.push([setting, handler]);
|
|
142
144
|
}
|
|
143
145
|
}
|
|
144
146
|
|
|
@@ -146,12 +148,13 @@ export class TextEditor extends HTMLElement {
|
|
|
146
148
|
const devtoolsElement =
|
|
147
149
|
WindowBoundsService.WindowBoundsService.WindowBoundsServiceImpl.instance().getDevToolsBoundingElement();
|
|
148
150
|
if (devtoolsElement) {
|
|
149
|
-
this
|
|
151
|
+
this.#devtoolsResizeObserver.observe(devtoolsElement);
|
|
150
152
|
}
|
|
153
|
+
window.addEventListener('resize', this.#resizeListener);
|
|
151
154
|
}
|
|
152
155
|
|
|
153
156
|
revealPosition(selection: CodeMirror.EditorSelection, highlight: boolean = true): void {
|
|
154
|
-
const view = this
|
|
157
|
+
const view = this.#activeEditor;
|
|
155
158
|
if (!view) {
|
|
156
159
|
return;
|
|
157
160
|
}
|
|
@@ -217,10 +217,10 @@ let cacheInstance: PropertyCache|null = null;
|
|
|
217
217
|
// Store recent collections of property completions. The empty string
|
|
218
218
|
// is used to store the set of global bindings.
|
|
219
219
|
class PropertyCache {
|
|
220
|
-
|
|
220
|
+
readonly #cache: Map<string, Promise<CompletionSet>> = new Map();
|
|
221
221
|
|
|
222
222
|
constructor() {
|
|
223
|
-
const clear = (): void => this
|
|
223
|
+
const clear = (): void => this.#cache.clear();
|
|
224
224
|
SDK.ConsoleModel.ConsoleModel.instance().addEventListener(SDK.ConsoleModel.Events.CommandEvaluated, clear);
|
|
225
225
|
UI.Context.Context.instance().addFlavorChangeListener(SDK.RuntimeModel.ExecutionContext, clear);
|
|
226
226
|
SDK.TargetManager.TargetManager.instance().addModelListener(
|
|
@@ -230,14 +230,14 @@ class PropertyCache {
|
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
get(expression: string): Promise<CompletionSet>|undefined {
|
|
233
|
-
return this
|
|
233
|
+
return this.#cache.get(expression);
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
set(expression: string, value: Promise<CompletionSet>): void {
|
|
237
|
-
this
|
|
237
|
+
this.#cache.set(expression, value);
|
|
238
238
|
setTimeout(() => {
|
|
239
|
-
if (this
|
|
240
|
-
this
|
|
239
|
+
if (this.#cache.get(expression) === value) {
|
|
240
|
+
this.#cache.delete(expression);
|
|
241
241
|
}
|
|
242
242
|
}, maxCacheAge);
|
|
243
243
|
}
|
|
@@ -26,27 +26,27 @@ export class PromptInputEvent extends Event {
|
|
|
26
26
|
|
|
27
27
|
export class TextPrompt extends HTMLElement {
|
|
28
28
|
static readonly litTagName = LitHtml.literal`devtools-text-prompt`;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
readonly #shadow = this.attachShadow({mode: 'open'});
|
|
30
|
+
#ariaLabelText = '';
|
|
31
|
+
#prefixText = '';
|
|
32
|
+
#suggestionText = '';
|
|
33
33
|
|
|
34
34
|
connectedCallback(): void {
|
|
35
|
-
this
|
|
35
|
+
this.#shadow.adoptedStyleSheets = [textPromptStyles];
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
set data(data: TextPromptData) {
|
|
39
|
-
this
|
|
40
|
-
this
|
|
41
|
-
this
|
|
39
|
+
this.#ariaLabelText = data.ariaLabel;
|
|
40
|
+
this.#prefixText = data.prefix;
|
|
41
|
+
this.#suggestionText = data.suggestion;
|
|
42
42
|
this.render();
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
get data(): TextPromptData {
|
|
46
46
|
return {
|
|
47
|
-
ariaLabel: this
|
|
48
|
-
prefix: this
|
|
49
|
-
suggestion: this
|
|
47
|
+
ariaLabel: this.#ariaLabelText,
|
|
48
|
+
prefix: this.#prefixText,
|
|
49
|
+
suggestion: this.#suggestionText,
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
52
|
|
|
@@ -55,7 +55,7 @@ export class TextPrompt extends HTMLElement {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
private input(): HTMLInputElement {
|
|
58
|
-
const inputElement = this
|
|
58
|
+
const inputElement = this.#shadow.querySelector<HTMLInputElement>('input');
|
|
59
59
|
if (!inputElement) {
|
|
60
60
|
throw new Error('Expected an input element!');
|
|
61
61
|
}
|
|
@@ -92,12 +92,12 @@ export class TextPrompt extends HTMLElement {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
setPrefix(prefix: string): void {
|
|
95
|
-
this
|
|
95
|
+
this.#prefixText = prefix;
|
|
96
96
|
this.render();
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
setSuggestion(suggestion: string): void {
|
|
100
|
-
this
|
|
100
|
+
this.#suggestionText = suggestion;
|
|
101
101
|
this.render();
|
|
102
102
|
}
|
|
103
103
|
|
|
@@ -112,7 +112,7 @@ export class TextPrompt extends HTMLElement {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
private suggestion(): HTMLSpanElement {
|
|
115
|
-
const suggestionElement = this
|
|
115
|
+
const suggestionElement = this.#shadow.querySelector<HTMLSpanElement>('.suggestion');
|
|
116
116
|
if (!suggestionElement) {
|
|
117
117
|
throw new Error('Expected an suggestion element!');
|
|
118
118
|
}
|
|
@@ -125,11 +125,11 @@ export class TextPrompt extends HTMLElement {
|
|
|
125
125
|
|
|
126
126
|
private render(): void {
|
|
127
127
|
const output = LitHtml.html`
|
|
128
|
-
<span class="prefix">${this
|
|
129
|
-
<span class="text-prompt-input"><input aria-label=${this
|
|
128
|
+
<span class="prefix">${this.#prefixText} </span>
|
|
129
|
+
<span class="text-prompt-input"><input aria-label=${this.#ariaLabelText} spellcheck="false" @input=${
|
|
130
130
|
this.onInput} @keydown=${this.onKeyDown}/><span class='suggestion' suggestion="${
|
|
131
|
-
this
|
|
132
|
-
LitHtml.render(output, this
|
|
131
|
+
this.#suggestionText}"></span></span>`;
|
|
132
|
+
LitHtml.render(output, this.#shadow, {host: this});
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
|
|
@@ -82,20 +82,20 @@ export const enum FilterOption {
|
|
|
82
82
|
|
|
83
83
|
export class TreeOutline<TreeNodeDataType> extends HTMLElement {
|
|
84
84
|
static readonly litTagName = LitHtml.literal`devtools-tree-outline`;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
readonly #shadow = this.attachShadow({mode: 'open'});
|
|
86
|
+
#treeData: readonly TreeNode<TreeNodeDataType>[] = [];
|
|
87
|
+
#nodeExpandedMap: Map<string, boolean> = new Map();
|
|
88
|
+
#domNodeToTreeNodeMap: WeakMap<HTMLLIElement, TreeNode<TreeNodeDataType>> = new WeakMap();
|
|
89
|
+
#hasRenderedAtLeastOnce = false;
|
|
90
90
|
/**
|
|
91
91
|
* If we have expanded to a certain node, we want to focus it once we've
|
|
92
92
|
* rendered. But we render lazily and wrapped in LitHtml.until, so we can't
|
|
93
93
|
* know for sure when that node will be rendered. This variable tracks the
|
|
94
94
|
* node that we want focused but may not yet have been rendered.
|
|
95
95
|
*/
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
#nodeIdPendingFocus: TreeNodeId|null = null;
|
|
97
|
+
#selectedTreeNode: TreeNode<TreeNodeDataType>|null = null;
|
|
98
|
+
#defaultRenderer =
|
|
99
99
|
(node: TreeNode<TreeNodeDataType>, _state: {isExpanded: boolean}): LitHtml.TemplateResult => {
|
|
100
100
|
if (typeof node.treeNodeData !== 'string') {
|
|
101
101
|
console.warn(`The default TreeOutline renderer simply stringifies its given value. You passed in ${
|
|
@@ -105,16 +105,16 @@ export class TreeOutline<TreeNodeDataType> extends HTMLElement {
|
|
|
105
105
|
}
|
|
106
106
|
return LitHtml.html`${String(node.treeNodeData)}`;
|
|
107
107
|
};
|
|
108
|
-
|
|
108
|
+
#nodeFilter?: ((node: TreeNodeDataType) => FilterOption);
|
|
109
109
|
|
|
110
110
|
/**
|
|
111
111
|
* scheduledRender = render() has been called and scheduled a render.
|
|
112
112
|
*/
|
|
113
|
-
|
|
113
|
+
#scheduledRender = false;
|
|
114
114
|
/**
|
|
115
115
|
* enqueuedRender = render() was called mid-way through an existing render.
|
|
116
116
|
*/
|
|
117
|
-
|
|
117
|
+
#enqueuedRender = false;
|
|
118
118
|
|
|
119
119
|
static get observedAttributes(): string[] {
|
|
120
120
|
return ['nowrap', 'toplevelbordercolor'];
|
|
@@ -136,22 +136,22 @@ export class TreeOutline<TreeNodeDataType> extends HTMLElement {
|
|
|
136
136
|
connectedCallback(): void {
|
|
137
137
|
this.setTopLevelNodeBorderColorCSSVariable(this.getAttribute('toplevelbordercolor'));
|
|
138
138
|
this.setNodeKeyNoWrapCSSVariable(this.getAttribute('nowrap'));
|
|
139
|
-
this
|
|
139
|
+
this.#shadow.adoptedStyleSheets = [treeOutlineStyles, CodeHighlighter.Style.default];
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
get data(): TreeOutlineData<TreeNodeDataType> {
|
|
143
143
|
return {
|
|
144
|
-
tree: this
|
|
145
|
-
defaultRenderer: this
|
|
144
|
+
tree: this.#treeData as TreeNode<TreeNodeDataType>[],
|
|
145
|
+
defaultRenderer: this.#defaultRenderer,
|
|
146
146
|
};
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
set data(data: TreeOutlineData<TreeNodeDataType>) {
|
|
150
|
-
this
|
|
151
|
-
this
|
|
152
|
-
this
|
|
153
|
-
if (!this
|
|
154
|
-
this
|
|
150
|
+
this.#defaultRenderer = data.defaultRenderer;
|
|
151
|
+
this.#treeData = data.tree;
|
|
152
|
+
this.#nodeFilter = data.filter;
|
|
153
|
+
if (!this.#hasRenderedAtLeastOnce) {
|
|
154
|
+
this.#selectedTreeNode = this.#treeData[0];
|
|
155
155
|
}
|
|
156
156
|
this.render();
|
|
157
157
|
}
|
|
@@ -162,7 +162,7 @@ export class TreeOutline<TreeNodeDataType> extends HTMLElement {
|
|
|
162
162
|
* 1 and 2.
|
|
163
163
|
*/
|
|
164
164
|
async expandRecursively(maxDepth = 2): Promise<void> {
|
|
165
|
-
await Promise.all(this
|
|
165
|
+
await Promise.all(this.#treeData.map(rootNode => this.expandAndRecurse(rootNode, 0, maxDepth)));
|
|
166
166
|
await this.render();
|
|
167
167
|
}
|
|
168
168
|
|
|
@@ -170,7 +170,7 @@ export class TreeOutline<TreeNodeDataType> extends HTMLElement {
|
|
|
170
170
|
* Collapses all nodes in the tree.
|
|
171
171
|
*/
|
|
172
172
|
async collapseAllNodes(): Promise<void> {
|
|
173
|
-
this
|
|
173
|
+
this.#nodeExpandedMap.clear();
|
|
174
174
|
await this.render();
|
|
175
175
|
}
|
|
176
176
|
|
|
@@ -185,7 +185,7 @@ export class TreeOutline<TreeNodeDataType> extends HTMLElement {
|
|
|
185
185
|
* Takes a TreeNode ID, expands the outline to reveal it, and focuses it.
|
|
186
186
|
*/
|
|
187
187
|
async expandToAndSelectTreeNodeId(targetTreeNodeId: TreeNodeId): Promise<void> {
|
|
188
|
-
const pathToTreeNode = await getPathToTreeNode(this
|
|
188
|
+
const pathToTreeNode = await getPathToTreeNode(this.#treeData, targetTreeNodeId);
|
|
189
189
|
|
|
190
190
|
if (pathToTreeNode === null) {
|
|
191
191
|
throw new Error(`Could not find node with id ${targetTreeNodeId} in the tree.`);
|
|
@@ -198,7 +198,7 @@ export class TreeOutline<TreeNodeDataType> extends HTMLElement {
|
|
|
198
198
|
});
|
|
199
199
|
|
|
200
200
|
// Mark the node as pending focus so when it is rendered into the DOM we can focus it
|
|
201
|
-
this
|
|
201
|
+
this.#nodeIdPendingFocus = targetTreeNodeId;
|
|
202
202
|
await this.render();
|
|
203
203
|
}
|
|
204
204
|
|
|
@@ -206,7 +206,7 @@ export class TreeOutline<TreeNodeDataType> extends HTMLElement {
|
|
|
206
206
|
* Takes a list of TreeNode IDs and expands the corresponding nodes.
|
|
207
207
|
*/
|
|
208
208
|
expandNodeIds(nodeIds: TreeNodeId[]): Promise<void> {
|
|
209
|
-
nodeIds.forEach(id => this
|
|
209
|
+
nodeIds.forEach(id => this.#nodeExpandedMap.set(id, true));
|
|
210
210
|
return this.render();
|
|
211
211
|
}
|
|
212
212
|
|
|
@@ -214,12 +214,12 @@ export class TreeOutline<TreeNodeDataType> extends HTMLElement {
|
|
|
214
214
|
* Takes a TreeNode ID and focuses the corresponding node.
|
|
215
215
|
*/
|
|
216
216
|
focusNodeId(nodeId: TreeNodeId): Promise<void> {
|
|
217
|
-
this
|
|
217
|
+
this.#nodeIdPendingFocus = nodeId;
|
|
218
218
|
return this.render();
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
async collapseChildrenOfNode(domNode: HTMLLIElement): Promise<void> {
|
|
222
|
-
const treeNode = this
|
|
222
|
+
const treeNode = this.#domNodeToTreeNodeMap.get(domNode);
|
|
223
223
|
if (!treeNode) {
|
|
224
224
|
return;
|
|
225
225
|
}
|
|
@@ -248,22 +248,22 @@ export class TreeOutline<TreeNodeDataType> extends HTMLElement {
|
|
|
248
248
|
}
|
|
249
249
|
|
|
250
250
|
private getSelectedTreeNode(): TreeNode<TreeNodeDataType> {
|
|
251
|
-
if (!this
|
|
251
|
+
if (!this.#selectedTreeNode) {
|
|
252
252
|
throw new Error('getSelectedNode was called but selectedTreeNode is null');
|
|
253
253
|
}
|
|
254
|
-
return this
|
|
254
|
+
return this.#selectedTreeNode;
|
|
255
255
|
}
|
|
256
256
|
|
|
257
257
|
private async fetchNodeChildren(node: TreeNodeWithChildren<TreeNodeDataType>): Promise<TreeNode<TreeNodeDataType>[]> {
|
|
258
258
|
const children = await getNodeChildren(node);
|
|
259
|
-
if (!this
|
|
259
|
+
if (!this.#nodeFilter) {
|
|
260
260
|
return children;
|
|
261
261
|
}
|
|
262
262
|
const filteredChildren = [];
|
|
263
263
|
for (const child of children) {
|
|
264
|
-
const filtering = this
|
|
264
|
+
const filtering = this.#nodeFilter(child.treeNodeData);
|
|
265
265
|
// We always include the selected node in the tree, regardless of its filtering status.
|
|
266
|
-
if (filtering === FilterOption.SHOW || this.isSelectedNode(child) || child.id === this
|
|
266
|
+
if (filtering === FilterOption.SHOW || this.isSelectedNode(child) || child.id === this.#nodeIdPendingFocus) {
|
|
267
267
|
filteredChildren.push(child);
|
|
268
268
|
} else if (filtering === FilterOption.FLATTEN && isExpandableNode(child)) {
|
|
269
269
|
const grandChildren = await this.fetchNodeChildren(child);
|
|
@@ -274,11 +274,11 @@ export class TreeOutline<TreeNodeDataType> extends HTMLElement {
|
|
|
274
274
|
}
|
|
275
275
|
|
|
276
276
|
private setNodeExpandedState(node: TreeNode<TreeNodeDataType>, newExpandedState: boolean): void {
|
|
277
|
-
this
|
|
277
|
+
this.#nodeExpandedMap.set(node.id, newExpandedState);
|
|
278
278
|
}
|
|
279
279
|
|
|
280
280
|
private nodeIsExpanded(node: TreeNode<TreeNodeDataType>): boolean {
|
|
281
|
-
return this
|
|
281
|
+
return this.#nodeExpandedMap.get(node.id) || false;
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
private async expandAndRecurse(node: TreeNode<TreeNodeDataType>, currentDepth: number, maxDepth: number):
|
|
@@ -309,7 +309,7 @@ export class TreeOutline<TreeNodeDataType> extends HTMLElement {
|
|
|
309
309
|
event.stopPropagation();
|
|
310
310
|
const nodeClickExpandsOrContracts = this.getAttribute('clickabletitle') !== null;
|
|
311
311
|
const domNode = event.currentTarget as HTMLLIElement;
|
|
312
|
-
const node = this
|
|
312
|
+
const node = this.#domNodeToTreeNodeMap.get(domNode);
|
|
313
313
|
if (nodeClickExpandsOrContracts && node && isExpandableNode(node)) {
|
|
314
314
|
this.setNodeExpandedState(node, !this.nodeIsExpanded(node));
|
|
315
315
|
}
|
|
@@ -317,11 +317,11 @@ export class TreeOutline<TreeNodeDataType> extends HTMLElement {
|
|
|
317
317
|
}
|
|
318
318
|
|
|
319
319
|
private async focusTreeNode(domNode: HTMLLIElement): Promise<void> {
|
|
320
|
-
const treeNode = this
|
|
320
|
+
const treeNode = this.#domNodeToTreeNodeMap.get(domNode);
|
|
321
321
|
if (!treeNode) {
|
|
322
322
|
return;
|
|
323
323
|
}
|
|
324
|
-
this
|
|
324
|
+
this.#selectedTreeNode = treeNode;
|
|
325
325
|
await this.render();
|
|
326
326
|
this.dispatchEvent(new ItemSelectedEvent(treeNode));
|
|
327
327
|
coordinator.write('DOMNode focus', () => {
|
|
@@ -331,7 +331,7 @@ export class TreeOutline<TreeNodeDataType> extends HTMLElement {
|
|
|
331
331
|
|
|
332
332
|
private processHomeAndEndKeysNavigation(key: 'Home'|'End'): void {
|
|
333
333
|
if (key === 'Home') {
|
|
334
|
-
const firstRootNode = this
|
|
334
|
+
const firstRootNode = this.#shadow.querySelector<HTMLLIElement>('ul[role="tree"] > li[role="treeitem"]');
|
|
335
335
|
if (firstRootNode) {
|
|
336
336
|
this.focusTreeNode(firstRootNode);
|
|
337
337
|
}
|
|
@@ -345,7 +345,7 @@ export class TreeOutline<TreeNodeDataType> extends HTMLElement {
|
|
|
345
345
|
* li[role="treeitem"] in the DOM because we only render visible nodes.
|
|
346
346
|
* Therefore we can select all the nodes and pick the last one.
|
|
347
347
|
*/
|
|
348
|
-
const allTreeItems = this
|
|
348
|
+
const allTreeItems = this.#shadow.querySelectorAll<HTMLLIElement>('li[role="treeitem"]');
|
|
349
349
|
const lastTreeItem = allTreeItems[allTreeItems.length - 1];
|
|
350
350
|
if (lastTreeItem) {
|
|
351
351
|
this.focusTreeNode(lastTreeItem);
|
|
@@ -355,7 +355,7 @@ export class TreeOutline<TreeNodeDataType> extends HTMLElement {
|
|
|
355
355
|
|
|
356
356
|
private async processArrowKeyNavigation(key: Platform.KeyboardUtilities.ArrowKey, currentDOMNode: HTMLLIElement):
|
|
357
357
|
Promise<void> {
|
|
358
|
-
const currentTreeNode = this
|
|
358
|
+
const currentTreeNode = this.#domNodeToTreeNodeMap.get(currentDOMNode);
|
|
359
359
|
if (!currentTreeNode) {
|
|
360
360
|
return;
|
|
361
361
|
}
|
|
@@ -370,7 +370,7 @@ export class TreeOutline<TreeNodeDataType> extends HTMLElement {
|
|
|
370
370
|
}
|
|
371
371
|
|
|
372
372
|
private processEnterOrSpaceNavigation(currentDOMNode: HTMLLIElement): void {
|
|
373
|
-
const currentTreeNode = this
|
|
373
|
+
const currentTreeNode = this.#domNodeToTreeNodeMap.get(currentDOMNode);
|
|
374
374
|
if (!currentTreeNode) {
|
|
375
375
|
return;
|
|
376
376
|
}
|
|
@@ -399,13 +399,13 @@ export class TreeOutline<TreeNodeDataType> extends HTMLElement {
|
|
|
399
399
|
}
|
|
400
400
|
|
|
401
401
|
private focusPendingNode(domNode: HTMLLIElement): void {
|
|
402
|
-
this
|
|
402
|
+
this.#nodeIdPendingFocus = null;
|
|
403
403
|
this.focusTreeNode(domNode);
|
|
404
404
|
}
|
|
405
405
|
|
|
406
406
|
private isSelectedNode(node: TreeNode<TreeNodeDataType>): boolean {
|
|
407
|
-
if (this
|
|
408
|
-
return node.id === this
|
|
407
|
+
if (this.#selectedTreeNode) {
|
|
408
|
+
return node.id === this.#selectedTreeNode.id;
|
|
409
409
|
}
|
|
410
410
|
return false;
|
|
411
411
|
}
|
|
@@ -446,7 +446,7 @@ export class TreeOutline<TreeNodeDataType> extends HTMLElement {
|
|
|
446
446
|
if (node.renderer) {
|
|
447
447
|
renderedNodeKey = node.renderer(node, {isExpanded: nodeIsExpanded});
|
|
448
448
|
} else {
|
|
449
|
-
renderedNodeKey = this
|
|
449
|
+
renderedNodeKey = this.#defaultRenderer(node, {isExpanded: nodeIsExpanded});
|
|
450
450
|
}
|
|
451
451
|
|
|
452
452
|
// Disabled until https://crbug.com/1079231 is fixed.
|
|
@@ -460,7 +460,7 @@ export class TreeOutline<TreeNodeDataType> extends HTMLElement {
|
|
|
460
460
|
aria-posinset=${positionInSet + 1}
|
|
461
461
|
class=${listItemClasses}
|
|
462
462
|
@click=${this.onNodeClick}
|
|
463
|
-
track-dom-node-to-tree-node=${trackDOMNodeToTreeNode(this
|
|
463
|
+
track-dom-node-to-tree-node=${trackDOMNodeToTreeNode(this.#domNodeToTreeNodeMap, node)}
|
|
464
464
|
on-render=${ComponentHelpers.Directives.nodeRenderedCallback(domNode => {
|
|
465
465
|
/**
|
|
466
466
|
* Because TreeNodes are lazily rendered, you can call
|
|
@@ -473,7 +473,7 @@ export class TreeOutline<TreeNodeDataType> extends HTMLElement {
|
|
|
473
473
|
return;
|
|
474
474
|
}
|
|
475
475
|
|
|
476
|
-
if (this
|
|
476
|
+
if (this.#nodeIdPendingFocus && node.id === this.#nodeIdPendingFocus) {
|
|
477
477
|
this.focusPendingNode(domNode);
|
|
478
478
|
}
|
|
479
479
|
})}
|
|
@@ -497,14 +497,14 @@ export class TreeOutline<TreeNodeDataType> extends HTMLElement {
|
|
|
497
497
|
}
|
|
498
498
|
|
|
499
499
|
private async render(): Promise<void> {
|
|
500
|
-
if (this
|
|
500
|
+
if (this.#scheduledRender) {
|
|
501
501
|
// If we are already rendering, don't render again immediately, but
|
|
502
502
|
// enqueue it to be run after we're done on our current render.
|
|
503
|
-
this
|
|
503
|
+
this.#enqueuedRender = true;
|
|
504
504
|
return;
|
|
505
505
|
}
|
|
506
506
|
|
|
507
|
-
this
|
|
507
|
+
this.#scheduledRender = true;
|
|
508
508
|
|
|
509
509
|
await coordinator.write('TreeOutline render', () => {
|
|
510
510
|
// Disabled until https://crbug.com/1079231 is fixed.
|
|
@@ -512,27 +512,27 @@ export class TreeOutline<TreeNodeDataType> extends HTMLElement {
|
|
|
512
512
|
LitHtml.render(LitHtml.html`
|
|
513
513
|
<div class="wrapping-container">
|
|
514
514
|
<ul role="tree" @keydown=${this.onTreeKeyDown}>
|
|
515
|
-
${this
|
|
515
|
+
${this.#treeData.map((topLevelNode, index) => {
|
|
516
516
|
return this.renderNode(topLevelNode, {
|
|
517
517
|
depth: 0,
|
|
518
|
-
setSize: this
|
|
518
|
+
setSize: this.#treeData.length,
|
|
519
519
|
positionInSet: index,
|
|
520
520
|
});
|
|
521
521
|
})}
|
|
522
522
|
</ul>
|
|
523
523
|
</div>
|
|
524
|
-
`, this
|
|
524
|
+
`, this.#shadow, {
|
|
525
525
|
host: this,
|
|
526
526
|
});
|
|
527
527
|
});
|
|
528
528
|
// clang-format on
|
|
529
|
-
this
|
|
530
|
-
this
|
|
529
|
+
this.#hasRenderedAtLeastOnce = true;
|
|
530
|
+
this.#scheduledRender = false;
|
|
531
531
|
|
|
532
532
|
// If render() was called when we were already mid-render, let's re-render
|
|
533
533
|
// to ensure we're not rendering any stale UI.
|
|
534
|
-
if (this
|
|
535
|
-
this
|
|
534
|
+
if (this.#enqueuedRender) {
|
|
535
|
+
this.#enqueuedRender = false;
|
|
536
536
|
return this.render();
|
|
537
537
|
}
|
|
538
538
|
}
|
|
@@ -48,6 +48,7 @@ export class Infobar {
|
|
|
48
48
|
private readonly toggleElement: HTMLButtonElement;
|
|
49
49
|
private readonly closeButton: HTMLElement;
|
|
50
50
|
private closeCallback: (() => void)|null;
|
|
51
|
+
#firstFocusableElement: HTMLElement|null = null;
|
|
51
52
|
private parentView?: Widget;
|
|
52
53
|
|
|
53
54
|
// TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
|
|
@@ -88,6 +89,9 @@ export class Infobar {
|
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
const button = createTextButton(action.text, actionCallback, buttonClass);
|
|
92
|
+
if (action.highlight && !this.#firstFocusableElement) {
|
|
93
|
+
this.#firstFocusableElement = button;
|
|
94
|
+
}
|
|
91
95
|
this.actionContainer.appendChild(button);
|
|
92
96
|
}
|
|
93
97
|
}
|
|
@@ -202,6 +206,11 @@ export class Infobar {
|
|
|
202
206
|
this.toggleElement.remove();
|
|
203
207
|
this.onResize();
|
|
204
208
|
ARIAUtils.alert(this.detailsMessage);
|
|
209
|
+
if (this.#firstFocusableElement) {
|
|
210
|
+
this.#firstFocusableElement.focus();
|
|
211
|
+
} else {
|
|
212
|
+
this.closeButton.focus();
|
|
213
|
+
}
|
|
205
214
|
}
|
|
206
215
|
|
|
207
216
|
createDetailsRowMessage(message?: string): Element {
|