chrome-devtools-frontend 1.0.1661063 → 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 (131) hide show
  1. package/.agents/skills/devtools-unicode-escaping/SKILL.md +64 -0
  2. package/.agents/skills/evaluate-ai-css-completion/SKILL.md +19 -9
  3. package/.agents/skills/evaluate-ai-css-completion/scripts/evaluate.js +44 -16
  4. package/.agents/skills/migrate-chromium-test/SKILL.md +32 -2
  5. package/.agents/skills/ui-eng-vision-local-lit-renderer/SKILL.md +7 -1
  6. package/docs/ui_engineering.md +75 -42
  7. package/front_end/core/common/SettingRegistration.ts +0 -10
  8. package/front_end/core/common/Settings.ts +69 -3
  9. package/front_end/core/platform/ArrayUtilities.ts +10 -0
  10. package/front_end/core/platform/StringUtilities.ts +38 -6
  11. package/front_end/core/sdk/ServiceWorkerCacheModel.ts +0 -10
  12. package/front_end/core/sdk/StorageBucketsModel.ts +0 -10
  13. package/front_end/core/sdk/sdk-meta.ts +0 -1
  14. package/front_end/entrypoints/inspector_main/OutermostTargetSelector.ts +0 -13
  15. package/front_end/entrypoints/inspector_main/RenderingOptions.ts +8 -3
  16. package/front_end/entrypoints/inspector_main/inspector_main-meta.ts +3 -3
  17. package/front_end/entrypoints/main/MainImpl.ts +7 -2
  18. package/front_end/foundation/README.md +86 -0
  19. package/front_end/foundation/Universe.ts +16 -0
  20. package/front_end/generated/InspectorBackendCommands.ts +6 -19
  21. package/front_end/generated/protocol-mapping.d.ts +0 -53
  22. package/front_end/generated/protocol-proxy-api.d.ts +0 -46
  23. package/front_end/generated/protocol.ts +20 -186
  24. package/front_end/models/ai_assistance/AiAgent2.ts +3 -3
  25. package/front_end/models/ai_assistance/AiConversation.ts +21 -10
  26. package/front_end/models/ai_assistance/AiHistoryStorage.ts +21 -10
  27. package/front_end/models/ai_assistance/ChangeManager.ts +2 -2
  28. package/front_end/models/ai_assistance/ExtensionScope.ts +6 -2
  29. package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +1 -1
  30. package/front_end/models/ai_assistance/agents/AiAgent.ts +8 -1
  31. package/front_end/models/ai_assistance/agents/StylingAgent.ts +1 -1
  32. package/front_end/models/badges/UserBadges.ts +31 -17
  33. package/front_end/models/bindings/CompilerScriptMapping.ts +5 -4
  34. package/front_end/models/bindings/ContentProviderBasedProject.ts +2 -3
  35. package/front_end/models/bindings/DebuggerLanguagePlugins.ts +4 -3
  36. package/front_end/models/bindings/DefaultScriptMapping.ts +3 -3
  37. package/front_end/models/bindings/NetworkProject.ts +22 -1
  38. package/front_end/models/bindings/ResourceMapping.ts +4 -3
  39. package/front_end/models/bindings/ResourceScriptMapping.ts +2 -1
  40. package/front_end/models/bindings/SASSSourceMapping.ts +4 -3
  41. package/front_end/models/bindings/StylesSourceMapping.ts +3 -3
  42. package/front_end/models/emulation/DeviceModeModel.ts +67 -5
  43. package/front_end/models/extensions/RecorderExtensionEndpoint.ts +7 -5
  44. package/front_end/models/extensions/RecorderPluginManager.ts +10 -6
  45. package/front_end/models/har/Log.snapshot.txt +193 -0
  46. package/front_end/models/har/Log.ts +3 -2
  47. package/front_end/models/heap_snapshot/HeapSnapshotProxy.ts +14 -5
  48. package/front_end/models/issues_manager/CheckFormsIssuesTrigger.ts +8 -9
  49. package/front_end/models/issues_manager/RelatedIssue.ts +10 -8
  50. package/front_end/models/javascript_metadata/NativeFunctions.js +1 -1
  51. package/front_end/models/logs/RequestResolver.ts +2 -2
  52. package/front_end/models/persistence/AutomaticFileSystemWorkspaceBinding.ts +0 -5
  53. package/front_end/models/source_map_scopes/FunctionCodeResolver.ts +8 -6
  54. package/front_end/models/source_map_scopes/NamesResolver.ts +36 -30
  55. package/front_end/models/stack_trace/StackTrace.ts +17 -0
  56. package/front_end/models/trace/handlers/FramesHandler.ts +4 -7
  57. package/front_end/models/trace/handlers/ModelHandlers.ts +0 -1
  58. package/front_end/models/trace/handlers/RendererHandler.ts +2 -15
  59. package/front_end/models/trace/handlers/Threads.ts +4 -11
  60. package/front_end/models/trace/insights/DOMSize.ts +1 -1
  61. package/front_end/models/workspace/WorkspaceImpl.ts +1 -10
  62. package/front_end/panels/application/ApplicationPanelSidebar.ts +0 -44
  63. package/front_end/panels/application/CookieItemsView.ts +1 -1
  64. package/front_end/panels/application/FrameDetailsView.ts +1 -1
  65. package/front_end/panels/application/IndexedDBModel.ts +0 -10
  66. package/front_end/panels/application/IndexedDBViews.ts +240 -164
  67. package/front_end/panels/application/ServiceWorkersView.ts +494 -411
  68. package/front_end/panels/application/SharedStorageModel.ts +0 -10
  69. package/front_end/panels/application/application.ts +0 -6
  70. package/front_end/panels/application/components/AdsView.ts +23 -0
  71. package/front_end/panels/application/components/SharedStorageAccessGrid.ts +1 -1
  72. package/front_end/panels/application/components/adsView.css +21 -1
  73. package/front_end/panels/application/components/components.ts +0 -2
  74. package/front_end/panels/application/indexedDBViews.css +1 -1
  75. package/front_end/panels/application/preloading/components/PreloadingDetailsReportView.ts +1 -1
  76. package/front_end/panels/application/serviceWorkersView.css +40 -4
  77. package/front_end/panels/browser_debugger/XHRBreakpointsSidebarPane.ts +147 -123
  78. package/front_end/panels/common/ExtensionServer.ts +3 -3
  79. package/front_end/panels/console/ConsoleView.ts +1 -1
  80. package/front_end/panels/emulation/DeviceModeView.ts +139 -70
  81. package/front_end/panels/issues/AffectedResourcesView.ts +1 -1
  82. package/front_end/panels/lighthouse/LighthouseProtocolService.ts +53 -12
  83. package/front_end/panels/network/NetworkDataGridNode.ts +43 -1
  84. package/front_end/panels/network/RequestInitiatorView.ts +24 -2
  85. package/front_end/panels/network/RequestPayloadView.ts +77 -39
  86. package/front_end/panels/network/components/ResponseHeaderSection.ts +5 -3
  87. package/front_end/panels/network/networkLogView.css +5 -0
  88. package/front_end/panels/network/requestInitiatorViewTree.css +5 -0
  89. package/front_end/panels/profiler/HeapProfileView.ts +198 -79
  90. package/front_end/panels/profiler/HeapSnapshotView.ts +2 -2
  91. package/front_end/panels/recorder/README.md +1 -2
  92. package/front_end/panels/recorder/RecorderPanel.ts +1749 -17
  93. package/front_end/panels/recorder/recorder.ts +0 -2
  94. package/front_end/panels/settings/AISettingsTab.ts +13 -0
  95. package/front_end/panels/sources/SourcesNavigator.ts +3 -2
  96. package/front_end/panels/sources/SourcesView.ts +0 -17
  97. package/front_end/panels/sources/UISourceCodeFrame.ts +42 -0
  98. package/front_end/panels/sources/sources.ts +0 -2
  99. package/front_end/panels/timeline/CompatibilityTracksAppender.ts +0 -17
  100. package/front_end/panels/timeline/ThreadAppender.ts +0 -106
  101. package/front_end/third_party/chromium/README.chromium +1 -1
  102. package/front_end/ui/components/report_view/ReportView.ts +11 -5
  103. package/front_end/ui/components/report_view/report.css +4 -1
  104. package/front_end/ui/components/spinners/Spinner.ts +29 -32
  105. package/front_end/ui/components/spinners/spinner.css +32 -84
  106. package/front_end/ui/legacy/TextPrompt.ts +49 -1
  107. package/front_end/ui/legacy/Treeoutline.ts +14 -6
  108. package/front_end/ui/legacy/UIUtils.ts +36 -11
  109. package/front_end/ui/legacy/Widget.ts +17 -10
  110. package/front_end/ui/legacy/components/cookie_table/CookiesTable.ts +2 -2
  111. package/front_end/ui/legacy/components/data_grid/DataGridElement.ts +36 -7
  112. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +63 -68
  113. package/front_end/ui/legacy/components/object_ui/RemoteObjectPreviewFormatter.ts +1 -1
  114. package/front_end/ui/legacy/components/object_ui/objectValue.css +2 -1
  115. package/front_end/ui/legacy/inspectorCommon.css +4 -4
  116. package/front_end/ui/legacy/textPrompt.css +10 -0
  117. package/front_end/ui/lit/lit.ts +1 -0
  118. package/front_end/ui/lit/render.ts +44 -10
  119. package/front_end/ui/lit/strip-whitespace.ts +23 -2
  120. package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
  121. package/package.json +1 -1
  122. package/front_end/models/trace/handlers/AuctionWorkletsHandler.ts +0 -183
  123. package/front_end/panels/application/InterestGroupStorageModel.ts +0 -81
  124. package/front_end/panels/application/InterestGroupStorageView.ts +0 -151
  125. package/front_end/panels/application/InterestGroupTreeElement.ts +0 -65
  126. package/front_end/panels/application/components/InterestGroupAccessGrid.ts +0 -135
  127. package/front_end/panels/application/components/interestGroupAccessGrid.css +0 -27
  128. package/front_end/panels/application/interestGroupStorageView.css +0 -9
  129. package/front_end/panels/recorder/RecorderController.ts +0 -1595
  130. package/front_end/panels/sources/InplaceFormatterEditorAction.ts +0 -149
  131. /package/front_end/panels/recorder/{recorderController.css → recorderPanel.css} +0 -0
@@ -113,9 +113,17 @@ export class DeviceModeView extends UI.Widget.VBox {
113
113
  this.showRulersSetting = Common.Settings.Settings.instance().moduleSetting('emulation.show-rulers');
114
114
  this.showRulersSetting.addChangeListener(this.updateUI, this);
115
115
 
116
- this.topRuler = new Ruler(true, this.model.setWidthAndScaleToFit.bind(this.model));
116
+ this.topRuler = new Ruler();
117
+ this.topRuler.horizontal = true;
118
+ this.topRuler.addEventListener(RulerEvents.MARKER_SELECTED, event => {
119
+ this.model.setWidthAndScaleToFit(event.data);
120
+ });
117
121
  this.topRuler.element.classList.add('device-mode-ruler-top');
118
- this.leftRuler = new Ruler(false, this.model.setHeightAndScaleToFit.bind(this.model));
122
+ this.leftRuler = new Ruler();
123
+ this.leftRuler.horizontal = false;
124
+ this.leftRuler.addEventListener(RulerEvents.MARKER_SELECTED, event => {
125
+ this.model.setHeightAndScaleToFit(event.data);
126
+ });
119
127
  this.leftRuler.element.classList.add('device-mode-ruler-left');
120
128
  this.createUI();
121
129
  UI.ZoomManager.ZoomManager.instance().addEventListener(UI.ZoomManager.Events.ZOOM_CHANGED, this.zoomChanged, this);
@@ -566,86 +574,147 @@ export class DeviceModeView extends UI.Widget.VBox {
566
574
  }
567
575
  }
568
576
 
569
- export class Ruler extends UI.Widget.VBox {
570
- #contentElement: HTMLElement;
571
- private readonly horizontal: boolean;
572
- private scale: number;
573
- private readonly throttler: Common.Throttler.Throttler;
574
- private readonly applyCallback: (arg0: number) => void;
575
- constructor(horizontal: boolean, applyCallback: (arg0: number) => void) {
576
- super({jslog: `${VisualLogging.deviceModeRuler().track({click: true})}`});
577
- this.element.classList.add('device-mode-ruler');
578
- this.#contentElement =
579
- this.element.createChild('div', 'device-mode-ruler-content').createChild('div', 'device-mode-ruler-inner');
580
- this.horizontal = horizontal;
581
- this.scale = 1;
582
- this.throttler = new Common.Throttler.Throttler(0);
583
- this.applyCallback = applyCallback;
577
+ export interface RulerViewInput {
578
+ horizontal: boolean;
579
+ scale: number;
580
+ onMarkerClick: (size: number) => void;
581
+ }
582
+
583
+ export type RulerView = (input: RulerViewInput, output: undefined, target: HTMLElement) => void;
584
+
585
+ export const DEFAULT_RULER_VIEW: RulerView = (input: RulerViewInput, output: undefined, target: HTMLElement): void => {
586
+ const zoomFactor = UI.ZoomManager.ZoomManager.instance().zoomFactor();
587
+ const size = input.horizontal ? target.offsetWidth : target.offsetHeight;
588
+
589
+ const dipSize = size * zoomFactor / input.scale;
590
+ const count = Math.ceil(dipSize / 5);
591
+ let step = 1;
592
+ if (input.scale < 0.8) {
593
+ step = 2;
594
+ }
595
+ if (input.scale < 0.6) {
596
+ step = 4;
597
+ }
598
+ if (input.scale < 0.4) {
599
+ step = 8;
600
+ }
601
+ if (input.scale < 0.2) {
602
+ step = 16;
603
+ }
604
+ if (input.scale < 0.1) {
605
+ step = 32;
584
606
  }
585
607
 
586
- render(scale: number): void {
587
- this.scale = scale;
588
- void this.throttler.schedule(this.update.bind(this));
608
+ const markers = [];
609
+ for (let i = 0; i < count; i++) {
610
+ if (i % step) {
611
+ continue;
612
+ }
613
+ const isLarge = !(i % 10);
614
+ const isMedium = !(i % 5);
615
+ const offset = i ? `${(5 * i) * input.scale / zoomFactor}px` : undefined;
616
+
617
+ // clang-format off
618
+ markers.push(html`
619
+ <div
620
+ class=${classMap({
621
+ 'device-mode-ruler-marker': true,
622
+ 'device-mode-ruler-marker-large': isLarge,
623
+ 'device-mode-ruler-marker-medium': isMedium && !isLarge,
624
+ })}
625
+ style=${styleMap(input.horizontal ? {left: offset} : {top: offset})}>
626
+ ${i && !(i % 20) ?
627
+ html`<div class="device-mode-ruler-text" @click=${() => input.onMarkerClick(i * 5)}>${i * 5}</div>` :
628
+ nothing}
629
+ </div>
630
+ `);
631
+ // clang-format on
589
632
  }
590
633
 
591
- override onResize(): void {
592
- void this.throttler.schedule(this.update.bind(this));
634
+ render(html`
635
+ <div class="device-mode-ruler-content">
636
+ <div class="device-mode-ruler-inner">
637
+ ${markers}
638
+ </div>
639
+ </div>
640
+ `,
641
+ target, {
642
+ container: {
643
+ classes: ['device-mode-ruler'],
644
+ attributes: {jslog: VisualLogging.deviceModeRuler().track({click: true})},
645
+ },
646
+ });
647
+ };
648
+
649
+ export const enum RulerEvents {
650
+ MARKER_SELECTED = 'MarkerSelected',
651
+ }
652
+
653
+ export interface RulerEventTypes {
654
+ [RulerEvents.MARKER_SELECTED]: number;
655
+ }
656
+
657
+ export class Ruler extends Common.ObjectWrapper.eventMixin<RulerEventTypes, typeof UI.Widget.Widget>(UI.Widget.Widget) {
658
+ #view: RulerView;
659
+ #horizontal = true;
660
+ #scale = 1;
661
+
662
+ constructor(element?: HTMLElement, view: RulerView = DEFAULT_RULER_VIEW) {
663
+ super(element);
664
+ this.#view = view;
593
665
  }
594
666
 
595
- update(): void {
596
- const zoomFactor = UI.ZoomManager.ZoomManager.instance().zoomFactor();
597
- const size = this.horizontal ? this.#contentElement.offsetWidth : this.#contentElement.offsetHeight;
667
+ get horizontal(): boolean {
668
+ return this.#horizontal;
669
+ }
598
670
 
599
- const dipSize = size * zoomFactor / this.scale;
600
- const count = Math.ceil(dipSize / 5);
601
- let step = 1;
602
- if (this.scale < 0.8) {
603
- step = 2;
604
- }
605
- if (this.scale < 0.6) {
606
- step = 4;
607
- }
608
- if (this.scale < 0.4) {
609
- step = 8;
610
- }
611
- if (this.scale < 0.2) {
612
- step = 16;
613
- }
614
- if (this.scale < 0.1) {
615
- step = 32;
671
+ set horizontal(horizontal: boolean) {
672
+ if (this.#horizontal === horizontal) {
673
+ return;
616
674
  }
675
+ this.#horizontal = horizontal;
676
+ this.requestUpdate();
677
+ }
617
678
 
618
- const markers = [];
619
- for (let i = 0; i < count; i++) {
620
- if (i % step) {
621
- continue;
622
- }
623
- const isLarge = !(i % 10);
624
- const isMedium = !(i % 5);
625
- const offset = i ? `${(5 * i) * this.scale / zoomFactor}px` : undefined;
626
-
627
- // clang-format off
628
- markers.push(html`
629
- <div
630
- class=${classMap({
631
- 'device-mode-ruler-marker': true,
632
- 'device-mode-ruler-marker-large': isLarge,
633
- 'device-mode-ruler-marker-medium': isMedium && !isLarge,
634
- })}
635
- style=${styleMap(this.horizontal ? {left: offset} : {top: offset})}>
636
- ${i && !(i % 20) ?
637
- html`<div class="device-mode-ruler-text" @click=${() => this.onMarkerClick(i * 5)}>${i * 5}</div>` :
638
- nothing}
639
- </div>
640
- `);
641
- // clang-format on
679
+ get scale(): number {
680
+ return this.#scale;
681
+ }
682
+
683
+ set scale(scale: number) {
684
+ if (this.#scale === scale) {
685
+ return;
642
686
  }
687
+ this.#scale = scale;
688
+ this.requestUpdate();
689
+ }
643
690
 
644
- // eslint-disable-next-line @devtools/no-lit-render-outside-of-view
645
- render(html`${markers}`, this.#contentElement);
691
+ render(scale: number): void {
692
+ this.scale = scale;
646
693
  }
647
694
 
648
- private onMarkerClick(size: number): void {
649
- this.applyCallback.call(null, size);
695
+ override wasShown(): void {
696
+ super.wasShown();
697
+ this.requestUpdate();
698
+ }
699
+
700
+ override onResize(): void {
701
+ super.onResize();
702
+ this.requestUpdate();
703
+ }
704
+
705
+ #onMarkerClick = (size: number): void => {
706
+ this.dispatchEventToListeners(RulerEvents.MARKER_SELECTED, size);
707
+ };
708
+
709
+ override performUpdate(): void {
710
+ if (!this.isShowing()) {
711
+ return;
712
+ }
713
+ const viewInput: RulerViewInput = {
714
+ horizontal: this.#horizontal,
715
+ scale: this.#scale,
716
+ onMarkerClick: this.#onMarkerClick,
717
+ };
718
+ this.#view(viewInput, undefined, this.contentElement);
650
719
  }
651
720
  }
@@ -84,7 +84,7 @@ export abstract class AffectedResourcesView extends UI.TreeOutline.TreeElement {
84
84
 
85
85
  this.affectedResources = this.createAffectedResources();
86
86
  this.#affectedResourcesCount = 0;
87
- this.requestResolver = new Logs.RequestResolver.RequestResolver();
87
+ this.requestResolver = new Logs.RequestResolver.RequestResolver(Logs.NetworkLog.NetworkLog.instance());
88
88
  this.#frameListeners = [];
89
89
  this.#unresolvedFrameIds = new Set();
90
90
  }
@@ -5,7 +5,7 @@
5
5
  import * as Common from '../../core/common/common.js';
6
6
  import * as i18n from '../../core/i18n/i18n.js';
7
7
  import type * as Platform from '../../core/platform/platform.js';
8
- import type * as ProtocolClient from '../../core/protocol_client/protocol_client.js';
8
+ import * as ProtocolClient from '../../core/protocol_client/protocol_client.js';
9
9
  import * as SDK from '../../core/sdk/sdk.js';
10
10
  import type * as Protocol from '../../generated/protocol.js';
11
11
  import type * as LighthouseModel from '../../models/lighthouse/lighthouse.js';
@@ -78,6 +78,14 @@ export class ProtocolService implements ProtocolClient.CDPConnection.CDPConnecti
78
78
  private configForTesting?: object;
79
79
  private connection?: ProtocolClient.CDPConnection.CDPConnection;
80
80
 
81
+ /**
82
+ * Session ids that belong to this Lighthouse run. The proxy only relays
83
+ * worker commands and connection events that target one of these sessions
84
+ * so that traffic stays scoped to the parallel session created in
85
+ * `attach()` and its descendants.
86
+ */
87
+ readonly #knownSessionIds = new Set<string>();
88
+
81
89
  /**
82
90
  * Tracks pending requests to the Lighthouse worker.
83
91
  * Key: The message ID sent to the worker.
@@ -119,6 +127,8 @@ export class ProtocolService implements ProtocolClient.CDPConnection.CDPConnecti
119
127
 
120
128
  const rootTargetId = await rootChildTargetManager.getParentTargetId();
121
129
  const {sessionId} = await rootTarget.targetAgent().invoke_attachToTarget({targetId: rootTargetId, flatten: true});
130
+ this.#knownSessionIds.clear();
131
+ this.#knownSessionIds.add(sessionId);
122
132
  this.connection = connection;
123
133
  this.connection.observe(this);
124
134
 
@@ -217,6 +227,7 @@ export class ProtocolService implements ProtocolClient.CDPConnection.CDPConnecti
217
227
  this.rootTarget = undefined;
218
228
  this.connection?.unobserve(this);
219
229
  this.connection = undefined;
230
+ this.#knownSessionIds.clear();
220
231
 
221
232
  if (oldLighthouseWorker) {
222
233
  (await oldLighthouseWorker).terminate();
@@ -237,18 +248,29 @@ export class ProtocolService implements ProtocolClient.CDPConnection.CDPConnecti
237
248
  }
238
249
 
239
250
  private dispatchProtocolMessage(message: ProtocolClient.CDPConnection.CDPReceivableMessage): void {
240
- // A message without a sessionId is the main session of the main target (call it "Main session").
241
- // A parallel connection and session was made that connects to the same main target (call it "Lighthouse session").
242
- // Messages from the "Lighthouse session" have a sessionId.
243
- // Without some care, there is a risk of sending the same events for the same main frame to Lighthouse–the backend
244
- // will create events for the "Main session" and the "Lighthouse session".
245
- // The workaround–only send message to Lighthouse if:
246
- // * the message has a sessionId (is not for the "Main session")
247
- // * the message does not have a sessionId (is for the "Main session"), but only for the Target domain
248
- // (to kickstart autoAttach in LH).
249
- if (message.sessionId || ('method' in message && message.method?.startsWith('Target'))) {
250
- void this.send('dispatchProtocolMessage', {message});
251
+ // The shared CDPConnection carries traffic for every DevTools session. A
252
+ // parallel session is created for the Lighthouse run in `attach()`, and the
253
+ // worker only ever needs traffic that belongs to that session or one of the
254
+ // child sessions it subsequently attaches. Filtering on `#knownSessionIds`
255
+ // keeps the proxy scoped to the run instead of broadcasting unrelated
256
+ // session traffic into the worker.
257
+ if (!message.sessionId || !this.#knownSessionIds.has(message.sessionId)) {
258
+ return;
259
+ }
260
+ if ('method' in message) {
261
+ if (message.method === 'Target.attachedToTarget') {
262
+ const childSessionId = (message.params as Protocol.Target.AttachedToTargetEvent | undefined)?.sessionId;
263
+ if (childSessionId) {
264
+ this.#knownSessionIds.add(childSessionId);
265
+ }
266
+ } else if (message.method === 'Target.detachedFromTarget') {
267
+ const childSessionId = (message.params as Protocol.Target.DetachedFromTargetEvent | undefined)?.sessionId;
268
+ if (childSessionId) {
269
+ this.#knownSessionIds.delete(childSessionId);
270
+ }
271
+ }
251
272
  }
273
+ void this.send('dispatchProtocolMessage', {message});
252
274
  }
253
275
 
254
276
  onDisconnect(): void {
@@ -303,11 +325,30 @@ export class ProtocolService implements ProtocolClient.CDPConnection.CDPConnecti
303
325
 
304
326
  private sendProtocolMessage(message: string): void {
305
327
  const {id, method, params, sessionId} = JSON.parse(message);
328
+ // The worker is expected to only address the parallel session created in
329
+ // `attach()` or sessions discovered beneath it. Anything else, including
330
+ // the root session, is outside the scope of the run.
331
+ if (!sessionId || !this.#knownSessionIds.has(sessionId)) {
332
+ void this.send('dispatchProtocolMessage', {
333
+ message: {
334
+ id,
335
+ sessionId,
336
+ error: {
337
+ code: ProtocolClient.CDPConnection.CDPErrorStatus.SESSION_NOT_FOUND,
338
+ message: `Unknown session id: ${sessionId}`,
339
+ },
340
+ },
341
+ });
342
+ return;
343
+ }
306
344
  // CDPConnection manages it's own message IDs and it's important, otherwise we'd clash
307
345
  // with the rest of the DevTools traffic.
308
346
  // Instead, we ignore the ID coming from the worker when sending the command, but
309
347
  // patch it back in when sending the response back to the worker.
310
348
  void this.connection?.send(method, params, sessionId).then(response => {
349
+ if ('result' in response && method === 'Target.attachToTarget' && response.result?.sessionId) {
350
+ this.#knownSessionIds.add(response.result.sessionId);
351
+ }
311
352
  const message =
312
353
  'result' in response ? {id, sessionId, result: response.result} : {id, sessionId, error: response.error};
313
354
  this.dispatchProtocolMessage(message);
@@ -47,6 +47,7 @@ import * as Bindings from '../../models/bindings/bindings.js';
47
47
  import type * as HAR from '../../models/har/har.js';
48
48
  import * as Logs from '../../models/logs/logs.js';
49
49
  import type * as NetworkTimeCalculator from '../../models/network_time_calculator/network_time_calculator.js';
50
+ import * as StackTrace from '../../models/stack_trace/stack_trace.js';
50
51
  import * as NetworkForward from '../../panels/network/forward/forward.js';
51
52
  import * as Buttons from '../../ui/components/buttons/buttons.js';
52
53
  import {createIcon} from '../../ui/kit/kit.js';
@@ -343,6 +344,16 @@ const UIStrings = {
343
344
  * @description Text in Network Data Grid Node of the Network panel. Noun. Refers to a potentially blocking resource.
344
345
  */
345
346
  potentiallyBlocking: 'Potentially blocking',
347
+ /**
348
+ * @description Text indicating a request originated from the DevTools console. Used as a subtitle in the
349
+ * Initiator column.
350
+ */
351
+ console: 'Console',
352
+ /**
353
+ * @description Tooltip for the console icon in the Network panel, indicating a request was initiated
354
+ * from the Console.
355
+ */
356
+ requestOriginatedFromConsole: 'Request originated from Console',
346
357
 
347
358
  } as const;
348
359
  const str_ = i18n.i18n.registerUIStrings('panels/network/NetworkDataGridNode.ts', UIStrings);
@@ -1009,6 +1020,29 @@ export class NetworkRequestNode extends NetworkNode {
1009
1020
  return aValue.localeCompare(bValue) || aRequest.identityCompare(bRequest);
1010
1021
  }
1011
1022
 
1023
+ static isConsoleOriginated(request: SDK.NetworkRequest.NetworkRequest): boolean {
1024
+ const resourceType = request.resourceType();
1025
+ if (resourceType !== Common.ResourceType.resourceTypes.Fetch &&
1026
+ resourceType !== Common.ResourceType.resourceTypes.XHR) {
1027
+ return false;
1028
+ }
1029
+ const initiator = request.initiator();
1030
+ if (!initiator || initiator.type !== Protocol.Network.InitiatorType.Script) {
1031
+ return false;
1032
+ }
1033
+ if (initiator.url) {
1034
+ return false;
1035
+ }
1036
+ if (!initiator.stack) {
1037
+ return false;
1038
+ }
1039
+ return StackTrace.StackTrace.isConsoleOriginated(initiator.stack);
1040
+ }
1041
+
1042
+ private isConsoleOriginated(): boolean {
1043
+ return NetworkRequestNode.isConsoleOriginated(this.requestInternal);
1044
+ }
1045
+
1012
1046
  override showingInitiatorChainChanged(): void {
1013
1047
  const showInitiatorChain = this.showingInitiatorChain();
1014
1048
 
@@ -1340,6 +1374,13 @@ export class NetworkRequestNode extends NetworkNode {
1340
1374
  // eslint-disable-next-line @devtools/no-lit-render-outside-of-view
1341
1375
  render(iconElement, cell);
1342
1376
 
1377
+ // render Console icon if this request originated from Console
1378
+ if (this.isConsoleOriginated()) {
1379
+ const consoleIcon = createIcon('terminal', 'network-console-icon');
1380
+ UI.Tooltip.Tooltip.install(consoleIcon, i18nString(UIStrings.requestOriginatedFromConsole));
1381
+ cell.appendChild(consoleIcon);
1382
+ }
1383
+
1343
1384
  // render Ask AI button
1344
1385
  const aiButtonContainer = this.createAiButtonIfAvailable();
1345
1386
  if (aiButtonContainer) {
@@ -1590,7 +1631,8 @@ export class NetworkRequestNode extends NetworkNode {
1590
1631
  }
1591
1632
  UI.Tooltip.Tooltip.install((this.linkifiedInitiatorAnchor), '');
1592
1633
  cell.appendChild(this.linkifiedInitiatorAnchor);
1593
- this.appendSubtitle(cell, i18nString(UIStrings.script));
1634
+ this.appendSubtitle(cell,
1635
+ this.isConsoleOriginated() ? i18nString(UIStrings.console) : i18nString(UIStrings.script));
1594
1636
  cell.classList.add('network-script-initiated');
1595
1637
  break;
1596
1638
  }
@@ -12,6 +12,7 @@ import * as UI from '../../ui/legacy/legacy.js';
12
12
  import {Directives, html, type LitTemplate, nothing, render, type TemplateResult} from '../../ui/lit/lit.js';
13
13
  import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
14
14
 
15
+ import {NetworkRequestNode} from './NetworkDataGridNode.js';
15
16
  import requestInitiatorViewStyles from './requestInitiatorView.css.js';
16
17
  import requestInitiatorViewTreeStyles from './requestInitiatorViewTree.css.js';
17
18
 
@@ -30,6 +31,10 @@ const UIStrings = {
30
31
  * @description Title of a section in Request Initiator view of the Network Panel
31
32
  */
32
33
  requestInitiatorChain: 'Request initiator chain',
34
+ /**
35
+ * @description Label shown in the initiator chain when a request was initiated from the Console.
36
+ */
37
+ console: 'Console',
33
38
  } as const;
34
39
  const str_ = i18n.i18n.registerUIStrings('panels/network/RequestInitiatorView.ts', UIStrings);
35
40
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -51,6 +56,7 @@ export interface ViewInput {
51
56
  initiatorGraph: Logs.NetworkLog.InitiatorGraph;
52
57
  stackTrace: StackTrace.StackTrace.StackTrace|null;
53
58
  request: SDK.NetworkRequest.NetworkRequest;
59
+ isConsoleOriginated: boolean;
54
60
  }
55
61
 
56
62
  export const DEFAULT_VIEW = (input: ViewInput, _output: undefined, target: HTMLElement): void => {
@@ -83,6 +89,11 @@ export const DEFAULT_VIEW = (input: ViewInput, _output: undefined, target: HTMLE
83
89
  stackTrace: input.stackTrace,
84
90
  })}
85
91
  </li>
92
+ ${input.isConsoleOriginated ? html`
93
+ <li role="treeitem" class="console-origin-label">
94
+ ${i18nString(UIStrings.console)}
95
+ </li>
96
+ ` : nothing}
86
97
  </ul>
87
98
  </li>
88
99
  `;
@@ -167,13 +178,21 @@ export const DEFAULT_VIEW = (input: ViewInput, _output: undefined, target: HTMLE
167
178
  ${i18nString(UIStrings.requestInitiatorChain)}
168
179
  ${hasInitiatorChain ? html`
169
180
  <ul role="group">
170
- ${renderInitiatorNodes(initiators, 0, initiatorGraph.initiated, visited)}
181
+ ${input.isConsoleOriginated ? html`
182
+ <li role="treeitem" aria-expanded="true" open>
183
+ <span>${i18nString(UIStrings.console)}</span>
184
+ <ul role="group">
185
+ ${renderInitiatorNodes(initiators, 0, initiatorGraph.initiated, visited)}
186
+ </ul>
187
+ </li>` :
188
+ renderInitiatorNodes(initiators, 0, initiatorGraph.initiated, visited)}
171
189
  </ul>` : nothing}
172
190
  </li>`;
173
191
  // clang-format on
174
192
  };
175
193
 
176
- const hasInitiatorChain = input.initiatorGraph.initiators.size > 1 || input.initiatorGraph.initiated.size > 1;
194
+ const hasInitiatorChain =
195
+ input.initiatorGraph.initiators.size > 1 || input.initiatorGraph.initiated.size > 1 || input.isConsoleOriginated;
177
196
 
178
197
  // clang-format off
179
198
  render(html`
@@ -242,10 +261,13 @@ export class RequestInitiatorView extends UI.Widget.VBox {
242
261
  .createStackTraceFromProtocolRuntime(rawStack, target);
243
262
  }
244
263
 
264
+ const isConsoleOriginated = NetworkRequestNode.isConsoleOriginated(this.request);
265
+
245
266
  const viewInput: ViewInput = {
246
267
  initiatorGraph,
247
268
  stackTrace,
248
269
  request: this.request,
270
+ isConsoleOriginated,
249
271
  };
250
272
 
251
273
  this.#view(viewInput, undefined, this.contentElement);