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.
- package/docs/checklist/README.md +80 -27
- package/front_end/core/host/GdpClient.ts +18 -6
- package/front_end/core/host/InspectorFrontendHost.ts +3 -0
- package/front_end/core/root/Runtime.ts +5 -0
- package/front_end/core/sdk/CSSModel.ts +8 -0
- package/front_end/core/sdk/CSSRule.ts +4 -0
- package/front_end/core/sdk/CSSStartingStyle.ts +29 -0
- package/front_end/core/sdk/DOMModel.ts +24 -1
- package/front_end/core/sdk/RehydratingConnection.snapshot.txt +209 -209
- package/front_end/core/sdk/RehydratingConnection.ts +5 -2
- package/front_end/core/sdk/sdk.ts +2 -0
- package/front_end/entrypoints/rehydrated_devtools_app/rehydrated_devtools_app.ts +1 -0
- package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +2 -26
- package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.snapshot.txt +32 -32
- package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +11 -21
- package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +32 -21
- package/front_end/models/badges/UserBadges.ts +5 -4
- package/front_end/models/trace/handlers/ScriptsHandler.ts +2 -1
- package/front_end/models/trace/insights/LegacyJavaScript.ts +2 -1
- package/front_end/panels/ai_assistance/components/ChatView.ts +1 -1
- package/front_end/panels/ai_assistance/components/chatView.css +1 -1
- package/front_end/panels/coverage/CoverageListView.ts +28 -55
- package/front_end/panels/coverage/CoverageView.ts +12 -5
- package/front_end/panels/elements/ComputedStyleModel.ts +2 -1
- package/front_end/panels/elements/ElementsTreeElement.ts +47 -0
- package/front_end/panels/elements/ElementsTreeOutline.ts +13 -0
- package/front_end/panels/elements/StylePropertiesSection.ts +16 -0
- package/front_end/panels/elements/components/AdornerManager.ts +7 -0
- package/front_end/panels/settings/components/SyncSection.ts +5 -17
- package/front_end/panels/timeline/components/ExportTraceOptions.ts +56 -4
- package/front_end/panels/timeline/components/exportTraceOptions.css +5 -0
- package/front_end/third_party/lighthouse/README.chromium +8 -1
- package/front_end/ui/legacy/InspectorView.ts +22 -15
- package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
- package/package.json +1 -1
package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.snapshot.txt
CHANGED
@@ -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://
|
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://
|
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://
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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://
|
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
|
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
|
-
#
|
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
|
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
|
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 =
|
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
|
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
|
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
|
167
|
-
|
168
|
-
|
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:
|
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('
|
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-
|
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(
|
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[]
|
213
|
-
|
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
|
-
|
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
|
-
|
220
|
-
|
221
|
-
|
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
|
-
|
230
|
-
|
231
|
-
|
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
|
-
|
250
|
-
|
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(
|
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
|
-
|
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.
|
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.
|
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.
|
612
|
+
this.updateListView();
|
606
613
|
this.updateStats();
|
607
614
|
}
|
608
615
|
|