chrome-devtools-mcp 0.0.1 → 0.1.0
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/LICENSE +202 -0
- package/README.md +196 -0
- package/build/node_modules/chrome-devtools-frontend/LICENSE +27 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/App.js +4 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/AppProvider.js +17 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Base64.js +43 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/CharacterIdMap.js +27 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Color.js +2029 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/ColorConverter.js +330 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/ColorUtils.js +221 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Console.js +86 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Debouncer.js +14 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/EventTarget.js +14 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Gzip.js +64 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/JavaScriptMetaData.js +4 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Lazy.js +29 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Linkifier.js +34 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/MapWithDefault.js +20 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Mutex.js +49 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Object.js +86 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/ParsedURL.js +459 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Progress.js +137 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/QueryParamHandler.js +4 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/ResolverBase.js +69 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/ResourceType.js +506 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/ReturnToPanel.js +13 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Revealer.js +159 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Runnable.js +24 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/SegmentedRange.js +79 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/SettingRegistration.js +164 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Settings.js +1281 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/SimpleHistoryManager.js +97 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/StringOutputStream.js +17 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/TextDictionary.js +40 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Throttler.js +71 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Trie.js +127 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Worker.js +44 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/common.js +44 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/AidaClient.js +415 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/GdpClient.js +161 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/InspectorFrontendHost.js +510 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/InspectorFrontendHostAPI.js +66 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/Platform.js +59 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/ResourceLoader.js +232 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/UserMetrics.js +953 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/host.js +12 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/i18n/i18n.js +9 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/ArrayUtilities.js +199 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/Brand.js +4 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/Constructor.js +4 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/DOMUtilities.js +122 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/DateUtilities.js +13 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/DevToolsPath.js +27 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/KeyboardUtilities.js +21 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/MapUtilities.js +79 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/MimeType.js +143 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/NumberUtilities.js +72 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/StringUtilities.js +536 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/Timing.js +9 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/TypedArrayUtilities.js +157 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/TypescriptUtilities.js +24 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/UIString.js +4 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/UserVisibleError.js +22 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/platform.js +26 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/protocol_client/InspectorBackend.js +800 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/protocol_client/NodeURL.js +31 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/protocol_client/protocol_client.js +13 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/root/Runtime.js +1 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/root/root.js +5 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/AccessibilityModel.js +281 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/AnimationModel.js +846 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/AutofillModel.js +155 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CPUProfilerModel.js +99 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CPUThrottlingManager.js +220 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSContainerQuery.js +98 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSFontFace.js +31 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSLayer.js +22 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSMatchedStyles.js +1279 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSMedia.js +100 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSMetadata.js +1518 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSModel.js +902 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSProperty.js +315 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSPropertyParser.js +572 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSPropertyParserMatchers.js +1316 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSQuery.js +53 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSRule.js +361 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSScope.js +22 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSStyleDeclaration.js +275 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSStyleSheetHeader.js +166 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSSupports.js +24 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CategorizedBreakpoint.js +29 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/ChildTargetManager.js +232 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CompilerSourceMappingContentProvider.js +47 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/Connections.js +242 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/ConsoleModel.js +629 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/ConsoleModelTypes.js +14 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/Cookie.js +241 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CookieModel.js +198 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CookieParser.js +163 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/DOMDebuggerModel.js +597 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/DOMModel.js +1610 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/DebuggerModel.js +1231 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/EmulationModel.js +527 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/EnhancedTracesParser.js +346 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/EventBreakpointsModel.js +125 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/FrameAssociated.js +4 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/FrameManager.js +199 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/HeapProfilerModel.js +130 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/HttpReasonPhraseStrings.js +73 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/IOModel.js +83 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/IsolateManager.js +208 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/IssuesModel.js +41 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/LayerTreeBase.js +95 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/LogModel.js +34 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/NetworkManager.js +1915 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/NetworkRequest.js +1660 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/OverlayColorGenerator.js +48 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/OverlayModel.js +765 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/OverlayPersistentHighlighter.js +384 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/PageLoad.js +25 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/PageResourceLoader.js +300 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/PaintProfiler.js +67 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/PerformanceMetricsModel.js +62 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/PreloadingModel.js +563 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/RehydratingConnection.js +292 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/RehydratingObject.js +4 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/RemoteObject.js +905 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/Resource.js +163 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/ResourceTreeModel.js +922 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/RuntimeModel.js +535 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SDKModel.js +45 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/ScreenCaptureModel.js +174 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/Script.js +395 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SecurityOriginManager.js +54 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/ServerSentEvents.js +67 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/ServerSentEventsProtocol.js +110 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/ServerTiming.js +216 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/ServiceWorkerCacheModel.js +271 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/ServiceWorkerManager.js +511 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMap.js +678 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMapCache.js +41 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMapFunctionRanges.js +132 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMapManager.js +189 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMapScopeChainEntry.js +156 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMapScopesInfo.js +276 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/StorageBucketsModel.js +143 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/StorageKeyManager.js +53 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/Target.js +262 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/TargetManager.js +382 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/TraceObject.js +49 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/WebAuthnModel.js +66 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/sdk-meta.js +1117 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/sdk.js +89 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/generated/ARIAProperties.js +1513 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/generated/Deprecation.js +339 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/generated/InspectorBackendCommands.js +1558 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/generated/SupportedCSSProperties.js +7473 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/generated/protocol.js +4 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.js +260 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.js +1096 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/UnitFormatters.js +130 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/cpu_profile/CPUProfileDataModel.js +514 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/cpu_profile/ProfileTreeModel.js +90 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/cpu_profile/cpu_profile.js +6 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/logs/LogManager.js +67 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/logs/NetworkLog.js +494 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/logs/RequestResolver.js +49 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/logs/logs-meta.js +73 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/logs/logs.js +7 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/network_time_calculator/Calculator.js +4 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/network_time_calculator/NetworkTimeCalculator.js +256 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/network_time_calculator/RequestTimeRanges.js +120 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/network_time_calculator/network_time_calculator.js +6 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/text_utils/CodeMirrorUtils.js +32 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/text_utils/ContentData.js +173 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/text_utils/ContentProvider.js +30 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/text_utils/StaticContentProvider.js +32 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/text_utils/StreamingContentData.js +79 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/text_utils/Text.js +69 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/text_utils/TextCursor.js +35 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/text_utils/TextRange.js +231 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/text_utils/TextUtils.js +332 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/text_utils/WasmDisassembly.js +68 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/text_utils/text_utils.js +14 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/EntityMapper.js +122 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/EventsSerializer.js +81 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/LanternComputationData.js +370 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/ModelImpl.js +171 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/Name.js +114 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/Processor.js +547 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/Styles.js +815 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/extras/FilmStrip.js +44 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/extras/MainThreadActivity.js +76 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/extras/ScriptDuplication.js +162 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/extras/StackTraceForEvent.js +191 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/extras/ThirdParties.js +118 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/extras/TraceFilter.js +50 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/extras/TraceTree.js +592 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/extras/extras.js +10 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/AnimationFramesHandler.js +109 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/AnimationHandler.js +26 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/AsyncJSCallsHandler.js +187 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/AuctionWorkletsHandler.js +162 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/DOMStatsHandler.js +21 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/ExtensionTraceDataHandler.js +263 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/FlowsHandler.js +148 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/FramesHandler.js +483 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/GPUHandler.js +35 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/ImagePaintingHandler.js +147 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/InitiatorsHandler.js +180 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/InvalidationsHandler.js +126 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/LargestImagePaintHandler.js +85 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/LargestTextPaintHandler.js +27 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/LayerTreeHandler.js +103 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/LayoutShiftsHandler.js +446 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/MemoryHandler.js +21 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/MetaHandler.js +400 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/ModelHandlers.js +33 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/NetworkRequestsHandler.js +540 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/PageFramesHandler.js +42 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/PageLoadMetricsHandler.js +349 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/RendererHandler.js +346 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/SamplesHandler.js +207 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/ScreenshotsHandler.js +98 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/ScriptsHandler.js +251 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/SelectorStatsHandler.js +71 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/Threads.js +101 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/UserInteractionsHandler.js +297 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/UserTimingsHandler.js +184 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/WarningsHandler.js +134 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/WorkersHandler.js +33 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/handlers.js +7 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/helpers.js +163 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/types.js +1 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/helpers/Extensions.js +41 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/helpers/Network.js +102 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/helpers/SamplesIntegrator.js +493 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/helpers/SyntheticEvents.js +74 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/helpers/Timing.js +184 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/helpers/Trace.js +744 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/helpers/TreeHelpers.js +254 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/helpers/helpers.js +10 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/CLSCulprits.js +488 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/Cache.js +208 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/Common.js +340 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/DOMSize.js +180 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/DocumentLatency.js +256 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/DuplicatedJavaScript.js +89 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/FontDisplay.js +81 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/ForcedReflow.js +160 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/INPBreakdown.js +117 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/ImageDelivery.js +262 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/LCPBreakdown.js +188 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/LCPDiscovery.js +182 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/LegacyJavaScript.js +88 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/Models.js +21 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/ModernHTTP.js +199 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/NetworkDependencyTree.js +544 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/RenderBlocking.js +191 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/SlowCSSSelector.js +135 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/Statistics.js +92 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/ThirdParties.js +97 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/Viewport.js +101 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/insights.js +9 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/types.js +18 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/lantern/core/LanternError.js +6 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/lantern/core/NetworkAnalyzer.js +488 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/lantern/core/core.js +5 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/lantern/graph/BaseNode.js +276 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/lantern/graph/CPUNode.js +63 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/lantern/graph/NetworkNode.js +82 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/lantern/graph/PageDependencyGraph.js +551 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/lantern/graph/graph.js +7 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/lantern/lantern.js +9 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/lantern/metrics/FirstContentfulPaint.js +136 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/lantern/metrics/Interactive.js +67 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/lantern/metrics/LargestContentfulPaint.js +68 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/lantern/metrics/MaxPotentialFID.js +48 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/lantern/metrics/Metric.js +68 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/lantern/metrics/SpeedIndex.js +101 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/lantern/metrics/TBTUtils.js +64 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/lantern/metrics/TotalBlockingTime.js +78 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/lantern/metrics/metrics.js +11 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/lantern/simulation/ConnectionPool.js +115 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/lantern/simulation/Constants.js +42 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/lantern/simulation/DNSCache.js +47 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/lantern/simulation/SimulationTimingMap.js +134 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/lantern/simulation/Simulator.js +450 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/lantern/simulation/TCPConnection.js +154 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/lantern/simulation/simulation.js +9 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/lantern/types/Lantern.js +24 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/lantern/types/types.js +4 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/trace.js +17 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/types/Configuration.js +18 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/types/Extensions.js +37 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/types/File.js +57 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/types/Overlays.js +4 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/types/Timing.js +15 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/types/TraceEvents.js +565 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/types/types.js +9 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/codemirror.next/codemirror.next.js +1 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/legacy-javascript/LICENSE +202 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/legacy-javascript/legacy-javascript.js +38 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/legacy-javascript/lib/legacy-javascript.js +944 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/source-map-scopes-codec/LICENSE +26 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/source-map-scopes-codec/package/src/builder/builder.js +196 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/source-map-scopes-codec/package/src/builder/safe_builder.js +226 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/source-map-scopes-codec/package/src/codec.js +4 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/source-map-scopes-codec/package/src/decode/decode.js +394 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/source-map-scopes-codec/package/src/encode/encode.js +24 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/source-map-scopes-codec/package/src/encode/encoder.js +283 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/source-map-scopes-codec/package/src/mod.js +7 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/source-map-scopes-codec/package/src/util.js +9 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/source-map-scopes-codec/package/src/vlq.js +84 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/source-map-scopes-codec/source-map-scopes-codec.js +4 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/third-party-web/LICENSE +20 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/third-party-web/lib/nostats-subset.js +148 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/third-party-web/package/httparchive-nostats-subset.js +2 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/third-party-web/package/httparchive-subset.js +2 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/third-party-web/package/lib/create-entity-finder-api.js +118 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/third-party-web/package/lib/create-entity-finder-api.test.js +40 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/third-party-web/package/lib/entities.test.js +26 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/third-party-web/package/lib/index.js +4 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/third-party-web/package/lib/index.test.js +215 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/third-party-web/package/lib/subsets/httparchive-nostats.js +4 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/third-party-web/package/lib/subsets/httparchive.js +4 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/third-party-web/package/lib/subsets/nostats.js +4 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/third-party-web/package/nostats-subset.js +2 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/third_party/third-party-web/third-party-web.js +8 -0
- package/build/src/McpContext.js +204 -0
- package/build/src/McpResponse.js +178 -0
- package/build/src/Mutex.js +40 -0
- package/build/src/PageCollector.js +68 -0
- package/build/src/browser.js +105 -0
- package/build/src/formatters/consoleFormatter.js +71 -0
- package/build/src/formatters/networkFormatter.js +34 -0
- package/build/src/formatters/snapshotFormatter.js +75 -0
- package/build/src/index.js +206 -0
- package/build/src/logger.js +26 -0
- package/build/src/tools/ToolDefinition.js +8 -0
- package/build/src/tools/categories.js +14 -0
- package/build/src/tools/console.js +19 -0
- package/build/src/tools/emulation.js +61 -0
- package/build/src/tools/input.js +202 -0
- package/build/src/tools/network.js +35 -0
- package/build/src/tools/pages.js +181 -0
- package/build/src/tools/performance.js +116 -0
- package/build/src/tools/screenshot.js +65 -0
- package/build/src/tools/script.js +35 -0
- package/build/src/tools/snapshot.js +42 -0
- package/build/src/trace-processing/parse.js +73 -0
- package/build/src/waitForHelpers.js +109 -0
- package/package.json +53 -6
- package/README +0 -1
|
@@ -0,0 +1,1096 @@
|
|
|
1
|
+
// Copyright 2025 The Chromium Authors
|
|
2
|
+
// Use of this source code is governed by a BSD-style license that can be
|
|
3
|
+
// found in the LICENSE file.
|
|
4
|
+
import * as Common from '../../../core/common/common.js';
|
|
5
|
+
import * as Trace from '../../trace/trace.js';
|
|
6
|
+
import { NetworkRequestFormatter, } from './NetworkRequestFormatter.js';
|
|
7
|
+
import { bytes, micros, millis } from './UnitFormatters.js';
|
|
8
|
+
/**
|
|
9
|
+
* For a given frame ID and navigation ID, returns the LCP Event and the LCP Request, if the resource was an image.
|
|
10
|
+
*/
|
|
11
|
+
function getLCPData(parsedTrace, frameId, navigationId) {
|
|
12
|
+
const navMetrics = parsedTrace.data.PageLoadMetrics.metricScoresByFrameId.get(frameId)?.get(navigationId);
|
|
13
|
+
if (!navMetrics) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const metric = navMetrics.get("LCP" /* Trace.Handlers.ModelHandlers.PageLoadMetrics.MetricName.LCP */);
|
|
17
|
+
if (!metric || !Trace.Handlers.ModelHandlers.PageLoadMetrics.metricIsLCP(metric)) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const lcpEvent = metric?.event;
|
|
21
|
+
if (!lcpEvent || !Trace.Types.Events.isLargestContentfulPaintCandidate(lcpEvent)) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
lcpEvent,
|
|
26
|
+
lcpRequest: parsedTrace.data.LargestImagePaint.lcpRequestByNavigationId.get(navigationId),
|
|
27
|
+
metricScore: metric,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export class PerformanceInsightFormatter {
|
|
31
|
+
#insight;
|
|
32
|
+
#parsedTrace;
|
|
33
|
+
constructor(parsedTrace, insight) {
|
|
34
|
+
this.#insight = insight;
|
|
35
|
+
this.#parsedTrace = parsedTrace;
|
|
36
|
+
}
|
|
37
|
+
#formatMilli(x) {
|
|
38
|
+
if (x === undefined) {
|
|
39
|
+
return '';
|
|
40
|
+
}
|
|
41
|
+
return millis(x);
|
|
42
|
+
}
|
|
43
|
+
#formatMicro(x) {
|
|
44
|
+
if (x === undefined) {
|
|
45
|
+
return '';
|
|
46
|
+
}
|
|
47
|
+
return this.#formatMilli(Trace.Helpers.Timing.microToMilli(x));
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Information about LCP which we pass to the LLM for all insights that relate to LCP.
|
|
51
|
+
*/
|
|
52
|
+
#lcpMetricSharedContext() {
|
|
53
|
+
if (!this.#insight.navigationId) {
|
|
54
|
+
// No navigation ID = no LCP.
|
|
55
|
+
return '';
|
|
56
|
+
}
|
|
57
|
+
if (!this.#insight.frameId || !this.#insight.navigationId) {
|
|
58
|
+
return '';
|
|
59
|
+
}
|
|
60
|
+
const data = getLCPData(this.#parsedTrace, this.#insight.frameId, this.#insight.navigationId);
|
|
61
|
+
if (!data) {
|
|
62
|
+
return '';
|
|
63
|
+
}
|
|
64
|
+
const { metricScore, lcpRequest, lcpEvent } = data;
|
|
65
|
+
const theLcpElement = lcpEvent.args.data?.nodeName ? `The LCP element (${lcpEvent.args.data.nodeName})` : 'The LCP element';
|
|
66
|
+
const parts = [
|
|
67
|
+
`The Largest Contentful Paint (LCP) time for this navigation was ${this.#formatMicro(metricScore.timing)}.`,
|
|
68
|
+
];
|
|
69
|
+
if (lcpRequest) {
|
|
70
|
+
parts.push(`${theLcpElement} is an image fetched from \`${lcpRequest.args.data.url}\`.`);
|
|
71
|
+
const request = TraceEventFormatter.networkRequests([lcpRequest], this.#parsedTrace, { verbose: true, customTitle: 'LCP resource network request' });
|
|
72
|
+
parts.push(request);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
parts.push(`${theLcpElement} is text and was not fetched from the network.`);
|
|
76
|
+
}
|
|
77
|
+
return parts.join('\n');
|
|
78
|
+
}
|
|
79
|
+
insightIsSupported() {
|
|
80
|
+
return this.#description().length > 0;
|
|
81
|
+
}
|
|
82
|
+
getSuggestions() {
|
|
83
|
+
switch (this.#insight.insightKey) {
|
|
84
|
+
case 'CLSCulprits':
|
|
85
|
+
return [
|
|
86
|
+
{ title: 'Help me optimize my CLS score' },
|
|
87
|
+
{ title: 'How can I prevent layout shifts on this page?' },
|
|
88
|
+
];
|
|
89
|
+
case 'DocumentLatency':
|
|
90
|
+
return [
|
|
91
|
+
{ title: 'How do I decrease the initial loading time of my page?' },
|
|
92
|
+
{ title: 'Did anything slow down the request for this document?' },
|
|
93
|
+
];
|
|
94
|
+
case 'DOMSize':
|
|
95
|
+
return [{ title: 'How can I reduce the size of my DOM?' }];
|
|
96
|
+
case 'DuplicatedJavaScript':
|
|
97
|
+
return [
|
|
98
|
+
{ title: 'How do I deduplicate the identified scripts in my bundle?' },
|
|
99
|
+
{ title: 'Which duplicated JavaScript modules are the most problematic?' }
|
|
100
|
+
];
|
|
101
|
+
case 'FontDisplay':
|
|
102
|
+
return [
|
|
103
|
+
{ title: 'How can I update my CSS to avoid layout shifts caused by incorrect `font-display` properties?' }
|
|
104
|
+
];
|
|
105
|
+
case 'ForcedReflow':
|
|
106
|
+
return [
|
|
107
|
+
{ title: 'How can I avoid forced reflows and layout thrashing?' },
|
|
108
|
+
{ title: 'What is forced reflow and why is it problematic?' }
|
|
109
|
+
];
|
|
110
|
+
case 'ImageDelivery':
|
|
111
|
+
return [
|
|
112
|
+
{ title: 'What should I do to improve and optimize the time taken to fetch and display images on the page?' },
|
|
113
|
+
{ title: 'Are all images on my site optimized?' },
|
|
114
|
+
];
|
|
115
|
+
case 'INPBreakdown':
|
|
116
|
+
return [
|
|
117
|
+
{ title: 'Suggest fixes for my longest interaction' }, { title: 'Why is a large INP score problematic?' },
|
|
118
|
+
{ title: 'What\'s the biggest contributor to my longest interaction?' }
|
|
119
|
+
];
|
|
120
|
+
case 'LCPDiscovery':
|
|
121
|
+
return [
|
|
122
|
+
{ title: 'Suggest fixes to reduce my LCP' }, { title: 'What can I do to reduce my LCP discovery time?' },
|
|
123
|
+
{ title: 'Why is LCP discovery time important?' }
|
|
124
|
+
];
|
|
125
|
+
case 'LCPBreakdown':
|
|
126
|
+
return [
|
|
127
|
+
{ title: 'Help me optimize my LCP score' }, { title: 'Which LCP phase was most problematic?' },
|
|
128
|
+
{ title: 'What can I do to reduce the LCP time for this page load?' }
|
|
129
|
+
];
|
|
130
|
+
case 'NetworkDependencyTree':
|
|
131
|
+
return [{ title: 'How do I optimize my network dependency tree?' }];
|
|
132
|
+
case 'RenderBlocking':
|
|
133
|
+
return [
|
|
134
|
+
{ title: 'Show me the most impactful render blocking requests that I should focus on' },
|
|
135
|
+
{ title: 'How can I reduce the number of render blocking requests?' }
|
|
136
|
+
];
|
|
137
|
+
case 'SlowCSSSelector':
|
|
138
|
+
return [{ title: 'How can I optimize my CSS to increase the performance of CSS selectors?' }];
|
|
139
|
+
case 'ThirdParties':
|
|
140
|
+
return [{ title: 'Which third parties are having the largest impact on my page performance?' }];
|
|
141
|
+
case 'Cache':
|
|
142
|
+
return [{ title: 'What caching strategies can I apply to improve my page performance?' }];
|
|
143
|
+
case 'Viewport':
|
|
144
|
+
return [{ title: 'How do I make sure my page is optimized for mobile viewing?' }];
|
|
145
|
+
case 'ModernHTTP':
|
|
146
|
+
return [
|
|
147
|
+
{ title: 'Is my site using the best HTTP practices?' },
|
|
148
|
+
{ title: 'Which resources are not using a modern HTTP protocol?' },
|
|
149
|
+
];
|
|
150
|
+
case 'LegacyJavaScript':
|
|
151
|
+
return [
|
|
152
|
+
{ title: 'Is my site polyfilling modern JavaScript features?' },
|
|
153
|
+
{ title: 'How can I reduce the amount of legacy JavaScript on my page?' },
|
|
154
|
+
];
|
|
155
|
+
default:
|
|
156
|
+
throw new Error('Unknown insight key');
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Create an AI prompt string out of the Cache Insight model to use with Ask AI.
|
|
161
|
+
* Note: This function accesses the UIStrings within Cache to help build the
|
|
162
|
+
* AI prompt, but does not (and should not) call i18nString to localize these strings. They
|
|
163
|
+
* should all be sent in English (at least for now).
|
|
164
|
+
* @param insight The Cache Insight Model to query.
|
|
165
|
+
* @returns a string formatted for sending to Ask AI.
|
|
166
|
+
*/
|
|
167
|
+
formatCacheInsight(insight) {
|
|
168
|
+
if (insight.requests.length === 0) {
|
|
169
|
+
return Trace.Insights.Models.Cache.UIStrings.noRequestsToCache + '.';
|
|
170
|
+
}
|
|
171
|
+
let output = 'The following resources were associated with ineffficient cache policies:\n';
|
|
172
|
+
for (const entry of insight.requests) {
|
|
173
|
+
output += `\n- ${entry.request.args.data.url}`;
|
|
174
|
+
output += `\n - Cache Time to Live (TTL): ${entry.ttl} seconds`;
|
|
175
|
+
output += `\n - Wasted bytes: ${bytes(entry.wastedBytes)}`;
|
|
176
|
+
}
|
|
177
|
+
output += '\n\n' + Trace.Insights.Models.Cache.UIStrings.description;
|
|
178
|
+
return output;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Create an AI prompt string out of the DOM Size model to use with Ask AI.
|
|
182
|
+
* Note: This function accesses the UIStrings within DomSize to help build the
|
|
183
|
+
* AI prompt, but does not (and should not) call i18nString to localize these strings. They
|
|
184
|
+
* should all be sent in English (at least for now).
|
|
185
|
+
* @param insight The DOM Size Insight Model to query.
|
|
186
|
+
* @returns a string formatted for sending to Ask AI.
|
|
187
|
+
*/
|
|
188
|
+
formatDomSizeInsight(insight) {
|
|
189
|
+
if (insight.state === 'pass') {
|
|
190
|
+
return 'No DOM size issues were detected.';
|
|
191
|
+
}
|
|
192
|
+
let output = Trace.Insights.Models.DOMSize.UIStrings.description + '\n';
|
|
193
|
+
if (insight.maxDOMStats) {
|
|
194
|
+
output += '\n' + Trace.Insights.Models.DOMSize.UIStrings.statistic + ':\n\n';
|
|
195
|
+
const maxDepthStats = insight.maxDOMStats.args.data.maxDepth;
|
|
196
|
+
const maxChildrenStats = insight.maxDOMStats.args.data.maxChildren;
|
|
197
|
+
output += Trace.Insights.Models.DOMSize.UIStrings.totalElements + ': ' +
|
|
198
|
+
insight.maxDOMStats.args.data.totalElements + '.\n';
|
|
199
|
+
if (maxDepthStats) {
|
|
200
|
+
output += Trace.Insights.Models.DOMSize.UIStrings.maxDOMDepth + ': ' + maxDepthStats.depth +
|
|
201
|
+
` nodes, starting with element '${maxDepthStats.nodeName}'` +
|
|
202
|
+
' (node id: ' + maxDepthStats.nodeId + ').\n';
|
|
203
|
+
}
|
|
204
|
+
if (maxChildrenStats) {
|
|
205
|
+
output += Trace.Insights.Models.DOMSize.UIStrings.maxChildren + ': ' + maxChildrenStats.numChildren +
|
|
206
|
+
`, for parent '${maxChildrenStats.nodeName}'` +
|
|
207
|
+
' (node id: ' + maxChildrenStats.nodeId + ').\n';
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (insight.largeLayoutUpdates.length > 0 || insight.largeStyleRecalcs.length > 0) {
|
|
211
|
+
output += `\nLarge layout updates/style calculations:\n`;
|
|
212
|
+
}
|
|
213
|
+
if (insight.largeLayoutUpdates.length > 0) {
|
|
214
|
+
for (const update of insight.largeLayoutUpdates) {
|
|
215
|
+
output += `\n - Layout update: Duration: ${this.#formatMicro(update.dur)},`;
|
|
216
|
+
output += ` with ${update.args.beginData.dirtyObjects} of ${update.args.beginData.totalObjects} nodes needing layout.`;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (insight.largeStyleRecalcs.length > 0) {
|
|
220
|
+
for (const recalc of insight.largeStyleRecalcs) {
|
|
221
|
+
output += `\n - Style recalculation: Duration: ${this.#formatMicro(recalc.dur)}, `;
|
|
222
|
+
output += `with ${recalc.args.elementCount} elements affected.`;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return output;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Create an AI prompt string out of the NetworkDependencyTree Insight model to use with Ask AI.
|
|
229
|
+
* Note: This function accesses the UIStrings within NetworkDependencyTree to help build the
|
|
230
|
+
* AI prompt, but does not (and should not) call i18nString to localize these strings. They
|
|
231
|
+
* should all be sent in English (at least for now).
|
|
232
|
+
* @param insight The Network Dependency Tree Insight Model to query.
|
|
233
|
+
* @returns a string formatted for sending to Ask AI.
|
|
234
|
+
*/
|
|
235
|
+
formatFontDisplayInsight(insight) {
|
|
236
|
+
if (insight.fonts.length === 0) {
|
|
237
|
+
return 'No font display issues were detected.';
|
|
238
|
+
}
|
|
239
|
+
let output = 'The following font display issues were found:\n';
|
|
240
|
+
for (const font of insight.fonts) {
|
|
241
|
+
let fontName = font.name;
|
|
242
|
+
if (!fontName) {
|
|
243
|
+
const url = new Common.ParsedURL.ParsedURL(font.request.args.data.url);
|
|
244
|
+
fontName = url.isValid ? url.lastPathComponent : '(not available)';
|
|
245
|
+
}
|
|
246
|
+
output += `\n - Font name: ${fontName}, URL: ${font.request.args.data.url}, Property 'font-display' set to: '${font.display}', Wasted time: ${this.#formatMilli(font.wastedTime)}.`;
|
|
247
|
+
}
|
|
248
|
+
output += '\n\n' + Trace.Insights.Models.FontDisplay.UIStrings.description;
|
|
249
|
+
return output;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Create an AI prompt string out of the Forced Reflow Insight model to use with Ask AI.
|
|
253
|
+
* Note: This function accesses the UIStrings within ForcedReflow model to help build the
|
|
254
|
+
* AI prompt, but does not (and should not) call i18nString to localize these strings. They
|
|
255
|
+
* should all be sent in English (at least for now).
|
|
256
|
+
* @param insight The ForcedReflow Insight Model to query.
|
|
257
|
+
* @returns a string formatted for sending to Ask AI.
|
|
258
|
+
*/
|
|
259
|
+
formatForcedReflowInsight(insight) {
|
|
260
|
+
let output = Trace.Insights.Models.ForcedReflow.UIStrings.description + '\n\n';
|
|
261
|
+
if (insight.topLevelFunctionCallData || insight.aggregatedBottomUpData.length > 0) {
|
|
262
|
+
output += 'The forced reflow checks revealed one or more problems.\n\n';
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
output += 'The forced reflow checks revealed no problems.';
|
|
266
|
+
return output;
|
|
267
|
+
}
|
|
268
|
+
function callFrameToString(frame) {
|
|
269
|
+
if (frame === null) {
|
|
270
|
+
return Trace.Insights.Models.ForcedReflow.UIStrings.unattributed;
|
|
271
|
+
}
|
|
272
|
+
let result = `${frame.functionName || Trace.Insights.Models.ForcedReflow.UIStrings.anonymous}`;
|
|
273
|
+
if (frame.url) {
|
|
274
|
+
result += ` @ ${frame.url}:${frame.lineNumber}:${frame.columnNumber}`;
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
result += ' @ unknown location';
|
|
278
|
+
}
|
|
279
|
+
return result;
|
|
280
|
+
}
|
|
281
|
+
if (insight.topLevelFunctionCallData) {
|
|
282
|
+
output += 'The following is the top function call that caused forced reflow(s):\n\n';
|
|
283
|
+
output += ' - ' + callFrameToString(insight.topLevelFunctionCallData.topLevelFunctionCall);
|
|
284
|
+
output += `\n\n${Trace.Insights.Models.ForcedReflow.UIStrings.totalReflowTime}: ${this.#formatMicro(insight.topLevelFunctionCallData.totalReflowTime)}\n`;
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
output += 'No top-level functions causing forced reflows were identified.\n';
|
|
288
|
+
}
|
|
289
|
+
if (insight.aggregatedBottomUpData.length > 0) {
|
|
290
|
+
output += '\n' + Trace.Insights.Models.ForcedReflow.UIStrings.relatedStackTrace + ' (including total time):\n';
|
|
291
|
+
for (const data of insight.aggregatedBottomUpData) {
|
|
292
|
+
output += `\n - ${this.#formatMicro(data.totalTime)} in ${callFrameToString(data.bottomUpData)}`;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
output += '\nNo aggregated bottom-up causes of forced reflows were identified.';
|
|
297
|
+
}
|
|
298
|
+
return output;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Create an AI prompt string out of the NetworkDependencyTree Insight model to use with Ask AI.
|
|
302
|
+
* Note: This function accesses the UIStrings within NetworkDependencyTree to help build the
|
|
303
|
+
* AI prompt, but does not (and should not) call i18nString to localize these strings. They
|
|
304
|
+
* should all be sent in English (at least for now).
|
|
305
|
+
* @param insight The Network Dependency Tree Insight Model to query.
|
|
306
|
+
* @returns a string formatted for sending to Ask AI.
|
|
307
|
+
*/
|
|
308
|
+
formatNetworkDependencyTreeInsight(insight) {
|
|
309
|
+
let output = insight.fail ?
|
|
310
|
+
'The network dependency tree checks found one or more problems.\n\n' :
|
|
311
|
+
'The network dependency tree checks revealed no problems, but optimization suggestions may be available.\n\n';
|
|
312
|
+
const rootNodes = insight.rootNodes;
|
|
313
|
+
if (rootNodes.length > 0) {
|
|
314
|
+
output += `Max critical path latency is ${this.#formatMicro(insight.maxTime)}\n\n`;
|
|
315
|
+
output += 'The following is the critical request chain:\n';
|
|
316
|
+
function formatNode(node, indent) {
|
|
317
|
+
const url = node.request.args.data.url;
|
|
318
|
+
const time = this.#formatMicro(node.timeFromInitialRequest);
|
|
319
|
+
const isLongest = node.isLongest ? ' (longest chain)' : '';
|
|
320
|
+
let nodeString = `${indent}- ${url} (${time})${isLongest}\n`;
|
|
321
|
+
for (const child of node.children) {
|
|
322
|
+
nodeString += formatNode.call(this, child, indent + ' ');
|
|
323
|
+
}
|
|
324
|
+
return nodeString;
|
|
325
|
+
}
|
|
326
|
+
for (const rootNode of rootNodes) {
|
|
327
|
+
output += formatNode.call(this, rootNode, '');
|
|
328
|
+
}
|
|
329
|
+
output += '\n';
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
output += `${Trace.Insights.Models.NetworkDependencyTree.UIStrings.noNetworkDependencyTree}.\n\n`;
|
|
333
|
+
}
|
|
334
|
+
if (insight.preconnectedOrigins?.length > 0) {
|
|
335
|
+
output += `${Trace.Insights.Models.NetworkDependencyTree.UIStrings.preconnectOriginsTableTitle}:\n`;
|
|
336
|
+
output += `${Trace.Insights.Models.NetworkDependencyTree.UIStrings.preconnectOriginsTableDescription}\n`;
|
|
337
|
+
for (const origin of insight.preconnectedOrigins) {
|
|
338
|
+
const headerText = 'headerText' in origin ? `'${origin.headerText}'` : ``;
|
|
339
|
+
output += `
|
|
340
|
+
- ${origin.url}
|
|
341
|
+
- ${Trace.Insights.Models.NetworkDependencyTree.UIStrings.columnSource}: '${origin.source}'`;
|
|
342
|
+
if (headerText) {
|
|
343
|
+
output += `\n - Header: ${headerText}`;
|
|
344
|
+
}
|
|
345
|
+
if (origin.unused) {
|
|
346
|
+
output += `\n - Warning: ${Trace.Insights.Models.NetworkDependencyTree.UIStrings.unusedWarning}`;
|
|
347
|
+
}
|
|
348
|
+
if (origin.crossorigin) {
|
|
349
|
+
output += `\n - Warning: ${Trace.Insights.Models.NetworkDependencyTree.UIStrings.crossoriginWarning}`;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (insight.preconnectedOrigins.length >
|
|
353
|
+
Trace.Insights.Models.NetworkDependencyTree.TOO_MANY_PRECONNECTS_THRESHOLD) {
|
|
354
|
+
output +=
|
|
355
|
+
`\n\n**Warning**: ${Trace.Insights.Models.NetworkDependencyTree.UIStrings.tooManyPreconnectLinksWarning}`;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
output += `${Trace.Insights.Models.NetworkDependencyTree.UIStrings.noPreconnectOrigins}.`;
|
|
360
|
+
}
|
|
361
|
+
if (insight.preconnectCandidates.length > 0 &&
|
|
362
|
+
insight.preconnectedOrigins.length <
|
|
363
|
+
Trace.Insights.Models.NetworkDependencyTree.TOO_MANY_PRECONNECTS_THRESHOLD) {
|
|
364
|
+
output += `\n\n${Trace.Insights.Models.NetworkDependencyTree.UIStrings.estSavingTableTitle}:\n${Trace.Insights.Models.NetworkDependencyTree.UIStrings.estSavingTableDescription}\n`;
|
|
365
|
+
for (const candidate of insight.preconnectCandidates) {
|
|
366
|
+
output += `\nAdding [preconnect] to origin '${candidate.origin}' would save ${this.#formatMilli(candidate.wastedMs)}.`;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return output;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Create an AI prompt string out of the Slow CSS Selector Insight model to use with Ask AI.
|
|
373
|
+
* Note: This function accesses the UIStrings within SlowCSSSelector to help build the
|
|
374
|
+
* AI prompt, but does not (and should not) call i18nString to localize these strings. They
|
|
375
|
+
* should all be sent in English (at least for now).
|
|
376
|
+
* @param insight The Network Dependency Tree Insight Model to query.
|
|
377
|
+
* @returns a string formatted for sending to Ask AI.
|
|
378
|
+
*/
|
|
379
|
+
formatSlowCssSelectorsInsight(insight) {
|
|
380
|
+
let output = '';
|
|
381
|
+
if (!insight.topSelectorElapsedMs && !insight.topSelectorMatchAttempts) {
|
|
382
|
+
return Trace.Insights.Models.SlowCSSSelector.UIStrings.enableSelectorData;
|
|
383
|
+
}
|
|
384
|
+
output += 'One or more slow CSS selectors were identified as negatively affecting page performance:\n\n';
|
|
385
|
+
if (insight.topSelectorElapsedMs) {
|
|
386
|
+
output += `${Trace.Insights.Models.SlowCSSSelector.UIStrings.topSelectorElapsedTime} (as ranked by elapsed time in ms):\n`;
|
|
387
|
+
output += `${this.#formatMicro(insight.topSelectorElapsedMs['elapsed (us)'])}: ${insight.topSelectorElapsedMs.selector}\n\n`;
|
|
388
|
+
}
|
|
389
|
+
if (insight.topSelectorMatchAttempts) {
|
|
390
|
+
output += Trace.Insights.Models.SlowCSSSelector.UIStrings.topSelectorMatchAttempt + ':\n';
|
|
391
|
+
output += `${insight.topSelectorMatchAttempts.match_attempts} attempts for selector: '${insight.topSelectorMatchAttempts.selector}'\n\n`;
|
|
392
|
+
}
|
|
393
|
+
output += `${Trace.Insights.Models.SlowCSSSelector.UIStrings.total}:\n`;
|
|
394
|
+
output +=
|
|
395
|
+
`${Trace.Insights.Models.SlowCSSSelector.UIStrings.elapsed}: ${this.#formatMicro(insight.totalElapsedMs)}\n`;
|
|
396
|
+
output += `${Trace.Insights.Models.SlowCSSSelector.UIStrings.matchAttempts}: ${insight.totalMatchAttempts}\n`;
|
|
397
|
+
output += `${Trace.Insights.Models.SlowCSSSelector.UIStrings.matchCount}: ${insight.totalMatchCount}\n\n`;
|
|
398
|
+
output += Trace.Insights.Models.SlowCSSSelector.UIStrings.description;
|
|
399
|
+
return output;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Create an AI prompt string out of the ThirdParties Insight model to use with Ask AI.
|
|
403
|
+
* Note: This function accesses the UIStrings within ThirdParties to help build the
|
|
404
|
+
* AI prompt, but does not (and should not) call i18nString to localize these strings. They
|
|
405
|
+
* should all be sent in English (at least for now).
|
|
406
|
+
* @param insight The Third Parties Insight Model to query.
|
|
407
|
+
* @returns a string formatted for sending to Ask AI.
|
|
408
|
+
*/
|
|
409
|
+
formatThirdPartiesInsight(insight) {
|
|
410
|
+
let output = '';
|
|
411
|
+
const entitySummaries = insight.entitySummaries ?? [];
|
|
412
|
+
const firstPartyEntity = insight.firstPartyEntity;
|
|
413
|
+
const thirdPartyTransferSizeEntries = entitySummaries.filter(s => s.entity !== firstPartyEntity).toSorted((a, b) => b.transferSize - a.transferSize);
|
|
414
|
+
const thirdPartyMainThreadTimeEntries = entitySummaries.filter(s => s.entity !== firstPartyEntity)
|
|
415
|
+
.toSorted((a, b) => b.mainThreadTime - a.mainThreadTime);
|
|
416
|
+
if (!thirdPartyTransferSizeEntries.length && !thirdPartyMainThreadTimeEntries.length) {
|
|
417
|
+
return `No 3rd party scripts were found on this page.`;
|
|
418
|
+
}
|
|
419
|
+
if (thirdPartyTransferSizeEntries.length) {
|
|
420
|
+
output += `The following list contains the largest transfer sizes by a 3rd party script:\n\n`;
|
|
421
|
+
for (const entry of thirdPartyTransferSizeEntries) {
|
|
422
|
+
if (entry.transferSize > 0) {
|
|
423
|
+
output += `- ${entry.entity.name}: ${bytes(entry.transferSize)}\n`;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
output += '\n';
|
|
427
|
+
}
|
|
428
|
+
if (thirdPartyMainThreadTimeEntries.length) {
|
|
429
|
+
output += `The following list contains the largest amount spent by a 3rd party script on the main thread:\n\n`;
|
|
430
|
+
for (const entry of thirdPartyMainThreadTimeEntries) {
|
|
431
|
+
if (entry.mainThreadTime > 0) {
|
|
432
|
+
output += `- ${entry.entity.name}: ${this.#formatMilli(entry.mainThreadTime)}\n`;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
output += '\n';
|
|
436
|
+
}
|
|
437
|
+
output += Trace.Insights.Models.ThirdParties.UIStrings.description;
|
|
438
|
+
return output;
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Create an AI prompt string out of the Viewport [Mobile] Insight model to use with Ask AI.
|
|
442
|
+
* Note: This function accesses the UIStrings within Viewport to help build the
|
|
443
|
+
* AI prompt, but does not (and should not) call i18nString to localize these strings. They
|
|
444
|
+
* should all be sent in English (at least for now).
|
|
445
|
+
* @param insight The Network Dependency Tree Insight Model to query.
|
|
446
|
+
* @returns a string formatted for sending to Ask AI.
|
|
447
|
+
*/
|
|
448
|
+
formatViewportInsight(insight) {
|
|
449
|
+
let output = '';
|
|
450
|
+
output += 'The webpage is ' + (insight.mobileOptimized ? 'already' : 'not') + ' optimized for mobile viewing.\n';
|
|
451
|
+
const hasMetaTag = insight.viewportEvent;
|
|
452
|
+
if (hasMetaTag) {
|
|
453
|
+
output += `\nThe viewport meta tag was found: \`${insight.viewportEvent?.args?.data.content}\`.`;
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
output += `\nThe viewport meta tag is missing.`;
|
|
457
|
+
}
|
|
458
|
+
if (!hasMetaTag) {
|
|
459
|
+
output += '\n\n' + Trace.Insights.Models.Viewport.UIStrings.description;
|
|
460
|
+
}
|
|
461
|
+
return output;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Formats and outputs the insight's data.
|
|
465
|
+
* Pass `{headingLevel: X}` to determine what heading level to use for the
|
|
466
|
+
* titles in the markdown output. The default is 2 (##).
|
|
467
|
+
*/
|
|
468
|
+
formatInsight(opts = { headingLevel: 2 }) {
|
|
469
|
+
const header = '#'.repeat(opts.headingLevel);
|
|
470
|
+
const { title } = this.#insight;
|
|
471
|
+
return `${header} Insight Title: ${title}
|
|
472
|
+
|
|
473
|
+
${header} Insight Summary:
|
|
474
|
+
${this.#description()}
|
|
475
|
+
|
|
476
|
+
${header} Detailed analysis:
|
|
477
|
+
${this.#details()}
|
|
478
|
+
|
|
479
|
+
${header} Estimated savings: ${this.estimatedSavings() || 'none'}
|
|
480
|
+
|
|
481
|
+
${header} External resources:
|
|
482
|
+
${this.#links()}`;
|
|
483
|
+
}
|
|
484
|
+
#details() {
|
|
485
|
+
if (Trace.Insights.Models.ImageDelivery.isImageDelivery(this.#insight)) {
|
|
486
|
+
const optimizableImages = this.#insight.optimizableImages;
|
|
487
|
+
if (optimizableImages.length === 0) {
|
|
488
|
+
return 'There are no unoptimized images on this page.';
|
|
489
|
+
}
|
|
490
|
+
const imageDetails = optimizableImages
|
|
491
|
+
.map(image => {
|
|
492
|
+
// List potential optimizations for the image
|
|
493
|
+
const optimizations = image.optimizations
|
|
494
|
+
.map(optimization => {
|
|
495
|
+
const message = Trace.Insights.Models.ImageDelivery.getOptimizationMessage(optimization);
|
|
496
|
+
const byteSavings = bytes(optimization.byteSavings);
|
|
497
|
+
return `${message} (Est ${byteSavings})`;
|
|
498
|
+
})
|
|
499
|
+
.join('\n');
|
|
500
|
+
return `### ${image.request.args.data.url}
|
|
501
|
+
- Potential savings: ${bytes(image.byteSavings)}
|
|
502
|
+
- Optimizations:\n${optimizations}`;
|
|
503
|
+
})
|
|
504
|
+
.join('\n\n');
|
|
505
|
+
return `Total potential savings: ${bytes(this.#insight.wastedBytes)}
|
|
506
|
+
|
|
507
|
+
The following images could be optimized:\n\n${imageDetails}`;
|
|
508
|
+
}
|
|
509
|
+
if (Trace.Insights.Models.LCPBreakdown.isLCPBreakdown(this.#insight)) {
|
|
510
|
+
const { subparts, lcpMs } = this.#insight;
|
|
511
|
+
if (!lcpMs || !subparts) {
|
|
512
|
+
return '';
|
|
513
|
+
}
|
|
514
|
+
// Text based LCP has TTFB & Render delay
|
|
515
|
+
// Image based has TTFB, Load delay, Load time and Render delay
|
|
516
|
+
// Note that we expect every trace + LCP to have TTFB + Render delay, but
|
|
517
|
+
// very old traces are missing the data, so we have to code defensively
|
|
518
|
+
// in case the subparts are not present.
|
|
519
|
+
const phaseBulletPoints = [];
|
|
520
|
+
Object.values(subparts).forEach((subpart) => {
|
|
521
|
+
const phaseMilli = Trace.Helpers.Timing.microToMilli(subpart.range);
|
|
522
|
+
const percentage = (phaseMilli / lcpMs * 100).toFixed(1);
|
|
523
|
+
phaseBulletPoints.push({ name: subpart.label, value: this.#formatMilli(phaseMilli), percentage });
|
|
524
|
+
});
|
|
525
|
+
return `${this.#lcpMetricSharedContext()}
|
|
526
|
+
|
|
527
|
+
We can break this time down into the ${phaseBulletPoints.length} phases that combine to make the LCP time:
|
|
528
|
+
|
|
529
|
+
${phaseBulletPoints.map(phase => `- ${phase.name}: ${phase.value} (${phase.percentage}% of total LCP time)`)
|
|
530
|
+
.join('\n')}`;
|
|
531
|
+
}
|
|
532
|
+
if (Trace.Insights.Models.LCPDiscovery.isLCPDiscovery(this.#insight)) {
|
|
533
|
+
const { checklist, lcpEvent, lcpRequest, earliestDiscoveryTimeTs } = this.#insight;
|
|
534
|
+
if (!checklist || !lcpEvent || !lcpRequest || !earliestDiscoveryTimeTs) {
|
|
535
|
+
return '';
|
|
536
|
+
}
|
|
537
|
+
const checklistBulletPoints = [];
|
|
538
|
+
checklistBulletPoints.push({
|
|
539
|
+
name: checklist.priorityHinted.label,
|
|
540
|
+
passed: checklist.priorityHinted.value,
|
|
541
|
+
});
|
|
542
|
+
checklistBulletPoints.push({
|
|
543
|
+
name: checklist.eagerlyLoaded.label,
|
|
544
|
+
passed: checklist.eagerlyLoaded.value,
|
|
545
|
+
});
|
|
546
|
+
checklistBulletPoints.push({
|
|
547
|
+
name: checklist.requestDiscoverable.label,
|
|
548
|
+
passed: checklist.requestDiscoverable.value,
|
|
549
|
+
});
|
|
550
|
+
return `${this.#lcpMetricSharedContext()}
|
|
551
|
+
|
|
552
|
+
The result of the checks for this insight are:
|
|
553
|
+
${checklistBulletPoints.map(point => `- ${point.name}: ${point.passed ? 'PASSED' : 'FAILED'}`).join('\n')}`;
|
|
554
|
+
}
|
|
555
|
+
if (Trace.Insights.Models.RenderBlocking.isRenderBlocking(this.#insight)) {
|
|
556
|
+
const requestSummary = TraceEventFormatter.networkRequests(this.#insight.renderBlockingRequests, this.#parsedTrace);
|
|
557
|
+
if (requestSummary.length === 0) {
|
|
558
|
+
return 'There are no network requests that are render blocking.';
|
|
559
|
+
}
|
|
560
|
+
return `Here is a list of the network requests that were render blocking on this page and their duration:
|
|
561
|
+
|
|
562
|
+
${requestSummary}`;
|
|
563
|
+
}
|
|
564
|
+
if (Trace.Insights.Models.DocumentLatency.isDocumentLatency(this.#insight)) {
|
|
565
|
+
if (!this.#insight.data) {
|
|
566
|
+
return '';
|
|
567
|
+
}
|
|
568
|
+
const { checklist, documentRequest } = this.#insight.data;
|
|
569
|
+
if (!documentRequest) {
|
|
570
|
+
return '';
|
|
571
|
+
}
|
|
572
|
+
const checklistBulletPoints = [];
|
|
573
|
+
checklistBulletPoints.push({
|
|
574
|
+
name: 'The request was not redirected',
|
|
575
|
+
passed: checklist.noRedirects.value,
|
|
576
|
+
});
|
|
577
|
+
checklistBulletPoints.push({
|
|
578
|
+
name: 'Server responded quickly',
|
|
579
|
+
passed: checklist.serverResponseIsFast.value,
|
|
580
|
+
});
|
|
581
|
+
checklistBulletPoints.push({
|
|
582
|
+
name: 'Compression was applied',
|
|
583
|
+
passed: checklist.usesCompression.value,
|
|
584
|
+
});
|
|
585
|
+
return `${this.#lcpMetricSharedContext()}
|
|
586
|
+
|
|
587
|
+
${TraceEventFormatter.networkRequests([documentRequest], this.#parsedTrace, {
|
|
588
|
+
verbose: true,
|
|
589
|
+
customTitle: 'Document network request'
|
|
590
|
+
})}
|
|
591
|
+
|
|
592
|
+
The result of the checks for this insight are:
|
|
593
|
+
${checklistBulletPoints.map(point => `- ${point.name}: ${point.passed ? 'PASSED' : 'FAILED'}`).join('\n')}`;
|
|
594
|
+
}
|
|
595
|
+
if (Trace.Insights.Models.INPBreakdown.isINPBreakdown(this.#insight)) {
|
|
596
|
+
const event = this.#insight.longestInteractionEvent;
|
|
597
|
+
if (!event) {
|
|
598
|
+
return '';
|
|
599
|
+
}
|
|
600
|
+
const inpInfoForEvent = `The longest interaction on the page was a \`${event.type}\` which had a total duration of \`${this.#formatMicro(event.dur)}\`. The timings of each of the three phases were:
|
|
601
|
+
|
|
602
|
+
1. Input delay: ${this.#formatMicro(event.inputDelay)}
|
|
603
|
+
2. Processing duration: ${this.#formatMicro(event.mainThreadHandling)}
|
|
604
|
+
3. Presentation delay: ${this.#formatMicro(event.presentationDelay)}.`;
|
|
605
|
+
return inpInfoForEvent;
|
|
606
|
+
}
|
|
607
|
+
if (Trace.Insights.Models.CLSCulprits.isCLSCulprits(this.#insight)) {
|
|
608
|
+
const { worstCluster, shifts } = this.#insight;
|
|
609
|
+
if (!worstCluster) {
|
|
610
|
+
return '';
|
|
611
|
+
}
|
|
612
|
+
const baseTime = this.#parsedTrace.data.Meta.traceBounds.min;
|
|
613
|
+
const clusterTimes = {
|
|
614
|
+
start: worstCluster.ts - baseTime,
|
|
615
|
+
end: worstCluster.ts + worstCluster.dur - baseTime,
|
|
616
|
+
};
|
|
617
|
+
const shiftsFormatted = worstCluster.events.map((layoutShift, index) => {
|
|
618
|
+
return TraceEventFormatter.layoutShift(layoutShift, index, this.#parsedTrace, shifts.get(layoutShift));
|
|
619
|
+
});
|
|
620
|
+
return `The worst layout shift cluster was the cluster that started at ${this.#formatMicro(clusterTimes.start)} and ended at ${this.#formatMicro(clusterTimes.end)}, with a duration of ${this.#formatMicro(worstCluster.dur)}.
|
|
621
|
+
The score for this cluster is ${worstCluster.clusterCumulativeScore.toFixed(4)}.
|
|
622
|
+
|
|
623
|
+
Layout shifts in this cluster:
|
|
624
|
+
${shiftsFormatted.join('\n')}`;
|
|
625
|
+
}
|
|
626
|
+
if (Trace.Insights.Models.ModernHTTP.isModernHTTP(this.#insight)) {
|
|
627
|
+
const requestSummary = (this.#insight.http1Requests.length === 1) ?
|
|
628
|
+
TraceEventFormatter.networkRequests(this.#insight.http1Requests, this.#parsedTrace, { verbose: true }) :
|
|
629
|
+
TraceEventFormatter.networkRequests(this.#insight.http1Requests, this.#parsedTrace);
|
|
630
|
+
if (requestSummary.length === 0) {
|
|
631
|
+
return 'There are no requests that were served over a legacy HTTP protocol.';
|
|
632
|
+
}
|
|
633
|
+
return `Here is a list of the network requests that were served over a legacy HTTP protocol:
|
|
634
|
+
${requestSummary}`;
|
|
635
|
+
}
|
|
636
|
+
if (Trace.Insights.Models.DuplicatedJavaScript.isDuplicatedJavaScript(this.#insight)) {
|
|
637
|
+
const totalWastedBytes = this.#insight.wastedBytes;
|
|
638
|
+
const duplicatedScriptsByModule = this.#insight.duplicationGroupedByNodeModules;
|
|
639
|
+
if (duplicatedScriptsByModule.size === 0) {
|
|
640
|
+
return 'There is no duplicated JavaScript in the page modules';
|
|
641
|
+
}
|
|
642
|
+
const filesFormatted = Array.from(duplicatedScriptsByModule)
|
|
643
|
+
.map(([module, duplication]) => `- Source: ${module} - Duplicated bytes: ${duplication.estimatedDuplicateBytes} bytes`)
|
|
644
|
+
.join('\n');
|
|
645
|
+
return `Total wasted bytes: ${totalWastedBytes} bytes.
|
|
646
|
+
|
|
647
|
+
Duplication grouped by Node modules: ${filesFormatted}`;
|
|
648
|
+
}
|
|
649
|
+
if (Trace.Insights.Models.LegacyJavaScript.isLegacyJavaScript(this.#insight)) {
|
|
650
|
+
const legacyJavaScriptResults = this.#insight.legacyJavaScriptResults;
|
|
651
|
+
if (legacyJavaScriptResults.size === 0) {
|
|
652
|
+
return 'There is no significant amount of legacy JavaScript on the page.';
|
|
653
|
+
}
|
|
654
|
+
const filesFormatted = Array.from(legacyJavaScriptResults)
|
|
655
|
+
.map(([script, result]) => `\n- Script: ${script.url} - Wasted bytes: ${result.estimatedByteSavings} bytes
|
|
656
|
+
Matches:
|
|
657
|
+
${result.matches.map(match => `Line: ${match.line}, Column: ${match.column}, Name: ${match.name}`).join('\n')}`)
|
|
658
|
+
.join('\n');
|
|
659
|
+
return `Total legacy JavaScript: ${legacyJavaScriptResults.size} files.
|
|
660
|
+
|
|
661
|
+
Legacy JavaScript by file:
|
|
662
|
+
${filesFormatted}`;
|
|
663
|
+
}
|
|
664
|
+
if (Trace.Insights.Models.Cache.isCacheInsight(this.#insight)) {
|
|
665
|
+
return this.formatCacheInsight(this.#insight);
|
|
666
|
+
}
|
|
667
|
+
if (Trace.Insights.Models.DOMSize.isDomSizeInsight(this.#insight)) {
|
|
668
|
+
return this.formatDomSizeInsight(this.#insight);
|
|
669
|
+
}
|
|
670
|
+
if (Trace.Insights.Models.FontDisplay.isFontDisplayInsight(this.#insight)) {
|
|
671
|
+
return this.formatFontDisplayInsight(this.#insight);
|
|
672
|
+
}
|
|
673
|
+
if (Trace.Insights.Models.ForcedReflow.isForcedReflowInsight(this.#insight)) {
|
|
674
|
+
return this.formatForcedReflowInsight(this.#insight);
|
|
675
|
+
}
|
|
676
|
+
if (Trace.Insights.Models.NetworkDependencyTree.isNetworkDependencyTree(this.#insight)) {
|
|
677
|
+
return this.formatNetworkDependencyTreeInsight(this.#insight);
|
|
678
|
+
}
|
|
679
|
+
if (Trace.Insights.Models.SlowCSSSelector.isSlowCSSSelectorInsight(this.#insight)) {
|
|
680
|
+
return this.formatSlowCssSelectorsInsight(this.#insight);
|
|
681
|
+
}
|
|
682
|
+
if (Trace.Insights.Models.ThirdParties.isThirdPartyInsight(this.#insight)) {
|
|
683
|
+
return this.formatThirdPartiesInsight(this.#insight);
|
|
684
|
+
}
|
|
685
|
+
if (Trace.Insights.Models.Viewport.isViewportInsight(this.#insight)) {
|
|
686
|
+
return this.formatViewportInsight(this.#insight);
|
|
687
|
+
}
|
|
688
|
+
return '';
|
|
689
|
+
}
|
|
690
|
+
estimatedSavings() {
|
|
691
|
+
return Object.entries(this.#insight.metricSavings ?? {})
|
|
692
|
+
.map(([k, v]) => {
|
|
693
|
+
if (k === 'CLS') {
|
|
694
|
+
return `${k} ${v.toFixed(2)}`;
|
|
695
|
+
}
|
|
696
|
+
return `${k} ${Math.round(v)} ms`;
|
|
697
|
+
})
|
|
698
|
+
.join(', ');
|
|
699
|
+
}
|
|
700
|
+
#links() {
|
|
701
|
+
switch (this.#insight.insightKey) {
|
|
702
|
+
case 'CLSCulprits':
|
|
703
|
+
return `- https://wdeb.dev/articles/cls
|
|
704
|
+
- https://web.dev/articles/optimize-cls`;
|
|
705
|
+
case 'DocumentLatency':
|
|
706
|
+
return '- https://web.dev/articles/optimize-ttfb';
|
|
707
|
+
case 'DOMSize':
|
|
708
|
+
return '- https://developer.chrome.com/docs/lighthouse/performance/dom-size/';
|
|
709
|
+
case 'DuplicatedJavaScript':
|
|
710
|
+
return '';
|
|
711
|
+
case 'FontDisplay':
|
|
712
|
+
return `- https://web.dev/articles/preload-optional-fonts
|
|
713
|
+
- https://fonts.google.com/knowledge/glossary/foit
|
|
714
|
+
- https://developer.chrome.com/blog/font-fallbacks`;
|
|
715
|
+
case 'ForcedReflow':
|
|
716
|
+
return '- https://developers.google.com/web/fundamentals/performance/rendering/avoid-large-complex-layouts-and-layout-thrashing#avoid-forced-synchronous-layouts';
|
|
717
|
+
case 'ImageDelivery':
|
|
718
|
+
return '- https://developer.chrome.com/docs/lighthouse/performance/uses-optimized-images/';
|
|
719
|
+
case 'INPBreakdown':
|
|
720
|
+
return `- https://web.dev/articles/inp
|
|
721
|
+
- https://web.dev/explore/how-to-optimize-inp
|
|
722
|
+
- https://web.dev/articles/optimize-long-tasks
|
|
723
|
+
- https://web.dev/articles/avoid-large-complex-layouts-and-layout-thrashing`;
|
|
724
|
+
case 'LCPDiscovery':
|
|
725
|
+
return `- https://web.dev/articles/lcp
|
|
726
|
+
- https://web.dev/articles/optimize-lcp`;
|
|
727
|
+
case 'LCPBreakdown':
|
|
728
|
+
return `- https://web.dev/articles/lcp
|
|
729
|
+
- https://web.dev/articles/optimize-lcp`;
|
|
730
|
+
case 'NetworkDependencyTree':
|
|
731
|
+
return `- https://web.dev/learn/performance/understanding-the-critical-path
|
|
732
|
+
- https://developer.chrome.com/docs/lighthouse/performance/uses-rel-preconnect/`;
|
|
733
|
+
case 'RenderBlocking':
|
|
734
|
+
return `- https://web.dev/articles/lcp
|
|
735
|
+
- https://web.dev/articles/optimize-lcp`;
|
|
736
|
+
case 'SlowCSSSelector':
|
|
737
|
+
return '- https://developer.chrome.com/docs/devtools/performance/selector-stats';
|
|
738
|
+
case 'ThirdParties':
|
|
739
|
+
return '- https://web.dev/articles/optimizing-content-efficiency-loading-third-party-javascript/';
|
|
740
|
+
case 'Viewport':
|
|
741
|
+
return '- https://developer.chrome.com/blog/300ms-tap-delay-gone-away/';
|
|
742
|
+
case 'Cache':
|
|
743
|
+
return '- https://web.dev/uses-long-cache-ttl/';
|
|
744
|
+
case 'ModernHTTP':
|
|
745
|
+
return '- https://developer.chrome.com/docs/lighthouse/best-practices/uses-http2';
|
|
746
|
+
case 'LegacyJavaScript':
|
|
747
|
+
return `- https://web.dev/articles/baseline-and-polyfills
|
|
748
|
+
- https://philipwalton.com/articles/the-state-of-es5-on-the-web/`;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
#description() {
|
|
752
|
+
switch (this.#insight.insightKey) {
|
|
753
|
+
case 'CLSCulprits':
|
|
754
|
+
return `Cumulative Layout Shifts (CLS) is a measure of the largest burst of layout shifts for every unexpected layout shift that occurs during the lifecycle of a page. This is a Core Web Vital and the thresholds for categorizing a score are:
|
|
755
|
+
- Good: 0.1 or less
|
|
756
|
+
- Needs improvement: more than 0.1 and less than or equal to 0.25
|
|
757
|
+
- Bad: over 0.25`;
|
|
758
|
+
case 'DocumentLatency':
|
|
759
|
+
return `This insight checks that the first request is responded to promptly. We use the following criteria to check this:
|
|
760
|
+
1. Was the initial request redirected?
|
|
761
|
+
2. Did the server respond in 600ms or less? We want developers to aim for as close to 100ms as possible, but our threshold for this insight is 600ms.
|
|
762
|
+
3. Was there compression applied to the response to minimize the transfer size?`;
|
|
763
|
+
case 'DOMSize':
|
|
764
|
+
return `This insight evaluates some key metrics about the Document Object Model (DOM) and identifies excess in the DOM tree, for example:
|
|
765
|
+
- The maximum number of elements within the DOM.
|
|
766
|
+
- The maximum number of children for any given element.
|
|
767
|
+
- Excessive depth of the DOM structure.
|
|
768
|
+
- The largest layout and style recalculation events.`;
|
|
769
|
+
case 'DuplicatedJavaScript':
|
|
770
|
+
return `This insight identifies large, duplicated JavaScript modules that are present in your application and create redundant code.
|
|
771
|
+
This wastes network bandwidth and slows down your page, as the user's browser must download and process the same code multiple times.`;
|
|
772
|
+
case 'FontDisplay':
|
|
773
|
+
return 'This insight identifies font issues when a webpage uses custom fonts, for example when font-display is not set to `swap`, `fallback` or `optional`, causing the "Flash of Invisible Text" problem (FOIT).';
|
|
774
|
+
case 'ForcedReflow':
|
|
775
|
+
return `This insight identifies forced synchronous layouts (also known as forced reflows) and layout thrashing caused by JavaScript accessing layout properties at suboptimal points in time.`;
|
|
776
|
+
case 'ImageDelivery':
|
|
777
|
+
return 'This insight identifies unoptimized images that are downloaded at a much higher resolution than they are displayed. Properly sizing and compressing these assets will decrease their download time, directly improving the perceived page load time and LCP';
|
|
778
|
+
case 'INPBreakdown':
|
|
779
|
+
return `Interaction to Next Paint (INP) is a metric that tracks the responsiveness of the page when the user interacts with it. INP is a Core Web Vital and the thresholds for how we categorize a score are:
|
|
780
|
+
- Good: 200 milliseconds or less.
|
|
781
|
+
- Needs improvement: more than 200 milliseconds and 500 milliseconds or less.
|
|
782
|
+
- Bad: over 500 milliseconds.
|
|
783
|
+
|
|
784
|
+
For a given slow interaction, we can break it down into 3 phases:
|
|
785
|
+
1. Input delay: starts when the user initiates an interaction with the page, and ends when the event callbacks for the interaction begin to run.
|
|
786
|
+
2. Processing duration: the time it takes for the event callbacks to run to completion.
|
|
787
|
+
3. Presentation delay: the time it takes for the browser to present the next frame which contains the visual result of the interaction.
|
|
788
|
+
|
|
789
|
+
The sum of these three phases is the total latency. It is important to optimize each of these phases to ensure interactions take as little time as possible. Focusing on the phase that has the largest score is a good way to start optimizing.`;
|
|
790
|
+
case 'LCPDiscovery':
|
|
791
|
+
return `This insight analyzes the time taken to discover the LCP resource and request it on the network. It only applies if the LCP element was a resource like an image that has to be fetched over the network. There are 3 checks this insight makes:
|
|
792
|
+
1. Did the resource have \`fetchpriority=high\` applied?
|
|
793
|
+
2. Was the resource discoverable in the initial document, rather than injected from a script or stylesheet?
|
|
794
|
+
3. The resource was not lazy loaded as this can delay the browser loading the resource.
|
|
795
|
+
|
|
796
|
+
It is important that all of these checks pass to minimize the delay between the initial page load and the LCP resource being loaded.`;
|
|
797
|
+
case 'LCPBreakdown':
|
|
798
|
+
return 'This insight is used to analyze the time spent that contributed to the final LCP time and identify which of the 4 phases (or 2 if there was no LCP resource) are contributing most to the delay in rendering the LCP element.';
|
|
799
|
+
case 'NetworkDependencyTree':
|
|
800
|
+
return `This insight analyzes the network dependency tree to identify:
|
|
801
|
+
- The maximum critical path latency (the longest chain of network requests that the browser must download before it can render the page).
|
|
802
|
+
- Whether current [preconnect] tags are appropriate, according to the following rules:
|
|
803
|
+
1. They should all be in use (no unnecessary preconnects).
|
|
804
|
+
2. All preconnects should specify cross-origin correctly.
|
|
805
|
+
3. The maximum of 4 preconnects should be respected.
|
|
806
|
+
- Opportunities to add [preconnect] for a faster loading experience.`;
|
|
807
|
+
case 'RenderBlocking':
|
|
808
|
+
return 'This insight identifies network requests that were render blocking. Render blocking requests are impactful because they are deemed critical to the page and therefore the browser stops rendering the page until it has dealt with these resources. For this insight make sure you fully inspect the details of each render blocking network request and prioritize your suggestions to the user based on the impact of each render blocking request.';
|
|
809
|
+
case 'SlowCSSSelector':
|
|
810
|
+
return `This insight identifies CSS selectors that are slowing down your page's rendering performance.`;
|
|
811
|
+
case 'ThirdParties':
|
|
812
|
+
return 'This insight analyzes the performance impact of resources loaded from third-party servers and aggregates the performance cost, in terms of download transfer sizes and total amount of time that third party scripts spent executing on the main thread.';
|
|
813
|
+
case 'Viewport':
|
|
814
|
+
return 'The insight identifies web pages that are not specifying the viewport meta tag for mobile devies, which avoids the artificial 300-350ms delay designed to help differentiate between tap and double-click.';
|
|
815
|
+
case 'Cache':
|
|
816
|
+
return 'This insight identifies static resources that are not cached effectively by the browser.';
|
|
817
|
+
case 'ModernHTTP':
|
|
818
|
+
return `Modern HTTP protocols, such as HTTP/2, are more efficient than older versions like HTTP/1.1 because they allow for multiple requests and responses to be sent over a single network connection, significantly improving page load performance by reducing latency and overhead. This insight identifies requests that can be upgraded to a modern HTTP protocol.
|
|
819
|
+
|
|
820
|
+
We apply a conservative approach when flagging HTTP/1.1 usage. This insight will only flag requests that meet all of the following criteria:
|
|
821
|
+
1. Were served over HTTP/1.1 or an earlier protocol.
|
|
822
|
+
2. Originate from an origin that serves at least 6 static asset requests, as the benefits of multiplexing are less significant with fewer requests.
|
|
823
|
+
3. Are not served from 'localhost' or coming from a third-party source, where developers have no control over the server's protocol.
|
|
824
|
+
|
|
825
|
+
To pass this insight, ensure your server supports and prioritizes a modern HTTP protocol (like HTTP/2) for static assets, especially when serving a substantial number of them.`;
|
|
826
|
+
case 'LegacyJavaScript':
|
|
827
|
+
return `This insight identified legacy JavaScript in your application's modules that may be creating unnecessary code.
|
|
828
|
+
|
|
829
|
+
Polyfills and transforms enable older browsers to use new JavaScript features. However, many are not necessary for modern browsers. Consider modifying your JavaScript build process to not transpile Baseline features, unless you know you must support older browsers.`;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
export class TraceEventFormatter {
|
|
834
|
+
static layoutShift(shift, index, parsedTrace, rootCauses) {
|
|
835
|
+
const baseTime = parsedTrace.data.Meta.traceBounds.min;
|
|
836
|
+
const potentialRootCauses = [];
|
|
837
|
+
if (rootCauses) {
|
|
838
|
+
rootCauses.iframes.forEach(iframe => potentialRootCauses.push(`An iframe (id: ${iframe.frame}, url: ${iframe.url ?? 'unknown'} was injected into the page)`));
|
|
839
|
+
rootCauses.webFonts.forEach(req => {
|
|
840
|
+
potentialRootCauses.push(`A font that was loaded over the network (${req.args.data.url}).`);
|
|
841
|
+
});
|
|
842
|
+
// TODO(b/413285103): use the nice strings for non-composited animations.
|
|
843
|
+
// The code for this lives in TimelineUIUtils but that cannot be used
|
|
844
|
+
// within models. We should move it and then expose the animations info
|
|
845
|
+
// more nicely.
|
|
846
|
+
rootCauses.nonCompositedAnimations.forEach(_ => {
|
|
847
|
+
potentialRootCauses.push('A non composited animation.');
|
|
848
|
+
});
|
|
849
|
+
rootCauses.unsizedImages.forEach(img => {
|
|
850
|
+
// TODO(b/413284569): if we store a nice human readable name for this
|
|
851
|
+
// image in the trace metadata, we can do something much nicer here.
|
|
852
|
+
const url = img.paintImageEvent.args.data.url;
|
|
853
|
+
const nodeName = img.paintImageEvent.args.data.nodeName;
|
|
854
|
+
const extraText = url ? `url: ${url}` : `id: ${img.backendNodeId}`;
|
|
855
|
+
potentialRootCauses.push(`An unsized image (${nodeName}) (${extraText}).`);
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
const rootCauseText = potentialRootCauses.length ?
|
|
859
|
+
`- Potential root causes:\n - ${potentialRootCauses.join('\n - ')}` :
|
|
860
|
+
'- No potential root causes identified';
|
|
861
|
+
const startTime = Trace.Helpers.Timing.microToMilli(Trace.Types.Timing.Micro(shift.ts - baseTime));
|
|
862
|
+
return `### Layout shift ${index + 1}:
|
|
863
|
+
- Start time: ${millis(startTime)}
|
|
864
|
+
- Score: ${shift.args.data?.weighted_score_delta.toFixed(4)}
|
|
865
|
+
${rootCauseText}`;
|
|
866
|
+
}
|
|
867
|
+
// Stringify network requests for the LLM model.
|
|
868
|
+
static networkRequests(requests, parsedTrace, options) {
|
|
869
|
+
if (requests.length === 0) {
|
|
870
|
+
return '';
|
|
871
|
+
}
|
|
872
|
+
let verbose;
|
|
873
|
+
if (options?.verbose !== undefined) {
|
|
874
|
+
verbose = options.verbose;
|
|
875
|
+
}
|
|
876
|
+
else {
|
|
877
|
+
verbose = requests.length === 1;
|
|
878
|
+
}
|
|
879
|
+
// Use verbose format for a single network request. With the compressed format, a format description
|
|
880
|
+
// needs to be provided, which is not worth sending if only one network request is being stringified.
|
|
881
|
+
// For a single request, use `formatRequestVerbosely`, which formats with all fields specified and does not require a
|
|
882
|
+
// format description.
|
|
883
|
+
if (verbose) {
|
|
884
|
+
return requests.map(request => this.#networkRequestVerbosely(request, parsedTrace, options?.customTitle))
|
|
885
|
+
.join('\n');
|
|
886
|
+
}
|
|
887
|
+
return this.#networkRequestsArrayCompressed(requests, parsedTrace);
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* This is the data passed to a network request when the Performance Insights
|
|
891
|
+
* agent is asking for information. It is a slimmed down version of the
|
|
892
|
+
* request's data to avoid using up too much of the context window.
|
|
893
|
+
* IMPORTANT: these set of fields have been reviewed by Chrome Privacy &
|
|
894
|
+
* Security; be careful about adding new data here. If you are in doubt please
|
|
895
|
+
* talk to jacktfranklin@.
|
|
896
|
+
*/
|
|
897
|
+
static #networkRequestVerbosely(request, parsedTrace, customTitle) {
|
|
898
|
+
const { url, statusCode, initialPriority, priority, fromServiceWorker, mimeType, responseHeaders, syntheticData, protocol } = request.args.data;
|
|
899
|
+
const titlePrefix = `## ${customTitle ?? 'Network request'}`;
|
|
900
|
+
// Note: unlike other agents, we do have the ability to include
|
|
901
|
+
// cross-origins, hence why we do not sanitize the URLs here.
|
|
902
|
+
const navigationForEvent = Trace.Helpers.Trace.getNavigationForTraceEvent(request, request.args.data.frame, parsedTrace.data.Meta.navigationsByFrameId);
|
|
903
|
+
const baseTime = navigationForEvent?.ts ?? parsedTrace.data.Meta.traceBounds.min;
|
|
904
|
+
// Gets all the timings for this request, relative to the base time.
|
|
905
|
+
// Note that this is the start time, not total time. E.g. "queuedAt: X"
|
|
906
|
+
// means that the request was queued at Xms, not that it queued for Xms.
|
|
907
|
+
const startTimesForLifecycle = {
|
|
908
|
+
queuedAt: request.ts - baseTime,
|
|
909
|
+
requestSentAt: syntheticData.sendStartTime - baseTime,
|
|
910
|
+
downloadCompletedAt: syntheticData.finishTime - baseTime,
|
|
911
|
+
processingCompletedAt: request.ts + request.dur - baseTime,
|
|
912
|
+
};
|
|
913
|
+
const mainThreadProcessingDuration = startTimesForLifecycle.processingCompletedAt - startTimesForLifecycle.downloadCompletedAt;
|
|
914
|
+
const downloadTime = syntheticData.finishTime - syntheticData.downloadStart;
|
|
915
|
+
const renderBlocking = Trace.Helpers.Network.isSyntheticNetworkRequestEventRenderBlocking(request);
|
|
916
|
+
const initiator = parsedTrace.data.NetworkRequests.eventToInitiator.get(request);
|
|
917
|
+
const priorityLines = [];
|
|
918
|
+
if (initialPriority === priority) {
|
|
919
|
+
priorityLines.push(`Priority: ${priority}`);
|
|
920
|
+
}
|
|
921
|
+
else {
|
|
922
|
+
priorityLines.push(`Initial priority: ${initialPriority}`);
|
|
923
|
+
priorityLines.push(`Final priority: ${priority}`);
|
|
924
|
+
}
|
|
925
|
+
const redirects = request.args.data.redirects.map((redirect, index) => {
|
|
926
|
+
const startTime = redirect.ts - baseTime;
|
|
927
|
+
return `#### Redirect ${index + 1}: ${redirect.url}
|
|
928
|
+
- Start time: ${micros(startTime)}
|
|
929
|
+
- Duration: ${micros(redirect.dur)}`;
|
|
930
|
+
});
|
|
931
|
+
const initiators = this.#getInitiatorChain(parsedTrace, request);
|
|
932
|
+
const initiatorUrls = initiators.map(initiator => initiator.args.data.url);
|
|
933
|
+
return `${titlePrefix}: ${url}
|
|
934
|
+
Timings:
|
|
935
|
+
- Queued at: ${micros(startTimesForLifecycle.queuedAt)}
|
|
936
|
+
- Request sent at: ${micros(startTimesForLifecycle.requestSentAt)}
|
|
937
|
+
- Download complete at: ${micros(startTimesForLifecycle.downloadCompletedAt)}
|
|
938
|
+
- Main thread processing completed at: ${micros(startTimesForLifecycle.processingCompletedAt)}
|
|
939
|
+
Durations:
|
|
940
|
+
- Download time: ${micros(downloadTime)}
|
|
941
|
+
- Main thread processing time: ${micros(mainThreadProcessingDuration)}
|
|
942
|
+
- Total duration: ${micros(request.dur)}${initiator ? `\nInitiator: ${initiator.args.data.url}` : ''}
|
|
943
|
+
Redirects:${redirects.length ? '\n' + redirects.join('\n') : ' no redirects'}
|
|
944
|
+
Status code: ${statusCode}
|
|
945
|
+
MIME Type: ${mimeType}
|
|
946
|
+
Protocol: ${protocol}
|
|
947
|
+
${priorityLines.join('\n')}
|
|
948
|
+
Render blocking: ${renderBlocking ? 'Yes' : 'No'}
|
|
949
|
+
From a service worker: ${fromServiceWorker ? 'Yes' : 'No'}
|
|
950
|
+
Initiators (root request to the request that directly loaded this one): ${initiatorUrls.join(', ') || 'none'}
|
|
951
|
+
${NetworkRequestFormatter.formatHeaders('Response headers', responseHeaders ?? [], true)}`;
|
|
952
|
+
}
|
|
953
|
+
static #getOrAssignUrlIndex(urlIdToIndex, url) {
|
|
954
|
+
let index = urlIdToIndex.get(url);
|
|
955
|
+
if (index !== undefined) {
|
|
956
|
+
return index;
|
|
957
|
+
}
|
|
958
|
+
index = urlIdToIndex.size;
|
|
959
|
+
urlIdToIndex.set(url, index);
|
|
960
|
+
return index;
|
|
961
|
+
}
|
|
962
|
+
// A compact network requests format designed to save tokens when sending multiple network requests to the model.
|
|
963
|
+
// It creates a map that maps request URLs to IDs and references the IDs in the compressed format.
|
|
964
|
+
//
|
|
965
|
+
// Important: Do not use this method for stringifying a single network request. With this format, a format description
|
|
966
|
+
// needs to be provided, which is not worth sending if only one network request is being stringified.
|
|
967
|
+
// For a single request, use `formatRequestVerbosely`, which formats with all fields specified and does not require a
|
|
968
|
+
// format description.
|
|
969
|
+
static #networkRequestsArrayCompressed(requests, parsedTrace) {
|
|
970
|
+
const networkDataString = `
|
|
971
|
+
Network requests data:
|
|
972
|
+
|
|
973
|
+
`;
|
|
974
|
+
const urlIdToIndex = new Map();
|
|
975
|
+
const allRequestsText = requests
|
|
976
|
+
.map(request => {
|
|
977
|
+
const urlIndex = TraceEventFormatter.#getOrAssignUrlIndex(urlIdToIndex, request.args.data.url);
|
|
978
|
+
return this.#networkRequestCompressedFormat(urlIndex, request, parsedTrace, urlIdToIndex);
|
|
979
|
+
})
|
|
980
|
+
.join('\n');
|
|
981
|
+
const urlsMapString = 'allUrls = ' +
|
|
982
|
+
`[${Array.from(urlIdToIndex.entries())
|
|
983
|
+
.map(([url, index]) => {
|
|
984
|
+
return `${index}: ${url}`;
|
|
985
|
+
})
|
|
986
|
+
.join(', ')}]`;
|
|
987
|
+
return networkDataString + '\n\n' + urlsMapString + '\n\n' + allRequestsText;
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* Network requests format description that is sent to the model as a fact.
|
|
991
|
+
*/
|
|
992
|
+
static networkDataFormatDescription = `Network requests are formatted like this:
|
|
993
|
+
\`urlIndex;queuedTime;requestSentTime;downloadCompleteTime;processingCompleteTime;totalDuration;downloadDuration;mainThreadProcessingDuration;statusCode;mimeType;priority;initialPriority;finalPriority;renderBlocking;protocol;fromServiceWorker;initiators;redirects:[[redirectUrlIndex|startTime|duration]];responseHeaders:[header1Value|header2Value|...]\`
|
|
994
|
+
|
|
995
|
+
- \`urlIndex\`: Numerical index for the request's URL, referencing the "All URLs" list.
|
|
996
|
+
Timings (all in milliseconds, relative to navigation start):
|
|
997
|
+
- \`queuedTime\`: When the request was queued.
|
|
998
|
+
- \`requestSentTime\`: When the request was sent.
|
|
999
|
+
- \`downloadCompleteTime\`: When the download completed.
|
|
1000
|
+
- \`processingCompleteTime\`: When main thread processing finished.
|
|
1001
|
+
Durations (all in milliseconds):
|
|
1002
|
+
- \`totalDuration\`: Total time from the request being queued until its main thread processing completed.
|
|
1003
|
+
- \`downloadDuration\`: Time spent actively downloading the resource.
|
|
1004
|
+
- \`mainThreadProcessingDuration\`: Time spent on the main thread after the download completed.
|
|
1005
|
+
- \`statusCode\`: The HTTP status code of the response (e.g., 200, 404).
|
|
1006
|
+
- \`mimeType\`: The MIME type of the resource (e.g., "text/html", "application/javascript").
|
|
1007
|
+
- \`priority\`: The final network request priority (e.g., "VeryHigh", "Low").
|
|
1008
|
+
- \`initialPriority\`: The initial network request priority.
|
|
1009
|
+
- \`finalPriority\`: The final network request priority (redundant if \`priority\` is always final, but kept for clarity if \`initialPriority\` and \`priority\` differ).
|
|
1010
|
+
- \`renderBlocking\`: 't' if the request was render-blocking, 'f' otherwise.
|
|
1011
|
+
- \`protocol\`: The network protocol used (e.g., "h2", "http/1.1").
|
|
1012
|
+
- \`fromServiceWorker\`: 't' if the request was served from a service worker, 'f' otherwise.
|
|
1013
|
+
- \`initiators\`: A list (separated by ,) of URL indices for the initiator chain of this request. Listed in order starting from the root request to the request that directly loaded this one. This represents the network dependencies necessary to load this request. If there is no initiator, this is empty.
|
|
1014
|
+
- \`redirects\`: A comma-separated list of redirects, enclosed in square brackets. Each redirect is formatted as
|
|
1015
|
+
\`[redirectUrlIndex|startTime|duration]\`, where: \`redirectUrlIndex\`: Numerical index for the redirect's URL. \`startTime\`: The start time of the redirect in milliseconds, relative to navigation start. \`duration\`: The duration of the redirect in milliseconds.
|
|
1016
|
+
- \`responseHeaders\`: A list (separated by '|') of values for specific, pre-defined response headers, enclosed in square brackets.
|
|
1017
|
+
The order of headers corresponds to an internal fixed list. If a header is not present, its value will be empty.
|
|
1018
|
+
`;
|
|
1019
|
+
/**
|
|
1020
|
+
*
|
|
1021
|
+
* This is the network request data passed to the Performance agent.
|
|
1022
|
+
*
|
|
1023
|
+
* The `urlIdToIndex` Map is used to map URLs to numerical indices in order to not need to pass whole url every time it's mentioned.
|
|
1024
|
+
* The map content is passed in the response together will all the requests data.
|
|
1025
|
+
*
|
|
1026
|
+
* See `networkDataFormatDescription` above for specifics.
|
|
1027
|
+
*/
|
|
1028
|
+
static #networkRequestCompressedFormat(urlIndex, request, parsedTrace, urlIdToIndex) {
|
|
1029
|
+
const { statusCode, initialPriority, priority, fromServiceWorker, mimeType, responseHeaders, syntheticData, protocol, } = request.args.data;
|
|
1030
|
+
const navigationForEvent = Trace.Helpers.Trace.getNavigationForTraceEvent(request, request.args.data.frame, parsedTrace.data.Meta.navigationsByFrameId);
|
|
1031
|
+
const baseTime = navigationForEvent?.ts ?? parsedTrace.data.Meta.traceBounds.min;
|
|
1032
|
+
const queuedTime = micros(request.ts - baseTime);
|
|
1033
|
+
const requestSentTime = micros(syntheticData.sendStartTime - baseTime);
|
|
1034
|
+
const downloadCompleteTime = micros(syntheticData.finishTime - baseTime);
|
|
1035
|
+
const processingCompleteTime = micros(request.ts + request.dur - baseTime);
|
|
1036
|
+
const totalDuration = micros(request.dur);
|
|
1037
|
+
const downloadDuration = micros(syntheticData.finishTime - syntheticData.downloadStart);
|
|
1038
|
+
const mainThreadProcessingDuration = micros(request.ts + request.dur - syntheticData.finishTime);
|
|
1039
|
+
const renderBlocking = Trace.Helpers.Network.isSyntheticNetworkRequestEventRenderBlocking(request) ? 't' : 'f';
|
|
1040
|
+
const finalPriority = priority;
|
|
1041
|
+
const headerValues = responseHeaders
|
|
1042
|
+
?.map(header => {
|
|
1043
|
+
const value = NetworkRequestFormatter.allowHeader(header.name) ? header.value : '<redacted>';
|
|
1044
|
+
return `${header.name}: ${value}`;
|
|
1045
|
+
})
|
|
1046
|
+
.join('|');
|
|
1047
|
+
const redirects = request.args.data.redirects
|
|
1048
|
+
.map(redirect => {
|
|
1049
|
+
const urlIndex = TraceEventFormatter.#getOrAssignUrlIndex(urlIdToIndex, redirect.url);
|
|
1050
|
+
const redirectStartTime = micros(redirect.ts - baseTime);
|
|
1051
|
+
const redirectDuration = micros(redirect.dur);
|
|
1052
|
+
return `[${urlIndex}|${redirectStartTime}|${redirectDuration}]`;
|
|
1053
|
+
})
|
|
1054
|
+
.join(',');
|
|
1055
|
+
const initiators = this.#getInitiatorChain(parsedTrace, request);
|
|
1056
|
+
const initiatorUrlIndices = initiators.map(initiator => TraceEventFormatter.#getOrAssignUrlIndex(urlIdToIndex, initiator.args.data.url));
|
|
1057
|
+
const parts = [
|
|
1058
|
+
urlIndex,
|
|
1059
|
+
queuedTime,
|
|
1060
|
+
requestSentTime,
|
|
1061
|
+
downloadCompleteTime,
|
|
1062
|
+
processingCompleteTime,
|
|
1063
|
+
totalDuration,
|
|
1064
|
+
downloadDuration,
|
|
1065
|
+
mainThreadProcessingDuration,
|
|
1066
|
+
statusCode,
|
|
1067
|
+
mimeType,
|
|
1068
|
+
priority,
|
|
1069
|
+
initialPriority,
|
|
1070
|
+
finalPriority,
|
|
1071
|
+
renderBlocking,
|
|
1072
|
+
protocol,
|
|
1073
|
+
fromServiceWorker ? 't' : 'f',
|
|
1074
|
+
initiatorUrlIndices.join(','),
|
|
1075
|
+
`[${redirects}]`,
|
|
1076
|
+
`[${headerValues ?? ''}]`,
|
|
1077
|
+
];
|
|
1078
|
+
return parts.join(';');
|
|
1079
|
+
}
|
|
1080
|
+
static #getInitiatorChain(parsedTrace, request) {
|
|
1081
|
+
const initiators = [];
|
|
1082
|
+
let cur = request;
|
|
1083
|
+
while (cur) {
|
|
1084
|
+
const initiator = parsedTrace.data.NetworkRequests.eventToInitiator.get(cur);
|
|
1085
|
+
if (initiator) {
|
|
1086
|
+
// Should never happen, but if it did that would be an infinite loop.
|
|
1087
|
+
if (initiators.includes(initiator)) {
|
|
1088
|
+
return [];
|
|
1089
|
+
}
|
|
1090
|
+
initiators.unshift(initiator);
|
|
1091
|
+
}
|
|
1092
|
+
cur = initiator;
|
|
1093
|
+
}
|
|
1094
|
+
return initiators;
|
|
1095
|
+
}
|
|
1096
|
+
}
|