@paulirish/trace_engine 0.0.41 → 0.0.42

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 (150) hide show
  1. package/.tmp/tsbuildinfo/tsconfig.tsbuildinfo +1 -1
  2. package/generated/protocol.d.ts +79 -3
  3. package/locales/en-US.json +222 -0
  4. package/locales/en-XL.json +222 -0
  5. package/models/cpu_profile/CPUProfileDataModel.d.ts +7 -0
  6. package/models/cpu_profile/CPUProfileDataModel.js +7 -0
  7. package/models/cpu_profile/CPUProfileDataModel.js.map +1 -1
  8. package/models/cpu_profile/ProfileTreeModel.d.ts +1 -1
  9. package/models/cpu_profile/ProfileTreeModel.js.map +1 -1
  10. package/models/trace/LanternComputationData.d.ts +1 -1
  11. package/models/trace/LanternComputationData.js +7 -5
  12. package/models/trace/LanternComputationData.js.map +1 -1
  13. package/models/trace/Processor.d.ts +2 -16
  14. package/models/trace/Processor.js +6 -4
  15. package/models/trace/Processor.js.map +1 -1
  16. package/models/trace/extras/FilmStrip.d.ts +5 -5
  17. package/models/trace/extras/FilmStrip.js +2 -1
  18. package/models/trace/extras/FilmStrip.js.map +1 -1
  19. package/models/trace/extras/MainThreadActivity.d.ts +1 -1
  20. package/models/trace/extras/MainThreadActivity.js +3 -3
  21. package/models/trace/extras/MainThreadActivity.js.map +1 -1
  22. package/models/trace/extras/ThirdParties.d.ts +14 -13
  23. package/models/trace/extras/ThirdParties.js +51 -61
  24. package/models/trace/extras/ThirdParties.js.map +1 -1
  25. package/models/trace/extras/TimelineJSProfile.d.ts +1 -1
  26. package/models/trace/extras/TimelineJSProfile.js +9 -4
  27. package/models/trace/extras/TimelineJSProfile.js.map +1 -1
  28. package/models/trace/extras/TraceTree.d.ts +6 -6
  29. package/models/trace/extras/TraceTree.js +4 -1
  30. package/models/trace/extras/TraceTree.js.map +1 -1
  31. package/models/trace/handlers/AnimationFramesHandler.js +1 -1
  32. package/models/trace/handlers/AnimationFramesHandler.js.map +1 -1
  33. package/models/trace/handlers/ExtensionTraceDataHandler.js +8 -2
  34. package/models/trace/handlers/ExtensionTraceDataHandler.js.map +1 -1
  35. package/models/trace/handlers/FlowsHandler.js.map +1 -1
  36. package/models/trace/handlers/FramesHandler.d.ts +12 -12
  37. package/models/trace/handlers/FramesHandler.js +3 -3
  38. package/models/trace/handlers/FramesHandler.js.map +1 -1
  39. package/models/trace/handlers/LayoutShiftsHandler.d.ts +2 -2
  40. package/models/trace/handlers/LayoutShiftsHandler.js +25 -16
  41. package/models/trace/handlers/LayoutShiftsHandler.js.map +1 -1
  42. package/models/trace/handlers/MetaHandler.d.ts +2 -2
  43. package/models/trace/handlers/MetaHandler.js +18 -18
  44. package/models/trace/handlers/MetaHandler.js.map +1 -1
  45. package/models/trace/handlers/NetworkRequestsHandler.js +37 -38
  46. package/models/trace/handlers/NetworkRequestsHandler.js.map +1 -1
  47. package/models/trace/handlers/PageLoadMetricsHandler.d.ts +6 -6
  48. package/models/trace/handlers/PageLoadMetricsHandler.js +9 -9
  49. package/models/trace/handlers/PageLoadMetricsHandler.js.map +1 -1
  50. package/models/trace/handlers/RendererHandler.js +3 -3
  51. package/models/trace/handlers/RendererHandler.js.map +1 -1
  52. package/models/trace/handlers/SamplesHandler.d.ts +1 -1
  53. package/models/trace/handlers/SamplesHandler.js +50 -42
  54. package/models/trace/handlers/SamplesHandler.js.map +1 -1
  55. package/models/trace/handlers/ScreenshotsHandler.d.ts +6 -3
  56. package/models/trace/handlers/ScreenshotsHandler.js +21 -7
  57. package/models/trace/handlers/ScreenshotsHandler.js.map +1 -1
  58. package/models/trace/handlers/ServerTimingsHandler.js +4 -4
  59. package/models/trace/handlers/ServerTimingsHandler.js.map +1 -1
  60. package/models/trace/handlers/Threads.js +3 -4
  61. package/models/trace/handlers/Threads.js.map +1 -1
  62. package/models/trace/handlers/UserInteractionsHandler.d.ts +2 -2
  63. package/models/trace/handlers/UserInteractionsHandler.js +12 -12
  64. package/models/trace/handlers/UserInteractionsHandler.js.map +1 -1
  65. package/models/trace/handlers/WarningsHandler.d.ts +2 -2
  66. package/models/trace/handlers/WarningsHandler.js +2 -2
  67. package/models/trace/handlers/WarningsHandler.js.map +1 -1
  68. package/models/trace/handlers/helpers.d.ts +3 -3
  69. package/models/trace/handlers/helpers.js +16 -19
  70. package/models/trace/handlers/helpers.js.map +1 -1
  71. package/models/trace/handlers/types.d.ts +1 -1
  72. package/models/trace/handlers/types.js.map +1 -1
  73. package/models/trace/helpers/SamplesIntegrator.js +52 -13
  74. package/models/trace/helpers/SamplesIntegrator.js.map +1 -1
  75. package/models/trace/helpers/Timing.d.ts +23 -23
  76. package/models/trace/helpers/Timing.js +11 -11
  77. package/models/trace/helpers/Timing.js.map +1 -1
  78. package/models/trace/helpers/Trace.d.ts +14 -13
  79. package/models/trace/helpers/Trace.js +10 -4
  80. package/models/trace/helpers/Trace.js.map +1 -1
  81. package/models/trace/helpers/TreeHelpers.d.ts +2 -2
  82. package/models/trace/helpers/TreeHelpers.js +4 -4
  83. package/models/trace/helpers/TreeHelpers.js.map +1 -1
  84. package/models/trace/insights/CLSCulprits.d.ts +44 -1
  85. package/models/trace/insights/CLSCulprits.js +44 -2
  86. package/models/trace/insights/CLSCulprits.js.map +1 -1
  87. package/models/trace/insights/Common.d.ts +11 -7
  88. package/models/trace/insights/Common.js +85 -26
  89. package/models/trace/insights/Common.js.map +1 -1
  90. package/models/trace/insights/DOMSize.d.ts +26 -1
  91. package/models/trace/insights/DOMSize.js +44 -7
  92. package/models/trace/insights/DOMSize.js.map +1 -1
  93. package/models/trace/insights/DocumentLatency.d.ts +50 -3
  94. package/models/trace/insights/DocumentLatency.js +51 -4
  95. package/models/trace/insights/DocumentLatency.js.map +1 -1
  96. package/models/trace/insights/FontDisplay.d.ts +8 -2
  97. package/models/trace/insights/FontDisplay.js +10 -4
  98. package/models/trace/insights/FontDisplay.js.map +1 -1
  99. package/models/trace/insights/ForcedReflow.d.ts +30 -0
  100. package/models/trace/insights/ForcedReflow.js +181 -0
  101. package/models/trace/insights/ForcedReflow.js.map +1 -0
  102. package/models/trace/insights/ImageDelivery.d.ts +15 -1
  103. package/models/trace/insights/ImageDelivery.js +21 -1
  104. package/models/trace/insights/ImageDelivery.js.map +1 -1
  105. package/models/trace/insights/InteractionToNextPaint.d.ts +26 -1
  106. package/models/trace/insights/InteractionToNextPaint.js +27 -1
  107. package/models/trace/insights/InteractionToNextPaint.js.map +1 -1
  108. package/models/trace/insights/LCPDiscovery.d.ts +38 -2
  109. package/models/trace/insights/LCPDiscovery.js +38 -2
  110. package/models/trace/insights/LCPDiscovery.js.map +1 -1
  111. package/models/trace/insights/LCPPhases.d.ts +36 -7
  112. package/models/trace/insights/LCPPhases.js +37 -8
  113. package/models/trace/insights/LCPPhases.js.map +1 -1
  114. package/models/trace/insights/LongCriticalNetworkTree.d.ts +22 -0
  115. package/models/trace/insights/LongCriticalNetworkTree.js +40 -0
  116. package/models/trace/insights/LongCriticalNetworkTree.js.map +1 -0
  117. package/models/trace/insights/Models.d.ts +2 -0
  118. package/models/trace/insights/Models.js +2 -0
  119. package/models/trace/insights/Models.js.map +1 -1
  120. package/models/trace/insights/RenderBlocking.d.ts +14 -1
  121. package/models/trace/insights/RenderBlocking.js +14 -1
  122. package/models/trace/insights/RenderBlocking.js.map +1 -1
  123. package/models/trace/insights/SlowCSSSelector.d.ts +27 -2
  124. package/models/trace/insights/SlowCSSSelector.js +27 -2
  125. package/models/trace/insights/SlowCSSSelector.js.map +1 -1
  126. package/models/trace/insights/ThirdParties.d.ts +13 -4
  127. package/models/trace/insights/ThirdParties.js +21 -12
  128. package/models/trace/insights/ThirdParties.js.map +1 -1
  129. package/models/trace/insights/Viewport.d.ts +2 -1
  130. package/models/trace/insights/Viewport.js +2 -1
  131. package/models/trace/insights/Viewport.js.map +1 -1
  132. package/models/trace/insights/insights-tsconfig.json +2 -0
  133. package/models/trace/insights/types.d.ts +23 -8
  134. package/models/trace/insights/types.js.map +1 -1
  135. package/models/trace/lantern/graph/CPUNode.d.ts +1 -1
  136. package/models/trace/lantern/graph/CPUNode.js.map +1 -1
  137. package/models/trace/lantern/graph/NetworkNode.d.ts +1 -1
  138. package/models/trace/lantern/graph/NetworkNode.js.map +1 -1
  139. package/models/trace/types/Configuration.d.ts +15 -0
  140. package/models/trace/types/Configuration.js.map +1 -1
  141. package/models/trace/types/File.d.ts +4 -4
  142. package/models/trace/types/File.js.map +1 -1
  143. package/models/trace/types/Timing.d.ts +7 -13
  144. package/models/trace/types/Timing.js +5 -2
  145. package/models/trace/types/Timing.js.map +1 -1
  146. package/models/trace/types/TraceEvents.d.ts +143 -108
  147. package/models/trace/types/TraceEvents.js +9 -7
  148. package/models/trace/types/TraceEvents.js.map +1 -1
  149. package/package.json +1 -1
  150. package/test/test-trace-engine.mjs +18 -14
@@ -1 +1 @@
1
- {"version":3,"file":"CLSCulprits.js","sourceRoot":"","sources":["../../../../../../../front_end/models/trace/insights/CLSCulprits.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 i18n from '../../../core/i18n/i18n.js';\nimport * as Platform from '../../../core/platform/platform.js';\nimport type * as Protocol from '../../../generated/protocol.js';\nimport * as Helpers from '../helpers/helpers.js';\nimport * as Types from '../types/types.js';\n\nimport {InsightCategory, type InsightModel, type InsightSetContext, type RequiredData} from './types.js';\n\nexport const UIStrings = {\n /** Title of an insight that provides details about why elements shift/move on the page. The causes for these shifts are referred to as culprits (\"reasons\"). */\n title: 'Layout shift culprits',\n /**\n * @description Description of a DevTools insight that identifies the reasons that elements shift on the page.\n * This is displayed after a user expands the section to see more. No character length limits.\n */\n description:\n 'Layout shifts occur when elements move absent any user interaction. [Investigate the causes of layout shifts](https://web.dev/articles/optimize-cls), such as elements being added, removed, or their fonts changing as the page loads.',\n};\nconst str_ = i18n.i18n.registerUIStrings('models/trace/insights/CLSCulprits.ts', UIStrings);\nconst i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);\n\nexport type CLSCulpritsInsightModel = InsightModel<{\n animationFailures: readonly NoncompositedAnimationFailure[],\n shifts: Map<Types.Events.SyntheticLayoutShift, LayoutShiftRootCausesData>,\n clusters: Types.Events.SyntheticLayoutShiftCluster[],\n worstCluster: Types.Events.SyntheticLayoutShiftCluster | undefined,\n}>;\n\nexport function deps(): ['Meta', 'Animations', 'LayoutShifts', 'NetworkRequests'] {\n return ['Meta', 'Animations', 'LayoutShifts', 'NetworkRequests'];\n}\n\nexport const enum AnimationFailureReasons {\n ACCELERATED_ANIMATIONS_DISABLED = 'ACCELERATED_ANIMATIONS_DISABLED',\n EFFECT_SUPPRESSED_BY_DEVTOOLS = 'EFFECT_SUPPRESSED_BY_DEVTOOLS',\n INVALID_ANIMATION_OR_EFFECT = 'INVALID_ANIMATION_OR_EFFECT',\n EFFECT_HAS_UNSUPPORTED_TIMING_PARAMS = 'EFFECT_HAS_UNSUPPORTED_TIMING_PARAMS',\n EFFECT_HAS_NON_REPLACE_COMPOSITE_MODE = 'EFFECT_HAS_NON_REPLACE_COMPOSITE_MODE',\n TARGET_HAS_INVALID_COMPOSITING_STATE = 'TARGET_HAS_INVALID_COMPOSITING_STATE',\n TARGET_HAS_INCOMPATIBLE_ANIMATIONS = 'TARGET_HAS_INCOMPATIBLE_ANIMATIONS',\n TARGET_HAS_CSS_OFFSET = 'TARGET_HAS_CSS_OFFSET',\n ANIMATION_AFFECTS_NON_CSS_PROPERTIES = 'ANIMATION_AFFECTS_NON_CSS_PROPERTIES',\n TRANSFORM_RELATED_PROPERTY_CANNOT_BE_ACCELERATED_ON_TARGET =\n 'TRANSFORM_RELATED_PROPERTY_CANNOT_BE_ACCELERATED_ON_TARGET',\n TRANSFROM_BOX_SIZE_DEPENDENT = 'TRANSFROM_BOX_SIZE_DEPENDENT',\n FILTER_RELATED_PROPERTY_MAY_MOVE_PIXELS = 'FILTER_RELATED_PROPERTY_MAY_MOVE_PIXELS',\n UNSUPPORTED_CSS_PROPERTY = 'UNSUPPORTED_CSS_PROPERTY',\n MIXED_KEYFRAME_VALUE_TYPES = 'MIXED_KEYFRAME_VALUE_TYPES',\n TIMELINE_SOURCE_HAS_INVALID_COMPOSITING_STATE = 'TIMELINE_SOURCE_HAS_INVALID_COMPOSITING_STATE',\n ANIMATION_HAS_NO_VISIBLE_CHANGE = 'ANIMATION_HAS_NO_VISIBLE_CHANGE',\n AFFECTS_IMPORTANT_PROPERTY = 'AFFECTS_IMPORTANT_PROPERTY',\n SVG_TARGET_HAS_INDEPENDENT_TRANSFORM_PROPERTY = 'SVG_TARGET_HAS_INDEPENDENT_TRANSFORM_PROPERTY',\n}\n\nexport interface NoncompositedAnimationFailure {\n /**\n * Animation name.\n */\n name?: string;\n /**\n * Failure reason based on mask number defined in\n * https://source.chromium.org/search?q=f:compositor_animations.h%20%22enum%20FailureReason%22.\n */\n failureReasons: AnimationFailureReasons[];\n /**\n * Unsupported properties.\n */\n unsupportedProperties?: Types.Events.Animation['args']['data']['unsupportedProperties'];\n /**\n * Animation event.\n */\n animation?: Types.Events.SyntheticAnimationPair;\n}\n\n/**\n * Each failure reason is represented by a bit flag. The bit shift operator '<<' is used to define\n * which bit corresponds to each failure reason.\n * https://source.chromium.org/search?q=f:compositor_animations.h%20%22enum%20FailureReason%22\n */\nconst ACTIONABLE_FAILURE_REASONS: Array<{\n flag: number,\n failure: AnimationFailureReasons,\n}> =\n [\n {\n flag: 1 << 0,\n failure: AnimationFailureReasons.ACCELERATED_ANIMATIONS_DISABLED,\n },\n {\n flag: 1 << 1,\n failure: AnimationFailureReasons.EFFECT_SUPPRESSED_BY_DEVTOOLS,\n },\n {\n flag: 1 << 2,\n failure: AnimationFailureReasons.INVALID_ANIMATION_OR_EFFECT,\n },\n {\n flag: 1 << 3,\n failure: AnimationFailureReasons.EFFECT_HAS_UNSUPPORTED_TIMING_PARAMS,\n },\n {\n flag: 1 << 4,\n failure: AnimationFailureReasons.EFFECT_HAS_NON_REPLACE_COMPOSITE_MODE,\n },\n {\n flag: 1 << 5,\n failure: AnimationFailureReasons.TARGET_HAS_INVALID_COMPOSITING_STATE,\n },\n {\n flag: 1 << 6,\n failure: AnimationFailureReasons.TARGET_HAS_INCOMPATIBLE_ANIMATIONS,\n },\n {\n flag: 1 << 7,\n failure: AnimationFailureReasons.TARGET_HAS_CSS_OFFSET,\n },\n // The failure 1 << 8 is marked as obsolete in Blink\n {\n flag: 1 << 9,\n failure: AnimationFailureReasons.ANIMATION_AFFECTS_NON_CSS_PROPERTIES,\n },\n {\n flag: 1 << 10,\n failure: AnimationFailureReasons.TRANSFORM_RELATED_PROPERTY_CANNOT_BE_ACCELERATED_ON_TARGET,\n },\n {\n flag: 1 << 11,\n failure: AnimationFailureReasons.TRANSFROM_BOX_SIZE_DEPENDENT,\n },\n {\n flag: 1 << 12,\n failure: AnimationFailureReasons.FILTER_RELATED_PROPERTY_MAY_MOVE_PIXELS,\n },\n {\n flag: 1 << 13,\n failure: AnimationFailureReasons.UNSUPPORTED_CSS_PROPERTY,\n },\n // The failure 1 << 14 is marked as obsolete in Blink\n {\n flag: 1 << 15,\n failure: AnimationFailureReasons.MIXED_KEYFRAME_VALUE_TYPES,\n },\n {\n flag: 1 << 16,\n failure: AnimationFailureReasons.TIMELINE_SOURCE_HAS_INVALID_COMPOSITING_STATE,\n },\n {\n flag: 1 << 17,\n failure: AnimationFailureReasons.ANIMATION_HAS_NO_VISIBLE_CHANGE,\n },\n {\n flag: 1 << 18,\n failure: AnimationFailureReasons.AFFECTS_IMPORTANT_PROPERTY,\n },\n {\n flag: 1 << 19,\n failure: AnimationFailureReasons.SVG_TARGET_HAS_INDEPENDENT_TRANSFORM_PROPERTY,\n },\n ] as const;\n\n// 500ms window.\n// Use this window to consider events and requests that may have caused a layout shift.\nconst ROOT_CAUSE_WINDOW = Helpers.Timing.secondsToMicro(Types.Timing.Seconds(0.5));\n\nexport interface LayoutShiftRootCausesData {\n iframeIds: string[];\n fontRequests: Types.Events.SyntheticNetworkRequest[];\n nonCompositedAnimations: NoncompositedAnimationFailure[];\n unsizedImages: Protocol.DOM.BackendNodeId[];\n}\n\n/**\n * Returns if an event happens within the root cause window, before the target event.\n * ROOT_CAUSE_WINDOW v target event\n * |------------------------|=======================\n */\nfunction isInRootCauseWindow(event: Types.Events.Event, targetEvent: Types.Events.Event): boolean {\n const eventEnd = event.dur ? event.ts + event.dur : event.ts;\n return eventEnd < targetEvent.ts && eventEnd >= targetEvent.ts - ROOT_CAUSE_WINDOW;\n}\n\nexport function getNonCompositedFailure(animationEvent: Types.Events.SyntheticAnimationPair):\n NoncompositedAnimationFailure[] {\n const failures: NoncompositedAnimationFailure[] = [];\n const beginEvent = animationEvent.args.data.beginEvent;\n const instantEvents = animationEvent.args.data.instantEvents || [];\n /**\n * Animation events containing composite information are ASYNC_NESTABLE_INSTANT ('n').\n * An animation may also contain multiple 'n' events, so we look through those with useful non-composited data.\n */\n for (const event of instantEvents) {\n const failureMask = event.args.data.compositeFailed;\n const unsupportedProperties = event.args.data.unsupportedProperties;\n if (!failureMask) {\n continue;\n }\n const failureReasons =\n ACTIONABLE_FAILURE_REASONS.filter(reason => failureMask & reason.flag).map(reason => reason.failure);\n const failure: NoncompositedAnimationFailure = {\n name: beginEvent.args.data.displayName,\n failureReasons,\n unsupportedProperties,\n animation: animationEvent,\n };\n failures.push(failure);\n }\n return failures;\n}\n\nfunction getNonCompositedFailureRootCauses(\n animationEvents: Types.Events.SyntheticAnimationPair[],\n prePaintEvents: Types.Events.PrePaint[],\n shiftsByPrePaint: Map<Types.Events.PrePaint, Types.Events.SyntheticLayoutShift[]>,\n rootCausesByShift: Map<Types.Events.SyntheticLayoutShift, LayoutShiftRootCausesData>,\n ): NoncompositedAnimationFailure[] {\n const allAnimationFailures: NoncompositedAnimationFailure[] = [];\n for (const animation of animationEvents) {\n /**\n * Animation events containing composite information are ASYNC_NESTABLE_INSTANT ('n').\n * An animation may also contain multiple 'n' events, so we look through those with useful non-composited data.\n */\n const failures = getNonCompositedFailure(animation);\n if (!failures) {\n continue;\n }\n allAnimationFailures.push(...failures);\n\n const nextPrePaint = getNextEvent(prePaintEvents, animation) as Types.Events.PrePaint | null;\n // If no following prePaint, this is not a root cause.\n if (!nextPrePaint) {\n continue;\n }\n\n // If the animation event is outside the ROOT_CAUSE_WINDOW, it could not be a root cause.\n if (!isInRootCauseWindow(animation, nextPrePaint)) {\n continue;\n }\n\n const shifts = shiftsByPrePaint.get(nextPrePaint);\n // if no layout shift(s), this is not a root cause.\n if (!shifts) {\n continue;\n }\n\n for (const shift of shifts) {\n const rootCausesForShift = rootCausesByShift.get(shift);\n if (!rootCausesForShift) {\n throw new Error('Unaccounted shift');\n }\n rootCausesForShift.nonCompositedAnimations.push(...failures);\n }\n }\n\n return allAnimationFailures;\n}\n\n/**\n * Given an array of layout shift and PrePaint events, returns a mapping from\n * PrePaint events to layout shifts dispatched within it.\n */\nfunction getShiftsByPrePaintEvents(\n layoutShifts: Types.Events.SyntheticLayoutShift[],\n prePaintEvents: Types.Events.PrePaint[],\n ): Map<Types.Events.PrePaint, Types.Events.SyntheticLayoutShift[]> {\n // Maps from PrePaint events to LayoutShifts that occured in each one.\n const shiftsByPrePaint = new Map<Types.Events.PrePaint, Types.Events.SyntheticLayoutShift[]>();\n\n // Associate all shifts to their corresponding PrePaint.\n for (const prePaintEvent of prePaintEvents) {\n const firstShiftIndex =\n Platform.ArrayUtilities.nearestIndexFromBeginning(layoutShifts, shift => shift.ts >= prePaintEvent.ts);\n if (firstShiftIndex === null) {\n // No layout shifts registered after this PrePaint start. Continue.\n continue;\n }\n for (let i = firstShiftIndex; i < layoutShifts.length; i++) {\n const shift = layoutShifts[i];\n if (shift.ts >= prePaintEvent.ts && shift.ts <= prePaintEvent.ts + prePaintEvent.dur) {\n const shiftsInPrePaint = Platform.MapUtilities.getWithDefault(shiftsByPrePaint, prePaintEvent, () => []);\n shiftsInPrePaint.push(shift);\n }\n if (shift.ts > prePaintEvent.ts + prePaintEvent.dur) {\n // Reached all layoutShifts of this PrePaint. Break out to continue with the next prePaint event.\n break;\n }\n }\n }\n return shiftsByPrePaint;\n}\n\n/**\n * Given a source event list, this returns the first event of that list that directly follows the target event.\n */\nfunction getNextEvent(sourceEvents: Types.Events.Event[], targetEvent: Types.Events.Event): Types.Events.Event|\n undefined {\n const index = Platform.ArrayUtilities.nearestIndexFromBeginning(\n sourceEvents, source => source.ts > targetEvent.ts + (targetEvent.dur || 0));\n // No PrePaint event registered after this event\n if (index === null) {\n return undefined;\n }\n\n return sourceEvents[index];\n}\n\n/**\n * An Iframe is considered a root cause if the iframe event occurs before a prePaint event\n * and within this prePaint event a layout shift(s) occurs.\n */\nfunction getIframeRootCauses(\n iframeCreatedEvents: readonly Types.Events.RenderFrameImplCreateChildFrame[],\n prePaintEvents: Types.Events.PrePaint[],\n shiftsByPrePaint: Map<Types.Events.PrePaint, Types.Events.SyntheticLayoutShift[]>,\n rootCausesByShift: Map<Types.Events.SyntheticLayoutShift, LayoutShiftRootCausesData>,\n domLoadingEvents: readonly Types.Events.DomLoading[]):\n Map<Types.Events.SyntheticLayoutShift, LayoutShiftRootCausesData> {\n for (const iframeEvent of iframeCreatedEvents) {\n const nextPrePaint = getNextEvent(prePaintEvents, iframeEvent) as Types.Events.PrePaint | null;\n // If no following prePaint, this is not a root cause.\n if (!nextPrePaint) {\n continue;\n }\n const shifts = shiftsByPrePaint.get(nextPrePaint);\n // if no layout shift(s), this is not a root cause.\n if (!shifts) {\n continue;\n }\n for (const shift of shifts) {\n const rootCausesForShift = rootCausesByShift.get(shift);\n if (!rootCausesForShift) {\n throw new Error('Unaccounted shift');\n }\n\n // Look for the first dom event that occurs within the bounds of the iframe event.\n // This contains the frame id.\n const domEvent = domLoadingEvents.find(e => {\n const maxIframe = Types.Timing.MicroSeconds(iframeEvent.ts + (iframeEvent.dur ?? 0));\n return e.ts >= iframeEvent.ts && e.ts <= maxIframe;\n });\n if (domEvent && domEvent.args.frame) {\n rootCausesForShift.iframeIds.push(domEvent.args.frame);\n }\n }\n }\n return rootCausesByShift;\n}\n\n/**\n * An unsized image is considered a root cause if its PaintImage can be correlated to a\n * layout shift. We can correlate PaintImages with unsized images by their matching nodeIds.\n * X <- layout shift\n * |----------------|\n * ^ PrePaint event |-----|\n * ^ PaintImage\n */\nfunction getUnsizedImageRootCauses(\n unsizedImageEvents: readonly Types.Events.LayoutImageUnsized[], paintImageEvents: Types.Events.PaintImage[],\n shiftsByPrePaint: Map<Types.Events.PrePaint, Types.Events.SyntheticLayoutShift[]>,\n rootCausesByShift: Map<Types.Events.SyntheticLayoutShift, LayoutShiftRootCausesData>):\n Map<Types.Events.SyntheticLayoutShift, LayoutShiftRootCausesData> {\n shiftsByPrePaint.forEach((shifts, prePaint) => {\n const paintImage = getNextEvent(paintImageEvents, prePaint) as Types.Events.PaintImage | null;\n // The unsized image corresponds to this PaintImage.\n const matchingNode =\n unsizedImageEvents.find(unsizedImage => unsizedImage.args.data.nodeId === paintImage?.args.data.nodeId);\n if (!matchingNode) {\n return;\n }\n // The unsized image is a potential root cause of all the shifts of this prePaint.\n for (const shift of shifts) {\n const rootCausesForShift = rootCausesByShift.get(shift);\n if (!rootCausesForShift) {\n throw new Error('Unaccounted shift');\n }\n rootCausesForShift.unsizedImages.push(matchingNode.args.data.nodeId);\n }\n });\n return rootCausesByShift;\n}\n\n/**\n * A font request is considered a root cause if the request occurs before a prePaint event\n * and within this prePaint event a layout shift(s) occurs. Additionally, this font request should\n * happen within the ROOT_CAUSE_WINDOW of the prePaint event.\n */\nfunction getFontRootCauses(\n networkRequests: Types.Events.SyntheticNetworkRequest[], prePaintEvents: Types.Events.PrePaint[],\n shiftsByPrePaint: Map<Types.Events.PrePaint, Types.Events.SyntheticLayoutShift[]>,\n rootCausesByShift: Map<Types.Events.SyntheticLayoutShift, LayoutShiftRootCausesData>):\n Map<Types.Events.SyntheticLayoutShift, LayoutShiftRootCausesData> {\n const fontRequests =\n networkRequests.filter(req => req.args.data.resourceType === 'Font' && req.args.data.mimeType.startsWith('font'));\n\n for (const req of fontRequests) {\n const nextPrePaint = getNextEvent(prePaintEvents, req) as Types.Events.PrePaint | null;\n if (!nextPrePaint) {\n continue;\n }\n\n // If the req is outside the ROOT_CAUSE_WINDOW, it could not be a root cause.\n if (!isInRootCauseWindow(req, nextPrePaint)) {\n continue;\n }\n\n // Get the shifts that belong to this prepaint\n const shifts = shiftsByPrePaint.get(nextPrePaint);\n\n // if no layout shift(s) in this prePaint, the request is not a root cause.\n if (!shifts) {\n continue;\n }\n // Include the root cause to the shifts in this prePaint.\n for (const shift of shifts) {\n const rootCausesForShift = rootCausesByShift.get(shift);\n if (!rootCausesForShift) {\n throw new Error('Unaccounted shift');\n }\n rootCausesForShift.fontRequests.push(req);\n }\n }\n return rootCausesByShift;\n}\n\nfunction finalize(partialModel: Omit<CLSCulpritsInsightModel, 'title'|'description'|'category'|'shouldShow'>):\n CLSCulpritsInsightModel {\n let maxScore = 0;\n for (const cluster of partialModel.clusters) {\n if (cluster.clusterCumulativeScore > maxScore) {\n maxScore = cluster.clusterCumulativeScore;\n }\n }\n\n return {\n title: i18nString(UIStrings.title),\n description: i18nString(UIStrings.description),\n category: InsightCategory.CLS,\n // TODO: getTopCulprits in component needs to move to model so this can be set properly here.\n shouldShow: maxScore > 0,\n ...partialModel,\n };\n}\n\nexport function generateInsight(\n parsedTrace: RequiredData<typeof deps>, context: InsightSetContext): CLSCulpritsInsightModel {\n const isWithinContext = (event: Types.Events.Event): boolean => Helpers.Timing.eventIsInBounds(event, context.bounds);\n\n const compositeAnimationEvents = parsedTrace.Animations.animations.filter(isWithinContext);\n const iframeEvents = parsedTrace.LayoutShifts.renderFrameImplCreateChildFrameEvents.filter(isWithinContext);\n const networkRequests = parsedTrace.NetworkRequests.byTime.filter(isWithinContext);\n const domLoadingEvents = parsedTrace.LayoutShifts.domLoadingEvents.filter(isWithinContext);\n const unsizedImageEvents = parsedTrace.LayoutShifts.layoutImageUnsizedEvents.filter(isWithinContext);\n\n const clusterKey = context.navigation ? context.navigationId : Types.Events.NO_NAVIGATION;\n const clusters = parsedTrace.LayoutShifts.clustersByNavigationId.get(clusterKey) ?? [];\n const clustersByScore = [...clusters].sort((a, b) => b.clusterCumulativeScore - a.clusterCumulativeScore);\n const worstCluster = clustersByScore.at(0);\n const layoutShifts = clusters.flatMap(cluster => cluster.events);\n const prePaintEvents = parsedTrace.LayoutShifts.prePaintEvents.filter(isWithinContext);\n const paintImageEvents = parsedTrace.LayoutShifts.paintImageEvents.filter(isWithinContext);\n\n // Get root causes.\n const rootCausesByShift = new Map<Types.Events.SyntheticLayoutShift, LayoutShiftRootCausesData>();\n const shiftsByPrePaint = getShiftsByPrePaintEvents(layoutShifts, prePaintEvents);\n\n for (const shift of layoutShifts) {\n rootCausesByShift.set(shift, {iframeIds: [], fontRequests: [], nonCompositedAnimations: [], unsizedImages: []});\n }\n\n // Populate root causes for rootCausesByShift.\n getIframeRootCauses(iframeEvents, prePaintEvents, shiftsByPrePaint, rootCausesByShift, domLoadingEvents);\n getFontRootCauses(networkRequests, prePaintEvents, shiftsByPrePaint, rootCausesByShift);\n getUnsizedImageRootCauses(unsizedImageEvents, paintImageEvents, shiftsByPrePaint, rootCausesByShift);\n const animationFailures =\n getNonCompositedFailureRootCauses(compositeAnimationEvents, prePaintEvents, shiftsByPrePaint, rootCausesByShift);\n\n const relatedEvents: Types.Events.Event[] = [...layoutShifts];\n if (worstCluster) {\n relatedEvents.push(worstCluster);\n }\n\n return finalize({\n relatedEvents,\n animationFailures,\n shifts: rootCausesByShift,\n clusters,\n worstCluster,\n });\n}\n"]}
1
+ {"version":3,"file":"CLSCulprits.js","sourceRoot":"","sources":["../../../../../../../front_end/models/trace/insights/CLSCulprits.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 i18n from '../../../core/i18n/i18n.js';\nimport * as Platform from '../../../core/platform/platform.js';\nimport type * as Protocol from '../../../generated/protocol.js';\nimport * as Helpers from '../helpers/helpers.js';\nimport * as Types from '../types/types.js';\n\nimport {\n InsightCategory,\n type InsightModel,\n type InsightSetContext,\n type PartialInsightModel,\n type RequiredData\n} from './types.js';\n\nexport const UIStrings = {\n /** Title of an insight that provides details about why elements shift/move on the page. The causes for these shifts are referred to as culprits (\"reasons\"). */\n title: 'Layout shift culprits',\n /**\n * @description Description of a DevTools insight that identifies the reasons that elements shift on the page.\n * This is displayed after a user expands the section to see more. No character length limits.\n */\n description:\n 'Layout shifts occur when elements move absent any user interaction. [Investigate the causes of layout shifts](https://web.dev/articles/optimize-cls), such as elements being added, removed, or their fonts changing as the page loads.',\n /**\n *@description Text indicating the worst layout shift cluster.\n */\n worstLayoutShiftCluster: 'Worst layout shift cluster',\n /**\n * @description Text indicating the worst layout shift cluster.\n */\n worstCluster: 'Worst cluster',\n /**\n * @description Text indicating a layout shift cluster and its start time.\n * @example {32 ms} PH1\n */\n layoutShiftCluster: 'Layout shift cluster @ {PH1}',\n /**\n *@description Text indicating the biggest reasons for the layout shifts.\n */\n topCulprits: 'Top layout shift culprits',\n /**\n * @description Text for a culprit type of Injected iframe.\n */\n injectedIframe: 'Injected iframe',\n /**\n * @description Text for a culprit type of Font request.\n */\n fontRequest: 'Font request',\n /**\n * @description Text for a culprit type of Animation.\n */\n animation: 'Animation',\n /**\n * @description Text for a culprit type of Unsized images.\n */\n unsizedImages: 'Unsized Images',\n /**\n * @description Text status when there were no layout shifts detected.\n */\n noLayoutShifts: 'No layout shifts',\n /**\n * @description Text status when there no layout shifts culprits/root causes were found.\n */\n noCulprits: 'Could not detect any layout shift culprits',\n};\n\nconst str_ = i18n.i18n.registerUIStrings('models/trace/insights/CLSCulprits.ts', UIStrings);\nexport const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);\n\nexport type CLSCulpritsInsightModel = InsightModel<typeof UIStrings, {\n animationFailures: readonly NoncompositedAnimationFailure[],\n shifts: Map<Types.Events.SyntheticLayoutShift, LayoutShiftRootCausesData>,\n clusters: Types.Events.SyntheticLayoutShiftCluster[],\n worstCluster: Types.Events.SyntheticLayoutShiftCluster | undefined,\n}>;\n\nexport function deps(): ['Meta', 'Animations', 'LayoutShifts', 'NetworkRequests'] {\n return ['Meta', 'Animations', 'LayoutShifts', 'NetworkRequests'];\n}\n\nexport const enum AnimationFailureReasons {\n ACCELERATED_ANIMATIONS_DISABLED = 'ACCELERATED_ANIMATIONS_DISABLED',\n EFFECT_SUPPRESSED_BY_DEVTOOLS = 'EFFECT_SUPPRESSED_BY_DEVTOOLS',\n INVALID_ANIMATION_OR_EFFECT = 'INVALID_ANIMATION_OR_EFFECT',\n EFFECT_HAS_UNSUPPORTED_TIMING_PARAMS = 'EFFECT_HAS_UNSUPPORTED_TIMING_PARAMS',\n EFFECT_HAS_NON_REPLACE_COMPOSITE_MODE = 'EFFECT_HAS_NON_REPLACE_COMPOSITE_MODE',\n TARGET_HAS_INVALID_COMPOSITING_STATE = 'TARGET_HAS_INVALID_COMPOSITING_STATE',\n TARGET_HAS_INCOMPATIBLE_ANIMATIONS = 'TARGET_HAS_INCOMPATIBLE_ANIMATIONS',\n TARGET_HAS_CSS_OFFSET = 'TARGET_HAS_CSS_OFFSET',\n ANIMATION_AFFECTS_NON_CSS_PROPERTIES = 'ANIMATION_AFFECTS_NON_CSS_PROPERTIES',\n TRANSFORM_RELATED_PROPERTY_CANNOT_BE_ACCELERATED_ON_TARGET =\n 'TRANSFORM_RELATED_PROPERTY_CANNOT_BE_ACCELERATED_ON_TARGET',\n TRANSFROM_BOX_SIZE_DEPENDENT = 'TRANSFROM_BOX_SIZE_DEPENDENT',\n FILTER_RELATED_PROPERTY_MAY_MOVE_PIXELS = 'FILTER_RELATED_PROPERTY_MAY_MOVE_PIXELS',\n UNSUPPORTED_CSS_PROPERTY = 'UNSUPPORTED_CSS_PROPERTY',\n MIXED_KEYFRAME_VALUE_TYPES = 'MIXED_KEYFRAME_VALUE_TYPES',\n TIMELINE_SOURCE_HAS_INVALID_COMPOSITING_STATE = 'TIMELINE_SOURCE_HAS_INVALID_COMPOSITING_STATE',\n ANIMATION_HAS_NO_VISIBLE_CHANGE = 'ANIMATION_HAS_NO_VISIBLE_CHANGE',\n AFFECTS_IMPORTANT_PROPERTY = 'AFFECTS_IMPORTANT_PROPERTY',\n SVG_TARGET_HAS_INDEPENDENT_TRANSFORM_PROPERTY = 'SVG_TARGET_HAS_INDEPENDENT_TRANSFORM_PROPERTY',\n}\n\nexport interface NoncompositedAnimationFailure {\n /**\n * Animation name.\n */\n name?: string;\n /**\n * Failure reason based on mask number defined in\n * https://source.chromium.org/search?q=f:compositor_animations.h%20%22enum%20FailureReason%22.\n */\n failureReasons: AnimationFailureReasons[];\n /**\n * Unsupported properties.\n */\n unsupportedProperties?: Types.Events.Animation['args']['data']['unsupportedProperties'];\n /**\n * Animation event.\n */\n animation?: Types.Events.SyntheticAnimationPair;\n}\n\n/**\n * Each failure reason is represented by a bit flag. The bit shift operator '<<' is used to define\n * which bit corresponds to each failure reason.\n * https://source.chromium.org/search?q=f:compositor_animations.h%20%22enum%20FailureReason%22\n */\nconst ACTIONABLE_FAILURE_REASONS: Array<{\n flag: number,\n failure: AnimationFailureReasons,\n}> =\n [\n {\n flag: 1 << 0,\n failure: AnimationFailureReasons.ACCELERATED_ANIMATIONS_DISABLED,\n },\n {\n flag: 1 << 1,\n failure: AnimationFailureReasons.EFFECT_SUPPRESSED_BY_DEVTOOLS,\n },\n {\n flag: 1 << 2,\n failure: AnimationFailureReasons.INVALID_ANIMATION_OR_EFFECT,\n },\n {\n flag: 1 << 3,\n failure: AnimationFailureReasons.EFFECT_HAS_UNSUPPORTED_TIMING_PARAMS,\n },\n {\n flag: 1 << 4,\n failure: AnimationFailureReasons.EFFECT_HAS_NON_REPLACE_COMPOSITE_MODE,\n },\n {\n flag: 1 << 5,\n failure: AnimationFailureReasons.TARGET_HAS_INVALID_COMPOSITING_STATE,\n },\n {\n flag: 1 << 6,\n failure: AnimationFailureReasons.TARGET_HAS_INCOMPATIBLE_ANIMATIONS,\n },\n {\n flag: 1 << 7,\n failure: AnimationFailureReasons.TARGET_HAS_CSS_OFFSET,\n },\n // The failure 1 << 8 is marked as obsolete in Blink\n {\n flag: 1 << 9,\n failure: AnimationFailureReasons.ANIMATION_AFFECTS_NON_CSS_PROPERTIES,\n },\n {\n flag: 1 << 10,\n failure: AnimationFailureReasons.TRANSFORM_RELATED_PROPERTY_CANNOT_BE_ACCELERATED_ON_TARGET,\n },\n {\n flag: 1 << 11,\n failure: AnimationFailureReasons.TRANSFROM_BOX_SIZE_DEPENDENT,\n },\n {\n flag: 1 << 12,\n failure: AnimationFailureReasons.FILTER_RELATED_PROPERTY_MAY_MOVE_PIXELS,\n },\n {\n flag: 1 << 13,\n failure: AnimationFailureReasons.UNSUPPORTED_CSS_PROPERTY,\n },\n // The failure 1 << 14 is marked as obsolete in Blink\n {\n flag: 1 << 15,\n failure: AnimationFailureReasons.MIXED_KEYFRAME_VALUE_TYPES,\n },\n {\n flag: 1 << 16,\n failure: AnimationFailureReasons.TIMELINE_SOURCE_HAS_INVALID_COMPOSITING_STATE,\n },\n {\n flag: 1 << 17,\n failure: AnimationFailureReasons.ANIMATION_HAS_NO_VISIBLE_CHANGE,\n },\n {\n flag: 1 << 18,\n failure: AnimationFailureReasons.AFFECTS_IMPORTANT_PROPERTY,\n },\n {\n flag: 1 << 19,\n failure: AnimationFailureReasons.SVG_TARGET_HAS_INDEPENDENT_TRANSFORM_PROPERTY,\n },\n ] as const;\n\n// 500ms window.\n// Use this window to consider events and requests that may have caused a layout shift.\nconst ROOT_CAUSE_WINDOW = Helpers.Timing.secondsToMicro(Types.Timing.Seconds(0.5));\n\nexport interface LayoutShiftRootCausesData {\n iframeIds: string[];\n fontRequests: Types.Events.SyntheticNetworkRequest[];\n nonCompositedAnimations: NoncompositedAnimationFailure[];\n unsizedImages: Protocol.DOM.BackendNodeId[];\n}\n\n/**\n * Returns if an event happens within the root cause window, before the target event.\n * ROOT_CAUSE_WINDOW v target event\n * |------------------------|=======================\n */\nfunction isInRootCauseWindow(event: Types.Events.Event, targetEvent: Types.Events.Event): boolean {\n const eventEnd = event.dur ? event.ts + event.dur : event.ts;\n return eventEnd < targetEvent.ts && eventEnd >= targetEvent.ts - ROOT_CAUSE_WINDOW;\n}\n\nexport function getNonCompositedFailure(animationEvent: Types.Events.SyntheticAnimationPair):\n NoncompositedAnimationFailure[] {\n const failures: NoncompositedAnimationFailure[] = [];\n const beginEvent = animationEvent.args.data.beginEvent;\n const instantEvents = animationEvent.args.data.instantEvents || [];\n /**\n * Animation events containing composite information are ASYNC_NESTABLE_INSTANT ('n').\n * An animation may also contain multiple 'n' events, so we look through those with useful non-composited data.\n */\n for (const event of instantEvents) {\n const failureMask = event.args.data.compositeFailed;\n const unsupportedProperties = event.args.data.unsupportedProperties;\n if (!failureMask) {\n continue;\n }\n const failureReasons =\n ACTIONABLE_FAILURE_REASONS.filter(reason => failureMask & reason.flag).map(reason => reason.failure);\n const failure: NoncompositedAnimationFailure = {\n name: beginEvent.args.data.displayName,\n failureReasons,\n unsupportedProperties,\n animation: animationEvent,\n };\n failures.push(failure);\n }\n return failures;\n}\n\nfunction getNonCompositedFailureRootCauses(\n animationEvents: Types.Events.SyntheticAnimationPair[],\n prePaintEvents: Types.Events.PrePaint[],\n shiftsByPrePaint: Map<Types.Events.PrePaint, Types.Events.SyntheticLayoutShift[]>,\n rootCausesByShift: Map<Types.Events.SyntheticLayoutShift, LayoutShiftRootCausesData>,\n ): NoncompositedAnimationFailure[] {\n const allAnimationFailures: NoncompositedAnimationFailure[] = [];\n for (const animation of animationEvents) {\n /**\n * Animation events containing composite information are ASYNC_NESTABLE_INSTANT ('n').\n * An animation may also contain multiple 'n' events, so we look through those with useful non-composited data.\n */\n const failures = getNonCompositedFailure(animation);\n if (!failures) {\n continue;\n }\n allAnimationFailures.push(...failures);\n\n const nextPrePaint = getNextEvent(prePaintEvents, animation) as Types.Events.PrePaint | null;\n // If no following prePaint, this is not a root cause.\n if (!nextPrePaint) {\n continue;\n }\n\n // If the animation event is outside the ROOT_CAUSE_WINDOW, it could not be a root cause.\n if (!isInRootCauseWindow(animation, nextPrePaint)) {\n continue;\n }\n\n const shifts = shiftsByPrePaint.get(nextPrePaint);\n // if no layout shift(s), this is not a root cause.\n if (!shifts) {\n continue;\n }\n\n for (const shift of shifts) {\n const rootCausesForShift = rootCausesByShift.get(shift);\n if (!rootCausesForShift) {\n throw new Error('Unaccounted shift');\n }\n rootCausesForShift.nonCompositedAnimations.push(...failures);\n }\n }\n\n return allAnimationFailures;\n}\n\n/**\n * Given an array of layout shift and PrePaint events, returns a mapping from\n * PrePaint events to layout shifts dispatched within it.\n */\nfunction getShiftsByPrePaintEvents(\n layoutShifts: Types.Events.SyntheticLayoutShift[],\n prePaintEvents: Types.Events.PrePaint[],\n ): Map<Types.Events.PrePaint, Types.Events.SyntheticLayoutShift[]> {\n // Maps from PrePaint events to LayoutShifts that occured in each one.\n const shiftsByPrePaint = new Map<Types.Events.PrePaint, Types.Events.SyntheticLayoutShift[]>();\n\n // Associate all shifts to their corresponding PrePaint.\n for (const prePaintEvent of prePaintEvents) {\n const firstShiftIndex =\n Platform.ArrayUtilities.nearestIndexFromBeginning(layoutShifts, shift => shift.ts >= prePaintEvent.ts);\n if (firstShiftIndex === null) {\n // No layout shifts registered after this PrePaint start. Continue.\n continue;\n }\n for (let i = firstShiftIndex; i < layoutShifts.length; i++) {\n const shift = layoutShifts[i];\n if (shift.ts >= prePaintEvent.ts && shift.ts <= prePaintEvent.ts + prePaintEvent.dur) {\n const shiftsInPrePaint = Platform.MapUtilities.getWithDefault(shiftsByPrePaint, prePaintEvent, () => []);\n shiftsInPrePaint.push(shift);\n }\n if (shift.ts > prePaintEvent.ts + prePaintEvent.dur) {\n // Reached all layoutShifts of this PrePaint. Break out to continue with the next prePaint event.\n break;\n }\n }\n }\n return shiftsByPrePaint;\n}\n\n/**\n * Given a source event list, this returns the first event of that list that directly follows the target event.\n */\nfunction getNextEvent(sourceEvents: Types.Events.Event[], targetEvent: Types.Events.Event): Types.Events.Event|\n undefined {\n const index = Platform.ArrayUtilities.nearestIndexFromBeginning(\n sourceEvents, source => source.ts > targetEvent.ts + (targetEvent.dur || 0));\n // No PrePaint event registered after this event\n if (index === null) {\n return undefined;\n }\n\n return sourceEvents[index];\n}\n\n/**\n * An Iframe is considered a root cause if the iframe event occurs before a prePaint event\n * and within this prePaint event a layout shift(s) occurs.\n */\nfunction getIframeRootCauses(\n iframeCreatedEvents: readonly Types.Events.RenderFrameImplCreateChildFrame[],\n prePaintEvents: Types.Events.PrePaint[],\n shiftsByPrePaint: Map<Types.Events.PrePaint, Types.Events.SyntheticLayoutShift[]>,\n rootCausesByShift: Map<Types.Events.SyntheticLayoutShift, LayoutShiftRootCausesData>,\n domLoadingEvents: readonly Types.Events.DomLoading[]):\n Map<Types.Events.SyntheticLayoutShift, LayoutShiftRootCausesData> {\n for (const iframeEvent of iframeCreatedEvents) {\n const nextPrePaint = getNextEvent(prePaintEvents, iframeEvent) as Types.Events.PrePaint | null;\n // If no following prePaint, this is not a root cause.\n if (!nextPrePaint) {\n continue;\n }\n const shifts = shiftsByPrePaint.get(nextPrePaint);\n // if no layout shift(s), this is not a root cause.\n if (!shifts) {\n continue;\n }\n for (const shift of shifts) {\n const rootCausesForShift = rootCausesByShift.get(shift);\n if (!rootCausesForShift) {\n throw new Error('Unaccounted shift');\n }\n\n // Look for the first dom event that occurs within the bounds of the iframe event.\n // This contains the frame id.\n const domEvent = domLoadingEvents.find(e => {\n const maxIframe = Types.Timing.Micro(iframeEvent.ts + (iframeEvent.dur ?? 0));\n return e.ts >= iframeEvent.ts && e.ts <= maxIframe;\n });\n if (domEvent && domEvent.args.frame) {\n rootCausesForShift.iframeIds.push(domEvent.args.frame);\n }\n }\n }\n return rootCausesByShift;\n}\n\n/**\n * An unsized image is considered a root cause if its PaintImage can be correlated to a\n * layout shift. We can correlate PaintImages with unsized images by their matching nodeIds.\n * X <- layout shift\n * |----------------|\n * ^ PrePaint event |-----|\n * ^ PaintImage\n */\nfunction getUnsizedImageRootCauses(\n unsizedImageEvents: readonly Types.Events.LayoutImageUnsized[], paintImageEvents: Types.Events.PaintImage[],\n shiftsByPrePaint: Map<Types.Events.PrePaint, Types.Events.SyntheticLayoutShift[]>,\n rootCausesByShift: Map<Types.Events.SyntheticLayoutShift, LayoutShiftRootCausesData>):\n Map<Types.Events.SyntheticLayoutShift, LayoutShiftRootCausesData> {\n shiftsByPrePaint.forEach((shifts, prePaint) => {\n const paintImage = getNextEvent(paintImageEvents, prePaint) as Types.Events.PaintImage | null;\n // The unsized image corresponds to this PaintImage.\n const matchingNode =\n unsizedImageEvents.find(unsizedImage => unsizedImage.args.data.nodeId === paintImage?.args.data.nodeId);\n if (!matchingNode) {\n return;\n }\n // The unsized image is a potential root cause of all the shifts of this prePaint.\n for (const shift of shifts) {\n const rootCausesForShift = rootCausesByShift.get(shift);\n if (!rootCausesForShift) {\n throw new Error('Unaccounted shift');\n }\n rootCausesForShift.unsizedImages.push(matchingNode.args.data.nodeId);\n }\n });\n return rootCausesByShift;\n}\n\n/**\n * A font request is considered a root cause if the request occurs before a prePaint event\n * and within this prePaint event a layout shift(s) occurs. Additionally, this font request should\n * happen within the ROOT_CAUSE_WINDOW of the prePaint event.\n */\nfunction getFontRootCauses(\n networkRequests: Types.Events.SyntheticNetworkRequest[], prePaintEvents: Types.Events.PrePaint[],\n shiftsByPrePaint: Map<Types.Events.PrePaint, Types.Events.SyntheticLayoutShift[]>,\n rootCausesByShift: Map<Types.Events.SyntheticLayoutShift, LayoutShiftRootCausesData>):\n Map<Types.Events.SyntheticLayoutShift, LayoutShiftRootCausesData> {\n const fontRequests =\n networkRequests.filter(req => req.args.data.resourceType === 'Font' && req.args.data.mimeType.startsWith('font'));\n\n for (const req of fontRequests) {\n const nextPrePaint = getNextEvent(prePaintEvents, req) as Types.Events.PrePaint | null;\n if (!nextPrePaint) {\n continue;\n }\n\n // If the req is outside the ROOT_CAUSE_WINDOW, it could not be a root cause.\n if (!isInRootCauseWindow(req, nextPrePaint)) {\n continue;\n }\n\n // Get the shifts that belong to this prepaint\n const shifts = shiftsByPrePaint.get(nextPrePaint);\n\n // if no layout shift(s) in this prePaint, the request is not a root cause.\n if (!shifts) {\n continue;\n }\n // Include the root cause to the shifts in this prePaint.\n for (const shift of shifts) {\n const rootCausesForShift = rootCausesByShift.get(shift);\n if (!rootCausesForShift) {\n throw new Error('Unaccounted shift');\n }\n rootCausesForShift.fontRequests.push(req);\n }\n }\n return rootCausesByShift;\n}\n\nfunction finalize(partialModel: PartialInsightModel<CLSCulpritsInsightModel>): CLSCulpritsInsightModel {\n let maxScore = 0;\n for (const cluster of partialModel.clusters) {\n if (cluster.clusterCumulativeScore > maxScore) {\n maxScore = cluster.clusterCumulativeScore;\n }\n }\n\n return {\n strings: UIStrings,\n title: i18nString(UIStrings.title),\n description: i18nString(UIStrings.description),\n category: InsightCategory.CLS,\n // TODO: getTopCulprits in component needs to move to model so this can be set properly here.\n shouldShow: maxScore > 0,\n ...partialModel,\n };\n}\n\nexport function generateInsight(\n parsedTrace: RequiredData<typeof deps>, context: InsightSetContext): CLSCulpritsInsightModel {\n const isWithinContext = (event: Types.Events.Event): boolean => Helpers.Timing.eventIsInBounds(event, context.bounds);\n\n const compositeAnimationEvents = parsedTrace.Animations.animations.filter(isWithinContext);\n const iframeEvents = parsedTrace.LayoutShifts.renderFrameImplCreateChildFrameEvents.filter(isWithinContext);\n const networkRequests = parsedTrace.NetworkRequests.byTime.filter(isWithinContext);\n const domLoadingEvents = parsedTrace.LayoutShifts.domLoadingEvents.filter(isWithinContext);\n const unsizedImageEvents = parsedTrace.LayoutShifts.layoutImageUnsizedEvents.filter(isWithinContext);\n\n const clusterKey = context.navigation ? context.navigationId : Types.Events.NO_NAVIGATION;\n const clusters = parsedTrace.LayoutShifts.clustersByNavigationId.get(clusterKey) ?? [];\n const clustersByScore = [...clusters].sort((a, b) => b.clusterCumulativeScore - a.clusterCumulativeScore);\n const worstCluster = clustersByScore.at(0);\n const layoutShifts = clusters.flatMap(cluster => cluster.events);\n const prePaintEvents = parsedTrace.LayoutShifts.prePaintEvents.filter(isWithinContext);\n const paintImageEvents = parsedTrace.LayoutShifts.paintImageEvents.filter(isWithinContext);\n\n // Get root causes.\n const rootCausesByShift = new Map<Types.Events.SyntheticLayoutShift, LayoutShiftRootCausesData>();\n const shiftsByPrePaint = getShiftsByPrePaintEvents(layoutShifts, prePaintEvents);\n\n for (const shift of layoutShifts) {\n rootCausesByShift.set(shift, {iframeIds: [], fontRequests: [], nonCompositedAnimations: [], unsizedImages: []});\n }\n\n // Populate root causes for rootCausesByShift.\n getIframeRootCauses(iframeEvents, prePaintEvents, shiftsByPrePaint, rootCausesByShift, domLoadingEvents);\n getFontRootCauses(networkRequests, prePaintEvents, shiftsByPrePaint, rootCausesByShift);\n getUnsizedImageRootCauses(unsizedImageEvents, paintImageEvents, shiftsByPrePaint, rootCausesByShift);\n const animationFailures =\n getNonCompositedFailureRootCauses(compositeAnimationEvents, prePaintEvents, shiftsByPrePaint, rootCausesByShift);\n\n const relatedEvents: Types.Events.Event[] = [...layoutShifts];\n if (worstCluster) {\n relatedEvents.push(worstCluster);\n }\n\n return finalize({\n relatedEvents,\n animationFailures,\n shifts: rootCausesByShift,\n clusters,\n worstCluster,\n });\n}\n"]}
@@ -1,24 +1,24 @@
1
1
  // import type * as CrUXManager from '../../crux-manager/crux-manager.js';
2
- import type * as Types from '../types/types.js';
3
- import type { InsightModels, InsightSet, TraceInsightSets } from './types.js';
2
+ import * as Types from '../types/types.js';
3
+ import type { InsightModels, InsightSet, InsightSetContext, MetricSavings, TraceInsightSets } from './types.js';
4
4
  export declare function getInsight<InsightName extends keyof InsightModels>(insightName: InsightName, insights: TraceInsightSets | null, key: string | null): InsightModels[InsightName] | null;
5
5
  export declare function getLCP(insights: TraceInsightSets | null, key: string | null): {
6
- value: Types.Timing.MicroSeconds;
6
+ value: Types.Timing.Micro;
7
7
  event: Types.Events.LargestContentfulPaintCandidate;
8
8
  } | null;
9
9
  export declare function getINP(insights: TraceInsightSets | null, key: string | null): {
10
- value: Types.Timing.MicroSeconds;
10
+ value: Types.Timing.Micro;
11
11
  event: Types.Events.SyntheticInteractionPair;
12
12
  } | null;
13
13
  export declare function getCLS(insights: TraceInsightSets | null, key: string | null): {
14
14
  value: number;
15
- worstShiftEvent: Types.Events.Event | null;
15
+ worstClusterEvent: Types.Events.Event | null;
16
16
  };
17
17
  export declare function evaluateLCPMetricScore(value: number): number;
18
18
  export declare function evaluateINPMetricScore(value: number): number;
19
19
  export declare function evaluateCLSMetricScore(value: number): number;
20
20
  export interface CrUXFieldMetricTimingResult {
21
- value: Types.Timing.MicroSeconds;
21
+ value: Types.Timing.Micro;
22
22
  pageScope: any;
23
23
  }
24
24
  export interface CrUXFieldMetricNumberResult {
@@ -31,9 +31,13 @@ export interface CrUXFieldMetricResults {
31
31
  inp: CrUXFieldMetricTimingResult | null;
32
32
  cls: CrUXFieldMetricNumberResult | null;
33
33
  }
34
- export declare function getFieldMetricsForInsightSet(insightSet: InsightSet, metadata: Types.File.MetaData | null): CrUXFieldMetricResults | null;
34
+ export declare function getFieldMetricsForInsightSet(insightSet: InsightSet, metadata: Types.File.MetaData | null, scope?: any | null): CrUXFieldMetricResults | null;
35
35
  export declare function calculateMetricWeightsForSorting(insightSet: InsightSet, metadata: Types.File.MetaData | null): {
36
36
  lcp: number;
37
37
  inp: number;
38
38
  cls: number;
39
39
  };
40
+ /**
41
+ * Estimates the FCP & LCP savings for wasted bytes in `wastedBytesByRequestId`.
42
+ */
43
+ export declare function metricSavingsForWastedBytes(wastedBytesByRequestId: Map<string, number>, context: InsightSetContext): MetricSavings | undefined;
@@ -2,7 +2,9 @@
2
2
  // Use of this source code is governed by a BSD-style license that can be
3
3
  // found in the LICENSE file.
4
4
  import * as Helpers from '../helpers/helpers.js';
5
+ import * as Types from '../types/types.js';
5
6
  import { getLogNormalScore } from './Statistics.js';
7
+ const GRAPH_SAVINGS_PRECISION = 50;
6
8
  export function getInsight(insightName, insights, key) {
7
9
  if (!insights || !key) {
8
10
  return null;
@@ -38,7 +40,7 @@ export function getCLS(insights, key) {
38
40
  const insight = getInsight('CLSCulprits', insights, key);
39
41
  if (!insight) {
40
42
  // Unlike the other metrics, there is always a value for CLS even with no data.
41
- return { value: 0, worstShiftEvent: null };
43
+ return { value: 0, worstClusterEvent: null };
42
44
  }
43
45
  // TODO(cjamcl): the CLS insight should be doing this for us.
44
46
  let maxScore = 0;
@@ -49,7 +51,7 @@ export function getCLS(insights, key) {
49
51
  worstCluster = cluster;
50
52
  }
51
53
  }
52
- return { value: maxScore, worstShiftEvent: worstCluster?.worstShiftEvent ?? null };
54
+ return { value: maxScore, worstClusterEvent: worstCluster ?? null };
53
55
  }
54
56
  export function evaluateLCPMetricScore(value) {
55
57
  return getLogNormalScore({ p10: 2500, median: 4000 }, value);
@@ -60,51 +62,56 @@ export function evaluateINPMetricScore(value) {
60
62
  export function evaluateCLSMetricScore(value) {
61
63
  return getLogNormalScore({ p10: 0.1, median: 0.25 }, value);
62
64
  }
63
- function getPageResult(cruxFieldData, url, origin) {
65
+ function getPageResult(cruxFieldData, url, origin, scope = null) {
64
66
  return cruxFieldData.find(result => {
65
- const key = (result['url-ALL'] || result['origin-ALL'])?.record.key;
67
+ const key = scope ? result[`${scope.pageScope}-${scope.deviceScope}`]?.record.key :
68
+ (result['url-ALL'] || result['origin-ALL'])?.record.key;
66
69
  return (key?.url && key.url === url) || (key?.origin && key.origin === origin);
67
70
  });
68
71
  }
69
- function getMetricResult(pageResult, name) {
70
- let value = pageResult['url-ALL']?.record.metrics[name]?.percentiles?.p75;
71
- if (typeof value === 'string') {
72
- value = Number(value);
73
- }
74
- if (typeof value === 'number' && Number.isFinite(value)) {
75
- return { value, pageScope: 'url' };
76
- }
77
- value = pageResult['origin-ALL']?.record.metrics[name]?.percentiles?.p75;
78
- if (typeof value === 'string') {
79
- value = Number(value);
80
- }
81
- if (typeof value === 'number' && Number.isFinite(value)) {
82
- return { value, pageScope: 'origin' };
72
+ function getMetricResult(pageResult, name, scope = null) {
73
+ const scopes = [];
74
+ if (scope) {
75
+ scopes.push(scope);
76
+ }
77
+ else {
78
+ scopes.push({ pageScope: 'url', deviceScope: 'ALL' });
79
+ scopes.push({ pageScope: 'origin', deviceScope: 'ALL' });
80
+ }
81
+ for (const scope of scopes) {
82
+ const key = `${scope.pageScope}-${scope.deviceScope}`;
83
+ let value = pageResult[key]?.record.metrics[name]?.percentiles?.p75;
84
+ if (typeof value === 'string') {
85
+ value = Number(value);
86
+ }
87
+ if (typeof value === 'number' && Number.isFinite(value)) {
88
+ return { value, pageScope: scope.pageScope };
89
+ }
83
90
  }
84
91
  return null;
85
92
  }
86
- function getMetricTimingResult(pageResult, name) {
87
- const result = getMetricResult(pageResult, name);
93
+ function getMetricTimingResult(pageResult, name, scope = null) {
94
+ const result = getMetricResult(pageResult, name, scope);
88
95
  if (result) {
89
96
  const valueMs = result.value;
90
97
  return { value: Helpers.Timing.milliToMicro(valueMs), pageScope: result.pageScope };
91
98
  }
92
99
  return null;
93
100
  }
94
- export function getFieldMetricsForInsightSet(insightSet, metadata) {
101
+ export function getFieldMetricsForInsightSet(insightSet, metadata, scope = null) {
95
102
  const cruxFieldData = metadata?.cruxFieldData;
96
103
  if (!cruxFieldData) {
97
104
  return null;
98
105
  }
99
- const pageResult = getPageResult(cruxFieldData, insightSet.url.href, insightSet.url.origin);
106
+ const pageResult = getPageResult(cruxFieldData, insightSet.url.href, insightSet.url.origin, scope);
100
107
  if (!pageResult) {
101
108
  return null;
102
109
  }
103
110
  return {
104
- fcp: getMetricTimingResult(pageResult, 'first_contentful_paint'),
105
- lcp: getMetricTimingResult(pageResult, 'largest_contentful_paint'),
106
- inp: getMetricTimingResult(pageResult, 'interaction_to_next_paint'),
107
- cls: getMetricResult(pageResult, 'cumulative_layout_shift'),
111
+ fcp: getMetricTimingResult(pageResult, 'first_contentful_paint', scope),
112
+ lcp: getMetricTimingResult(pageResult, 'largest_contentful_paint', scope),
113
+ inp: getMetricTimingResult(pageResult, 'interaction_to_next_paint', scope),
114
+ cls: getMetricResult(pageResult, 'cumulative_layout_shift', scope),
108
115
  };
109
116
  }
110
117
  export function calculateMetricWeightsForSorting(insightSet, metadata) {
@@ -139,4 +146,56 @@ export function calculateMetricWeightsForSorting(insightSet, metadata) {
139
146
  weights.cls = fieldClsScoreInverted / invertedSum;
140
147
  return weights;
141
148
  }
149
+ /**
150
+ * Simulates the provided graph before and after the byte savings from `wastedBytesByRequestId` are applied.
151
+ */
152
+ function estimateSavingsWithGraphs(wastedBytesByRequestId, simulator, graph) {
153
+ const simulationBeforeChanges = simulator.simulate(graph);
154
+ const originalTransferSizes = new Map();
155
+ graph.traverse(node => {
156
+ if (node.type !== 'network') {
157
+ return;
158
+ }
159
+ const wastedBytes = wastedBytesByRequestId.get(node.request.requestId);
160
+ if (!wastedBytes) {
161
+ return;
162
+ }
163
+ const original = node.request.transferSize;
164
+ originalTransferSizes.set(node.request.requestId, original);
165
+ node.request.transferSize = Math.max(original - wastedBytes, 0);
166
+ });
167
+ const simulationAfterChanges = simulator.simulate(graph);
168
+ // Restore the original transfer size after we've done our simulation
169
+ graph.traverse(node => {
170
+ if (node.type !== 'network') {
171
+ return;
172
+ }
173
+ const originalTransferSize = originalTransferSizes.get(node.request.requestId);
174
+ if (originalTransferSize === undefined) {
175
+ return;
176
+ }
177
+ node.request.transferSize = originalTransferSize;
178
+ });
179
+ let savings = simulationBeforeChanges.timeInMs - simulationAfterChanges.timeInMs;
180
+ savings = Math.round(savings / GRAPH_SAVINGS_PRECISION) * GRAPH_SAVINGS_PRECISION;
181
+ return Types.Timing.Milli(savings);
182
+ }
183
+ /**
184
+ * Estimates the FCP & LCP savings for wasted bytes in `wastedBytesByRequestId`.
185
+ */
186
+ export function metricSavingsForWastedBytes(wastedBytesByRequestId, context) {
187
+ if (!context.navigation || !context.lantern) {
188
+ return;
189
+ }
190
+ if (!wastedBytesByRequestId.size) {
191
+ return { FCP: Types.Timing.Milli(0), LCP: Types.Timing.Milli(0) };
192
+ }
193
+ const simulator = context.lantern.simulator;
194
+ const fcpGraph = context.lantern.metrics.firstContentfulPaint.optimisticGraph;
195
+ const lcpGraph = context.lantern.metrics.largestContentfulPaint.optimisticGraph;
196
+ return {
197
+ FCP: estimateSavingsWithGraphs(wastedBytesByRequestId, simulator, fcpGraph),
198
+ LCP: estimateSavingsWithGraphs(wastedBytesByRequestId, simulator, lcpGraph),
199
+ };
200
+ }
142
201
  //# sourceMappingURL=Common.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Common.js","sourceRoot":"","sources":["../../../../../../../front_end/models/trace/insights/Common.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 type * as CrUXManager from '../../crux-manager/crux-manager.js';\nimport * as Helpers from '../helpers/helpers.js';\nimport type * as Types from '../types/types.js';\n\nimport {getLogNormalScore} from './Statistics.js';\nimport type {InsightModels, InsightSet, TraceInsightSets} from './types.js';\n\nexport function getInsight<InsightName extends keyof InsightModels>(\n insightName: InsightName, insights: TraceInsightSets|null, key: string|null): InsightModels[InsightName]|null {\n if (!insights || !key) {\n return null;\n }\n\n const insightSets = insights.get(key);\n if (!insightSets) {\n return null;\n }\n\n const insight = insightSets.model[insightName];\n if (insight instanceof Error) {\n return null;\n }\n\n // For some reason typescript won't narrow the type by removing Error, so do it manually.\n return insight as InsightModels[InsightName];\n}\n\nexport function getLCP(insights: TraceInsightSets|null, key: string|null):\n {value: Types.Timing.MicroSeconds, event: Types.Events.LargestContentfulPaintCandidate}|null {\n const insight = getInsight('LCPPhases', insights, key);\n if (!insight || !insight.lcpMs || !insight.lcpEvent) {\n return null;\n }\n\n const value = Helpers.Timing.milliToMicro(insight.lcpMs);\n return {value, event: insight.lcpEvent};\n}\n\nexport function getINP(insights: TraceInsightSets|null, key: string|null):\n {value: Types.Timing.MicroSeconds, event: Types.Events.SyntheticInteractionPair}|null {\n const insight = getInsight('InteractionToNextPaint', insights, key);\n if (!insight?.longestInteractionEvent?.dur) {\n return null;\n }\n\n const value = insight.longestInteractionEvent.dur;\n return {value, event: insight.longestInteractionEvent};\n}\n\nexport function getCLS(\n insights: TraceInsightSets|null, key: string|null): {value: number, worstShiftEvent: Types.Events.Event|null} {\n const insight = getInsight('CLSCulprits', insights, key);\n if (!insight) {\n // Unlike the other metrics, there is always a value for CLS even with no data.\n return {value: 0, worstShiftEvent: null};\n }\n\n // TODO(cjamcl): the CLS insight should be doing this for us.\n let maxScore = 0;\n let worstCluster;\n for (const cluster of insight.clusters) {\n if (cluster.clusterCumulativeScore > maxScore) {\n maxScore = cluster.clusterCumulativeScore;\n worstCluster = cluster;\n }\n }\n\n return {value: maxScore, worstShiftEvent: worstCluster?.worstShiftEvent ?? null};\n}\n\nexport function evaluateLCPMetricScore(value: number): number {\n return getLogNormalScore({p10: 2500, median: 4000}, value);\n}\n\nexport function evaluateINPMetricScore(value: number): number {\n return getLogNormalScore({p10: 200, median: 500}, value);\n}\n\nexport function evaluateCLSMetricScore(value: number): number {\n return getLogNormalScore({p10: 0.1, median: 0.25}, value);\n}\n\nexport interface CrUXFieldMetricTimingResult {\n value: Types.Timing.MicroSeconds;\n pageScope: CrUXManager.PageScope;\n}\nexport interface CrUXFieldMetricNumberResult {\n value: number;\n pageScope: CrUXManager.PageScope;\n}\nexport interface CrUXFieldMetricResults {\n fcp: CrUXFieldMetricTimingResult|null;\n lcp: CrUXFieldMetricTimingResult|null;\n inp: CrUXFieldMetricTimingResult|null;\n cls: CrUXFieldMetricNumberResult|null;\n}\n\nfunction getPageResult(cruxFieldData: CrUXManager.PageResult[], url: string, origin: string): CrUXManager.PageResult|\n undefined {\n return cruxFieldData.find(result => {\n const key = (result['url-ALL'] || result['origin-ALL'])?.record.key;\n return (key?.url && key.url === url) || (key?.origin && key.origin === origin);\n });\n}\n\nfunction getMetricResult(\n pageResult: CrUXManager.PageResult, name: CrUXManager.StandardMetricNames): CrUXFieldMetricNumberResult|null {\n let value = pageResult['url-ALL']?.record.metrics[name]?.percentiles?.p75;\n if (typeof value === 'string') {\n value = Number(value);\n }\n if (typeof value === 'number' && Number.isFinite(value)) {\n return {value, pageScope: 'url'};\n }\n\n value = pageResult['origin-ALL']?.record.metrics[name]?.percentiles?.p75;\n if (typeof value === 'string') {\n value = Number(value);\n }\n if (typeof value === 'number' && Number.isFinite(value)) {\n return {value, pageScope: 'origin'};\n }\n\n return null;\n}\n\nfunction getMetricTimingResult(\n pageResult: CrUXManager.PageResult, name: CrUXManager.StandardMetricNames): CrUXFieldMetricTimingResult|null {\n const result = getMetricResult(pageResult, name);\n if (result) {\n const valueMs = result.value as Types.Timing.MilliSeconds;\n return {value: Helpers.Timing.milliToMicro(valueMs), pageScope: result.pageScope};\n }\n\n return null;\n}\n\nexport function getFieldMetricsForInsightSet(\n insightSet: InsightSet, metadata: Types.File.MetaData|null): CrUXFieldMetricResults|null {\n const cruxFieldData = metadata?.cruxFieldData;\n if (!cruxFieldData) {\n return null;\n }\n\n const pageResult = getPageResult(cruxFieldData, insightSet.url.href, insightSet.url.origin);\n if (!pageResult) {\n return null;\n }\n\n return {\n fcp: getMetricTimingResult(pageResult, 'first_contentful_paint'),\n lcp: getMetricTimingResult(pageResult, 'largest_contentful_paint'),\n inp: getMetricTimingResult(pageResult, 'interaction_to_next_paint'),\n cls: getMetricResult(pageResult, 'cumulative_layout_shift'),\n };\n}\n\nexport function calculateMetricWeightsForSorting(\n insightSet: InsightSet, metadata: Types.File.MetaData|null): {lcp: number, inp: number, cls: number} {\n const weights = {\n lcp: 1 / 3,\n inp: 1 / 3,\n cls: 1 / 3,\n };\n\n const cruxFieldData = metadata?.cruxFieldData;\n if (!cruxFieldData) {\n return weights;\n }\n\n const fieldMetrics = getFieldMetricsForInsightSet(insightSet, metadata);\n if (!fieldMetrics) {\n return weights;\n }\n\n const fieldLcp = fieldMetrics.lcp?.value ?? null;\n const fieldInp = fieldMetrics.inp?.value ?? null;\n const fieldCls = fieldMetrics.cls?.value ?? null;\n const fieldLcpScore = fieldLcp !== null ? evaluateLCPMetricScore(fieldLcp) : 0;\n const fieldInpScore = fieldInp !== null ? evaluateINPMetricScore(fieldInp) : 0;\n const fieldClsScore = fieldCls !== null ? evaluateCLSMetricScore(fieldCls) : 0;\n const fieldLcpScoreInverted = 1 - fieldLcpScore;\n const fieldInpScoreInverted = 1 - fieldInpScore;\n const fieldClsScoreInverted = 1 - fieldClsScore;\n const invertedSum = fieldLcpScoreInverted + fieldInpScoreInverted + fieldClsScoreInverted;\n if (!invertedSum) {\n return weights;\n }\n\n weights.lcp = fieldLcpScoreInverted / invertedSum;\n weights.inp = fieldInpScoreInverted / invertedSum;\n weights.cls = fieldClsScoreInverted / invertedSum;\n\n return weights;\n}\n"]}
1
+ {"version":3,"file":"Common.js","sourceRoot":"","sources":["../../../../../../../front_end/models/trace/insights/Common.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 type * as CrUXManager from '../../crux-manager/crux-manager.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 {getLogNormalScore} from './Statistics.js';\nimport type {InsightModels, InsightSet, InsightSetContext, MetricSavings, TraceInsightSets} from './types.js';\n\nconst GRAPH_SAVINGS_PRECISION = 50;\n\nexport function getInsight<InsightName extends keyof InsightModels>(\n insightName: InsightName, insights: TraceInsightSets|null, key: string|null): InsightModels[InsightName]|null {\n if (!insights || !key) {\n return null;\n }\n\n const insightSets = insights.get(key);\n if (!insightSets) {\n return null;\n }\n\n const insight = insightSets.model[insightName];\n if (insight instanceof Error) {\n return null;\n }\n\n // For some reason typescript won't narrow the type by removing Error, so do it manually.\n return insight as InsightModels[InsightName];\n}\n\nexport function getLCP(insights: TraceInsightSets|null, key: string|null):\n {value: Types.Timing.Micro, event: Types.Events.LargestContentfulPaintCandidate}|null {\n const insight = getInsight('LCPPhases', insights, key);\n if (!insight || !insight.lcpMs || !insight.lcpEvent) {\n return null;\n }\n\n const value = Helpers.Timing.milliToMicro(insight.lcpMs);\n return {value, event: insight.lcpEvent};\n}\n\nexport function getINP(insights: TraceInsightSets|null, key: string|null):\n {value: Types.Timing.Micro, event: Types.Events.SyntheticInteractionPair}|null {\n const insight = getInsight('InteractionToNextPaint', insights, key);\n if (!insight?.longestInteractionEvent?.dur) {\n return null;\n }\n\n const value = insight.longestInteractionEvent.dur;\n return {value, event: insight.longestInteractionEvent};\n}\n\nexport function getCLS(\n insights: TraceInsightSets|null, key: string|null): {value: number, worstClusterEvent: Types.Events.Event|null} {\n const insight = getInsight('CLSCulprits', insights, key);\n if (!insight) {\n // Unlike the other metrics, there is always a value for CLS even with no data.\n return {value: 0, worstClusterEvent: null};\n }\n\n // TODO(cjamcl): the CLS insight should be doing this for us.\n let maxScore = 0;\n let worstCluster;\n for (const cluster of insight.clusters) {\n if (cluster.clusterCumulativeScore > maxScore) {\n maxScore = cluster.clusterCumulativeScore;\n worstCluster = cluster;\n }\n }\n\n return {value: maxScore, worstClusterEvent: worstCluster ?? null};\n}\n\nexport function evaluateLCPMetricScore(value: number): number {\n return getLogNormalScore({p10: 2500, median: 4000}, value);\n}\n\nexport function evaluateINPMetricScore(value: number): number {\n return getLogNormalScore({p10: 200, median: 500}, value);\n}\n\nexport function evaluateCLSMetricScore(value: number): number {\n return getLogNormalScore({p10: 0.1, median: 0.25}, value);\n}\n\nexport interface CrUXFieldMetricTimingResult {\n value: Types.Timing.Micro;\n pageScope: CrUXManager.PageScope;\n}\nexport interface CrUXFieldMetricNumberResult {\n value: number;\n pageScope: CrUXManager.PageScope;\n}\nexport interface CrUXFieldMetricResults {\n fcp: CrUXFieldMetricTimingResult|null;\n lcp: CrUXFieldMetricTimingResult|null;\n inp: CrUXFieldMetricTimingResult|null;\n cls: CrUXFieldMetricNumberResult|null;\n}\n\nfunction getPageResult(\n cruxFieldData: CrUXManager.PageResult[], url: string, origin: string,\n scope: CrUXManager.Scope|null = null): CrUXManager.PageResult|undefined {\n return cruxFieldData.find(result => {\n const key = scope ? result[`${scope.pageScope}-${scope.deviceScope}`]?.record.key :\n (result['url-ALL'] || result['origin-ALL'])?.record.key;\n return (key?.url && key.url === url) || (key?.origin && key.origin === origin);\n });\n}\n\nfunction getMetricResult(\n pageResult: CrUXManager.PageResult, name: CrUXManager.StandardMetricNames,\n scope: CrUXManager.Scope|null = null): CrUXFieldMetricNumberResult|null {\n const scopes: Array<{pageScope: CrUXManager.PageScope, deviceScope: CrUXManager.DeviceScope}> = [];\n if (scope) {\n scopes.push(scope);\n } else {\n scopes.push({pageScope: 'url', deviceScope: 'ALL'});\n scopes.push({pageScope: 'origin', deviceScope: 'ALL'});\n }\n\n for (const scope of scopes) {\n const key = `${scope.pageScope}-${scope.deviceScope}` as const;\n let value = pageResult[key]?.record.metrics[name]?.percentiles?.p75;\n if (typeof value === 'string') {\n value = Number(value);\n }\n if (typeof value === 'number' && Number.isFinite(value)) {\n return {value, pageScope: scope.pageScope};\n }\n }\n\n return null;\n}\n\nfunction getMetricTimingResult(\n pageResult: CrUXManager.PageResult, name: CrUXManager.StandardMetricNames,\n scope: CrUXManager.Scope|null = null): CrUXFieldMetricTimingResult|null {\n const result = getMetricResult(pageResult, name, scope);\n if (result) {\n const valueMs = result.value as Types.Timing.Milli;\n return {value: Helpers.Timing.milliToMicro(valueMs), pageScope: result.pageScope};\n }\n\n return null;\n}\n\nexport function getFieldMetricsForInsightSet(\n insightSet: InsightSet, metadata: Types.File.MetaData|null,\n scope: CrUXManager.Scope|null = null): CrUXFieldMetricResults|null {\n const cruxFieldData = metadata?.cruxFieldData;\n if (!cruxFieldData) {\n return null;\n }\n\n const pageResult = getPageResult(cruxFieldData, insightSet.url.href, insightSet.url.origin, scope);\n if (!pageResult) {\n return null;\n }\n\n return {\n fcp: getMetricTimingResult(pageResult, 'first_contentful_paint', scope),\n lcp: getMetricTimingResult(pageResult, 'largest_contentful_paint', scope),\n inp: getMetricTimingResult(pageResult, 'interaction_to_next_paint', scope),\n cls: getMetricResult(pageResult, 'cumulative_layout_shift', scope),\n };\n}\n\nexport function calculateMetricWeightsForSorting(\n insightSet: InsightSet, metadata: Types.File.MetaData|null): {lcp: number, inp: number, cls: number} {\n const weights = {\n lcp: 1 / 3,\n inp: 1 / 3,\n cls: 1 / 3,\n };\n\n const cruxFieldData = metadata?.cruxFieldData;\n if (!cruxFieldData) {\n return weights;\n }\n\n const fieldMetrics = getFieldMetricsForInsightSet(insightSet, metadata);\n if (!fieldMetrics) {\n return weights;\n }\n\n const fieldLcp = fieldMetrics.lcp?.value ?? null;\n const fieldInp = fieldMetrics.inp?.value ?? null;\n const fieldCls = fieldMetrics.cls?.value ?? null;\n const fieldLcpScore = fieldLcp !== null ? evaluateLCPMetricScore(fieldLcp) : 0;\n const fieldInpScore = fieldInp !== null ? evaluateINPMetricScore(fieldInp) : 0;\n const fieldClsScore = fieldCls !== null ? evaluateCLSMetricScore(fieldCls) : 0;\n const fieldLcpScoreInverted = 1 - fieldLcpScore;\n const fieldInpScoreInverted = 1 - fieldInpScore;\n const fieldClsScoreInverted = 1 - fieldClsScore;\n const invertedSum = fieldLcpScoreInverted + fieldInpScoreInverted + fieldClsScoreInverted;\n if (!invertedSum) {\n return weights;\n }\n\n weights.lcp = fieldLcpScoreInverted / invertedSum;\n weights.inp = fieldInpScoreInverted / invertedSum;\n weights.cls = fieldClsScoreInverted / invertedSum;\n\n return weights;\n}\n\n/**\n * Simulates the provided graph before and after the byte savings from `wastedBytesByRequestId` are applied.\n */\nfunction estimateSavingsWithGraphs(\n wastedBytesByRequestId: Map<string, number>, simulator: Lantern.Simulation.Simulator,\n graph: Lantern.Graph.Node): Types.Timing.Milli {\n const simulationBeforeChanges = simulator.simulate(graph);\n\n const originalTransferSizes = new Map<string, number>();\n graph.traverse(node => {\n if (node.type !== 'network') {\n return;\n }\n const wastedBytes = wastedBytesByRequestId.get(node.request.requestId);\n if (!wastedBytes) {\n return;\n }\n\n const original = node.request.transferSize;\n originalTransferSizes.set(node.request.requestId, original);\n\n node.request.transferSize = Math.max(original - wastedBytes, 0);\n });\n\n const simulationAfterChanges = simulator.simulate(graph);\n\n // Restore the original transfer size after we've done our simulation\n graph.traverse(node => {\n if (node.type !== 'network') {\n return;\n }\n const originalTransferSize = originalTransferSizes.get(node.request.requestId);\n if (originalTransferSize === undefined) {\n return;\n }\n node.request.transferSize = originalTransferSize;\n });\n\n let savings = simulationBeforeChanges.timeInMs - simulationAfterChanges.timeInMs;\n savings = Math.round(savings / GRAPH_SAVINGS_PRECISION) * GRAPH_SAVINGS_PRECISION;\n return Types.Timing.Milli(savings);\n}\n\n/**\n * Estimates the FCP & LCP savings for wasted bytes in `wastedBytesByRequestId`.\n */\nexport function metricSavingsForWastedBytes(\n wastedBytesByRequestId: Map<string, number>, context: InsightSetContext): MetricSavings|undefined {\n if (!context.navigation || !context.lantern) {\n return;\n }\n\n if (!wastedBytesByRequestId.size) {\n return {FCP: Types.Timing.Milli(0), LCP: Types.Timing.Milli(0)};\n }\n\n const simulator = context.lantern.simulator;\n const fcpGraph = context.lantern.metrics.firstContentfulPaint.optimisticGraph;\n const lcpGraph = context.lantern.metrics.largestContentfulPaint.optimisticGraph;\n\n return {\n FCP: estimateSavingsWithGraphs(wastedBytesByRequestId, simulator, fcpGraph),\n LCP: estimateSavingsWithGraphs(wastedBytesByRequestId, simulator, lcpGraph),\n };\n}\n"]}
@@ -9,8 +9,33 @@ export declare const UIStrings: {
9
9
  * @description Description of an insight that recommends reducing the size of the DOM tree as a means to improve page responsiveness. "DOM" is an acronym and should not be translated. "layout reflows" are when the browser will recompute the layout of content on the page.
10
10
  */
11
11
  description: string;
12
+ /**
13
+ * @description Header for a column containing the names of statistics as opposed to the actual statistic values.
14
+ */
15
+ statistic: string;
16
+ /**
17
+ * @description Header for a column containing the value of a statistic.
18
+ */
19
+ value: string;
20
+ /**
21
+ * @description Header for a column containing the page element related to a statistic.
22
+ */
23
+ element: string;
24
+ /**
25
+ * @description Label for a value representing the total number of elements on the page.
26
+ */
27
+ totalElements: string;
28
+ /**
29
+ * @description Label for a value representing the maximum depth of the Document Object Model (DOM). "DOM" is a acronym and should not be translated.
30
+ */
31
+ maxDOMDepth: string;
32
+ /**
33
+ * @description Label for a value representing the maximum number of child elements of any parent element on the page.
34
+ */
35
+ maxChildren: string;
12
36
  };
13
- export type DOMSizeInsightModel = InsightModel<{
37
+ export declare const i18nString: (id: string, values?: Record<string, string> | undefined) => Record<string, string>;
38
+ export type DOMSizeInsightModel = InsightModel<typeof UIStrings, {
14
39
  largeLayoutUpdates: Types.Events.Layout[];
15
40
  largeStyleRecalcs: Types.Events.UpdateLayoutTree[];
16
41
  maxDOMStats?: Types.Events.DOMStats;
@@ -15,16 +15,46 @@ export const UIStrings = {
15
15
  * @description Description of an insight that recommends reducing the size of the DOM tree as a means to improve page responsiveness. "DOM" is an acronym and should not be translated. "layout reflows" are when the browser will recompute the layout of content on the page.
16
16
  */
17
17
  description: 'A large DOM can increase the duration of style calculations and layout reflows, impacting page responsiveness. A large DOM will also increase memory usage. [Learn how to avoid an excessive DOM size](https://developer.chrome.com/docs/lighthouse/performance/dom-size/).',
18
+ /**
19
+ * @description Header for a column containing the names of statistics as opposed to the actual statistic values.
20
+ */
21
+ statistic: 'Statistic',
22
+ /**
23
+ * @description Header for a column containing the value of a statistic.
24
+ */
25
+ value: 'Value',
26
+ /**
27
+ * @description Header for a column containing the page element related to a statistic.
28
+ */
29
+ element: 'Element',
30
+ /**
31
+ * @description Label for a value representing the total number of elements on the page.
32
+ */
33
+ totalElements: 'Total elements',
34
+ /**
35
+ * @description Label for a value representing the maximum depth of the Document Object Model (DOM). "DOM" is a acronym and should not be translated.
36
+ */
37
+ maxDOMDepth: 'DOM depth',
38
+ /**
39
+ * @description Label for a value representing the maximum number of child elements of any parent element on the page.
40
+ */
41
+ maxChildren: 'Most children',
18
42
  };
19
43
  // const str_ = i18n.i18n.registerUIStrings('models/trace/insights/DOMSize.ts', UIStrings);
20
- const i18nString = string => string; // i18n.i18n.getLocalizedString.bind(undefined, str_);
21
- const DOM_UPDATE_LIMIT = 800;
44
+ export const i18nString = string => string; // i18n.i18n.getLocalizedString.bind(undefined, str_);
45
+ const DOM_SIZE_DURATION_THRESHOLD = Helpers.Timing.milliToMicro(Types.Timing.Milli(40));
46
+ // These thresholds were selected to maximize the number of long (>40ms) events above
47
+ // the threshold while maximizing the number of short (<40ms) events below the threshold.
48
+ // See go/rpp-dom-size-thresholds for the analysis that produced these thresholds.
49
+ const LAYOUT_OBJECTS_THRESHOLD = 100;
50
+ const STYLE_RECALC_ELEMENTS_THRESHOLD = 300;
22
51
  export function deps() {
23
52
  return ['Renderer', 'AuctionWorklets', 'DOMStats'];
24
53
  }
25
54
  function finalize(partialModel) {
26
55
  const relatedEvents = [...partialModel.largeLayoutUpdates, ...partialModel.largeStyleRecalcs];
27
56
  return {
57
+ strings: UIStrings,
28
58
  title: i18nString(UIStrings.title),
29
59
  description: i18nString(UIStrings.description),
30
60
  category: InsightCategory.INP,
@@ -63,25 +93,25 @@ export function generateInsight(parsedTrace, context) {
63
93
  }
64
94
  const first = entries[0];
65
95
  const last = entries[entries.length - 1];
66
- const timeRange = Helpers.Timing.traceWindowFromMicroSeconds(first.ts, Types.Timing.MicroSeconds(last.ts + (last.dur ?? 0)));
96
+ const timeRange = Helpers.Timing.traceWindowFromMicroSeconds(first.ts, Types.Timing.Micro(last.ts + (last.dur ?? 0)));
67
97
  if (!Helpers.Timing.boundsIncludeTimeRange({ timeRange, bounds: context.bounds })) {
68
98
  continue;
69
99
  }
70
100
  for (const event of layoutEvents) {
71
- if (!isWithinContext(event)) {
101
+ if (event.dur < DOM_SIZE_DURATION_THRESHOLD || !isWithinContext(event)) {
72
102
  continue;
73
103
  }
74
104
  const { dirtyObjects } = event.args.beginData;
75
- if (dirtyObjects > DOM_UPDATE_LIMIT) {
105
+ if (dirtyObjects > LAYOUT_OBJECTS_THRESHOLD) {
76
106
  largeLayoutUpdates.push(event);
77
107
  }
78
108
  }
79
109
  for (const event of updateLayoutTreeEvents) {
80
- if (!isWithinContext(event)) {
110
+ if (event.dur < DOM_SIZE_DURATION_THRESHOLD || !isWithinContext(event)) {
81
111
  continue;
82
112
  }
83
113
  const { elementCount } = event.args;
84
- if (elementCount > DOM_UPDATE_LIMIT) {
114
+ if (elementCount > STYLE_RECALC_ELEMENTS_THRESHOLD) {
85
115
  largeStyleRecalcs.push(event);
86
116
  }
87
117
  }
@@ -89,6 +119,13 @@ export function generateInsight(parsedTrace, context) {
89
119
  const domStatsEvents = parsedTrace.DOMStats.domStatsByFrameId.get(context.frameId)?.filter(isWithinContext) ?? [];
90
120
  let maxDOMStats;
91
121
  for (const domStats of domStatsEvents) {
122
+ // While recording a cross-origin navigation, there can be overlapping dom stats from before & after
123
+ // the navigation which share a frameId. In this case we should also ensure the pid matches up with
124
+ // the navigation we care about (i.e. from after the navigation event).
125
+ const navigationPid = context.navigation?.pid;
126
+ if (navigationPid && domStats.pid !== navigationPid) {
127
+ continue;
128
+ }
92
129
  if (!maxDOMStats || domStats.args.data.totalElements > maxDOMStats.args.data.totalElements) {
93
130
  maxDOMStats = domStats;
94
131
  }
@@ -1 +1 @@
1
- {"version":3,"file":"DOMSize.js","sourceRoot":"","sources":["../../../../../../../front_end/models/trace/insights/DOMSize.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 i18n from '../../../core/i18n/i18n.js';\nimport * as Handlers from '../handlers/handlers.js';\nimport * as Helpers from '../helpers/helpers.js';\nimport * as Types from '../types/types.js';\n\nimport {InsightCategory, type InsightModel, type InsightSetContext, type RequiredData} from './types.js';\n\nexport const UIStrings = {\n /**\n * @description Title of an insight that recommends reducing the size of the DOM tree as a means to improve page responsiveness. \"DOM\" is an acronym and should not be translated.\n */\n title: 'Optimize DOM size',\n /**\n * @description Description of an insight that recommends reducing the size of the DOM tree as a means to improve page responsiveness. \"DOM\" is an acronym and should not be translated. \"layout reflows\" are when the browser will recompute the layout of content on the page.\n */\n description:\n 'A large DOM can increase the duration of style calculations and layout reflows, impacting page responsiveness. A large DOM will also increase memory usage. [Learn how to avoid an excessive DOM size](https://developer.chrome.com/docs/lighthouse/performance/dom-size/).',\n};\n\nconst str_ = i18n.i18n.registerUIStrings('models/trace/insights/DOMSize.ts', UIStrings);\nconst i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);\n\nconst DOM_UPDATE_LIMIT = 800;\n\nexport type DOMSizeInsightModel = InsightModel<{\n largeLayoutUpdates: Types.Events.Layout[],\n largeStyleRecalcs: Types.Events.UpdateLayoutTree[],\n maxDOMStats?: Types.Events.DOMStats,\n}>;\n\nexport function deps(): ['Renderer', 'AuctionWorklets', 'DOMStats'] {\n return ['Renderer', 'AuctionWorklets', 'DOMStats'];\n}\n\nfunction finalize(partialModel: Omit<DOMSizeInsightModel, 'title'|'description'|'category'|'shouldShow'>):\n DOMSizeInsightModel {\n const relatedEvents = [...partialModel.largeLayoutUpdates, ...partialModel.largeStyleRecalcs];\n return {\n title: i18nString(UIStrings.title),\n description: i18nString(UIStrings.description),\n category: InsightCategory.INP,\n shouldShow: relatedEvents.length > 0,\n ...partialModel,\n relatedEvents,\n };\n}\n\nexport function generateInsight(\n parsedTrace: RequiredData<typeof deps>, context: InsightSetContext): DOMSizeInsightModel {\n const isWithinContext = (event: Types.Events.Event): boolean => Helpers.Timing.eventIsInBounds(event, context.bounds);\n\n const mainTid = context.navigation?.tid;\n\n const largeLayoutUpdates: Types.Events.Layout[] = [];\n const largeStyleRecalcs: Types.Events.UpdateLayoutTree[] = [];\n\n const threads = Handlers.Threads.threadsInRenderer(parsedTrace.Renderer, parsedTrace.AuctionWorklets);\n for (const thread of threads) {\n if (thread.type !== Handlers.Threads.ThreadType.MAIN_THREAD) {\n continue;\n }\n\n if (mainTid === undefined) {\n // We won't have a specific thread ID to reference if the context does not have a navigation.\n // In this case, we'll just filter out any OOPIFs threads.\n if (!thread.processIsOnMainFrame) {\n continue;\n }\n } else if (thread.tid !== mainTid) {\n continue;\n }\n\n const rendererThread = parsedTrace.Renderer.processes.get(thread.pid)?.threads.get(thread.tid);\n if (!rendererThread) {\n continue;\n }\n\n const {entries, layoutEvents, updateLayoutTreeEvents} = rendererThread;\n if (!entries.length) {\n continue;\n }\n\n const first = entries[0];\n const last = entries[entries.length - 1];\n const timeRange =\n Helpers.Timing.traceWindowFromMicroSeconds(first.ts, Types.Timing.MicroSeconds(last.ts + (last.dur ?? 0)));\n if (!Helpers.Timing.boundsIncludeTimeRange({timeRange, bounds: context.bounds})) {\n continue;\n }\n\n for (const event of layoutEvents) {\n if (!isWithinContext(event)) {\n continue;\n }\n\n const {dirtyObjects} = event.args.beginData;\n if (dirtyObjects > DOM_UPDATE_LIMIT) {\n largeLayoutUpdates.push(event);\n }\n }\n\n for (const event of updateLayoutTreeEvents) {\n if (!isWithinContext(event)) {\n continue;\n }\n\n const {elementCount} = event.args;\n if (elementCount > DOM_UPDATE_LIMIT) {\n largeStyleRecalcs.push(event);\n }\n }\n }\n\n const domStatsEvents = parsedTrace.DOMStats.domStatsByFrameId.get(context.frameId)?.filter(isWithinContext) ?? [];\n let maxDOMStats: Types.Events.DOMStats|undefined;\n for (const domStats of domStatsEvents) {\n if (!maxDOMStats || domStats.args.data.totalElements > maxDOMStats.args.data.totalElements) {\n maxDOMStats = domStats;\n }\n }\n\n return finalize({\n largeLayoutUpdates,\n largeStyleRecalcs,\n maxDOMStats,\n });\n}\n"]}
1
+ {"version":3,"file":"DOMSize.js","sourceRoot":"","sources":["../../../../../../../front_end/models/trace/insights/DOMSize.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 i18n from '../../../core/i18n/i18n.js';\nimport * as Handlers from '../handlers/handlers.js';\nimport * as Helpers from '../helpers/helpers.js';\nimport * as Types from '../types/types.js';\n\nimport {\n InsightCategory,\n type InsightModel,\n type InsightSetContext,\n type PartialInsightModel,\n type RequiredData\n} from './types.js';\n\nexport const UIStrings = {\n /**\n * @description Title of an insight that recommends reducing the size of the DOM tree as a means to improve page responsiveness. \"DOM\" is an acronym and should not be translated.\n */\n title: 'Optimize DOM size',\n /**\n * @description Description of an insight that recommends reducing the size of the DOM tree as a means to improve page responsiveness. \"DOM\" is an acronym and should not be translated. \"layout reflows\" are when the browser will recompute the layout of content on the page.\n */\n description:\n 'A large DOM can increase the duration of style calculations and layout reflows, impacting page responsiveness. A large DOM will also increase memory usage. [Learn how to avoid an excessive DOM size](https://developer.chrome.com/docs/lighthouse/performance/dom-size/).',\n /**\n * @description Header for a column containing the names of statistics as opposed to the actual statistic values.\n */\n statistic: 'Statistic',\n /**\n * @description Header for a column containing the value of a statistic.\n */\n value: 'Value',\n /**\n * @description Header for a column containing the page element related to a statistic.\n */\n element: 'Element',\n /**\n * @description Label for a value representing the total number of elements on the page.\n */\n totalElements: 'Total elements',\n /**\n * @description Label for a value representing the maximum depth of the Document Object Model (DOM). \"DOM\" is a acronym and should not be translated.\n */\n maxDOMDepth: 'DOM depth',\n /**\n * @description Label for a value representing the maximum number of child elements of any parent element on the page.\n */\n maxChildren: 'Most children',\n};\n\nconst str_ = i18n.i18n.registerUIStrings('models/trace/insights/DOMSize.ts', UIStrings);\nexport const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);\n\nconst DOM_SIZE_DURATION_THRESHOLD = Helpers.Timing.milliToMicro(Types.Timing.Milli(40));\n\n// These thresholds were selected to maximize the number of long (>40ms) events above\n// the threshold while maximizing the number of short (<40ms) events below the threshold.\n// See go/rpp-dom-size-thresholds for the analysis that produced these thresholds.\nconst LAYOUT_OBJECTS_THRESHOLD = 100;\nconst STYLE_RECALC_ELEMENTS_THRESHOLD = 300;\n\nexport type DOMSizeInsightModel = InsightModel<typeof UIStrings, {\n largeLayoutUpdates: Types.Events.Layout[],\n largeStyleRecalcs: Types.Events.UpdateLayoutTree[],\n maxDOMStats?: Types.Events.DOMStats,\n}>;\n\nexport function deps(): ['Renderer', 'AuctionWorklets', 'DOMStats'] {\n return ['Renderer', 'AuctionWorklets', 'DOMStats'];\n}\n\nfunction finalize(partialModel: PartialInsightModel<DOMSizeInsightModel>): DOMSizeInsightModel {\n const relatedEvents = [...partialModel.largeLayoutUpdates, ...partialModel.largeStyleRecalcs];\n return {\n strings: UIStrings,\n title: i18nString(UIStrings.title),\n description: i18nString(UIStrings.description),\n category: InsightCategory.INP,\n shouldShow: relatedEvents.length > 0,\n ...partialModel,\n relatedEvents,\n };\n}\n\nexport function generateInsight(\n parsedTrace: RequiredData<typeof deps>, context: InsightSetContext): DOMSizeInsightModel {\n const isWithinContext = (event: Types.Events.Event): boolean => Helpers.Timing.eventIsInBounds(event, context.bounds);\n\n const mainTid = context.navigation?.tid;\n\n const largeLayoutUpdates: Types.Events.Layout[] = [];\n const largeStyleRecalcs: Types.Events.UpdateLayoutTree[] = [];\n\n const threads = Handlers.Threads.threadsInRenderer(parsedTrace.Renderer, parsedTrace.AuctionWorklets);\n for (const thread of threads) {\n if (thread.type !== Handlers.Threads.ThreadType.MAIN_THREAD) {\n continue;\n }\n\n if (mainTid === undefined) {\n // We won't have a specific thread ID to reference if the context does not have a navigation.\n // In this case, we'll just filter out any OOPIFs threads.\n if (!thread.processIsOnMainFrame) {\n continue;\n }\n } else if (thread.tid !== mainTid) {\n continue;\n }\n\n const rendererThread = parsedTrace.Renderer.processes.get(thread.pid)?.threads.get(thread.tid);\n if (!rendererThread) {\n continue;\n }\n\n const {entries, layoutEvents, updateLayoutTreeEvents} = rendererThread;\n if (!entries.length) {\n continue;\n }\n\n const first = entries[0];\n const last = entries[entries.length - 1];\n const timeRange =\n Helpers.Timing.traceWindowFromMicroSeconds(first.ts, Types.Timing.Micro(last.ts + (last.dur ?? 0)));\n if (!Helpers.Timing.boundsIncludeTimeRange({timeRange, bounds: context.bounds})) {\n continue;\n }\n\n for (const event of layoutEvents) {\n if (event.dur < DOM_SIZE_DURATION_THRESHOLD || !isWithinContext(event)) {\n continue;\n }\n\n const {dirtyObjects} = event.args.beginData;\n if (dirtyObjects > LAYOUT_OBJECTS_THRESHOLD) {\n largeLayoutUpdates.push(event);\n }\n }\n\n for (const event of updateLayoutTreeEvents) {\n if (event.dur < DOM_SIZE_DURATION_THRESHOLD || !isWithinContext(event)) {\n continue;\n }\n\n const {elementCount} = event.args;\n if (elementCount > STYLE_RECALC_ELEMENTS_THRESHOLD) {\n largeStyleRecalcs.push(event);\n }\n }\n }\n\n const domStatsEvents = parsedTrace.DOMStats.domStatsByFrameId.get(context.frameId)?.filter(isWithinContext) ?? [];\n let maxDOMStats: Types.Events.DOMStats|undefined;\n for (const domStats of domStatsEvents) {\n // While recording a cross-origin navigation, there can be overlapping dom stats from before & after\n // the navigation which share a frameId. In this case we should also ensure the pid matches up with\n // the navigation we care about (i.e. from after the navigation event).\n const navigationPid = context.navigation?.pid;\n if (navigationPid && domStats.pid !== navigationPid) {\n continue;\n }\n\n if (!maxDOMStats || domStats.args.data.totalElements > maxDOMStats.args.data.totalElements) {\n maxDOMStats = domStats;\n }\n }\n\n return finalize({\n largeLayoutUpdates,\n largeStyleRecalcs,\n maxDOMStats,\n });\n}\n"]}