@paulirish/trace_engine 0.0.53 → 0.0.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.tmp/tsbuildinfo/analyze-inspector-issues.d.mts +13 -0
- package/.tmp/tsbuildinfo/analyze-inspector-issues.d.mts.map +1 -0
- package/.tmp/tsbuildinfo/tsconfig.tsbuildinfo +1 -1
- package/analyze-inspector-issues.mjs +60 -0
- package/core/common/common.d.ts +1 -0
- package/core/common/common.js +1 -0
- package/core/host/host.d.ts +1 -0
- package/core/host/host.js +1 -0
- package/core/platform/MimeType.d.ts +3 -2
- package/core/platform/MimeType.js +4 -3
- package/core/platform/MimeType.js.map +1 -1
- package/core/root/root.d.ts +1 -0
- package/core/root/root.js +1 -0
- package/core/sdk/sdk.d.ts +1 -0
- package/core/sdk/sdk.js +1 -0
- package/generated/protocol.d.ts +115 -16
- package/locales/af.json +52 -10
- package/locales/am.json +52 -10
- package/locales/ar.json +51 -9
- package/locales/as.json +52 -10
- package/locales/az.json +52 -10
- package/locales/be.json +52 -10
- package/locales/bg.json +52 -10
- package/locales/bn.json +52 -10
- package/locales/bs.json +52 -10
- package/locales/ca.json +52 -10
- package/locales/cs.json +52 -10
- package/locales/cy.json +52 -10
- package/locales/da.json +52 -10
- package/locales/de.json +52 -10
- package/locales/el.json +52 -10
- package/locales/en-GB.json +52 -10
- package/locales/en-US.json +12 -12
- package/locales/en-XL.json +12 -12
- package/locales/es-419.json +52 -10
- package/locales/es.json +50 -8
- package/locales/et.json +52 -10
- package/locales/eu.json +52 -10
- package/locales/fa.json +51 -9
- package/locales/fi.json +52 -10
- package/locales/fil.json +52 -10
- package/locales/fr-CA.json +52 -10
- package/locales/fr.json +52 -10
- package/locales/gl.json +52 -10
- package/locales/gu.json +52 -10
- package/locales/he.json +52 -10
- package/locales/hi.json +52 -10
- package/locales/hr.json +52 -10
- package/locales/hu.json +51 -9
- package/locales/hy.json +51 -9
- package/locales/id.json +52 -10
- package/locales/is.json +53 -11
- package/locales/it.json +51 -9
- package/locales/ja.json +52 -10
- package/locales/ka.json +53 -11
- package/locales/kk.json +51 -9
- package/locales/km.json +52 -10
- package/locales/kn.json +52 -10
- package/locales/ko.json +52 -10
- package/locales/ky.json +51 -9
- package/locales/lo.json +52 -10
- package/locales/lt.json +52 -10
- package/locales/lv.json +51 -9
- package/locales/mk.json +52 -10
- package/locales/ml.json +53 -11
- package/locales/mn.json +52 -10
- package/locales/mr.json +52 -10
- package/locales/ms.json +52 -10
- package/locales/my.json +51 -9
- package/locales/ne.json +52 -10
- package/locales/nl.json +52 -10
- package/locales/no.json +52 -10
- package/locales/or.json +53 -11
- package/locales/pa.json +53 -11
- package/locales/pl.json +51 -9
- package/locales/pt-PT.json +52 -10
- package/locales/pt.json +52 -10
- package/locales/ro.json +52 -10
- package/locales/ru.json +53 -11
- package/locales/si.json +52 -10
- package/locales/sk.json +51 -9
- package/locales/sl.json +51 -9
- package/locales/sq.json +52 -10
- package/locales/sr-Latn.json +52 -10
- package/locales/sr.json +52 -10
- package/locales/sv.json +52 -10
- package/locales/sw.json +51 -9
- package/locales/ta.json +52 -10
- package/locales/te.json +52 -10
- package/locales/th.json +51 -9
- package/locales/tr.json +52 -10
- package/locales/uk.json +52 -10
- package/locales/ur.json +52 -10
- package/locales/uz.json +51 -9
- package/locales/vi.json +52 -10
- package/locales/zh-HK.json +52 -10
- package/locales/zh-TW.json +51 -9
- package/locales/zh.json +52 -10
- package/locales/zu.json +52 -10
- package/models/cpu_profile/CPUProfileDataModel.d.ts +4 -2
- package/models/cpu_profile/CPUProfileDataModel.js.map +1 -1
- package/models/cpu_profile/ProfileTreeModel.d.ts +0 -1
- package/models/cpu_profile/ProfileTreeModel.js +0 -2
- package/models/cpu_profile/ProfileTreeModel.js.map +1 -1
- package/models/issues_manager/CheckFormsIssuesTrigger.d.ts +1 -0
- package/models/issues_manager/CheckFormsIssuesTrigger.js +1 -0
- package/models/issues_manager/ContrastCheckTrigger.d.ts +1 -0
- package/models/issues_manager/ContrastCheckTrigger.js +1 -0
- package/models/issues_manager/DeprecationIssue.d.ts +1 -0
- package/models/issues_manager/DeprecationIssue.js +1 -0
- package/models/issues_manager/IssueResolver.d.ts +1 -0
- package/models/issues_manager/IssueResolver.js +1 -0
- package/models/issues_manager/RelatedIssue.d.ts +1 -0
- package/models/issues_manager/RelatedIssue.js +1 -0
- package/models/issues_manager/SourceFrameIssuesManager.d.ts +1 -0
- package/models/issues_manager/SourceFrameIssuesManager.js +1 -0
- package/models/trace/LanternComputationData.js +9 -8
- package/models/trace/LanternComputationData.js.map +1 -1
- package/models/trace/ModelImpl.js +1 -1
- package/models/trace/ModelImpl.js.map +1 -1
- package/models/trace/Processor.js +23 -16
- package/models/trace/Processor.js.map +1 -1
- package/models/trace/extras/ThirdParties.js +1 -1
- package/models/trace/extras/ThirdParties.js.map +1 -1
- package/models/trace/extras/TraceFilter.js +2 -2
- package/models/trace/extras/TraceFilter.js.map +1 -1
- package/models/trace/extras/extras-tsconfig.json +0 -2
- package/models/trace/extras/extras.d.ts +4 -1
- package/models/trace/extras/extras.js +4 -1
- package/models/trace/extras/extras.js.map +1 -1
- package/models/trace/handlers/AuctionWorkletsHandler.js +5 -5
- package/models/trace/handlers/AuctionWorkletsHandler.js.map +1 -1
- package/models/trace/handlers/ExtensionTraceDataHandler.js +4 -4
- package/models/trace/handlers/ExtensionTraceDataHandler.js.map +1 -1
- package/models/trace/handlers/FlowsHandler.js +3 -3
- package/models/trace/handlers/FlowsHandler.js.map +1 -1
- package/models/trace/handlers/FramesHandler.js +7 -7
- package/models/trace/handlers/FramesHandler.js.map +1 -1
- package/models/trace/handlers/LargestImagePaintHandler.js +2 -2
- package/models/trace/handlers/LargestImagePaintHandler.js.map +1 -1
- package/models/trace/handlers/LayoutShiftsHandler.d.ts +1 -1
- package/models/trace/handlers/LayoutShiftsHandler.js +18 -10
- package/models/trace/handlers/LayoutShiftsHandler.js.map +1 -1
- package/models/trace/handlers/MetaHandler.js +9 -7
- package/models/trace/handlers/MetaHandler.js.map +1 -1
- package/models/trace/handlers/NetworkRequestsHandler.d.ts +0 -5
- package/models/trace/handlers/NetworkRequestsHandler.js +3 -22
- package/models/trace/handlers/NetworkRequestsHandler.js.map +1 -1
- package/models/trace/handlers/PageLoadMetricsHandler.d.ts +2 -2
- package/models/trace/handlers/PageLoadMetricsHandler.js +54 -25
- package/models/trace/handlers/PageLoadMetricsHandler.js.map +1 -1
- package/models/trace/handlers/RendererHandler.js +1 -1
- package/models/trace/handlers/RendererHandler.js.map +1 -1
- package/models/trace/handlers/SamplesHandler.js +7 -2
- package/models/trace/handlers/SamplesHandler.js.map +1 -1
- package/models/trace/handlers/ScriptsHandler.js.map +1 -1
- package/models/trace/handlers/Threads.d.ts +1 -1
- package/models/trace/handlers/Threads.js +17 -7
- package/models/trace/handlers/Threads.js.map +1 -1
- package/models/trace/handlers/UserInteractionsHandler.js +4 -3
- package/models/trace/handlers/UserInteractionsHandler.js.map +1 -1
- package/models/trace/handlers/WarningsHandler.js +2 -2
- package/models/trace/handlers/WarningsHandler.js.map +1 -1
- package/models/trace/helpers/SamplesIntegrator.d.ts +1 -1
- package/models/trace/helpers/SamplesIntegrator.js +23 -21
- package/models/trace/helpers/SamplesIntegrator.js.map +1 -1
- package/models/trace/helpers/Trace.js +111 -118
- package/models/trace/helpers/Trace.js.map +1 -1
- package/models/trace/insights/CLSCulprits.d.ts +25 -5
- package/models/trace/insights/CLSCulprits.js +73 -33
- package/models/trace/insights/CLSCulprits.js.map +1 -1
- package/models/trace/insights/Common.js +4 -3
- package/models/trace/insights/Common.js.map +1 -1
- package/models/trace/insights/DOMSize.js +3 -3
- package/models/trace/insights/DOMSize.js.map +1 -1
- package/models/trace/insights/DocumentLatency.d.ts +2 -2
- package/models/trace/insights/DocumentLatency.js +4 -4
- package/models/trace/insights/DocumentLatency.js.map +1 -1
- package/models/trace/insights/DuplicatedJavaScript.js +2 -2
- package/models/trace/insights/DuplicatedJavaScript.js.map +1 -1
- package/models/trace/insights/FontDisplay.js +2 -2
- package/models/trace/insights/FontDisplay.js.map +1 -1
- package/models/trace/insights/ForcedReflow.js +2 -2
- package/models/trace/insights/ForcedReflow.js.map +1 -1
- package/models/trace/insights/ImageDelivery.js +2 -2
- package/models/trace/insights/ImageDelivery.js.map +1 -1
- package/models/trace/insights/InteractionToNextPaint.js +3 -3
- package/models/trace/insights/InteractionToNextPaint.js.map +1 -1
- package/models/trace/insights/LCPDiscovery.js +3 -3
- package/models/trace/insights/LCPDiscovery.js.map +1 -1
- package/models/trace/insights/LCPPhases.js +3 -3
- package/models/trace/insights/LCPPhases.js.map +1 -1
- package/models/trace/insights/LegacyJavaScript.js +2 -2
- package/models/trace/insights/LegacyJavaScript.js.map +1 -1
- package/models/trace/insights/ModernHTTP.js +2 -2
- package/models/trace/insights/ModernHTTP.js.map +1 -1
- package/models/trace/insights/NetworkDependencyTree.d.ts +31 -5
- package/models/trace/insights/NetworkDependencyTree.js +137 -12
- package/models/trace/insights/NetworkDependencyTree.js.map +1 -1
- package/models/trace/insights/RenderBlocking.js +3 -3
- package/models/trace/insights/RenderBlocking.js.map +1 -1
- package/models/trace/insights/SlowCSSSelector.js +2 -2
- package/models/trace/insights/SlowCSSSelector.js.map +1 -1
- package/models/trace/insights/ThirdParties.js +2 -2
- package/models/trace/insights/ThirdParties.js.map +1 -1
- package/models/trace/insights/Viewport.d.ts +8 -2
- package/models/trace/insights/Viewport.js +18 -3
- package/models/trace/insights/Viewport.js.map +1 -1
- package/models/trace/insights/types.d.ts +1 -1
- package/models/trace/insights/types.js +19 -0
- package/models/trace/insights/types.js.map +1 -1
- package/models/trace/lantern/graph/BaseNode.d.ts +5 -2
- package/models/trace/lantern/graph/BaseNode.js +8 -5
- package/models/trace/lantern/graph/BaseNode.js.map +1 -1
- package/models/trace/lantern/graph/PageDependencyGraph.js +46 -3
- package/models/trace/lantern/graph/PageDependencyGraph.js.map +1 -1
- package/models/trace/lantern/simulation/Simulator.js +1 -1
- package/models/trace/lantern/simulation/Simulator.js.map +1 -1
- package/models/trace/trace-tsconfig.json +0 -1
- package/models/trace/trace.d.ts +1 -2
- package/models/trace/trace.js +1 -2
- package/models/trace/trace.js.map +1 -1
- package/models/trace/types/Extensions.d.ts +6 -1
- package/models/trace/types/Extensions.js.map +1 -1
- package/models/trace/types/File.d.ts +18 -4
- package/models/trace/types/File.js +28 -4
- package/models/trace/types/File.js.map +1 -1
- package/models/trace/types/TraceEvents.d.ts +13 -10
- package/models/trace/types/TraceEvents.js +377 -108
- package/models/trace/types/TraceEvents.js.map +1 -1
- package/package.json +1 -1
- package/test/test-trace-engine.mjs +77 -0
- package/third_party/marked/marked.d.ts +1 -0
- package/third_party/marked/marked.js +1 -0
- package/models/trace/TracingManager.js.map +0 -1
- package/models/trace/extras/FetchNodes.d.ts +0 -61
- package/models/trace/extras/FetchNodes.js +0 -214
- package/models/trace/extras/FetchNodes.js.map +0 -1
- package/models/trace/extras/Metadata.d.ts +0 -3
- package/models/trace/extras/Metadata.js +0 -71
- package/models/trace/extras/Metadata.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RenderBlocking.js","sourceRoot":"","sources":["../../../../../../../front_end/models/trace/insights/RenderBlocking.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\nimport * as i18n from '../../../core/i18n/i18n.js';\nimport * as Protocol from '../../../generated/protocol.js';\nimport * as Handlers from '../handlers/handlers.js';\nimport * as Helpers from '../helpers/helpers.js';\nimport type * as Lantern from '../lantern/lantern.js';\nimport type * as Types from '../types/types.js';\n\nimport {\n InsightCategory,\n InsightKeys,\n type InsightModel,\n type InsightSetContext,\n type InsightSetContextWithNavigation,\n InsightWarning,\n type LanternContext,\n type PartialInsightModel,\n} from './types.js';\n\nexport const UIStrings = {\n /**\n * @description Title of an insight that provides the user with the list of network requests that blocked and therefore slowed down the page rendering and becoming visible to the user.\n */\n title: 'Render blocking requests',\n /**\n * @description Text to describe that there are requests blocking rendering, which may affect LCP.\n */\n description: 'Requests are blocking the page\\'s initial render, which may delay LCP. ' +\n '[Deferring or inlining](https://web.dev/learn/performance/understanding-the-critical-path#render-blocking_resources) ' +\n 'can move these network requests out of the critical path.',\n /**\n * @description Label to describe a network request (that happens to be render-blocking).\n */\n renderBlockingRequest: 'Request',\n /**\n *@description Label used for a time duration.\n */\n duration: 'Duration',\n /**\n * @description Text status indicating that no requests blocked the initial render of a navigation\n */\n noRenderBlocking: 'No render blocking requests for this navigation',\n} as const;\n\nconst str_ = i18n.i18n.registerUIStrings('models/trace/insights/RenderBlocking.ts', UIStrings);\nexport const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);\n\nexport function isRenderBlocking(insight: InsightModel): insight is RenderBlockingInsightModel {\n return insight.insightKey === 'RenderBlocking';\n}\n\nexport type RenderBlockingInsightModel = InsightModel<typeof UIStrings, {\n renderBlockingRequests: Types.Events.SyntheticNetworkRequest[],\n requestIdToWastedMs?: Map<string, number>,\n}>;\n\n// Because of the way we detect blocking stylesheets, asynchronously loaded\n// CSS with link[rel=preload] and an onload handler (see https://github.com/filamentgroup/loadCSS)\n// can be falsely flagged as blocking. Therefore, ignore stylesheets that loaded fast enough\n// to possibly be non-blocking (and they have minimal impact anyway).\nconst MINIMUM_WASTED_MS = 50;\n\n/**\n * Given a simulation's nodeTimings, return an object with the nodes/timing keyed by network URL\n */\nfunction getNodesAndTimingByRequestId(nodeTimings: Lantern.Simulation.Result['nodeTimings']):\n Map<string, {node: Lantern.Graph.Node, nodeTiming: Lantern.Types.Simulation.NodeTiming}> {\n const requestIdToNode =\n new Map<string, {node: Lantern.Graph.Node, nodeTiming: Lantern.Types.Simulation.NodeTiming}>();\n\n for (const [node, nodeTiming] of nodeTimings) {\n if (node.type !== 'network') {\n continue;\n }\n\n requestIdToNode.set(node.request.requestId, {node, nodeTiming});\n }\n\n return requestIdToNode;\n}\n\nfunction estimateSavingsWithGraphs(deferredIds: Set<string>, lanternContext: LanternContext): Types.Timing.Milli {\n const simulator = lanternContext.simulator;\n const fcpGraph = lanternContext.metrics.firstContentfulPaint.optimisticGraph;\n const {nodeTimings} = lanternContext.simulator.simulate(fcpGraph);\n const adjustedNodeTimings = new Map(nodeTimings);\n\n const totalChildNetworkBytes = 0;\n const minimalFCPGraph = fcpGraph.cloneWithRelationships(node => {\n // If a node can be deferred, exclude it from the new FCP graph\n const canDeferRequest = deferredIds.has(node.id);\n return !canDeferRequest;\n });\n\n if (minimalFCPGraph.type !== 'network') {\n throw new Error('minimalFCPGraph not a NetworkNode');\n }\n\n // Recalculate the \"before\" time based on our adjusted node timings.\n const estimateBeforeInline = Math.max(...Array.from(\n Array.from(adjustedNodeTimings).map(timing => timing[1].endTime),\n ));\n\n // Add the inlined bytes to the HTML response\n const originalTransferSize = minimalFCPGraph.request.transferSize;\n const safeTransferSize = originalTransferSize || 0;\n minimalFCPGraph.request.transferSize = safeTransferSize + totalChildNetworkBytes;\n const estimateAfterInline = simulator.simulate(minimalFCPGraph).timeInMs;\n minimalFCPGraph.request.transferSize = originalTransferSize;\n return Math.round(Math.max(estimateBeforeInline - estimateAfterInline, 0)) as Types.Timing.Milli;\n}\n\nfunction hasImageLCP(parsedTrace: Handlers.Types.ParsedTrace, context: InsightSetContextWithNavigation): boolean {\n return parsedTrace.LargestImagePaint.lcpRequestByNavigationId.has(context.navigationId);\n}\n\nfunction computeSavings(\n parsedTrace: Handlers.Types.ParsedTrace, context: InsightSetContextWithNavigation,\n renderBlockingRequests: Types.Events.SyntheticNetworkRequest[]):\n Pick<RenderBlockingInsightModel, 'metricSavings'|'requestIdToWastedMs'>|undefined {\n if (!context.lantern) {\n return;\n }\n\n const nodesAndTimingsByRequestId =\n getNodesAndTimingByRequestId(context.lantern.metrics.firstContentfulPaint.optimisticEstimate.nodeTimings);\n\n const metricSavings = {FCP: 0 as Types.Timing.Milli, LCP: 0 as Types.Timing.Milli};\n const requestIdToWastedMs = new Map<string, number>();\n const deferredNodeIds = new Set<string>();\n for (const request of renderBlockingRequests) {\n const nodeAndTiming = nodesAndTimingsByRequestId.get(request.args.data.requestId);\n if (!nodeAndTiming) {\n continue;\n }\n\n const {node, nodeTiming} = nodeAndTiming;\n\n // Mark this node and all its dependents as deferrable\n node.traverse(node => deferredNodeIds.add(node.id));\n\n // \"wastedMs\" is the download time of the network request, responseReceived - requestSent\n const wastedMs = Math.round(nodeTiming.duration);\n if (wastedMs < MINIMUM_WASTED_MS) {\n continue;\n }\n\n requestIdToWastedMs.set(node.id, wastedMs);\n }\n\n if (requestIdToWastedMs.size) {\n metricSavings.FCP = estimateSavingsWithGraphs(deferredNodeIds, context.lantern);\n\n // In most cases, render blocking resources only affect LCP if LCP isn't an image.\n if (!hasImageLCP(parsedTrace, context)) {\n metricSavings.LCP = metricSavings.FCP;\n }\n }\n\n return {metricSavings, requestIdToWastedMs};\n}\n\nfunction finalize(partialModel: PartialInsightModel<RenderBlockingInsightModel>): RenderBlockingInsightModel {\n return {\n insightKey: InsightKeys.RENDER_BLOCKING,\n strings: UIStrings,\n title: i18nString(UIStrings.title),\n description: i18nString(UIStrings.description),\n category: InsightCategory.LCP,\n state: partialModel.renderBlockingRequests.length > 0 ? 'fail' : 'pass',\n ...partialModel,\n };\n}\n\nexport function generateInsight(\n parsedTrace: Handlers.Types.ParsedTrace, context: InsightSetContext): RenderBlockingInsightModel {\n if (!context.navigation) {\n return finalize({\n renderBlockingRequests: [],\n });\n }\n\n const firstPaintTs = parsedTrace.PageLoadMetrics.metricScoresByFrameId.get(context.frameId)\n ?.get(context.navigationId)\n ?.get(Handlers.ModelHandlers.PageLoadMetrics.MetricName.FP)\n ?.event?.ts;\n if (!firstPaintTs) {\n return finalize({\n renderBlockingRequests: [],\n warnings: [InsightWarning.NO_FP],\n });\n }\n\n let renderBlockingRequests: Types.Events.SyntheticNetworkRequest[] = [];\n for (const req of parsedTrace.NetworkRequests.byTime) {\n if (req.args.data.frame !== context.frameId) {\n continue;\n }\n\n if (!Helpers.Network.isSyntheticNetworkRequestEventRenderBlocking(req)) {\n continue;\n }\n\n if (req.args.data.syntheticData.finishTime > firstPaintTs) {\n continue;\n }\n\n // If a request is marked `in_body_parser_blocking` it should only be considered render blocking if it is a\n // high enough priority. Some requests (e.g. scripts) are not marked as high priority if they are fetched\n // after a non-preloaded image. (See \"early\" definition in https://web.dev/articles/fetch-priority)\n //\n // There are edge cases and exceptions (e.g. priority hints) but this gives us the best approximation\n // of render blocking resources in the document body.\n if (req.args.data.renderBlocking === 'in_body_parser_blocking') {\n const priority = req.args.data.priority;\n const isScript = req.args.data.resourceType === Protocol.Network.ResourceType.Script;\n const isBlockingScript = isScript && priority === Protocol.Network.ResourcePriority.High;\n if (priority !== Protocol.Network.ResourcePriority.VeryHigh && !isBlockingScript) {\n continue;\n }\n }\n\n const navigation =\n Helpers.Trace.getNavigationForTraceEvent(req, context.frameId, parsedTrace.Meta.navigationsByFrameId);\n if (navigation === context.navigation) {\n renderBlockingRequests.push(req);\n }\n }\n\n const savings = computeSavings(parsedTrace, context, renderBlockingRequests);\n\n // Sort by request duration for insights.\n renderBlockingRequests = renderBlockingRequests.sort((a, b) => {\n return b.dur - a.dur;\n });\n\n return finalize({\n relatedEvents: renderBlockingRequests,\n renderBlockingRequests,\n ...savings,\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"RenderBlocking.js","sourceRoot":"","sources":["../../../../../../../front_end/models/trace/insights/RenderBlocking.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\nimport * as i18n from '../../../core/i18n/i18n.js';\nimport * as Protocol from '../../../generated/protocol.js';\nimport * as Handlers from '../handlers/handlers.js';\nimport * as Helpers from '../helpers/helpers.js';\nimport type * as Lantern from '../lantern/lantern.js';\nimport type * as Types from '../types/types.js';\n\nimport {\n InsightCategory,\n InsightKeys,\n type InsightModel,\n type InsightSetContext,\n type InsightSetContextWithNavigation,\n InsightWarning,\n type LanternContext,\n type PartialInsightModel,\n} from './types.js';\n\nexport const UIStrings = {\n /**\n * @description Title of an insight that provides the user with the list of network requests that blocked and therefore slowed down the page rendering and becoming visible to the user.\n */\n title: 'Render blocking requests',\n /**\n * @description Text to describe that there are requests blocking rendering, which may affect LCP.\n */\n description: 'Requests are blocking the page\\'s initial render, which may delay LCP. ' +\n '[Deferring or inlining](https://web.dev/learn/performance/understanding-the-critical-path#render-blocking_resources) ' +\n 'can move these network requests out of the critical path.',\n /**\n * @description Label to describe a network request (that happens to be render-blocking).\n */\n renderBlockingRequest: 'Request',\n /**\n *@description Label used for a time duration.\n */\n duration: 'Duration',\n /**\n * @description Text status indicating that no requests blocked the initial render of a navigation\n */\n noRenderBlocking: 'No render blocking requests for this navigation',\n} as const;\n\nconst str_ = i18n.i18n.registerUIStrings('models/trace/insights/RenderBlocking.ts', UIStrings);\nexport const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);\n\nexport function isRenderBlocking(insight: InsightModel): insight is RenderBlockingInsightModel {\n return insight.insightKey === 'RenderBlocking';\n}\n\nexport type RenderBlockingInsightModel = InsightModel<typeof UIStrings, {\n renderBlockingRequests: Types.Events.SyntheticNetworkRequest[],\n requestIdToWastedMs?: Map<string, number>,\n}>;\n\n// Because of the way we detect blocking stylesheets, asynchronously loaded\n// CSS with link[rel=preload] and an onload handler (see https://github.com/filamentgroup/loadCSS)\n// can be falsely flagged as blocking. Therefore, ignore stylesheets that loaded fast enough\n// to possibly be non-blocking (and they have minimal impact anyway).\nconst MINIMUM_WASTED_MS = 50;\n\n/**\n * Given a simulation's nodeTimings, return an object with the nodes/timing keyed by network URL\n */\nfunction getNodesAndTimingByRequestId(nodeTimings: Lantern.Simulation.Result['nodeTimings']):\n Map<string, {node: Lantern.Graph.Node, nodeTiming: Lantern.Types.Simulation.NodeTiming}> {\n const requestIdToNode =\n new Map<string, {node: Lantern.Graph.Node, nodeTiming: Lantern.Types.Simulation.NodeTiming}>();\n\n for (const [node, nodeTiming] of nodeTimings) {\n if (node.type !== 'network') {\n continue;\n }\n\n requestIdToNode.set(node.request.requestId, {node, nodeTiming});\n }\n\n return requestIdToNode;\n}\n\nfunction estimateSavingsWithGraphs(deferredIds: Set<string>, lanternContext: LanternContext): Types.Timing.Milli {\n const simulator = lanternContext.simulator;\n const fcpGraph = lanternContext.metrics.firstContentfulPaint.optimisticGraph;\n const {nodeTimings} = lanternContext.simulator.simulate(fcpGraph);\n const adjustedNodeTimings = new Map(nodeTimings);\n\n const totalChildNetworkBytes = 0;\n const minimalFCPGraph = fcpGraph.cloneWithRelationships(node => {\n // If a node can be deferred, exclude it from the new FCP graph\n const canDeferRequest = deferredIds.has(node.id);\n return !canDeferRequest;\n });\n\n if (minimalFCPGraph.type !== 'network') {\n throw new Error('minimalFCPGraph not a NetworkNode');\n }\n\n // Recalculate the \"before\" time based on our adjusted node timings.\n const estimateBeforeInline = Math.max(...Array.from(\n Array.from(adjustedNodeTimings).map(timing => timing[1].endTime),\n ));\n\n // Add the inlined bytes to the HTML response\n const originalTransferSize = minimalFCPGraph.request.transferSize;\n const safeTransferSize = originalTransferSize || 0;\n minimalFCPGraph.request.transferSize = safeTransferSize + totalChildNetworkBytes;\n const estimateAfterInline = simulator.simulate(minimalFCPGraph).timeInMs;\n minimalFCPGraph.request.transferSize = originalTransferSize;\n return Math.round(Math.max(estimateBeforeInline - estimateAfterInline, 0)) as Types.Timing.Milli;\n}\n\nfunction hasImageLCP(parsedTrace: Handlers.Types.ParsedTrace, context: InsightSetContextWithNavigation): boolean {\n return parsedTrace.LargestImagePaint.lcpRequestByNavigationId.has(context.navigationId);\n}\n\nfunction computeSavings(\n parsedTrace: Handlers.Types.ParsedTrace, context: InsightSetContextWithNavigation,\n renderBlockingRequests: Types.Events.SyntheticNetworkRequest[]):\n Pick<RenderBlockingInsightModel, 'metricSavings'|'requestIdToWastedMs'>|undefined {\n if (!context.lantern) {\n return;\n }\n\n const nodesAndTimingsByRequestId =\n getNodesAndTimingByRequestId(context.lantern.metrics.firstContentfulPaint.optimisticEstimate.nodeTimings);\n\n const metricSavings = {FCP: 0 as Types.Timing.Milli, LCP: 0 as Types.Timing.Milli};\n const requestIdToWastedMs = new Map<string, number>();\n const deferredNodeIds = new Set<string>();\n for (const request of renderBlockingRequests) {\n const nodeAndTiming = nodesAndTimingsByRequestId.get(request.args.data.requestId);\n if (!nodeAndTiming) {\n continue;\n }\n\n const {node, nodeTiming} = nodeAndTiming;\n\n // Mark this node and all its dependents as deferrable\n node.traverse(node => deferredNodeIds.add(node.id));\n\n // \"wastedMs\" is the download time of the network request, responseReceived - requestSent\n const wastedMs = Math.round(nodeTiming.duration);\n if (wastedMs < MINIMUM_WASTED_MS) {\n continue;\n }\n\n requestIdToWastedMs.set(node.id, wastedMs);\n }\n\n if (requestIdToWastedMs.size) {\n metricSavings.FCP = estimateSavingsWithGraphs(deferredNodeIds, context.lantern);\n\n // In most cases, render blocking resources only affect LCP if LCP isn't an image.\n if (!hasImageLCP(parsedTrace, context)) {\n metricSavings.LCP = metricSavings.FCP;\n }\n }\n\n return {metricSavings, requestIdToWastedMs};\n}\n\nfunction finalize(partialModel: PartialInsightModel<RenderBlockingInsightModel>): RenderBlockingInsightModel {\n return {\n insightKey: InsightKeys.RENDER_BLOCKING,\n strings: UIStrings,\n title: i18nString(UIStrings.title),\n description: i18nString(UIStrings.description),\n category: InsightCategory.LCP,\n state: partialModel.renderBlockingRequests.length > 0 ? 'fail' : 'pass',\n ...partialModel,\n };\n}\n\nexport function generateInsight(\n parsedTrace: Handlers.Types.ParsedTrace, context: InsightSetContext): RenderBlockingInsightModel {\n if (!context.navigation) {\n return finalize({\n renderBlockingRequests: [],\n });\n }\n\n const firstPaintTs = parsedTrace.PageLoadMetrics.metricScoresByFrameId.get(context.frameId)\n ?.get(context.navigationId)\n ?.get(Handlers.ModelHandlers.PageLoadMetrics.MetricName.FP)\n ?.event?.ts;\n if (!firstPaintTs) {\n return finalize({\n renderBlockingRequests: [],\n warnings: [InsightWarning.NO_FP],\n });\n }\n\n let renderBlockingRequests: Types.Events.SyntheticNetworkRequest[] = [];\n for (const req of parsedTrace.NetworkRequests.byTime) {\n if (req.args.data.frame !== context.frameId) {\n continue;\n }\n\n if (!Helpers.Network.isSyntheticNetworkRequestEventRenderBlocking(req)) {\n continue;\n }\n\n if (req.args.data.syntheticData.finishTime > firstPaintTs) {\n continue;\n }\n\n // If a request is marked `in_body_parser_blocking` it should only be considered render blocking if it is a\n // high enough priority. Some requests (e.g. scripts) are not marked as high priority if they are fetched\n // after a non-preloaded image. (See \"early\" definition in https://web.dev/articles/fetch-priority)\n //\n // There are edge cases and exceptions (e.g. priority hints) but this gives us the best approximation\n // of render blocking resources in the document body.\n if (req.args.data.renderBlocking === 'in_body_parser_blocking') {\n const priority = req.args.data.priority;\n const isScript = req.args.data.resourceType === Protocol.Network.ResourceType.Script;\n const isBlockingScript = isScript && priority === Protocol.Network.ResourcePriority.High;\n if (priority !== Protocol.Network.ResourcePriority.VeryHigh && !isBlockingScript) {\n continue;\n }\n }\n\n const navigation =\n Helpers.Trace.getNavigationForTraceEvent(req, context.frameId, parsedTrace.Meta.navigationsByFrameId);\n if (navigation === context.navigation) {\n renderBlockingRequests.push(req);\n }\n }\n\n const savings = computeSavings(parsedTrace, context, renderBlockingRequests);\n\n // Sort by request duration for insights.\n renderBlockingRequests = renderBlockingRequests.sort((a, b) => {\n return b.dur - a.dur;\n });\n\n return finalize({\n relatedEvents: renderBlockingRequests,\n renderBlockingRequests,\n ...savings,\n });\n}\n"]}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import * as Helpers from '../helpers/helpers.js';
|
|
6
6
|
import { SelectorTimingsKey } from '../types/TraceEvents.js';
|
|
7
7
|
import * as Types from '../types/types.js';
|
|
8
|
-
import { InsightCategory, } from './types.js';
|
|
8
|
+
import { InsightCategory, InsightKeys, } from './types.js';
|
|
9
9
|
export const UIStrings = {
|
|
10
10
|
/**
|
|
11
11
|
*@description Title of an insight that provides details about slow CSS selectors.
|
|
@@ -69,7 +69,7 @@ function aggregateSelectorStats(data, context) {
|
|
|
69
69
|
}
|
|
70
70
|
function finalize(partialModel) {
|
|
71
71
|
return {
|
|
72
|
-
insightKey:
|
|
72
|
+
insightKey: InsightKeys.SLOW_CSS_SELECTOR,
|
|
73
73
|
strings: UIStrings,
|
|
74
74
|
title: i18nString(UIStrings.title),
|
|
75
75
|
description: i18nString(UIStrings.description),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SlowCSSSelector.js","sourceRoot":"","sources":["../../../../../../../front_end/models/trace/insights/SlowCSSSelector.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,yEAAyE;AACzE,6BAA6B;AAE7B,OAAO,KAAK,IAAI,MAAM,4BAA4B,CAAC;AAEnD,OAAO,KAAK,OAAO,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAsB,kBAAkB,EAAC,MAAM,yBAAyB,CAAC;AAChF,OAAO,KAAK,KAAK,MAAM,mBAAmB,CAAC;AAE3C,OAAO,EACL,eAAe,
|
|
1
|
+
{"version":3,"file":"SlowCSSSelector.js","sourceRoot":"","sources":["../../../../../../../front_end/models/trace/insights/SlowCSSSelector.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\nimport * as i18n from '../../../core/i18n/i18n.js';\nimport type * as Handlers from '../handlers/handlers.js';\nimport * as Helpers from '../helpers/helpers.js';\nimport {type SelectorTiming, SelectorTimingsKey} from '../types/TraceEvents.js';\nimport * as Types from '../types/types.js';\n\nimport {\n InsightCategory,\n InsightKeys,\n type InsightModel,\n type InsightSetContext,\n type PartialInsightModel,\n} from './types.js';\n\nexport const UIStrings = {\n /**\n *@description Title of an insight that provides details about slow CSS selectors.\n */\n title: 'CSS Selector costs',\n\n /**\n * @description Text to describe how to improve the performance of CSS selectors.\n */\n description:\n 'If Recalculate Style costs remain high, selector optimization can reduce them. [Optimize the selectors](https://developer.chrome.com/docs/devtools/performance/selector-stats) with both high elapsed time and high slow-path %. Simpler selectors, fewer selectors, a smaller DOM, and a shallower DOM will all reduce matching costs.',\n /**\n *@description Column name for count of elements that the engine attempted to match against a style rule\n */\n matchAttempts: 'Match attempts',\n /**\n *@description Column name for count of elements that matched a style rule\n */\n matchCount: 'Match count',\n /**\n *@description Column name for elapsed time spent computing a style rule\n */\n elapsed: 'Elapsed time',\n /**\n *@description Column name for the selectors that took the longest amount of time/effort.\n */\n topSelectors: 'Top selectors',\n /**\n *@description Column name for a total sum.\n */\n total: 'Total',\n /**\n * @description Text status indicating that no CSS selector data was found.\n */\n enableSelectorData:\n 'No CSS selector data was found. CSS selector stats need to be enabled in the performance panel settings.',\n} as const;\n\nconst str_ = i18n.i18n.registerUIStrings('models/trace/insights/SlowCSSSelector.ts', UIStrings);\nexport const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);\n\nexport type SlowCSSSelectorInsightModel = InsightModel<typeof UIStrings, {\n totalElapsedMs: Types.Timing.Milli,\n totalMatchAttempts: number,\n totalMatchCount: number,\n topElapsedMs: Types.Events.SelectorTiming[],\n topMatchAttempts: Types.Events.SelectorTiming[],\n}>;\n\nfunction aggregateSelectorStats(\n data: Map<Types.Events.UpdateLayoutTree, {\n timings: Types.Events.SelectorTiming[],\n }>,\n context: InsightSetContext): SelectorTiming[] {\n const selectorMap = new Map<String, SelectorTiming>();\n\n for (const [event, value] of data) {\n if (event.args.beginData?.frame !== context.frameId) {\n continue;\n }\n if (!Helpers.Timing.eventIsInBounds(event, context.bounds)) {\n continue;\n }\n for (const timing of value.timings) {\n const key = timing[SelectorTimingsKey.Selector] + '_' + timing[SelectorTimingsKey.StyleSheetId];\n const findTiming = selectorMap.get(key);\n if (findTiming !== undefined) {\n findTiming[SelectorTimingsKey.Elapsed] += timing[SelectorTimingsKey.Elapsed];\n findTiming[SelectorTimingsKey.FastRejectCount] += timing[SelectorTimingsKey.FastRejectCount];\n findTiming[SelectorTimingsKey.MatchAttempts] += timing[SelectorTimingsKey.MatchAttempts];\n findTiming[SelectorTimingsKey.MatchCount] += timing[SelectorTimingsKey.MatchCount];\n } else {\n selectorMap.set(key, {...timing});\n }\n }\n }\n\n return [...selectorMap.values()];\n}\n\nfunction finalize(partialModel: PartialInsightModel<SlowCSSSelectorInsightModel>): SlowCSSSelectorInsightModel {\n return {\n insightKey: InsightKeys.SLOW_CSS_SELECTOR,\n strings: UIStrings,\n title: i18nString(UIStrings.title),\n description: i18nString(UIStrings.description),\n category: InsightCategory.ALL,\n state: partialModel.topElapsedMs.length !== 0 && partialModel.topMatchAttempts.length !== 0 ? 'informative' :\n 'pass',\n ...partialModel,\n };\n}\n\nexport function generateInsight(\n parsedTrace: Handlers.Types.ParsedTrace, context: InsightSetContext): SlowCSSSelectorInsightModel {\n const selectorStatsData = parsedTrace.SelectorStats;\n\n if (!selectorStatsData) {\n throw new Error('no selector stats data');\n }\n\n const selectorTimings = aggregateSelectorStats(selectorStatsData.dataForUpdateLayoutEvent, context);\n\n let totalElapsedUs = 0;\n let totalMatchAttempts = 0;\n let totalMatchCount = 0;\n\n selectorTimings.map(timing => {\n totalElapsedUs += timing[SelectorTimingsKey.Elapsed];\n totalMatchAttempts += timing[SelectorTimingsKey.MatchAttempts];\n totalMatchCount += timing[SelectorTimingsKey.MatchCount];\n });\n\n // sort by elapsed time\n const sortByElapsedMs = [...selectorTimings].sort((a, b) => {\n return b[SelectorTimingsKey.Elapsed] - a[SelectorTimingsKey.Elapsed];\n });\n\n // sort by match attempts\n const sortByMatchAttempts = [...selectorTimings].sort((a, b) => {\n return b[SelectorTimingsKey.MatchAttempts] - a[SelectorTimingsKey.MatchAttempts];\n });\n\n return finalize({\n // TODO: should we identify UpdateLayout events as linked to this insight?\n relatedEvents: [],\n totalElapsedMs: Types.Timing.Milli(totalElapsedUs / 1000.0),\n totalMatchAttempts,\n totalMatchCount,\n topElapsedMs: sortByElapsedMs.slice(0, 3),\n topMatchAttempts: sortByMatchAttempts.slice(0, 3),\n });\n}\n"]}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import * as ThirdPartyWeb from '../../../third_party/third-party-web/third-party-web.js';
|
|
6
6
|
import * as Extras from '../extras/extras.js';
|
|
7
7
|
import * as Handlers from '../handlers/handlers.js';
|
|
8
|
-
import { InsightCategory, } from './types.js';
|
|
8
|
+
import { InsightCategory, InsightKeys, } from './types.js';
|
|
9
9
|
export const UIStrings = {
|
|
10
10
|
/** Title of an insight that provides details about the code on a web page that the user doesn't control (referred to as "third-party code"). */
|
|
11
11
|
title: '3rd parties',
|
|
@@ -39,7 +39,7 @@ function getRelatedEvents(summaries, firstPartyEntity) {
|
|
|
39
39
|
}
|
|
40
40
|
function finalize(partialModel) {
|
|
41
41
|
return {
|
|
42
|
-
insightKey:
|
|
42
|
+
insightKey: InsightKeys.THIRD_PARTIES,
|
|
43
43
|
strings: UIStrings,
|
|
44
44
|
title: i18nString(UIStrings.title),
|
|
45
45
|
description: i18nString(UIStrings.description),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThirdParties.js","sourceRoot":"","sources":["../../../../../../../front_end/models/trace/insights/ThirdParties.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,yEAAyE;AACzE,6BAA6B;AAE7B,OAAO,KAAK,IAAI,MAAM,4BAA4B,CAAC;AACnD,OAAO,KAAK,aAAa,MAAM,yDAAyD,CAAC;AACzF,OAAO,KAAK,MAAM,MAAM,qBAAqB,CAAC;AAC9C,OAAO,KAAK,QAAQ,MAAM,yBAAyB,CAAC;AAGpD,OAAO,EACL,eAAe,
|
|
1
|
+
{"version":3,"file":"ThirdParties.js","sourceRoot":"","sources":["../../../../../../../front_end/models/trace/insights/ThirdParties.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,yEAAyE;AACzE,6BAA6B;AAE7B,OAAO,KAAK,IAAI,MAAM,4BAA4B,CAAC;AACnD,OAAO,KAAK,aAAa,MAAM,yDAAyD,CAAC;AACzF,OAAO,KAAK,MAAM,MAAM,qBAAqB,CAAC;AAC9C,OAAO,KAAK,QAAQ,MAAM,yBAAyB,CAAC;AAGpD,OAAO,EACL,eAAe,EACf,WAAW,GAIZ,MAAM,YAAY,CAAC;AAEpB,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,gJAAgJ;IAChJ,KAAK,EAAE,aAAa;IACpB;;;OAGG;IACH,WAAW,EAAE,4DAA4D;QACrE,0KAA0K;IAC9K,iFAAiF;IACjF,gBAAgB,EAAE,WAAW;IAC7B,4GAA4G;IAC5G,kBAAkB,EAAE,eAAe;IACnC,wJAAwJ;IACxJ,oBAAoB,EAAE,kBAAkB;IACxC;;OAEG;IACH,cAAc,EAAE,wBAAwB;CAChC,CAAC;AAEX,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,uCAAuC,EAAE,SAAS,CAAC,CAAC;AAC7F,MAAM,CAAC,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;AAQ7E,SAAS,gBAAgB,CACrB,SAA8C,EAC9C,gBAAsD;IACxD,MAAM,aAAa,GAAG,EAAE,CAAC;IACzB,KAAK,MAAM,OAAO,IAAI,SAAS,EAAE,CAAC;QAChC,IAAI,OAAO,CAAC,MAAM,KAAK,gBAAgB,EAAE,CAAC;YACxC,aAAa,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IAED,OAAO,aAAa,CAAC;AACvB,CAAC;AAED,SAAS,QAAQ,CAAC,YAA2D;IAC3E,OAAO;QACL,UAAU,EAAE,WAAW,CAAC,aAAa;QACrC,OAAO,EAAE,SAAS;QAClB,KAAK,EAAE,UAAU,CAAC,SAAS,CAAC,KAAK,CAAC;QAClC,WAAW,EAAE,UAAU,CAAC,SAAS,CAAC,WAAW,CAAC;QAC9C,QAAQ,EAAE,eAAe,CAAC,GAAG;QAC7B,KAAK,EAAE,YAAY,CAAC,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,KAAK,YAAY,CAAC,gBAAgB,CAAC,CAAC,CAAC;YACnG,aAAa,CAAC,CAAC;YACf,MAAM;QACV,GAAG,YAAY;KAChB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,eAAe,CAC3B,WAAuC,EAAE,OAA0B;IACrE,MAAM,eAAe,GACjB,MAAM,CAAC,YAAY,CAAC,qBAAqB,CAAC,WAAyC,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAEzG,MAAM,aAAa,GAAG,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,EAAE,iBAAiB,IAAI,WAAW,CAAC,IAAI,CAAC,YAAY,CAAC;IACxG,MAAM,gBAAgB,GAAG,aAAa,CAAC,aAAa,CAAC,SAAS,CAAC,aAAa,CAAC;QACzE,QAAQ,CAAC,OAAO,CAAC,YAAY,CAAC,WAAW,CAAC,QAAQ,CAAC,cAAc,CAAC,kBAAkB,EAAE,aAAa,CAAC,CAAC;IAEzG,OAAO,QAAQ,CAAC;QACd,aAAa,EAAE,gBAAgB,CAAC,eAAe,EAAE,gBAAgB,CAAC;QAClE,gBAAgB;QAChB,eAAe;KAChB,CAAC,CAAC;AACL,CAAC","sourcesContent":["// Copyright 2024 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\nimport * as i18n from '../../../core/i18n/i18n.js';\nimport * as ThirdPartyWeb from '../../../third_party/third-party-web/third-party-web.js';\nimport * as Extras from '../extras/extras.js';\nimport * as Handlers from '../handlers/handlers.js';\nimport type * as Types from '../types/types.js';\n\nimport {\n InsightCategory,\n InsightKeys,\n type InsightModel,\n type InsightSetContext,\n type PartialInsightModel,\n} from './types.js';\n\nexport const UIStrings = {\n /** Title of an insight that provides details about the code on a web page that the user doesn't control (referred to as \"third-party code\"). */\n title: '3rd parties',\n /**\n * @description Description of a DevTools insight that identifies the code on the page that the user doesn't control.\n * This is displayed after a user expands the section to see more. No character length limits.\n */\n description: '3rd party code can significantly impact load performance. ' +\n '[Reduce and defer loading of 3rd party code](https://web.dev/articles/optimizing-content-efficiency-loading-third-party-javascript/) to prioritize your page\\'s content.',\n /** Label for a table column that displays the name of a third-party provider. */\n columnThirdParty: '3rd party',\n /** Label for a column in a data table; entries will be the download size of a web resource in kilobytes. */\n columnTransferSize: 'Transfer size',\n /** Label for a table column that displays how much time each row spent running on the main thread, entries will be the number of milliseconds spent. */\n columnMainThreadTime: 'Main thread time',\n /**\n * @description Text block indicating that no third party content was detected on the page\n */\n noThirdParties: 'No third parties found',\n} as const;\n\nconst str_ = i18n.i18n.registerUIStrings('models/trace/insights/ThirdParties.ts', UIStrings);\nexport const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);\n\nexport type ThirdPartiesInsightModel = InsightModel<typeof UIStrings, {\n /** The entity for this navigation's URL. Any other entity is from a third party. */\n entitySummaries: Extras.ThirdParties.EntitySummary[],\n firstPartyEntity?: Extras.ThirdParties.Entity,\n}>;\n\nfunction getRelatedEvents(\n summaries: Extras.ThirdParties.EntitySummary[],\n firstPartyEntity: Extras.ThirdParties.Entity|undefined): Types.Events.Event[] {\n const relatedEvents = [];\n for (const summary of summaries) {\n if (summary.entity !== firstPartyEntity) {\n relatedEvents.push(...summary.relatedEvents);\n }\n }\n\n return relatedEvents;\n}\n\nfunction finalize(partialModel: PartialInsightModel<ThirdPartiesInsightModel>): ThirdPartiesInsightModel {\n return {\n insightKey: InsightKeys.THIRD_PARTIES,\n strings: UIStrings,\n title: i18nString(UIStrings.title),\n description: i18nString(UIStrings.description),\n category: InsightCategory.ALL,\n state: partialModel.entitySummaries.find(summary => summary.entity !== partialModel.firstPartyEntity) ?\n 'informative' :\n 'pass',\n ...partialModel,\n };\n}\n\nexport function generateInsight(\n parsedTrace: Handlers.Types.ParsedTrace, context: InsightSetContext): ThirdPartiesInsightModel {\n const entitySummaries =\n Extras.ThirdParties.summarizeByThirdParty(parsedTrace as Handlers.Types.ParsedTrace, context.bounds);\n\n const firstPartyUrl = context.navigation?.args.data?.documentLoaderURL ?? parsedTrace.Meta.mainFrameURL;\n const firstPartyEntity = ThirdPartyWeb.ThirdPartyWeb.getEntity(firstPartyUrl) ||\n Handlers.Helpers.makeUpEntity(parsedTrace.Renderer.entityMappings.createdEntityCache, firstPartyUrl);\n\n return finalize({\n relatedEvents: getRelatedEvents(entitySummaries, firstPartyEntity),\n firstPartyEntity,\n entitySummaries,\n });\n}\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Platform from '../../../core/platform/platform.js';
|
|
2
|
+
import * as Handlers from '../handlers/handlers.js';
|
|
2
3
|
import type * as Types from '../types/types.js';
|
|
3
4
|
import { type InsightModel, type InsightSetContext } from './types.js';
|
|
4
5
|
export declare const UIStrings: {
|
|
@@ -8,10 +9,15 @@ export declare const UIStrings: {
|
|
|
8
9
|
* @description Text to tell the user how a viewport meta element can improve performance. \xa0 is a non-breaking space
|
|
9
10
|
*/
|
|
10
11
|
readonly description: "Tap interactions may be [delayed by up to 300 ms](https://developer.chrome.com/blog/300ms-tap-delay-gone-away/) if the viewport is not optimized for mobile.";
|
|
12
|
+
/**
|
|
13
|
+
* @description Text for a label describing the portion of an interaction event that was delayed due to a bad mobile viewport.
|
|
14
|
+
*/
|
|
15
|
+
readonly mobileTapDelayLabel: "Mobile tap delay";
|
|
11
16
|
};
|
|
12
|
-
export declare const i18nString: (id: string, values?: Record<string, string> | undefined) => Record<string, string
|
|
17
|
+
export declare const i18nString: (id: string, values?: Record<string, string> | undefined) => {i18nId: string, values: Record<string, string|number>, formattedDefault: string};
|
|
13
18
|
export type ViewportInsightModel = InsightModel<typeof UIStrings, {
|
|
14
19
|
mobileOptimized: boolean | null;
|
|
15
20
|
viewportEvent?: Types.Events.ParseMetaViewport;
|
|
21
|
+
longPointerInteractions?: Types.Events.SyntheticInteractionPair[];
|
|
16
22
|
}>;
|
|
17
23
|
export declare function generateInsight(parsedTrace: Handlers.Types.ParsedTrace, context: InsightSetContext): ViewportInsightModel;
|
|
@@ -2,8 +2,10 @@
|
|
|
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 i18n from '../../../core/i18n/i18n.js';
|
|
5
|
+
import * as Platform from '../../../core/platform/platform.js';
|
|
6
|
+
import * as Handlers from '../handlers/handlers.js';
|
|
5
7
|
import * as Helpers from '../helpers/helpers.js';
|
|
6
|
-
import { InsightCategory, InsightWarning, } from './types.js';
|
|
8
|
+
import { InsightCategory, InsightKeys, InsightWarning, } from './types.js';
|
|
7
9
|
export const UIStrings = {
|
|
8
10
|
/** Title of an insight that provides details about if the page's viewport is optimized for mobile viewing. */
|
|
9
11
|
title: 'Optimize viewport for mobile',
|
|
@@ -11,12 +13,16 @@ export const UIStrings = {
|
|
|
11
13
|
* @description Text to tell the user how a viewport meta element can improve performance. \xa0 is a non-breaking space
|
|
12
14
|
*/
|
|
13
15
|
description: 'Tap interactions may be [delayed by up to 300\xA0ms](https://developer.chrome.com/blog/300ms-tap-delay-gone-away/) if the viewport is not optimized for mobile.',
|
|
16
|
+
/**
|
|
17
|
+
* @description Text for a label describing the portion of an interaction event that was delayed due to a bad mobile viewport.
|
|
18
|
+
*/
|
|
19
|
+
mobileTapDelayLabel: 'Mobile tap delay',
|
|
14
20
|
};
|
|
15
21
|
// const str_ = i18n.i18n.registerUIStrings('models/trace/insights/Viewport.ts', UIStrings);
|
|
16
22
|
export const i18nString = (i18nId, values) => ({i18nId, values}); // i18n.i18n.getLocalizedString.bind(undefined, str_);
|
|
17
23
|
function finalize(partialModel) {
|
|
18
24
|
return {
|
|
19
|
-
insightKey:
|
|
25
|
+
insightKey: InsightKeys.VIEWPORT,
|
|
20
26
|
strings: UIStrings,
|
|
21
27
|
title: i18nString(UIStrings.title),
|
|
22
28
|
description: i18nString(UIStrings.description),
|
|
@@ -53,10 +59,19 @@ export function generateInsight(parsedTrace, context) {
|
|
|
53
59
|
// Returns true only if all events are mobile optimized.
|
|
54
60
|
for (const event of compositorEvents) {
|
|
55
61
|
if (!event.args.is_mobile_optimized) {
|
|
62
|
+
// Grab all the pointer events with at least 50ms of input delay.
|
|
63
|
+
const longPointerInteractions = [...parsedTrace.UserInteractions.interactionsOverThreshold.values()].filter(interaction => Handlers.ModelHandlers.UserInteractions.categoryOfInteraction(interaction) === 'POINTER' &&
|
|
64
|
+
interaction.inputDelay >= 50_000);
|
|
65
|
+
// The actual impact varies between 0 and 300.
|
|
66
|
+
// Using inputDelay as the closest thing we have for measuring this, though inputDelay may be high for other reasons.
|
|
67
|
+
// b/371566378#comment8
|
|
68
|
+
const inputDelay = Math.max(0, ...longPointerInteractions.map(interaction => interaction.inputDelay)) / 1000;
|
|
69
|
+
const inpMetricSavings = Platform.NumberUtilities.clamp(inputDelay, 0, 300);
|
|
56
70
|
return finalize({
|
|
57
71
|
mobileOptimized: false,
|
|
58
72
|
viewportEvent,
|
|
59
|
-
|
|
73
|
+
longPointerInteractions,
|
|
74
|
+
metricSavings: { INP: inpMetricSavings },
|
|
60
75
|
});
|
|
61
76
|
}
|
|
62
77
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Viewport.js","sourceRoot":"","sources":["../../../../../../../front_end/models/trace/insights/Viewport.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,yEAAyE;AACzE,6BAA6B;AAE7B,OAAO,KAAK,IAAI,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"Viewport.js","sourceRoot":"","sources":["../../../../../../../front_end/models/trace/insights/Viewport.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 * as Handlers from '../handlers/handlers.js';\nimport * as Helpers from '../helpers/helpers.js';\nimport type * as Types from '../types/types.js';\n\nimport {\n InsightCategory,\n InsightKeys,\n type InsightModel,\n type InsightSetContext,\n InsightWarning,\n type PartialInsightModel,\n} from './types.js';\n\nexport const UIStrings = {\n /** Title of an insight that provides details about if the page's viewport is optimized for mobile viewing. */\n title: 'Optimize viewport for mobile',\n /**\n * @description Text to tell the user how a viewport meta element can improve performance. \\xa0 is a non-breaking space\n */\n description:\n 'Tap interactions may be [delayed by up to 300\\xA0ms](https://developer.chrome.com/blog/300ms-tap-delay-gone-away/) if the viewport is not optimized for mobile.',\n /**\n * @description Text for a label describing the portion of an interaction event that was delayed due to a bad mobile viewport.\n */\n mobileTapDelayLabel: 'Mobile tap delay',\n} as const;\n\nconst str_ = i18n.i18n.registerUIStrings('models/trace/insights/Viewport.ts', UIStrings);\nexport const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);\n\nexport type ViewportInsightModel = InsightModel<typeof UIStrings, {\n mobileOptimized: boolean | null,\n viewportEvent?: Types.Events.ParseMetaViewport,\n longPointerInteractions?: Types.Events.SyntheticInteractionPair[],\n}>;\n\nfunction finalize(partialModel: PartialInsightModel<ViewportInsightModel>): ViewportInsightModel {\n return {\n insightKey: InsightKeys.VIEWPORT,\n strings: UIStrings,\n title: i18nString(UIStrings.title),\n description: i18nString(UIStrings.description),\n category: InsightCategory.INP,\n state: partialModel.mobileOptimized === false ? 'fail' : 'pass',\n ...partialModel,\n };\n}\n\nexport function generateInsight(\n parsedTrace: Handlers.Types.ParsedTrace, context: InsightSetContext): ViewportInsightModel {\n const viewportEvent = parsedTrace.UserInteractions.parseMetaViewportEvents.find(event => {\n if (event.args.data.frame !== context.frameId) {\n return false;\n }\n\n return Helpers.Timing.eventIsInBounds(event, context.bounds);\n });\n\n const compositorEvents = parsedTrace.UserInteractions.beginCommitCompositorFrameEvents.filter(event => {\n if (event.args.frame !== context.frameId) {\n return false;\n }\n\n // Commit compositor frame events can be emitted before the viewport tag is parsed.\n // We shouldn't count these since the browser hasn't had time to make the viewport mobile optimized.\n if (viewportEvent && event.ts < viewportEvent.ts) {\n return false;\n }\n\n return Helpers.Timing.eventIsInBounds(event, context.bounds);\n });\n\n if (!compositorEvents.length) {\n // Trace doesn't have the data we need.\n return finalize({\n mobileOptimized: null,\n warnings: [InsightWarning.NO_LAYOUT],\n });\n }\n\n // Returns true only if all events are mobile optimized.\n for (const event of compositorEvents) {\n if (!event.args.is_mobile_optimized) {\n // Grab all the pointer events with at least 50ms of input delay.\n const longPointerInteractions = [...parsedTrace.UserInteractions.interactionsOverThreshold.values()].filter(\n interaction => Handlers.ModelHandlers.UserInteractions.categoryOfInteraction(interaction) === 'POINTER' &&\n interaction.inputDelay >= 50_000);\n\n // The actual impact varies between 0 and 300.\n // Using inputDelay as the closest thing we have for measuring this, though inputDelay may be high for other reasons.\n // b/371566378#comment8\n const inputDelay = Math.max(0, ...longPointerInteractions.map(interaction => interaction.inputDelay)) / 1000;\n const inpMetricSavings = Platform.NumberUtilities.clamp(inputDelay, 0, 300);\n\n return finalize({\n mobileOptimized: false,\n viewportEvent,\n longPointerInteractions,\n metricSavings: {INP: inpMetricSavings as Types.Timing.Milli},\n });\n }\n }\n\n return finalize({\n mobileOptimized: true,\n viewportEvent,\n });\n}\n"]}
|
|
@@ -106,7 +106,7 @@ export type InsightModels = {
|
|
|
106
106
|
* navigation to map it to. In this case `Types.Events.NO_NAVIGATION` is used for the key.
|
|
107
107
|
*/
|
|
108
108
|
export type TraceInsightSets = Map<Types.Events.NavigationId, InsightSet>;
|
|
109
|
-
export declare
|
|
109
|
+
export declare enum InsightKeys {
|
|
110
110
|
LCP_PHASES = "LCPPhases",
|
|
111
111
|
INTERACTION_TO_NEXT_PAINT = "InteractionToNextPaint",
|
|
112
112
|
CLS_CULPRITS = "CLSCulprits",
|
|
@@ -16,4 +16,23 @@ export var InsightCategory;
|
|
|
16
16
|
InsightCategory["LCP"] = "LCP";
|
|
17
17
|
InsightCategory["CLS"] = "CLS";
|
|
18
18
|
})(InsightCategory || (InsightCategory = {}));
|
|
19
|
+
export var InsightKeys;
|
|
20
|
+
(function (InsightKeys) {
|
|
21
|
+
InsightKeys["LCP_PHASES"] = "LCPPhases";
|
|
22
|
+
InsightKeys["INTERACTION_TO_NEXT_PAINT"] = "InteractionToNextPaint";
|
|
23
|
+
InsightKeys["CLS_CULPRITS"] = "CLSCulprits";
|
|
24
|
+
InsightKeys["THIRD_PARTIES"] = "ThirdParties";
|
|
25
|
+
InsightKeys["DOCUMENT_LATENCY"] = "DocumentLatency";
|
|
26
|
+
InsightKeys["DOM_SIZE"] = "DOMSize";
|
|
27
|
+
InsightKeys["DUPLICATE_JAVASCRIPT"] = "DuplicatedJavaScript";
|
|
28
|
+
InsightKeys["FONT_DISPLAY"] = "FontDisplay";
|
|
29
|
+
InsightKeys["FORCED_REFLOW"] = "ForcedReflow";
|
|
30
|
+
InsightKeys["IMAGE_DELIVERY"] = "ImageDelivery";
|
|
31
|
+
InsightKeys["LCP_DISCOVERY"] = "LCPDiscovery";
|
|
32
|
+
InsightKeys["LEGACY_JAVASCRIPT"] = "LegacyJavaScript";
|
|
33
|
+
InsightKeys["NETWORK_DEPENDENCY_TREE"] = "NetworkDependencyTree";
|
|
34
|
+
InsightKeys["RENDER_BLOCKING"] = "RenderBlocking";
|
|
35
|
+
InsightKeys["SLOW_CSS_SELECTOR"] = "SlowCSSSelector";
|
|
36
|
+
InsightKeys["VIEWPORT"] = "Viewport";
|
|
37
|
+
})(InsightKeys || (InsightKeys = {}));
|
|
19
38
|
//# sourceMappingURL=types.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../../../../front_end/models/trace/insights/types.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,yEAAyE;AACzE,6BAA6B;AAoC7B,MAAM,CAAN,IAAY,cAMX;AAND,WAAY,cAAc;IACxB,iCAAe,CAAA;IACf,mCAAiB,CAAA;IACjB,uEAAuE;IACvE,6DAA2C,CAAA;IAC3C,yCAAuB,CAAA;AACzB,CAAC,EANW,cAAc,KAAd,cAAc,QAMzB;AAYD,MAAM,CAAN,IAAY,eAKX;AALD,WAAY,eAAe;IACzB,8BAAW,CAAA;IACX,8BAAW,CAAA;IACX,8BAAW,CAAA;IACX,8BAAW,CAAA;AACb,CAAC,EALW,eAAe,KAAf,eAAe,QAK1B","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 Common from '../../../core/common/common.js';\nimport type * as Lantern from '../lantern/lantern.js';\nimport type * as Types from '../types/types.js';\n\nimport type * as Models from './Models.js';\n\n/**\n * Context for the portion of the trace an insight should look at.\n */\nexport type InsightSetContext = InsightSetContextWithoutNavigation|InsightSetContextWithNavigation;\n\nexport interface InsightSetContextWithoutNavigation {\n bounds: Types.Timing.TraceWindowMicro;\n frameId: string;\n navigation?: never;\n}\n\nexport interface InsightSetContextWithNavigation {\n bounds: Types.Timing.TraceWindowMicro;\n frameId: string;\n navigation: Types.Events.NavigationStart;\n navigationId: string;\n lantern?: LanternContext;\n}\n\nexport interface LanternContext {\n requests: Array<Lantern.Types.NetworkRequest<Types.Events.SyntheticNetworkRequest>>;\n graph: Lantern.Graph.Node<Types.Events.SyntheticNetworkRequest>;\n simulator: Lantern.Simulation.Simulator<Types.Events.SyntheticNetworkRequest>;\n metrics: Record<string, Lantern.Metrics.MetricResult>;\n}\n\nexport type InsightModelsType = typeof Models;\n\nexport enum InsightWarning {\n NO_FP = 'NO_FP',\n NO_LCP = 'NO_LCP',\n // No network request could be identified as the primary HTML document.\n NO_DOCUMENT_REQUEST = 'NO_DOCUMENT_REQUEST',\n NO_LAYOUT = 'NO_LAYOUT',\n}\n\nexport interface MetricSavings {\n /* eslint-disable @typescript-eslint/naming-convention */\n FCP?: Types.Timing.Milli;\n LCP?: Types.Timing.Milli;\n TBT?: Types.Timing.Milli;\n CLS?: number;\n INP?: Types.Timing.Milli;\n /* eslint-enable @typescript-eslint/naming-convention */\n}\n\nexport enum InsightCategory {\n ALL = 'All',\n INP = 'INP',\n LCP = 'LCP',\n CLS = 'CLS',\n}\n\nexport type RelatedEventsMap = Map<Types.Events.Event, string[]>;\n\nexport type Checklist<Keys extends string> = Record<Keys, {label: Common.UIString.LocalizedString, value: boolean}>;\n\nexport type InsightModel<UIStrings extends Record<string, string> = Record<string, string>,\n ExtraDetail extends Record<string, unknown> = Record<string, unknown>> =\n ExtraDetail&{\n /** Used internally to identify the type of a model, not shown visibly to users **/\n insightKey: keyof InsightModelsType,\n /** Not used within DevTools - this is for external consumers (like Lighthouse). */\n strings: UIStrings,\n title: Common.UIString.LocalizedString,\n description: Common.UIString.LocalizedString,\n category: InsightCategory,\n state: 'pass' | 'fail' | 'informative',\n /** Used by RelatedInsightChips.ts */\n relatedEvents?: RelatedEventsMap | Types.Events.Event[],\n warnings?: InsightWarning[],\n metricSavings?: MetricSavings,\n /**\n * An estimate for the number of bytes that this insight deems to have been wasted.\n * Bytes are in terms of transfer size: for each component of savings related to an\n * individual request, the insight will estimate its impact on transfer size by using\n * the compression ratio of the resource.\n *\n * This field is only displayed for informational purposes.\n */\n wastedBytes?: number,\n frameId?: string,\n /**\n * If this insight is attached to a navigation, this stores its ID.\n */\n navigationId?: string,\n };\n\nexport type PartialInsightModel<T> =\n Omit<T, 'strings'|'title'|'description'|'category'|'state'|'insightKey'|'navigationId'|'frameId'>;\n\n/**\n * Contains insights for a specific navigation. If a trace began after a navigation already started,\n * this could instead represent the duration from the beginning of the trace up to the first recorded\n * navigation (or the end of the trace).\n */\nexport interface InsightSet {\n /** If for a navigation, this is the navigationId. Else it is Trace.Types.Events.NO_NAVIGATION. */\n id: Types.Events.NavigationId;\n /** The URL to show in the accordion list. */\n url: URL;\n frameId: string;\n bounds: Types.Timing.TraceWindowMicro;\n model: InsightModels;\n navigation?: Types.Events.NavigationStart;\n}\n\n/**\n * Contains insights for a specific insight set.\n */\nexport type InsightModels = {\n [I in keyof InsightModelsType]: ReturnType<InsightModelsType[I]['generateInsight']>;\n};\n\n/**\n * Contains insights for the entire trace. Insights are mostly grouped by `navigationId`, with one exception:\n *\n * If the analyzed trace started after the navigation, and has meaningful work with that span, there is no\n * navigation to map it to. In this case `Types.Events.NO_NAVIGATION` is used for the key.\n */\nexport type TraceInsightSets = Map<Types.Events.NavigationId, InsightSet>;\n\nexport
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../../../../front_end/models/trace/insights/types.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,yEAAyE;AACzE,6BAA6B;AAoC7B,MAAM,CAAN,IAAY,cAMX;AAND,WAAY,cAAc;IACxB,iCAAe,CAAA;IACf,mCAAiB,CAAA;IACjB,uEAAuE;IACvE,6DAA2C,CAAA;IAC3C,yCAAuB,CAAA;AACzB,CAAC,EANW,cAAc,KAAd,cAAc,QAMzB;AAYD,MAAM,CAAN,IAAY,eAKX;AALD,WAAY,eAAe;IACzB,8BAAW,CAAA;IACX,8BAAW,CAAA;IACX,8BAAW,CAAA;IACX,8BAAW,CAAA;AACb,CAAC,EALW,eAAe,KAAf,eAAe,QAK1B;AAuED,MAAM,CAAN,IAAY,WAiBX;AAjBD,WAAY,WAAW;IACrB,uCAAwB,CAAA;IACxB,mEAAoD,CAAA;IACpD,2CAA4B,CAAA;IAC5B,6CAA8B,CAAA;IAC9B,mDAAoC,CAAA;IACpC,mCAAoB,CAAA;IACpB,4DAA6C,CAAA;IAC7C,2CAA4B,CAAA;IAC5B,6CAA8B,CAAA;IAC9B,+CAAgC,CAAA;IAChC,6CAA8B,CAAA;IAC9B,qDAAsC,CAAA;IACtC,gEAAiD,CAAA;IACjD,iDAAkC,CAAA;IAClC,oDAAqC,CAAA;IACrC,oCAAqB,CAAA;AACvB,CAAC,EAjBW,WAAW,KAAX,WAAW,QAiBtB","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 Common from '../../../core/common/common.js';\nimport type * as Lantern from '../lantern/lantern.js';\nimport type * as Types from '../types/types.js';\n\nimport type * as Models from './Models.js';\n\n/**\n * Context for the portion of the trace an insight should look at.\n */\nexport type InsightSetContext = InsightSetContextWithoutNavigation|InsightSetContextWithNavigation;\n\nexport interface InsightSetContextWithoutNavigation {\n bounds: Types.Timing.TraceWindowMicro;\n frameId: string;\n navigation?: never;\n}\n\nexport interface InsightSetContextWithNavigation {\n bounds: Types.Timing.TraceWindowMicro;\n frameId: string;\n navigation: Types.Events.NavigationStart;\n navigationId: string;\n lantern?: LanternContext;\n}\n\nexport interface LanternContext {\n requests: Array<Lantern.Types.NetworkRequest<Types.Events.SyntheticNetworkRequest>>;\n graph: Lantern.Graph.Node<Types.Events.SyntheticNetworkRequest>;\n simulator: Lantern.Simulation.Simulator<Types.Events.SyntheticNetworkRequest>;\n metrics: Record<string, Lantern.Metrics.MetricResult>;\n}\n\nexport type InsightModelsType = typeof Models;\n\nexport enum InsightWarning {\n NO_FP = 'NO_FP',\n NO_LCP = 'NO_LCP',\n // No network request could be identified as the primary HTML document.\n NO_DOCUMENT_REQUEST = 'NO_DOCUMENT_REQUEST',\n NO_LAYOUT = 'NO_LAYOUT',\n}\n\nexport interface MetricSavings {\n /* eslint-disable @typescript-eslint/naming-convention */\n FCP?: Types.Timing.Milli;\n LCP?: Types.Timing.Milli;\n TBT?: Types.Timing.Milli;\n CLS?: number;\n INP?: Types.Timing.Milli;\n /* eslint-enable @typescript-eslint/naming-convention */\n}\n\nexport enum InsightCategory {\n ALL = 'All',\n INP = 'INP',\n LCP = 'LCP',\n CLS = 'CLS',\n}\n\nexport type RelatedEventsMap = Map<Types.Events.Event, string[]>;\n\nexport type Checklist<Keys extends string> = Record<Keys, {label: Common.UIString.LocalizedString, value: boolean}>;\n\nexport type InsightModel<UIStrings extends Record<string, string> = Record<string, string>,\n ExtraDetail extends Record<string, unknown> = Record<string, unknown>> =\n ExtraDetail&{\n /** Used internally to identify the type of a model, not shown visibly to users **/\n insightKey: keyof InsightModelsType,\n /** Not used within DevTools - this is for external consumers (like Lighthouse). */\n strings: UIStrings,\n title: Common.UIString.LocalizedString,\n description: Common.UIString.LocalizedString,\n category: InsightCategory,\n state: 'pass' | 'fail' | 'informative',\n /** Used by RelatedInsightChips.ts */\n relatedEvents?: RelatedEventsMap | Types.Events.Event[],\n warnings?: InsightWarning[],\n metricSavings?: MetricSavings,\n /**\n * An estimate for the number of bytes that this insight deems to have been wasted.\n * Bytes are in terms of transfer size: for each component of savings related to an\n * individual request, the insight will estimate its impact on transfer size by using\n * the compression ratio of the resource.\n *\n * This field is only displayed for informational purposes.\n */\n wastedBytes?: number,\n frameId?: string,\n /**\n * If this insight is attached to a navigation, this stores its ID.\n */\n navigationId?: string,\n };\n\nexport type PartialInsightModel<T> =\n Omit<T, 'strings'|'title'|'description'|'category'|'state'|'insightKey'|'navigationId'|'frameId'>;\n\n/**\n * Contains insights for a specific navigation. If a trace began after a navigation already started,\n * this could instead represent the duration from the beginning of the trace up to the first recorded\n * navigation (or the end of the trace).\n */\nexport interface InsightSet {\n /** If for a navigation, this is the navigationId. Else it is Trace.Types.Events.NO_NAVIGATION. */\n id: Types.Events.NavigationId;\n /** The URL to show in the accordion list. */\n url: URL;\n frameId: string;\n bounds: Types.Timing.TraceWindowMicro;\n model: InsightModels;\n navigation?: Types.Events.NavigationStart;\n}\n\n/**\n * Contains insights for a specific insight set.\n */\nexport type InsightModels = {\n [I in keyof InsightModelsType]: ReturnType<InsightModelsType[I]['generateInsight']>;\n};\n\n/**\n * Contains insights for the entire trace. Insights are mostly grouped by `navigationId`, with one exception:\n *\n * If the analyzed trace started after the navigation, and has meaningful work with that span, there is no\n * navigation to map it to. In this case `Types.Events.NO_NAVIGATION` is used for the key.\n */\nexport type TraceInsightSets = Map<Types.Events.NavigationId, InsightSet>;\n\nexport enum InsightKeys {\n LCP_PHASES = 'LCPPhases',\n INTERACTION_TO_NEXT_PAINT = 'InteractionToNextPaint',\n CLS_CULPRITS = 'CLSCulprits',\n THIRD_PARTIES = 'ThirdParties',\n DOCUMENT_LATENCY = 'DocumentLatency',\n DOM_SIZE = 'DOMSize',\n DUPLICATE_JAVASCRIPT = 'DuplicatedJavaScript',\n FONT_DISPLAY = 'FontDisplay',\n FORCED_REFLOW = 'ForcedReflow',\n IMAGE_DELIVERY = 'ImageDelivery',\n LCP_DISCOVERY = 'LCPDiscovery',\n LEGACY_JAVASCRIPT = 'LegacyJavaScript',\n NETWORK_DEPENDENCY_TREE = 'NetworkDependencyTree',\n RENDER_BLOCKING = 'RenderBlocking',\n SLOW_CSS_SELECTOR = 'SlowCSSSelector',\n VIEWPORT = 'Viewport',\n}\n"]}
|
|
@@ -87,9 +87,12 @@ declare class BaseNode<T = Lantern.AnyNetworkObject> {
|
|
|
87
87
|
traversalPath: Node[];
|
|
88
88
|
}, void, unknown>;
|
|
89
89
|
/**
|
|
90
|
-
*
|
|
90
|
+
* If the given node has a cycle, returns a path representing that cycle.
|
|
91
|
+
* Else returns null.
|
|
92
|
+
*
|
|
93
|
+
* Does a DFS on in its dependent graph.
|
|
91
94
|
*/
|
|
92
|
-
static
|
|
95
|
+
static findCycle(node: Node, direction?: 'dependents' | 'dependencies' | 'both'): BaseNode[] | null;
|
|
93
96
|
canDependOn(node: Node): boolean;
|
|
94
97
|
}
|
|
95
98
|
export { BaseNode };
|
|
@@ -222,12 +222,15 @@ class BaseNode {
|
|
|
222
222
|
}
|
|
223
223
|
}
|
|
224
224
|
/**
|
|
225
|
-
*
|
|
225
|
+
* If the given node has a cycle, returns a path representing that cycle.
|
|
226
|
+
* Else returns null.
|
|
227
|
+
*
|
|
228
|
+
* Does a DFS on in its dependent graph.
|
|
226
229
|
*/
|
|
227
|
-
static
|
|
230
|
+
static findCycle(node, direction = 'both') {
|
|
228
231
|
// Checking 'both' is the default entrypoint to recursively check both directions
|
|
229
232
|
if (direction === 'both') {
|
|
230
|
-
return BaseNode.
|
|
233
|
+
return BaseNode.findCycle(node, 'dependents') || BaseNode.findCycle(node, 'dependencies');
|
|
231
234
|
}
|
|
232
235
|
const visited = new Set();
|
|
233
236
|
const currentPath = [];
|
|
@@ -240,7 +243,7 @@ class BaseNode {
|
|
|
240
243
|
const currentNode = toVisit.pop();
|
|
241
244
|
// We've hit a cycle if the node we're visiting is in our current dependency path
|
|
242
245
|
if (currentPath.includes(currentNode)) {
|
|
243
|
-
return
|
|
246
|
+
return currentPath;
|
|
244
247
|
}
|
|
245
248
|
// If we've already visited the node, no need to revisit it
|
|
246
249
|
if (visited.has(currentNode)) {
|
|
@@ -264,7 +267,7 @@ class BaseNode {
|
|
|
264
267
|
depthAdded.set(nextNode, currentPath.length);
|
|
265
268
|
}
|
|
266
269
|
}
|
|
267
|
-
return
|
|
270
|
+
return null;
|
|
268
271
|
}
|
|
269
272
|
canDependOn(node) {
|
|
270
273
|
return node.startTime <= this.startTime;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BaseNode.js","sourceRoot":"","sources":["../../../../../../../../front_end/models/trace/lantern/graph/BaseNode.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 Core from '../core/core.js';\nimport type * as Lantern from '../types/types.js';\n\nimport type {CPUNode} from './CPUNode.js';\nimport type {NetworkNode} from './NetworkNode.js';\n\n/**\n * A union of all types derived from BaseNode, allowing type check discrimination\n * based on `node.type`. If a new node type is created, it should be added here.\n */\nexport type Node<T = Lantern.AnyNetworkObject> = CPUNode<T>|NetworkNode<T>;\n\n/**\n * @fileoverview This class encapsulates logic for handling resources and tasks used to model the\n * execution dependency graph of the page. A node has a unique identifier and can depend on other\n * nodes/be depended on. The construction of the graph maintains some important invariants that are\n * inherent to the model:\n *\n * 1. The graph is a DAG, there are no cycles.\n * 2. There is always a root node upon which all other nodes eventually depend.\n *\n * This allows particular optimizations in this class so that we do no need to check for cycles as\n * these methods are called and we can always start traversal at the root node.\n */\n\nclass BaseNode<T = Lantern.AnyNetworkObject> {\n static types = {\n NETWORK: 'network',\n CPU: 'cpu',\n } as const;\n\n _id: string;\n _isMainDocument: boolean;\n dependents: Node[];\n dependencies: Node[];\n\n constructor(id: string) {\n this._id = id;\n this._isMainDocument = false;\n this.dependents = [];\n this.dependencies = [];\n }\n\n get id(): string {\n return this._id;\n }\n\n get type(): 'network'|'cpu' {\n throw new Core.LanternError('Unimplemented');\n }\n\n /**\n * In microseconds\n */\n get startTime(): number {\n throw new Core.LanternError('Unimplemented');\n }\n\n /**\n * In microseconds\n */\n get endTime(): number {\n throw new Core.LanternError('Unimplemented');\n }\n\n setIsMainDocument(value: boolean): void {\n this._isMainDocument = value;\n }\n\n isMainDocument(): boolean {\n return this._isMainDocument;\n }\n\n getDependents(): Node[] {\n return this.dependents.slice();\n }\n\n getNumberOfDependents(): number {\n return this.dependents.length;\n }\n\n getDependencies(): Node[] {\n return this.dependencies.slice();\n }\n\n getNumberOfDependencies(): number {\n return this.dependencies.length;\n }\n\n getRootNode(): Node<T> {\n let rootNode = this as BaseNode as Node;\n while (rootNode.dependencies.length) {\n rootNode = rootNode.dependencies[0];\n }\n\n return rootNode;\n }\n\n addDependent(node: Node): void {\n node.addDependency(this as BaseNode as Node);\n }\n\n addDependency(node: Node): void {\n // @ts-expect-error - in checkJs, ts doesn't know that CPUNode and NetworkNode *are* BaseNodes.\n if (node === this) {\n throw new Core.LanternError('Cannot add dependency on itself');\n }\n\n if (this.dependencies.includes(node)) {\n return;\n }\n\n node.dependents.push(this as BaseNode as Node);\n this.dependencies.push(node);\n }\n\n removeDependent(node: Node): void {\n node.removeDependency(this as BaseNode as Node);\n }\n\n removeDependency(node: Node): void {\n if (!this.dependencies.includes(node)) {\n return;\n }\n\n const thisIndex = node.dependents.indexOf(this as BaseNode as Node);\n node.dependents.splice(thisIndex, 1);\n this.dependencies.splice(this.dependencies.indexOf(node), 1);\n }\n\n // Unused in devtools, but used in LH.\n removeAllDependencies(): void {\n for (const node of this.dependencies.slice()) {\n this.removeDependency(node);\n }\n }\n\n /**\n * Computes whether the given node is anywhere in the dependency graph of this node.\n * While this method can prevent cycles, it walks the graph and should be used sparingly.\n * Nodes are always considered dependent on themselves for the purposes of cycle detection.\n */\n isDependentOn(node: BaseNode<T>): boolean {\n let isDependentOnNode = false;\n this.traverse(\n currentNode => {\n if (isDependentOnNode) {\n return;\n }\n isDependentOnNode = currentNode === node;\n },\n currentNode => {\n // If we've already found the dependency, don't traverse further.\n if (isDependentOnNode) {\n return [];\n }\n // Otherwise, traverse the dependencies.\n return currentNode.getDependencies();\n });\n\n return isDependentOnNode;\n }\n\n /**\n * Clones the node's information without adding any dependencies/dependents.\n */\n cloneWithoutRelationships(): Node<T> {\n const node = new BaseNode(this.id) as Node<T>;\n node.setIsMainDocument(this._isMainDocument);\n return node;\n }\n\n /**\n * Clones the entire graph connected to this node filtered by the optional predicate. If a node is\n * included by the predicate, all nodes along the paths between the node and the root will be included. If the\n * node this was called on is not included in the resulting filtered graph, the method will throw.\n *\n * This does not clone NetworkNode's `record` or `rawRecord` fields. It may be reasonable to clone the former,\n * to assist in graph construction, but the latter should never be cloned as one constraint of Lantern is that\n * the underlying data records are accessible for plain object reference equality checks.\n */\n cloneWithRelationships(predicate?: (arg0: Node) => boolean): Node {\n const rootNode = this.getRootNode();\n\n const idsToIncludedClones = new Map<string, Node>();\n\n // Walk down dependents.\n rootNode.traverse(node => {\n if (idsToIncludedClones.has(node.id)) {\n return;\n }\n\n if (predicate === undefined) {\n // No condition for entry, so clone every node.\n idsToIncludedClones.set(node.id, node.cloneWithoutRelationships());\n return;\n }\n\n if (predicate(node)) {\n // Node included, so walk back up dependencies, cloning nodes from here back to the root.\n node.traverse(\n node => idsToIncludedClones.set(node.id, node.cloneWithoutRelationships()),\n // Dependencies already cloned have already cloned ancestors, so no need to visit again.\n node => node.dependencies.filter(parent => !idsToIncludedClones.has(parent.id)),\n );\n }\n });\n\n // Copy dependencies between nodes.\n rootNode.traverse(originalNode => {\n const clonedNode = idsToIncludedClones.get(originalNode.id);\n if (!clonedNode) {\n return;\n }\n\n for (const dependency of originalNode.dependencies) {\n const clonedDependency = idsToIncludedClones.get(dependency.id);\n if (!clonedDependency) {\n throw new Core.LanternError('Dependency somehow not cloned');\n }\n clonedNode.addDependency(clonedDependency);\n }\n });\n\n const clonedThisNode = idsToIncludedClones.get(this.id);\n if (!clonedThisNode) {\n throw new Core.LanternError('Cloned graph missing node');\n }\n return clonedThisNode;\n }\n\n /**\n * Traverses all connected nodes in BFS order, calling `callback` exactly once\n * on each. `traversalPath` is the shortest (though not necessarily unique)\n * path from `node` to the root of the iteration.\n *\n * The `getNextNodes` function takes a visited node and returns which nodes to\n * visit next. It defaults to returning the node's dependents.\n */\n traverse(\n callback: (node: Node<T>, traversalPath: Array<Node<T>>) => void,\n getNextNodes?: (arg0: Node<T>) => Array<Node<T>>): void {\n for (const {node, traversalPath} of this.traverseGenerator(getNextNodes)) {\n callback(node, traversalPath);\n }\n }\n\n /**\n * @see BaseNode.traverse\n */\n // clang-format off\n *traverseGenerator(getNextNodes?: (arg0: Node) => Node[]):\n Generator<{node: Node, traversalPath: Node[]}, void, unknown> {\n // clang-format on\n if (!getNextNodes) {\n getNextNodes = node => node.getDependents();\n }\n\n // @ts-expect-error - only traverses graphs of Node, so force tsc to treat `this` as one\n const queue: Node[][] = [[this]];\n const visited = new Set([this.id]);\n\n while (queue.length) {\n // @ts-expect-error - queue has length so it's guaranteed to have an item\n const traversalPath: Node[] = queue.shift();\n const node = traversalPath[0];\n yield {node, traversalPath};\n\n for (const nextNode of getNextNodes(node)) {\n if (visited.has(nextNode.id)) {\n continue;\n }\n visited.add(nextNode.id);\n\n queue.push([nextNode, ...traversalPath]);\n }\n }\n }\n\n /**\n * Returns whether the given node has a cycle in its dependent graph by performing a DFS.\n */\n static hasCycle(node: Node, direction: 'dependents'|'dependencies'|'both' = 'both'): boolean {\n // Checking 'both' is the default entrypoint to recursively check both directions\n if (direction === 'both') {\n return BaseNode.hasCycle(node, 'dependents') || BaseNode.hasCycle(node, 'dependencies');\n }\n\n const visited = new Set();\n const currentPath: BaseNode[] = [];\n const toVisit = [node];\n const depthAdded = new Map([[node, 0]]);\n\n // Keep going while we have nodes to visit in the stack\n while (toVisit.length) {\n // Get the last node in the stack (DFS uses stack, not queue)\n // @ts-expect-error - toVisit has length so it's guaranteed to have an item\n const currentNode: BaseNode = toVisit.pop();\n\n // We've hit a cycle if the node we're visiting is in our current dependency path\n if (currentPath.includes(currentNode)) {\n return true;\n }\n // If we've already visited the node, no need to revisit it\n if (visited.has(currentNode)) {\n continue;\n }\n\n // Since we're visiting this node, clear out any nodes in our path that we had to backtrack\n // @ts-expect-error\n while (currentPath.length > depthAdded.get(currentNode)) {\n currentPath.pop();\n }\n\n // Update our data structures to reflect that we're adding this node to our path\n visited.add(currentNode);\n currentPath.push(currentNode);\n\n // Add all of its dependents to our toVisit stack\n const nodesToExplore = direction === 'dependents' ? currentNode.dependents : currentNode.dependencies;\n for (const nextNode of nodesToExplore) {\n if (toVisit.includes(nextNode)) {\n continue;\n }\n toVisit.push(nextNode);\n depthAdded.set(nextNode, currentPath.length);\n }\n }\n\n return false;\n }\n\n canDependOn(node: Node): boolean {\n return node.startTime <= this.startTime;\n }\n}\n\nexport {BaseNode};\n"]}
|
|
1
|
+
{"version":3,"file":"BaseNode.js","sourceRoot":"","sources":["../../../../../../../../front_end/models/trace/lantern/graph/BaseNode.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 Core from '../core/core.js';\nimport type * as Lantern from '../types/types.js';\n\nimport type {CPUNode} from './CPUNode.js';\nimport type {NetworkNode} from './NetworkNode.js';\n\n/**\n * A union of all types derived from BaseNode, allowing type check discrimination\n * based on `node.type`. If a new node type is created, it should be added here.\n */\nexport type Node<T = Lantern.AnyNetworkObject> = CPUNode<T>|NetworkNode<T>;\n\n/**\n * @fileoverview This class encapsulates logic for handling resources and tasks used to model the\n * execution dependency graph of the page. A node has a unique identifier and can depend on other\n * nodes/be depended on. The construction of the graph maintains some important invariants that are\n * inherent to the model:\n *\n * 1. The graph is a DAG, there are no cycles.\n * 2. There is always a root node upon which all other nodes eventually depend.\n *\n * This allows particular optimizations in this class so that we do no need to check for cycles as\n * these methods are called and we can always start traversal at the root node.\n */\n\nclass BaseNode<T = Lantern.AnyNetworkObject> {\n static types = {\n NETWORK: 'network',\n CPU: 'cpu',\n } as const;\n\n _id: string;\n _isMainDocument: boolean;\n dependents: Node[];\n dependencies: Node[];\n\n constructor(id: string) {\n this._id = id;\n this._isMainDocument = false;\n this.dependents = [];\n this.dependencies = [];\n }\n\n get id(): string {\n return this._id;\n }\n\n get type(): 'network'|'cpu' {\n throw new Core.LanternError('Unimplemented');\n }\n\n /**\n * In microseconds\n */\n get startTime(): number {\n throw new Core.LanternError('Unimplemented');\n }\n\n /**\n * In microseconds\n */\n get endTime(): number {\n throw new Core.LanternError('Unimplemented');\n }\n\n setIsMainDocument(value: boolean): void {\n this._isMainDocument = value;\n }\n\n isMainDocument(): boolean {\n return this._isMainDocument;\n }\n\n getDependents(): Node[] {\n return this.dependents.slice();\n }\n\n getNumberOfDependents(): number {\n return this.dependents.length;\n }\n\n getDependencies(): Node[] {\n return this.dependencies.slice();\n }\n\n getNumberOfDependencies(): number {\n return this.dependencies.length;\n }\n\n getRootNode(): Node<T> {\n let rootNode = this as BaseNode as Node;\n while (rootNode.dependencies.length) {\n rootNode = rootNode.dependencies[0];\n }\n\n return rootNode;\n }\n\n addDependent(node: Node): void {\n node.addDependency(this as BaseNode as Node);\n }\n\n addDependency(node: Node): void {\n // @ts-expect-error - in checkJs, ts doesn't know that CPUNode and NetworkNode *are* BaseNodes.\n if (node === this) {\n throw new Core.LanternError('Cannot add dependency on itself');\n }\n\n if (this.dependencies.includes(node)) {\n return;\n }\n\n node.dependents.push(this as BaseNode as Node);\n this.dependencies.push(node);\n }\n\n removeDependent(node: Node): void {\n node.removeDependency(this as BaseNode as Node);\n }\n\n removeDependency(node: Node): void {\n if (!this.dependencies.includes(node)) {\n return;\n }\n\n const thisIndex = node.dependents.indexOf(this as BaseNode as Node);\n node.dependents.splice(thisIndex, 1);\n this.dependencies.splice(this.dependencies.indexOf(node), 1);\n }\n\n // Unused in devtools, but used in LH.\n removeAllDependencies(): void {\n for (const node of this.dependencies.slice()) {\n this.removeDependency(node);\n }\n }\n\n /**\n * Computes whether the given node is anywhere in the dependency graph of this node.\n * While this method can prevent cycles, it walks the graph and should be used sparingly.\n * Nodes are always considered dependent on themselves for the purposes of cycle detection.\n */\n isDependentOn(node: BaseNode<T>): boolean {\n let isDependentOnNode = false;\n this.traverse(\n currentNode => {\n if (isDependentOnNode) {\n return;\n }\n isDependentOnNode = currentNode === node;\n },\n currentNode => {\n // If we've already found the dependency, don't traverse further.\n if (isDependentOnNode) {\n return [];\n }\n // Otherwise, traverse the dependencies.\n return currentNode.getDependencies();\n });\n\n return isDependentOnNode;\n }\n\n /**\n * Clones the node's information without adding any dependencies/dependents.\n */\n cloneWithoutRelationships(): Node<T> {\n const node = new BaseNode(this.id) as Node<T>;\n node.setIsMainDocument(this._isMainDocument);\n return node;\n }\n\n /**\n * Clones the entire graph connected to this node filtered by the optional predicate. If a node is\n * included by the predicate, all nodes along the paths between the node and the root will be included. If the\n * node this was called on is not included in the resulting filtered graph, the method will throw.\n *\n * This does not clone NetworkNode's `record` or `rawRecord` fields. It may be reasonable to clone the former,\n * to assist in graph construction, but the latter should never be cloned as one constraint of Lantern is that\n * the underlying data records are accessible for plain object reference equality checks.\n */\n cloneWithRelationships(predicate?: (arg0: Node) => boolean): Node {\n const rootNode = this.getRootNode();\n\n const idsToIncludedClones = new Map<string, Node>();\n\n // Walk down dependents.\n rootNode.traverse(node => {\n if (idsToIncludedClones.has(node.id)) {\n return;\n }\n\n if (predicate === undefined) {\n // No condition for entry, so clone every node.\n idsToIncludedClones.set(node.id, node.cloneWithoutRelationships());\n return;\n }\n\n if (predicate(node)) {\n // Node included, so walk back up dependencies, cloning nodes from here back to the root.\n node.traverse(\n node => idsToIncludedClones.set(node.id, node.cloneWithoutRelationships()),\n // Dependencies already cloned have already cloned ancestors, so no need to visit again.\n node => node.dependencies.filter(parent => !idsToIncludedClones.has(parent.id)),\n );\n }\n });\n\n // Copy dependencies between nodes.\n rootNode.traverse(originalNode => {\n const clonedNode = idsToIncludedClones.get(originalNode.id);\n if (!clonedNode) {\n return;\n }\n\n for (const dependency of originalNode.dependencies) {\n const clonedDependency = idsToIncludedClones.get(dependency.id);\n if (!clonedDependency) {\n throw new Core.LanternError('Dependency somehow not cloned');\n }\n clonedNode.addDependency(clonedDependency);\n }\n });\n\n const clonedThisNode = idsToIncludedClones.get(this.id);\n if (!clonedThisNode) {\n throw new Core.LanternError('Cloned graph missing node');\n }\n return clonedThisNode;\n }\n\n /**\n * Traverses all connected nodes in BFS order, calling `callback` exactly once\n * on each. `traversalPath` is the shortest (though not necessarily unique)\n * path from `node` to the root of the iteration.\n *\n * The `getNextNodes` function takes a visited node and returns which nodes to\n * visit next. It defaults to returning the node's dependents.\n */\n traverse(\n callback: (node: Node<T>, traversalPath: Array<Node<T>>) => void,\n getNextNodes?: (arg0: Node<T>) => Array<Node<T>>): void {\n for (const {node, traversalPath} of this.traverseGenerator(getNextNodes)) {\n callback(node, traversalPath);\n }\n }\n\n /**\n * @see BaseNode.traverse\n */\n // clang-format off\n *traverseGenerator(getNextNodes?: (arg0: Node) => Node[]):\n Generator<{node: Node, traversalPath: Node[]}, void, unknown> {\n // clang-format on\n if (!getNextNodes) {\n getNextNodes = node => node.getDependents();\n }\n\n // @ts-expect-error - only traverses graphs of Node, so force tsc to treat `this` as one\n const queue: Node[][] = [[this]];\n const visited = new Set([this.id]);\n\n while (queue.length) {\n // @ts-expect-error - queue has length so it's guaranteed to have an item\n const traversalPath: Node[] = queue.shift();\n const node = traversalPath[0];\n yield {node, traversalPath};\n\n for (const nextNode of getNextNodes(node)) {\n if (visited.has(nextNode.id)) {\n continue;\n }\n visited.add(nextNode.id);\n\n queue.push([nextNode, ...traversalPath]);\n }\n }\n }\n\n /**\n * If the given node has a cycle, returns a path representing that cycle.\n * Else returns null.\n *\n * Does a DFS on in its dependent graph.\n */\n static findCycle(node: Node, direction: 'dependents'|'dependencies'|'both' = 'both'): BaseNode[]|null {\n // Checking 'both' is the default entrypoint to recursively check both directions\n if (direction === 'both') {\n return BaseNode.findCycle(node, 'dependents') || BaseNode.findCycle(node, 'dependencies');\n }\n\n const visited = new Set();\n const currentPath: BaseNode[] = [];\n const toVisit = [node];\n const depthAdded = new Map([[node, 0]]);\n\n // Keep going while we have nodes to visit in the stack\n while (toVisit.length) {\n // Get the last node in the stack (DFS uses stack, not queue)\n // @ts-expect-error - toVisit has length so it's guaranteed to have an item\n const currentNode: BaseNode = toVisit.pop();\n\n // We've hit a cycle if the node we're visiting is in our current dependency path\n if (currentPath.includes(currentNode)) {\n return currentPath;\n }\n // If we've already visited the node, no need to revisit it\n if (visited.has(currentNode)) {\n continue;\n }\n\n // Since we're visiting this node, clear out any nodes in our path that we had to backtrack\n // @ts-expect-error\n while (currentPath.length > depthAdded.get(currentNode)) {\n currentPath.pop();\n }\n\n // Update our data structures to reflect that we're adding this node to our path\n visited.add(currentNode);\n currentPath.push(currentNode);\n\n // Add all of its dependents to our toVisit stack\n const nodesToExplore = direction === 'dependents' ? currentNode.dependents : currentNode.dependencies;\n for (const nextNode of nodesToExplore) {\n if (toVisit.includes(nextNode)) {\n continue;\n }\n toVisit.push(nextNode);\n depthAdded.set(nextNode, currentPath.length);\n }\n }\n\n return null;\n }\n\n canDependOn(node: Node): boolean {\n return node.startTime <= this.startTime;\n }\n}\n\nexport {BaseNode};\n"]}
|
|
@@ -477,19 +477,34 @@ class PageDependencyGraph {
|
|
|
477
477
|
PageDependencyGraph.linkNetworkNodes(rootNode, networkNodeOutput);
|
|
478
478
|
PageDependencyGraph.linkCPUNodes(rootNode, networkNodeOutput, cpuNodes);
|
|
479
479
|
mainDocumentNode.setIsMainDocument(true);
|
|
480
|
-
if (NetworkNode.
|
|
480
|
+
if (NetworkNode.findCycle(rootNode)) {
|
|
481
|
+
// Uncomment the following if you are debugging cycles.
|
|
482
|
+
// this.printGraph(rootNode);
|
|
481
483
|
throw new Core.LanternError('Invalid dependency graph created, cycle detected');
|
|
482
484
|
}
|
|
483
485
|
return rootNode;
|
|
484
486
|
}
|
|
485
487
|
// Unused, but useful for debugging.
|
|
486
|
-
static printGraph(rootNode, widthInCharacters =
|
|
488
|
+
static printGraph(rootNode, widthInCharacters = 80) {
|
|
487
489
|
function padRight(str, target, padChar = ' ') {
|
|
488
490
|
return str + padChar.repeat(Math.max(target - str.length, 0));
|
|
489
491
|
}
|
|
490
492
|
const nodes = [];
|
|
491
493
|
rootNode.traverse(node => nodes.push(node));
|
|
492
494
|
nodes.sort((a, b) => a.startTime - b.startTime);
|
|
495
|
+
// Assign labels (A, B, C, ..., Z, Z1, Z2, ...) for each node.
|
|
496
|
+
const nodeToLabel = new Map();
|
|
497
|
+
rootNode.traverse(node => {
|
|
498
|
+
const ascii = 65 + nodeToLabel.size;
|
|
499
|
+
let label;
|
|
500
|
+
if (ascii > 90) {
|
|
501
|
+
label = `Z${ascii - 90}`;
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
label = String.fromCharCode(ascii);
|
|
505
|
+
}
|
|
506
|
+
nodeToLabel.set(node, label);
|
|
507
|
+
});
|
|
493
508
|
const min = nodes[0].startTime;
|
|
494
509
|
const max = nodes.reduce((max, node) => Math.max(max, node.endTime), 0);
|
|
495
510
|
const totalTime = max - min;
|
|
@@ -501,8 +516,36 @@ class PageDependencyGraph {
|
|
|
501
516
|
// @ts-expect-error -- disambiguate displayName from across possible Node types.
|
|
502
517
|
const displayName = node.request ? node.request.url : node.type;
|
|
503
518
|
// eslint-disable-next-line
|
|
504
|
-
console.log(padRight(bar, widthInCharacters), `| ${displayName.slice(0,
|
|
519
|
+
console.log(padRight(bar, widthInCharacters), `| ${displayName.slice(0, 50)}`);
|
|
520
|
+
});
|
|
521
|
+
// Print labels for each node.
|
|
522
|
+
// eslint-disable-next-line
|
|
523
|
+
console.log();
|
|
524
|
+
// Print dependencies.
|
|
525
|
+
nodes.forEach(node => {
|
|
526
|
+
// @ts-expect-error -- disambiguate displayName from across possible Node types.
|
|
527
|
+
const displayName = node.request ? node.request.url : node.type;
|
|
528
|
+
// eslint-disable-next-line
|
|
529
|
+
console.log(nodeToLabel.get(node), displayName.slice(0, widthInCharacters - 5));
|
|
530
|
+
for (const child of node.dependents) {
|
|
531
|
+
// @ts-expect-error -- disambiguate displayName from across possible Node types.
|
|
532
|
+
const displayName = child.request ? child.request.url : child.type;
|
|
533
|
+
// eslint-disable-next-line
|
|
534
|
+
console.log(' ->', nodeToLabel.get(child), displayName.slice(0, widthInCharacters - 10));
|
|
535
|
+
}
|
|
536
|
+
// eslint-disable-next-line
|
|
537
|
+
console.log();
|
|
505
538
|
});
|
|
539
|
+
// Show cycle.
|
|
540
|
+
const cyclePath = NetworkNode.findCycle(rootNode);
|
|
541
|
+
// eslint-disable-next-line
|
|
542
|
+
console.log('Cycle?', cyclePath ? 'yes' : 'no');
|
|
543
|
+
if (cyclePath) {
|
|
544
|
+
const path = [...cyclePath];
|
|
545
|
+
path.push(path[0]);
|
|
546
|
+
// eslint-disable-next-line
|
|
547
|
+
console.log(path.map(node => nodeToLabel.get(node)).join(' -> '));
|
|
548
|
+
}
|
|
506
549
|
}
|
|
507
550
|
}
|
|
508
551
|
export { PageDependencyGraph };
|