chrome-devtools-frontend 1.0.1662289 → 1.0.1662965

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 (67) hide show
  1. package/.agents/skills/devtools-unicode-escaping/SKILL.md +64 -0
  2. package/.agents/skills/migrate-chromium-test/SKILL.md +29 -4
  3. package/front_end/core/common/SettingRegistration.ts +0 -5
  4. package/front_end/core/common/Settings.ts +69 -3
  5. package/front_end/core/platform/StringUtilities.ts +38 -6
  6. package/front_end/core/sdk/ServiceWorkerCacheModel.ts +0 -10
  7. package/front_end/core/sdk/StorageBucketsModel.ts +0 -10
  8. package/front_end/core/sdk/sdk-meta.ts +0 -1
  9. package/front_end/entrypoints/inspector_main/OutermostTargetSelector.ts +0 -13
  10. package/front_end/entrypoints/inspector_main/RenderingOptions.ts +8 -3
  11. package/front_end/entrypoints/inspector_main/inspector_main-meta.ts +3 -3
  12. package/front_end/entrypoints/main/MainImpl.ts +7 -2
  13. package/front_end/foundation/Universe.ts +16 -0
  14. package/front_end/generated/InspectorBackendCommands.ts +2 -14
  15. package/front_end/generated/protocol-mapping.d.ts +0 -53
  16. package/front_end/generated/protocol-proxy-api.d.ts +0 -46
  17. package/front_end/generated/protocol.ts +0 -148
  18. package/front_end/models/ai_assistance/AiAgent2.ts +3 -3
  19. package/front_end/models/ai_assistance/AiConversation.ts +21 -10
  20. package/front_end/models/ai_assistance/AiHistoryStorage.ts +21 -10
  21. package/front_end/models/ai_assistance/ChangeManager.ts +2 -2
  22. package/front_end/models/ai_assistance/ExtensionScope.ts +6 -2
  23. package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +1 -1
  24. package/front_end/models/ai_assistance/agents/AiAgent.ts +8 -1
  25. package/front_end/models/ai_assistance/agents/StylingAgent.ts +1 -1
  26. package/front_end/models/badges/UserBadges.ts +31 -17
  27. package/front_end/models/bindings/NetworkProject.ts +16 -4
  28. package/front_end/models/emulation/DeviceModeModel.ts +67 -5
  29. package/front_end/models/extensions/RecorderPluginManager.ts +10 -6
  30. package/front_end/models/har/Log.snapshot.txt +193 -0
  31. package/front_end/models/har/Log.ts +3 -2
  32. package/front_end/models/source_map_scopes/FunctionCodeResolver.ts +7 -5
  33. package/front_end/models/source_map_scopes/NamesResolver.ts +36 -30
  34. package/front_end/models/stack_trace/StackTrace.ts +17 -0
  35. package/front_end/panels/application/IndexedDBModel.ts +0 -10
  36. package/front_end/panels/application/IndexedDBViews.ts +240 -164
  37. package/front_end/panels/application/SharedStorageModel.ts +0 -10
  38. package/front_end/panels/application/components/AdsView.ts +23 -0
  39. package/front_end/panels/application/components/SharedStorageAccessGrid.ts +1 -1
  40. package/front_end/panels/application/components/adsView.css +21 -1
  41. package/front_end/panels/application/indexedDBViews.css +1 -1
  42. package/front_end/panels/network/NetworkDataGridNode.ts +43 -1
  43. package/front_end/panels/network/RequestInitiatorView.ts +24 -2
  44. package/front_end/panels/network/networkLogView.css +5 -0
  45. package/front_end/panels/network/requestInitiatorViewTree.css +5 -0
  46. package/front_end/panels/profiler/HeapProfileView.ts +198 -79
  47. package/front_end/panels/recorder/README.md +1 -2
  48. package/front_end/panels/recorder/RecorderPanel.ts +1749 -17
  49. package/front_end/panels/recorder/recorder.ts +0 -2
  50. package/front_end/panels/settings/AISettingsTab.ts +13 -0
  51. package/front_end/third_party/chromium/README.chromium +1 -1
  52. package/front_end/ui/components/spinners/Spinner.ts +29 -32
  53. package/front_end/ui/components/spinners/spinner.css +32 -84
  54. package/front_end/ui/legacy/Treeoutline.ts +14 -6
  55. package/front_end/ui/legacy/UIUtils.ts +25 -10
  56. package/front_end/ui/legacy/Widget.ts +17 -10
  57. package/front_end/ui/legacy/components/data_grid/DataGridElement.ts +36 -7
  58. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +63 -68
  59. package/front_end/ui/legacy/components/object_ui/RemoteObjectPreviewFormatter.ts +1 -1
  60. package/front_end/ui/legacy/components/object_ui/objectValue.css +2 -1
  61. package/front_end/ui/legacy/inspectorCommon.css +4 -4
  62. package/front_end/ui/lit/lit.ts +1 -0
  63. package/front_end/ui/lit/render.ts +44 -10
  64. package/front_end/ui/lit/strip-whitespace.ts +23 -2
  65. package/package.json +1 -1
  66. package/front_end/panels/recorder/RecorderController.ts +0 -1759
  67. /package/front_end/panels/recorder/{recorderController.css → recorderPanel.css} +0 -0
@@ -4,7 +4,6 @@
4
4
 
5
5
  import * as ControlButton from './ControlButton.js';
6
6
  import * as CreateRecordingView from './CreateRecordingView.js';
7
- import * as RecorderController from './RecorderController.js';
8
7
  import * as RecorderEvents from './RecorderEvents.js';
9
8
  import * as RecorderPanel from './RecorderPanel.js';
10
9
  import * as RecordingListView from './RecordingListView.js';
@@ -18,7 +17,6 @@ import * as TimelineSection from './TimelineSection.js';
18
17
  export {
19
18
  ControlButton,
20
19
  CreateRecordingView,
21
- RecorderController,
22
20
  RecorderEvents,
23
21
  RecorderPanel,
24
22
  RecordingListView,
@@ -75,6 +75,16 @@ const UIStrings = {
75
75
  * @description Label for a button to collapse an accordion.
76
76
  */
77
77
  showLess: 'Show less',
78
+ /**
79
+ * @description Accessible label for a button to expand an accordion for a specific setting.
80
+ * @example {Code suggestions} PH1
81
+ */
82
+ showMoreOfSetting: 'Show more of {PH1}',
83
+ /**
84
+ * @description Accessible label for a button to collapse an accordion for a specific setting.
85
+ * @example {Code suggestions} PH1
86
+ */
87
+ showLessOfSetting: 'Show less of {PH1}',
78
88
  /**
79
89
  * @description Header for a list of feature attributes. 'When on, you’ll be able to …'.
80
90
  */
@@ -358,6 +368,9 @@ export const AI_SETTINGS_TAB_DEFAULT_VIEW: View = (input, _output, target): void
358
368
  <devtools-button
359
369
  .data=${{
360
370
  title: settingData.settingExpandState.isSettingExpanded ? i18nString(UIStrings.showLess) : i18nString(UIStrings.showMore),
371
+ accessibleLabel: settingData.settingExpandState.isSettingExpanded ?
372
+ i18nString(UIStrings.showLessOfSetting, {PH1: settingData.settingName}) :
373
+ i18nString(UIStrings.showMoreOfSetting, {PH1: settingData.settingName}),
361
374
  size: Buttons.Button.Size.SMALL,
362
375
  iconName: settingData.settingExpandState.isSettingExpanded ? 'chevron-up' : 'chevron-down',
363
376
  variant: Buttons.Button.Variant.ICON,
@@ -1,7 +1,7 @@
1
1
  Name: Dependencies sourced from the upstream `chromium` repository
2
2
  URL: Internal
3
3
  Version: N/A
4
- Revision: 27fac8d1924b08d4a6bb70e21df6482a1d430b2b
4
+ Revision: 7eaaa785deda657a652efc66266b8cc2957167f9
5
5
  Update Mechanism: Manual (https://crbug.com/428069060)
6
6
  License: BSD-3-Clause
7
7
  License File: LICENSE
@@ -3,10 +3,12 @@
3
3
  // found in the LICENSE file.
4
4
  /* eslint-disable @devtools/no-lit-render-outside-of-view, @devtools/enforce-custom-element-definitions-location */
5
5
 
6
- import {html, render} from '../../lit/lit.js';
6
+ import {Directives, html, render} from '../../lit/lit.js';
7
7
 
8
8
  import spinnerStyles from './spinner.css.js';
9
9
 
10
+ const {classMap} = Directives;
11
+
10
12
  export interface SpinnerProperties {
11
13
  active: boolean;
12
14
  }
@@ -48,39 +50,34 @@ export class Spinner extends HTMLElement {
48
50
  }
49
51
 
50
52
  #render(): void {
51
- // The radius of the circles are set to 2.75rem as per implementation
52
- // of indeterminate progress indicator in
53
- // https://github.com/material-components/material-components-web/tree/master/packages/mdc-circular-progress.
54
- // Changing the value of the radius will cause errors in animation.
53
+ // The radius is set to 40 to allow for stroke width padding, and
54
+ // pathLength=100 is used for scalable, unitless animation length.
55
55
  // clang-format off
56
- const content = this.active ? html`
57
- <div class="indeterminate-spinner">
58
- <div class="left-circle">
59
- <svg viewBox="0 0 100 100">
60
- <circle cx="50%" cy="50%" r="2.75rem"></circle></svg>
61
- </div>
62
- <div class="center-circle">
63
- <svg viewBox="0 0 100 100">
64
- <circle cx="50%" cy="50%" r="2.75rem"></circle></svg>
65
- </div>
66
- <div class="right-circle">
67
- <svg viewBox="0 0 100 100">
68
- <circle cx="50%" cy="50%" r="2.75rem"></circle></svg>
69
- </div>
70
- </div>
71
- ` : html`
72
- <div class="inactive-spinner">
73
- <svg viewBox="0 0 100 100">
74
- <circle cx="50%" cy="50%" r="2.75rem"></circle>
56
+ const spinnerClasses = {
57
+ indeterminate: this.active,
58
+ spinner: true,
59
+ };
60
+
61
+ render(
62
+ html`
63
+ <style>
64
+ ${spinnerStyles}
65
+ </style>
66
+ <svg
67
+ class=${classMap(spinnerClasses)}
68
+ viewBox="0 0 100 100"
69
+ >
70
+ <circle
71
+ cx="50"
72
+ cy="50"
73
+ r="44"
74
+ pathLength="100"
75
+ ></circle>
75
76
  </svg>
76
- </div>
77
- `;
78
- render(html`
79
- <style>
80
- ${spinnerStyles}
81
- </style>
82
- ${content}
83
- `, this.#shadow, {host: this});
77
+ `,
78
+ this.#shadow,
79
+ {host: this},
80
+ );
84
81
  // clang-format on
85
82
  }
86
83
  }
@@ -18,87 +18,43 @@
18
18
  animation: spinner-container-animation 1.5s linear infinite;
19
19
  }
20
20
 
21
- .indeterminate-spinner {
21
+ .spinner {
22
+ height: 100%;
23
+ width: 100%;
24
+ }
25
+
26
+ .spinner.indeterminate {
22
27
  /*
23
28
  * The value for animation duration has been obtained by plugging in values defined
24
29
  * in packages/mdc-circular-progress/_circular-progress-theme.scss to
25
30
  * functions defined in packages/mdc-circular-progress/_circular-progress.scss.
26
31
  * https://github.com/material-components/material-components-web
27
32
  */
28
- animation: indeterminate-spinner-animation 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
29
- height: 100%;
30
- width: 100%;
31
-
32
- .left-circle {
33
- height: 100%;
34
- width: 50%;
35
- display: inline-block;
36
- position: relative;
37
- overflow: hidden;
38
-
39
- & > svg {
40
- position: absolute;
41
- width: 200%;
42
- /*
43
- * The value for animation duration has been obtained from values defined
44
- * in packages/mdc-circular-progress/_circular-progress-theme.scss
45
- * https://github.com/material-components/material-components-web
46
- */
47
- animation: indeterminate-left-circle-spinner-animation 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
48
- }
49
- }
50
-
51
- .center-circle {
52
- height: 100%;
53
- width: 5%;
54
- display: inline-block;
55
- position: absolute;
56
- overflow: hidden;
57
- top: 0;
58
- left: 47.5%;
59
- box-sizing: border-box;
60
-
61
- & > svg {
62
- position: absolute;
63
- width: 2000%;
64
- left: -900%;
65
- transform: rotate(180deg);
66
- }
67
- }
33
+ animation: indeterminate-spinner-animation 5332ms cubic-bezier(0.4, 0, 0.2, 1)
34
+ infinite both;
68
35
 
69
- .right-circle {
70
- height: 100%;
71
- width: 50%;
72
- display: inline-block;
73
- position: relative;
74
- overflow: hidden;
75
-
76
- & > svg {
77
- position: absolute;
78
- width: 200%;
79
- left: -100%;
80
- /*
81
- * The value for animation duration has been obtained from values defined
82
- * in packages/mdc-circular-progress/_circular-progress-theme.scss
83
- * https://github.com/material-components/material-components-web
84
- */
85
- animation: indeterminate-right-circle-spinner-animation 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
86
- }
87
- }
88
36
  }
89
37
 
90
- .inactive-spinner circle {
38
+ .spinner circle {
91
39
  stroke: var(--sys-color-state-disabled);
92
40
  stroke-width: var(--sys-size-6);
93
41
  fill: transparent;
42
+ transform-origin: 50% 50%;
43
+ transform: rotate(-90deg);
94
44
  }
95
45
 
96
- .indeterminate-spinner circle {
46
+
47
+ .spinner.indeterminate circle {
97
48
  stroke: var(--sys-color-primary);
98
- stroke-width: var(--sys-size-6);
99
- fill: transparent;
100
- stroke-dasharray: 290px;
101
- stroke-dashoffset: 150px;
49
+ stroke-dasharray: 100, 100;
50
+ stroke-dashoffset: 0;
51
+ /*
52
+ * The value for animation duration has been obtained from values defined
53
+ * in packages/mdc-circular-progress/_circular-progress-theme.scss
54
+ * https://github.com/material-components/material-components-web
55
+ */
56
+ animation: indeterminate-spinner-circle-animation 1333ms
57
+ cubic-bezier(0.4, 0, 0.2, 1) infinite both;
102
58
  }
103
59
 
104
60
  @keyframes spinner-container-animation {
@@ -141,30 +97,22 @@
141
97
  }
142
98
  }
143
99
 
144
- @keyframes indeterminate-left-circle-spinner-animation {
145
- 0% {
146
- transform: rotate(265deg);
147
- }
148
-
149
- 50% {
150
- transform: rotate(130deg);
151
- }
152
-
153
- 100% {
154
- transform: rotate(265deg);
155
- }
156
- }
157
-
158
- @keyframes indeterminate-right-circle-spinner-animation {
100
+ @keyframes indeterminate-spinner-circle-animation {
159
101
  0% {
160
- transform: rotate(-265deg);
102
+ stroke-dasharray: 5, 100;
103
+ stroke-dashoffset: 0;
104
+ transform: rotate(-90deg);
161
105
  }
162
106
 
163
107
  50% {
164
- transform: rotate(-130deg);
108
+ stroke-dasharray: 75, 100;
109
+ stroke-dashoffset: 0;
110
+ transform: rotate(-225deg);
165
111
  }
166
112
 
167
113
  100% {
168
- transform: rotate(-265deg);
114
+ stroke-dasharray: 5, 100;
115
+ stroke-dashoffset: 0;
116
+ transform: rotate(-90deg);
169
117
  }
170
118
  }
@@ -1864,7 +1864,7 @@ export class TreeViewElement extends HTMLElementWithLightDOMTemplate {
1864
1864
  }
1865
1865
  }
1866
1866
 
1867
- protected override addNodes(nodes: NodeList|Node[], nextSibling?: Node|null): void {
1867
+ protected override addNodes(nodes: NodeList|Node[]): void {
1868
1868
  for (const node of getTreeNodes(nodes)) {
1869
1869
  if (TreeViewTreeElement.get(node)) {
1870
1870
  continue; // Not sure this can happen
@@ -1876,10 +1876,18 @@ export class TreeViewElement extends HTMLElementWithLightDOMTemplate {
1876
1876
  if (parent.treeElement.childCount() === 0) {
1877
1877
  parent.treeElement.childrenListElement.classList.add(...parent.classes.values());
1878
1878
  }
1879
- while (nextSibling && nextSibling.nodeType !== Node.ELEMENT_NODE) {
1880
- nextSibling = nextSibling.nextSibling;
1879
+ let nextElement: TreeElement|null = null;
1880
+ for (let e: Element|null = node.nextElementSibling; e; e = e.nextElementSibling) {
1881
+ const nextTreeEl = TreeViewTreeElement.get(e);
1882
+ if (nextTreeEl) {
1883
+ nextElement = nextTreeEl;
1884
+ break;
1885
+ }
1886
+ if (e instanceof TreeElementWrapper && e.treeElement && e.treeElement.parent === parent.treeElement) {
1887
+ nextElement = e.treeElement;
1888
+ break;
1889
+ }
1881
1890
  }
1882
- const nextElement = nextSibling ? TreeViewTreeElement.get(nextSibling) : null;
1883
1891
  const index = nextElement ? parent.treeElement.indexOfChild(nextElement) : parent.treeElement.children().length;
1884
1892
  let treeElement;
1885
1893
  if (node instanceof HTMLLIElement) {
@@ -1906,12 +1914,12 @@ export class TreeViewElement extends HTMLElementWithLightDOMTemplate {
1906
1914
  }
1907
1915
  }
1908
1916
  }
1909
- for (const element of getStyleElements(nodes)) {
1917
+ for (const element of new Set(getStyleElements(nodes))) {
1910
1918
  this.#treeOutline.shadowRoot.appendChild(element.cloneNode(true));
1911
1919
  }
1912
1920
  }
1913
1921
 
1914
- protected override removeNodes(nodes: NodeList): void {
1922
+ protected override removeNodes(nodes: NodeList|Node[]): void {
1915
1923
  for (const node of getTreeNodes(nodes)) {
1916
1924
  if (node instanceof HTMLLIElement) {
1917
1925
  TreeViewTreeElement.get(node)?.remove();
@@ -2124,10 +2124,6 @@ export class HTMLElementWithLightDOMTemplate extends HTMLElement {
2124
2124
  value['_$litType$'] === 1);
2125
2125
  }
2126
2126
 
2127
- function isLitDirective(value: unknown): value is {values: unknown[]} {
2128
- return Boolean(typeof value === 'object' && value && '_$litDirective$' in value && 'values' in value);
2129
- }
2130
-
2131
2127
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
2132
2128
  function isCallable(value: unknown): value is(...args: any[]) => any {
2133
2129
  // Native class constructors cannot be invoked without 'new', and we shouldn't attempt to wrap them.
@@ -2148,7 +2144,7 @@ export class HTMLElementWithLightDOMTemplate extends HTMLElement {
2148
2144
  HTMLElementWithLightDOMTemplate.patchLitTemplate(value);
2149
2145
  return value;
2150
2146
  }
2151
- if (isLitDirective(value)) {
2147
+ if (Lit.isLitDirective(value)) {
2152
2148
  for (let i = 0; i < value.values.length; i++) {
2153
2149
  const subvalue = value.values[i];
2154
2150
  if (isCallable(subvalue)) {
@@ -2186,9 +2182,28 @@ export class HTMLElementWithLightDOMTemplate extends HTMLElement {
2186
2182
 
2187
2183
  #onChange(mutationList: MutationRecord[]): void {
2188
2184
  this.onChange(mutationList);
2189
- for (const mutation of mutationList) {
2190
- this.removeNodes(mutation.removedNodes);
2191
- this.addNodes(mutation.addedNodes, mutation.nextSibling);
2185
+ const addedNodes = new Set<Node>();
2186
+ const removedNodes = new Set<Node>();
2187
+ for (let i = 0; i < mutationList.length; i++) {
2188
+ const mutation = mutationList[i];
2189
+ for (const node of mutation.addedNodes) {
2190
+ addedNodes.add(node);
2191
+ }
2192
+ for (const node of mutation.removedNodes) {
2193
+ removedNodes.add(node);
2194
+ }
2195
+ }
2196
+ if (removedNodes.size > 0) {
2197
+ this.removeNodes([...removedNodes]);
2198
+ }
2199
+
2200
+ const finalAddedNodes = [...addedNodes].filter(n => this.templateRoot.contains(n));
2201
+ if (finalAddedNodes.length > 0) {
2202
+ this.addNodes(finalAddedNodes);
2203
+ }
2204
+
2205
+ for (let i = 0; i < mutationList.length; i++) {
2206
+ const mutation = mutationList[i];
2192
2207
  this.updateNode(mutation.target, mutation.attributeName);
2193
2208
  }
2194
2209
  }
@@ -2199,10 +2214,10 @@ export class HTMLElementWithLightDOMTemplate extends HTMLElement {
2199
2214
  protected updateNode(_node: Node, _attributeName: string|null): void {
2200
2215
  }
2201
2216
 
2202
- protected addNodes(_nodes: NodeList|Node[], _nextSibling?: Node|null): void {
2217
+ protected addNodes(_nodes: NodeList|Node[]): void {
2203
2218
  }
2204
2219
 
2205
- protected removeNodes(_nodes: NodeList): void {
2220
+ protected removeNodes(_nodes: NodeList|Node[]): void {
2206
2221
  }
2207
2222
 
2208
2223
  static findCorrespondingElement(
@@ -76,16 +76,20 @@ export function widgetConfig<F extends WidgetFactory<AnyWidget>, ParamKeys exten
76
76
  let currentUpdateQueue: Map<AnyWidget, PromiseWithResolvers<void>>|null = null;
77
77
  const currentlyProcessed = new Set<AnyWidget>();
78
78
  let nextUpdateQueue = new Map<AnyWidget, PromiseWithResolvers<void>>();
79
- let pendingAnimationFrame: number|null = null;
79
+ const pendingAnimationFrames = new WeakMap<Window, number>();
80
80
  let overallUpdatePromise: PromiseWithResolvers<void>|null = null;
81
81
 
82
82
  function enqueueIntoNextUpdateQueue(widget: AnyWidget): Promise<void> {
83
83
  const scheduledUpdate = nextUpdateQueue.get(widget) ?? Promise.withResolvers<void>();
84
84
  nextUpdateQueue.delete(widget);
85
85
  nextUpdateQueue.set(widget, scheduledUpdate);
86
- if (pendingAnimationFrame === null) {
87
- const widgetWindow = widget.contentElement.window() || window;
88
- pendingAnimationFrame = widgetWindow.requestAnimationFrame(runNextUpdate);
86
+ const widgetWindow = widget.contentElement.window() || window;
87
+ if (!pendingAnimationFrames.has(widgetWindow)) {
88
+ const frameId = widgetWindow.requestAnimationFrame(() => {
89
+ pendingAnimationFrames.delete(widgetWindow);
90
+ runNextUpdate();
91
+ });
92
+ pendingAnimationFrames.set(widgetWindow, frameId);
89
93
  }
90
94
  return scheduledUpdate.promise;
91
95
  }
@@ -121,14 +125,13 @@ function cancelUpdate(widget: AnyWidget): void {
121
125
 
122
126
  function resolveOverallUpdatePromise(): void {
123
127
  if (currentlyProcessed.size === 0 && (!currentUpdateQueue || currentUpdateQueue.size === 0) &&
124
- nextUpdateQueue.size === 0 && !pendingAnimationFrame && overallUpdatePromise) {
128
+ nextUpdateQueue.size === 0 && overallUpdatePromise) {
125
129
  overallUpdatePromise.resolve();
126
130
  overallUpdatePromise = null;
127
131
  }
128
132
  }
129
133
 
130
134
  function runNextUpdate(): void {
131
- pendingAnimationFrame = null;
132
135
  if (!currentUpdateQueue) {
133
136
  currentUpdateQueue = nextUpdateQueue;
134
137
  nextUpdateQueue = new Map();
@@ -155,9 +158,13 @@ function runNextUpdate(): void {
155
158
  const nextUpdate = nextUpdateQueue.get(widget);
156
159
  if (nextUpdate) {
157
160
  void nextUpdate.promise.then(resolve);
158
- if (pendingAnimationFrame === null) {
159
- const widgetWindow = widget.contentElement.window() || window;
160
- pendingAnimationFrame = widgetWindow.requestAnimationFrame(runNextUpdate);
161
+ const widgetWindow = widget.contentElement.window() || window;
162
+ if (!pendingAnimationFrames.has(widgetWindow)) {
163
+ const frameId = widgetWindow.requestAnimationFrame(() => {
164
+ pendingAnimationFrames.delete(widgetWindow);
165
+ runNextUpdate();
166
+ });
167
+ pendingAnimationFrames.set(widgetWindow, frameId);
161
168
  }
162
169
  } else {
163
170
  resolve();
@@ -578,7 +585,7 @@ export class Widget<ContentTypeT extends HTMLElement|DocumentFragment = HTMLElem
578
585
  }
579
586
 
580
587
  static get allUpdatesComplete(): Promise<void> {
581
- if (!pendingAnimationFrame && !currentUpdateQueue && currentlyProcessed.size === 0) {
588
+ if (nextUpdateQueue.size === 0 && !currentUpdateQueue && currentlyProcessed.size === 0) {
582
589
  return Promise.resolve();
583
590
  }
584
591
  if (!overallUpdatePromise) {
@@ -71,6 +71,7 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
71
71
  #hideableColumns = new Set<string>();
72
72
  #hiddenColumns = new Set<string>();
73
73
  #usedCreationNode: DataGridElementNode|null = null;
74
+ #sortingChangedScheduled = false;
74
75
 
75
76
  constructor() {
76
77
  super();
@@ -247,6 +248,7 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
247
248
  const sort = column.getAttribute('sort') === 'descending' ? Order.Descending :
248
249
  column.getAttribute('sort') === 'ascending' ? Order.Ascending :
249
250
  undefined;
251
+ const disclosure = hasBooleanAttribute(column, 'disclosure');
250
252
  const columnDescriptor = {
251
253
  id,
252
254
  title: title as Platform.UIString.LocalizedString,
@@ -259,6 +261,7 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
259
261
  weight,
260
262
  editable,
261
263
  dataType,
264
+ disclosure,
262
265
  };
263
266
  this.#dataGrid.addColumn(columnDescriptor);
264
267
  this.#columns.push(columnDescriptor);
@@ -289,7 +292,7 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
289
292
  return false;
290
293
  }
291
294
 
292
- #getDataRows(nodes: NodeList): HTMLElement[] {
295
+ #getDataRows(nodes: NodeList|Node[]): HTMLElement[] {
293
296
  return [...nodes]
294
297
  .flatMap(node => {
295
298
  if (node instanceof HTMLTableRowElement) {
@@ -303,7 +306,7 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
303
306
  .filter(node => node.querySelector('td') && !hasBooleanAttribute(node, 'placeholder'));
304
307
  }
305
308
 
306
- #getStyleElements(nodes: NodeList): HTMLElement[] {
309
+ #getStyleElements(nodes: NodeList|Node[]): HTMLElement[] {
307
310
  return [...nodes].flatMap(node => {
308
311
  if (node instanceof HTMLStyleElement) {
309
312
  return [node];
@@ -325,10 +328,16 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
325
328
  return null;
326
329
  }
327
330
 
328
- override addNodes(nodes: NodeList): void {
331
+ override addNodes(nodes: NodeList|Node[]): void {
329
332
  for (const element of this.#getDataRows(nodes)) {
333
+ if (getNode(element)) {
334
+ continue;
335
+ }
330
336
  const parentRow = element.parentElement?.closest('td')?.closest('tr');
331
337
  const parentDataGridNode = parentRow ? getNode(parentRow) : undefined;
338
+ if (parentRow && !parentDataGridNode) {
339
+ continue;
340
+ }
332
341
  const parentNode = parentDataGridNode || this.#dataGrid.rootNode();
333
342
  const nextNode = this.#findNextExistingNode(element);
334
343
  const index = nextNode ? parentNode.children.indexOf(nextNode) : parentNode.children.length;
@@ -352,14 +361,17 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
352
361
  if (hasBooleanAttribute(element, 'highlighted')) {
353
362
  node.setHighlighted(true);
354
363
  }
364
+ if (hasBooleanAttribute(element, 'expanded')) {
365
+ node.expand();
366
+ }
355
367
  }
356
- for (const element of this.#getStyleElements(nodes)) {
368
+ for (const element of new Set(this.#getStyleElements(nodes))) {
357
369
  this.#shadowRoot.appendChild(element.cloneNode(true));
358
370
  }
359
- this.#dataGrid.dispatchEventToListeners(DataGridEvents.SORTING_CHANGED);
371
+ this.#scheduleSortingChanged();
360
372
  }
361
373
 
362
- override removeNodes(nodes: NodeList): void {
374
+ override removeNodes(nodes: NodeList|Node[]): void {
363
375
  for (const element of this.#getDataRows(nodes)) {
364
376
  const node = getNode(element);
365
377
  if (node) {
@@ -387,6 +399,12 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
387
399
  dataGridNode.setInactive(hasBooleanAttribute(dataRow, 'inactive'));
388
400
  } else if (attributeName === 'highlighted') {
389
401
  dataGridNode.setHighlighted(hasBooleanAttribute(dataRow, 'highlighted'));
402
+ } else if (attributeName === 'expanded') {
403
+ if (hasBooleanAttribute(dataRow, 'expanded')) {
404
+ dataGridNode.expand();
405
+ } else {
406
+ dataGridNode.collapse();
407
+ }
390
408
  } else {
391
409
  this.#updateHasChildren(dataGridNode, dataRow);
392
410
  dataGridNode.refresh();
@@ -394,6 +412,17 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
394
412
  }
395
413
  }
396
414
 
415
+ #scheduleSortingChanged(): void {
416
+ if (this.#sortingChangedScheduled) {
417
+ return;
418
+ }
419
+ this.#sortingChangedScheduled = true;
420
+ queueMicrotask(() => {
421
+ this.#sortingChangedScheduled = false;
422
+ this.#dataGrid.dispatchEventToListeners(DataGridEvents.SORTING_CHANGED);
423
+ });
424
+ }
425
+
397
426
  deselectRow(): void {
398
427
  this.#dataGrid.selectedNode?.deselect();
399
428
  }
@@ -430,7 +459,7 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
430
459
  // However, if we have nodes added, that will trigger a sort anyway so we
431
460
  // don't need to re-sort again.
432
461
  if (this.#dataGrid.sortColumnId() !== null && !hadAddedNodes) {
433
- this.#dataGrid.dispatchEventToListeners(DataGridEvents.SORTING_CHANGED);
462
+ this.#scheduleSortingChanged();
434
463
  }
435
464
  }
436
465