@paulirish/trace_engine 0.0.32 → 0.0.34

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 (309) hide show
  1. package/.tmp/tsbuildinfo/analyze-trace.d.mts +18 -0
  2. package/.tmp/tsbuildinfo/analyze-trace.d.mts.map +1 -0
  3. package/.tmp/tsbuildinfo/models/trace/LanternComputationData.d.ts +46 -0
  4. package/.tmp/tsbuildinfo/models/trace/LanternComputationData.d.ts.map +1 -0
  5. package/.tmp/tsbuildinfo/models/trace/LegacyTracingModel.d.ts +2 -0
  6. package/.tmp/tsbuildinfo/models/trace/LegacyTracingModel.d.ts.map +1 -0
  7. package/.tmp/tsbuildinfo/models/trace/ModelImpl.d.ts +72 -0
  8. package/.tmp/tsbuildinfo/models/trace/ModelImpl.d.ts.map +1 -0
  9. package/.tmp/tsbuildinfo/models/trace/Processor.d.ts +25 -0
  10. package/.tmp/tsbuildinfo/models/trace/Processor.d.ts.map +1 -0
  11. package/.tmp/tsbuildinfo/models/trace/TracingManager.d.ts +2 -0
  12. package/.tmp/tsbuildinfo/models/trace/TracingManager.d.ts.map +1 -0
  13. package/.tmp/tsbuildinfo/models/trace/trace.d.ts +13 -0
  14. package/.tmp/tsbuildinfo/models/trace/trace.d.ts.map +1 -0
  15. package/.tmp/tsbuildinfo/test/test-trace-engine.d.mts +2 -0
  16. package/.tmp/tsbuildinfo/test/test-trace-engine.d.mts.map +1 -0
  17. package/.tmp/tsbuildinfo/third_party/third-party-web/third-party-web.d.ts +3 -0
  18. package/.tmp/tsbuildinfo/third_party/third-party-web/third-party-web.d.ts.map +1 -0
  19. package/.tmp/tsbuildinfo/tsconfig.tsbuildinfo +1 -0
  20. package/README.md +6 -10
  21. package/analyze-trace.mjs +51 -16
  22. package/core/platform/ArrayUtilities.js +1 -0
  23. package/core/platform/ArrayUtilities.js.map +1 -1
  24. package/core/platform/DevToolsPath.d.ts +1 -1
  25. package/core/platform/DevToolsPath.js.map +1 -1
  26. package/core/platform/MimeType.js +4 -2
  27. package/core/platform/MimeType.js.map +1 -1
  28. package/core/platform/NumberUtilities.js +8 -0
  29. package/core/platform/NumberUtilities.js.map +1 -1
  30. package/core/platform/ServerTiming.d.ts +31 -0
  31. package/core/platform/ServerTiming.js +212 -0
  32. package/core/platform/ServerTiming.js.map +1 -0
  33. package/core/platform/Timing.d.ts +1 -1
  34. package/core/platform/Timing.js.map +1 -1
  35. package/core/platform/TypescriptUtilities.d.ts +3 -0
  36. package/core/platform/TypescriptUtilities.js.map +1 -1
  37. package/core/platform/UIString.d.ts +1 -1
  38. package/core/platform/UIString.js.map +1 -1
  39. package/core/platform/UserVisibleError.d.ts +1 -1
  40. package/core/platform/UserVisibleError.js.map +1 -1
  41. package/core/platform/platform-tsconfig.json +1 -1
  42. package/core/platform/platform.d.ts +2 -2
  43. package/core/platform/platform.js +2 -2
  44. package/core/platform/platform.js.map +1 -1
  45. package/generated/protocol.d.ts +289 -16
  46. package/models/trace/LanternComputationData.d.ts +4 -4
  47. package/models/trace/LanternComputationData.js +22 -23
  48. package/models/trace/LanternComputationData.js.map +1 -1
  49. package/models/trace/ModelImpl.d.ts +11 -12
  50. package/models/trace/ModelImpl.js +22 -33
  51. package/models/trace/ModelImpl.js.map +1 -1
  52. package/models/trace/Processor.d.ts +21 -12
  53. package/models/trace/Processor.js +151 -67
  54. package/models/trace/Processor.js.map +1 -1
  55. package/models/trace/TracingManager.js.map +1 -1
  56. package/models/trace/extras/FetchNodes.d.ts +8 -8
  57. package/models/trace/extras/FetchNodes.js +16 -11
  58. package/models/trace/extras/FetchNodes.js.map +1 -1
  59. package/models/trace/extras/FilmStrip.d.ts +2 -2
  60. package/models/trace/extras/FilmStrip.js +8 -8
  61. package/models/trace/extras/FilmStrip.js.map +1 -1
  62. package/models/trace/extras/MainThreadActivity.d.ts +1 -1
  63. package/models/trace/extras/MainThreadActivity.js +1 -1
  64. package/models/trace/extras/MainThreadActivity.js.map +1 -1
  65. package/models/trace/extras/Metadata.js +2 -2
  66. package/models/trace/extras/Metadata.js.map +1 -1
  67. package/models/trace/extras/ThirdParties.d.ts +23 -0
  68. package/models/trace/extras/ThirdParties.js +152 -0
  69. package/models/trace/extras/ThirdParties.js.map +1 -0
  70. package/models/trace/extras/URLForEntry.d.ts +9 -1
  71. package/models/trace/extras/URLForEntry.js +18 -10
  72. package/models/trace/extras/URLForEntry.js.map +1 -1
  73. package/models/trace/extras/extras-tsconfig.json +1 -0
  74. package/models/trace/extras/extras.d.ts +4 -1
  75. package/models/trace/extras/extras.js +4 -1
  76. package/models/trace/extras/extras.js.map +1 -1
  77. package/models/trace/handlers/AnimationHandler.d.ts +2 -2
  78. package/models/trace/handlers/AnimationHandler.js +1 -1
  79. package/models/trace/handlers/AnimationHandler.js.map +1 -1
  80. package/models/trace/handlers/AuctionWorkletsHandler.d.ts +2 -2
  81. package/models/trace/handlers/AuctionWorkletsHandler.js +11 -11
  82. package/models/trace/handlers/AuctionWorkletsHandler.js.map +1 -1
  83. package/models/trace/handlers/ExtensionTraceDataHandler.d.ts +6 -6
  84. package/models/trace/handlers/ExtensionTraceDataHandler.js +12 -8
  85. package/models/trace/handlers/ExtensionTraceDataHandler.js.map +1 -1
  86. package/models/trace/handlers/FramesHandler.d.ts +34 -22
  87. package/models/trace/handlers/FramesHandler.js +54 -26
  88. package/models/trace/handlers/FramesHandler.js.map +1 -1
  89. package/models/trace/handlers/GPUHandler.d.ts +4 -4
  90. package/models/trace/handlers/GPUHandler.js +3 -3
  91. package/models/trace/handlers/GPUHandler.js.map +1 -1
  92. package/models/trace/handlers/ImagePaintingHandler.d.ts +3 -3
  93. package/models/trace/handlers/ImagePaintingHandler.js +6 -8
  94. package/models/trace/handlers/ImagePaintingHandler.js.map +1 -1
  95. package/models/trace/handlers/InitiatorsHandler.d.ts +3 -3
  96. package/models/trace/handlers/InitiatorsHandler.js +25 -14
  97. package/models/trace/handlers/InitiatorsHandler.js.map +1 -1
  98. package/models/trace/handlers/InvalidationsHandler.d.ts +4 -2
  99. package/models/trace/handlers/InvalidationsHandler.js +29 -11
  100. package/models/trace/handlers/InvalidationsHandler.js.map +1 -1
  101. package/models/trace/handlers/LargestImagePaintHandler.d.ts +2 -2
  102. package/models/trace/handlers/LargestImagePaintHandler.js +1 -1
  103. package/models/trace/handlers/LargestImagePaintHandler.js.map +1 -1
  104. package/models/trace/handlers/LargestTextPaintHandler.d.ts +2 -2
  105. package/models/trace/handlers/LargestTextPaintHandler.js +1 -1
  106. package/models/trace/handlers/LargestTextPaintHandler.js.map +1 -1
  107. package/models/trace/handlers/LayerTreeHandler.d.ts +6 -6
  108. package/models/trace/handlers/LayerTreeHandler.js +6 -6
  109. package/models/trace/handlers/LayerTreeHandler.js.map +1 -1
  110. package/models/trace/handlers/LayoutShiftsHandler.d.ts +14 -20
  111. package/models/trace/handlers/LayoutShiftsHandler.js +87 -12
  112. package/models/trace/handlers/LayoutShiftsHandler.js.map +1 -1
  113. package/models/trace/handlers/MemoryHandler.d.ts +2 -2
  114. package/models/trace/handlers/MemoryHandler.js +1 -1
  115. package/models/trace/handlers/MemoryHandler.js.map +1 -1
  116. package/models/trace/handlers/MetaHandler.d.ts +15 -14
  117. package/models/trace/handlers/MetaHandler.js +32 -30
  118. package/models/trace/handlers/MetaHandler.js.map +1 -1
  119. package/models/trace/handlers/ModelHandlers.d.ts +1 -1
  120. package/models/trace/handlers/ModelHandlers.js +1 -1
  121. package/models/trace/handlers/ModelHandlers.js.map +1 -1
  122. package/models/trace/handlers/NetworkRequestsHandler.d.ts +13 -12
  123. package/models/trace/handlers/NetworkRequestsHandler.js +68 -66
  124. package/models/trace/handlers/NetworkRequestsHandler.js.map +1 -1
  125. package/models/trace/handlers/PageFramesHandler.d.ts +2 -2
  126. package/models/trace/handlers/PageFramesHandler.js +2 -2
  127. package/models/trace/handlers/PageFramesHandler.js.map +1 -1
  128. package/models/trace/handlers/PageLoadMetricsHandler.d.ts +7 -7
  129. package/models/trace/handlers/PageLoadMetricsHandler.js +21 -24
  130. package/models/trace/handlers/PageLoadMetricsHandler.js.map +1 -1
  131. package/models/trace/handlers/RendererHandler.d.ts +19 -19
  132. package/models/trace/handlers/RendererHandler.js +5 -5
  133. package/models/trace/handlers/RendererHandler.js.map +1 -1
  134. package/models/trace/handlers/SamplesHandler.d.ts +6 -6
  135. package/models/trace/handlers/SamplesHandler.js +3 -3
  136. package/models/trace/handlers/SamplesHandler.js.map +1 -1
  137. package/models/trace/handlers/ScreenshotsHandler.d.ts +6 -4
  138. package/models/trace/handlers/ScreenshotsHandler.js +11 -9
  139. package/models/trace/handlers/ScreenshotsHandler.js.map +1 -1
  140. package/models/trace/handlers/SelectorStatsHandler.d.ts +3 -3
  141. package/models/trace/handlers/SelectorStatsHandler.js +2 -2
  142. package/models/trace/handlers/SelectorStatsHandler.js.map +1 -1
  143. package/models/trace/handlers/ServerTimingsHandler.d.ts +10 -0
  144. package/models/trace/handlers/ServerTimingsHandler.js +118 -0
  145. package/models/trace/handlers/ServerTimingsHandler.js.map +1 -0
  146. package/models/trace/handlers/Threads.d.ts +7 -7
  147. package/models/trace/handlers/Threads.js +5 -5
  148. package/models/trace/handlers/Threads.js.map +1 -1
  149. package/models/trace/handlers/UserInteractionsHandler.d.ts +13 -11
  150. package/models/trace/handlers/UserInteractionsHandler.js +13 -7
  151. package/models/trace/handlers/UserInteractionsHandler.js.map +1 -1
  152. package/models/trace/handlers/UserTimingsHandler.d.ts +5 -5
  153. package/models/trace/handlers/UserTimingsHandler.js +52 -9
  154. package/models/trace/handlers/UserTimingsHandler.js.map +1 -1
  155. package/models/trace/handlers/WarningsHandler.d.ts +5 -5
  156. package/models/trace/handlers/WarningsHandler.js +4 -5
  157. package/models/trace/handlers/WarningsHandler.js.map +1 -1
  158. package/models/trace/handlers/WorkersHandler.d.ts +4 -4
  159. package/models/trace/handlers/WorkersHandler.js +1 -1
  160. package/models/trace/handlers/WorkersHandler.js.map +1 -1
  161. package/models/trace/handlers/handlers-tsconfig.json +1 -1
  162. package/models/trace/handlers/types.d.ts +7 -7
  163. package/models/trace/handlers/types.js.map +1 -1
  164. package/models/trace/helpers/Extensions.d.ts +2 -2
  165. package/models/trace/helpers/Extensions.js.map +1 -1
  166. package/models/trace/helpers/Network.d.ts +2 -2
  167. package/models/trace/helpers/Network.js +19 -2
  168. package/models/trace/helpers/Network.js.map +1 -1
  169. package/models/trace/helpers/SamplesIntegrator.d.ts +5 -5
  170. package/models/trace/helpers/SamplesIntegrator.js +10 -11
  171. package/models/trace/helpers/SamplesIntegrator.js.map +1 -1
  172. package/models/trace/helpers/SyntheticEvents.d.ts +8 -14
  173. package/models/trace/helpers/SyntheticEvents.js +20 -31
  174. package/models/trace/helpers/SyntheticEvents.js.map +1 -1
  175. package/models/trace/helpers/Timing.d.ts +18 -4
  176. package/models/trace/helpers/Timing.js +43 -1
  177. package/models/trace/helpers/Timing.js.map +1 -1
  178. package/models/trace/helpers/Trace.d.ts +46 -32
  179. package/models/trace/helpers/Trace.js +53 -24
  180. package/models/trace/helpers/Trace.js.map +1 -1
  181. package/models/trace/helpers/TreeHelpers.d.ts +40 -8
  182. package/models/trace/helpers/TreeHelpers.js +147 -19
  183. package/models/trace/helpers/TreeHelpers.js.map +1 -1
  184. package/models/trace/helpers/helpers-tsconfig.json +3 -0
  185. package/models/trace/insights/Common.d.ts +4 -3
  186. package/models/trace/insights/Common.js +22 -7
  187. package/models/trace/insights/Common.js.map +1 -1
  188. package/models/trace/insights/CumulativeLayoutShift.d.ts +36 -13
  189. package/models/trace/insights/CumulativeLayoutShift.js +199 -73
  190. package/models/trace/insights/CumulativeLayoutShift.js.map +1 -1
  191. package/models/trace/insights/DocumentLatency.d.ts +9 -4
  192. package/models/trace/insights/DocumentLatency.js +82 -7
  193. package/models/trace/insights/DocumentLatency.js.map +1 -1
  194. package/models/trace/insights/FontDisplay.d.ts +11 -0
  195. package/models/trace/insights/FontDisplay.js +44 -0
  196. package/models/trace/insights/FontDisplay.js.map +1 -0
  197. package/models/trace/insights/InsightRunners.d.ts +3 -0
  198. package/models/trace/insights/InsightRunners.js +3 -0
  199. package/models/trace/insights/InsightRunners.js.map +1 -1
  200. package/models/trace/insights/InteractionToNextPaint.d.ts +4 -5
  201. package/models/trace/insights/InteractionToNextPaint.js +5 -3
  202. package/models/trace/insights/InteractionToNextPaint.js.map +1 -1
  203. package/models/trace/insights/LargestContentfulPaint.d.ts +20 -7
  204. package/models/trace/insights/LargestContentfulPaint.js +57 -37
  205. package/models/trace/insights/LargestContentfulPaint.js.map +1 -1
  206. package/models/trace/insights/RenderBlocking.d.ts +3 -3
  207. package/models/trace/insights/RenderBlocking.js +29 -24
  208. package/models/trace/insights/RenderBlocking.js.map +1 -1
  209. package/models/trace/insights/SlowCSSSelector.d.ts +11 -0
  210. package/models/trace/insights/SlowCSSSelector.js +67 -0
  211. package/models/trace/insights/SlowCSSSelector.js.map +1 -0
  212. package/models/trace/insights/ThirdPartyWeb.d.ts +13 -0
  213. package/models/trace/insights/ThirdPartyWeb.js +42 -0
  214. package/models/trace/insights/ThirdPartyWeb.js.map +1 -0
  215. package/models/trace/insights/Viewport.d.ts +5 -2
  216. package/models/trace/insights/Viewport.js +14 -9
  217. package/models/trace/insights/Viewport.js.map +1 -1
  218. package/models/trace/insights/insights-tsconfig.json +9 -0
  219. package/models/trace/insights/insights.d.ts +1 -0
  220. package/models/trace/insights/insights.js +1 -0
  221. package/models/trace/insights/insights.js.map +1 -1
  222. package/models/trace/insights/types.d.ts +43 -25
  223. package/models/trace/insights/types.js.map +1 -1
  224. package/models/trace/lantern/core/NetworkAnalyzer.d.ts +9 -9
  225. package/models/trace/lantern/core/NetworkAnalyzer.js +18 -15
  226. package/models/trace/lantern/core/NetworkAnalyzer.js.map +1 -1
  227. package/models/trace/lantern/graph/BaseNode.d.ts +4 -4
  228. package/models/trace/lantern/graph/BaseNode.js +21 -21
  229. package/models/trace/lantern/graph/BaseNode.js.map +1 -1
  230. package/models/trace/lantern/graph/CPUNode.d.ts +1 -1
  231. package/models/trace/lantern/graph/CPUNode.js +5 -5
  232. package/models/trace/lantern/graph/CPUNode.js.map +1 -1
  233. package/models/trace/lantern/graph/PageDependencyGraph.d.ts +4 -4
  234. package/models/trace/lantern/graph/PageDependencyGraph.js +5 -5
  235. package/models/trace/lantern/graph/PageDependencyGraph.js.map +1 -1
  236. package/models/trace/lantern/simulation/ConnectionPool.d.ts +7 -7
  237. package/models/trace/lantern/simulation/ConnectionPool.js +26 -26
  238. package/models/trace/lantern/simulation/ConnectionPool.js.map +1 -1
  239. package/models/trace/lantern/simulation/DNSCache.d.ts +3 -3
  240. package/models/trace/lantern/simulation/DNSCache.js +11 -11
  241. package/models/trace/lantern/simulation/DNSCache.js.map +1 -1
  242. package/models/trace/lantern/simulation/SimulationTimingMap.d.ts +1 -1
  243. package/models/trace/lantern/simulation/SimulationTimingMap.js +15 -15
  244. package/models/trace/lantern/simulation/SimulationTimingMap.js.map +1 -1
  245. package/models/trace/lantern/simulation/Simulator.d.ts +28 -28
  246. package/models/trace/lantern/simulation/Simulator.js +113 -113
  247. package/models/trace/lantern/simulation/Simulator.js.map +1 -1
  248. package/models/trace/lantern/simulation/TCPConnection.d.ts +9 -9
  249. package/models/trace/lantern/simulation/TCPConnection.js +36 -36
  250. package/models/trace/lantern/simulation/TCPConnection.js.map +1 -1
  251. package/models/trace/root-causes/LayoutShift.d.ts +13 -13
  252. package/models/trace/root-causes/LayoutShift.js +7 -25
  253. package/models/trace/root-causes/LayoutShift.js.map +1 -1
  254. package/models/trace/types/Configuration.d.ts +16 -0
  255. package/models/trace/types/Configuration.js +1 -0
  256. package/models/trace/types/Configuration.js.map +1 -1
  257. package/models/trace/types/Extensions.d.ts +9 -12
  258. package/models/trace/types/Extensions.js +2 -1
  259. package/models/trace/types/Extensions.js.map +1 -1
  260. package/models/trace/types/File.d.ts +55 -23
  261. package/models/trace/types/File.js +15 -3
  262. package/models/trace/types/File.js.map +1 -1
  263. package/models/trace/types/TraceEvents.d.ts +868 -713
  264. package/models/trace/types/TraceEvents.js +281 -276
  265. package/models/trace/types/TraceEvents.js.map +1 -1
  266. package/models/trace/types/types.d.ts +1 -1
  267. package/models/trace/types/types.js +1 -1
  268. package/models/trace/types/types.js.map +1 -1
  269. package/package.json +10 -4
  270. package/test/test-trace-engine.mjs +56 -3
  271. package/third_party/third-party-web/third-party-web.js +1 -0
  272. package/tsconfig.json +29 -0
  273. package/core/platform/PromiseUtilities.d.ts +0 -10
  274. package/core/platform/PromiseUtilities.js +0 -18
  275. package/core/platform/PromiseUtilities.js.map +0 -1
  276. package/core/platform/SetUtilities.d.ts +0 -2
  277. package/core/platform/SetUtilities.js +0 -23
  278. package/core/platform/SetUtilities.js.map +0 -1
  279. package/models/trace/EntriesFilter.d.ts +0 -72
  280. package/models/trace/EntriesFilter.js +0 -296
  281. package/models/trace/EntriesFilter.js.map +0 -1
  282. package/models/trace/LegacyTracingModel.js.map +0 -1
  283. package/models/trace/handlers/EnhancedTracesHandler.d.ts +0 -48
  284. package/models/trace/handlers/EnhancedTracesHandler.js +0 -165
  285. package/models/trace/handlers/EnhancedTracesHandler.js.map +0 -1
  286. package/models/trace/lantern/BaseNode.d.ts +0 -91
  287. package/models/trace/lantern/BaseNode.js +0 -268
  288. package/models/trace/lantern/BaseNode.js.map +0 -1
  289. package/models/trace/lantern/CPUNode.d.ts +0 -24
  290. package/models/trace/lantern/CPUNode.js +0 -64
  291. package/models/trace/lantern/CPUNode.js.map +0 -1
  292. package/models/trace/lantern/LanternError.d.ts +0 -3
  293. package/models/trace/lantern/LanternError.js +0 -7
  294. package/models/trace/lantern/LanternError.js.map +0 -1
  295. package/models/trace/lantern/MetricsModule.d.ts +0 -11
  296. package/models/trace/lantern/MetricsModule.js +0 -14
  297. package/models/trace/lantern/MetricsModule.js.map +0 -1
  298. package/models/trace/lantern/NetworkNode.d.ts +0 -22
  299. package/models/trace/lantern/NetworkNode.js +0 -83
  300. package/models/trace/lantern/NetworkNode.js.map +0 -1
  301. package/models/trace/lantern/PageDependencyGraph.d.ts +0 -43
  302. package/models/trace/lantern/PageDependencyGraph.js +0 -509
  303. package/models/trace/lantern/PageDependencyGraph.js.map +0 -1
  304. package/models/trace/lantern/SimulationModule.d.ts +0 -17
  305. package/models/trace/lantern/SimulationModule.js +0 -13
  306. package/models/trace/lantern/SimulationModule.js.map +0 -1
  307. package/models/trace/lantern/simulation/NetworkAnalyzer.d.ts +0 -112
  308. package/models/trace/lantern/simulation/NetworkAnalyzer.js +0 -486
  309. package/models/trace/lantern/simulation/NetworkAnalyzer.js.map +0 -1
@@ -9,26 +9,41 @@ import { InsightWarning } from './types.js';
9
9
  export function deps() {
10
10
  return ['NetworkRequests', 'PageLoadMetrics', 'LargestImagePaint', 'Meta'];
11
11
  }
12
- function breakdownPhases(nav, mainRequest, lcpMs, lcpRequest) {
13
- const mainReqTiming = mainRequest.args.data.timing;
14
- if (!mainReqTiming) {
15
- throw new Error('no timing for main resource');
12
+ function anyValuesNaN(...values) {
13
+ return values.some(v => Number.isNaN(v));
14
+ }
15
+ /**
16
+ * Calculates the 4 phases of an LCP and the timings of each.
17
+ * Will return `null` if any required values were missing. We don't ever expect
18
+ * them to be missing on newer traces, but old trace files may lack some of the
19
+ * data we rely on, so we want to handle that case.
20
+ */
21
+ function breakdownPhases(nav, docRequest, lcpMs, lcpRequest) {
22
+ const docReqTiming = docRequest.args.data.timing;
23
+ if (!docReqTiming) {
24
+ throw new Error('no timing for document request');
16
25
  }
17
- const firstDocByteTs = Helpers.Timing.secondsToMicroseconds(mainReqTiming.requestTime) +
18
- Helpers.Timing.millisecondsToMicroseconds(mainReqTiming.receiveHeadersStart);
26
+ const firstDocByteTs = Helpers.Timing.secondsToMicroseconds(docReqTiming.requestTime) +
27
+ Helpers.Timing.millisecondsToMicroseconds(docReqTiming.receiveHeadersStart);
19
28
  const firstDocByteTiming = Types.Timing.MicroSeconds(firstDocByteTs - nav.ts);
20
29
  const ttfb = Helpers.Timing.microSecondsToMilliseconds(firstDocByteTiming);
21
30
  let renderDelay = Types.Timing.MilliSeconds(lcpMs - ttfb);
22
31
  if (!lcpRequest) {
32
+ if (anyValuesNaN(ttfb, renderDelay)) {
33
+ return null;
34
+ }
23
35
  return { ttfb, renderDelay };
24
36
  }
25
37
  const lcpStartTs = Types.Timing.MicroSeconds(lcpRequest.ts - nav.ts);
26
- const resourceStart = Helpers.Timing.microSecondsToMilliseconds(lcpStartTs);
38
+ const requestStart = Helpers.Timing.microSecondsToMilliseconds(lcpStartTs);
27
39
  const lcpReqEndTs = Types.Timing.MicroSeconds(lcpRequest.args.data.syntheticData.finishTime - nav.ts);
28
- const resourceEnd = Helpers.Timing.microSecondsToMilliseconds(lcpReqEndTs);
29
- const loadDelay = Types.Timing.MilliSeconds(resourceStart - ttfb);
30
- const loadTime = Types.Timing.MilliSeconds(resourceEnd - resourceStart);
31
- renderDelay = Types.Timing.MilliSeconds(lcpMs - resourceEnd);
40
+ const requestEnd = Helpers.Timing.microSecondsToMilliseconds(lcpReqEndTs);
41
+ const loadDelay = Types.Timing.MilliSeconds(requestStart - ttfb);
42
+ const loadTime = Types.Timing.MilliSeconds(requestEnd - requestStart);
43
+ renderDelay = Types.Timing.MilliSeconds(lcpMs - requestEnd);
44
+ if (anyValuesNaN(ttfb, loadDelay, loadTime, renderDelay)) {
45
+ return null;
46
+ }
32
47
  return {
33
48
  ttfb,
34
49
  loadDelay,
@@ -36,13 +51,12 @@ function breakdownPhases(nav, mainRequest, lcpMs, lcpRequest) {
36
51
  renderDelay,
37
52
  };
38
53
  }
39
- export function generateInsight(traceParsedData, context) {
40
- const networkRequests = traceParsedData.NetworkRequests;
41
- const nav = traceParsedData.Meta.navigationsByNavigationId.get(context.navigationId);
42
- if (!nav) {
43
- throw new Error('no trace navigation');
54
+ export function generateInsight(parsedTrace, context) {
55
+ if (!context.navigation) {
56
+ return {};
44
57
  }
45
- const frameMetrics = traceParsedData.PageLoadMetrics.metricScoresByFrameId.get(context.frameId);
58
+ const networkRequests = parsedTrace.NetworkRequests;
59
+ const frameMetrics = parsedTrace.PageLoadMetrics.metricScoresByFrameId.get(context.frameId);
46
60
  if (!frameMetrics) {
47
61
  throw new Error('no frame metrics');
48
62
  }
@@ -52,41 +66,47 @@ export function generateInsight(traceParsedData, context) {
52
66
  }
53
67
  const metricScore = navMetrics.get("LCP" /* Handlers.ModelHandlers.PageLoadMetrics.MetricName.LCP */);
54
68
  const lcpEvent = metricScore?.event;
55
- if (!lcpEvent || !Types.TraceEvents.isTraceEventLargestContentfulPaintCandidate(lcpEvent)) {
69
+ if (!lcpEvent || !Types.Events.isLargestContentfulPaintCandidate(lcpEvent)) {
56
70
  return { warnings: [InsightWarning.NO_LCP] };
57
71
  }
58
72
  // This helps calculate the phases.
59
73
  const lcpMs = Helpers.Timing.microSecondsToMilliseconds(metricScore.timing);
60
74
  // This helps position things on the timeline's UI accurately for a trace.
61
75
  const lcpTs = metricScore.event?.ts ? Helpers.Timing.microSecondsToMilliseconds(metricScore.event?.ts) : undefined;
62
- const lcpResource = findLCPRequest(traceParsedData, context, lcpEvent);
63
- const mainReq = networkRequests.byTime.find(req => req.args.data.requestId === context.navigationId);
64
- if (!mainReq) {
65
- return { lcpMs, lcpTs, warnings: [InsightWarning.NO_DOCUMENT_REQUEST] };
76
+ const lcpRequest = findLCPRequest(parsedTrace, context, lcpEvent);
77
+ const docRequest = networkRequests.byTime.find(req => req.args.data.requestId === context.navigationId);
78
+ if (!docRequest) {
79
+ return { lcpMs, lcpTs, lcpEvent, warnings: [InsightWarning.NO_DOCUMENT_REQUEST] };
66
80
  }
67
- if (!lcpResource) {
81
+ if (!lcpRequest) {
68
82
  return {
69
- lcpMs: lcpMs,
70
- lcpTs: lcpTs,
71
- phases: breakdownPhases(nav, mainReq, lcpMs, lcpResource),
83
+ lcpMs,
84
+ lcpTs,
85
+ lcpEvent,
86
+ phases: breakdownPhases(context.navigation, docRequest, lcpMs, lcpRequest) ?? undefined,
72
87
  };
73
88
  }
89
+ const initiatorUrl = lcpRequest.args.data.initiator?.url;
90
+ // TODO(b/372319476): Explore using trace event HTMLDocumentParser::FetchQueuedPreloads to determine if the request
91
+ // is discovered by the preload scanner.
92
+ const initiatedByMainDoc = lcpRequest?.args.data.initiator?.type === 'parser' && docRequest.args.data.url === initiatorUrl;
93
+ const imgPreloadedOrFoundInHTML = lcpRequest?.args.data.isLinkPreload || initiatedByMainDoc;
74
94
  const imageLoadingAttr = lcpEvent.args.data?.loadingAttr;
75
- const imagePreloaded = lcpResource?.args.data.isLinkPreload || lcpResource?.args.data.initiator?.type === 'preload';
76
- const imageFetchPriorityHint = lcpResource?.args.data.fetchPriorityHint;
77
- // This is the earliest discovery time an LCP resource could have - it's TTFB.
78
- const earliestDiscoveryTime = mainReq && mainReq.args.data.timing ?
79
- Helpers.Timing.secondsToMicroseconds(mainReq.args.data.timing.requestTime) +
80
- Helpers.Timing.millisecondsToMicroseconds(mainReq.args.data.timing.receiveHeadersStart) :
95
+ const imageFetchPriorityHint = lcpRequest?.args.data.fetchPriorityHint;
96
+ // This is the earliest discovery time an LCP request could have - it's TTFB.
97
+ const earliestDiscoveryTime = docRequest && docRequest.args.data.timing ?
98
+ Helpers.Timing.secondsToMicroseconds(docRequest.args.data.timing.requestTime) +
99
+ Helpers.Timing.millisecondsToMicroseconds(docRequest.args.data.timing.receiveHeadersStart) :
81
100
  undefined;
82
101
  return {
83
- lcpMs: lcpMs,
84
- lcpTs: lcpTs,
85
- phases: breakdownPhases(nav, mainReq, lcpMs, lcpResource),
102
+ lcpMs,
103
+ lcpTs,
104
+ lcpEvent,
105
+ phases: breakdownPhases(context.navigation, docRequest, lcpMs, lcpRequest) ?? undefined,
86
106
  shouldRemoveLazyLoading: imageLoadingAttr === 'lazy',
87
107
  shouldIncreasePriorityHint: imageFetchPriorityHint !== 'high',
88
- shouldPreloadImage: !imagePreloaded,
89
- lcpResource,
108
+ shouldPreloadImage: !imgPreloadedOrFoundInHTML,
109
+ lcpRequest,
90
110
  earliestDiscoveryTimeTs: earliestDiscoveryTime ? Types.Timing.MicroSeconds(earliestDiscoveryTime) : undefined,
91
111
  };
92
112
  }
@@ -1 +1 @@
1
- {"version":3,"file":"LargestContentfulPaint.js","sourceRoot":"","sources":["../../../../../../../front_end/models/trace/insights/LargestContentfulPaint.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\nimport * as Handlers from '../handlers/handlers.js';\nimport * as Helpers from '../helpers/helpers.js';\nimport * as Types from '../types/types.js';\n\nimport {findLCPRequest} from './Common.js';\nimport {InsightWarning, type LCPInsightResult, type NavigationInsightContext, type RequiredData} from './types.js';\n\nexport function deps(): ['NetworkRequests', 'PageLoadMetrics', 'LargestImagePaint', 'Meta'] {\n return ['NetworkRequests', 'PageLoadMetrics', 'LargestImagePaint', 'Meta'];\n}\n\nexport interface LCPPhases {\n /**\n * The time between when the user initiates loading the page until when\n * the browser receives the first byte of the html response.\n */\n ttfb: Types.Timing.MilliSeconds;\n /**\n * The time between ttfb and the LCP resource request being started.\n * For a text LCP, this is undefined given no resource is loaded.\n */\n loadDelay?: Types.Timing.MilliSeconds;\n /**\n * The time it takes to load the LCP resource.\n */\n loadTime?: Types.Timing.MilliSeconds;\n /**\n * The time between when the LCP resource finishes loading and when\n * the LCP element is rendered.\n */\n renderDelay: Types.Timing.MilliSeconds;\n}\n\nfunction breakdownPhases(\n nav: Types.TraceEvents.TraceEventNavigationStart, mainRequest: Types.TraceEvents.SyntheticNetworkRequest,\n lcpMs: Types.Timing.MilliSeconds, lcpRequest: Types.TraceEvents.SyntheticNetworkRequest|null): LCPPhases {\n const mainReqTiming = mainRequest.args.data.timing;\n if (!mainReqTiming) {\n throw new Error('no timing for main resource');\n }\n const firstDocByteTs = Helpers.Timing.secondsToMicroseconds(mainReqTiming.requestTime) +\n Helpers.Timing.millisecondsToMicroseconds(mainReqTiming.receiveHeadersStart);\n\n const firstDocByteTiming = Types.Timing.MicroSeconds(firstDocByteTs - nav.ts);\n const ttfb = Helpers.Timing.microSecondsToMilliseconds(firstDocByteTiming);\n let renderDelay = Types.Timing.MilliSeconds(lcpMs - ttfb);\n\n if (!lcpRequest) {\n return {ttfb, renderDelay};\n }\n\n const lcpStartTs = Types.Timing.MicroSeconds(lcpRequest.ts - nav.ts);\n const resourceStart = Helpers.Timing.microSecondsToMilliseconds(lcpStartTs);\n\n const lcpReqEndTs = Types.Timing.MicroSeconds(lcpRequest.args.data.syntheticData.finishTime - nav.ts);\n const resourceEnd = Helpers.Timing.microSecondsToMilliseconds(lcpReqEndTs);\n\n const loadDelay = Types.Timing.MilliSeconds(resourceStart - ttfb);\n const loadTime = Types.Timing.MilliSeconds(resourceEnd - resourceStart);\n renderDelay = Types.Timing.MilliSeconds(lcpMs - resourceEnd);\n\n return {\n ttfb,\n loadDelay,\n loadTime,\n renderDelay,\n };\n}\n\nexport function generateInsight(\n traceParsedData: RequiredData<typeof deps>, context: NavigationInsightContext): LCPInsightResult {\n const networkRequests = traceParsedData.NetworkRequests;\n\n const nav = traceParsedData.Meta.navigationsByNavigationId.get(context.navigationId);\n if (!nav) {\n throw new Error('no trace navigation');\n }\n\n const frameMetrics = traceParsedData.PageLoadMetrics.metricScoresByFrameId.get(context.frameId);\n if (!frameMetrics) {\n throw new Error('no frame metrics');\n }\n\n const navMetrics = frameMetrics.get(context.navigationId);\n if (!navMetrics) {\n throw new Error('no navigation metrics');\n }\n const metricScore = navMetrics.get(Handlers.ModelHandlers.PageLoadMetrics.MetricName.LCP);\n const lcpEvent = metricScore?.event;\n if (!lcpEvent || !Types.TraceEvents.isTraceEventLargestContentfulPaintCandidate(lcpEvent)) {\n return {warnings: [InsightWarning.NO_LCP]};\n }\n\n // This helps calculate the phases.\n const lcpMs = Helpers.Timing.microSecondsToMilliseconds(metricScore.timing);\n // This helps position things on the timeline's UI accurately for a trace.\n const lcpTs = metricScore.event?.ts ? Helpers.Timing.microSecondsToMilliseconds(metricScore.event?.ts) : undefined;\n const lcpResource = findLCPRequest(traceParsedData, context, lcpEvent);\n const mainReq = networkRequests.byTime.find(req => req.args.data.requestId === context.navigationId);\n if (!mainReq) {\n return {lcpMs, lcpTs, warnings: [InsightWarning.NO_DOCUMENT_REQUEST]};\n }\n\n if (!lcpResource) {\n return {\n lcpMs: lcpMs,\n lcpTs: lcpTs,\n phases: breakdownPhases(nav, mainReq, lcpMs, lcpResource),\n };\n }\n\n const imageLoadingAttr = lcpEvent.args.data?.loadingAttr;\n const imagePreloaded = lcpResource?.args.data.isLinkPreload || lcpResource?.args.data.initiator?.type === 'preload';\n const imageFetchPriorityHint = lcpResource?.args.data.fetchPriorityHint;\n\n // This is the earliest discovery time an LCP resource could have - it's TTFB.\n const earliestDiscoveryTime = mainReq && mainReq.args.data.timing ?\n Helpers.Timing.secondsToMicroseconds(mainReq.args.data.timing.requestTime) +\n Helpers.Timing.millisecondsToMicroseconds(mainReq.args.data.timing.receiveHeadersStart) :\n undefined;\n\n return {\n lcpMs: lcpMs,\n lcpTs: lcpTs,\n phases: breakdownPhases(nav, mainReq, lcpMs, lcpResource),\n shouldRemoveLazyLoading: imageLoadingAttr === 'lazy',\n shouldIncreasePriorityHint: imageFetchPriorityHint !== 'high',\n shouldPreloadImage: !imagePreloaded,\n lcpResource,\n earliestDiscoveryTimeTs: earliestDiscoveryTime ? Types.Timing.MicroSeconds(earliestDiscoveryTime) : undefined,\n };\n}\n"]}
1
+ {"version":3,"file":"LargestContentfulPaint.js","sourceRoot":"","sources":["../../../../../../../front_end/models/trace/insights/LargestContentfulPaint.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\nimport * as Handlers from '../handlers/handlers.js';\nimport * as Helpers from '../helpers/helpers.js';\nimport * as Types from '../types/types.js';\n\nimport {findLCPRequest} from './Common.js';\nimport {type InsightResult, type InsightSetContext, InsightWarning, type RequiredData} from './types.js';\n\nexport function deps(): ['NetworkRequests', 'PageLoadMetrics', 'LargestImagePaint', 'Meta'] {\n return ['NetworkRequests', 'PageLoadMetrics', 'LargestImagePaint', 'Meta'];\n}\n\ninterface LCPPhases {\n /**\n * The time between when the user initiates loading the page until when\n * the browser receives the first byte of the html response.\n */\n ttfb: Types.Timing.MilliSeconds;\n /**\n * The time between ttfb and the LCP request request being started.\n * For a text LCP, this is undefined given no request is loaded.\n */\n loadDelay?: Types.Timing.MilliSeconds;\n /**\n * The time it takes to load the LCP request.\n */\n loadTime?: Types.Timing.MilliSeconds;\n /**\n * The time between when the LCP request finishes loading and when\n * the LCP element is rendered.\n */\n renderDelay: Types.Timing.MilliSeconds;\n}\n\nexport type LCPInsightResult = InsightResult<{\n lcpMs?: Types.Timing.MilliSeconds,\n lcpTs?: Types.Timing.MilliSeconds,\n lcpEvent?: Types.Events.LargestContentfulPaintCandidate,\n phases?: LCPPhases,\n shouldRemoveLazyLoading?: boolean,\n shouldIncreasePriorityHint?: boolean,\n shouldPreloadImage?: boolean,\n /** The network request for the LCP image, if there was one. */\n lcpRequest?: Types.Events.SyntheticNetworkRequest,\n earliestDiscoveryTimeTs?: Types.Timing.MicroSeconds,\n}>;\n\nfunction anyValuesNaN(...values: number[]): boolean {\n return values.some(v => Number.isNaN(v));\n}\n/**\n * Calculates the 4 phases of an LCP and the timings of each.\n * Will return `null` if any required values were missing. We don't ever expect\n * them to be missing on newer traces, but old trace files may lack some of the\n * data we rely on, so we want to handle that case.\n */\nfunction breakdownPhases(\n nav: Types.Events.NavigationStart, docRequest: Types.Events.SyntheticNetworkRequest,\n lcpMs: Types.Timing.MilliSeconds, lcpRequest: Types.Events.SyntheticNetworkRequest|null): LCPPhases|null {\n const docReqTiming = docRequest.args.data.timing;\n if (!docReqTiming) {\n throw new Error('no timing for document request');\n }\n const firstDocByteTs = Helpers.Timing.secondsToMicroseconds(docReqTiming.requestTime) +\n Helpers.Timing.millisecondsToMicroseconds(docReqTiming.receiveHeadersStart);\n\n const firstDocByteTiming = Types.Timing.MicroSeconds(firstDocByteTs - nav.ts);\n const ttfb = Helpers.Timing.microSecondsToMilliseconds(firstDocByteTiming);\n let renderDelay = Types.Timing.MilliSeconds(lcpMs - ttfb);\n\n if (!lcpRequest) {\n if (anyValuesNaN(ttfb, renderDelay)) {\n return null;\n }\n return {ttfb, renderDelay};\n }\n\n const lcpStartTs = Types.Timing.MicroSeconds(lcpRequest.ts - nav.ts);\n const requestStart = Helpers.Timing.microSecondsToMilliseconds(lcpStartTs);\n\n const lcpReqEndTs = Types.Timing.MicroSeconds(lcpRequest.args.data.syntheticData.finishTime - nav.ts);\n const requestEnd = Helpers.Timing.microSecondsToMilliseconds(lcpReqEndTs);\n\n const loadDelay = Types.Timing.MilliSeconds(requestStart - ttfb);\n const loadTime = Types.Timing.MilliSeconds(requestEnd - requestStart);\n renderDelay = Types.Timing.MilliSeconds(lcpMs - requestEnd);\n if (anyValuesNaN(ttfb, loadDelay, loadTime, renderDelay)) {\n return null;\n }\n\n return {\n ttfb,\n loadDelay,\n loadTime,\n renderDelay,\n };\n}\n\nexport function generateInsight(parsedTrace: RequiredData<typeof deps>, context: InsightSetContext): LCPInsightResult {\n if (!context.navigation) {\n return {};\n }\n\n const networkRequests = parsedTrace.NetworkRequests;\n\n const frameMetrics = parsedTrace.PageLoadMetrics.metricScoresByFrameId.get(context.frameId);\n if (!frameMetrics) {\n throw new Error('no frame metrics');\n }\n\n const navMetrics = frameMetrics.get(context.navigationId);\n if (!navMetrics) {\n throw new Error('no navigation metrics');\n }\n const metricScore = navMetrics.get(Handlers.ModelHandlers.PageLoadMetrics.MetricName.LCP);\n const lcpEvent = metricScore?.event;\n if (!lcpEvent || !Types.Events.isLargestContentfulPaintCandidate(lcpEvent)) {\n return {warnings: [InsightWarning.NO_LCP]};\n }\n\n // This helps calculate the phases.\n const lcpMs = Helpers.Timing.microSecondsToMilliseconds(metricScore.timing);\n // This helps position things on the timeline's UI accurately for a trace.\n const lcpTs = metricScore.event?.ts ? Helpers.Timing.microSecondsToMilliseconds(metricScore.event?.ts) : undefined;\n const lcpRequest = findLCPRequest(parsedTrace, context, lcpEvent);\n const docRequest = networkRequests.byTime.find(req => req.args.data.requestId === context.navigationId);\n if (!docRequest) {\n return {lcpMs, lcpTs, lcpEvent, warnings: [InsightWarning.NO_DOCUMENT_REQUEST]};\n }\n\n if (!lcpRequest) {\n return {\n lcpMs,\n lcpTs,\n lcpEvent,\n phases: breakdownPhases(context.navigation, docRequest, lcpMs, lcpRequest) ?? undefined,\n };\n }\n\n const initiatorUrl = lcpRequest.args.data.initiator?.url;\n // TODO(b/372319476): Explore using trace event HTMLDocumentParser::FetchQueuedPreloads to determine if the request\n // is discovered by the preload scanner.\n const initiatedByMainDoc =\n lcpRequest?.args.data.initiator?.type === 'parser' && docRequest.args.data.url === initiatorUrl;\n const imgPreloadedOrFoundInHTML = lcpRequest?.args.data.isLinkPreload || initiatedByMainDoc;\n\n const imageLoadingAttr = lcpEvent.args.data?.loadingAttr;\n const imageFetchPriorityHint = lcpRequest?.args.data.fetchPriorityHint;\n // This is the earliest discovery time an LCP request could have - it's TTFB.\n const earliestDiscoveryTime = docRequest && docRequest.args.data.timing ?\n Helpers.Timing.secondsToMicroseconds(docRequest.args.data.timing.requestTime) +\n Helpers.Timing.millisecondsToMicroseconds(docRequest.args.data.timing.receiveHeadersStart) :\n undefined;\n\n return {\n lcpMs,\n lcpTs,\n lcpEvent,\n phases: breakdownPhases(context.navigation, docRequest, lcpMs, lcpRequest) ?? undefined,\n shouldRemoveLazyLoading: imageLoadingAttr === 'lazy',\n shouldIncreasePriorityHint: imageFetchPriorityHint !== 'high',\n shouldPreloadImage: !imgPreloadedOrFoundInHTML,\n lcpRequest,\n earliestDiscoveryTimeTs: earliestDiscoveryTime ? Types.Timing.MicroSeconds(earliestDiscoveryTime) : undefined,\n };\n}\n"]}
@@ -1,8 +1,8 @@
1
1
  import * as Types from '../types/types.js';
2
- import { type InsightResult, type NavigationInsightContext, type RequiredData } from './types.js';
2
+ import { type InsightResult, type InsightSetContext, type RequiredData } from './types.js';
3
3
  export type RenderBlockingInsightResult = InsightResult<{
4
- renderBlockingRequests: Types.TraceEvents.SyntheticNetworkRequest[];
4
+ renderBlockingRequests: Types.Events.SyntheticNetworkRequest[];
5
5
  requestIdToWastedMs?: Map<string, number>;
6
6
  }>;
7
7
  export declare function deps(): ['NetworkRequests', 'PageLoadMetrics', 'LargestImagePaint'];
8
- export declare function generateInsight(traceParsedData: RequiredData<typeof deps>, context: NavigationInsightContext): RenderBlockingInsightResult;
8
+ export declare function generateInsight(parsedTrace: RequiredData<typeof deps>, context: InsightSetContext): RenderBlockingInsightResult;
@@ -51,12 +51,8 @@ function estimateSavingsWithGraphs(deferredIds, lanternContext) {
51
51
  minimalFCPGraph.request.transferSize = originalTransferSize;
52
52
  return Math.round(Math.max(estimateBeforeInline - estimateAfterInline, 0));
53
53
  }
54
- function hasImageLCP(traceParsedData, context) {
55
- const nav = traceParsedData.Meta.navigationsByNavigationId.get(context.navigationId);
56
- if (!nav) {
57
- throw new Error('no trace navigation');
58
- }
59
- const frameMetrics = traceParsedData.PageLoadMetrics.metricScoresByFrameId.get(context.frameId);
54
+ function hasImageLCP(parsedTrace, context) {
55
+ const frameMetrics = parsedTrace.PageLoadMetrics.metricScoresByFrameId.get(context.frameId);
60
56
  if (!frameMetrics) {
61
57
  throw new Error('no frame metrics');
62
58
  }
@@ -66,12 +62,12 @@ function hasImageLCP(traceParsedData, context) {
66
62
  }
67
63
  const metricScore = navMetrics.get("LCP" /* Handlers.ModelHandlers.PageLoadMetrics.MetricName.LCP */);
68
64
  const lcpEvent = metricScore?.event;
69
- if (!lcpEvent || !Types.TraceEvents.isTraceEventLargestContentfulPaintCandidate(lcpEvent)) {
65
+ if (!lcpEvent || !Types.Events.isLargestContentfulPaintCandidate(lcpEvent)) {
70
66
  return false;
71
67
  }
72
- return findLCPRequest(traceParsedData, context, lcpEvent) !== null;
68
+ return findLCPRequest(parsedTrace, context, lcpEvent) !== null;
73
69
  }
74
- function computeSavings(traceParsedData, context, renderBlockingRequests) {
70
+ function computeSavings(parsedTrace, context, renderBlockingRequests) {
75
71
  if (!context.lantern) {
76
72
  return;
77
73
  }
@@ -79,8 +75,8 @@ function computeSavings(traceParsedData, context, renderBlockingRequests) {
79
75
  const metricSavings = { FCP: 0, LCP: 0 };
80
76
  const requestIdToWastedMs = new Map();
81
77
  const deferredNodeIds = new Set();
82
- for (const resource of renderBlockingRequests) {
83
- const nodeAndTiming = nodesAndTimingsByRequestId.get(resource.args.data.requestId);
78
+ for (const request of renderBlockingRequests) {
79
+ const nodeAndTiming = nodesAndTimingsByRequestId.get(request.args.data.requestId);
84
80
  if (!nodeAndTiming) {
85
81
  continue;
86
82
  }
@@ -97,14 +93,19 @@ function computeSavings(traceParsedData, context, renderBlockingRequests) {
97
93
  if (requestIdToWastedMs.size) {
98
94
  metricSavings.FCP = estimateSavingsWithGraphs(deferredNodeIds, context.lantern);
99
95
  // In most cases, render blocking resources only affect LCP if LCP isn't an image.
100
- if (!hasImageLCP(traceParsedData, context)) {
96
+ if (!hasImageLCP(parsedTrace, context)) {
101
97
  metricSavings.LCP = metricSavings.FCP;
102
98
  }
103
99
  }
104
100
  return { metricSavings, requestIdToWastedMs };
105
101
  }
106
- export function generateInsight(traceParsedData, context) {
107
- const firstPaintTs = traceParsedData.PageLoadMetrics.metricScoresByFrameId.get(context.frameId)
102
+ export function generateInsight(parsedTrace, context) {
103
+ if (!context.navigation) {
104
+ return {
105
+ renderBlockingRequests: [],
106
+ };
107
+ }
108
+ const firstPaintTs = parsedTrace.PageLoadMetrics.metricScoresByFrameId.get(context.frameId)
108
109
  ?.get(context.navigationId)
109
110
  ?.get("FP" /* Handlers.ModelHandlers.PageLoadMetrics.MetricName.FP */)
110
111
  ?.event?.ts;
@@ -114,19 +115,19 @@ export function generateInsight(traceParsedData, context) {
114
115
  warnings: [InsightWarning.NO_FP],
115
116
  };
116
117
  }
117
- const renderBlockingRequests = [];
118
- for (const req of traceParsedData.NetworkRequests.byTime) {
118
+ let renderBlockingRequests = [];
119
+ for (const req of parsedTrace.NetworkRequests.byTime) {
119
120
  if (req.args.data.frame !== context.frameId) {
120
121
  continue;
121
122
  }
122
- if (req.args.data.renderBlocking !== 'blocking' && req.args.data.renderBlocking !== 'in_body_parser_blocking') {
123
+ if (!Helpers.Network.isSyntheticNetworkRequestEventRenderBlocking(req)) {
123
124
  continue;
124
125
  }
125
126
  if (req.args.data.syntheticData.finishTime > firstPaintTs) {
126
127
  continue;
127
128
  }
128
- // If a resource is marked `in_body_parser_blocking` it should only be considered render blocking if it is a
129
- // high enough priority. Some resources (e.g. scripts) are not marked as high priority if they are fetched
129
+ // If a request is marked `in_body_parser_blocking` it should only be considered render blocking if it is a
130
+ // high enough priority. Some requests (e.g. scripts) are not marked as high priority if they are fetched
130
131
  // after a non-preloaded image. (See "early" definition in https://web.dev/articles/fetch-priority)
131
132
  //
132
133
  // There are edge cases and exceptions (e.g. priority hints) but this gives us the best approximation
@@ -139,14 +140,18 @@ export function generateInsight(traceParsedData, context) {
139
140
  continue;
140
141
  }
141
142
  }
142
- const navigation = Helpers.Trace.getNavigationForTraceEvent(req, context.frameId, traceParsedData.Meta.navigationsByFrameId);
143
- if (navigation?.args.data?.navigationId !== context.navigationId) {
144
- continue;
143
+ const navigation = Helpers.Trace.getNavigationForTraceEvent(req, context.frameId, parsedTrace.Meta.navigationsByFrameId);
144
+ if (navigation === context.navigation) {
145
+ renderBlockingRequests.push(req);
145
146
  }
146
- renderBlockingRequests.push(req);
147
147
  }
148
- const savings = computeSavings(traceParsedData, context, renderBlockingRequests);
148
+ const savings = computeSavings(parsedTrace, context, renderBlockingRequests);
149
+ // Sort by request duration for insights.
150
+ renderBlockingRequests = renderBlockingRequests.sort((a, b) => {
151
+ return b.dur - a.dur;
152
+ });
149
153
  return {
154
+ relatedEvents: renderBlockingRequests,
150
155
  renderBlockingRequests,
151
156
  ...savings,
152
157
  };
@@ -1 +1 @@
1
- {"version":3,"file":"RenderBlocking.js","sourceRoot":"","sources":["../../../../../../../front_end/models/trace/insights/RenderBlocking.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\nimport * as Protocol from '../../../generated/protocol.js';\nimport * as Handlers from '../handlers/handlers.js';\nimport * as Helpers from '../helpers/helpers.js';\nimport type * as Lantern from '../lantern/lantern.js';\nimport * as Types from '../types/types.js';\n\nimport {findLCPRequest} from './Common.js';\nimport {\n type InsightResult,\n InsightWarning,\n type LanternContext,\n type NavigationInsightContext,\n type RequiredData,\n} from './types.js';\n\nexport type RenderBlockingInsightResult = InsightResult<{\n renderBlockingRequests: Types.TraceEvents.SyntheticNetworkRequest[],\n requestIdToWastedMs?: Map<string, number>,\n}>;\n\n// Because of the way we detect blocking stylesheets, asynchronously loaded\n// CSS with link[rel=preload] and an onload handler (see https://github.com/filamentgroup/loadCSS)\n// can be falsely flagged as blocking. Therefore, ignore stylesheets that loaded fast enough\n// to possibly be non-blocking (and they have minimal impact anyway).\nconst MINIMUM_WASTED_MS = 50;\n\nexport function deps(): ['NetworkRequests', 'PageLoadMetrics', 'LargestImagePaint'] {\n return ['NetworkRequests', 'PageLoadMetrics', 'LargestImagePaint'];\n}\n\n/**\n * Given a simulation's nodeTimings, return an object with the nodes/timing keyed by network URL\n */\nfunction getNodesAndTimingByRequestId(nodeTimings: Lantern.Simulation.Result['nodeTimings']):\n Map<string, {node: Lantern.Graph.Node, nodeTiming: Lantern.Types.Simulation.NodeTiming}> {\n const requestIdToNode =\n new Map<string, {node: Lantern.Graph.Node, nodeTiming: Lantern.Types.Simulation.NodeTiming}>();\n\n for (const [node, nodeTiming] of nodeTimings) {\n if (node.type !== 'network') {\n continue;\n }\n\n requestIdToNode.set(node.request.requestId, {node, nodeTiming});\n }\n\n return requestIdToNode;\n}\n\nfunction estimateSavingsWithGraphs(deferredIds: Set<string>, lanternContext: LanternContext): number {\n const simulator = lanternContext.simulator;\n const fcpGraph = lanternContext.metrics.firstContentfulPaint.optimisticGraph;\n const {nodeTimings} = lanternContext.simulator.simulate(fcpGraph);\n const adjustedNodeTimings = new Map(nodeTimings);\n\n const totalChildNetworkBytes = 0;\n const minimalFCPGraph = fcpGraph.cloneWithRelationships(node => {\n // If a node can be deferred, exclude it from the new FCP graph\n const canDeferRequest = deferredIds.has(node.id);\n return !canDeferRequest;\n });\n\n if (minimalFCPGraph.type !== 'network') {\n throw new Error('minimalFCPGraph not a NetworkNode');\n }\n\n // Recalculate the \"before\" time based on our adjusted node timings.\n const estimateBeforeInline = Math.max(...Array.from(\n Array.from(adjustedNodeTimings).map(timing => timing[1].endTime),\n ));\n\n // Add the inlined bytes to the HTML response\n const originalTransferSize = minimalFCPGraph.request.transferSize;\n const safeTransferSize = originalTransferSize || 0;\n minimalFCPGraph.request.transferSize = safeTransferSize + totalChildNetworkBytes;\n const estimateAfterInline = simulator.simulate(minimalFCPGraph).timeInMs;\n minimalFCPGraph.request.transferSize = originalTransferSize;\n return Math.round(Math.max(estimateBeforeInline - estimateAfterInline, 0));\n}\n\nfunction hasImageLCP(traceParsedData: RequiredData<typeof deps>, context: NavigationInsightContext): boolean {\n const nav = traceParsedData.Meta.navigationsByNavigationId.get(context.navigationId);\n if (!nav) {\n throw new Error('no trace navigation');\n }\n\n const frameMetrics = traceParsedData.PageLoadMetrics.metricScoresByFrameId.get(context.frameId);\n if (!frameMetrics) {\n throw new Error('no frame metrics');\n }\n\n const navMetrics = frameMetrics.get(context.navigationId);\n if (!navMetrics) {\n throw new Error('no navigation metrics');\n }\n const metricScore = navMetrics.get(Handlers.ModelHandlers.PageLoadMetrics.MetricName.LCP);\n const lcpEvent = metricScore?.event;\n if (!lcpEvent || !Types.TraceEvents.isTraceEventLargestContentfulPaintCandidate(lcpEvent)) {\n return false;\n }\n\n return findLCPRequest(traceParsedData, context, lcpEvent) !== null;\n}\n\nfunction computeSavings(\n traceParsedData: RequiredData<typeof deps>, context: NavigationInsightContext,\n renderBlockingRequests: Types.TraceEvents.SyntheticNetworkRequest[]):\n Pick<RenderBlockingInsightResult, 'metricSavings'|'requestIdToWastedMs'>|undefined {\n if (!context.lantern) {\n return;\n }\n\n const nodesAndTimingsByRequestId =\n getNodesAndTimingByRequestId(context.lantern.metrics.firstContentfulPaint.optimisticEstimate.nodeTimings);\n\n const metricSavings = {FCP: 0, LCP: 0};\n const requestIdToWastedMs = new Map<string, number>();\n const deferredNodeIds = new Set<string>();\n for (const resource of renderBlockingRequests) {\n const nodeAndTiming = nodesAndTimingsByRequestId.get(resource.args.data.requestId);\n if (!nodeAndTiming) {\n continue;\n }\n\n const {node, nodeTiming} = nodeAndTiming;\n\n // Mark this node and all its dependents as deferrable\n node.traverse(node => deferredNodeIds.add(node.id));\n\n // \"wastedMs\" is the download time of the network request, responseReceived - requestSent\n const wastedMs = Math.round(nodeTiming.duration);\n if (wastedMs < MINIMUM_WASTED_MS) {\n continue;\n }\n\n requestIdToWastedMs.set(node.id, wastedMs);\n }\n\n if (requestIdToWastedMs.size) {\n metricSavings.FCP = estimateSavingsWithGraphs(deferredNodeIds, context.lantern);\n\n // In most cases, render blocking resources only affect LCP if LCP isn't an image.\n if (!hasImageLCP(traceParsedData, context)) {\n metricSavings.LCP = metricSavings.FCP;\n }\n }\n\n return {metricSavings, requestIdToWastedMs};\n}\n\nexport function generateInsight(\n traceParsedData: RequiredData<typeof deps>, context: NavigationInsightContext): RenderBlockingInsightResult {\n const firstPaintTs = traceParsedData.PageLoadMetrics.metricScoresByFrameId.get(context.frameId)\n ?.get(context.navigationId)\n ?.get(Handlers.ModelHandlers.PageLoadMetrics.MetricName.FP)\n ?.event?.ts;\n if (!firstPaintTs) {\n return {\n renderBlockingRequests: [],\n warnings: [InsightWarning.NO_FP],\n };\n }\n\n const renderBlockingRequests = [];\n for (const req of traceParsedData.NetworkRequests.byTime) {\n if (req.args.data.frame !== context.frameId) {\n continue;\n }\n\n if (req.args.data.renderBlocking !== 'blocking' && req.args.data.renderBlocking !== 'in_body_parser_blocking') {\n continue;\n }\n\n if (req.args.data.syntheticData.finishTime > firstPaintTs) {\n continue;\n }\n\n // If a resource is marked `in_body_parser_blocking` it should only be considered render blocking if it is a\n // high enough priority. Some resources (e.g. scripts) are not marked as high priority if they are fetched\n // after a non-preloaded image. (See \"early\" definition in https://web.dev/articles/fetch-priority)\n //\n // There are edge cases and exceptions (e.g. priority hints) but this gives us the best approximation\n // of render blocking resources in the document body.\n if (req.args.data.renderBlocking === 'in_body_parser_blocking') {\n const priority = req.args.data.priority;\n const isScript = req.args.data.resourceType === Protocol.Network.ResourceType.Script;\n const isBlockingScript = isScript && priority === Protocol.Network.ResourcePriority.High;\n if (priority !== Protocol.Network.ResourcePriority.VeryHigh && !isBlockingScript) {\n continue;\n }\n }\n\n const navigation =\n Helpers.Trace.getNavigationForTraceEvent(req, context.frameId, traceParsedData.Meta.navigationsByFrameId);\n if (navigation?.args.data?.navigationId !== context.navigationId) {\n continue;\n }\n\n renderBlockingRequests.push(req);\n }\n\n const savings = computeSavings(traceParsedData, context, renderBlockingRequests);\n\n return {\n renderBlockingRequests,\n ...savings,\n };\n}\n"]}
1
+ {"version":3,"file":"RenderBlocking.js","sourceRoot":"","sources":["../../../../../../../front_end/models/trace/insights/RenderBlocking.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\nimport * as Protocol from '../../../generated/protocol.js';\nimport * as Handlers from '../handlers/handlers.js';\nimport * as Helpers from '../helpers/helpers.js';\nimport type * as Lantern from '../lantern/lantern.js';\nimport * as Types from '../types/types.js';\n\nimport {findLCPRequest} from './Common.js';\nimport {\n type InsightResult,\n type InsightSetContext,\n type InsightSetContextWithNavigation,\n InsightWarning,\n type LanternContext,\n type RequiredData,\n} from './types.js';\n\nexport type RenderBlockingInsightResult = InsightResult<{\n renderBlockingRequests: Types.Events.SyntheticNetworkRequest[],\n requestIdToWastedMs?: Map<string, number>,\n}>;\n\n// Because of the way we detect blocking stylesheets, asynchronously loaded\n// CSS with link[rel=preload] and an onload handler (see https://github.com/filamentgroup/loadCSS)\n// can be falsely flagged as blocking. Therefore, ignore stylesheets that loaded fast enough\n// to possibly be non-blocking (and they have minimal impact anyway).\nconst MINIMUM_WASTED_MS = 50;\n\nexport function deps(): ['NetworkRequests', 'PageLoadMetrics', 'LargestImagePaint'] {\n return ['NetworkRequests', 'PageLoadMetrics', 'LargestImagePaint'];\n}\n\n/**\n * Given a simulation's nodeTimings, return an object with the nodes/timing keyed by network URL\n */\nfunction getNodesAndTimingByRequestId(nodeTimings: Lantern.Simulation.Result['nodeTimings']):\n Map<string, {node: Lantern.Graph.Node, nodeTiming: Lantern.Types.Simulation.NodeTiming}> {\n const requestIdToNode =\n new Map<string, {node: Lantern.Graph.Node, nodeTiming: Lantern.Types.Simulation.NodeTiming}>();\n\n for (const [node, nodeTiming] of nodeTimings) {\n if (node.type !== 'network') {\n continue;\n }\n\n requestIdToNode.set(node.request.requestId, {node, nodeTiming});\n }\n\n return requestIdToNode;\n}\n\nfunction estimateSavingsWithGraphs(\n deferredIds: Set<string>, lanternContext: LanternContext): Types.Timing.MilliSeconds {\n const simulator = lanternContext.simulator;\n const fcpGraph = lanternContext.metrics.firstContentfulPaint.optimisticGraph;\n const {nodeTimings} = lanternContext.simulator.simulate(fcpGraph);\n const adjustedNodeTimings = new Map(nodeTimings);\n\n const totalChildNetworkBytes = 0;\n const minimalFCPGraph = fcpGraph.cloneWithRelationships(node => {\n // If a node can be deferred, exclude it from the new FCP graph\n const canDeferRequest = deferredIds.has(node.id);\n return !canDeferRequest;\n });\n\n if (minimalFCPGraph.type !== 'network') {\n throw new Error('minimalFCPGraph not a NetworkNode');\n }\n\n // Recalculate the \"before\" time based on our adjusted node timings.\n const estimateBeforeInline = Math.max(...Array.from(\n Array.from(adjustedNodeTimings).map(timing => timing[1].endTime),\n ));\n\n // Add the inlined bytes to the HTML response\n const originalTransferSize = minimalFCPGraph.request.transferSize;\n const safeTransferSize = originalTransferSize || 0;\n minimalFCPGraph.request.transferSize = safeTransferSize + totalChildNetworkBytes;\n const estimateAfterInline = simulator.simulate(minimalFCPGraph).timeInMs;\n minimalFCPGraph.request.transferSize = originalTransferSize;\n return Math.round(Math.max(estimateBeforeInline - estimateAfterInline, 0)) as Types.Timing.MilliSeconds;\n}\n\nfunction hasImageLCP(parsedTrace: RequiredData<typeof deps>, context: InsightSetContextWithNavigation): boolean {\n const frameMetrics = parsedTrace.PageLoadMetrics.metricScoresByFrameId.get(context.frameId);\n if (!frameMetrics) {\n throw new Error('no frame metrics');\n }\n\n const navMetrics = frameMetrics.get(context.navigationId);\n if (!navMetrics) {\n throw new Error('no navigation metrics');\n }\n const metricScore = navMetrics.get(Handlers.ModelHandlers.PageLoadMetrics.MetricName.LCP);\n const lcpEvent = metricScore?.event;\n if (!lcpEvent || !Types.Events.isLargestContentfulPaintCandidate(lcpEvent)) {\n return false;\n }\n\n return findLCPRequest(parsedTrace, context, lcpEvent) !== null;\n}\n\nfunction computeSavings(\n parsedTrace: RequiredData<typeof deps>, context: InsightSetContextWithNavigation,\n renderBlockingRequests: Types.Events.SyntheticNetworkRequest[]):\n Pick<RenderBlockingInsightResult, 'metricSavings'|'requestIdToWastedMs'>|undefined {\n if (!context.lantern) {\n return;\n }\n\n const nodesAndTimingsByRequestId =\n getNodesAndTimingByRequestId(context.lantern.metrics.firstContentfulPaint.optimisticEstimate.nodeTimings);\n\n const metricSavings = {FCP: 0 as Types.Timing.MilliSeconds, LCP: 0 as Types.Timing.MilliSeconds};\n const requestIdToWastedMs = new Map<string, number>();\n const deferredNodeIds = new Set<string>();\n for (const request of renderBlockingRequests) {\n const nodeAndTiming = nodesAndTimingsByRequestId.get(request.args.data.requestId);\n if (!nodeAndTiming) {\n continue;\n }\n\n const {node, nodeTiming} = nodeAndTiming;\n\n // Mark this node and all its dependents as deferrable\n node.traverse(node => deferredNodeIds.add(node.id));\n\n // \"wastedMs\" is the download time of the network request, responseReceived - requestSent\n const wastedMs = Math.round(nodeTiming.duration);\n if (wastedMs < MINIMUM_WASTED_MS) {\n continue;\n }\n\n requestIdToWastedMs.set(node.id, wastedMs);\n }\n\n if (requestIdToWastedMs.size) {\n metricSavings.FCP = estimateSavingsWithGraphs(deferredNodeIds, context.lantern);\n\n // In most cases, render blocking resources only affect LCP if LCP isn't an image.\n if (!hasImageLCP(parsedTrace, context)) {\n metricSavings.LCP = metricSavings.FCP;\n }\n }\n\n return {metricSavings, requestIdToWastedMs};\n}\n\nexport function generateInsight(\n parsedTrace: RequiredData<typeof deps>, context: InsightSetContext): RenderBlockingInsightResult {\n if (!context.navigation) {\n return {\n renderBlockingRequests: [],\n };\n }\n\n const firstPaintTs = parsedTrace.PageLoadMetrics.metricScoresByFrameId.get(context.frameId)\n ?.get(context.navigationId)\n ?.get(Handlers.ModelHandlers.PageLoadMetrics.MetricName.FP)\n ?.event?.ts;\n if (!firstPaintTs) {\n return {\n renderBlockingRequests: [],\n warnings: [InsightWarning.NO_FP],\n };\n }\n\n let renderBlockingRequests: Types.Events.SyntheticNetworkRequest[] = [];\n for (const req of parsedTrace.NetworkRequests.byTime) {\n if (req.args.data.frame !== context.frameId) {\n continue;\n }\n\n if (!Helpers.Network.isSyntheticNetworkRequestEventRenderBlocking(req)) {\n continue;\n }\n\n if (req.args.data.syntheticData.finishTime > firstPaintTs) {\n continue;\n }\n\n // If a request is marked `in_body_parser_blocking` it should only be considered render blocking if it is a\n // high enough priority. Some requests (e.g. scripts) are not marked as high priority if they are fetched\n // after a non-preloaded image. (See \"early\" definition in https://web.dev/articles/fetch-priority)\n //\n // There are edge cases and exceptions (e.g. priority hints) but this gives us the best approximation\n // of render blocking resources in the document body.\n if (req.args.data.renderBlocking === 'in_body_parser_blocking') {\n const priority = req.args.data.priority;\n const isScript = req.args.data.resourceType === Protocol.Network.ResourceType.Script;\n const isBlockingScript = isScript && priority === Protocol.Network.ResourcePriority.High;\n if (priority !== Protocol.Network.ResourcePriority.VeryHigh && !isBlockingScript) {\n continue;\n }\n }\n\n const navigation =\n Helpers.Trace.getNavigationForTraceEvent(req, context.frameId, parsedTrace.Meta.navigationsByFrameId);\n if (navigation === context.navigation) {\n renderBlockingRequests.push(req);\n }\n }\n\n const savings = computeSavings(parsedTrace, context, renderBlockingRequests);\n\n // Sort by request duration for insights.\n renderBlockingRequests = renderBlockingRequests.sort((a, b) => {\n return b.dur - a.dur;\n });\n\n return {\n relatedEvents: renderBlockingRequests,\n renderBlockingRequests,\n ...savings,\n };\n}\n"]}
@@ -0,0 +1,11 @@
1
+ import * as Types from '../types/types.js';
2
+ import type { InsightResult, InsightSetContext, RequiredData } from './types.js';
3
+ export declare function deps(): ['SelectorStats'];
4
+ export type SlowCSSSelectorInsightResult = InsightResult<{
5
+ totalElapsedMs: Types.Timing.MilliSeconds;
6
+ totalMatchAttempts: number;
7
+ totalMatchCount: number;
8
+ topElapsedMs: Types.Events.SelectorTiming[];
9
+ topMatchAttempts: Types.Events.SelectorTiming[];
10
+ }>;
11
+ export declare function generateInsight(parsedTrace: RequiredData<typeof deps>, context: InsightSetContext): SlowCSSSelectorInsightResult;
@@ -0,0 +1,67 @@
1
+ // Copyright 2024 The Chromium Authors. All rights reserved.
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+ import * as Helpers from '../helpers/helpers.js';
5
+ import { SelectorTimingsKey } from '../types/TraceEvents.js';
6
+ import * as Types from '../types/types.js';
7
+ export function deps() {
8
+ return ['SelectorStats'];
9
+ }
10
+ function aggregateSelectorStats(data, context) {
11
+ const selectorMap = new Map();
12
+ for (const [event, value] of data) {
13
+ if (event.args.beginData?.frame !== context.frameId) {
14
+ continue;
15
+ }
16
+ if (!Helpers.Timing.eventIsInBounds(event, context.bounds)) {
17
+ continue;
18
+ }
19
+ for (const timing of value.timings) {
20
+ const key = timing[SelectorTimingsKey.Selector] + '_' + timing[SelectorTimingsKey.StyleSheetId];
21
+ const findTiming = selectorMap.get(key);
22
+ if (findTiming !== undefined) {
23
+ findTiming[SelectorTimingsKey.Elapsed] += timing[SelectorTimingsKey.Elapsed];
24
+ findTiming[SelectorTimingsKey.FastRejectCount] += timing[SelectorTimingsKey.FastRejectCount];
25
+ findTiming[SelectorTimingsKey.MatchAttempts] += timing[SelectorTimingsKey.MatchAttempts];
26
+ findTiming[SelectorTimingsKey.MatchCount] += timing[SelectorTimingsKey.MatchCount];
27
+ }
28
+ else {
29
+ selectorMap.set(key, { ...timing });
30
+ }
31
+ }
32
+ }
33
+ return [...selectorMap.values()];
34
+ }
35
+ export function generateInsight(parsedTrace, context) {
36
+ const selectorStatsData = parsedTrace.SelectorStats;
37
+ if (!selectorStatsData) {
38
+ throw new Error('no selector stats data');
39
+ }
40
+ const selectorTimings = aggregateSelectorStats(selectorStatsData.dataForUpdateLayoutEvent, context);
41
+ let totalElapsedUs = 0;
42
+ let totalMatchAttempts = 0;
43
+ let totalMatchCount = 0;
44
+ selectorTimings.map(timing => {
45
+ totalElapsedUs += timing[SelectorTimingsKey.Elapsed];
46
+ totalMatchAttempts += timing[SelectorTimingsKey.MatchAttempts];
47
+ totalMatchCount += timing[SelectorTimingsKey.MatchCount];
48
+ });
49
+ // sort by elapsed time
50
+ const sortByElapsedMs = selectorTimings.toSorted((a, b) => {
51
+ return b[SelectorTimingsKey.Elapsed] - a[SelectorTimingsKey.Elapsed];
52
+ });
53
+ // sort by match attempts
54
+ const sortByMatchAttempts = selectorTimings.toSorted((a, b) => {
55
+ return b[SelectorTimingsKey.MatchAttempts] - a[SelectorTimingsKey.MatchAttempts];
56
+ });
57
+ return {
58
+ // TODO: should we identify UpdateLayout events as linked to this insight?
59
+ relatedEvents: [],
60
+ totalElapsedMs: Types.Timing.MilliSeconds(totalElapsedUs / 1000.0),
61
+ totalMatchAttempts,
62
+ totalMatchCount,
63
+ topElapsedMs: sortByElapsedMs.slice(0, 3),
64
+ topMatchAttempts: sortByMatchAttempts.slice(0, 3),
65
+ };
66
+ }
67
+ //# sourceMappingURL=SlowCSSSelector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SlowCSSSelector.js","sourceRoot":"","sources":["../../../../../../../front_end/models/trace/insights/SlowCSSSelector.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\nimport * as Helpers from '../helpers/helpers.js';\nimport {type SelectorTiming, SelectorTimingsKey} from '../types/TraceEvents.js';\nimport * as Types from '../types/types.js';\n\nimport type {InsightResult, InsightSetContext, RequiredData} from './types.js';\n\nexport function deps(): ['SelectorStats'] {\n return ['SelectorStats'];\n}\n\nexport type SlowCSSSelectorInsightResult = InsightResult<{\n totalElapsedMs: Types.Timing.MilliSeconds,\n totalMatchAttempts: number,\n totalMatchCount: number,\n topElapsedMs: Types.Events.SelectorTiming[],\n topMatchAttempts: Types.Events.SelectorTiming[],\n}>;\n\nfunction aggregateSelectorStats(\n data: Map<Types.Events.UpdateLayoutTree, {\n timings: Types.Events.SelectorTiming[],\n }>,\n context: InsightSetContext): SelectorTiming[] {\n const selectorMap = new Map<String, SelectorTiming>();\n\n for (const [event, value] of data) {\n if (event.args.beginData?.frame !== context.frameId) {\n continue;\n }\n if (!Helpers.Timing.eventIsInBounds(event, context.bounds)) {\n continue;\n }\n for (const timing of value.timings) {\n const key = timing[SelectorTimingsKey.Selector] + '_' + timing[SelectorTimingsKey.StyleSheetId];\n const findTiming = selectorMap.get(key);\n if (findTiming !== undefined) {\n findTiming[SelectorTimingsKey.Elapsed] += timing[SelectorTimingsKey.Elapsed];\n findTiming[SelectorTimingsKey.FastRejectCount] += timing[SelectorTimingsKey.FastRejectCount];\n findTiming[SelectorTimingsKey.MatchAttempts] += timing[SelectorTimingsKey.MatchAttempts];\n findTiming[SelectorTimingsKey.MatchCount] += timing[SelectorTimingsKey.MatchCount];\n } else {\n selectorMap.set(key, {...timing});\n }\n }\n }\n\n return [...selectorMap.values()];\n}\n\nexport function generateInsight(\n parsedTrace: RequiredData<typeof deps>, context: InsightSetContext): SlowCSSSelectorInsightResult {\n const selectorStatsData = parsedTrace.SelectorStats;\n\n if (!selectorStatsData) {\n throw new Error('no selector stats data');\n }\n\n const selectorTimings = aggregateSelectorStats(selectorStatsData.dataForUpdateLayoutEvent, context);\n\n let totalElapsedUs = 0;\n let totalMatchAttempts = 0;\n let totalMatchCount = 0;\n\n selectorTimings.map(timing => {\n totalElapsedUs += timing[SelectorTimingsKey.Elapsed];\n totalMatchAttempts += timing[SelectorTimingsKey.MatchAttempts];\n totalMatchCount += timing[SelectorTimingsKey.MatchCount];\n });\n\n // sort by elapsed time\n const sortByElapsedMs = selectorTimings.toSorted((a, b) => {\n return b[SelectorTimingsKey.Elapsed] - a[SelectorTimingsKey.Elapsed];\n });\n\n // sort by match attempts\n const sortByMatchAttempts = selectorTimings.toSorted((a, b) => {\n return b[SelectorTimingsKey.MatchAttempts] - a[SelectorTimingsKey.MatchAttempts];\n });\n\n return {\n // TODO: should we identify UpdateLayout events as linked to this insight?\n relatedEvents: [],\n totalElapsedMs: Types.Timing.MilliSeconds(totalElapsedUs / 1000.0),\n totalMatchAttempts,\n totalMatchCount,\n topElapsedMs: sortByElapsedMs.slice(0, 3),\n topMatchAttempts: sortByMatchAttempts.slice(0, 3),\n };\n}\n"]}
@@ -0,0 +1,13 @@
1
+ import * as Extras from '../extras/extras.js';
2
+ import type * as Types from '../types/types.js';
3
+ import type { InsightResult, InsightSetContext, RequiredData } from './types.js';
4
+ export declare function deps(): ['Meta', 'NetworkRequests', 'Renderer', 'ImagePainting'];
5
+ export type ThirdPartyWebInsightResult = InsightResult<{
6
+ entityByRequest: Map<Types.Events.SyntheticNetworkRequest, Extras.ThirdParties.Entity>;
7
+ requestsByEntity: Map<Extras.ThirdParties.Entity, Types.Events.SyntheticNetworkRequest[]>;
8
+ summaryByRequest: Map<Types.Events.SyntheticNetworkRequest, Extras.ThirdParties.Summary>;
9
+ summaryByEntity: Map<Extras.ThirdParties.Entity, Extras.ThirdParties.Summary>;
10
+ /** The entity for this navigation's URL. Any other entity is from a third party. */
11
+ firstPartyEntity?: Extras.ThirdParties.Entity;
12
+ }>;
13
+ export declare function generateInsight(parsedTrace: RequiredData<typeof deps>, context: InsightSetContext): ThirdPartyWebInsightResult;
@@ -0,0 +1,42 @@
1
+ // Copyright 2024 The Chromium Authors. All rights reserved.
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+ import * as ThirdPartyWeb from '../../../third_party/third-party-web/third-party-web.js';
5
+ import * as Extras from '../extras/extras.js';
6
+ import * as Helpers from '../helpers/helpers.js';
7
+ export function deps() {
8
+ return ['Meta', 'NetworkRequests', 'Renderer', 'ImagePainting'];
9
+ }
10
+ function getRelatedEvents(summaries, firstPartyEntity) {
11
+ const events = [];
12
+ for (const [entity, requests] of summaries.requestsByEntity.entries()) {
13
+ if (entity !== firstPartyEntity) {
14
+ events.push(...requests);
15
+ }
16
+ }
17
+ return events;
18
+ }
19
+ export function generateInsight(parsedTrace, context) {
20
+ const networkRequests = parsedTrace.NetworkRequests.byTime.filter(event => {
21
+ if (!context.navigation) {
22
+ return false;
23
+ }
24
+ if (event.args.data.frame !== context.frameId) {
25
+ return false;
26
+ }
27
+ return Helpers.Timing.eventIsInBounds(event, context.bounds);
28
+ });
29
+ const { entityByRequest, madeUpEntityCache, summaries } = Extras.ThirdParties.getSummariesAndEntitiesForTraceBounds(parsedTrace, context.bounds, networkRequests);
30
+ const firstPartyUrl = context.navigation?.args.data?.documentLoaderURL ?? parsedTrace.Meta.mainFrameURL;
31
+ const firstPartyEntity = ThirdPartyWeb.ThirdPartyWeb.getEntity(firstPartyUrl) ||
32
+ Extras.ThirdParties.makeUpEntity(madeUpEntityCache, firstPartyUrl);
33
+ return {
34
+ relatedEvents: getRelatedEvents(summaries, firstPartyEntity),
35
+ entityByRequest,
36
+ requestsByEntity: summaries.requestsByEntity,
37
+ summaryByRequest: summaries.byRequest,
38
+ summaryByEntity: summaries.byEntity,
39
+ firstPartyEntity,
40
+ };
41
+ }
42
+ //# sourceMappingURL=ThirdPartyWeb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ThirdPartyWeb.js","sourceRoot":"","sources":["../../../../../../../front_end/models/trace/insights/ThirdPartyWeb.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,yEAAyE;AACzE,6BAA6B;AAE7B,OAAO,KAAK,aAAa,MAAM,yDAAyD,CAAC;AACzF,OAAO,KAAK,MAAM,MAAM,qBAAqB,CAAC;AAE9C,OAAO,KAAK,OAAO,MAAM,uBAAuB,CAAC;AAKjD,MAAM,UAAU,IAAI;IAClB,OAAO,CAAC,MAAM,EAAE,iBAAiB,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC;AAClE,CAAC;AAWD,SAAS,gBAAgB,CACrB,SAA0C,EAC1C,gBAAsD;IACxD,MAAM,MAAM,GAAG,EAAE,CAAC;IAElB,KAAK,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,IAAI,SAAS,CAAC,gBAAgB,CAAC,OAAO,EAAE,EAAE,CAAC;QACtE,IAAI,MAAM,KAAK,gBAAgB,EAAE,CAAC;YAChC,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,eAAe,CAC3B,WAAsC,EAAE,OAA0B;IACpE,MAAM,eAAe,GAAG,WAAW,CAAC,eAAe,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;QACxE,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;YACxB,OAAO,KAAK,CAAC;QACf,CAAC;QAED,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC;YAC9C,OAAO,KAAK,CAAC;QACf,CAAC;QAED,OAAO,OAAO,CAAC,MAAM,CAAC,eAAe,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,MAAM,EAAC,eAAe,EAAE,iBAAiB,EAAE,SAAS,EAAC,GAAG,MAAM,CAAC,YAAY,CAAC,qCAAqC,CAC7G,WAAyC,EAAE,OAAO,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAEhF,MAAM,aAAa,GAAG,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,EAAE,iBAAiB,IAAI,WAAW,CAAC,IAAI,CAAC,YAAY,CAAC;IACxG,MAAM,gBAAgB,GAAG,aAAa,CAAC,aAAa,CAAC,SAAS,CAAC,aAAa,CAAC;QACzE,MAAM,CAAC,YAAY,CAAC,YAAY,CAAC,iBAAiB,EAAE,aAAa,CAAC,CAAC;IAEvE,OAAO;QACL,aAAa,EAAE,gBAAgB,CAAC,SAAS,EAAE,gBAAgB,CAAC;QAC5D,eAAe;QACf,gBAAgB,EAAE,SAAS,CAAC,gBAAgB;QAC5C,gBAAgB,EAAE,SAAS,CAAC,SAAS;QACrC,eAAe,EAAE,SAAS,CAAC,QAAQ;QACnC,gBAAgB;KACjB,CAAC;AACJ,CAAC","sourcesContent":["// Copyright 2024 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\nimport * as ThirdPartyWeb from '../../../third_party/third-party-web/third-party-web.js';\nimport * as Extras from '../extras/extras.js';\nimport type * as Handlers from '../handlers/handlers.js';\nimport * as Helpers from '../helpers/helpers.js';\nimport type * as Types from '../types/types.js';\n\nimport type {InsightResult, InsightSetContext, RequiredData} from './types.js';\n\nexport function deps(): ['Meta', 'NetworkRequests', 'Renderer', 'ImagePainting'] {\n return ['Meta', 'NetworkRequests', 'Renderer', 'ImagePainting'];\n}\n\nexport type ThirdPartyWebInsightResult = InsightResult<{\n entityByRequest: Map<Types.Events.SyntheticNetworkRequest, Extras.ThirdParties.Entity>,\n requestsByEntity: Map<Extras.ThirdParties.Entity, Types.Events.SyntheticNetworkRequest[]>,\n summaryByRequest: Map<Types.Events.SyntheticNetworkRequest, Extras.ThirdParties.Summary>,\n summaryByEntity: Map<Extras.ThirdParties.Entity, Extras.ThirdParties.Summary>,\n /** The entity for this navigation's URL. Any other entity is from a third party. */\n firstPartyEntity?: Extras.ThirdParties.Entity,\n}>;\n\nfunction getRelatedEvents(\n summaries: Extras.ThirdParties.SummaryMaps,\n firstPartyEntity: Extras.ThirdParties.Entity|undefined): Types.Events.Event[] {\n const events = [];\n\n for (const [entity, requests] of summaries.requestsByEntity.entries()) {\n if (entity !== firstPartyEntity) {\n events.push(...requests);\n }\n }\n\n return events;\n}\n\nexport function generateInsight(\n parsedTrace: RequiredData<typeof deps>, context: InsightSetContext): ThirdPartyWebInsightResult {\n const networkRequests = parsedTrace.NetworkRequests.byTime.filter(event => {\n if (!context.navigation) {\n return false;\n }\n\n if (event.args.data.frame !== context.frameId) {\n return false;\n }\n\n return Helpers.Timing.eventIsInBounds(event, context.bounds);\n });\n\n const {entityByRequest, madeUpEntityCache, summaries} = Extras.ThirdParties.getSummariesAndEntitiesForTraceBounds(\n parsedTrace as Handlers.Types.ParsedTrace, context.bounds, networkRequests);\n\n const firstPartyUrl = context.navigation?.args.data?.documentLoaderURL ?? parsedTrace.Meta.mainFrameURL;\n const firstPartyEntity = ThirdPartyWeb.ThirdPartyWeb.getEntity(firstPartyUrl) ||\n Extras.ThirdParties.makeUpEntity(madeUpEntityCache, firstPartyUrl);\n\n return {\n relatedEvents: getRelatedEvents(summaries, firstPartyEntity),\n entityByRequest,\n requestsByEntity: summaries.requestsByEntity,\n summaryByRequest: summaries.byRequest,\n summaryByEntity: summaries.byEntity,\n firstPartyEntity,\n };\n}\n"]}
@@ -1,5 +1,8 @@
1
- import { type InsightResult, type NavigationInsightContext, type RequiredData } from './types.js';
1
+ import type * as Types from '../types/types.js';
2
+ import { type InsightResult, type InsightSetContext, type RequiredData } from './types.js';
2
3
  export declare function deps(): ['Meta', 'UserInteractions'];
3
- export declare function generateInsight(traceParsedData: RequiredData<typeof deps>, context: NavigationInsightContext): InsightResult<{
4
+ export type ViewportInsightResult = InsightResult<{
4
5
  mobileOptimized: boolean | null;
6
+ viewportEvent?: Types.Events.ParseMetaViewport;
5
7
  }>;
8
+ export declare function generateInsight(parsedTrace: RequiredData<typeof deps>, context: InsightSetContext): ViewportInsightResult;
@@ -6,34 +6,39 @@ import { InsightWarning } from './types.js';
6
6
  export function deps() {
7
7
  return ['Meta', 'UserInteractions'];
8
8
  }
9
- export function generateInsight(traceParsedData, context) {
10
- const events = traceParsedData.UserInteractions.beginCommitCompositorFrameEvents.filter(event => {
9
+ export function generateInsight(parsedTrace, context) {
10
+ const compositorEvents = parsedTrace.UserInteractions.beginCommitCompositorFrameEvents.filter(event => {
11
11
  if (event.args.frame !== context.frameId) {
12
12
  return false;
13
13
  }
14
- const navigation = Helpers.Trace.getNavigationForTraceEvent(event, context.frameId, traceParsedData.Meta.navigationsByFrameId);
15
- if (navigation?.args.data?.navigationId !== context.navigationId) {
16
- return false;
17
- }
18
- return true;
14
+ return Helpers.Timing.eventIsInBounds(event, context.bounds);
19
15
  });
20
- if (!events.length) {
16
+ if (!compositorEvents.length) {
21
17
  // Trace doesn't have the data we need.
22
18
  return {
23
19
  mobileOptimized: null,
24
20
  warnings: [InsightWarning.NO_LAYOUT],
25
21
  };
26
22
  }
23
+ const viewportEvent = parsedTrace.UserInteractions.parseMetaViewportEvents.find(event => {
24
+ if (event.args.data.frame !== context.frameId) {
25
+ return false;
26
+ }
27
+ return Helpers.Timing.eventIsInBounds(event, context.bounds);
28
+ });
27
29
  // Returns true only if all events are mobile optimized.
28
- for (const event of events) {
30
+ for (const event of compositorEvents) {
29
31
  if (!event.args.is_mobile_optimized) {
30
32
  return {
31
33
  mobileOptimized: false,
34
+ viewportEvent,
35
+ metricSavings: { INP: 300 },
32
36
  };
33
37
  }
34
38
  }
35
39
  return {
36
40
  mobileOptimized: true,
41
+ viewportEvent,
37
42
  };
38
43
  }
39
44
  //# sourceMappingURL=Viewport.js.map