chrome-devtools-frontend 1.0.1520139 → 1.0.1521223

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 (35) hide show
  1. package/docs/checklist/README.md +80 -27
  2. package/front_end/core/host/GdpClient.ts +18 -6
  3. package/front_end/core/host/InspectorFrontendHost.ts +3 -0
  4. package/front_end/core/root/Runtime.ts +5 -0
  5. package/front_end/core/sdk/CSSModel.ts +8 -0
  6. package/front_end/core/sdk/CSSRule.ts +4 -0
  7. package/front_end/core/sdk/CSSStartingStyle.ts +29 -0
  8. package/front_end/core/sdk/DOMModel.ts +24 -1
  9. package/front_end/core/sdk/RehydratingConnection.snapshot.txt +209 -209
  10. package/front_end/core/sdk/RehydratingConnection.ts +5 -2
  11. package/front_end/core/sdk/sdk.ts +2 -0
  12. package/front_end/entrypoints/rehydrated_devtools_app/rehydrated_devtools_app.ts +1 -0
  13. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +2 -26
  14. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.snapshot.txt +32 -32
  15. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +11 -21
  16. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +32 -21
  17. package/front_end/models/badges/UserBadges.ts +5 -4
  18. package/front_end/models/trace/handlers/ScriptsHandler.ts +2 -1
  19. package/front_end/models/trace/insights/LegacyJavaScript.ts +2 -1
  20. package/front_end/panels/ai_assistance/components/ChatView.ts +1 -1
  21. package/front_end/panels/ai_assistance/components/chatView.css +1 -1
  22. package/front_end/panels/coverage/CoverageListView.ts +28 -55
  23. package/front_end/panels/coverage/CoverageView.ts +12 -5
  24. package/front_end/panels/elements/ComputedStyleModel.ts +2 -1
  25. package/front_end/panels/elements/ElementsTreeElement.ts +47 -0
  26. package/front_end/panels/elements/ElementsTreeOutline.ts +13 -0
  27. package/front_end/panels/elements/StylePropertiesSection.ts +16 -0
  28. package/front_end/panels/elements/components/AdornerManager.ts +7 -0
  29. package/front_end/panels/settings/components/SyncSection.ts +5 -17
  30. package/front_end/panels/timeline/components/ExportTraceOptions.ts +56 -4
  31. package/front_end/panels/timeline/components/exportTraceOptions.css +5 -0
  32. package/front_end/third_party/lighthouse/README.chromium +8 -1
  33. package/front_end/ui/legacy/InspectorView.ts +22 -15
  34. package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
  35. package/package.json +1 -1
@@ -7,7 +7,7 @@ This insight is used to analyze the time spent that contributed to the final LCP
7
7
 
8
8
  ## Detailed analysis:
9
9
  The Largest Contentful Paint (LCP) time for this navigation was 240 ms.
10
- The LCP element (IMG, nodeId: 23) is an image fetched from https://creativetouchrotherham.co.uk/images/creative-touch-home/creative_touch_rotherham_homehero/creative_touch_rotherham_homehero_700.webp (eventKey: s-1418).
10
+ The LCP element (IMG, nodeId: 23) is an image fetched from https://creativetouchrotherham.co.uk/images/creative-touch-home/creative_touch_rotherham_homehero/creative_touch_rotherham_homehero_700.webp (eventKey: s-1418, ts: 397824185142).
11
11
  ## LCP resource network request: https://creativetouchrotherham.co.uk/images/creative-touch-home/creative_touch_rotherham_homehero/creative_touch_rotherham_homehero_700.webp
12
12
  eventKey: s-1418
13
13
  Timings:
@@ -68,7 +68,7 @@ This insight is used to analyze the time spent that contributed to the final LCP
68
68
 
69
69
  ## Detailed analysis:
70
70
  The Largest Contentful Paint (LCP) time for this navigation was 129 ms.
71
- The LCP element is an image fetched from https://web-dev.imgix.net/image/kheDArv5csY6rvQUJDbWRscckLr1/4i7JstVZvgTFk9dxCe4a.svg (eventKey: s-1314).
71
+ The LCP element is an image fetched from https://web-dev.imgix.net/image/kheDArv5csY6rvQUJDbWRscckLr1/4i7JstVZvgTFk9dxCe4a.svg (eventKey: s-1314, ts: 122411037986).
72
72
  ## LCP resource network request: https://web-dev.imgix.net/image/kheDArv5csY6rvQUJDbWRscckLr1/4i7JstVZvgTFk9dxCe4a.svg
73
73
  eventKey: s-1314
74
74
  Timings:
@@ -186,7 +186,7 @@ It is important that all of these checks pass to minimize the delay between the
186
186
 
187
187
  ## Detailed analysis:
188
188
  The Largest Contentful Paint (LCP) time for this navigation was 1,077 ms.
189
- The LCP element is an image fetched from http://localhost:8787/lcp-discovery-delay/lcp-image.jpg (eventKey: s-25281).
189
+ The LCP element is an image fetched from http://localhost:8787/lcp-discovery-delay/lcp-image.jpg (eventKey: s-25281, ts: 197696859337).
190
190
  ## LCP resource network request: http://localhost:8787/lcp-discovery-delay/lcp-image.jpg
191
191
  eventKey: s-25281
192
192
  Timings:
@@ -310,7 +310,7 @@ Layout shifts in this cluster:
310
310
  - Start time: 472 ms
311
311
  - Score: 0.0003
312
312
  - Potential root causes:
313
- - A font that was loaded over the network: https://fonts.gstatic.com/s/specialgothicexpandedone/v2/IurO6Zxk74-YaYk1r3HOet4g75ENmBxUmOK61tA0Iu5QmJF_.woff2 (eventKey: s-1158).
313
+ - A font that was loaded over the network: https://fonts.gstatic.com/s/specialgothicexpandedone/v2/IurO6Zxk74-YaYk1r3HOet4g75ENmBxUmOK61tA0Iu5QmJF_.woff2 (eventKey: s-1158, ts: 1355335690947).
314
314
  ### Layout shift 2:
315
315
  - Start time: 857 ms
316
316
  - Score: 0.0844
@@ -320,12 +320,12 @@ Layout shifts in this cluster:
320
320
  - Start time: 1,352 ms
321
321
  - Score: 0.0068
322
322
  - Potential root causes:
323
- - An unsized image (IMG) (url: http://localhost:8000/unsized-image.png (eventKey: s-4487)).
323
+ - An unsized image (IMG) (url: http://localhost:8000/unsized-image.png (eventKey: s-4487, ts: 1355336697136)).
324
324
  ### Layout shift 4:
325
325
  - Start time: 1,537 ms
326
326
  - Score: 0.3344
327
327
  - Potential root causes:
328
- - An unsized image (IMG) (url: http://localhost:8000/unsized-image.png (eventKey: s-4487)).
328
+ - An unsized image (IMG) (url: http://localhost:8000/unsized-image.png (eventKey: s-4487, ts: 1355336697136)).
329
329
  ### Layout shift 5:
330
330
  - Start time: 2,343 ms
331
331
  - Score: 0.3396
@@ -334,7 +334,7 @@ Layout shifts in this cluster:
334
334
  ## Estimated savings: none
335
335
 
336
336
  ## External resources:
337
- - https://wdeb.dev/articles/cls
337
+ - https://web.dev/articles/cls
338
338
  - https://web.dev/articles/optimize-cls
339
339
  === end content
340
340
 
@@ -354,7 +354,7 @@ No layout shifts were found.
354
354
  ## Estimated savings: none
355
355
 
356
356
  ## External resources:
357
- - https://wdeb.dev/articles/cls
357
+ - https://web.dev/articles/cls
358
358
  - https://web.dev/articles/optimize-cls
359
359
  === end content
360
360
 
@@ -432,7 +432,7 @@ Layout shifts in this cluster:
432
432
  ## Estimated savings: none
433
433
 
434
434
  ## External resources:
435
- - https://wdeb.dev/articles/cls
435
+ - https://web.dev/articles/cls
436
436
  - https://web.dev/articles/optimize-cls
437
437
  === end content
438
438
 
@@ -687,16 +687,16 @@ Total legacy JavaScript: 8 files.
687
687
 
688
688
  Legacy JavaScript by file:
689
689
 
690
- - Script: https://s.yimg.com/aaq/benji/benji-2.2.99.js (eventKey: s-3387) - Wasted bytes: 37204 bytes
690
+ - Script: https://s.yimg.com/aaq/benji/benji-2.2.99.js (eventKey: s-3387, ts: 157423742567) - Wasted bytes: 37204 bytes
691
691
  Matches:
692
692
  Line: 0, Column: 133, Name: Promise.allSettled
693
693
 
694
- - Script: https://s.yimg.com/aaq/c/25fa214.caas-news_web.min.js (eventKey: s-3412) - Wasted bytes: 36084 bytes
694
+ - Script: https://s.yimg.com/aaq/c/25fa214.caas-news_web.min.js (eventKey: s-3412, ts: 157423743431) - Wasted bytes: 36084 bytes
695
695
  Matches:
696
696
  Line: 0, Column: 13310, Name: Array.from
697
697
  Line: 0, Column: 14623, Name: Object.assign
698
698
 
699
- - Script: https://s.yimg.com/du/ay/wnsrvbjmeprtfrnfx.js (eventKey: s-6273) - Wasted bytes: 12850 bytes
699
+ - Script: https://s.yimg.com/du/ay/wnsrvbjmeprtfrnfx.js (eventKey: s-6273, ts: 157423760794) - Wasted bytes: 12850 bytes
700
700
  Matches:
701
701
  Line: 111, Column: 7829, Name: @babel/plugin-transform-spread
702
702
  Line: 111, Column: 1794, Name: Array.prototype.find
@@ -705,21 +705,21 @@ Line: 111, Column: 2748, Name: Object.values
705
705
  Line: 111, Column: 2473, Name: String.prototype.includes
706
706
  Line: 111, Column: 2627, Name: String.prototype.startsWith
707
707
 
708
- - Script: https://static.criteo.net/js/ld/publishertag.prebid.144.js (eventKey: s-257378) - Wasted bytes: 10751 bytes
708
+ - Script: https://static.criteo.net/js/ld/publishertag.prebid.144.js (eventKey: s-257378, ts: 157426026656) - Wasted bytes: 10751 bytes
709
709
  Matches:
710
710
  Line: 1, Column: 74871, Name: Array.isArray
711
711
  Line: 1, Column: 75344, Name: Array.prototype.filter
712
712
  Line: 1, Column: 75013, Name: Array.prototype.indexOf
713
713
 
714
- - Script: https://s.yimg.com/oa/consent.js (eventKey: s-3384) - Wasted bytes: 8157 bytes
714
+ - Script: https://s.yimg.com/oa/consent.js (eventKey: s-3384, ts: 157423742450) - Wasted bytes: 8157 bytes
715
715
  Matches:
716
716
  Line: 1, Column: 132267, Name: Array.prototype.includes
717
717
 
718
- - Script: https://pm-widget.taboola.com/yahooweb-network/pmk-20220605.1.js (eventKey: s-52229) - Wasted bytes: 7625 bytes
718
+ - Script: https://pm-widget.taboola.com/yahooweb-network/pmk-20220605.1.js (eventKey: s-52229, ts: 157424128231) - Wasted bytes: 7625 bytes
719
719
  Matches:
720
720
  Line: 181, Column: 26, Name: Object.keys
721
721
 
722
- - Script: https://news.yahoo.com/ (eventKey: s-2116) - Wasted bytes: 7141 bytes
722
+ - Script: https://news.yahoo.com/ (eventKey: s-2116, ts: 157423489126) - Wasted bytes: 7141 bytes
723
723
  Matches:
724
724
  Line: 0, Column: 8382, Name: @babel/plugin-transform-classes
725
725
  Line: 0, Column: 107712, Name: Array.prototype.filter
@@ -727,7 +727,7 @@ Line: 0, Column: 107393, Name: Array.prototype.forEach
727
727
  Line: 0, Column: 108005, Name: Array.prototype.map
728
728
  Line: 0, Column: 108358, Name: String.prototype.includes
729
729
 
730
- - Script: https://cdn.taboola.com/libtrc/yahooweb-network/loader.js (eventKey: s-6352) - Wasted bytes: 7061 bytes
730
+ - Script: https://cdn.taboola.com/libtrc/yahooweb-network/loader.js (eventKey: s-6352, ts: 157423761978) - Wasted bytes: 7061 bytes
731
731
  Matches:
732
732
  Line: 0, Column: 390544, Name: Object.entries
733
733
  Line: 0, Column: 390688, Name: Object.values
@@ -767,12 +767,12 @@ This insight identifies font issues when a webpage uses custom fonts, for exampl
767
767
  ## Detailed analysis:
768
768
  The following font display issues were found:
769
769
 
770
- - Font name: jizaRExUiTo99u79D0KExcOPIDU.woff2, URL: https://fonts.gstatic.com/s/ptsans/v17/jizaRExUiTo99u79D0KExcOPIDU.woff2 (eventKey: s-5246), Property 'font-display' set to: 'auto', Wasted time: 20 ms.
771
- - Font name: SlGVmQWMvZQIdix7AFxXkHNSbRYXags.woff2, URL: https://fonts.gstatic.com/s/droidsans/v18/SlGVmQWMvZQIdix7AFxXkHNSbRYXags.woff2 (eventKey: s-5232), Property 'font-display' set to: 'auto', Wasted time: 15 ms.
772
- - Font name: jizfRExUiTo99u79B_mh0O6tLR8a8zI.woff2, URL: https://fonts.gstatic.com/s/ptsans/v17/jizfRExUiTo99u79B_mh0O6tLR8a8zI.woff2 (eventKey: s-5259), Property 'font-display' set to: 'auto', Wasted time: 15 ms.
773
- - Font name: SlGWmQWMvZQIdix7AFxXmMh3eDs1ZyHKpWg.woff2, URL: https://fonts.gstatic.com/s/droidsans/v18/SlGWmQWMvZQIdix7AFxXmMh3eDs1ZyHKpWg.woff2 (eventKey: s-5269), Property 'font-display' set to: 'auto', Wasted time: 15 ms.
774
- - Font name: EJRVQgYoZZY2vCFuvAFWzr-_dSb_.woff2, URL: https://fonts.gstatic.com/s/ptserif/v18/EJRVQgYoZZY2vCFuvAFWzr-_dSb_.woff2 (eventKey: s-5325), Property 'font-display' set to: 'auto', Wasted time: 15 ms.
775
- - Font name: S6u9w4BMUTPHh6UVSwiPGQ3q5d0.woff2, URL: https://fonts.gstatic.com/s/lato/v24/S6u9w4BMUTPHh6UVSwiPGQ3q5d0.woff2 (eventKey: s-5238), Property 'font-display' set to: 'auto', Wasted time: 10 ms.
770
+ - Font name: jizaRExUiTo99u79D0KExcOPIDU.woff2, URL: https://fonts.gstatic.com/s/ptsans/v17/jizaRExUiTo99u79D0KExcOPIDU.woff2 (eventKey: s-5246, ts: 409057956748), Property 'font-display' set to: 'auto', Wasted time: 20 ms.
771
+ - Font name: SlGVmQWMvZQIdix7AFxXkHNSbRYXags.woff2, URL: https://fonts.gstatic.com/s/droidsans/v18/SlGVmQWMvZQIdix7AFxXkHNSbRYXags.woff2 (eventKey: s-5232, ts: 409057956250), Property 'font-display' set to: 'auto', Wasted time: 15 ms.
772
+ - Font name: jizfRExUiTo99u79B_mh0O6tLR8a8zI.woff2, URL: https://fonts.gstatic.com/s/ptsans/v17/jizfRExUiTo99u79B_mh0O6tLR8a8zI.woff2 (eventKey: s-5259, ts: 409057957711), Property 'font-display' set to: 'auto', Wasted time: 15 ms.
773
+ - Font name: SlGWmQWMvZQIdix7AFxXmMh3eDs1ZyHKpWg.woff2, URL: https://fonts.gstatic.com/s/droidsans/v18/SlGWmQWMvZQIdix7AFxXmMh3eDs1ZyHKpWg.woff2 (eventKey: s-5269, ts: 409057958585), Property 'font-display' set to: 'auto', Wasted time: 15 ms.
774
+ - Font name: EJRVQgYoZZY2vCFuvAFWzr-_dSb_.woff2, URL: https://fonts.gstatic.com/s/ptserif/v18/EJRVQgYoZZY2vCFuvAFWzr-_dSb_.woff2 (eventKey: s-5325, ts: 409057959655), Property 'font-display' set to: 'auto', Wasted time: 15 ms.
775
+ - Font name: S6u9w4BMUTPHh6UVSwiPGQ3q5d0.woff2, URL: https://fonts.gstatic.com/s/lato/v24/S6u9w4BMUTPHh6UVSwiPGQ3q5d0.woff2 (eventKey: s-5238, ts: 409057956530), Property 'font-display' set to: 'auto', Wasted time: 10 ms.
776
776
 
777
777
  Consider setting [`font-display`](https://developer.chrome.com/blog/font-display) to `swap` or `optional` to ensure text is consistently visible. `swap` can be further optimized to mitigate layout shifts with [font metric overrides](https://developer.chrome.com/blog/font-fallbacks).
778
778
 
@@ -812,28 +812,28 @@ Total potential savings: 2 MB
812
812
 
813
813
  The following images could be optimized:
814
814
 
815
- ### https://images.ctfassets.net/u275ja1nivmq/6T6z40ay5GFCUtwV7DONgh/0e23606ed1692d9721ab0f39a8d8a99e/yeti_cover.jpg (eventKey: s-1212)
815
+ ### https://images.ctfassets.net/u275ja1nivmq/6T6z40ay5GFCUtwV7DONgh/0e23606ed1692d9721ab0f39a8d8a99e/yeti_cover.jpg (eventKey: s-1212, ts: 59728701403)
816
816
  - Potential savings: 1.1 MB
817
817
  - Optimizations:
818
818
  Using a modern image format (WebP, AVIF) or increasing the image compression could improve this image's download size. (Est 1.1 MB)
819
819
 
820
- ### https://raw.githubusercontent.com/GoogleChrome/lighthouse/refs/heads/main/cli/test/fixtures/dobetterweb/lighthouse-rotating.gif (eventKey: s-1216)
820
+ ### https://raw.githubusercontent.com/GoogleChrome/lighthouse/refs/heads/main/cli/test/fixtures/dobetterweb/lighthouse-rotating.gif (eventKey: s-1216, ts: 59728702014)
821
821
  - Potential savings: 682 kB
822
822
  - Optimizations:
823
823
  Using video formats instead of GIFs can improve the download size of animated content. (Est 682 kB)
824
824
 
825
- ### https://onlinepngtools.com/images/examples-onlinepngtools/elephant-hd-quality.png (eventKey: s-1228)
825
+ ### https://onlinepngtools.com/images/examples-onlinepngtools/elephant-hd-quality.png (eventKey: s-1228, ts: 59728702737)
826
826
  - Potential savings: 176 kB
827
827
  - Optimizations:
828
828
  Using a modern image format (WebP, AVIF) or increasing the image compression could improve this image's download size. (Est 134.1 kB)
829
829
  This image file is larger than it needs to be (640x436) for its displayed dimensions (200x136). Use responsive images to reduce the image download size. (Est 162.9 kB)
830
830
 
831
- ### https://images.ctfassets.net/u275ja1nivmq/6T6z40ay5GFCUtwV7DONgh/0e23606ed1692d9721ab0f39a8d8a99e/yeti_cover.jpg?fm=webp (eventKey: s-1224)
831
+ ### https://images.ctfassets.net/u275ja1nivmq/6T6z40ay5GFCUtwV7DONgh/0e23606ed1692d9721ab0f39a8d8a99e/yeti_cover.jpg?fm=webp (eventKey: s-1224, ts: 59728702313)
832
832
  - Potential savings: 49.8 kB
833
833
  - Optimizations:
834
834
  Increasing the image compression factor could improve this image's download size. (Est 49.8 kB)
835
835
 
836
- ### https://raw.githubusercontent.com/GoogleChrome/lighthouse/refs/heads/main/cli/test/fixtures/byte-efficiency/lighthouse-2048x1356.webp (eventKey: s-1226)
836
+ ### https://raw.githubusercontent.com/GoogleChrome/lighthouse/refs/heads/main/cli/test/fixtures/byte-efficiency/lighthouse-2048x1356.webp (eventKey: s-1226, ts: 59728702539)
837
837
  - Potential savings: 41.4 kB
838
838
  - Optimizations:
839
839
  This image file is larger than it needs to be (2048x1356) for its displayed dimensions (200x132). Use responsive images to reduce the image download size. (Est 41.4 kB)
@@ -950,9 +950,9 @@ The network dependency tree checks found one or more problems.
950
950
  Max critical path latency is 5,015 ms
951
951
 
952
952
  The following is the critical request chain:
953
- - http://localhost:8787/lcp-iframes/index.html (eventKey: s-7103) (5,006 ms) (longest chain)
954
- - http://localhost:8787/lcp-iframes/app.js (eventKey: s-7225) (5,015 ms) (longest chain)
955
- - http://localhost:8787/lcp-iframes/app.css (eventKey: s-7213) (5,010 ms)
953
+ - http://localhost:8787/lcp-iframes/index.html (eventKey: s-7103, ts: 566777570990) (5,006 ms) (longest chain)
954
+ - http://localhost:8787/lcp-iframes/app.js (eventKey: s-7225, ts: 566782574106) (5,015 ms) (longest chain)
955
+ - http://localhost:8787/lcp-iframes/app.css (eventKey: s-7213, ts: 566782573909) (5,010 ms)
956
956
 
957
957
  no origins were preconnected.
958
958
 
@@ -1077,7 +1077,7 @@ This insight identifies static resources that are not cached effectively by the
1077
1077
  ## Detailed analysis:
1078
1078
  The following resources were associated with ineffficient cache policies:
1079
1079
 
1080
- - https://chromedevtools.github.io/performance-stories/lcp-large-image/app.css (eventKey: s-1106)
1080
+ - https://chromedevtools.github.io/performance-stories/lcp-large-image/app.css (eventKey: s-1106, ts: 658799777502)
1081
1081
  - Cache Time to Live (TTL): 600 seconds
1082
1082
  - Wasted bytes: 0 B
1083
1083
 
@@ -39,22 +39,13 @@ function getLCPData(parsedTrace: Trace.TraceModel.ParsedTrace, frameId: string,
39
39
  };
40
40
  }
41
41
 
42
- export class PerformanceInsightFormatter extends PerformanceTraceFormatter {
42
+ export class PerformanceInsightFormatter {
43
+ #traceFormatter: PerformanceTraceFormatter;
43
44
  #insight: Trace.Insights.Types.InsightModel;
44
45
  #parsedTrace: Trace.TraceModel.ParsedTrace;
45
46
 
46
- /**
47
- * A utility method because we dependency inject this formatter into
48
- * PerformanceTraceFormatter; this allows you to pass
49
- * PerformanceInsightFormatter.create rather than an anonymous
50
- * function that wraps the constructor.
51
- */
52
- static create(focus: AgentFocus, insight: Trace.Insights.Types.InsightModel): PerformanceInsightFormatter {
53
- return new PerformanceInsightFormatter(focus, insight);
54
- }
55
-
56
47
  constructor(focus: AgentFocus, insight: Trace.Insights.Types.InsightModel) {
57
- super(focus, null);
48
+ this.#traceFormatter = new PerformanceTraceFormatter(focus);
58
49
  this.#insight = insight;
59
50
  this.#parsedTrace = focus.parsedTrace;
60
51
  }
@@ -74,8 +65,7 @@ export class PerformanceInsightFormatter extends PerformanceTraceFormatter {
74
65
  }
75
66
 
76
67
  #formatRequestUrl(request: Trace.Types.Events.SyntheticNetworkRequest): string {
77
- const eventKey = this.eventsSerializer.keyForEvent(request);
78
- return `${request.args.data.url} (eventKey: ${eventKey})`;
68
+ return `${request.args.data.url} ${this.#traceFormatter.serializeEvent(request)}`;
79
69
  }
80
70
 
81
71
  #formatScriptUrl(script: Trace.Handlers.ModelHandlers.Scripts.Script): string {
@@ -122,8 +112,8 @@ export class PerformanceInsightFormatter extends PerformanceTraceFormatter {
122
112
 
123
113
  if (lcpRequest) {
124
114
  parts.push(`${theLcpElement} is an image fetched from ${this.#formatRequestUrl(lcpRequest)}.`);
125
- const request =
126
- this.formatNetworkRequests([lcpRequest], {verbose: true, customTitle: 'LCP resource network request'});
115
+ const request = this.#traceFormatter.formatNetworkRequests(
116
+ [lcpRequest], {verbose: true, customTitle: 'LCP resource network request'});
127
117
  parts.push(request);
128
118
  } else {
129
119
  parts.push(`${theLcpElement} is text and was not fetched from the network.`);
@@ -356,7 +346,7 @@ ${shiftsFormatted.join('\n')}`;
356
346
 
357
347
  return `${this.#lcpMetricSharedContext()}
358
348
 
359
- ${this.formatNetworkRequests([documentRequest], {
349
+ ${this.#traceFormatter.formatNetworkRequests([documentRequest], {
360
350
  verbose: true,
361
351
  customTitle: 'Document network request'
362
352
  })}
@@ -685,8 +675,8 @@ ${filesFormatted}`;
685
675
  */
686
676
  formatModernHttpInsight(insight: Trace.Insights.Models.ModernHTTP.ModernHTTPInsightModel): string {
687
677
  const requestSummary = (insight.http1Requests.length === 1) ?
688
- this.formatNetworkRequests(insight.http1Requests, {verbose: true}) :
689
- this.formatNetworkRequests(insight.http1Requests);
678
+ this.#traceFormatter.formatNetworkRequests(insight.http1Requests, {verbose: true}) :
679
+ this.#traceFormatter.formatNetworkRequests(insight.http1Requests);
690
680
 
691
681
  if (requestSummary.length === 0) {
692
682
  return 'There are no requests that were served over a legacy HTTP protocol.';
@@ -783,7 +773,7 @@ ${requestSummary}`;
783
773
  * @returns a string formatted for sending to Ask AI.
784
774
  */
785
775
  formatRenderBlockingInsight(insight: Trace.Insights.Models.RenderBlocking.RenderBlockingInsightModel): string {
786
- const requestSummary = this.formatNetworkRequests(insight.renderBlockingRequests);
776
+ const requestSummary = this.#traceFormatter.formatNetworkRequests(insight.renderBlockingRequests);
787
777
 
788
778
  if (requestSummary.length === 0) {
789
779
  return 'There are no network requests that are render blocking.';
@@ -1021,7 +1011,7 @@ ${this.#links()}`;
1021
1011
  #links(): string {
1022
1012
  switch (this.#insight.insightKey) {
1023
1013
  case 'CLSCulprits':
1024
- return `- https://wdeb.dev/articles/cls
1014
+ return `- https://web.dev/articles/cls
1025
1015
  - https://web.dev/articles/optimize-cls`;
1026
1016
  case 'DocumentLatency':
1027
1017
  return '- https://web.dev/articles/optimize-ttfb';
@@ -9,7 +9,7 @@ import type {AgentFocus} from '../performance/AIContext.js';
9
9
  import {AIQueries} from '../performance/AIQueries.js';
10
10
 
11
11
  import {NetworkRequestFormatter} from './NetworkRequestFormatter.js';
12
- import type {PerformanceInsightFormatter} from './PerformanceInsightFormatter.js';
12
+ import {PerformanceInsightFormatter} from './PerformanceInsightFormatter.js';
13
13
  import {bytes, micros, millis} from './UnitFormatters.js';
14
14
 
15
15
  export interface NetworkRequestFormatOptions {
@@ -17,31 +17,21 @@ export interface NetworkRequestFormatOptions {
17
17
  customTitle?: string;
18
18
  }
19
19
 
20
- type GetInsightFormatter = (focus: AgentFocus, model: Trace.Insights.Types.InsightModel) => PerformanceInsightFormatter;
21
-
22
20
  export class PerformanceTraceFormatter {
23
21
  #focus: AgentFocus;
24
22
  #parsedTrace: Trace.TraceModel.ParsedTrace;
25
23
  #insightSet: Trace.Insights.Types.InsightSet|null;
26
- #getInsightFormatter: GetInsightFormatter|null = null;
27
- protected eventsSerializer: Trace.EventsSerializer.EventsSerializer;
24
+ #eventsSerializer: Trace.EventsSerializer.EventsSerializer;
28
25
 
29
- /**
30
- * We inject the insight formatter because otherwise we get a circular
31
- * dependency between PerformanceInsightFormatter and
32
- * PerformanceTraceFormatter. This is OK in the browser build, but breaks when
33
- * we reuse this code in NodeJS for DevTools MCP.
34
- */
35
- constructor(focus: AgentFocus, getInsightFormatter: GetInsightFormatter|null) {
26
+ constructor(focus: AgentFocus) {
36
27
  this.#focus = focus;
37
28
  this.#parsedTrace = focus.parsedTrace;
38
29
  this.#insightSet = focus.insightSet;
39
- this.eventsSerializer = focus.eventsSerializer;
40
- this.#getInsightFormatter = getInsightFormatter;
30
+ this.#eventsSerializer = focus.eventsSerializer;
41
31
  }
42
32
 
43
33
  serializeEvent(event: Trace.Types.Events.Event): string {
44
- const key = this.eventsSerializer.keyForEvent(event);
34
+ const key = this.#eventsSerializer.keyForEvent(event);
45
35
  return `(eventKey: ${key}, ts: ${event.ts})`;
46
36
  }
47
37
 
@@ -188,10 +178,7 @@ export class PerformanceTraceFormatter {
188
178
  continue;
189
179
  }
190
180
 
191
- const formatter = this.#getInsightFormatter?.(this.#focus, model);
192
- if (!formatter) {
193
- continue;
194
- }
181
+ const formatter = new PerformanceInsightFormatter(this.#focus, model);
195
182
  if (!formatter.insightIsSupported()) {
196
183
  continue;
197
184
  }
@@ -584,7 +571,7 @@ export class PerformanceTraceFormatter {
584
571
  const initiators = this.#getInitiatorChain(parsedTrace, request);
585
572
  const initiatorUrls = initiators.map(initiator => initiator.args.data.url);
586
573
 
587
- const eventKey = this.eventsSerializer.keyForEvent(request);
574
+ const eventKey = this.#eventsSerializer.keyForEvent(request);
588
575
  const eventKeyLine = eventKey ? `eventKey: ${eventKey}\n` : '';
589
576
 
590
577
  return `${titlePrefix}: ${url}
@@ -639,6 +626,30 @@ Network requests data:
639
626
  return networkDataString + '\n\n' + urlsMapString + '\n\n' + allRequestsText;
640
627
  }
641
628
 
629
+ static callFrameDataFormatDescription = `Each call frame is presented in the following format:
630
+
631
+ 'id;eventKey;name;duration;selfTime;urlIndex;childRange;[S]'
632
+
633
+ Key definitions:
634
+
635
+ * id: A unique numerical identifier for the call frame. Never mention this id in the output to the user.
636
+ * eventKey: String that uniquely identifies this event in the flame chart.
637
+ * name: A concise string describing the call frame (e.g., 'Evaluate Script', 'render', 'fetchData').
638
+ * duration: The total execution time of the call frame, including its children.
639
+ * selfTime: The time spent directly within the call frame, excluding its children's execution.
640
+ * urlIndex: Index referencing the "All URLs" list. Empty if no specific script URL is associated.
641
+ * childRange: Specifies the direct children of this node using their IDs. If empty ('' or 'S' at the end), the node has no children. If a single number (e.g., '4'), the node has one child with that ID. If in the format 'firstId-lastId' (e.g., '4-5'), it indicates a consecutive range of child IDs from 'firstId' to 'lastId', inclusive.
642
+ * S: _Optional_. The letter 'S' terminates the line if that call frame was selected by the user.
643
+
644
+ Example Call Tree:
645
+
646
+ 1;r-123;main;500;100;;
647
+ 2;r-124;update;200;50;;3
648
+ 3;p-49575-15428179-2834-374;animate;150;20;0;4-5;S
649
+ 4;p-49575-15428179-3505-1162;calculatePosition;80;80;;
650
+ 5;p-49575-15428179-5391-2767;applyStyles;50;50;;
651
+ `;
652
+
642
653
  /**
643
654
  * Network requests format description that is sent to the model as a fact.
644
655
  */
@@ -731,7 +742,7 @@ The order of headers corresponds to an internal fixed list. If a header is not p
731
742
 
732
743
  const parts = [
733
744
  urlIndex,
734
- this.eventsSerializer.keyForEvent(request) ?? '',
745
+ this.#eventsSerializer.keyForEvent(request) ?? '',
735
746
  queuedTime,
736
747
  requestSentTime,
737
748
  downloadCompleteTime,
@@ -163,10 +163,11 @@ export class UserBadges extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
163
163
  return;
164
164
  }
165
165
 
166
- const [gdpProfile, isEligibleToCreateProfile] = await Promise.all([
167
- Host.GdpClient.GdpClient.instance().getProfile(),
168
- Host.GdpClient.GdpClient.instance().isEligibleToCreateProfile(),
169
- ]);
166
+ const gdpProfile = await Host.GdpClient.GdpClient.instance().getProfile();
167
+ let isEligibleToCreateProfile = Boolean(gdpProfile);
168
+ if (!gdpProfile) {
169
+ isEligibleToCreateProfile = await Host.GdpClient.GdpClient.instance().isEligibleToCreateProfile();
170
+ }
170
171
 
171
172
  // User does not have a GDP profile & not eligible to create one.
172
173
  // So, we don't activate any badges for them.
@@ -58,7 +58,7 @@ export function handleEvent(event: Types.Events.Event): void {
58
58
  const scriptId = String(scriptIdAsNumber) as Protocol.Runtime.ScriptId;
59
59
  const key = `${isolate}.${scriptId}`;
60
60
  return Platform.MapUtilities.getWithDefault(
61
- scriptById, key, () => ({isolate, scriptId, frame: '', ts: 0} as Script));
61
+ scriptById, key, () => ({isolate, scriptId, frame: '', ts: event.ts} as Script));
62
62
  };
63
63
 
64
64
  if (Types.Events.isRundownScriptCompiled(event) && event.args.data) {
@@ -74,6 +74,7 @@ export function handleEvent(event: Types.Events.Event): void {
74
74
  const {isolate, scriptId, url, sourceUrl, sourceMapUrl, sourceMapUrlElided} = event.args.data;
75
75
  const script = getOrMakeScript(isolate, scriptId);
76
76
  script.url = url;
77
+ script.ts = event.ts;
77
78
  if (sourceUrl) {
78
79
  script.sourceUrl = sourceUrl;
79
80
  }
@@ -87,7 +87,8 @@ export function generateInsight(
87
87
  return false;
88
88
  }
89
89
 
90
- return Helpers.Timing.timestampIsInBounds(context.bounds, script.ts);
90
+ return Helpers.Timing.timestampIsInBounds(context.bounds, script.ts) ||
91
+ (script.request && Helpers.Timing.eventIsInBounds(script.request, context.bounds));
91
92
  });
92
93
 
93
94
  const legacyJavaScriptResults: LegacyJavaScriptResults = new Map();
@@ -1530,7 +1530,7 @@ function renderAidaUnavailableContents(
1530
1530
  }
1531
1531
 
1532
1532
  function renderConsentViewContents(): Lit.TemplateResult {
1533
- const settingsLink = document.createElement('button');
1533
+ const settingsLink = document.createElement('span');
1534
1534
  settingsLink.textContent = i18nString(UIStrings.settingsLink);
1535
1535
  settingsLink.classList.add('link');
1536
1536
  UI.ARIAUtils.markAsLink(settingsLink);
@@ -674,7 +674,7 @@ main {
674
674
  justify-content: center;
675
675
  font: var(--sys-typescale-headline4);
676
676
  gap: var(--sys-size-8);
677
- padding: var(--sys-size-3);
677
+ padding: var(--sys-size-4);
678
678
  max-width: var(--sys-size-33);
679
679
 
680
680
  /* Prevents the container from jumping when the scrollbar is shown */
@@ -141,15 +141,13 @@ export function coverageTypeToString(type: CoverageType): string {
141
141
 
142
142
  export class CoverageListView extends UI.Widget.VBox {
143
143
  private readonly nodeForUrl: Map<Platform.DevToolsPath.UrlString, GridNode>;
144
- private readonly isVisibleFilter: (arg0: CoverageListItem) => boolean;
145
144
  private highlightRegExp: RegExp|null;
146
145
  private dataGrid: DataGrid.SortableDataGrid.SortableDataGrid<GridNode>;
147
146
 
148
- constructor(isVisibleFilter: (arg0: CoverageListItem) => boolean) {
147
+ constructor() {
149
148
  super({useShadowDom: true});
150
149
  this.registerRequiredCSS(coverageListViewStyles);
151
150
  this.nodeForUrl = new Map();
152
- this.isVisibleFilter = isVisibleFilter;
153
151
  this.highlightRegExp = null;
154
152
 
155
153
  const columns = [
@@ -209,30 +207,37 @@ export class CoverageListView extends UI.Widget.VBox {
209
207
  this.setDefaultFocusedChild(dataGridWidget);
210
208
  }
211
209
 
212
- update(coverageInfo: CoverageListItem[] = []): void {
213
- let hadUpdates = false;
210
+ update(coverageInfo: CoverageListItem[], highlightRegExp: RegExp|null): void {
211
+ this.highlightRegExp = highlightRegExp;
214
212
  const maxSize = coverageInfo.reduce((acc, entry) => Math.max(acc, entry.size), 0);
215
- const rootNode = this.dataGrid.rootNode();
213
+
214
+ const coverageUrls = new Set(coverageInfo.map(info => info.url));
215
+ for (const [url, node] of this.nodeForUrl.entries()) {
216
+ if (!coverageUrls.has(url)) {
217
+ node.remove();
218
+ this.nodeForUrl.delete(url);
219
+ }
220
+ }
221
+
222
+ let hadUpdates = false;
216
223
  for (const entry of coverageInfo) {
217
224
  let node = this.nodeForUrl.get(entry.url);
218
225
  if (node) {
219
- if (this.isVisibleFilter(node.coverageInfo)) {
220
- hadUpdates = node.refreshIfNeeded(maxSize, entry) || hadUpdates;
221
- if (entry.sources.length > 0) {
222
- this.updateSourceNodes(entry.sources, maxSize, node);
223
- }
226
+ hadUpdates = node.refreshIfNeeded(maxSize, entry) || hadUpdates;
227
+ if (entry.sources.length > 0) {
228
+ this.updateSourceNodes(entry.sources, maxSize, node);
224
229
  }
230
+ node.setHighlight(this.highlightRegExp);
225
231
  continue;
226
232
  }
227
233
  node = new GridNode(entry, maxSize);
228
234
  this.nodeForUrl.set(entry.url, node);
229
- if (this.isVisibleFilter(node.coverageInfo)) {
230
- rootNode.appendChild(node);
231
- if (entry.sources.length > 0) {
232
- void this.createSourceNodes(entry.sources, maxSize, node);
233
- }
234
- hadUpdates = true;
235
+ this.appendNodeByType(node);
236
+ if (entry.sources.length > 0) {
237
+ this.updateSourceNodes(entry.sources, maxSize, node);
235
238
  }
239
+ node.setHighlight(this.highlightRegExp);
240
+ hadUpdates = true;
236
241
  }
237
242
  if (hadUpdates) {
238
243
  this.dataGrid.dispatchEventToListeners(DataGrid.DataGrid.Events.SORTING_CHANGED);
@@ -240,27 +245,16 @@ export class CoverageListView extends UI.Widget.VBox {
240
245
  }
241
246
 
242
247
  updateSourceNodes(sources: CoverageListItem[], maxSize: number, node: GridNode): void {
243
- let shouldCreateSourceNodes = false;
244
248
  for (const coverageInfo of sources) {
245
249
  const sourceNode = this.nodeForUrl.get(coverageInfo.url);
246
250
  if (sourceNode) {
247
251
  sourceNode.refreshIfNeeded(maxSize, coverageInfo);
248
252
  } else {
249
- shouldCreateSourceNodes = true;
250
- break;
253
+ const sourceNode = new GridNode(coverageInfo, maxSize);
254
+ node.appendChild(sourceNode);
255
+ this.nodeForUrl.set(coverageInfo.url, sourceNode);
251
256
  }
252
257
  }
253
- if (shouldCreateSourceNodes) {
254
- void this.createSourceNodes(sources, maxSize, node);
255
- }
256
- }
257
-
258
- async createSourceNodes(sources: CoverageListItem[], maxSize: number, node: GridNode): Promise<void> {
259
- for (const coverageInfo of sources) {
260
- const sourceNode = new GridNode(coverageInfo, maxSize);
261
- node.appendChild(sourceNode);
262
- this.nodeForUrl.set(coverageInfo.url, sourceNode);
263
- }
264
258
  }
265
259
 
266
260
  reset(): void {
@@ -268,30 +262,6 @@ export class CoverageListView extends UI.Widget.VBox {
268
262
  this.dataGrid.rootNode().removeChildren();
269
263
  }
270
264
 
271
- updateFilterAndHighlight(highlightRegExp: RegExp|null): void {
272
- this.highlightRegExp = highlightRegExp;
273
- let hadTreeUpdates = false;
274
- for (const node of this.nodeForUrl.values()) {
275
- const shouldBeVisible = this.isVisibleFilter(node.coverageInfo);
276
- const isVisible = Boolean(node.parent);
277
- if (shouldBeVisible) {
278
- node.setHighlight(this.highlightRegExp);
279
- }
280
- if (shouldBeVisible === isVisible) {
281
- continue;
282
- }
283
- hadTreeUpdates = true;
284
- if (!shouldBeVisible) {
285
- node.remove();
286
- } else {
287
- this.appendNodeByType(node);
288
- }
289
- }
290
- if (hadTreeUpdates) {
291
- this.dataGrid.dispatchEventToListeners(DataGrid.DataGrid.Events.SORTING_CHANGED);
292
- }
293
- }
294
-
295
265
  private appendNodeByType(node: GridNode): void {
296
266
  if (node.coverageInfo.generatedUrl) {
297
267
  const parentNode = this.nodeForUrl.get(node.coverageInfo.generatedUrl);
@@ -381,6 +351,9 @@ export class GridNode extends DataGrid.SortableDataGrid.SortableDataGridNode<Gri
381
351
  return;
382
352
  }
383
353
  this.highlightRegExp = highlightRegExp;
354
+ for (const child of this.children) {
355
+ (child as GridNode).setHighlight(this.highlightRegExp);
356
+ }
384
357
  this.refresh();
385
358
  }
386
359
 
@@ -261,7 +261,7 @@ export class CoverageView extends UI.Widget.VBox {
261
261
  this.bfcacheReloadPromptPage = this.buildReloadPromptPage(i18nString(UIStrings.bfcacheNoCapture), 'bfcache-page');
262
262
  this.activationReloadPromptPage =
263
263
  this.buildReloadPromptPage(i18nString(UIStrings.activationNoCapture), 'prerender-page');
264
- this.listView = new CoverageListView(this.isVisible.bind(this));
264
+ this.listView = new CoverageListView();
265
265
 
266
266
  this.statusToolbarElement = this.contentElement.createChild('div', 'coverage-toolbar-summary');
267
267
  this.statusMessageElement = this.statusToolbarElement.createChild('div', 'coverage-message');
@@ -442,7 +442,14 @@ export class CoverageView extends UI.Widget.VBox {
442
442
  }
443
443
 
444
444
  private updateListView(): void {
445
- this.listView.update(this.model?.entries().map(this.toCoverageListItem, this) || []);
445
+ const entries =
446
+ (this.model?.entries() || [])
447
+ .map(entry => this.toCoverageListItem(entry))
448
+ .filter(info => this.isVisible(info))
449
+ .map(
450
+ (entry: CoverageListItem) =>
451
+ ({...entry, sources: entry.sources.filter((entry: CoverageListItem) => this.isVisible(entry))}));
452
+ this.listView.update(entries, this.textFilterRegExp);
446
453
  }
447
454
 
448
455
  private toCoverageListItem(info: URLCoverageInfo): CoverageListItem {
@@ -536,7 +543,7 @@ export class CoverageView extends UI.Widget.VBox {
536
543
 
537
544
  private updateViews(updatedEntries: CoverageInfo[]): void {
538
545
  this.updateStats();
539
- this.listView.update(this.model?.entries().map(this.toCoverageListItem, this) || []);
546
+ this.updateListView();
540
547
  this.exportAction.setEnabled(this.model !== null && this.model.entries().length > 0);
541
548
  this.decorationManager?.update(updatedEntries);
542
549
  }
@@ -588,7 +595,7 @@ export class CoverageView extends UI.Widget.VBox {
588
595
  }
589
596
  const text = this.filterInput.value();
590
597
  this.textFilterRegExp = text ? Platform.StringUtilities.createPlainTextSearchRegex(text, 'i') : null;
591
- this.listView.updateFilterAndHighlight(this.textFilterRegExp);
598
+ this.updateListView();
592
599
  this.updateStats();
593
600
  }
594
601
 
@@ -602,7 +609,7 @@ export class CoverageView extends UI.Widget.VBox {
602
609
  const option = this.filterByTypeComboBox.selectedOption();
603
610
  const type = option?.value;
604
611
  this.typeFilterValue = parseInt(type || '', 10) || null;
605
- this.listView.updateFilterAndHighlight(this.textFilterRegExp);
612
+ this.updateListView();
606
613
  this.updateStats();
607
614
  }
608
615