chrome-devtools-frontend 1.0.1666631 → 1.0.1667564

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 (78) hide show
  1. package/front_end/core/host/AidaClient.ts +6 -4
  2. package/front_end/core/host/InspectorFrontendHostAPI.ts +1 -0
  3. package/front_end/core/host/InspectorFrontendHostStub.ts +3 -3
  4. package/front_end/core/host/UserMetrics.ts +49 -1
  5. package/front_end/core/sdk/CSSMatchedStyles.ts +2 -5
  6. package/front_end/core/sdk/CSSPropertyParserMatchers.ts +12 -0
  7. package/front_end/core/sdk/Connections.ts +1 -1
  8. package/front_end/core/sdk/DOMModel.test.api.ts +104 -0
  9. package/front_end/core/sdk/DOMModel.ts +23 -11
  10. package/front_end/core/sdk/DebuggerModel.ts +9 -9
  11. package/front_end/core/sdk/IsolateManager.ts +1 -1
  12. package/front_end/core/sdk/NetworkManager.ts +105 -4
  13. package/front_end/core/sdk/OverlayModel.ts +15 -14
  14. package/front_end/core/sdk/RemoteObject.ts +1 -1
  15. package/front_end/devtools_compatibility.js +1 -0
  16. package/front_end/entrypoints/main/MainImpl.ts +5 -2
  17. package/front_end/entrypoints/main/SimpleApp.ts +9 -3
  18. package/front_end/foundation/Universe.ts +6 -0
  19. package/front_end/models/ai_assistance/contexts/PerformanceTraceContext.ts +23 -10
  20. package/front_end/models/computed_style/ComputedStyleModel.ts +4 -7
  21. package/front_end/models/crux-manager/CrUXManager.ts +4 -0
  22. package/front_end/models/extensions/ExtensionAPI.ts +3 -3
  23. package/front_end/models/heap_snapshot/HeapSnapshotProxy.ts +2 -2
  24. package/front_end/models/live-metrics/LiveMetrics.ts +59 -19
  25. package/front_end/models/live-metrics/web-vitals-injected/README.md +1 -1
  26. package/front_end/models/live-metrics/web-vitals-injected/spec/spec.ts +6 -1
  27. package/front_end/models/live-metrics/web-vitals-injected/web-vitals-injected.ts +11 -3
  28. package/front_end/models/logs/LogManager.ts +1 -1
  29. package/front_end/models/persistence/FileSystemWorkspaceBinding.ts +1 -1
  30. package/front_end/models/source_map_scopes/FunctionCodeResolver.ts +6 -7
  31. package/front_end/models/workspace_diff/WorkspaceDiff.ts +13 -14
  32. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +15 -7
  33. package/front_end/panels/application/BackgroundServiceView.ts +18 -20
  34. package/front_end/panels/autofill/AutofillView.ts +17 -17
  35. package/front_end/panels/autofill/autofillView.css +7 -8
  36. package/front_end/panels/browser_debugger/CategorizedBreakpointsSidebarPane.ts +31 -31
  37. package/front_end/panels/browser_debugger/DOMBreakpointsSidebarPane.ts +15 -15
  38. package/front_end/panels/browser_debugger/XHRBreakpointsSidebarPane.ts +12 -12
  39. package/front_end/panels/browser_debugger/browser_debugger-meta.ts +27 -27
  40. package/front_end/panels/common/AiCodeCompletionDisclaimer.ts +14 -6
  41. package/front_end/panels/common/AiCodeCompletionSummaryToolbar.ts +14 -6
  42. package/front_end/panels/common/AiCodeCompletionTeaser.ts +13 -6
  43. package/front_end/panels/common/GeminiRebrandPromoDialog.ts +1 -1
  44. package/front_end/panels/console/ConsoleInsightTeaser.ts +13 -6
  45. package/front_end/panels/console/ConsoleView.ts +6 -6
  46. package/front_end/panels/console_counters/WarningErrorCounter.ts +4 -4
  47. package/front_end/panels/coverage/CoverageModel.ts +11 -1
  48. package/front_end/panels/elements/StylePropertiesSection.ts +50 -17
  49. package/front_end/panels/elements/StylesAiCodeCompletionProvider.ts +18 -7
  50. package/front_end/panels/emulation/AdvancedApp.ts +18 -8
  51. package/front_end/panels/event_listeners/EventListenersView.ts +8 -8
  52. package/front_end/panels/explain/components/ConsoleInsight.ts +15 -7
  53. package/front_end/panels/network/NetworkLogView.ts +13 -14
  54. package/front_end/panels/screencast/ScreencastApp.ts +12 -6
  55. package/front_end/panels/screencast/ScreencastView.ts +37 -11
  56. package/front_end/panels/sensors/locationsSettingsTab.css +4 -0
  57. package/front_end/panels/settings/AISettingsTab.ts +13 -6
  58. package/front_end/panels/sources/DebuggerPlugin.ts +13 -2
  59. package/front_end/panels/timeline/components/LiveMetricsView.ts +14 -1
  60. package/front_end/panels/timeline/components/cpuThrottlingSelector.css +4 -0
  61. package/front_end/panels/timeline/components/liveMetricsView.css +28 -0
  62. package/front_end/panels/timeline/timeline-meta.ts +13 -0
  63. package/front_end/third_party/chromium/README.chromium +1 -1
  64. package/front_end/ui/components/menus/menu.css +4 -0
  65. package/front_end/ui/components/menus/menuItem.css +4 -0
  66. package/front_end/ui/components/menus/selectMenu.css +4 -0
  67. package/front_end/ui/components/menus/selectMenuButton.css +4 -0
  68. package/front_end/ui/components/text_editor/AiCodeCompletionProvider.ts +21 -10
  69. package/front_end/ui/components/text_editor/AiCodeGenerationProvider.ts +22 -10
  70. package/front_end/ui/components/text_editor/config.ts +7 -0
  71. package/front_end/ui/legacy/AppProvider.ts +2 -1
  72. package/front_end/ui/legacy/RootView.ts +8 -1
  73. package/front_end/ui/legacy/UniverseRequestEvent.ts +19 -0
  74. package/front_end/ui/legacy/Widget.ts +49 -6
  75. package/front_end/ui/legacy/inspectorCommon.css +1 -1
  76. package/front_end/ui/legacy/legacy.ts +2 -0
  77. package/front_end/ui/visual_logging/KnownContextValues.ts +2 -0
  78. package/package.json +1 -1
@@ -456,6 +456,10 @@ export class HostConfigTracker extends Common.ObjectWrapper.ObjectWrapper<EventT
456
456
  #pollTimer?: ReturnType<typeof setTimeout>;
457
457
  #aidaAvailability?: AidaAccessPreconditions;
458
458
 
459
+ get aidaAvailability(): AidaAccessPreconditions|undefined {
460
+ return this.#aidaAvailability;
461
+ }
462
+
459
463
  static instance({forceNew}: {
460
464
  forceNew: boolean,
461
465
  } = {forceNew: false}): HostConfigTracker {
@@ -507,9 +511,7 @@ export class HostConfigTracker extends Common.ObjectWrapper.ObjectWrapper<EventT
507
511
  const config =
508
512
  await new Promise<Root.Runtime.HostConfig>(resolve => InspectorFrontendHostInstance.getHostConfig(resolve));
509
513
  Object.assign(Root.Runtime.hostConfig, config);
510
- // TODO(crbug.com/442545623): Send `currentAidaAvailability` to the listeners as part of the event so that
511
- // `await AidaClient.checkAccessPreconditions()` does not need to be called again in the event handlers.
512
- this.dispatchEventToListeners(Events.AIDA_AVAILABILITY_CHANGED);
514
+ this.dispatchEventToListeners(Events.AIDA_AVAILABILITY_CHANGED, currentAidaAvailability);
513
515
  }
514
516
  }
515
517
  }
@@ -519,7 +521,7 @@ export const enum Events {
519
521
  }
520
522
 
521
523
  export interface EventTypes {
522
- [Events.AIDA_AVAILABILITY_CHANGED]: void;
524
+ [Events.AIDA_AVAILABILITY_CHANGED]: AidaAccessPreconditions;
523
525
  }
524
526
 
525
527
  export function isQuotaError(...inputs: Array<string|undefined>): boolean {
@@ -556,5 +556,6 @@ export const enum EnumeratedHistogram {
556
556
  LighthouseCategoryUsed = 'DevTools.LighthouseCategoryUsed',
557
557
  SwatchActivated = 'DevTools.SwatchActivated',
558
558
  BuiltInAiAvailability = 'DevTools.BuiltInAiAvailability',
559
+ ResendRequest = 'DevTools.ResendRequest',
559
560
  // LINT.ThenChange(/front_end/devtools_compatibility.js:EnumeratedHistogram)
560
561
  }
@@ -118,15 +118,15 @@ export class InspectorFrontendHostStub implements InspectorFrontendHostAPI {
118
118
  }
119
119
 
120
120
  setIsDocked(_isDocked: boolean, callback: () => void): void {
121
- window.setTimeout(callback, 0);
121
+ globalThis.setTimeout(callback, 0);
122
122
  }
123
123
 
124
124
  showSurvey(_trigger: string, callback: (arg0: ShowSurveyResult) => void): void {
125
- window.setTimeout(() => callback({surveyShown: false}), 0);
125
+ globalThis.setTimeout(() => callback({surveyShown: false}), 0);
126
126
  }
127
127
 
128
128
  canShowSurvey(_trigger: string, callback: (arg0: CanShowSurveyResult) => void): void {
129
- window.setTimeout(() => callback({canShowSurvey: false}), 0);
129
+ globalThis.setTimeout(() => callback({canShowSurvey: false}), 0);
130
130
  }
131
131
 
132
132
  /**
@@ -2,6 +2,8 @@
2
2
  // Use of this source code is governed by a BSD-style license that can be
3
3
  // found in the LICENSE file.
4
4
 
5
+ import * as Common from '../common/common.js';
6
+
5
7
  import {InspectorFrontendHostInstance} from './InspectorFrontendHost.js';
6
8
  import {EnumeratedHistogram} from './InspectorFrontendHostAPI.js';
7
9
 
@@ -29,6 +31,11 @@ export class UserMetrics {
29
31
  InspectorFrontendHostInstance.recordEnumeratedHistogram(EnumeratedHistogram.ActionTaken, action, Action.MAX_VALUE);
30
32
  }
31
33
 
34
+ resendRequest(resourceType: ResendRequestType): void {
35
+ InspectorFrontendHostInstance.recordEnumeratedHistogram(EnumeratedHistogram.ResendRequest, resourceType,
36
+ ResendRequestType.MAX_VALUE);
37
+ }
38
+
32
39
  keybindSetSettingChanged(keybindSet: string): void {
33
40
  const value = KeybindSetSettings[keybindSet as keyof typeof KeybindSetSettings] || 0;
34
41
  InspectorFrontendHostInstance.recordEnumeratedHistogram(
@@ -253,7 +260,6 @@ export class UserMetrics {
253
260
  InspectorFrontendHostInstance.recordPerformanceHistogram(
254
261
  'DevTools.Insights.ShortTeaserGenerationTime', timeInMilliseconds);
255
262
  }
256
-
257
263
  }
258
264
 
259
265
  /**
@@ -1182,3 +1188,45 @@ export const enum BuiltInAiAvailability {
1182
1188
  DISABLED_NO_GPU = 9,
1183
1189
  MAX_VALUE = 10,
1184
1190
  }
1191
+
1192
+ export const enum ResendRequestType {
1193
+ XHR = 0,
1194
+ FETCH = 1,
1195
+ SCRIPT = 2,
1196
+ STYLESHEET = 3,
1197
+ IMAGE = 4,
1198
+ MEDIA = 5,
1199
+ FONT = 6,
1200
+ WASM = 7,
1201
+ MANIFEST = 8,
1202
+ TEXT_TRACK = 9,
1203
+ SOURCE_MAP_SCRIPT = 10,
1204
+ SOURCE_MAP_STYLE_SHEET = 11,
1205
+ DOCUMENT = 12,
1206
+ PREFETCH = 13,
1207
+ PING = 14,
1208
+ OTHER = 15,
1209
+ MAX_VALUE = 16,
1210
+ }
1211
+
1212
+ const resendRequestTypeMap = new Map<Common.ResourceType.ResourceType, ResendRequestType>([
1213
+ [Common.ResourceType.resourceTypes.XHR, ResendRequestType.XHR],
1214
+ [Common.ResourceType.resourceTypes.Fetch, ResendRequestType.FETCH],
1215
+ [Common.ResourceType.resourceTypes.Script, ResendRequestType.SCRIPT],
1216
+ [Common.ResourceType.resourceTypes.Stylesheet, ResendRequestType.STYLESHEET],
1217
+ [Common.ResourceType.resourceTypes.Image, ResendRequestType.IMAGE],
1218
+ [Common.ResourceType.resourceTypes.Media, ResendRequestType.MEDIA],
1219
+ [Common.ResourceType.resourceTypes.Font, ResendRequestType.FONT],
1220
+ [Common.ResourceType.resourceTypes.Wasm, ResendRequestType.WASM],
1221
+ [Common.ResourceType.resourceTypes.Manifest, ResendRequestType.MANIFEST],
1222
+ [Common.ResourceType.resourceTypes.TextTrack, ResendRequestType.TEXT_TRACK],
1223
+ [Common.ResourceType.resourceTypes.SourceMapScript, ResendRequestType.SOURCE_MAP_SCRIPT],
1224
+ [Common.ResourceType.resourceTypes.SourceMapStyleSheet, ResendRequestType.SOURCE_MAP_STYLE_SHEET],
1225
+ [Common.ResourceType.resourceTypes.Document, ResendRequestType.DOCUMENT],
1226
+ [Common.ResourceType.resourceTypes.Prefetch, ResendRequestType.PREFETCH],
1227
+ [Common.ResourceType.resourceTypes.Ping, ResendRequestType.PING],
1228
+ ]);
1229
+
1230
+ export function resendRequestType(resourceType: Common.ResourceType.ResourceType): ResendRequestType {
1231
+ return resendRequestTypeMap.get(resourceType) ?? ResendRequestType.OTHER;
1232
+ }
@@ -52,7 +52,7 @@ import {
52
52
  CSSStyleRule,
53
53
  } from './CSSRule.js';
54
54
  import {CSSStyleDeclaration, Type} from './CSSStyleDeclaration.js';
55
- import type {DOMNode} from './DOMModel.js';
55
+ import {type DOMNode, NodeType} from './DOMModel.js';
56
56
 
57
57
  function containsStyle(styles: CSSStyleDeclaration[]|Set<CSSStyleDeclaration>, query: CSSStyleDeclaration): boolean {
58
58
  if (!query.styleSheetId || !query.range) {
@@ -456,7 +456,7 @@ export class CSSMatchedStyles {
456
456
  }
457
457
 
458
458
  // Inline style takes precedence over regular and inherited rules.
459
- if (inlinePayload && this.#node.nodeType() === Node.ELEMENT_NODE) {
459
+ if (inlinePayload && this.#node.nodeType() === NodeType.ELEMENT_NODE) {
460
460
  const style = new CSSStyleDeclaration(this.#cssModel, null, inlinePayload, Type.Inline);
461
461
  this.#nodeForStyle.set(style, this.#node);
462
462
  nodeStyles.push(style);
@@ -905,9 +905,6 @@ export class CSSMatchedStyles {
905
905
 
906
906
  const matchCascade = (cascade: DOMInheritanceCascade): CSSStyleRule|null => {
907
907
  for (const style of cascade.styles()) {
908
- if (this.isInherited(style)) {
909
- continue;
910
- }
911
908
  const parentRule = style.parentRule;
912
909
  if (!(parentRule instanceof CSSStyleRule)) {
913
910
  continue;
@@ -243,6 +243,18 @@ function getCssEvaluationElement(): HTMLElement {
243
243
  return cssEvaluationElement;
244
244
  }
245
245
 
246
+ /**
247
+ * If a test calls localEvalCSS, an element is created on demand for this
248
+ * purpose. This element is not removed from the DOM and will leak between tests
249
+ * if not removed.
250
+ */
251
+ export function removeCSSEvaluationElement(): void {
252
+ if (cssEvaluationElement) {
253
+ document.body.removeChild(cssEvaluationElement);
254
+ cssEvaluationElement = null;
255
+ }
256
+ }
257
+
246
258
  /**
247
259
  * These functions use an element in the frontend to evaluate CSS. The advantage
248
260
  * of this is that it is synchronous and doesn't require a CDP method. The
@@ -189,7 +189,7 @@ export class StubTransport implements ProtocolClient.ConnectionTransport.Connect
189
189
  }
190
190
 
191
191
  sendRawMessage(message: string): void {
192
- window.setTimeout(this.respondWithError.bind(this, message), 0);
192
+ globalThis.setTimeout(this.respondWithError.bind(this, message), 0);
193
193
  }
194
194
 
195
195
  private respondWithError(message: string): void {
@@ -0,0 +1,104 @@
1
+ // Copyright 2026 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+
5
+ import {assert} from 'chai';
6
+
7
+ import type * as Common from '../common/common.js';
8
+
9
+ // eslint-disable-next-line @devtools/es-modules-import
10
+ import * as SDK from './sdk.js';
11
+
12
+ function findNode(node: SDK.DOMModel.DOMNode, predicate: (node: SDK.DOMModel.DOMNode) => boolean): SDK.DOMModel.DOMNode|
13
+ null {
14
+ if (predicate(node)) {
15
+ return node;
16
+ }
17
+ const children = node.children();
18
+ if (children) {
19
+ for (const child of children) {
20
+ const result = findNode(child, predicate);
21
+ if (result) {
22
+ return result;
23
+ }
24
+ }
25
+ }
26
+ return null;
27
+ }
28
+
29
+ describe('DOMModel API Test', () => {
30
+ it('generates attribute updated event only when attribute is actually changed', async ({inspectedPage, universe}) => {
31
+ const primaryTarget = universe.targetManager.primaryPageTarget();
32
+ assert.isNotNull(primaryTarget);
33
+
34
+ const domModel = primaryTarget.model(SDK.DOMModel.DOMModel);
35
+ assert.isNotNull(domModel);
36
+
37
+ await inspectedPage.goToHtml(`
38
+ <div id="container">
39
+ <div id="node-set-new-value" style="color:red"></div>
40
+ <div id="node-set-same-value" style="color:red"></div>
41
+ </div>
42
+ `);
43
+
44
+ const documentNode = await domModel.requestDocument();
45
+ assert.isNotNull(documentNode);
46
+
47
+ // Retrieve the subtree to populate the DOMModel cache.
48
+ await documentNode.getSubtree(5, true);
49
+
50
+ const container = findNode(documentNode, n => n.getAttribute('id') === 'container');
51
+ assert.isNotNull(container);
52
+
53
+ const nodeSetNewValue = findNode(documentNode, n => n.getAttribute('id') === 'node-set-new-value');
54
+ assert.isNotNull(nodeSetNewValue);
55
+
56
+ const nodeSetSameValue = findNode(documentNode, n => n.getAttribute('id') === 'node-set-same-value');
57
+ assert.isNotNull(nodeSetSameValue);
58
+
59
+ if (!nodeSetNewValue || !nodeSetSameValue) {
60
+ assert.fail('Could not find test nodes');
61
+ }
62
+
63
+ // Verifies that setting a new style attribute value triggers the AttrModified event.
64
+ let attrModifiedPromise = domModel.once(SDK.DOMModel.Events.AttrModified);
65
+
66
+ await inspectedPage.evaluate(() => {
67
+ (document.getElementById('node-set-new-value') as HTMLElement).style.setProperty('color', 'blue');
68
+ });
69
+
70
+ let eventData = await attrModifiedPromise;
71
+ assert.strictEqual(eventData.node, nodeSetNewValue);
72
+ assert.strictEqual(eventData.name, 'style');
73
+ assert.strictEqual(nodeSetNewValue.getAttribute('style'), 'color: blue;');
74
+
75
+ // Verifies that setting the style attribute to the same value does not trigger the AttrModified event.
76
+ let attrModifiedFired = false;
77
+ const listener =
78
+ (event: Common.EventTarget
79
+ .EventTargetEvent<SDK.DOMModel.EventTypes[SDK.DOMModel.Events.AttrModified], SDK.DOMModel.EventTypes>):
80
+ void => {
81
+ if (event.data.node === nodeSetSameValue) {
82
+ attrModifiedFired = true;
83
+ }
84
+ };
85
+ domModel.addEventListener(SDK.DOMModel.Events.AttrModified, listener);
86
+
87
+ await inspectedPage.evaluate(() => {
88
+ (document.getElementById('node-set-same-value') as HTMLElement).style.setProperty('color', 'red');
89
+ });
90
+
91
+ // Flushes pending events by triggering a style change on the new value node.
92
+ attrModifiedPromise = domModel.once(SDK.DOMModel.Events.AttrModified);
93
+
94
+ await inspectedPage.evaluate(() => {
95
+ (document.getElementById('node-set-new-value') as HTMLElement).style.setProperty('color', 'green');
96
+ });
97
+
98
+ eventData = await attrModifiedPromise;
99
+ assert.strictEqual(eventData.node, nodeSetNewValue);
100
+ assert.isFalse(attrModifiedFired, 'AttrModified should not have fired for same value');
101
+
102
+ domModel.removeEventListener(SDK.DOMModel.Events.AttrModified, listener);
103
+ });
104
+ });
@@ -50,6 +50,18 @@ import {SDKModel} from './SDKModel.js';
50
50
  import {Capability, type Target} from './Target.js';
51
51
  import type {TargetManager} from './TargetManager.js';
52
52
 
53
+ export const enum NodeType {
54
+ ELEMENT_NODE = 1,
55
+ ATTRIBUTE_NODE = 2,
56
+ TEXT_NODE = 3,
57
+ CDATA_SECTION_NODE = 4,
58
+ PROCESSING_INSTRUCTION_NODE = 7,
59
+ COMMENT_NODE = 8,
60
+ DOCUMENT_NODE = 9,
61
+ DOCUMENT_TYPE_NODE = 10,
62
+ DOCUMENT_FRAGMENT_NODE = 11,
63
+ }
64
+
53
65
  /** Keep this list in sync with https://w3c.github.io/aria/#state_prop_def **/
54
66
  export const ARIA_ATTRIBUTES = new Set<string>([
55
67
  'role',
@@ -297,7 +309,7 @@ export class DOMNode extends Common.ObjectWrapper.ObjectWrapper<DOMNodeEventType
297
309
  this.#adProvenance = payload.adProvenance;
298
310
  }
299
311
 
300
- if (this.#nodeType === Node.ELEMENT_NODE) {
312
+ if (this.#nodeType === NodeType.ELEMENT_NODE) {
301
313
  // HTML and BODY from internal iframes should not overwrite top-level ones.
302
314
  if (this.ownerDocument && !this.ownerDocument.documentElement && this.#nodeName === 'HTML') {
303
315
  this.ownerDocument.documentElement = this;
@@ -305,11 +317,11 @@ export class DOMNode extends Common.ObjectWrapper.ObjectWrapper<DOMNodeEventType
305
317
  if (this.ownerDocument && !this.ownerDocument.body && this.#nodeName === 'BODY') {
306
318
  this.ownerDocument.body = this;
307
319
  }
308
- } else if (this.#nodeType === Node.DOCUMENT_TYPE_NODE) {
320
+ } else if (this.#nodeType === NodeType.DOCUMENT_TYPE_NODE) {
309
321
  this.publicId = payload.publicId;
310
322
  this.systemId = payload.systemId;
311
323
  this.internalSubset = payload.internalSubset;
312
- } else if (this.#nodeType === Node.ATTRIBUTE_NODE) {
324
+ } else if (this.#nodeType === NodeType.ATTRIBUTE_NODE) {
313
325
  this.name = payload.name;
314
326
  this.value = payload.value;
315
327
  }
@@ -354,7 +366,7 @@ export class DOMNode extends Common.ObjectWrapper.ObjectWrapper<DOMNodeEventType
354
366
  }
355
367
 
356
368
  isRootNode(): boolean {
357
- if (this.nodeType() === Node.ELEMENT_NODE && this.nodeName() === 'HTML') {
369
+ if (this.nodeType() === NodeType.ELEMENT_NODE && this.nodeName() === 'HTML') {
358
370
  return true;
359
371
  }
360
372
  return false;
@@ -762,7 +774,7 @@ export class DOMNode extends Common.ObjectWrapper.ObjectWrapper<DOMNodeEventType
762
774
  if (node.isShadowRoot()) {
763
775
  return node.shadowRootType() === DOMNode.ShadowRootTypes.UserAgent ? 'u' : 'a';
764
776
  }
765
- if (node.nodeType() === Node.DOCUMENT_NODE) {
777
+ if (node.nodeType() === NodeType.DOCUMENT_NODE) {
766
778
  return 'd';
767
779
  }
768
780
  return null;
@@ -1018,7 +1030,7 @@ export class DOMNode extends Common.ObjectWrapper.ObjectWrapper<DOMNodeEventType
1018
1030
  }
1019
1031
 
1020
1032
  isCustomElement(): boolean {
1021
- if (this.nodeType() !== Node.ELEMENT_NODE || this.isXMLNode()) {
1033
+ if (this.nodeType() !== NodeType.ELEMENT_NODE || this.isXMLNode()) {
1022
1034
  return false;
1023
1035
  }
1024
1036
  const localName = this.localName() || this.nodeName().toLowerCase();
@@ -1158,11 +1170,11 @@ export class DOMNode extends Common.ObjectWrapper.ObjectWrapper<DOMNodeEventType
1158
1170
 
1159
1171
  enclosingElementOrSelf(): DOMNode|null {
1160
1172
  let node: DOMNode|null = this;
1161
- if (node && node.nodeType() === Node.TEXT_NODE && node.parentNode) {
1173
+ if (node && node.nodeType() === NodeType.TEXT_NODE && node.parentNode) {
1162
1174
  node = node.parentNode;
1163
1175
  }
1164
1176
 
1165
- if (node && node.nodeType() !== Node.ELEMENT_NODE) {
1177
+ if (node && node.nodeType() !== NodeType.ELEMENT_NODE) {
1166
1178
  node = null;
1167
1179
  }
1168
1180
  return node;
@@ -1223,7 +1235,7 @@ export class DOMNode extends Common.ObjectWrapper.ObjectWrapper<DOMNodeEventType
1223
1235
 
1224
1236
  simpleSelector(): string {
1225
1237
  const lowerCaseName = this.localName() || this.nodeName().toLowerCase();
1226
- if (this.nodeType() !== Node.ELEMENT_NODE) {
1238
+ if (this.nodeType() !== NodeType.ELEMENT_NODE) {
1227
1239
  return lowerCaseName;
1228
1240
  }
1229
1241
  const type = this.getAttribute('type');
@@ -1474,7 +1486,7 @@ export class DOMModel extends SDKModel<EventTypes> {
1474
1486
  #lastMutationId!: number;
1475
1487
  #pendingDocumentRequestPromise: Promise<DOMDocument|null>|null = null;
1476
1488
  #frameOwnerNode?: DOMNode|null;
1477
- #loadNodeAttributesTimeout?: number;
1489
+ #loadNodeAttributesTimeout?: ReturnType<typeof setTimeout>;
1478
1490
  #searchId?: string;
1479
1491
  #topLayerThrottler = new Common.Throttler.Throttler(100);
1480
1492
  #topLayerNodes: DOMNode[] = [];
@@ -1663,7 +1675,7 @@ export class DOMModel extends SDKModel<EventTypes> {
1663
1675
  inlineStyleInvalidated(nodeIds: Protocol.DOM.NodeId[]): void {
1664
1676
  nodeIds.forEach(nodeId => this.#attributeLoadNodeIds.add(nodeId));
1665
1677
  if (!this.#loadNodeAttributesTimeout) {
1666
- this.#loadNodeAttributesTimeout = window.setTimeout(this.loadNodeAttributes.bind(this), 20);
1678
+ this.#loadNodeAttributesTimeout = globalThis.setTimeout(this.loadNodeAttributes.bind(this), 20);
1667
1679
  }
1668
1680
  }
1669
1681
 
@@ -144,7 +144,7 @@ export class DebuggerModel extends SDKModel<EventTypes> {
144
144
  #selectedCallFrame: CallFrame|null = null;
145
145
  #debuggerEnabled = false;
146
146
  #debuggerId: string|null = null;
147
- #skipAllPausesTimeout = 0;
147
+ #skipAllPausesTimeout?: ReturnType<typeof setTimeout>;
148
148
  #beforePausedCallback: ((arg0: DebuggerPausedDetails, stepOver: Location|null) => Promise<boolean>)|null = null;
149
149
  #computeAutoStepRangesCallback: ((arg0: StepMode, arg1: CallFrame) => Promise<Array<{
150
150
  start: Location,
@@ -337,20 +337,20 @@ export class DebuggerModel extends SDKModel<EventTypes> {
337
337
  }
338
338
 
339
339
  private skipAllPauses(skip: boolean): void {
340
- if (this.#skipAllPausesTimeout) {
341
- clearTimeout(this.#skipAllPausesTimeout);
342
- this.#skipAllPausesTimeout = 0;
343
- }
340
+ clearTimeout(this.#skipAllPausesTimeout);
341
+
344
342
  void this.agent.invoke_setSkipAllPauses({skip});
345
343
  }
346
344
 
347
345
  skipAllPausesUntilReloadOrTimeout(timeout: number): void {
348
- if (this.#skipAllPausesTimeout) {
349
- clearTimeout(this.#skipAllPausesTimeout);
350
- }
346
+ clearTimeout(this.#skipAllPausesTimeout);
347
+
351
348
  void this.agent.invoke_setSkipAllPauses({skip: true});
352
349
  // If reload happens before the timeout, the flag will be already unset and the timeout callback won't change anything.
353
- this.#skipAllPausesTimeout = window.setTimeout(this.skipAllPauses.bind(this, false), timeout);
350
+ this.#skipAllPausesTimeout = globalThis.setTimeout(
351
+ this.skipAllPauses.bind(this, false),
352
+ timeout,
353
+ );
354
354
  }
355
355
 
356
356
  private pauseOnExceptionStateChanged(): void {
@@ -121,7 +121,7 @@ export class IsolateManager extends Common.ObjectWrapper.ObjectWrapper<EventType
121
121
  const pollId = this.#pollId;
122
122
  while (pollId === this.#pollId) {
123
123
  await Promise.all(Array.from(this.isolates(), isolate => isolate.update()));
124
- await new Promise(r => window.setTimeout(r, PollIntervalMs));
124
+ await new Promise(r => globalThis.setTimeout(r, PollIntervalMs));
125
125
  }
126
126
  }
127
127
  }
@@ -5,6 +5,7 @@
5
5
  import type * as ProtocolProxyApi from '../../generated/protocol-proxy-api.js';
6
6
  import * as Protocol from '../../generated/protocol.js';
7
7
  import * as Common from '../common/common.js';
8
+ import * as Host from '../host/host.js';
8
9
  import * as i18n from '../i18n/i18n.js';
9
10
  import * as Platform from '../platform/platform.js';
10
11
  import * as Root from '../root/root.js';
@@ -23,6 +24,7 @@ import {
23
24
  type NameValue,
24
25
  NetworkRequest,
25
26
  } from './NetworkRequest.js';
27
+ import {type ExecutionContext, RuntimeModel} from './RuntimeModel.js';
26
28
  import {SDKModel} from './SDKModel.js';
27
29
  import {Capability, type Target} from './Target.js';
28
30
  import {type SDKModelObserver, TargetManager} from './TargetManager.js';
@@ -116,6 +118,22 @@ const i18nLazyString = i18n.i18n.getLazilyComputedLocalizedString.bind(undefined
116
118
 
117
119
  const requestToManagerMap = new WeakMap<NetworkRequest, NetworkManager>();
118
120
 
121
+ /** Resource types eligible for resend with full fidelity. */
122
+ const FULL_FIDELITY_RESEND_TYPES = new Set([
123
+ Common.ResourceType.resourceTypes.XHR,
124
+ Common.ResourceType.resourceTypes.Fetch,
125
+ Common.ResourceType.resourceTypes.Script,
126
+ Common.ResourceType.resourceTypes.Stylesheet,
127
+ Common.ResourceType.resourceTypes.Image,
128
+ Common.ResourceType.resourceTypes.Media,
129
+ Common.ResourceType.resourceTypes.Font,
130
+ Common.ResourceType.resourceTypes.Wasm,
131
+ Common.ResourceType.resourceTypes.Manifest,
132
+ Common.ResourceType.resourceTypes.TextTrack,
133
+ Common.ResourceType.resourceTypes.SourceMapScript,
134
+ Common.ResourceType.resourceTypes.SourceMapStyleSheet,
135
+ ]);
136
+
119
137
  const CONNECTION_TYPES = new Map([
120
138
  ['2g', Protocol.Network.ConnectionType.Cellular2g],
121
139
  ['3g', Protocol.Network.ConnectionType.Cellular3g],
@@ -193,18 +211,101 @@ export class NetworkManager extends SDKModel<EventTypes> {
193
211
  return requestToManagerMap.get(request) || null;
194
212
  }
195
213
 
196
- static canReplayRequest(request: NetworkRequest): boolean {
197
- return Boolean(requestToManagerMap.get(request)) && Boolean(request.backendRequestId()) && !request.isRedirect() &&
198
- request.resourceType() === Common.ResourceType.resourceTypes.XHR;
214
+ static canResendRequest(request: NetworkRequest): boolean {
215
+ if (!requestToManagerMap.get(request) || !request.backendRequestId() || request.isRedirect()) {
216
+ return false;
217
+ }
218
+ return FULL_FIDELITY_RESEND_TYPES.has(request.resourceType());
199
219
  }
200
220
 
201
221
  static replayRequest(request: NetworkRequest): void {
222
+ void NetworkManager.resendRequest(request);
223
+ }
224
+
225
+ static async resendRequest(request: NetworkRequest): Promise<void> {
202
226
  const manager = requestToManagerMap.get(request);
203
227
  const requestId = request.backendRequestId();
204
228
  if (!manager || !requestId || request.isRedirect()) {
205
229
  return;
206
230
  }
207
- void manager.#networkAgent.invoke_replayXHR({requestId});
231
+
232
+ Host.userMetrics.resendRequest(Host.UserMetrics.resendRequestType(request.resourceType()));
233
+
234
+ // XHR requests use the existing CDP replay mechanism.
235
+ if (request.resourceType() === Common.ResourceType.resourceTypes.XHR) {
236
+ void manager.#networkAgent.invoke_replayXHR({requestId});
237
+ return;
238
+ }
239
+
240
+ // All other eligible types use fetch via Runtime.evaluate.
241
+ const target = manager.target();
242
+ const runtimeModel = target.model(RuntimeModel);
243
+ if (!runtimeModel) {
244
+ return;
245
+ }
246
+
247
+ // Resolve execution context: prefer the frame's default context.
248
+ let executionContext: ExecutionContext|null = null;
249
+ const frameId = request.frameId;
250
+ if (frameId) {
251
+ executionContext = runtimeModel.executionContexts().find(ctx => ctx.frameId === frameId && ctx.isDefault) ?? null;
252
+ }
253
+ const usesFallbackContext = !executionContext;
254
+ if (!executionContext) {
255
+ executionContext = runtimeModel.defaultExecutionContext();
256
+ }
257
+ if (!executionContext) {
258
+ return;
259
+ }
260
+
261
+ if (usesFallbackContext) {
262
+ runtimeModel.target().targetManager().getConsole().warn(
263
+ 'Resend: original execution context unavailable, using top-level context.');
264
+ }
265
+
266
+ // Build the fetch expression.
267
+ const method = request.requestMethod;
268
+ const url = request.url();
269
+ const headers: Array<[string, string]> = [];
270
+ for (const {name, value} of request.requestHeaders()) {
271
+ // Skip HTTP/2+ pseudo-headers (e.g. :authority, :method, :path, :scheme).
272
+ if (name.startsWith(':')) {
273
+ continue;
274
+ }
275
+ // Skip headers the browser sets automatically for fetch.
276
+ const lower = name.toLowerCase();
277
+ if (lower === 'host' || lower === 'connection' || lower === 'content-length' || lower === 'cookie' ||
278
+ lower === 'origin' || lower === 'referer') {
279
+ continue;
280
+ }
281
+ headers.push([name, value]);
282
+ }
283
+
284
+ const body = await request.requestFormData();
285
+ const fetchOptions: {method: string, headers: Array<[string, string]>, credentials: string, body?: string} = {
286
+ method,
287
+ headers,
288
+ credentials: 'include',
289
+ };
290
+ const isGetOrHead = method === 'GET' || method === 'HEAD';
291
+ if (body && !isGetOrHead) {
292
+ fetchOptions.body = body;
293
+ }
294
+
295
+ const expression = `fetch(${JSON.stringify(url)}, ${JSON.stringify(fetchOptions)})`;
296
+ const response = await target.runtimeAgent().invoke_evaluate({
297
+ expression,
298
+ // Use uniqueContextId if available, otherwise fall back to contextId.
299
+ ...(executionContext.uniqueId ? {uniqueContextId: executionContext.uniqueId} : {contextId: executionContext.id}),
300
+ silent: false,
301
+ awaitPromise: true,
302
+ });
303
+
304
+ if (response.getError() || response.exceptionDetails) {
305
+ const errorText = response.getError() || response.exceptionDetails?.exception?.description ||
306
+ response.exceptionDetails?.text || 'Unknown error';
307
+ runtimeModel.target().targetManager().getConsole().error(`Resend failed for ${url}: ${errorText}`);
308
+ }
208
309
  }
209
310
 
210
311
  static async searchInRequest(request: NetworkRequest, query: string, caseSensitive: boolean, isRegex: boolean):
@@ -86,7 +86,7 @@ export class OverlayModel extends SDKModel<EventTypes> implements ProtocolProxyA
86
86
  overlayAgent: ProtocolProxyApi.OverlayApi;
87
87
  readonly #debuggerModel: DebuggerModel|null;
88
88
  #inspectModeEnabled = false;
89
- #hideHighlightTimeout: number|null = null;
89
+ #hideHighlightTimeout?: ReturnType<typeof setTimeout>;
90
90
  #defaultHighlighter: Highlighter;
91
91
  #highlighter: Highlighter;
92
92
  #showPaintRectsSetting: Common.Settings.Setting<boolean>;
@@ -331,14 +331,13 @@ export class OverlayModel extends SDKModel<EventTypes> implements ProtocolProxyA
331
331
  // overlay, so that it is not cleared by the highlight
332
332
  return;
333
333
  }
334
- if (this.#hideHighlightTimeout) {
335
334
  clearTimeout(this.#hideHighlightTimeout);
336
- this.#hideHighlightTimeout = null;
337
- }
338
- const highlightConfig = this.buildHighlightConfig(mode);
339
- if (typeof showInfo !== 'undefined') {
340
- highlightConfig.showInfo = showInfo;
341
- }
335
+ this.#hideHighlightTimeout = undefined;
336
+
337
+ const highlightConfig = this.buildHighlightConfig(mode);
338
+ if (typeof showInfo !== 'undefined') {
339
+ highlightConfig.showInfo = showInfo;
340
+ }
342
341
  this.#highlighter.highlightInOverlay(data, highlightConfig);
343
342
  }
344
343
 
@@ -486,17 +485,19 @@ export class OverlayModel extends SDKModel<EventTypes> implements ProtocolProxyA
486
485
  }
487
486
 
488
487
  private delayedHideHighlight(delay: number): void {
489
- if (this.#hideHighlightTimeout === null) {
490
- this.#hideHighlightTimeout = window.setTimeout(() => this.highlightInOverlay({clear: true}), delay);
488
+ if (this.#hideHighlightTimeout === undefined) {
489
+ this.#hideHighlightTimeout = globalThis.setTimeout(
490
+ () => this.highlightInOverlay({clear: true}),
491
+ delay,
492
+ );
491
493
  }
492
494
  }
493
495
 
494
496
  highlightFrame(frameId: Protocol.Page.FrameId): void {
495
- if (this.#hideHighlightTimeout) {
496
497
  clearTimeout(this.#hideHighlightTimeout);
497
- this.#hideHighlightTimeout = null;
498
- }
499
- this.#highlighter.highlightFrame(frameId);
498
+ this.#hideHighlightTimeout = undefined;
499
+
500
+ this.#highlighter.highlightFrame(frameId);
500
501
  }
501
502
 
502
503
  showHingeForDualScreen(hinge: Hinge|null): void {
@@ -868,7 +868,7 @@ export class LocalJSONObject extends RemoteObject {
868
868
  Promise<GetPropertiesResult> {
869
869
  function isArrayIndex(name: string): boolean {
870
870
  const index = Number(name) >>> 0;
871
- return String(index) === name;
871
+ return String(index) === name && index < 4294967295;
872
872
  }
873
873
 
874
874
  let properties = this.children();
@@ -445,6 +445,7 @@
445
445
  SyncSetting: 'DevTools.SyncSetting',
446
446
  SwatchActivated: 'DevTools.SwatchActivated',
447
447
  BuiltInAiAvailability: 'DevTools.BuiltInAiAvailability',
448
+ ResendRequest: 'DevTools.ResendRequest',
448
449
  // LINT.ThenChange(/front_end/core/host/InspectorFrontendHostAPI.ts:EnumeratedHistogram)
449
450
  };
450
451