chrome-devtools-frontend 1.0.1708524 → 1.0.1709383

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/AUTHORS +2 -0
  2. package/front_end/core/common/Color.ts +6 -0
  3. package/front_end/core/common/VersionController.ts +0 -53
  4. package/front_end/core/root/Runtime.ts +0 -41
  5. package/front_end/core/sdk/CSSModel.ts +57 -0
  6. package/front_end/core/sdk/CSSPropertyParser.ts +10 -17
  7. package/front_end/core/sdk/DebuggerModel.ts +90 -56
  8. package/front_end/core/sdk/NetworkRequest.ts +37 -15
  9. package/front_end/core/sdk/OverlayModel.ts +47 -0
  10. package/front_end/core/sdk/PageResourceLoader.ts +26 -6
  11. package/front_end/core/sdk/SourceMap.ts +38 -1
  12. package/front_end/core/sdk/SourceMapScopesInfo.ts +89 -0
  13. package/front_end/core/text_utils/TextUtils.ts +1 -1
  14. package/front_end/entrypoints/devtools_app/devtools_app.ts +1 -0
  15. package/front_end/entrypoints/main/MainImpl.ts +2 -21
  16. package/front_end/generated/InspectorBackendCommands.ts +2 -1
  17. package/front_end/generated/SupportedCSSProperties.ts +1 -1
  18. package/front_end/generated/protocol.ts +8 -3
  19. package/front_end/models/ai_assistance/AiAgent2.ts +126 -2
  20. package/front_end/models/ai_assistance/AiConversation.ts +2 -2
  21. package/front_end/models/ai_assistance/AiUtils.ts +4 -0
  22. package/front_end/models/ai_assistance/agents/AiAgent.ts +64 -13
  23. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +2 -2
  24. package/front_end/models/ai_assistance/agents/ExecuteJavascript.ts +4 -1
  25. package/front_end/models/ai_assistance/agents/PerformanceAgent.snapshot.txt +257 -2
  26. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +1 -1
  27. package/front_end/models/ai_assistance/ai_assistance.ts +2 -2
  28. package/front_end/models/ai_assistance/contexts/{AccessibilityContext.snapshot.txt → LighthouseContext.snapshot.txt} +23 -2
  29. package/front_end/models/ai_assistance/contexts/{AccessibilityContext.ts → LighthouseContext.ts} +24 -5
  30. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.snapshot.txt +8 -18
  31. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +2 -2
  32. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +81 -103
  33. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +202 -15
  34. package/front_end/models/ai_assistance/tools/ExecuteJavaScript.ts +6 -0
  35. package/front_end/models/ai_assistance/tools/GetCookieValues.ts +3 -0
  36. package/front_end/models/ai_assistance/tools/GetDetailedCallTree.ts +2 -0
  37. package/front_end/models/ai_assistance/tools/GetElementAccessibilityDetails.ts +2 -0
  38. package/front_end/models/ai_assistance/tools/GetInsightDetails.ts +2 -0
  39. package/front_end/models/ai_assistance/tools/GetLighthouseAudits.ts +2 -1
  40. package/front_end/models/ai_assistance/tools/GetNetworkRequestDetails.ts +2 -0
  41. package/front_end/models/ai_assistance/tools/GetSourceContent.ts +2 -0
  42. package/front_end/models/ai_assistance/tools/GetStorageBreakdown.ts +2 -0
  43. package/front_end/models/ai_assistance/tools/GetStorageValues.ts +3 -0
  44. package/front_end/models/ai_assistance/tools/GetStyles.ts +2 -0
  45. package/front_end/models/ai_assistance/tools/GetTraceEventByKey.ts +3 -1
  46. package/front_end/models/ai_assistance/tools/GetTraceFunctionCode.ts +2 -0
  47. package/front_end/models/ai_assistance/tools/GetTraceMainThreadSummary.ts +2 -0
  48. package/front_end/models/ai_assistance/tools/GetTraceNetworkSummary.ts +2 -0
  49. package/front_end/models/ai_assistance/tools/GetTraceResourceContent.ts +2 -0
  50. package/front_end/models/ai_assistance/tools/ListCookies.ts +2 -0
  51. package/front_end/models/ai_assistance/tools/ListNetworkRequests.ts +2 -0
  52. package/front_end/models/ai_assistance/tools/ListPageOrigins.ts +2 -0
  53. package/front_end/models/ai_assistance/tools/ListSources.ts +2 -0
  54. package/front_end/models/ai_assistance/tools/ListStorageKeys.ts +2 -0
  55. package/front_end/models/ai_assistance/tools/RecordPerformanceTrace.ts +2 -0
  56. package/front_end/models/ai_assistance/tools/ResolveDevtoolsNodePath.ts +2 -0
  57. package/front_end/models/ai_assistance/tools/RunLighthouse.ts +14 -9
  58. package/front_end/models/ai_assistance/tools/SelectTraceEventByKey.ts +2 -0
  59. package/front_end/models/ai_assistance/tools/Tool.ts +53 -0
  60. package/front_end/models/bindings/DebuggerWorkspaceBinding.ts +37 -10
  61. package/front_end/models/bindings/SourceMapStepping.ts +50 -0
  62. package/front_end/models/bindings/bindings.ts +2 -0
  63. package/front_end/models/emulation/DeviceModeModel.ts +85 -62
  64. package/front_end/models/har/Importer.ts +2 -3
  65. package/front_end/models/lighthouse/LighthouseReporterTypes.ts +5 -1
  66. package/front_end/models/source_map_scopes/NamesResolver.ts +30 -5
  67. package/front_end/models/trace/LanternComputationData.ts +8 -0
  68. package/front_end/models/trace/Processor.ts +2 -1
  69. package/front_end/models/trace/lantern/core/NetworkAnalyzer.ts +7 -2
  70. package/front_end/models/trace/lantern/graph/NetworkNode.ts +14 -0
  71. package/front_end/models/trace/lantern/graph/PageDependencyGraph.ts +1 -1
  72. package/front_end/models/trace/lantern/simulation/ConnectionPool.ts +4 -4
  73. package/front_end/models/trace/lantern/simulation/Simulator.ts +6 -2
  74. package/front_end/models/trace/lantern/simulation/TCPConnection.ts +12 -12
  75. package/front_end/models/trace/lantern/types/Lantern.ts +8 -0
  76. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +14 -12
  77. package/front_end/panels/ai_assistance/components/ChatInput.ts +1 -1
  78. package/front_end/panels/ai_assistance/components/ChatMessage.ts +121 -13
  79. package/front_end/panels/ai_assistance/components/chatMessage.css +47 -0
  80. package/front_end/panels/application/BackgroundServiceView.ts +31 -33
  81. package/front_end/panels/application/IndexedDBModel.ts +8 -20
  82. package/front_end/panels/application/IndexedDBViews.ts +2 -6
  83. package/front_end/panels/application/ServiceWorkerUpdateCycleView.ts +8 -8
  84. package/front_end/panels/application/ServiceWorkersView.ts +44 -52
  85. package/front_end/panels/application/WebMCPView.ts +50 -50
  86. package/front_end/panels/comments/CommentsPane.ts +266 -0
  87. package/front_end/panels/comments/comments-meta.ts +47 -0
  88. package/front_end/panels/comments/comments.ts +9 -0
  89. package/front_end/panels/comments/commentsPane.css +206 -0
  90. package/front_end/panels/common/CommentsStatusBarPill.ts +3 -1
  91. package/front_end/panels/console/ConsoleViewMessage.ts +1 -2
  92. package/front_end/panels/console/ConsoleViewport.ts +1 -1
  93. package/front_end/panels/console/SymbolizedErrorWidget.ts +18 -9
  94. package/front_end/panels/elements/CSSValueTraceView.ts +4 -4
  95. package/front_end/panels/elements/ComputedStyleWidget.ts +46 -42
  96. package/front_end/panels/elements/DOMPath.ts +11 -1
  97. package/front_end/panels/elements/DOMTreeContextMenu.ts +15 -0
  98. package/front_end/panels/elements/DOMTreeWidget.ts +30 -20
  99. package/front_end/panels/elements/ElementsPanel.ts +14 -11
  100. package/front_end/panels/elements/ElementsTreeElement.ts +24 -18
  101. package/front_end/panels/elements/EventListenersWidget.ts +5 -3
  102. package/front_end/panels/elements/InspectElementModeController.ts +6 -3
  103. package/front_end/panels/elements/LayoutPane.ts +3 -3
  104. package/front_end/panels/elements/PropertyRenderer.ts +132 -87
  105. package/front_end/panels/elements/StandaloneStylesContainer.ts +7 -3
  106. package/front_end/panels/elements/StylePropertiesSection.ts +49 -4
  107. package/front_end/panels/elements/StylePropertyTreeElement.ts +58 -50
  108. package/front_end/panels/elements/StylesContainer.ts +2 -1
  109. package/front_end/panels/elements/StylesSidebarPane.ts +75 -16
  110. package/front_end/panels/elements/components/CSSPropertyDocsView.ts +2 -1
  111. package/front_end/panels/elements/elements-meta.ts +24 -75
  112. package/front_end/panels/emulation/DeviceModeToolbar.ts +5 -2
  113. package/front_end/panels/emulation/DeviceModeView.ts +5 -2
  114. package/front_end/panels/emulation/emulation-meta.ts +3 -8
  115. package/front_end/panels/layer_viewer/Layers3DView.ts +8 -3
  116. package/front_end/panels/layer_viewer/layer_viewer-meta.ts +2 -5
  117. package/front_end/panels/lighthouse/LighthouseReportRenderer.ts +13 -3
  118. package/front_end/panels/network/NetworkConfigView.ts +30 -3
  119. package/front_end/panels/network/NetworkLogView.ts +7 -5
  120. package/front_end/panels/network/NetworkPanel.ts +4 -3
  121. package/front_end/panels/network/NetworkWaterfallColumn.ts +7 -4
  122. package/front_end/panels/network/network-meta.ts +23 -34
  123. package/front_end/panels/profiler/HeapProfileView.ts +20 -24
  124. package/front_end/panels/profiler/ProfilesPanel.ts +8 -3
  125. package/front_end/panels/screencast/ScreencastView.ts +6 -4
  126. package/front_end/panels/sources/SourcesView.ts +1 -1
  127. package/front_end/panels/sources/TabbedEditorContainer.ts +36 -32
  128. package/front_end/panels/timeline/TimelineHistoryManager.ts +2 -4
  129. package/front_end/panels/timeline/components/LiveMetricsView.ts +7 -20
  130. package/front_end/panels/timeline/components/MetricCard.ts +262 -315
  131. package/front_end/panels/timeline/components/liveMetricsView.css +4 -0
  132. package/front_end/panels/timeline/components/metricCard.css +2 -24
  133. package/front_end/panels/timeline/overlays/OverlaysImpl.ts +33 -17
  134. package/front_end/panels/timeline/overlays/components/TimeRangeOverlay.ts +182 -130
  135. package/front_end/panels/timeline/overlays/components/timeRangeOverlay.css +76 -74
  136. package/front_end/panels/whats_new/WhatsNewImpl.ts +2 -1
  137. package/front_end/panels/whats_new/whats_new-meta.ts +2 -5
  138. package/front_end/third_party/chromium/README.chromium +1 -1
  139. package/front_end/third_party/web-vitals/README.chromium +2 -2
  140. package/front_end/third_party/web-vitals/package/README.md +5 -3
  141. package/front_end/third_party/web-vitals/package/dist/modules/attribution/onINP.js +1 -1
  142. package/front_end/third_party/web-vitals/package/dist/modules/attribution/onLCP.js +8 -5
  143. package/front_end/third_party/web-vitals/package/package.json +1 -1
  144. package/front_end/third_party/web-vitals/package/src/attribution/onINP.ts +1 -1
  145. package/front_end/third_party/web-vitals/package/src/attribution/onLCP.ts +9 -5
  146. package/front_end/third_party/web-vitals/rebuild.sh +2 -2
  147. package/front_end/ui/components/text_editor/config.ts +2 -1
  148. package/front_end/ui/legacy/TabbedPane.ts +67 -12
  149. package/front_end/ui/legacy/Toolbar.ts +29 -9
  150. package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +93 -31
  151. package/front_end/ui/legacy/components/quick_open/FilteredListWidget.ts +15 -7
  152. package/front_end/ui/legacy/components/utils/Linkifier.ts +4 -0
  153. package/front_end/ui/legacy/tabbedPane.css +12 -8
  154. package/front_end/ui/settings/ElementsSettings.ts +93 -0
  155. package/front_end/ui/settings/EmulationSettings.ts +17 -0
  156. package/front_end/ui/settings/LayerViewerSettings.ts +12 -0
  157. package/front_end/ui/settings/NetworkSettings.ts +26 -0
  158. package/front_end/ui/settings/WhatsNewSettings.ts +11 -0
  159. package/front_end/ui/settings/settings.ts +10 -0
  160. package/front_end/ui/visual_logging/KnownContextValues.ts +7 -0
  161. package/inspector_overlay/highlight_anchor.ts +1 -1
  162. package/package.json +1 -1
@@ -662,6 +662,18 @@ export class OverlayModel extends SDKModel<EventTypes> implements ProtocolProxyA
662
662
  color: Common.Color.PageHighlight.LayoutLine.toProtocolRGBA(),
663
663
  },
664
664
  };
665
+
666
+ highlightConfig.imcbHighlightConfig = {
667
+ imcbBorderColor: Common.Color.PageHighlight.AnchorIMCB.toProtocolRGBA(),
668
+ imcbBackgroundColor: Common.Color.PageHighlight.AnchorIMCBBackground.toProtocolRGBA(),
669
+ insetsBackgroundColor: Common.Color.PageHighlight.AnchorInsetsBackground.toProtocolRGBA(),
670
+ insetsHatchColor: Common.Color.PageHighlight.AnchorInsetsHatch.toProtocolRGBA(),
671
+ anchorBorderColor: Common.Color.PageHighlight.AnchorTarget.toProtocolRGBA(),
672
+ anchorBackgroundColor: Common.Color.PageHighlight.AnchorTargetBackground.toProtocolRGBA(),
673
+ showPositionAreaGrid: true,
674
+ positionAreaGridLineColor: Common.Color.PageHighlight.AnchorTarget.toProtocolRGBA(),
675
+ positionAreaActiveRegionColor: Common.Color.PageHighlight.AnchorTargetBackground.toProtocolRGBA(),
676
+ };
665
677
  }
666
678
 
667
679
  if (mode.endsWith('gap')) {
@@ -793,6 +805,41 @@ export class OverlayModel extends SDKModel<EventTypes> implements ProtocolProxyA
793
805
  };
794
806
  }
795
807
 
808
+ const baseImcbHighlightConfig = {
809
+ imcbBorderColor: Common.Color.PageHighlight.AnchorIMCB.toProtocolRGBA(),
810
+ imcbBackgroundColor: Common.Color.PageHighlight.AnchorIMCBBackground.toProtocolRGBA(),
811
+ anchorBorderColor: Common.Color.PageHighlight.AnchorTarget.toProtocolRGBA(),
812
+ anchorBackgroundColor: Common.Color.PageHighlight.AnchorTargetBackground.toProtocolRGBA(),
813
+ };
814
+
815
+ if (mode === 'anchor-positioning') {
816
+ highlightConfig.imcbHighlightConfig = {
817
+ ...baseImcbHighlightConfig,
818
+ insetsBackgroundColor: Common.Color.PageHighlight.AnchorInsetsBackground.toProtocolRGBA(),
819
+ insetsHatchColor: Common.Color.PageHighlight.AnchorInsetsHatch.toProtocolRGBA(),
820
+ showPositionAreaGrid: true,
821
+ positionAreaGridLineColor: Common.Color.PageHighlight.AnchorTarget.toProtocolRGBA(),
822
+ positionAreaActiveRegionColor: Common.Color.PageHighlight.AnchorTargetBackground.toProtocolRGBA(),
823
+ };
824
+ }
825
+
826
+ if (mode === 'position-area') {
827
+ highlightConfig.imcbHighlightConfig = {
828
+ ...baseImcbHighlightConfig,
829
+ showPositionAreaGrid: true,
830
+ positionAreaGridLineColor: Common.Color.PageHighlight.AnchorTarget.toProtocolRGBA(),
831
+ positionAreaActiveRegionColor: Common.Color.PageHighlight.AnchorTargetBackground.toProtocolRGBA(),
832
+ };
833
+ }
834
+
835
+ if (mode === 'insets') {
836
+ highlightConfig.imcbHighlightConfig = {
837
+ ...baseImcbHighlightConfig,
838
+ insetsBackgroundColor: Common.Color.PageHighlight.AnchorInsetsBackground.toProtocolRGBA(),
839
+ insetsHatchColor: Common.Color.PageHighlight.AnchorInsetsHatch.toProtocolRGBA(),
840
+ };
841
+ }
842
+
796
843
  return highlightConfig;
797
844
  }
798
845
 
@@ -317,6 +317,29 @@ export class PageResourceLoader extends Common.ObjectWrapper.ObjectWrapper<Event
317
317
  }
318
318
  }
319
319
 
320
+ #resolveFrameTarget(initiator: PageResourceLoadInitiator): {
321
+ frameTarget: Target|null,
322
+ frameId: Protocol.Page.FrameId|null,
323
+ } {
324
+ let frameTarget: Target|null = initiator.target;
325
+ let parentFrameId: Protocol.Page.FrameId|null = null;
326
+ while (frameTarget && !frameTarget.model(ResourceTreeModel)) {
327
+ parentFrameId = parentFrameId ?? frameTarget.targetInfo()?.parentFrameId ?? null;
328
+ frameTarget = frameTarget.parentTarget();
329
+ }
330
+ const frameId = initiator.frameId ?? parentFrameId ?? frameTarget?.model(ResourceTreeModel)?.mainFrame?.id ?? null;
331
+ return {frameTarget, frameId};
332
+ }
333
+
334
+ #isSameOriginWithPrimaryPage(frameTarget: Target|null, frameId: Protocol.Page.FrameId|null): boolean {
335
+ const primaryFrame = this.#targetManager.primaryPageTarget()?.model(ResourceTreeModel)?.mainFrame;
336
+ const initiatorFrame = frameId ? frameTarget?.model(ResourceTreeModel)?.frameForId(frameId) : null;
337
+ if (!primaryFrame || !initiatorFrame) {
338
+ return false;
339
+ }
340
+ return initiatorFrame.securityOrigin().isSameOriginWith(primaryFrame.securityOrigin());
341
+ }
342
+
320
343
  private async dispatchLoad(
321
344
  url: Platform.DevToolsPath.UrlString, initiator: PageResourceLoadInitiator, isBinary: boolean): Promise<{
322
345
  success: boolean,
@@ -343,11 +366,7 @@ export class PageResourceLoader extends Common.ObjectWrapper.ObjectWrapper<Event
343
366
  // directive is present. A null/error response keeps the guard armed so that a
344
367
  // detached-frame or protocol-error path cannot fall through to loadFromHostBindings.
345
368
  let mustEnforceCSP = isHttp;
346
- let frameTarget: Target|null = initiator.target;
347
- while (frameTarget && !frameTarget.model(ResourceTreeModel)) {
348
- frameTarget = frameTarget.parentTarget();
349
- }
350
- const frameId = initiator.frameId ?? frameTarget?.model(ResourceTreeModel)?.mainFrame?.id ?? null;
369
+ const {frameTarget, frameId} = this.#resolveFrameTarget(initiator);
351
370
  if (isHttp && frameTarget) {
352
371
  const networkManager = frameTarget.model(NetworkManager);
353
372
  if (networkManager) {
@@ -366,7 +385,8 @@ export class PageResourceLoader extends Common.ObjectWrapper.ObjectWrapper<Event
366
385
  } catch (e) {
367
386
  if (e instanceof Error) {
368
387
  Host.userMetrics.developerResourceLoaded(Host.UserMetrics.DeveloperResourceLoaded.LOAD_THROUGH_PAGE_FAILURE);
369
- if (mustEnforceCSP || e.message.includes('CSP violation')) {
388
+ if (mustEnforceCSP || !this.#isSameOriginWithPrimaryPage(frameTarget, frameId) ||
389
+ e.message.includes('CSP violation')) {
370
390
  return {
371
391
  success: false,
372
392
  content: '',
@@ -12,7 +12,12 @@ import {scopeTreeForScript} from './ScopeTreeCache.js';
12
12
  import type {Script} from './Script.js';
13
13
  import {buildOriginalScopes, decodePastaRanges, type NamedFunctionRange} from './SourceMapFunctionRanges.js';
14
14
  import {decodeRangeMappings} from './SourceMapRangeMappings.js';
15
- import {type RawFrameTranslation, scriptRelativePosition, SourceMapScopesInfo} from './SourceMapScopesInfo.js';
15
+ import {
16
+ type PositionRange,
17
+ type RawFrameTranslation,
18
+ scriptRelativePosition,
19
+ SourceMapScopesInfo,
20
+ } from './SourceMapScopesInfo.js';
16
21
 
17
22
  /**
18
23
  * Type of the base source map JSON object, which contains the sources and the mappings at the very least, plus
@@ -250,6 +255,16 @@ export class SourceMap {
250
255
  return this.#scopesInfo !== null && !this.#scopesInfo.isEmpty();
251
256
  }
252
257
 
258
+ /**
259
+ * True iff the scopes come from the source map itself (encoded `scopes`), not from the AST fallback or from an
260
+ * extension.
261
+ */
262
+ hasEncodedScopeInfo(): boolean {
263
+ this.#ensureSourceMapProcessed();
264
+ return this.#scopesInfo !== null && this.#scopesFallbackPromise === undefined &&
265
+ this.#scopesInfo.hasGeneratedRanges();
266
+ }
267
+
253
268
  waitForScopeInfo(): Promise<void> {
254
269
  this.#ensureSourceMapProcessed();
255
270
  return this.#scopesFallbackPromise ?? Promise.resolve();
@@ -872,6 +887,28 @@ export class SourceMap {
872
887
  this.#ensureSourceMapProcessed();
873
888
  return this.#scopesInfo?.translateRawFrame(generatedLine, generatedColumn) ?? null;
874
889
  }
890
+
891
+ /** See {@link SourceMapScopesInfo.inlinedFunctionRange}. `null` without encoded scopes. */
892
+ inlinedFunctionRange(generatedLine: number, generatedColumn: number): PositionRange|null {
893
+ return this.hasEncodedScopeInfo() ? this.#scopesInfo?.inlinedFunctionRange(generatedLine, generatedColumn) ?? null :
894
+ null;
895
+ }
896
+
897
+ /** See {@link SourceMapScopesInfo.inlinedCalleeRanges}. Empty without encoded scopes. */
898
+ inlinedCalleeRanges(generatedLine: number, generatedColumn: number): PositionRange[] {
899
+ return this.hasEncodedScopeInfo() ? this.#scopesInfo?.inlinedCalleeRanges(generatedLine, generatedColumn) ?? [] :
900
+ [];
901
+ }
902
+
903
+ /** See {@link SourceMapScopesInfo.hasOutlinedFunctions}. False without encoded scopes. */
904
+ hasOutlinedFunctions(): boolean {
905
+ return this.hasEncodedScopeInfo() && (this.#scopesInfo?.hasOutlinedFunctions() ?? false);
906
+ }
907
+
908
+ /** See {@link SourceMapScopesInfo.artificialFunctionRanges}. Empty without encoded scopes. */
909
+ artificialFunctionRanges(): PositionRange[] {
910
+ return this.hasEncodedScopeInfo() ? this.#scopesInfo?.artificialFunctionRanges() ?? [] : [];
911
+ }
875
912
  }
876
913
 
877
914
  /** @returns a copy of the {@link entry} that is marked as a range mapping. */
@@ -215,6 +215,10 @@ export class SourceMapScopesInfo {
215
215
  return noScopes && !this.#generatedRanges.length;
216
216
  }
217
217
 
218
+ hasGeneratedRanges(): boolean {
219
+ return this.#generatedRanges.length > 0;
220
+ }
221
+
218
222
  addOriginalScopesAtIndex(sourceIdx: number, scopes: ScopesCodec.OriginalScope[]): void {
219
223
  if (!this.#originalScopes[sourceIdx]?.length) {
220
224
  this.#originalScopes[sourceIdx] = scopes;
@@ -561,6 +565,91 @@ export class SourceMapScopesInfo {
561
565
 
562
566
  return result;
563
567
  }
568
+
569
+ /**
570
+ * @returns the body of the innermost inlined function at the position (the innermost range with a `callSite`
571
+ * within the generated function), or null if the position is not inside an inlined function.
572
+ *
573
+ * The innermost inlined function is the logical frame the position belongs to, i.e. the analogue of the top frame
574
+ * of a real call stack. Stepping over its body therefore goes up exactly one logical frame: into the caller, which
575
+ * may itself be inlined. An outer range would leave several logical frames at once.
576
+ */
577
+ inlinedFunctionRange(generatedLine: number, generatedColumn: number): PositionRange|null {
578
+ const rangeChain = this.#findGeneratedRangeChain(generatedLine, generatedColumn);
579
+ for (let i = rangeChain.length - 1; i >= 0 && !rangeChain[i].isStackFrame; --i) {
580
+ if (rangeChain[i].callSite) {
581
+ return {start: rangeChain[i].start, end: rangeChain[i].end};
582
+ }
583
+ }
584
+ return null;
585
+ }
586
+
587
+ /**
588
+ * @returns the bodies of all functions that were inlined directly into the logical function at the position
589
+ * (the innermost inlined function, or else the generated function). Doesn't descend into inlined
590
+ * functions or nested generated functions.
591
+ */
592
+ inlinedCalleeRanges(generatedLine: number, generatedColumn: number): PositionRange[] {
593
+ const rangeChain = this.#findGeneratedRangeChain(generatedLine, generatedColumn);
594
+ let body: ScopesCodec.GeneratedRange|undefined;
595
+ for (let i = rangeChain.length - 1; i >= 0 && !body; --i) {
596
+ if (rangeChain[i].callSite || rangeChain[i].isStackFrame) {
597
+ body = rangeChain[i];
598
+ }
599
+ }
600
+ const result: PositionRange[] = [];
601
+ (function walk(range: ScopesCodec.GeneratedRange) {
602
+ for (const child of range.children) {
603
+ if (child.isStackFrame) {
604
+ continue;
605
+ }
606
+ if (child.callSite) {
607
+ result.push({start: child.start, end: child.end});
608
+ } else {
609
+ walk(child);
610
+ }
611
+ }
612
+ })(body ?? {children: this.#generatedRanges} as ScopesCodec.GeneratedRange);
613
+ return result;
614
+ }
615
+
616
+ /**
617
+ * @returns true, iff any generated function is outlined, i.e. marked as "hidden" but with a definition (see
618
+ * {@link GeneratedFrameKind.OUTLINED}). Hidden functions without a definition are compiler helpers.
619
+ */
620
+ hasOutlinedFunctions(): boolean {
621
+ const hasOutlined = (ranges: ScopesCodec.GeneratedRange[]): boolean =>
622
+ ranges.some(range => (range.isStackFrame && range.isHidden && range.originalScope !== undefined) ||
623
+ hasOutlined(range.children));
624
+ return hasOutlined(this.#generatedRanges);
625
+ }
626
+
627
+ /**
628
+ * @returns the "artificial" generated functions (in the DWARF sense): functions that contain no authored code at all
629
+ * (no original scope anywhere in their subtree), e.g. compiler helpers. Sorted by start position,
630
+ * non-overlapping.
631
+ */
632
+ artificialFunctionRanges(): PositionRange[] {
633
+ const hasOriginalScope = (range: ScopesCodec.GeneratedRange): boolean =>
634
+ range.originalScope !== undefined || range.children.some(hasOriginalScope);
635
+ const result: PositionRange[] = [];
636
+ (function walk(ranges: ScopesCodec.GeneratedRange[]) {
637
+ for (const range of ranges) {
638
+ if (range.isStackFrame && !hasOriginalScope(range)) {
639
+ result.push({start: range.start, end: range.end});
640
+ } else {
641
+ walk(range.children);
642
+ }
643
+ }
644
+ })(this.#generatedRanges);
645
+ return result;
646
+ }
647
+ }
648
+
649
+ /** A range of generated positions, relative to the start of the script. `end` is exclusive. */
650
+ export interface PositionRange {
651
+ readonly start: ScopesCodec.Position;
652
+ readonly end: ScopesCodec.Position;
564
653
  }
565
654
 
566
655
  /**
@@ -9,7 +9,7 @@ import {SearchMatch} from './ContentProvider.js';
9
9
  import type {Text} from './Text.js';
10
10
 
11
11
  const KEY_VALUE_FILTER_REGEXP = /(?:^|\s)(\-)?([\w\-]+):([^\s]+)/;
12
- const REGEXP_FILTER_REGEXP = /(?:^|\s)(\-)?\/([^\/\\]+(\\.[^\/]*)*)\//;
12
+ const REGEXP_FILTER_REGEXP = /(?:^|\s)(\-)?\/((?:[^\/\\]|\\.)+)\//;
13
13
  const TEXT_FILTER_REGEXP = /(?:^|\s)(\-)?([^\s]+)/;
14
14
  const SPACE_CHAR_REGEXP = /\s/;
15
15
 
@@ -2,6 +2,7 @@
2
2
  // Use of this source code is governed by a BSD-style license that can be
3
3
  // found in the LICENSE file.
4
4
  import '../shell/shell.js';
5
+ import '../../panels/comments/comments-meta.js';
5
6
  import '../../panels/css_overview/css_overview-meta.js';
6
7
  import '../../panels/elements/elements-meta.js';
7
8
  import '../../panels/browser_debugger/browser_debugger-meta.js';
@@ -223,8 +223,6 @@ export class MainImpl {
223
223
  this.#universe.settings.resolve(SDK.SDKSettings.cacheDisabledSettingDescriptor).setRequiresUserAction(true);
224
224
  }
225
225
 
226
- Root.Runtime.experiments.removeAllExperimentsFromLocalStorage();
227
-
228
226
  await this.requestAndRegisterLocaleData();
229
227
 
230
228
  Host.userMetrics.syncSetting(
@@ -352,23 +350,10 @@ export class MainImpl {
352
350
  return {syncedStorage, globalStorage, localStorage};
353
351
  }
354
352
 
355
- // TODO(crbug.com/464173054) remove after M156
356
- #migrateValueFromLegacyExperiment(legacyExperimentName: Root.ExperimentNames.ExperimentName,
357
- experiment: Root.Runtime.Experiment): void {
358
- const value = Root.Runtime.experiments.getValueFromStorage(legacyExperimentName);
359
- if (value !== undefined && experiment.aboutFlag) {
360
- // Set the experiment to the same value as the legacy experiment.
361
- experiment.setEnabled(value);
362
- // Set the chrome flag to the same value as the legacy experiment.
363
- Host.InspectorFrontendHost.InspectorFrontendHostInstance.setChromeFlag(experiment.aboutFlag, value);
364
- // The legacy experiment will be cleaned up by `removeAllExperimentsFromLocalStorage`.
365
- }
366
- }
367
-
368
353
  #initializeExperiments(): void {
369
354
  const enableProtocolMonitor = (Root.Runtime.hostConfig.devToolsProtocolMonitor?.enabled ?? false) ||
370
355
  Boolean(Root.Runtime.Runtime.queryParam('isChromeForTesting'));
371
- const protocolMonitorExperiment = Root.Runtime.experiments.register({
356
+ Root.Runtime.experiments.register({
372
357
  name: Root.ExperimentNames.ExperimentName.PROTOCOL_MONITOR,
373
358
  title: 'Protocol Monitor',
374
359
  aboutFlag: 'devtools-protocol-monitor',
@@ -377,19 +362,15 @@ export class MainImpl {
377
362
  docLink: 'https://developer.chrome.com/blog/new-in-devtools-92/#protocol-monitor' as
378
363
  Platform.DevToolsPath.UrlString,
379
364
  });
380
- this.#migrateValueFromLegacyExperiment(Root.ExperimentNames.ExperimentName.PROTOCOL_MONITOR,
381
- protocolMonitorExperiment);
382
365
 
383
366
  // Debugging
384
- const instrumentationBreakpointsExperiment = Root.Runtime.experiments.register({
367
+ Root.Runtime.experiments.register({
385
368
  name: Root.ExperimentNames.ExperimentName.INSTRUMENTATION_BREAKPOINTS,
386
369
  title: 'Instrumentation breakpoints',
387
370
  aboutFlag: 'devtools-instrumentation-breakpoints',
388
371
  isEnabled: Root.Runtime.hostConfig.devToolsInstrumentationBreakpoints?.enabled ?? false,
389
372
  requiresChromeRestart: false,
390
373
  });
391
- this.#migrateValueFromLegacyExperiment(Root.ExperimentNames.ExperimentName.INSTRUMENTATION_BREAKPOINTS,
392
- instrumentationBreakpointsExperiment);
393
374
 
394
375
  Root.Runtime.experiments.register({
395
376
  name: Root.ExperimentNames.ExperimentName.DURABLE_MESSAGES,
@@ -1520,6 +1520,7 @@ inspectorBackend.registerType("WebMCP.RemovedTool", [{"name": "name", "type": "s
1520
1520
 
1521
1521
  // Debugger.
1522
1522
  inspectorBackend.registerEnum("Debugger.ScopeType", {Global: "global", Local: "local", With: "with", Closure: "closure", Catch: "catch", Block: "block", Script: "script", Eval: "eval", Module: "module", WasmExpressionStack: "wasm-expression-stack"});
1523
+ inspectorBackend.registerEnum("Debugger.ScopeEmptyReason", {NoVariables: "no-variables", AllUnavailable: "all-unavailable"});
1523
1524
  inspectorBackend.registerEnum("Debugger.BreakLocationType", {DebuggerStatement: "debuggerStatement", Call: "call", Return: "return"});
1524
1525
  inspectorBackend.registerEnum("Debugger.ScriptLanguage", {JavaScript: "JavaScript", WebAssembly: "WebAssembly"});
1525
1526
  inspectorBackend.registerEnum("Debugger.DebugSymbolsType", {SourceMap: "SourceMap", EmbeddedDWARF: "EmbeddedDWARF", ExternalDWARF: "ExternalDWARF"});
@@ -1570,7 +1571,7 @@ inspectorBackend.registerType("Debugger.Location", [{"name": "scriptId", "type":
1570
1571
  inspectorBackend.registerType("Debugger.ScriptPosition", [{"name": "lineNumber", "type": "number", "optional": false, "description": "", "typeRef": null}, {"name": "columnNumber", "type": "number", "optional": false, "description": "", "typeRef": null}]);
1571
1572
  inspectorBackend.registerType("Debugger.LocationRange", [{"name": "scriptId", "type": "string", "optional": false, "description": "", "typeRef": "Runtime.ScriptId"}, {"name": "start", "type": "object", "optional": false, "description": "", "typeRef": "Debugger.ScriptPosition"}, {"name": "end", "type": "object", "optional": false, "description": "", "typeRef": "Debugger.ScriptPosition"}]);
1572
1573
  inspectorBackend.registerType("Debugger.CallFrame", [{"name": "callFrameId", "type": "string", "optional": false, "description": "Call frame identifier. This identifier is only valid while the virtual machine is paused.", "typeRef": "Debugger.CallFrameId"}, {"name": "functionName", "type": "string", "optional": false, "description": "Name of the JavaScript function called on this call frame.", "typeRef": null}, {"name": "functionLocation", "type": "object", "optional": true, "description": "Location in the source code.", "typeRef": "Debugger.Location"}, {"name": "location", "type": "object", "optional": false, "description": "Location in the source code.", "typeRef": "Debugger.Location"}, {"name": "url", "type": "string", "optional": false, "description": "JavaScript script name or url. Deprecated in favor of using the `location.scriptId` to resolve the URL via a previously sent `Debugger.scriptParsed` event.", "typeRef": null}, {"name": "scopeChain", "type": "array", "optional": false, "description": "Scope chain for this call frame.", "typeRef": "Debugger.Scope"}, {"name": "this", "type": "object", "optional": false, "description": "`this` object for this call frame.", "typeRef": "Runtime.RemoteObject"}, {"name": "returnValue", "type": "object", "optional": true, "description": "The value being returned, if the function is at return point.", "typeRef": "Runtime.RemoteObject"}, {"name": "canBeRestarted", "type": "boolean", "optional": true, "description": "Valid only while the VM is paused and indicates whether this frame can be restarted or not. Note that a `true` value here does not guarantee that Debugger#restartFrame with this CallFrameId will be successful, but it is very likely.", "typeRef": null}]);
1573
- inspectorBackend.registerType("Debugger.Scope", [{"name": "type", "type": "string", "optional": false, "description": "Scope type.", "typeRef": null}, {"name": "object", "type": "object", "optional": false, "description": "Object representing the scope. For `global` and `with` scopes it represents the actual object; for the rest of the scopes, it is artificial transient object enumerating scope variables as its properties.", "typeRef": "Runtime.RemoteObject"}, {"name": "name", "type": "string", "optional": true, "description": "", "typeRef": null}, {"name": "startLocation", "type": "object", "optional": true, "description": "Location in the source code where scope starts", "typeRef": "Debugger.Location"}, {"name": "endLocation", "type": "object", "optional": true, "description": "Location in the source code where scope ends", "typeRef": "Debugger.Location"}, {"name": "empty", "type": "boolean", "optional": true, "description": "True if the scope does not declare any variables. Only present if true. Empty scopes are retained in the scope chain because they can be targeted via `evaluateOnCallFrame` (using `scopeNumber`) or matched against scopes in source maps.", "typeRef": null}]);
1574
+ inspectorBackend.registerType("Debugger.Scope", [{"name": "type", "type": "string", "optional": false, "description": "Scope type.", "typeRef": null}, {"name": "object", "type": "object", "optional": false, "description": "Object representing the scope. For `global` and `with` scopes it represents the actual object; for the rest of the scopes, it is artificial transient object enumerating scope variables as its properties.", "typeRef": "Runtime.RemoteObject"}, {"name": "name", "type": "string", "optional": true, "description": "", "typeRef": null}, {"name": "startLocation", "type": "object", "optional": true, "description": "Location in the source code where scope starts", "typeRef": "Debugger.Location"}, {"name": "endLocation", "type": "object", "optional": true, "description": "Location in the source code where scope ends", "typeRef": "Debugger.Location"}, {"name": "emptyReason", "type": "string", "optional": true, "description": "Present if the scope has no variable values to show. Absent means that the scope declares at least one variable with an available value. Empty scopes are retained in the scope chain because they can be targeted via `evaluateOnCallFrame` (using `scopeNumber`) or matched against scopes in source maps.", "typeRef": null}]);
1574
1575
  inspectorBackend.registerType("Debugger.SearchMatch", [{"name": "lineNumber", "type": "number", "optional": false, "description": "Line number in resource content.", "typeRef": null}, {"name": "lineContent", "type": "string", "optional": false, "description": "Line with match content.", "typeRef": null}]);
1575
1576
  inspectorBackend.registerType("Debugger.BreakLocation", [{"name": "scriptId", "type": "string", "optional": false, "description": "Script identifier as reported in the `Debugger.scriptParsed`.", "typeRef": "Runtime.ScriptId"}, {"name": "lineNumber", "type": "number", "optional": false, "description": "Line number in the script (0-based).", "typeRef": null}, {"name": "columnNumber", "type": "number", "optional": true, "description": "Column number in the script (0-based).", "typeRef": null}, {"name": "type", "type": "string", "optional": true, "description": "", "typeRef": null}]);
1576
1577
  inspectorBackend.registerType("Debugger.WasmDisassemblyChunk", [{"name": "lines", "type": "array", "optional": false, "description": "The next chunk of disassembled lines.", "typeRef": "string"}, {"name": "bytecodeOffsets", "type": "array", "optional": false, "description": "The bytecode offsets describing the start of each line.", "typeRef": "integer"}]);
@@ -4313,7 +4313,7 @@ export const generatedProperties: CSSProperty[] = [
4313
4313
  "overlay"
4314
4314
  ],
4315
4315
  "name": "overscroll-container-type",
4316
- "runtime_flag": "OverscrollGestures",
4316
+ "runtime_flag": "OverscrollAreas",
4317
4317
  "runtime_flag_status": "experimental"
4318
4318
  },
4319
4319
  {
@@ -20731,6 +20731,11 @@ export namespace Debugger {
20731
20731
  WasmExpressionStack = 'wasm-expression-stack',
20732
20732
  }
20733
20733
 
20734
+ export const enum ScopeEmptyReason {
20735
+ NoVariables = 'no-variables',
20736
+ AllUnavailable = 'all-unavailable',
20737
+ }
20738
+
20734
20739
  /**
20735
20740
  * Scope description.
20736
20741
  */
@@ -20755,13 +20760,13 @@ export namespace Debugger {
20755
20760
  */
20756
20761
  endLocation?: Location;
20757
20762
  /**
20758
- * True if the scope does not declare any variables.
20759
- * Only present if true.
20763
+ * Present if the scope has no variable values to show. Absent means that
20764
+ * the scope declares at least one variable with an available value.
20760
20765
  * Empty scopes are retained in the scope chain because
20761
20766
  * they can be targeted via `evaluateOnCallFrame` (using `scopeNumber`) or
20762
20767
  * matched against scopes in source maps.
20763
20768
  */
20764
- empty?: boolean;
20769
+ emptyReason?: ScopeEmptyReason;
20765
20770
  }
20766
20771
 
20767
20772
  /**
@@ -13,13 +13,15 @@ import {
13
13
  type ContextResponse,
14
14
  type ConversationContext,
15
15
  type MultimodalInputType,
16
+ type ParsedResponse,
16
17
  type RequestOptions,
17
18
  ResponseType,
19
+ sanitizeSuggestions,
18
20
  } from './agents/AiAgent.js';
19
21
  import {type ExecuteJsAgentOptions, executeJsCode} from './agents/ExecuteJavascript.js';
20
22
  import {ChangeManager} from './ChangeManager.js';
21
- import {AccessibilityContext} from './contexts/AccessibilityContext.js';
22
23
  import {DOMNodeContext} from './contexts/DOMNodeContext.js';
24
+ import {LighthouseContext} from './contexts/LighthouseContext.js';
23
25
  import {PerformanceTraceContext} from './contexts/PerformanceTraceContext.js';
24
26
  import {debugLog} from './debug.js';
25
27
  import {ExtensionScope} from './ExtensionScope.js';
@@ -85,6 +87,110 @@ export interface AiAgent2Options extends ExecuteJsAgentOptions {
85
87
  performanceRecordAndReload?: () => Promise<Trace.TraceModel.ParsedTrace>;
86
88
  }
87
89
 
90
+ /**
91
+ * Matches the follow-up suggestions directive in plain uppercase, at the start
92
+ * of the line or after whitespace: `SUGGESTIONS: ["a"]`, `Done. SUGGESTIONS: ["a"]`.
93
+ * It is case-sensitive, so `Suggestions:` and `DEFAULT_SUGGESTIONS:` do not match.
94
+ * - Group 1: the text before the directive.
95
+ */
96
+ const PLAIN_SUGGESTIONS_REGEX = /^(.*?)(?:^|\s)SUGGESTIONS:/;
97
+
98
+ /**
99
+ * Matches any spelling of the follow-up suggestions directive that is followed
100
+ * by an array ending the line, for example `**Suggestions**: ["a"]`,
101
+ * `- suggestions: ["a"]` or `` `SUGGESTIONS`: ["a"] ``. The keyword is
102
+ * case-insensitive, must be at the start of the line or after whitespace, and
103
+ * can have up to three markdown characters (`*`, `_` or `` ` ``) on each side
104
+ * of it and of the colon. Only markdown characters or whitespace can follow the
105
+ * closing `]`, so `A few suggestions: [see docs](url)` does not match.
106
+ * - Group 1: the text before the directive. It is greedy, so the last keyword
107
+ * on the line is used: `**Suggestions**: [1] Fix. SUGGESTIONS: ["a"]` keeps
108
+ * `**Suggestions**: [1] Fix.` as answer text.
109
+ * - Group 2: the array, from the first `[` after the keyword to the last `]`.
110
+ */
111
+ const FORMATTED_SUGGESTIONS_REGEX = /^(.*)(?:^|\s)[*_`]{0,3}suggestions[*_`]{0,3}:[*_`]{0,3}\s*`?(\[.*\])[*_`\s]*$/i;
112
+
113
+ /**
114
+ * Matches the start of any spelling of the follow-up suggestions directive
115
+ * while it is streaming: the keyword in any case, at the start of the line or
116
+ * after whitespace, with optional markdown characters, followed by `:`. Nothing
117
+ * after the colon is checked, because the array may still be arriving.
118
+ * - Group 1: the text before the directive.
119
+ */
120
+ const STREAMING_SUGGESTIONS_REGEX = /^(.*?)(?:^|\s)[*_`]{0,3}suggestions[*_`]{0,3}:/i;
121
+
122
+ /**
123
+ * Matches a markdown list or heading marker on its own, such as `-`, `1.` or
124
+ * `###`. The marker is dropped when the directive was the only thing after it.
125
+ */
126
+ const LIST_OR_HEADING_MARKER_REGEX = /^(?:[-*+]|\d+\.|#{1,6})$/;
127
+
128
+ /**
129
+ * Returns the answer text with the directive removed from the last line.
130
+ * `textBeforeDirective` is what the last line contained before the directive.
131
+ */
132
+ function removeDirectiveFromLastLine(lines: string[], textBeforeDirective: string): string {
133
+ const keptText = textBeforeDirective.trimEnd();
134
+ const lastLine = LIST_OR_HEADING_MARKER_REGEX.test(keptText.trim()) ? '' : keptText;
135
+ return [...lines.slice(0, -1), lastLine].join('\n').trimEnd();
136
+ }
137
+
138
+ /**
139
+ * Parses the JSON array of a suggestions directive. Returns `null` if `array`
140
+ * is not valid JSON, and `{suggestions: undefined}` if it is valid but holds no
141
+ * usable suggestions.
142
+ */
143
+ function parseSuggestionsArray(array: string): {suggestions: [string, ...string[]]|undefined}|null {
144
+ try {
145
+ return {suggestions: sanitizeSuggestions(array)};
146
+ } catch {
147
+ // Invalid JSON: the caller decides whether the line is still a directive.
148
+ return null;
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Parses a completed response. Only the last line can be the follow-up
154
+ * suggestions directive; every other line is always kept as answer text.
155
+ * 1. If any spelling of the directive is followed by a valid JSON array that
156
+ * ends the line, it is removed and the array becomes the suggestions.
157
+ * 2. Otherwise, if the line contains `SUGGESTIONS:` in plain uppercase, it is
158
+ * removed from the keyword onward even though the array is missing or not
159
+ * valid JSON, and no suggestions are returned.
160
+ * 3. Otherwise, the line is kept unchanged.
161
+ * Text before the directive on the same line is kept.
162
+ */
163
+ function parseCompletedSuggestions(text: string): ParsedResponse {
164
+ const lines = text.split('\n');
165
+ const lastLine = lines[lines.length - 1];
166
+
167
+ const formatted = lastLine.match(FORMATTED_SUGGESTIONS_REGEX);
168
+ const parsedArray = formatted ? parseSuggestionsArray(formatted[2]) : null;
169
+ if (formatted && parsedArray) {
170
+ const answer = removeDirectiveFromLastLine(lines, formatted[1]);
171
+ return parsedArray.suggestions ? {answer, suggestions: parsedArray.suggestions} : {answer};
172
+ }
173
+
174
+ const plain = lastLine.match(PLAIN_SUGGESTIONS_REGEX);
175
+ if (plain) {
176
+ return {answer: removeDirectiveFromLastLine(lines, plain[1])};
177
+ }
178
+
179
+ return {answer: text};
180
+ }
181
+
182
+ /**
183
+ * Hides a follow-up suggestions directive that is still streaming on the last
184
+ * line, from the keyword onward, without looking at its array. Hiding text by
185
+ * mistake is temporary: the completed response is parsed again by
186
+ * `parseCompletedSuggestions()`.
187
+ */
188
+ function hideStreamingSuggestions(text: string): string {
189
+ const lines = text.split('\n');
190
+ const match = lines[lines.length - 1].match(STREAMING_SUGGESTIONS_REGEX);
191
+ return match ? removeDirectiveFromLastLine(lines, match[1]) : text;
192
+ }
193
+
88
194
  export class AiAgent2 extends AiAgent<unknown> {
89
195
  // TODO: The static preamble is a placeholder and will eventually live server-side.
90
196
  readonly preamble: string = preamble;
@@ -236,6 +342,22 @@ Do NOT call \`learnSkills\` for skills that are already loaded.
236
342
  User query: ${enhancedQuery}`;
237
343
  }
238
344
 
345
+ /**
346
+ * Parses a completed response. Only the last line can be the follow-up
347
+ * suggestions directive. See `parseCompletedSuggestions()`.
348
+ */
349
+ override parseTextResponse(response: string): ParsedResponse {
350
+ return parseCompletedSuggestions(response.trim());
351
+ }
352
+
353
+ /**
354
+ * Parses a response that is still streaming, hiding a suggestions directive
355
+ * on the last line. See `hideStreamingSuggestions()`.
356
+ */
357
+ protected override parsePartialTextResponse(response: string): ParsedResponse {
358
+ return {answer: hideStreamingSuggestions(response.trim())};
359
+ }
360
+
239
361
  override async *
240
362
  handleContextDetails(selected: ConversationContext<unknown>|null): AsyncGenerator<ContextResponse, void, void> {
241
363
  if (selected) {
@@ -311,6 +433,8 @@ User query: ${enhancedQuery}`;
311
433
  description: tool.description,
312
434
  parameters: tool.parameters,
313
435
  displayInfoFromArgs: tool.displayInfoFromArgs,
436
+ permissionPrompt: tool.permissionPrompt,
437
+ permissionTitle: tool.permissionTitle,
314
438
  handler: (args, options) => {
315
439
  const context: AllToolsCapabilities = {
316
440
  changeManager: this.#changes,
@@ -319,7 +443,7 @@ User query: ${enhancedQuery}`;
319
443
  getExecutionContextNode: () => this.#getExecutionContextNode(),
320
444
  getTarget: () => this.#getTarget(),
321
445
  getOriginLock: () => this.#originLock(),
322
- getLighthouseReport: () => (this.context instanceof AccessibilityContext ? this.context.getItem() : null),
446
+ getLighthouseReport: () => (this.context instanceof LighthouseContext ? this.context.getItem() : null),
323
447
  runLighthouse: async overrides => await (this.#lighthouseRecording?.(overrides) ?? null),
324
448
  getPerformanceTraceContext: () => (this.context instanceof PerformanceTraceContext ? this.context : null),
325
449
  performanceRecordAndReload: this.#performanceRecordAndReload,
@@ -33,9 +33,9 @@ import {AiAgent2} from './AiAgent2.js';
33
33
  import {AiHistoryStorage, ConversationType, type SerializedConversation} from './AiHistoryStorage.js';
34
34
  import {isContextSelectionEnabled} from './AiUtils.js';
35
35
  import type {ChangeManager} from './ChangeManager.js';
36
- import {AccessibilityContext} from './contexts/AccessibilityContext.js';
37
36
  import {DOMNodeContext} from './contexts/DOMNodeContext.js';
38
37
  import {FileContext} from './contexts/FileContext.js';
38
+ import {LighthouseContext} from './contexts/LighthouseContext.js';
39
39
  import {PerformanceTraceContext} from './contexts/PerformanceTraceContext.js';
40
40
  import {RequestContext} from './contexts/RequestContext.js';
41
41
  import {StorageContext} from './contexts/StorageContext.js';
@@ -207,7 +207,7 @@ export class AiConversation {
207
207
  this.#updateAgent(ConversationType.NETWORK);
208
208
  } else if (updateContext instanceof PerformanceTraceContext) {
209
209
  this.#updateAgent(ConversationType.PERFORMANCE);
210
- } else if (updateContext instanceof AccessibilityContext) {
210
+ } else if (updateContext instanceof LighthouseContext) {
211
211
  this.#updateAgent(ConversationType.ACCESSIBILITY);
212
212
  } else if (updateContext instanceof StorageContext) {
213
213
  this.#updateAgent(ConversationType.STORAGE);
@@ -122,6 +122,10 @@ export function isGeminiBranding(): boolean {
122
122
  return !!Root.Runtime.hostConfig.devToolsGeminiRebranding?.enabled;
123
123
  }
124
124
 
125
+ export function isNaturalLanguageInterfaceEnabled(): boolean {
126
+ return Boolean(Root.Runtime.hostConfig.devToolsAiNaturalLanguageInterface?.enabled);
127
+ }
128
+
125
129
  /**
126
130
  * Returns true if context selection / dynamic context switching is enabled.
127
131
  *