chrome-devtools-frontend 1.0.1716465 → 1.0.1717196

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 (109) hide show
  1. package/.agents/skills/devtools-model-management/SKILL.md +4 -4
  2. package/.agents/skills/devtools-setting-migration/SKILL.md +1 -1
  3. package/.agents/skills/devtools-ux-writing-refactor/SKILL.md +5 -6
  4. package/.agents/skills/devtools-verification/SKILL.md +9 -5
  5. package/.agents/skills/fix-tests/SKILL.md +1 -1
  6. package/.agents/skills/foundation-test-migration/SKILL.md +2 -2
  7. package/.agents/skills/ui-eng-vision-test-scaffolder/SKILL.md +4 -5
  8. package/docs/README.md +1 -1
  9. package/docs/contributing/README.md +3 -3
  10. package/docs/cookbook/dependencies.md +1 -1
  11. package/docs/get_the_code.md +35 -26
  12. package/dt +7 -0
  13. package/dt.bat +8 -0
  14. package/front_end/core/common/ParsedURL.ts +1 -1
  15. package/front_end/core/sdk/PageResourceLoader.ts +141 -21
  16. package/front_end/core/sdk/ResourceTreeModel.ts +34 -10
  17. package/front_end/generated/Deprecation.ts +1 -1
  18. package/front_end/generated/InspectorBackendCommands.ts +1 -0
  19. package/front_end/generated/protocol-mapping.d.ts +8 -1
  20. package/front_end/generated/protocol-proxy-api.d.ts +7 -1
  21. package/front_end/generated/protocol.ts +8 -1
  22. package/front_end/models/ai_assistance/README.md +2 -2
  23. package/front_end/models/ai_assistance/agents/NetworkAgent.snapshot.txt +2 -2
  24. package/front_end/models/ai_assistance/agents/StylingAgent.snapshot.txt +1 -1
  25. package/front_end/models/ai_assistance/contexts/RequestContext.snapshot.txt +2 -2
  26. package/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.ts +7 -0
  27. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +9 -6
  28. package/front_end/models/ai_assistance/performance/AIQueries.ts +2 -1
  29. package/front_end/models/ai_assistance/tools/ExecuteJavaScript.ts +62 -23
  30. package/front_end/models/bindings/ResourceScriptMapping.ts +21 -2
  31. package/front_end/models/stack_trace/StackTrace.ts +10 -0
  32. package/front_end/models/stack_trace/StackTraceImpl.ts +15 -0
  33. package/front_end/models/trace/Processor.ts +7 -9
  34. package/front_end/models/trace/extras/StackTraceForEvent.ts +1 -2
  35. package/front_end/models/trace/extras/TraceTree.ts +7 -2
  36. package/front_end/models/trace/handlers/AnimationFramesHandler.ts +4 -3
  37. package/front_end/models/trace/handlers/AsyncJSCallsHandler.ts +10 -9
  38. package/front_end/models/trace/handlers/ImagePaintingHandler.ts +6 -6
  39. package/front_end/models/trace/handlers/InitiatorsHandler.ts +6 -6
  40. package/front_end/models/trace/handlers/InvalidationsHandler.ts +2 -4
  41. package/front_end/models/trace/handlers/LayerTreeHandler.ts +3 -3
  42. package/front_end/models/trace/handlers/MetaHandler.ts +1 -1
  43. package/front_end/models/trace/handlers/NetworkRequestsHandler.ts +5 -5
  44. package/front_end/models/trace/handlers/PageLoadMetricsHandler.ts +9 -9
  45. package/front_end/models/trace/handlers/ScriptsHandler.ts +2 -2
  46. package/front_end/models/trace/handlers/UserInteractionsHandler.ts +1 -1
  47. package/front_end/models/trace/handlers/WarningsHandler.ts +3 -3
  48. package/front_end/models/trace/handlers/helpers.ts +8 -5
  49. package/front_end/models/trace/helpers/Timing.ts +17 -12
  50. package/front_end/models/trace/helpers/Trace.ts +41 -14
  51. package/front_end/models/trace/insights/DuplicatedJavaScript.ts +12 -2
  52. package/front_end/models/trace/lantern/core/NetworkAnalyzer.ts +15 -1
  53. package/front_end/models/trace/types/TraceEvents.ts +213 -143
  54. package/front_end/panels/accessibility/AccessibilityAnnouncementRecordingView.ts +46 -0
  55. package/front_end/panels/ai_assistance/components/ChatMessage.ts +1 -1
  56. package/front_end/panels/application/AppManifestView.ts +26 -4
  57. package/front_end/panels/application/serviceWorkersView.css +1 -1
  58. package/front_end/panels/comments/CommentsPane.ts +81 -32
  59. package/front_end/panels/comments/commentsPane.css +42 -1
  60. package/front_end/panels/elements/DOMTreeElementWidget.ts +508 -747
  61. package/front_end/panels/elements/DOMTreeWidget.ts +418 -502
  62. package/front_end/panels/elements/StylePropertiesSection.ts +1 -1
  63. package/front_end/panels/layer_viewer/Layers3DView.ts +6 -6
  64. package/front_end/panels/lighthouse/LighthouseProtocolService.ts +3 -0
  65. package/front_end/panels/recorder/recordingView.css +1 -1
  66. package/front_end/panels/settings/SettingsScreen.ts +6 -1
  67. package/front_end/panels/sources/DebuggerPlugin.ts +1 -1
  68. package/front_end/panels/sources/SourcesPanel.ts +511 -374
  69. package/front_end/panels/sources/sourcesPanel.css +6 -31
  70. package/front_end/panels/timeline/CountersGraph.ts +5 -4
  71. package/front_end/panels/timeline/Initiators.ts +1 -1
  72. package/front_end/panels/timeline/TimelineFlameChartView.ts +17 -19
  73. package/front_end/panels/timeline/TimelineMiniMap.ts +11 -19
  74. package/front_end/panels/timeline/TimelinePanel.ts +90 -43
  75. package/front_end/panels/timeline/TimelineUIUtils.ts +2 -2
  76. package/front_end/panels/timeline/components/BreadcrumbsUI.ts +109 -87
  77. package/front_end/panels/timeline/components/LayoutShiftDetails.ts +3 -3
  78. package/front_end/panels/timeline/components/breadcrumbsUI.css +43 -24
  79. package/front_end/panels/timeline/overlays/components/TimeRangeOverlay.ts +21 -11
  80. package/front_end/panels/timeline/utils/EntryNodes.ts +1 -1
  81. package/front_end/third_party/chromium/README.chromium +1 -1
  82. package/front_end/third_party/source-map-scopes-codec/README.chromium +2 -2
  83. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/decode/decode.d.ts +2 -0
  84. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/decode/decode.d.ts.map +1 -1
  85. package/front_end/third_party/source-map-scopes-codec/package/deno.json +1 -1
  86. package/front_end/third_party/source-map-scopes-codec/package/package.json +1 -1
  87. package/front_end/third_party/source-map-scopes-codec/package/src/codec.js +2 -2
  88. package/front_end/third_party/source-map-scopes-codec/package/src/codec.ts +2 -2
  89. package/front_end/third_party/source-map-scopes-codec/package/src/decode/decode.js +19 -1
  90. package/front_end/third_party/source-map-scopes-codec/package/src/decode/decode.js.map +1 -1
  91. package/front_end/third_party/source-map-scopes-codec/package/src/decode/decode.ts +26 -4
  92. package/front_end/ui/comments/CommentAnchorResolver.ts +2 -0
  93. package/front_end/ui/components/buttons/Button.ts +9 -1
  94. package/front_end/ui/components/buttons/button.css +4 -0
  95. package/front_end/ui/components/helpers/scheduled-render.ts +1 -1
  96. package/front_end/ui/legacy/GlassPane.ts +1 -1
  97. package/front_end/ui/legacy/SplitWidget.ts +1 -1
  98. package/front_end/ui/legacy/Toolbar.ts +1 -107
  99. package/front_end/ui/legacy/UIUtils.ts +133 -11
  100. package/front_end/ui/legacy/Widget.ts +5 -1
  101. package/front_end/ui/legacy/components/data_grid/DataGridElement.ts +5 -2
  102. package/front_end/ui/legacy/components/object_ui/objectPopover.css +1 -1
  103. package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +47 -15
  104. package/front_end/ui/legacy/components/perf_ui/TimelineGrid.ts +1 -1
  105. package/front_end/ui/legacy/components/utils/Linkifier.ts +2 -2
  106. package/front_end/ui/legacy/theme_support/ThemeSupport.ts +6 -5
  107. package/front_end/ui/visual_logging/KnownContextValues.ts +2 -0
  108. package/front_end/ui/visual_logging/LoggingDriver.ts +4 -4
  109. package/package.json +3 -3
@@ -18,6 +18,7 @@ import {
18
18
  ResourceTreeModel,
19
19
  } from './ResourceTreeModel.js';
20
20
  import {cacheDisabledSettingDescriptor, enableRemoteFileLoadingSettingDescriptor} from './SDKSettings.js';
21
+ import {SecurityOrigin} from './SecurityOrigin.js';
21
22
  import type {Target} from './Target.js';
22
23
  import {TargetManager} from './TargetManager.js';
23
24
 
@@ -26,6 +27,17 @@ const UIStrings = {
26
27
  * @description Error message for canceled source map loads.
27
28
  */
28
29
  loadCanceledDueToReloadOf: 'Load canceled due to reload of inspected page',
30
+ /**
31
+ * @description Error message in the Developer resources panel when DevTools refuses to load a
32
+ * resource (e.g. a source map) for a frame that is no longer available and is cross-origin with
33
+ * the page.
34
+ */
35
+ crossOriginFrameLoadBlocked: 'Can’t load resource for an unavailable cross-origin frame',
36
+ /**
37
+ * @description Error message in the Developer resources panel when an http(s) resource cannot be
38
+ * loaded because no target is available.
39
+ */
40
+ noTargetForHttpLoad: 'Can’t load http(s) resource without a target',
29
41
  } as const;
30
42
  const str_ = i18n.i18n.registerUIStrings('core/sdk/PageResourceLoader.ts', UIStrings);
31
43
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -280,20 +292,34 @@ export class PageResourceLoader extends Common.ObjectWrapper.ObjectWrapper<Event
280
292
  if (isExtensionInitiator(initiator)) {
281
293
  throw new Error('Invalid initiator');
282
294
  }
283
- const key = PageResourceLoader.makeKey(url, initiator);
295
+ const hasExplicitTarget = Boolean(initiator.target);
296
+ // Fall back to the primary page target when the initiator has no explicit target
297
+ // so that resources loaded for detached frames remain scoped to the primary page
298
+ // and go through target-based CSP checks instead of host bindings.
299
+ let effectiveInitiator: PageResourceLoadInitiator = initiator;
300
+ if (!initiator.target) {
301
+ const primaryTarget = this.#targetManager.primaryPageTarget();
302
+ if (primaryTarget) {
303
+ effectiveInitiator = {
304
+ ...initiator,
305
+ target: primaryTarget,
306
+ };
307
+ }
308
+ }
309
+ const key = PageResourceLoader.makeKey(url, effectiveInitiator);
284
310
  const pageResource: PageResource = {
285
311
  success: null,
286
312
  size: null,
287
313
  duration: null,
288
314
  url,
289
- initiator,
315
+ initiator: effectiveInitiator,
290
316
  };
291
317
  this.#pageResources.set(key, pageResource);
292
318
  this.dispatchEventToListeners(Events.UPDATE);
293
319
  const startTime = performance.now();
294
320
  try {
295
- await this.acquireLoadSlot(initiator.target);
296
- const resultPromise = this.dispatchLoad(url, initiator, isBinary);
321
+ await this.acquireLoadSlot(effectiveInitiator.target);
322
+ const resultPromise = this.dispatchLoad(url, effectiveInitiator, isBinary, hasExplicitTarget);
297
323
  const result = await resultPromise;
298
324
  pageResource.errorMessage = result.errorDescription.message;
299
325
  pageResource.success = result.success;
@@ -312,7 +338,7 @@ export class PageResourceLoader extends Common.ObjectWrapper.ObjectWrapper<Event
312
338
  throw e;
313
339
  } finally {
314
340
  pageResource.duration = performance.now() - startTime;
315
- this.releaseLoadSlot(initiator.target);
341
+ this.releaseLoadSlot(effectiveInitiator.target);
316
342
  this.dispatchEventToListeners(Events.UPDATE);
317
343
  }
318
344
  }
@@ -320,6 +346,8 @@ export class PageResourceLoader extends Common.ObjectWrapper.ObjectWrapper<Event
320
346
  #resolveFrameTarget(initiator: PageResourceLoadInitiator): {
321
347
  frameTarget: Target|null,
322
348
  frameId: Protocol.Page.FrameId|null,
349
+ initiatorFrameId: Protocol.Page.FrameId|null,
350
+ usedFallbackMainFrame: boolean,
323
351
  } {
324
352
  let frameTarget: Target|null = initiator.target;
325
353
  let parentFrameId: Protocol.Page.FrameId|null = null;
@@ -327,21 +355,88 @@ export class PageResourceLoader extends Common.ObjectWrapper.ObjectWrapper<Event
327
355
  parentFrameId = parentFrameId ?? frameTarget.targetInfo()?.parentFrameId ?? null;
328
356
  frameTarget = frameTarget.parentTarget();
329
357
  }
330
- const frameId = initiator.frameId ?? parentFrameId ?? frameTarget?.model(ResourceTreeModel)?.mainFrame?.id ?? null;
331
- return {frameTarget, frameId};
358
+ const resourceTreeModel = frameTarget?.model(ResourceTreeModel);
359
+ const mainFrameId = resourceTreeModel?.mainFrame?.id ?? null;
360
+
361
+ // Normalize empty-string frame IDs (used by ScriptsHandler for unassociated scripts)
362
+ // to null and fall back to the parent frame ID discovered from a worker or subtarget.
363
+ let candidateFrameId: Protocol.Page.FrameId|null = null;
364
+ if (initiator.frameId) {
365
+ candidateFrameId = initiator.frameId;
366
+ } else if (parentFrameId) {
367
+ candidateFrameId = parentFrameId;
368
+ }
369
+
370
+ if (frameTarget && frameTarget !== initiator.target && !candidateFrameId) {
371
+ // The initiator is a non-frame target (e.g. a service worker or a shared worker) that is not
372
+ // owned by any single frame. Don't attribute the load to the page's main frame, as the browser
373
+ // would then issue the request with the main frame's origin and credentials. Load through the
374
+ // initiator's own target instead, where the browser attributes the request to the worker's origin.
375
+ return {frameTarget: initiator.target, frameId: null, initiatorFrameId: null, usedFallbackMainFrame: false};
376
+ }
377
+
378
+ // Keep candidateFrameId only if ResourceTreeModel has not populated frames yet
379
+ // (mainFrame is null) or if candidateFrameId still exists in the active frame tree.
380
+ let validFrameId: Protocol.Page.FrameId|null = null;
381
+ if (candidateFrameId) {
382
+ if (!resourceTreeModel?.mainFrame || resourceTreeModel.frameForId(candidateFrameId)) {
383
+ validFrameId = candidateFrameId;
384
+ }
385
+ }
386
+
387
+ // Track whether we had to fall back to the main frame because the initiator's
388
+ // frame was missing or detached, so dispatchLoad can verify same-origin before
389
+ // issuing a network load in the main frame's context.
390
+ let usedFallbackMainFrame = false;
391
+ if (!validFrameId && mainFrameId) {
392
+ usedFallbackMainFrame = true;
393
+ }
394
+
395
+ // Use the validated frame ID if available; otherwise fall back to the main frame ID
396
+ // so that CDP's Network.loadNetworkResource and getSecurityIsolationStatus can
397
+ // enforce the page's CSP even after the original frame was detached.
398
+ const frameId = validFrameId ?? mainFrameId;
399
+ // Preserve the candidate frame ID (before mainFrameId fallback) so
400
+ // #isSameOriginWithPrimaryPage can detect detached or unassociated frames and
401
+ // disallow host-bindings fallback.
402
+ return {frameTarget, frameId, initiatorFrameId: candidateFrameId, usedFallbackMainFrame};
332
403
  }
333
404
 
334
- #isSameOriginWithPrimaryPage(frameTarget: Target|null, frameId: Protocol.Page.FrameId|null): boolean {
405
+ #isSameOriginWithPrimaryPage(
406
+ hasExplicitTarget: boolean,
407
+ frameTarget: Target|null,
408
+ initiatorFrameId: Protocol.Page.FrameId|null,
409
+ ): boolean {
410
+ // Disallow host-bindings fallback when the initiator had no explicit target.
411
+ if (!hasExplicitTarget) {
412
+ return false;
413
+ }
335
414
  const primaryFrame = this.#targetManager.primaryPageTarget()?.model(ResourceTreeModel)?.mainFrame;
336
- const initiatorFrame = frameId ? frameTarget?.model(ResourceTreeModel)?.frameForId(frameId) : null;
415
+ // Look up the original initiator frame in frameTarget's active frame tree; if the frame
416
+ // was detached, frameForId returns null and we fail closed below.
417
+ const initiatorFrame =
418
+ initiatorFrameId ? frameTarget?.model(ResourceTreeModel)?.frameForId(initiatorFrameId) : null;
337
419
  if (!primaryFrame || !initiatorFrame) {
338
420
  return false;
339
421
  }
340
422
  return initiatorFrame.securityOrigin().isSameOriginWith(primaryFrame.securityOrigin());
341
423
  }
342
424
 
343
- private async dispatchLoad(
344
- url: Platform.DevToolsPath.UrlString, initiator: PageResourceLoadInitiator, isBinary: boolean): Promise<{
425
+ #isInitiatorUrlSameOriginWithMainFrame(
426
+ initiator: PageResourceLoadInitiator,
427
+ frameTarget: Target|null,
428
+ ): boolean {
429
+ const mainFrame = frameTarget?.model(ResourceTreeModel)?.mainFrame;
430
+ if (!mainFrame || !initiator.initiatorUrl) {
431
+ return false;
432
+ }
433
+ // Compare the target main frame's security origin against the initiator script's URL origin
434
+ // to ensure cross-origin detached frame requests are not loaded under mainFrame.id.
435
+ return mainFrame.securityOrigin().isSameOriginWith(SecurityOrigin.create(initiator.initiatorUrl));
436
+ }
437
+
438
+ private async dispatchLoad(url: Platform.DevToolsPath.UrlString, initiator: PageResourceLoadInitiator,
439
+ isBinary: boolean, hasExplicitTarget: boolean): Promise<{
345
440
  success: boolean,
346
441
  content: string|Uint8Array<ArrayBuffer>,
347
442
  errorDescription: Host.ResourceLoader.LoadErrorDescription,
@@ -355,18 +450,32 @@ export class PageResourceLoader extends Common.ObjectWrapper.ObjectWrapper<Event
355
450
  return await this.#loadOverride(url);
356
451
  }
357
452
  const parsedURL = new Common.ParsedURL.ParsedURL(url);
358
- const eligibleForLoadFromTarget = this.getLoadThroughTargetSetting().get() && parsedURL &&
359
- parsedURL.scheme !== 'file' && parsedURL.scheme !== 'data' && parsedURL.scheme !== 'devtools' &&
360
- initiator.target;
453
+ const isHttp = Boolean(parsedURL && (parsedURL.scheme === 'http' || parsedURL.scheme === 'https'));
454
+ const loadThroughTarget = this.getLoadThroughTargetSetting().get();
455
+ const eligibleForLoadFromTarget =
456
+ Boolean(loadThroughTarget && parsedURL && parsedURL.scheme !== 'file' && parsedURL.scheme !== 'data' &&
457
+ parsedURL.scheme !== 'devtools' && initiator.target);
361
458
  Host.userMetrics.developerResourceScheme(this.getDeveloperResourceScheme(parsedURL));
362
- if (eligibleForLoadFromTarget) {
363
- const isHttp = parsedURL.scheme === 'http' || parsedURL.scheme === 'https';
459
+ if (eligibleForLoadFromTarget && initiator.target) {
364
460
  // Default to enforcing CSP for http(s) resources (fail-closed). Only relax
365
461
  // if the isolation-status query positively confirms no connect-src/default-src
366
462
  // directive is present. A null/error response keeps the guard armed so that a
367
463
  // detached-frame or protocol-error path cannot fall through to loadFromHostBindings.
368
464
  let mustEnforceCSP = isHttp;
369
- const {frameTarget, frameId} = this.#resolveFrameTarget(initiator);
465
+ const {frameTarget, frameId, initiatorFrameId, usedFallbackMainFrame} = this.#resolveFrameTarget(initiator);
466
+ // Do not execute network loads under mainFrame.id (which uses the main frame's origin,
467
+ // cookies, and CSP) if a detached frame's initiatorUrl is cross-origin with the main frame.
468
+ if (usedFallbackMainFrame && !this.#isInitiatorUrlSameOriginWithMainFrame(initiator, frameTarget)) {
469
+ Host.userMetrics.developerResourceLoaded(Host.UserMetrics.DeveloperResourceLoaded.LOAD_THROUGH_PAGE_FAILURE);
470
+ return {
471
+ success: false,
472
+ content: '',
473
+ errorDescription: {
474
+ statusCode: 0,
475
+ message: i18nString(UIStrings.crossOriginFrameLoadBlocked),
476
+ },
477
+ };
478
+ }
370
479
  if (isHttp && frameTarget) {
371
480
  const networkManager = frameTarget.model(NetworkManager);
372
481
  if (networkManager) {
@@ -385,7 +494,7 @@ export class PageResourceLoader extends Common.ObjectWrapper.ObjectWrapper<Event
385
494
  } catch (e) {
386
495
  if (e instanceof Error) {
387
496
  Host.userMetrics.developerResourceLoaded(Host.UserMetrics.DeveloperResourceLoaded.LOAD_THROUGH_PAGE_FAILURE);
388
- if (mustEnforceCSP || !this.#isSameOriginWithPrimaryPage(frameTarget, frameId) ||
497
+ if (mustEnforceCSP || !this.#isSameOriginWithPrimaryPage(hasExplicitTarget, frameTarget, initiatorFrameId) ||
389
498
  e.message.includes('CSP violation')) {
390
499
  return {
391
500
  success: false,
@@ -400,10 +509,21 @@ export class PageResourceLoader extends Common.ObjectWrapper.ObjectWrapper<Event
400
509
  }
401
510
  Host.userMetrics.developerResourceLoaded(Host.UserMetrics.DeveloperResourceLoaded.LOAD_THROUGH_PAGE_FALLBACK);
402
511
  } else {
403
- const code = this.getLoadThroughTargetSetting().get() ?
404
- Host.UserMetrics.DeveloperResourceLoaded.FALLBACK_PER_PROTOCOL :
405
- Host.UserMetrics.DeveloperResourceLoaded.FALLBACK_PER_OVERRIDE;
512
+ const code = loadThroughTarget ? Host.UserMetrics.DeveloperResourceLoaded.FALLBACK_PER_PROTOCOL :
513
+ Host.UserMetrics.DeveloperResourceLoaded.FALLBACK_PER_OVERRIDE;
406
514
  Host.userMetrics.developerResourceLoaded(code);
515
+ // Fail closed for HTTP(S) loads when target-based loading is enabled but no target is available,
516
+ // preventing unauthenticated fallback to privileged host bindings.
517
+ if (loadThroughTarget && isHttp && !initiator.target) {
518
+ return {
519
+ success: false,
520
+ content: '',
521
+ errorDescription: {
522
+ statusCode: 0,
523
+ message: i18nString(UIStrings.noTargetForHttpLoad),
524
+ },
525
+ };
526
+ }
407
527
  }
408
528
 
409
529
  const result = await this.loadFromHostBindings(url);
@@ -32,6 +32,7 @@ export class ResourceTreeModel extends SDKModel<EventTypes> {
32
32
  #pendingReloadOptions: {
33
33
  ignoreCache: (boolean|undefined),
34
34
  scriptToEvaluateOnLoad: (string|undefined),
35
+ loaderId: Protocol.Network.LoaderId,
35
36
  }|null = null;
36
37
  #reloadSuspensionCount = 0;
37
38
  isInterstitialShowing = false;
@@ -209,6 +210,11 @@ export class ResourceTreeModel extends SDKModel<EventTypes> {
209
210
  }
210
211
 
211
212
  frameNavigated(framePayload: Protocol.Page.Frame, type: Protocol.Page.NavigationType|undefined): void {
213
+ this.#frameNavigated(framePayload, type, /* isDocumentOpened */ false);
214
+ }
215
+
216
+ #frameNavigated(framePayload: Protocol.Page.Frame, type: Protocol.Page.NavigationType|undefined,
217
+ isDocumentOpened: boolean): void {
212
218
  const sameTargetParentFrame =
213
219
  framePayload.parentId ? (this.framesInternal.get(framePayload.parentId) || null) : null;
214
220
  // Do nothing unless cached resource tree is processed - it will overwrite everything.
@@ -245,6 +251,23 @@ export class ResourceTreeModel extends SDKModel<EventTypes> {
245
251
  this.dispatchEventToListeners(Events.ResourceAdded, resources[i]);
246
252
  }
247
253
 
254
+ // The document resource is normally added once the Network domain reports
255
+ // the document request as finished. Clients that run without the Network
256
+ // domain never get it, so inline scripts and styles can't be mapped to the
257
+ // document. Unless the request is still in flight, add the document
258
+ // resource right away, like `addFramesRecursively()` does for loaded frames.
259
+ // Documents created by `document.open()` have no request of their own and
260
+ // their content is not available.
261
+ const networkManager = this.target().model(NetworkManager);
262
+ if (!frame.getResourcesMap().get(framePayload.url as Platform.DevToolsPath.UrlString) &&
263
+ (isDocumentOpened || !networkManager?.requestForLoaderId(framePayload.loaderId))) {
264
+ const frameResource = this.createResourceFromFramePayload(
265
+ framePayload, framePayload.url as Platform.DevToolsPath.UrlString, Common.ResourceType.resourceTypes.Document,
266
+ framePayload.mimeType, null, null);
267
+ frameResource.isGenerated = isDocumentOpened;
268
+ frame.addResource(frameResource);
269
+ }
270
+
248
271
  this.updateSecurityOrigins();
249
272
  void this.updateStorageKeys();
250
273
 
@@ -265,17 +288,10 @@ export class ResourceTreeModel extends SDKModel<EventTypes> {
265
288
  }
266
289
 
267
290
  documentOpened(framePayload: Protocol.Page.Frame): void {
268
- this.frameNavigated(framePayload, undefined);
291
+ this.#frameNavigated(framePayload, undefined, /* isDocumentOpened */ true);
269
292
  const frame = this.framesInternal.get(framePayload.id);
270
293
  if (frame) {
271
294
  this.dispatchEventToListeners(Events.DocumentOpened, frame);
272
- if (!frame.getResourcesMap().get(framePayload.url)) {
273
- const frameResource = this.createResourceFromFramePayload(
274
- framePayload, framePayload.url as Platform.DevToolsPath.UrlString,
275
- Common.ResourceType.resourceTypes.Document, framePayload.mimeType, null, null);
276
- frameResource.isGenerated = true;
277
- frame.addResource(frameResource);
278
- }
279
295
  }
280
296
  }
281
297
 
@@ -434,7 +450,15 @@ export class ResourceTreeModel extends SDKModel<EventTypes> {
434
450
  this.#reloadSuspensionCount--;
435
451
  console.assert(this.#reloadSuspensionCount >= 0, 'Unbalanced call to ResourceTreeModel.resumeReload()');
436
452
  if (!this.#reloadSuspensionCount && this.#pendingReloadOptions) {
437
- const {ignoreCache, scriptToEvaluateOnLoad} = this.#pendingReloadOptions;
453
+ const {ignoreCache, scriptToEvaluateOnLoad, loaderId} = this.#pendingReloadOptions;
454
+ // A deferred reload (and its scriptToEvaluateOnLoad) only applies to the
455
+ // document it was requested against. If the main frame has since
456
+ // navigated to another document, drop the stale request instead of
457
+ // replaying it against the new document.
458
+ if (loaderId !== this.mainFrame?.loaderId) {
459
+ this.#pendingReloadOptions = null;
460
+ return;
461
+ }
438
462
  this.reloadPage(ignoreCache, scriptToEvaluateOnLoad);
439
463
  }
440
464
  }
@@ -449,7 +473,7 @@ export class ResourceTreeModel extends SDKModel<EventTypes> {
449
473
  this.dispatchEventToListeners(Events.PageReloadRequested, this);
450
474
  }
451
475
  if (this.#reloadSuspensionCount) {
452
- this.#pendingReloadOptions = {ignoreCache, scriptToEvaluateOnLoad};
476
+ this.#pendingReloadOptions = {ignoreCache, scriptToEvaluateOnLoad, loaderId};
453
477
  return;
454
478
  }
455
479
  this.#pendingReloadOptions = null;
@@ -3,7 +3,7 @@
3
3
  // found in the LICENSE file.
4
4
 
5
5
  // This file is auto-generated, do not edit manually.
6
- // Re-generate with: npm run generate-protocol-resources
6
+ // Re-generate with: scripts/tools/dt.py generate-protocol-resources
7
7
 
8
8
  export const UIStrings = {
9
9
  /**
@@ -1180,6 +1180,7 @@ inspectorBackend.registerCommand("Page.waitForDebugger", [], [], "Pauses page ex
1180
1180
  inspectorBackend.registerCommand("Page.setInterceptFileChooserDialog", [{"name": "enabled", "type": "boolean", "optional": false, "description": "", "typeRef": null}, {"name": "cancel", "type": "boolean", "optional": true, "description": "If true, cancels the dialog by emitting relevant events (if any) in addition to not showing it if the interception is enabled (default: false).", "typeRef": null}], [], "Intercept file chooser requests and transfer control to protocol clients. When file chooser interception is enabled, native file chooser dialog is not shown. Instead, a protocol event `Page.fileChooserOpened` is emitted.");
1181
1181
  inspectorBackend.registerCommand("Page.setPrerenderingAllowed", [{"name": "isAllowed", "type": "boolean", "optional": false, "description": "", "typeRef": null}], [], "Enable/disable prerendering manually. This command is a short-term solution for https://crbug.com/1440085. See https://docs.google.com/document/d/12HVmFxYj5Jc-eJr5OmWsa2bqTJsbgGLKI6ZIyx0_wpA for more details. TODO(https://crbug.com/1440085): Remove this once Puppeteer supports tab targets.");
1182
1182
  inspectorBackend.registerCommand("Page.getAnnotatedPageContent", [{"name": "includeActionableInformation", "type": "boolean", "optional": true, "description": "Whether to include actionable information. Defaults to true.", "typeRef": null}], ["content"], "Get the annotated page content for the main frame. This is an experimental command that is subject to change.");
1183
+ inspectorBackend.registerCommand("Page.setInputVisualization", [{"name": "clicks", "type": "boolean", "optional": true, "description": "Whether to enable click visualization.", "typeRef": null}], [], "Configures input visualization on the page.");
1183
1184
  inspectorBackend.registerType("Page.AdFrameStatus", [{"name": "adFrameType", "type": "string", "optional": false, "description": "", "typeRef": "Page.AdFrameType"}, {"name": "explanations", "type": "array", "optional": true, "description": "", "typeRef": "Page.AdFrameExplanation"}]);
1184
1185
  inspectorBackend.registerType("Page.PermissionsPolicyBlockLocator", [{"name": "frameId", "type": "string", "optional": false, "description": "", "typeRef": "Page.FrameId"}, {"name": "blockReason", "type": "string", "optional": false, "description": "", "typeRef": "Page.PermissionsPolicyBlockReason"}]);
1185
1186
  inspectorBackend.registerType("Page.PermissionsPolicyFeatureState", [{"name": "feature", "type": "string", "optional": false, "description": "", "typeRef": "Page.PermissionsPolicyFeature"}, {"name": "allowed", "type": "boolean", "optional": false, "description": "", "typeRef": null}, {"name": "locator", "type": "object", "optional": true, "description": "", "typeRef": "Page.PermissionsPolicyBlockLocator"}]);
@@ -4,7 +4,7 @@
4
4
 
5
5
  /**
6
6
  * This file is auto-generated, do not edit manually. *
7
- * Re-generate with: npm run generate-protocol-resources.
7
+ * Re-generate with: scripts/tools/dt.py generate-protocol-resources.
8
8
  */
9
9
 
10
10
 
@@ -4582,6 +4582,13 @@ export namespace ProtocolMapping {
4582
4582
  paramsType: [Protocol.Page.GetAnnotatedPageContentRequest?];
4583
4583
  returnType: Protocol.Page.GetAnnotatedPageContentResponse;
4584
4584
  };
4585
+ /**
4586
+ * Configures input visualization on the page.
4587
+ */
4588
+ 'Page.setInputVisualization': {
4589
+ paramsType: [Protocol.Page.SetInputVisualizationRequest?];
4590
+ returnType: void;
4591
+ };
4585
4592
  /**
4586
4593
  * Disable collecting and reporting metrics.
4587
4594
  */
@@ -4,7 +4,7 @@
4
4
 
5
5
  /**
6
6
  * This file is auto-generated, do not edit manually. *
7
- * Re-generate with: npm run generate-protocol-resources.
7
+ * Re-generate with: scripts/tools/dt.py generate-protocol-resources.
8
8
  */
9
9
 
10
10
 
@@ -2258,6 +2258,7 @@ declare namespace ProtocolProxyApi {
2258
2258
 
2259
2259
  /**
2260
2260
  * Return UUID of Blob object specified by a remote object id.
2261
+ * @deprecated
2261
2262
  */
2262
2263
  invoke_resolveBlob(params: Protocol.IO.ResolveBlobRequest): Promise<Protocol.IO.ResolveBlobResponse>;
2263
2264
 
@@ -3688,6 +3689,11 @@ declare namespace ProtocolProxyApi {
3688
3689
  */
3689
3690
  invoke_getAnnotatedPageContent(params: Protocol.Page.GetAnnotatedPageContentRequest): Promise<Protocol.Page.GetAnnotatedPageContentResponse>;
3690
3691
 
3692
+ /**
3693
+ * Configures input visualization on the page.
3694
+ */
3695
+ invoke_setInputVisualization(params: Protocol.Page.SetInputVisualizationRequest): Promise<Protocol.ProtocolResponseWithError>;
3696
+
3691
3697
  }
3692
3698
  export interface PageDispatcher {
3693
3699
  domContentEventFired(params: Protocol.Page.DomContentEventFiredEvent): void;
@@ -4,7 +4,7 @@
4
4
 
5
5
  /**
6
6
  * This file is auto-generated, do not edit manually. *
7
- * Re-generate with: npm run generate-protocol-resources.
7
+ * Re-generate with: scripts/tools/dt.py generate-protocol-resources.
8
8
  */
9
9
 
10
10
 
@@ -16683,6 +16683,13 @@ export namespace Page {
16683
16683
  content: binary;
16684
16684
  }
16685
16685
 
16686
+ export interface SetInputVisualizationRequest {
16687
+ /**
16688
+ * Whether to enable click visualization.
16689
+ */
16690
+ clicks?: boolean;
16691
+ }
16692
+
16686
16693
  export interface DomContentEventFiredEvent {
16687
16694
  timestamp: Network.MonotonicTime;
16688
16695
  }
@@ -120,10 +120,10 @@ Once it's working, it's a good idea to manually experiment with the new Insight
120
120
 
121
121
  ## Debugging & Local Development
122
122
 
123
- To test the AI code, you must be running DevTools in a branded Chrome (e.g. official Chrome Canary) and be signed in. You can use the `npm start` with a canary flag in this repository to run against Chromium, or by running Chrome Canary and pointing it to your local DevTools:
123
+ To test the AI code, you must be running DevTools in a branded Chrome (e.g. official Chrome Canary) and be signed in. You can use the `./dt start` command with a canary flag in this repository to run against Chromium, or by running Chrome Canary and pointing it to your local DevTools:
124
124
 
125
125
  ```
126
- npm start -- --browser=canary
126
+ ./dt start --browser=canary
127
127
  ```
128
128
 
129
129
  or
@@ -10,7 +10,7 @@ Content:
10
10
  },
11
11
  {
12
12
  "title": "Response",
13
- "text": "Response headers:\ncontent-type: bar2\nx-forwarded-for: bar3\n\nResponse body:\n{\"request\":\"body\"}\n\nResponse status: 200\nNetwork request status: pending\n"
13
+ "text": "Response headers:\ncontent-type: bar2\nx-forwarded-for: bar3\n\nResponse body:\n{\"request\":\"body\"}\n\nResponse status: 200\nNetwork request status: finished\n"
14
14
  },
15
15
  {
16
16
  "title": "Timing",
@@ -59,7 +59,7 @@ Content:
59
59
  {
60
60
  "parts": [
61
61
  {
62
- "text": "# Selected network request\nRequest: https://www.example.com\nRequest headers:\ncontent-type: bar1\n\nResponse headers:\ncontent-type: bar2\nx-forwarded-for: bar3\n\nResponse body:\n{\"request\":\"body\"}\n\nResponse status: 200\nNetwork request status: pending\n\nRequest timing:\nQueued at (timestamp): 0 s\nStarted at (timestamp): 501 s\nQueueing (duration): 501 s\nConnection start (stalled) (duration): 800 ms\nRequest sent (duration): 100 ms\nDuration (duration): 501 s\n\nRequest initiator chain:\n- URL: <redacted cross-origin initiator URL>\n\t- URL: https://www.example.com\n\t\t- URL: https://www.example.com/1\n\t\t- URL: https://www.example.com/2\n\n# User request\n\ntest"
62
+ "text": "# Selected network request\nRequest: https://www.example.com\nRequest headers:\ncontent-type: bar1\n\nResponse headers:\ncontent-type: bar2\nx-forwarded-for: bar3\n\nResponse body:\n{\"request\":\"body\"}\n\nResponse status: 200\nNetwork request status: finished\n\nRequest timing:\nQueued at (timestamp): 0 s\nStarted at (timestamp): 501 s\nQueueing (duration): 501 s\nConnection start (stalled) (duration): 800 ms\nRequest sent (duration): 100 ms\nDuration (duration): 501 s\n\nRequest initiator chain:\n- URL: <redacted cross-origin initiator URL>\n\t- URL: https://www.example.com\n\t\t- URL: https://www.example.com/1\n\t\t- URL: https://www.example.com/2\n\n# User request\n\ntest"
63
63
  }
64
64
  ],
65
65
  "role": 1
@@ -78,7 +78,7 @@ Content:
78
78
  "properties": {
79
79
  "code": {
80
80
  "type": 1,
81
- "description": "JavaScript code snippet to run on the inspected page. Make sure the code is formatted for readability.\n\n# Instructions\n\n* To return data, define a top-level `data` variable and populate it with data you want to get. Only JSON-serializable objects can be assigned to `data`.\n* $0 refers to the currently selected DOM node (or document body if none selected).\n* **CRITICAL** Never assume a selector for the elements unless you verified your knowledge.\n* **CRITICAL** Variables from previous function calls are not available in subsequent calls.\n* **CRITICAL** Keep code concise (max 40 lines and 2,500 characters). Split complex logic into multiple steps.\n* **CRITICAL** Network requests (e.g., fetch, XMLHttpRequest) are disabled and cannot be made.\n\nFor example, to query DOM elements:\n\n```\nconst data = {\n title: document.title,\n elementText: $0?.textContent?.trim(),\n childCount: $0?.children.length,\n};\n```\n"
81
+ "description": "JavaScript code snippet to run on the inspected page. Make sure the code is formatted for readability.\n\n# Instructions\n\n* To return data, define a top-level `data` variable and populate it with data you want to get. Only JSON-serializable objects can be assigned to `data`.\n* $0 refers to the currently selected DOM node (or document body if none selected).\n* **CRITICAL** Never assume a selector for the elements unless you verified your knowledge.\n* **CRITICAL** Variables from previous function calls are not available in subsequent calls.\n* **CRITICAL** Keep code concise (max 40 lines, 120 characters per line, and 2,500 characters). Split complex logic into multiple steps.\n* **CRITICAL** Network requests (e.g., fetch, XMLHttpRequest) are disabled and cannot be made.\n\nFor example, to query DOM elements:\n\n```\nconst data = {\n title: document.title,\n elementText: $0?.textContent?.trim(),\n childCount: $0?.children.length,\n};\n```\n"
82
82
  },
83
83
  "explanation": {
84
84
  "type": 1,
@@ -12,7 +12,7 @@ Response body:
12
12
  {}
13
13
 
14
14
  Response status: 200
15
- Network request status: pending
15
+ Network request status: finished
16
16
 
17
17
  Request timing:
18
18
  Queued at (timestamp): 0 s
@@ -33,7 +33,7 @@ Content:
33
33
  },
34
34
  {
35
35
  "title": "Response",
36
- "text": "Response headers:\nContent-Type: application/json\n\nResponse body:\n{}\n\nResponse status: 200\nNetwork request status: pending\n"
36
+ "text": "Response headers:\nContent-Type: application/json\n\nResponse body:\n{}\n\nResponse status: 200\nNetwork request status: finished\n"
37
37
  },
38
38
  {
39
39
  "title": "Timing",
@@ -98,6 +98,13 @@ export class NetworkRequestFormatter {
98
98
 
99
99
  static async formatBody(title: string, request: SDK.NetworkRequest.NetworkRequest, maxBodySize: number):
100
100
  Promise<string> {
101
+ // requestContentData() waits until the request finishes. Some requests
102
+ // never finish: Chrome does not send loadingFinished for a
103
+ // `Cache-Control: no-store` fetch() whose body the page never reads.
104
+ if (!request.finished) {
105
+ return `${title}\n<not available: the request has not finished loading>`;
106
+ }
107
+
101
108
  const data = await request.requestContentData();
102
109
 
103
110
  if (TextUtils.ContentData.ContentData.isError(data)) {
@@ -472,8 +472,9 @@ export class PerformanceTraceFormatter {
472
472
  description: this.#getSerializeBottomUpRootNodeFormat(limit),
473
473
  empty: 'no activity',
474
474
  cb: async insightSet => {
475
+ const navigationId = Trace.Helpers.Trace.navigationIDForEvent(insightSet.navigation);
475
476
  const rootNode = AIQueries.mainThreadActivityBottomUpSingleNavigation(
476
- insightSet.navigation?.args.data?.navigationId,
477
+ navigationId,
477
478
  insightSet.bounds,
478
479
  parsedTrace,
479
480
  );
@@ -519,8 +520,8 @@ export class PerformanceTraceFormatter {
519
520
  title: 'Longest tasks',
520
521
  empty: 'none',
521
522
  cb: async insightSet => {
522
- const longestTaskTrees =
523
- AIQueries.longestTasks(insightSet.navigation?.args.data?.navigationId, insightSet.bounds, parsedTrace, 3);
523
+ const navigationId = Trace.Helpers.Trace.navigationIDForEvent(insightSet.navigation);
524
+ const longestTaskTrees = AIQueries.longestTasks(navigationId, insightSet.bounds, parsedTrace, 3);
524
525
  if (!longestTaskTrees?.length) {
525
526
  return null;
526
527
  }
@@ -585,7 +586,7 @@ export class PerformanceTraceFormatter {
585
586
  if (Trace.Types.Events.isSyntheticNetworkRequest(event)) {
586
587
  const safeUrl = filteredUrl(event, this.#parsedTrace);
587
588
  if (safeUrl !== event.args.data.url) {
588
- return Trace.Name.forEntry({
589
+ const syntheticEvent: Trace.Types.Events.SyntheticNetworkRequest = {
589
590
  ...event,
590
591
  args: {
591
592
  ...event.args,
@@ -594,7 +595,8 @@ export class PerformanceTraceFormatter {
594
595
  url: safeUrl,
595
596
  },
596
597
  },
597
- });
598
+ };
599
+ return Trace.Name.forEntry(syntheticEvent);
598
600
  }
599
601
  }
600
602
  return Trace.Name.forEntry(event);
@@ -609,8 +611,9 @@ export class PerformanceTraceFormatter {
609
611
 
610
612
  const insightSet = this.#parsedTrace.insights?.values().find(
611
613
  insightSet => Trace.Helpers.Timing.boundsIncludeTimeRange({bounds, timeRange: insightSet.bounds}));
614
+ const navigationId = Trace.Helpers.Trace.navigationIDForEvent(insightSet?.navigation);
612
615
  const topDownTree = AIQueries.mainThreadActivityTopDown(
613
- insightSet?.navigation?.args.data?.navigationId,
616
+ navigationId,
614
617
  bounds,
615
618
  this.#parsedTrace,
616
619
  );
@@ -89,7 +89,8 @@ export class AIQueries {
89
89
  const threads: Trace.Handlers.Threads.ThreadData[] = [];
90
90
  if (parsedTrace.insights) {
91
91
  for (const insightSet of parsedTrace.insights?.values()) {
92
- const thread = this.findMainThread(insightSet.navigation?.args.data?.navigationId, parsedTrace);
92
+ const navigationId = Trace.Helpers.Trace.navigationIDForEvent(insightSet.navigation);
93
+ const thread = this.findMainThread(navigationId, parsedTrace);
93
94
  if (thread) {
94
95
  threads.push(thread);
95
96
  }