chrome-devtools-frontend 1.0.1724140 → 1.0.1726686

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 (95) hide show
  1. package/.agents/skills/review-ui-widgets/SKILL.md +7 -7
  2. package/.agents/skills/ui-eng-vision-local-lit-renderer/SKILL.md +12 -0
  3. package/.agents/skills/ui-eng-vision-widget-promoter/SKILL.md +3 -3
  4. package/.agents/skills/ui-widgets/SKILL.md +2 -1
  5. package/docs/cookbook/dependencies.md +6 -4
  6. package/front_end/core/i18n/i18nImpl.ts +1 -1
  7. package/front_end/core/root/Runtime.ts +1 -1
  8. package/front_end/core/sdk/ResourceTreeModel.ts +16 -0
  9. package/front_end/entrypoints/js_app/js_app.ts +2 -5
  10. package/front_end/entrypoints/node_app/node_app.ts +2 -3
  11. package/front_end/generated/InspectorBackendCommands.ts +1 -1
  12. package/front_end/generated/protocol.ts +1 -0
  13. package/front_end/models/ai_assistance/skills/lighthouse.md +2 -1
  14. package/front_end/models/ai_assistance/skills/network.md +1 -0
  15. package/front_end/models/ai_assistance/tools/ExecuteJavaScript.ts +26 -2
  16. package/front_end/models/ai_assistance/tools/GetLighthouseAudits.ts +1 -1
  17. package/front_end/models/ai_assistance/tools/ListNetworkRequests.ts +2 -7
  18. package/front_end/models/ai_assistance/tools/RunLighthouse.ts +2 -2
  19. package/front_end/models/comment_manager/CommentManager.ts +0 -6
  20. package/front_end/models/comment_manager/CommentThread.ts +23 -19
  21. package/front_end/models/elements/StyleChanges.ts +66 -0
  22. package/front_end/models/trace/EventsSerializer.ts +2 -7
  23. package/front_end/models/trace/ModelImpl.ts +0 -2
  24. package/front_end/models/trace/handlers/FlowsHandler.ts +37 -29
  25. package/front_end/models/trace/helpers/SyntheticEvents.ts +10 -37
  26. package/front_end/models/trace/insights/RenderBlocking.ts +3 -1
  27. package/front_end/models/trace/lantern/simulation/Simulator.ts +4 -0
  28. package/front_end/models/trace/lantern/types/Lantern.ts +1 -0
  29. package/front_end/panels/accessibility/SourceOrderView.ts +1 -1
  30. package/front_end/panels/application/ApplicationPanelSidebar.ts +90 -0
  31. package/front_end/panels/application/StorageView.ts +196 -174
  32. package/front_end/panels/application/SubAppsView.ts +2 -2
  33. package/front_end/panels/application/preloading/PreloadingView.ts +360 -336
  34. package/front_end/panels/application/preloading/components/PreloadingDisabledInfobar.ts +2 -2
  35. package/front_end/panels/application/preloading/components/PreloadingGrid.ts +6 -3
  36. package/front_end/panels/application/preloading/components/RuleSetGrid.ts +17 -17
  37. package/front_end/panels/application/preloading/components/UsedPreloadingView.ts +2 -2
  38. package/front_end/panels/application/preloading/preloadingView.css +12 -4
  39. package/front_end/panels/application/preloading/preloadingViewDropDown.css +1 -1
  40. package/front_end/panels/application/storageView.css +16 -21
  41. package/front_end/panels/browser_debugger/CategorizedBreakpointsSidebarPane.ts +2 -2
  42. package/front_end/panels/browser_debugger/browser_debugger-meta.ts +6 -8
  43. package/front_end/panels/comments/CommentsPane.ts +26 -9
  44. package/front_end/panels/comments/commentsPane.css +25 -6
  45. package/front_end/panels/common/CommentThreadWidget.ts +2 -2
  46. package/front_end/panels/common/CommentsOverlayWidget.ts +94 -35
  47. package/front_end/panels/common/commentThreadWidget.css +17 -0
  48. package/front_end/panels/common/commentsOverlay.css +5 -10
  49. package/front_end/panels/common/common-meta.ts +11 -1
  50. package/front_end/panels/elements/ClassesPaneWidget.ts +130 -88
  51. package/front_end/panels/elements/ColorSwatchPopoverIcon.ts +7 -33
  52. package/front_end/panels/elements/DOMTreeWidget.ts +7 -3
  53. package/front_end/panels/elements/ElementStatePaneWidget.ts +11 -1
  54. package/front_end/panels/elements/PropertyRenderer.ts +3 -2
  55. package/front_end/panels/elements/StyleEditorWidget.ts +60 -41
  56. package/front_end/panels/elements/StylePropertiesSection.ts +32 -5
  57. package/front_end/panels/elements/StylePropertyTreeElement.ts +227 -162
  58. package/front_end/panels/event_listeners/EventListenersView.ts +11 -8
  59. package/front_end/panels/linear_memory_inspector/components/ValueInterpreterSettings.ts +1 -1
  60. package/front_end/panels/network/NetworkDataGridNode.ts +3 -1
  61. package/front_end/panels/network/RequestConditionsDrawer.ts +1 -1
  62. package/front_end/panels/network/RequestPayloadView.ts +12 -5
  63. package/front_end/panels/profiler/HeapProfileView.ts +28 -6
  64. package/front_end/panels/profiler/HeapSnapshotDataGrids.ts +7 -3
  65. package/front_end/panels/protocol_monitor/JSONEditor.ts +0 -1
  66. package/front_end/panels/recorder/RecorderPanel.ts +2 -2
  67. package/front_end/panels/settings/SettingsScreen.ts +1 -1
  68. package/front_end/panels/sources/NavigatorView.ts +105 -45
  69. package/front_end/panels/sources/ScopeChainSidebarPane.ts +8 -5
  70. package/front_end/panels/sources/SourcesNavigator.ts +421 -213
  71. package/front_end/panels/sources/SourcesPanel.ts +6 -4
  72. package/front_end/panels/sources/WatchExpressionsSidebarPane.ts +10 -5
  73. package/front_end/panels/sources/sources-meta.ts +4 -15
  74. package/front_end/panels/sources/sourcesNavigator.css +5 -0
  75. package/front_end/panels/timeline/ModificationsManager.ts +0 -2
  76. package/front_end/panels/timeline/README.md +2 -0
  77. package/front_end/panels/timeline/TimelineCommentController.ts +132 -30
  78. package/front_end/panels/timeline/TimelinePanel.ts +4 -0
  79. package/front_end/panels/timeline/components/IgnoreListSetting.ts +1 -1
  80. package/front_end/panels/timeline/utils/Treemap.ts +8 -0
  81. package/front_end/third_party/chromium/README.chromium +1 -1
  82. package/front_end/ui/comments/CommentAnchorResolver.ts +3 -0
  83. package/front_end/ui/comments/CommentOverlayManager.ts +2 -4
  84. package/front_end/ui/components/lists/List.ts +14 -1
  85. package/front_end/ui/components/panel_feedback/PreviewToggle.ts +1 -1
  86. package/front_end/ui/components/text_editor/AiCodeCompletionProvider.ts +23 -14
  87. package/front_end/ui/components/text_editor/AiCodeGenerationProvider.ts +3 -0
  88. package/front_end/ui/legacy/Toolbar.ts +0 -1
  89. package/front_end/ui/legacy/Treeoutline.ts +7 -2
  90. package/front_end/ui/legacy/UIUtils.ts +4 -1
  91. package/front_end/ui/legacy/components/inline_editor/Swatches.ts +19 -6
  92. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +20 -11
  93. package/front_end/ui/legacy/components/source_frame/XMLView.ts +27 -7
  94. package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
  95. package/package.json +1 -1
@@ -14,7 +14,7 @@ import * as Types from '../types/types.js';
14
14
  // included, while the amount of `step` phase events can be >= 0.
15
15
 
16
16
  // A flow phase event is assigned to a trace event when their cat, tid,
17
- // pid and ts are equal (see @flowPhaseBindingTokenForEvent ).
17
+ // pid and ts are equal (see findBinding()).
18
18
 
19
19
  // It's possible for a single event to belong to multiple flows. In that
20
20
  // case, it will have multiple corresponding flow phase events (one
@@ -26,24 +26,27 @@ import * as Types from '../types/types.js';
26
26
  // there are any corresponding flow phase events at all).
27
27
  let flowDataByGroupToken = new Map<string, number>();
28
28
 
29
- interface EventFlowData {
29
+ /**
30
+ * The flows bound to one flow binding tuple (timestamp, process id,
31
+ * thread id and category).
32
+ */
33
+ interface FlowBinding {
34
+ ts: Types.Timing.Micro;
35
+ pid: Types.Events.ProcessID;
36
+ tid: Types.Events.ThreadID;
37
+ cat: string;
30
38
  flows: Set<number>;
31
39
  bindingParsed: boolean;
32
40
  }
33
- type FlowBindingTuple =
34
- Map<Types.Timing.Micro, Map<Types.Events.ProcessID, Map<Types.Events.ThreadID, Map<string, EventFlowData>>>>;
35
-
36
- // Given a trace event's flow binding tuple (timestamp, process id,
37
- // thread id and category) we determine if there is any flow data bound
38
- // to it by using this map's content. It's built when processing flow
39
- // events in a trace.
40
- // An alternative to having a map of four levels is having single map
41
- // from a string token built from concatenating the binding data to the
42
- // corresponding flow data. However, this token would be calculated for
43
- // every event in a trace, resulting in a lot of memory overhead and
44
- // major GC triggering. So we are trading off readability for
45
- // performance.
46
- let boundFlowData: FlowBindingTuple = new Map();
41
+
42
+ // Trace timestamps are microseconds and too large to be small integers
43
+ // in V8, so a Map keyed by the timestamp hashes a heap number on every
44
+ // lookup. Every non-flow event in a trace is looked up here, so this
45
+ // map is keyed by the low 30 bits of the timestamp instead, which are
46
+ // always a small integer. Bindings that share a key are told apart by
47
+ // comparing the full flow binding tuple.
48
+ const TS_KEY_MASK = 0x3fffffff;
49
+ let boundFlowData = new Map<number, FlowBinding[]>();
47
50
 
48
51
  let flowsById = new Map<number, Map<Types.Timing.Micro, Types.Events.Event>>();
49
52
  let flowEvents: Types.Events.FlowEvent[] = [];
@@ -67,8 +70,17 @@ export function handleEvent(event: Types.Events.Event): void {
67
70
  nonFlowEvents.push(event);
68
71
  }
69
72
 
73
+ function findBinding(bindings: FlowBinding[], event: Types.Events.Event): FlowBinding|undefined {
74
+ return bindings.find(binding => binding.ts === event.ts && binding.pid === event.pid && binding.tid === event.tid &&
75
+ binding.cat === event.cat);
76
+ }
77
+
70
78
  function processNonFlowEvent(event: Types.Events.Event): void {
71
- const flowDataForEvent = boundFlowData.get(event.ts)?.get(event.pid)?.get(event.tid)?.get(event.cat);
79
+ const bindings = boundFlowData.get(event.ts & TS_KEY_MASK);
80
+ if (!bindings) {
81
+ return;
82
+ }
83
+ const flowDataForEvent = findBinding(bindings, event);
72
84
  if (!flowDataForEvent) {
73
85
  return;
74
86
  }
@@ -95,9 +107,8 @@ function processFlowEvent(flowPhaseEvent: Types.Events.FlowEvent): void {
95
107
  const flowGroup = flowGroupTokenForFlowPhaseEvent(flowPhaseEvent);
96
108
  switch (flowPhaseEvent.ph) {
97
109
  case (Types.Events.Phase.FLOW_START): {
98
- const flowMetadata = {flowId: flowPhaseEvent.id, times: new Map([[flowPhaseEvent.ts, undefined]])};
99
110
  flowDataByGroupToken.set(flowGroup, flowPhaseEvent.id);
100
- addFlowIdToEventBinding(flowPhaseEvent, flowMetadata.flowId);
111
+ addFlowIdToEventBinding(flowPhaseEvent, flowPhaseEvent.id);
101
112
  return;
102
113
  }
103
114
  case (Types.Events.Phase.FLOW_STEP): {
@@ -125,22 +136,19 @@ function processFlowEvent(flowPhaseEvent: Types.Events.FlowEvent): void {
125
136
  }
126
137
  }
127
138
 
128
- type MapValueType<T extends Map<unknown, unknown>> = NonNullable<ReturnType<T['get']>>;
129
139
  /**
130
140
  * A single trace event can belong to multiple flows. This method
131
141
  * tracks which flows (flowId) an event belongs to given its flow
132
142
  * binding tuple (made of its ts, pid, tid and cat).
133
143
  */
134
144
  function addFlowIdToEventBinding(event: Types.Events.Event, flowId: number): void {
135
- const flowsByPid = Platform.MapUtilities.getWithDefault<Types.Timing.Micro, MapValueType<typeof boundFlowData>>(
136
- boundFlowData, event.ts, () => new Map());
137
- const flowsByTid = Platform.MapUtilities.getWithDefault<Types.Events.ProcessID, MapValueType<typeof flowsByPid>>(
138
- flowsByPid, event.pid, () => new Map());
139
- const flowsByCat = Platform.MapUtilities.getWithDefault<Types.Events.ThreadID, MapValueType<typeof flowsByTid>>(
140
- flowsByTid, event.tid, () => new Map());
141
- const flowData =
142
- Platform.MapUtilities.getWithDefault(flowsByCat, event.cat, () => ({flows: new Set(), bindingParsed: false}));
143
- flowData.flows.add(flowId);
145
+ const bindings = Platform.MapUtilities.getWithDefault(boundFlowData, event.ts & TS_KEY_MASK, () => []);
146
+ let binding = findBinding(bindings, event);
147
+ if (!binding) {
148
+ binding = {ts: event.ts, pid: event.pid, tid: event.tid, cat: event.cat, flows: new Set(), bindingParsed: false};
149
+ bindings.push(binding);
150
+ }
151
+ binding.flows.add(flowId);
144
152
  }
145
153
 
146
154
  /**
@@ -8,12 +8,14 @@ let activeManager: SyntheticEventsManager|null = null;
8
8
 
9
9
  export class SyntheticEventsManager {
10
10
  /**
11
- * All synthetic entries created in a trace from
12
- * corresponding trace events.
13
- * (ProfileCalls and layout shift clusters are excluded
14
- * because they are not based on a real trace event.)
11
+ * All synthetic entries created in a trace, keyed by the raw trace event
12
+ * they are based on. (ProfileCalls and layout shift clusters are excluded
13
+ * because they are not based on a real trace event.) Keying by the raw
14
+ * event rather than by its index in the raw events array means
15
+ * registration does not need to look up the index, which would require a
16
+ * map of every raw event in the trace.
15
17
  */
16
- #syntheticTraces: Types.Events.SyntheticBased[] = [];
18
+ #syntheticEventByRawEvent = new WeakMap<Types.Events.Event, Types.Events.SyntheticBased>();
17
19
  /**
18
20
  * All raw entries from a trace.
19
21
  */
@@ -56,55 +58,26 @@ export class SyntheticEventsManager {
56
58
  this.#rawTraceEvents = rawEvents;
57
59
  }
58
60
 
59
- #rawEventIndexes: Map<Types.Events.Event, number>|null = null;
60
-
61
- #getRawEventIndex(event: Types.Events.Event): number {
62
- if (!this.#rawEventIndexes) {
63
- this.#rawEventIndexes = new Map();
64
- for (let i = 0; i < this.#rawTraceEvents.length; i++) {
65
- this.#rawEventIndexes.set(this.#rawTraceEvents[i], i);
66
- }
67
- }
68
- return this.#rawEventIndexes.get(event) ?? -1;
69
- }
70
-
71
- /**
72
- * Clears the transient raw event index map.
73
- * During trace parsing, this map provides O(1) lookups for registering synthetic events.
74
- * Once parsing is complete, queries look up synthetic events exclusively by index in
75
- * `#syntheticTraces`, so this map is discarded to reclaim memory.
76
- */
77
- clearRegistrationIndex(): void {
78
- this.#rawEventIndexes = null;
79
- }
80
-
81
61
  /**
82
62
  * Registers and returns a branded synthetic event. Synthetic events need to
83
63
  * be created with this method to ensure they are registered and made
84
64
  * available to load events using serialized keys.
85
65
  */
86
66
  #registerSyntheticEvent<T extends Types.Events.SyntheticBased>(syntheticEvent: Omit<T, '_tag'>): T {
87
- const rawIndex = this.#getRawEventIndex(syntheticEvent.rawSourceEvent);
88
- if (rawIndex < 0) {
89
- throw new Error('Attempted to register a synthetic event paired to an unknown raw event.');
90
- }
91
67
  const eventAsSynthetic = syntheticEvent as T;
92
- this.#syntheticTraces[rawIndex] = eventAsSynthetic;
68
+ this.#syntheticEventByRawEvent.set(syntheticEvent.rawSourceEvent, eventAsSynthetic);
93
69
  return eventAsSynthetic;
94
70
  }
95
71
 
96
72
  syntheticEventForRawEventIndex(rawEventIndex: number): Types.Events.SyntheticBased {
97
- const syntheticEvent = this.#syntheticTraces.at(rawEventIndex);
73
+ const rawEvent = this.#rawTraceEvents.at(rawEventIndex);
74
+ const syntheticEvent = rawEvent && this.#syntheticEventByRawEvent.get(rawEvent);
98
75
  if (!syntheticEvent) {
99
76
  throw new Error(`Attempted to get a synthetic event from an unknown raw event index: ${rawEventIndex}`);
100
77
  }
101
78
  return syntheticEvent;
102
79
  }
103
80
 
104
- getSyntheticTraces(): Types.Events.SyntheticBased[] {
105
- return this.#syntheticTraces;
106
- }
107
-
108
81
  getRawTraceEvents(): readonly Types.Events.Event[] {
109
82
  return this.#rawTraceEvents;
110
83
  }
@@ -82,18 +82,20 @@ function getNodesAndTimingByRequestId(nodeTimings: Lantern.Simulation.Result['no
82
82
 
83
83
  let duration = nodeTiming.duration;
84
84
  let startTime = nodeTiming.startTime;
85
+ let queuedTime = nodeTiming.queuedTime;
85
86
  for (const redirect of node.request.redirects ?? []) {
86
87
  const redirectTiming = timingByNodeId.get(redirect.requestId);
87
88
  if (redirectTiming) {
88
89
  duration += redirectTiming.duration;
89
90
  startTime = Math.min(startTime, redirectTiming.startTime);
91
+ queuedTime = Math.min(queuedTime, redirectTiming.queuedTime);
90
92
  }
91
93
  }
92
94
 
93
95
  const backendRequestId = node.request.rawRequest?.args.data.requestId ?? node.request.requestId;
94
96
  requestIdToNode.set(backendRequestId, {
95
97
  node,
96
- nodeTiming: {startTime, endTime: nodeTiming.endTime, duration},
98
+ nodeTiming: {startTime, endTime: nodeTiming.endTime, queuedTime, duration},
97
99
  });
98
100
  }
99
101
 
@@ -262,6 +262,9 @@ class Simulator<T = Lantern.AnyNetworkObject> {
262
262
  if (!connection) {
263
263
  return;
264
264
  }
265
+ if (this.nodeTimings.getQueued(node).queuedTime === totalElapsedTime) {
266
+ connection.setH2OverflowBytesDownloaded(0);
267
+ }
265
268
  }
266
269
 
267
270
  this.markNodeAsInProgress(node, totalElapsedTime);
@@ -430,6 +433,7 @@ class Simulator<T = Lantern.AnyNetworkObject> {
430
433
  {
431
434
  startTime: timing.startTime,
432
435
  endTime: timing.endTime,
436
+ queuedTime: timing.queuedTime,
433
437
  duration: timing.endTime - timing.startTime,
434
438
  },
435
439
  ];
@@ -233,6 +233,7 @@ export namespace Simulation {
233
233
  export interface NodeTiming {
234
234
  startTime: number;
235
235
  endTime: number;
236
+ queuedTime: number;
236
237
  duration: number;
237
238
  }
238
239
  }
@@ -72,7 +72,7 @@ const DEFAULT_VIEW: View = (input, _output, target) => {
72
72
  }
73
73
  <devtools-checkbox class="source-order-checkbox"
74
74
  jslog=${VisualLogging.toggle().track({click: true})}
75
- ?checked=${input.showSourceOrder}
75
+ .checked=${Boolean(input.showSourceOrder)}
76
76
  @change=${onShowSourceOrderChanged}>
77
77
  ${i18nString(UIStrings.showSourceOrder)}
78
78
  </devtools-checkbox>
@@ -81,6 +81,7 @@ import {ServiceWorkerCacheTreeElement} from './ServiceWorkerCacheTreeElement.js'
81
81
  import {ServiceWorkersView} from './ServiceWorkersView.js';
82
82
  import {StorageBucketsTreeParentElement} from './StorageBucketsTreeElement.js';
83
83
  import {StorageView} from './StorageView.js';
84
+ import {type SubAppEntry, SubAppsView} from './SubAppsView.js';
84
85
  import {TrustTokensTreeElement} from './TrustTokensTreeElement.js';
85
86
  import {WebMCPTreeElement} from './WebMCPTreeElement.js';
86
87
 
@@ -97,6 +98,14 @@ const UIStrings = {
97
98
  * @description Context menu item to explain cookies with AI in the Application panel sidebar.
98
99
  */
99
100
  explainCookies: 'Explain cookies',
101
+ /**
102
+ * @description Tree item label for Sub Apps in the sidebar of the Application panel.
103
+ */
104
+ subApps: 'Sub apps',
105
+ /**
106
+ * @description Tree item label for Sibling Sub Apps in the sidebar of the Application panel.
107
+ */
108
+ siblingSubApps: 'Sibling sub apps',
100
109
  /**
101
110
  * @description Section header for the application section in the sidebar of the Application panel.
102
111
  */
@@ -335,6 +344,7 @@ export class ApplicationPanelSidebar extends UI.Widget.VBox implements SDK.Targe
335
344
  pushMessagingTreeElement: BackgroundServiceTreeElement;
336
345
  reportingApiTreeElement: ReportingApiTreeElement;
337
346
  webMcpTreeElement?: WebMCPTreeElement;
347
+ subAppsTreeElement: SubAppsTreeElement;
338
348
  adsTreeElement?: ApplicationPanelTreeElement;
339
349
  storageTreeElement?: StorageTreeElement;
340
350
  deviceBoundSessionsRootTreeElement: DeviceBoundSessionsRootTreeElement|undefined;
@@ -374,6 +384,8 @@ export class ApplicationPanelSidebar extends UI.Widget.VBox implements SDK.Targe
374
384
  const manifestTreeElement = new AppManifestTreeElement(panel);
375
385
  this.applicationTreeElement.appendChild(manifestTreeElement);
376
386
  manifestTreeElement.generateChildren();
387
+ this.subAppsTreeElement = new SubAppsTreeElement(panel);
388
+ this.applicationTreeElement.appendChild(this.subAppsTreeElement);
377
389
  this.serviceWorkersTreeElement = new ServiceWorkersTreeElement(panel);
378
390
  this.applicationTreeElement.appendChild(this.serviceWorkersTreeElement);
379
391
  this.storageTreeElement = new StorageTreeElement(panel);
@@ -623,6 +635,11 @@ export class ApplicationPanelSidebar extends UI.Widget.VBox implements SDK.Targe
623
635
  if (preloadingModel) {
624
636
  this.preloadingSummaryTreeElement?.initialize(preloadingModel);
625
637
  }
638
+
639
+ const resourceTreeModel = this.target?.model(SDK.ResourceTreeModel.ResourceTreeModel);
640
+ if (resourceTreeModel) {
641
+ void this.subAppsTreeElement.update(resourceTreeModel);
642
+ }
626
643
  }
627
644
 
628
645
  private domStorageModelAdded(model: SDK.DOMStorageModel.DOMStorageModel): void {
@@ -721,6 +738,7 @@ export class ApplicationPanelSidebar extends UI.Widget.VBox implements SDK.Targe
721
738
 
722
739
  if (frame.isOutermostFrame()) {
723
740
  this.reset();
741
+ void this.subAppsTreeElement.update(frame.resourceTreeModel());
724
742
  const selectedElement = this.sidebarTree.selectedTreeElement;
725
743
  if (selectedElement instanceof ExpandableApplicationPanelTreeElement) {
726
744
  // Only update AI Assistance storage context for expandable category headers,
@@ -1146,6 +1164,78 @@ export class AppManifestTreeElement extends ApplicationPanelTreeElement {
1146
1164
  }
1147
1165
  }
1148
1166
 
1167
+ export class SubAppsTreeElement extends ApplicationPanelTreeElement {
1168
+ #view?: SubAppsView;
1169
+ #subApps: SubAppEntry[] = [];
1170
+ #isSubApp = false;
1171
+ #latestUpdate?: Promise<void>;
1172
+
1173
+ constructor(storagePanel: ResourcesPanel) {
1174
+ super(storagePanel, i18nString(UIStrings.subApps), false, 'sub-apps');
1175
+ const icon = createIcon('table');
1176
+ this.setLeadingIcons([icon]);
1177
+ this.hidden = true;
1178
+ }
1179
+
1180
+ override get itemURL(): Platform.DevToolsPath.UrlString {
1181
+ return 'sub-apps://' as Platform.DevToolsPath.UrlString;
1182
+ }
1183
+
1184
+ override onselect(selectedByUser?: boolean): boolean {
1185
+ super.onselect(selectedByUser);
1186
+ if (!this.#view) {
1187
+ this.#view = new SubAppsView();
1188
+ }
1189
+ this.#view.subApps = this.#subApps;
1190
+ this.#view.isSiblingSubApps = this.#isSubApp;
1191
+ this.showView(this.#view);
1192
+ UI.UIUserMetrics.UIUserMetrics.instance().panelShown('sub-apps');
1193
+ return false;
1194
+ }
1195
+
1196
+ /**
1197
+ * Fetches the Sub Apps of the inspected app, or its Sibling Sub Apps when
1198
+ * the inspected app is itself a Sub App. Only the most recent update is
1199
+ * applied; results of superseded updates are dropped.
1200
+ */
1201
+ update(resourceTreeModel: SDK.ResourceTreeModel.ResourceTreeModel): Promise<void> {
1202
+ const subAppsFetch = Promise.all([resourceTreeModel.getSubApps(), resourceTreeModel.getSiblingSubApps()]);
1203
+ const update: Promise<void> = subAppsFetch.then(([subApps, siblingSubApps]) => {
1204
+ if (update !== this.#latestUpdate) {
1205
+ return;
1206
+ }
1207
+ if (siblingSubApps.length > 0) {
1208
+ this.#setSubApps(siblingSubApps, /* isSubApp= */ true);
1209
+ } else {
1210
+ this.#setSubApps(subApps, /* isSubApp= */ false);
1211
+ }
1212
+ });
1213
+ this.#latestUpdate = update;
1214
+ return update;
1215
+ }
1216
+
1217
+ get latestUpdateForTest(): Promise<void>|undefined {
1218
+ return this.#latestUpdate;
1219
+ }
1220
+
1221
+ #setSubApps(subApps: SubAppEntry[], isSubApp: boolean): void {
1222
+ this.#subApps = subApps;
1223
+ this.#isSubApp = isSubApp;
1224
+ this.hidden = subApps.length === 0;
1225
+ const title = isSubApp ? i18nString(UIStrings.siblingSubApps) : i18nString(UIStrings.subApps);
1226
+ this.title = title;
1227
+ UI.ARIAUtils.setLabel(this.listItemElement, title);
1228
+ if (this.#view) {
1229
+ this.#view.subApps = this.#subApps;
1230
+ this.#view.isSiblingSubApps = this.#isSubApp;
1231
+ }
1232
+ }
1233
+
1234
+ getSubApps(): SubAppEntry[] {
1235
+ return this.#subApps;
1236
+ }
1237
+ }
1238
+
1149
1239
  export class StorageTreeElement extends ApplicationPanelTreeElement {
1150
1240
  private view?: StorageView;
1151
1241
  constructor(storagePanel: ResourcesPanel) {