chrome-devtools-frontend 1.0.1556696 → 1.0.1559913

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 (201) hide show
  1. package/front_end/Images/src/container.svg +4 -0
  2. package/front_end/core/common/Gzip.ts +15 -0
  3. package/front_end/core/common/Object.ts +5 -1
  4. package/front_end/core/host/ResourceLoader.ts +1 -1
  5. package/front_end/core/host/UserMetrics.ts +3 -1
  6. package/front_end/core/sdk/CSSMetadata.ts +6 -6
  7. package/front_end/core/sdk/CSSModel.ts +2 -2
  8. package/front_end/core/sdk/DOMModel.ts +14 -3
  9. package/front_end/core/sdk/NetworkManager.ts +0 -7
  10. package/front_end/core/sdk/SourceMap.ts +16 -2
  11. package/front_end/core/sdk/SourceMapManager.ts +1 -1
  12. package/front_end/core/sdk/SourceMapScopesInfo.ts +11 -4
  13. package/front_end/entrypoints/formatter_worker/FormatterActions.ts +1 -0
  14. package/front_end/entrypoints/formatter_worker/ScopeParser.ts +51 -8
  15. package/front_end/entrypoints/main/GlobalAiButton.ts +5 -1
  16. package/front_end/generated/Deprecation.ts +0 -7
  17. package/front_end/generated/InspectorBackendCommands.ts +5 -4
  18. package/front_end/generated/SupportedCSSProperties.js +64 -32
  19. package/front_end/generated/protocol-mapping.d.ts +9 -0
  20. package/front_end/generated/protocol-proxy-api.d.ts +7 -0
  21. package/front_end/generated/protocol.ts +23 -1
  22. package/front_end/models/ai_assistance/agents/StylingAgent.ts +1 -1
  23. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +11 -7
  24. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +3 -3
  25. package/front_end/models/bindings/CompilerScriptMapping.ts +7 -6
  26. package/front_end/models/bindings/DebuggerWorkspaceBinding.ts +4 -4
  27. package/front_end/models/javascript_metadata/NativeFunctions.js +2 -2
  28. package/front_end/models/stack_trace/StackTraceImpl.ts +5 -3
  29. package/front_end/models/stack_trace/StackTraceModel.ts +53 -40
  30. package/front_end/models/trace/EventsSerializer.ts +8 -2
  31. package/front_end/models/trace/LanternComputationData.ts +4 -3
  32. package/front_end/models/trace/Processor.ts +6 -5
  33. package/front_end/models/trace/Styles.ts +10 -1
  34. package/front_end/models/trace/handlers/LargestImagePaintHandler.ts +2 -2
  35. package/front_end/models/trace/handlers/LayoutShiftsHandler.ts +2 -2
  36. package/front_end/models/trace/handlers/MetaHandler.ts +14 -0
  37. package/front_end/models/trace/handlers/PageLoadMetricsHandler.ts +54 -34
  38. package/front_end/models/trace/helpers/Timing.ts +8 -1
  39. package/front_end/models/trace/insights/Common.ts +1 -1
  40. package/front_end/models/trace/insights/LCPBreakdown.ts +4 -4
  41. package/front_end/models/trace/insights/LCPDiscovery.ts +3 -3
  42. package/front_end/models/trace/insights/RenderBlocking.ts +1 -1
  43. package/front_end/models/trace/insights/types.ts +1 -1
  44. package/front_end/models/trace/types/TraceEvents.ts +62 -10
  45. package/front_end/panels/application/AppManifestView.ts +134 -223
  46. package/front_end/panels/application/CookieItemsView.ts +1 -0
  47. package/front_end/panels/application/SharedStorageTreeElement.ts +3 -0
  48. package/front_end/panels/application/appManifestView.css +1 -1
  49. package/front_end/panels/common/AiCodeGenerationTeaser.ts +48 -12
  50. package/front_end/panels/common/aiCodeGenerationTeaser.css +14 -0
  51. package/front_end/panels/common/common.ts +1 -1
  52. package/front_end/panels/console/ConsoleViewMessage.ts +4 -3
  53. package/front_end/panels/console/consoleView.css +1 -1
  54. package/front_end/panels/elements/CSSRuleValidator.ts +38 -0
  55. package/front_end/panels/elements/ElementsTreeElement.ts +108 -58
  56. package/front_end/panels/elements/ElementsTreeOutline.ts +0 -17
  57. package/front_end/panels/elements/ElementsTreeOutlineRenderer.ts +7 -1
  58. package/front_end/panels/elements/StylesSidebarPane.ts +15 -4
  59. package/front_end/panels/elements/components/AdornerManager.ts +8 -0
  60. package/front_end/panels/emulation/DeviceModeToolbar.ts +3 -1
  61. package/front_end/panels/issues/AffectedResourcesView.ts +0 -1
  62. package/front_end/panels/lighthouse/LighthousePanel.ts +10 -0
  63. package/front_end/panels/lighthouse/lighthousePanel.css +46 -3
  64. package/front_end/panels/network/NetworkLogViewColumns.ts +9 -9
  65. package/front_end/panels/network/RequestCookiesView.ts +125 -141
  66. package/front_end/panels/network/components/RequestHeadersView.ts +2 -2
  67. package/front_end/panels/network/requestCookiesView.css +22 -20
  68. package/front_end/panels/recorder/components/RecordingView.ts +3 -3
  69. package/front_end/panels/recorder/components/StepView.ts +2 -1
  70. package/front_end/panels/settings/keybindsSettingsTab.css +4 -0
  71. package/front_end/panels/sources/CallStackSidebarPane.ts +7 -3
  72. package/front_end/panels/sources/DebuggerPausedMessage.ts +125 -90
  73. package/front_end/panels/sources/SourcesPanel.ts +10 -7
  74. package/front_end/panels/sources/debuggerPausedMessage.css +8 -0
  75. package/front_end/panels/timeline/StatusDialog.ts +4 -3
  76. package/front_end/panels/timeline/TimelineFlameChartNetworkDataProvider.ts +3 -16
  77. package/front_end/panels/timeline/TimelineFlameChartView.ts +64 -21
  78. package/front_end/panels/timeline/TimelinePanel.ts +71 -24
  79. package/front_end/panels/timeline/TimelineUIUtils.ts +28 -2
  80. package/front_end/panels/timeline/TimingsTrackAppender.ts +3 -1
  81. package/front_end/panels/timeline/components/SidebarSingleInsightSet.ts +1 -1
  82. package/front_end/panels/timeline/components/insights/RenderBlocking.ts +6 -4
  83. package/front_end/panels/timeline/components/sidebarInsightsTab.css +2 -0
  84. package/front_end/panels/timeline/overlays/OverlaysImpl.ts +4 -0
  85. package/front_end/panels/timeline/timelinePanel.css +8 -1
  86. package/front_end/panels/timeline/utils/EntryNodes.ts +2 -1
  87. package/front_end/third_party/acorn/estree-legacy.d.ts +2 -0
  88. package/front_end/third_party/chromium/README.chromium +1 -1
  89. package/front_end/third_party/puppeteer/README.chromium +2 -2
  90. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Browser.d.ts +12 -0
  91. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Browser.d.ts.map +1 -1
  92. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Browser.js.map +1 -1
  93. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.d.ts +14 -2
  94. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.d.ts.map +1 -1
  95. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.js.map +1 -1
  96. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Browser.d.ts +3 -1
  97. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Browser.d.ts.map +1 -1
  98. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Browser.js +6 -0
  99. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Browser.js.map +1 -1
  100. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPRequest.d.ts +0 -1
  101. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPRequest.d.ts.map +1 -1
  102. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPRequest.js +0 -20
  103. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPRequest.js.map +1 -1
  104. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Page.d.ts +3 -1
  105. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Page.d.ts.map +1 -1
  106. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Page.js +10 -14
  107. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Page.js.map +1 -1
  108. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/BrowsingContext.d.ts +1 -0
  109. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/BrowsingContext.d.ts.map +1 -1
  110. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/BrowsingContext.js +14 -0
  111. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/BrowsingContext.js.map +1 -1
  112. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.d.ts +3 -1
  113. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.d.ts.map +1 -1
  114. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.js +12 -0
  115. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.js.map +1 -1
  116. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/EmulationManager.d.ts +1 -0
  117. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/EmulationManager.d.ts.map +1 -1
  118. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/EmulationManager.js +22 -0
  119. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/EmulationManager.js.map +1 -1
  120. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.d.ts +3 -1
  121. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.d.ts.map +1 -1
  122. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.js +9 -2
  123. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.js.map +1 -1
  124. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
  125. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +3 -3
  126. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +3 -3
  127. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js.map +1 -1
  128. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
  129. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.d.ts +1 -1
  130. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.js +1 -1
  131. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.d.ts +26 -0
  132. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +72 -15
  133. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Browser.d.ts +12 -0
  134. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Browser.d.ts.map +1 -1
  135. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Browser.js.map +1 -1
  136. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.d.ts +14 -2
  137. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.d.ts.map +1 -1
  138. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.js.map +1 -1
  139. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Browser.d.ts +3 -1
  140. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Browser.d.ts.map +1 -1
  141. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Browser.js +6 -0
  142. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Browser.js.map +1 -1
  143. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPRequest.d.ts +0 -1
  144. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPRequest.d.ts.map +1 -1
  145. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPRequest.js +0 -20
  146. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPRequest.js.map +1 -1
  147. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Page.d.ts +3 -1
  148. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Page.d.ts.map +1 -1
  149. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Page.js +11 -15
  150. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Page.js.map +1 -1
  151. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/BrowsingContext.d.ts +1 -0
  152. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/BrowsingContext.d.ts.map +1 -1
  153. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/BrowsingContext.js +14 -0
  154. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/BrowsingContext.js.map +1 -1
  155. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.d.ts +3 -1
  156. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.d.ts.map +1 -1
  157. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.js +12 -0
  158. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.js.map +1 -1
  159. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/EmulationManager.d.ts +1 -0
  160. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/EmulationManager.d.ts.map +1 -1
  161. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/EmulationManager.js +22 -0
  162. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/EmulationManager.js.map +1 -1
  163. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.d.ts +3 -1
  164. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.d.ts.map +1 -1
  165. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.js +9 -2
  166. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.js.map +1 -1
  167. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +3 -3
  168. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +3 -3
  169. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js.map +1 -1
  170. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.d.ts +1 -1
  171. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.js +1 -1
  172. package/front_end/third_party/puppeteer/package/lib/types.d.ts +26 -0
  173. package/front_end/third_party/puppeteer/package/package.json +1 -1
  174. package/front_end/third_party/puppeteer/package/src/api/Browser.ts +18 -0
  175. package/front_end/third_party/puppeteer/package/src/api/Page.ts +16 -2
  176. package/front_end/third_party/puppeteer/package/src/bidi/Browser.ts +13 -0
  177. package/front_end/third_party/puppeteer/package/src/bidi/HTTPRequest.ts +0 -33
  178. package/front_end/third_party/puppeteer/package/src/bidi/Page.ts +14 -28
  179. package/front_end/third_party/puppeteer/package/src/bidi/core/BrowsingContext.ts +19 -0
  180. package/front_end/third_party/puppeteer/package/src/cdp/Browser.ts +19 -0
  181. package/front_end/third_party/puppeteer/package/src/cdp/EmulationManager.ts +30 -0
  182. package/front_end/third_party/puppeteer/package/src/cdp/Page.ts +15 -6
  183. package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
  184. package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
  185. package/front_end/ui/components/icon_button/iconButton.css +3 -1
  186. package/front_end/ui/components/report_view/ReportView.ts +11 -2
  187. package/front_end/ui/components/report_view/report.css +16 -0
  188. package/front_end/ui/components/text_editor/AiCodeGenerationProvider.ts +202 -32
  189. package/front_end/ui/components/text_editor/config.ts +6 -6
  190. package/front_end/ui/legacy/ContextMenu.ts +11 -2
  191. package/front_end/ui/legacy/SearchableView.ts +11 -5
  192. package/front_end/ui/legacy/SplitWidget.ts +1 -1
  193. package/front_end/ui/legacy/TextPrompt.ts +1 -1
  194. package/front_end/ui/legacy/Toolbar.ts +4 -0
  195. package/front_end/ui/legacy/UIUtils.ts +0 -2
  196. package/front_end/ui/legacy/components/cookie_table/CookiesTable.ts +18 -3
  197. package/front_end/ui/legacy/components/data_grid/DataGrid.ts +3 -3
  198. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +6 -0
  199. package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +43 -9
  200. package/front_end/ui/visual_logging/KnownContextValues.ts +13 -0
  201. package/package.json +1 -1
@@ -12,6 +12,13 @@ import * as UI from '../../../ui/legacy/legacy.js';
12
12
  import * as VisualLogging from '../../visual_logging/visual_logging.js';
13
13
 
14
14
  import {AiCodeCompletionTeaserPlaceholder} from './AiCodeCompletionTeaserPlaceholder.js';
15
+ import {
16
+ acceptAiAutoCompleteSuggestion,
17
+ aiAutoCompleteSuggestion,
18
+ aiAutoCompleteSuggestionState,
19
+ hasActiveAiSuggestion,
20
+ setAiAutoCompleteSuggestion,
21
+ } from './config.js';
15
22
  import type {TextEditor} from './TextEditor.js';
16
23
 
17
24
  export enum AiCodeGenerationTeaserMode {
@@ -28,30 +35,48 @@ const aiCodeGenerationTeaserModeState = CodeMirror.StateField.define<AiCodeGener
28
35
  },
29
36
  });
30
37
 
38
+ export interface AiCodeGenerationConfig {
39
+ generationContext: {
40
+ inferenceLanguage?: Host.AidaClient.AidaInferenceLanguage,
41
+ };
42
+ onSuggestionAccepted: () => void;
43
+ onRequestTriggered: () => void;
44
+ // TODO(b/445394511): Move exposing citations to onSuggestionAccepted
45
+ onResponseReceived: (citations: Host.AidaClient.Citation[]) => void;
46
+ }
47
+
31
48
  export class AiCodeGenerationProvider {
32
49
  #devtoolsLocale: string;
33
50
  #aiCodeCompletionSetting = Common.Settings.Settings.instance().createSetting('ai-code-completion-enabled', false);
34
51
  #generationTeaserCompartment = new CodeMirror.Compartment();
35
- #generationTeaser: PanelCommon.AiCodeGenerationTeaser;
52
+ #generationTeaser: PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaser;
36
53
  #editor?: TextEditor;
54
+ #aiCodeGenerationConfig: AiCodeGenerationConfig;
55
+ #aiCodeGeneration?: AiCodeGeneration.AiCodeGeneration.AiCodeGeneration;
37
56
 
57
+ #aidaClient: Host.AidaClient.AidaClient = new Host.AidaClient.AidaClient();
38
58
  #boundOnUpdateAiCodeGenerationState = this.#updateAiCodeGenerationState.bind(this);
59
+ #controller = new AbortController();
39
60
 
40
- private constructor() {
61
+ private constructor(aiCodeGenerationConfig: AiCodeGenerationConfig) {
41
62
  this.#devtoolsLocale = i18n.DevToolsLocale.DevToolsLocale.instance().locale;
42
63
  if (!AiCodeGeneration.AiCodeGeneration.AiCodeGeneration.isAiCodeGenerationEnabled(this.#devtoolsLocale)) {
43
64
  throw new Error('AI code generation feature is not enabled.');
44
65
  }
45
- this.#generationTeaser = new PanelCommon.AiCodeGenerationTeaser();
66
+ this.#generationTeaser = new PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaser();
67
+ this.#aiCodeGenerationConfig = aiCodeGenerationConfig;
46
68
  }
47
69
 
48
- static createInstance(): AiCodeGenerationProvider {
49
- return new AiCodeGenerationProvider();
70
+ static createInstance(aiCodeGenerationConfig: AiCodeGenerationConfig): AiCodeGenerationProvider {
71
+ return new AiCodeGenerationProvider(aiCodeGenerationConfig);
50
72
  }
51
73
 
52
74
  extension(): CodeMirror.Extension[] {
53
75
  return [
54
- CodeMirror.EditorView.updateListener.of(update => this.activateTeaser(update)),
76
+ CodeMirror.EditorView.updateListener.of(update => this.#activateTeaser(update)),
77
+ CodeMirror.EditorView.updateListener.of(update => this.#abortGenerationDuringUpdate(update)),
78
+ aiAutoCompleteSuggestion,
79
+ aiAutoCompleteSuggestionState,
55
80
  aiCodeGenerationTeaserModeState,
56
81
  this.#generationTeaserCompartment.of([]),
57
82
  CodeMirror.Prec.highest(CodeMirror.keymap.of(this.#editorKeymap())),
@@ -59,6 +84,7 @@ export class AiCodeGenerationProvider {
59
84
  }
60
85
 
61
86
  dispose(): void {
87
+ this.#controller.abort();
62
88
  this.#cleanupAiCodeGeneration();
63
89
  }
64
90
 
@@ -71,6 +97,10 @@ export class AiCodeGenerationProvider {
71
97
  }
72
98
 
73
99
  #setupAiCodeGeneration(): void {
100
+ if (this.#aiCodeGeneration) {
101
+ return;
102
+ }
103
+ this.#aiCodeGeneration = new AiCodeGeneration.AiCodeGeneration.AiCodeGeneration({aidaClient: this.#aidaClient});
74
104
  this.#editor?.dispatch({
75
105
  effects:
76
106
  [this.#generationTeaserCompartment.reconfigure([aiCodeGenerationTeaserExtension(this.#generationTeaser)])],
@@ -78,6 +108,10 @@ export class AiCodeGenerationProvider {
78
108
  }
79
109
 
80
110
  #cleanupAiCodeGeneration(): void {
111
+ if (!this.#aiCodeGeneration) {
112
+ return;
113
+ }
114
+ this.#aiCodeGeneration = undefined;
81
115
  this.#editor?.dispatch({
82
116
  effects: [this.#generationTeaserCompartment.reconfigure([])],
83
117
  });
@@ -99,23 +133,53 @@ export class AiCodeGenerationProvider {
99
133
  {
100
134
  key: 'Escape',
101
135
  run: (): boolean => {
102
- if (!this.#editor || !this.#generationTeaser.isShowing() || !this.#generationTeaser.loading) {
136
+ if (!this.#editor || !this.#aiCodeGeneration) {
137
+ return false;
138
+ }
139
+ if (hasActiveAiSuggestion(this.#editor.state)) {
140
+ this.#editor.dispatch({
141
+ effects: setAiAutoCompleteSuggestion.of(null),
142
+ });
143
+ return true;
144
+ }
145
+ const generationTeaserIsLoading = this.#generationTeaser.displayState ===
146
+ PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaserDisplayState.LOADING;
147
+ if (this.#generationTeaser.isShowing() && generationTeaserIsLoading) {
148
+ this.#controller.abort();
149
+ this.#controller = new AbortController();
150
+ this.#dismissTeaser();
151
+ return true;
152
+ }
153
+ return false;
154
+ },
155
+ },
156
+ {
157
+ key: 'Tab',
158
+ run: (): boolean => {
159
+ if (!this.#aiCodeGeneration || !this.#editor || !hasActiveAiSuggestion(this.#editor.state)) {
103
160
  return false;
104
161
  }
105
- this.#editor.dispatch({effects: setAiCodeGenerationTeaserMode.of(AiCodeGenerationTeaserMode.DISMISSED)});
162
+ const {accepted, suggestion} = acceptAiAutoCompleteSuggestion(this.#editor.editor);
163
+ if (!accepted) {
164
+ return false;
165
+ }
166
+ if (suggestion?.rpcGlobalId) {
167
+ this.#aiCodeGeneration.registerUserAcceptance(suggestion.rpcGlobalId, suggestion.sampleId);
168
+ }
169
+ this.#aiCodeGenerationConfig?.onSuggestionAccepted();
106
170
  return true;
107
171
  },
108
172
  },
109
173
  {
110
174
  any: (_view: unknown, event: KeyboardEvent) => {
111
- if (!this.#editor || !this.#generationTeaser.isShowing()) {
175
+ if (!this.#editor || !this.#aiCodeGeneration || !this.#generationTeaser.isShowing()) {
112
176
  return false;
113
177
  }
114
178
  if (UI.KeyboardShortcut.KeyboardShortcut.eventHasCtrlEquivalentKey(event)) {
115
179
  if (event.key === 'i') {
116
180
  event.consume(true);
117
181
  void VisualLogging.logKeyDown(event.currentTarget, event, 'ai-code-generation.triggered');
118
- this.#generationTeaser.loading = true;
182
+ void this.#triggerAiCodeGeneration({signal: this.#controller.signal});
119
183
  return true;
120
184
  }
121
185
  }
@@ -125,7 +189,12 @@ export class AiCodeGenerationProvider {
125
189
  ];
126
190
  }
127
191
 
128
- async activateTeaser(update: CodeMirror.ViewUpdate): Promise<void> {
192
+ #dismissTeaser(): void {
193
+ this.#generationTeaser.displayState = PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaserDisplayState.TRIGGER;
194
+ this.#editor?.dispatch({effects: setAiCodeGenerationTeaserMode.of(AiCodeGenerationTeaserMode.DISMISSED)});
195
+ }
196
+
197
+ #activateTeaser(update: CodeMirror.ViewUpdate): void {
129
198
  const currentTeaserMode = update.state.field(aiCodeGenerationTeaserModeState);
130
199
  if (currentTeaserMode === AiCodeGenerationTeaserMode.ACTIVE) {
131
200
  return;
@@ -135,46 +204,147 @@ export class AiCodeGenerationProvider {
135
204
  }
136
205
  update.view.dispatch({effects: setAiCodeGenerationTeaserMode.of(AiCodeGenerationTeaserMode.ACTIVE)});
137
206
  }
207
+
208
+ /**
209
+ * Monitors editor changes to cancel an ongoing AI generation.
210
+ * We abort the request and dismiss the teaser if the user modifies the
211
+ * document or moves their cursor/selection. These actions indicate the user
212
+ * is no longer focused on the current generation point or has manually
213
+ * resumed editing, making the pending suggestion irrelevant.
214
+ */
215
+ #abortGenerationDuringUpdate(update: CodeMirror.ViewUpdate): void {
216
+ if (!update.docChanged && update.state.selection.main.head === update.startState.selection.main.head) {
217
+ return;
218
+ }
219
+ const currentTeaserMode = update.state.field(aiCodeGenerationTeaserModeState);
220
+ const generationTeaserIsLoading = this.#generationTeaser.displayState ===
221
+ PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaserDisplayState.LOADING;
222
+ // Generation should be in progress
223
+ if (currentTeaserMode === AiCodeGenerationTeaserMode.DISMISSED || !generationTeaserIsLoading) {
224
+ return;
225
+ }
226
+ this.#controller.abort();
227
+ this.#controller = new AbortController();
228
+ this.#dismissTeaser();
229
+ }
230
+
231
+ async #triggerAiCodeGeneration(options?: {signal?: AbortSignal}): Promise<void> {
232
+ if (!this.#editor || !this.#aiCodeGeneration) {
233
+ return;
234
+ }
235
+
236
+ this.#generationTeaser.displayState = PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaserDisplayState.LOADING;
237
+ const cursor = this.#editor.state.selection.main.head;
238
+ // TODO(b/445899453): Detect all types of comments
239
+ const query = this.#editor.state.doc.lineAt(cursor).text;
240
+ if (query.trim().length === 0) {
241
+ return;
242
+ }
243
+
244
+ try {
245
+ const startTime = performance.now();
246
+ this.#aiCodeGenerationConfig?.onRequestTriggered();
247
+ Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiCodeGenerationRequestTriggered);
248
+
249
+ const generationResponse = await this.#aiCodeGeneration.generateCode(
250
+ query, AiCodeGeneration.AiCodeGeneration.basePreamble,
251
+ this.#aiCodeGenerationConfig?.generationContext.inferenceLanguage, options);
252
+
253
+ if (this.#generationTeaser) {
254
+ this.#dismissTeaser();
255
+ }
256
+
257
+ if (!generationResponse || generationResponse.samples.length === 0) {
258
+ this.#aiCodeGenerationConfig?.onResponseReceived([]);
259
+ return;
260
+ }
261
+ const topSample = generationResponse.samples[0];
262
+
263
+ const shouldBlock = topSample.attributionMetadata?.attributionAction === Host.AidaClient.RecitationAction.BLOCK;
264
+ if (shouldBlock) {
265
+ return;
266
+ }
267
+
268
+ this.#editor.dispatch({
269
+ effects: setAiAutoCompleteSuggestion.of({
270
+ text: '\n' + topSample.generationString,
271
+ from: cursor,
272
+ rpcGlobalId: generationResponse.metadata.rpcGlobalId,
273
+ sampleId: topSample.sampleId,
274
+ startTime,
275
+ onImpression: this.#aiCodeGeneration?.registerUserImpression.bind(this.#aiCodeGeneration),
276
+ })
277
+ });
278
+
279
+ AiCodeGeneration.debugLog('Suggestion dispatched to the editor', topSample.generationString);
280
+ const citations = topSample.attributionMetadata?.citations ?? [];
281
+ this.#aiCodeGenerationConfig?.onResponseReceived(citations);
282
+ } catch (e) {
283
+ AiCodeGeneration.debugLog('Error while fetching code generation suggestions from AIDA', e);
284
+ this.#aiCodeGenerationConfig?.onResponseReceived([]);
285
+ Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiCodeGenerationError);
286
+ }
287
+
288
+ if (this.#generationTeaser) {
289
+ this.#dismissTeaser();
290
+ }
291
+ }
138
292
  }
139
293
 
140
- // TODO(b/445899453): Handle teaser's discovery mode
141
- function aiCodeGenerationTeaserExtension(teaser: PanelCommon.AiCodeGenerationTeaser): CodeMirror.Extension {
294
+ function aiCodeGenerationTeaserExtension(teaser: PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaser):
295
+ CodeMirror.Extension {
142
296
  return CodeMirror.ViewPlugin.fromClass(class {
143
- #teaserMode: AiCodeGenerationTeaserMode;
297
+ #view: CodeMirror.EditorView;
144
298
 
145
- constructor(readonly view: CodeMirror.EditorView) {
146
- this.#teaserMode = view.state.field(aiCodeGenerationTeaserModeState);
299
+ constructor(view: CodeMirror.EditorView) {
300
+ this.#view = view;
301
+ this.#updateTeaserState(view.state);
147
302
  }
148
303
 
149
304
  update(update: CodeMirror.ViewUpdate): void {
150
- const currentTeaserMode = update.state.field(aiCodeGenerationTeaserModeState);
151
- if (currentTeaserMode !== this.#teaserMode) {
152
- this.#teaserMode = currentTeaserMode;
153
- }
154
305
  if (!update.docChanged && update.state.selection.main.head === update.startState.selection.main.head) {
155
306
  return;
156
307
  }
157
- if (teaser.loading) {
158
- teaser.loading = false;
159
- }
308
+ this.#updateTeaserState(update.state);
160
309
  }
161
310
 
162
311
  get decorations(): CodeMirror.DecorationSet {
163
- if (this.#teaserMode === AiCodeGenerationTeaserMode.DISMISSED) {
312
+ const teaserMode = this.#view.state.field(aiCodeGenerationTeaserModeState);
313
+ if (teaserMode === AiCodeGenerationTeaserMode.DISMISSED) {
164
314
  return CodeMirror.Decoration.none;
165
315
  }
166
- const cursorPosition = this.view.state.selection.main.head;
167
- const line = this.view.state.doc.lineAt(cursorPosition);
316
+
317
+ const cursorPosition = this.#view.state.selection.main.head;
318
+ const line = this.#view.state.doc.lineAt(cursorPosition);
319
+
320
+ const isEmptyLine = line.length === 0;
168
321
  // TODO(b/445899453): Detect all types of comments
169
322
  const isComment = line.text.startsWith('//');
170
323
  const isCursorAtEndOfLine = cursorPosition >= line.to;
171
- if (!isComment || !isCursorAtEndOfLine) {
172
- return CodeMirror.Decoration.none;
324
+
325
+ if ((isEmptyLine) || (isComment && isCursorAtEndOfLine)) {
326
+ return CodeMirror.Decoration.set([
327
+ CodeMirror.Decoration.widget({widget: new AiCodeCompletionTeaserPlaceholder(teaser), side: 1})
328
+ .range(cursorPosition),
329
+ ]);
330
+ }
331
+ return CodeMirror.Decoration.none;
332
+ }
333
+
334
+ #updateTeaserState(state: CodeMirror.EditorState): void {
335
+ // Only handle non loading states, as updates during generation are handled by
336
+ // #abortGenerationDuringUpdate in AiCodeGenerationProvider
337
+ if (teaser.displayState === PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaserDisplayState.LOADING) {
338
+ return;
339
+ }
340
+ const cursorPosition = state.selection.main.head;
341
+ const line = state.doc.lineAt(cursorPosition);
342
+ const isEmptyLine = line.length === 0;
343
+ if (isEmptyLine) {
344
+ teaser.displayState = PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaserDisplayState.DISCOVERY;
345
+ } else {
346
+ teaser.displayState = PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaserDisplayState.TRIGGER;
173
347
  }
174
- return CodeMirror.Decoration.set([
175
- CodeMirror.Decoration.widget({widget: new AiCodeCompletionTeaserPlaceholder(teaser), side: 1})
176
- .range(cursorPosition),
177
- ]);
178
348
  }
179
349
  }, {
180
350
  decorations: v => v.decorations,
@@ -490,7 +490,7 @@ export interface ActiveSuggestion {
490
490
  rpcGlobalId?: Host.AidaClient.RpcGlobalId;
491
491
  startTime: number;
492
492
  onImpression: (rpcGlobalId: Host.AidaClient.RpcGlobalId, latency: number, sampleId?: number) => void;
493
- clearCachedRequest: () => void;
493
+ clearCachedRequest?: () => void;
494
494
  }
495
495
 
496
496
  export const aiAutoCompleteSuggestionState = CM.StateField.define<ActiveSuggestion|null>({
@@ -501,7 +501,7 @@ export const aiAutoCompleteSuggestionState = CM.StateField.define<ActiveSuggesti
501
501
  if (effect.value) {
502
502
  return effect.value;
503
503
  }
504
- value?.clearCachedRequest();
504
+ value?.clearCachedRequest?.();
505
505
  return null;
506
506
  }
507
507
  }
@@ -514,14 +514,14 @@ export const aiAutoCompleteSuggestionState = CM.StateField.define<ActiveSuggesti
514
514
  // between when the request was sent and the response was received.
515
515
  // We check if the position is still valid before trying to map it.
516
516
  if (value.from > tr.state.doc.length) {
517
- value.clearCachedRequest();
517
+ value.clearCachedRequest?.();
518
518
  return null;
519
519
  }
520
520
 
521
521
  // If deletion occurs, set to null. Otherwise, the mapping might fail if
522
522
  // the position is inside the deleted range.
523
523
  if (tr.docChanged && tr.state.doc.length < tr.startState.doc.length) {
524
- value.clearCachedRequest();
524
+ value.clearCachedRequest?.();
525
525
  return null;
526
526
  }
527
527
 
@@ -530,7 +530,7 @@ export const aiAutoCompleteSuggestionState = CM.StateField.define<ActiveSuggesti
530
530
 
531
531
  // If a change happened before the position from which suggestion was generated, set to null.
532
532
  if (tr.docChanged && head < from) {
533
- value.clearCachedRequest();
533
+ value.clearCachedRequest?.();
534
534
  return null;
535
535
  }
536
536
 
@@ -571,7 +571,7 @@ export function acceptAiAutoCompleteSuggestion(view: CM.EditorView):
571
571
  userEvent: 'input.complete',
572
572
  });
573
573
 
574
- suggestion.clearCachedRequest();
574
+ suggestion.clearCachedRequest?.();
575
575
  return {accepted: true, suggestion};
576
576
  }
577
577
 
@@ -787,8 +787,17 @@ export class ContextMenu extends SubMenu {
787
787
 
788
788
  const menuObject = this.buildMenuDescriptors();
789
789
  const ownerDocument = (this.eventTarget as HTMLElement).ownerDocument;
790
- if (this.useSoftMenu || ContextMenu.useSoftMenu ||
791
- Host.InspectorFrontendHost.InspectorFrontendHostInstance.isHostedMode()) {
790
+
791
+ let useSoftMenu = this.useSoftMenu || ContextMenu.useSoftMenu ||
792
+ Host.InspectorFrontendHost.InspectorFrontendHostInstance.isHostedMode();
793
+
794
+ // Allow force opening a Native menu when DevTools is under test.
795
+ // This allows opening DevTools on DevTools
796
+ if (!this.useSoftMenu && ContextMenu.useSoftMenu && this.event.altKey) {
797
+ useSoftMenu = false;
798
+ }
799
+
800
+ if (useSoftMenu) {
792
801
  this.softMenu = new SoftContextMenu(
793
802
  (menuObject as SoftContextMenuDescriptor[]), this.itemSelected.bind(this), this.keepOpen, undefined,
794
803
  this.onSoftMenuClosed, this.loggableParent);
@@ -150,6 +150,7 @@ export class SearchableView extends VBox {
150
150
  private replaceToggleButton: ToolbarToggle;
151
151
  private searchInputElement: HTMLInputElement;
152
152
  private matchesElement: HTMLElement;
153
+ private matchesElementValue: HTMLElement;
153
154
  private searchNavigationPrevElement: ToolbarButton;
154
155
  private searchNavigationNextElement: ToolbarButton;
155
156
  private readonly replaceInputElement: HTMLInputElement;
@@ -314,6 +315,9 @@ export class SearchableView extends VBox {
314
315
  this.matchesElement.style.color = 'var(--sys-color-on-surface-subtle)';
315
316
  this.matchesElement.style.padding = '0 var(--sys-size-3)';
316
317
  this.matchesElement.classList.add('search-results-matches');
318
+ ARIAUtils.markAsPoliteLiveRegion(this.matchesElement, false);
319
+ this.matchesElementValue = this.matchesElement.createChild('span');
320
+ ARIAUtils.setHidden(this.matchesElementValue, true);
317
321
  toolbar.appendToolbarItem(matchesText);
318
322
 
319
323
  const cancelButtonElement = new Buttons.Button.Button();
@@ -455,7 +459,7 @@ export class SearchableView extends VBox {
455
459
  resetSearch(): void {
456
460
  this.clearSearch();
457
461
  this.updateReplaceVisibility();
458
- this.matchesElement.textContent = '';
462
+ this.matchesElementValue.textContent = '';
459
463
  }
460
464
 
461
465
  refreshSearch(): void {
@@ -504,15 +508,17 @@ export class SearchableView extends VBox {
504
508
 
505
509
  private updateSearchMatchesCountAndCurrentMatchIndex(matches: number, currentMatchIndex: number): void {
506
510
  if (!this.currentQuery) {
507
- this.matchesElement.textContent = '';
511
+ this.matchesElementValue.textContent = '';
508
512
  } else if (matches === 0 || currentMatchIndex >= 0) {
509
- this.matchesElement.textContent = i18nString(UIStrings.dOfD, {PH1: currentMatchIndex + 1, PH2: matches});
513
+ this.matchesElementValue.textContent = i18nString(UIStrings.dOfD, {PH1: currentMatchIndex + 1, PH2: matches});
510
514
  ARIAUtils.setLabel(
511
515
  this.matchesElement, i18nString(UIStrings.accessibledOfD, {PH1: currentMatchIndex + 1, PH2: matches}));
512
516
  } else if (matches === 1) {
513
- this.matchesElement.textContent = i18nString(UIStrings.matchString);
517
+ this.matchesElementValue.textContent = i18nString(UIStrings.matchString);
518
+ ARIAUtils.setLabel(this.matchesElement, i18nString(UIStrings.matchString));
514
519
  } else {
515
- this.matchesElement.textContent = i18nString(UIStrings.dMatches, {PH1: matches});
520
+ this.matchesElementValue.textContent = i18nString(UIStrings.dMatches, {PH1: matches});
521
+ ARIAUtils.setLabel(this.matchesElement, i18nString(UIStrings.dMatches, {PH1: matches}));
516
522
  }
517
523
  this.updateSearchNavigationButtonState(matches > 0);
518
524
  }
@@ -236,7 +236,7 @@ export class SplitWidget extends Common.ObjectWrapper.eventMixin<EventTypes, typ
236
236
  this.#restoreAndApplyShowModeFromSettings();
237
237
  }
238
238
 
239
- showMode(): string {
239
+ showMode(): ShowMode {
240
240
  return this.#showMode;
241
241
  }
242
242
 
@@ -327,7 +327,7 @@ export class TextPrompt extends Common.ObjectWrapper.ObjectWrapper<EventTypes> i
327
327
  * or the |blurListener| parameter to register a "blur" event listener on the |element|
328
328
  * (since the "blur" event does not bubble.)
329
329
  */
330
- attachAndStartEditing(element: Element, blurListener: (arg0: Event) => void): Element {
330
+ attachAndStartEditing(element: Element, blurListener?: (arg0: Event) => void): Element {
331
331
  const proxyElement = this.#attach(element);
332
332
  this.startEditing(blurListener);
333
333
  return proxyElement;
@@ -1166,6 +1166,10 @@ export class ToolbarComboBox extends ToolbarItem<void, HTMLSelectElement> {
1166
1166
  }
1167
1167
  }
1168
1168
 
1169
+ turnShrinkable(): void {
1170
+ this.element.classList.add('toolbar-has-dropdown-shrinkable');
1171
+ }
1172
+
1169
1173
  size(): number {
1170
1174
  return this.element.childElementCount;
1171
1175
  }
@@ -1295,8 +1295,6 @@ export class DevToolsIconLabel extends HTMLElement {
1295
1295
 
1296
1296
  set data(data: IconData) {
1297
1297
  this.#icon.data = data;
1298
- // TODO(crbug.com/1427397): Clean this up. This was necessary so `DevToolsIconLabel` can use Lit icon
1299
- // while being backwards-compatible with the legacy Icon while working for both small and large icons.
1300
1298
  if (data.height === '14px') {
1301
1299
  this.#icon.style.setProperty('margin-bottom', '-2px');
1302
1300
  } else if (data.height === '20px') {
@@ -176,6 +176,12 @@ const i18nLazyString = i18n.i18n.getLazilyComputedLocalizedString.bind(undefined
176
176
 
177
177
  const expiresSessionValue = i18nLazyString(UIStrings.session);
178
178
 
179
+ export interface CookiesTableData {
180
+ cookies: SDK.Cookie.Cookie[];
181
+ cookieToBlockedReasons?: ReadonlyMap<SDK.Cookie.Cookie, SDK.CookieModel.BlockedReason[]>;
182
+ cookieToExemptionReason?: ReadonlyMap<SDK.Cookie.Cookie, SDK.CookieModel.ExemptionReason>;
183
+ }
184
+
179
185
  export class CookiesTable extends UI.Widget.VBox {
180
186
  private saveCallback?: ((arg0: SDK.Cookie.Cookie, arg1: SDK.Cookie.Cookie|null) => Promise<boolean>);
181
187
  private readonly refreshCallback?: (() => void);
@@ -190,15 +196,15 @@ export class CookiesTable extends UI.Widget.VBox {
190
196
  private readonly view: ViewFunction;
191
197
  private selectedKey?: string;
192
198
  private readonly editable: boolean;
193
- private readonly renderInline: boolean;
199
+ private renderInline: boolean;
194
200
  private readonly schemeBindingEnabled: boolean;
195
201
  private readonly portBindingEnabled: boolean;
196
202
  constructor(
197
- renderInline?: boolean,
203
+ element?: HTMLElement, renderInline?: boolean,
198
204
  saveCallback?: ((arg0: SDK.Cookie.Cookie, arg1: SDK.Cookie.Cookie|null) => Promise<boolean>),
199
205
  refreshCallback?: (() => void), selectedCallback?: (() => void),
200
206
  deleteCallback?: ((arg0: SDK.Cookie.Cookie, arg1: () => void) => void), view?: ViewFunction) {
201
- super();
207
+ super(element);
202
208
  if (!view) {
203
209
  view = (input, _, target) => {
204
210
  // clang-format off
@@ -325,6 +331,15 @@ export class CookiesTable extends UI.Widget.VBox {
325
331
  this.requestUpdate();
326
332
  }
327
333
 
334
+ set cookiesData(data: CookiesTableData) {
335
+ this.setCookies(data.cookies, data.cookieToBlockedReasons, data.cookieToExemptionReason);
336
+ }
337
+
338
+ set inline(value: boolean) {
339
+ this.renderInline = value;
340
+ this.requestUpdate();
341
+ }
342
+
328
343
  setCookies(
329
344
  cookies: SDK.Cookie.Cookie[],
330
345
  cookieToBlockedReasons?: ReadonlyMap<SDK.Cookie.Cookie, SDK.CookieModel.BlockedReason[]>,
@@ -68,15 +68,15 @@ const UIStrings = {
68
68
  /**
69
69
  * @description A context menu item in the Data Grid of a data grid
70
70
  */
71
- sortByString: 'Sort By',
71
+ sortByString: 'Sort by',
72
72
  /**
73
73
  * @description A context menu item in data grids to reset the columns to their default weight
74
74
  */
75
- resetColumns: 'Reset Columns',
75
+ resetColumns: 'Reset columns',
76
76
  /**
77
77
  * @description A context menu item in data grids to list header options.
78
78
  */
79
- headerOptions: 'Header Options',
79
+ headerOptions: 'Header options',
80
80
  /**
81
81
  * @description Text to refresh the page
82
82
  */
@@ -1697,6 +1697,12 @@ export class Renderer implements UI.UIUtils.Renderer {
1697
1697
  if (options?.expand) {
1698
1698
  section.firstChild()?.expand();
1699
1699
  }
1700
+ const dispatchDimensionChange = (): void => {
1701
+ section.element.dispatchEvent(new CustomEvent('dimensionschanged'));
1702
+ };
1703
+ section.addEventListener(UI.TreeOutline.Events.ElementAttached, dispatchDimensionChange);
1704
+ section.addEventListener(UI.TreeOutline.Events.ElementExpanded, dispatchDimensionChange);
1705
+ section.addEventListener(UI.TreeOutline.Events.ElementCollapsed, dispatchDimensionChange);
1700
1706
  return {
1701
1707
  element: section.element,
1702
1708
  forceSelect: section.forceSelect.bind(section),
@@ -1187,6 +1187,39 @@ export class FlameChart extends Common.ObjectWrapper.eventMixin<EventTypes, type
1187
1187
  this.expandGroup(groupIndex, !this.rawTimelineData.groups[groupIndex].expanded /* setExpanded */);
1188
1188
  }
1189
1189
 
1190
+ bulkExpandGroups(indexes: number[]): void {
1191
+ if (indexes.length === 0) {
1192
+ return;
1193
+ }
1194
+ if (!this.rawTimelineData) {
1195
+ return;
1196
+ }
1197
+
1198
+ const groups = this.rawTimelineData.groups;
1199
+ if (!groups) {
1200
+ return;
1201
+ }
1202
+
1203
+ let didUpdate = false;
1204
+ for (const index of indexes) {
1205
+ if (!this.isGroupCollapsible(index) || groups[index].expanded) {
1206
+ continue;
1207
+ }
1208
+ didUpdate = true;
1209
+ groups[index].expanded = true;
1210
+ }
1211
+ if (didUpdate) {
1212
+ this.#updateAfterGroupExpansionChange();
1213
+ }
1214
+ }
1215
+
1216
+ #updateAfterGroupExpansionChange(): void {
1217
+ this.updateLevelPositions();
1218
+ this.updateHeight();
1219
+ this.draw();
1220
+ this.#notifyProviderOfConfigurationChange();
1221
+ }
1222
+
1190
1223
  private expandGroup(
1191
1224
  groupIndex: number, setExpanded: boolean|undefined = true, propagatedExpand: boolean|undefined = false): void {
1192
1225
  if (groupIndex < 0 || !this.isGroupCollapsible(groupIndex)) {
@@ -1203,12 +1236,10 @@ export class FlameChart extends Common.ObjectWrapper.eventMixin<EventTypes, type
1203
1236
  }
1204
1237
 
1205
1238
  const group = groups[groupIndex];
1206
- group.expanded = setExpanded;
1207
-
1208
- this.updateLevelPositions();
1209
1239
 
1210
- this.updateHighlight();
1211
- if (!group.expanded) {
1240
+ // If a group was expanded and is now being collapsed, and the selected
1241
+ // entry is within that group, then we have to deselect it.
1242
+ if (!setExpanded) {
1212
1243
  const timelineData = this.timelineData();
1213
1244
  if (timelineData) {
1214
1245
  const level = timelineData.entryLevels[this.selectedEntryIndex];
@@ -1220,10 +1251,13 @@ export class FlameChart extends Common.ObjectWrapper.eventMixin<EventTypes, type
1220
1251
  }
1221
1252
  }
1222
1253
  }
1223
-
1224
- this.updateHeight();
1225
- this.draw();
1226
- this.#notifyProviderOfConfigurationChange();
1254
+ if (group.expanded === setExpanded) {
1255
+ // If the state isn't changing, early exit so we don't waste cycles
1256
+ // redrawing.
1257
+ return;
1258
+ }
1259
+ group.expanded = setExpanded;
1260
+ this.#updateAfterGroupExpansionChange();
1227
1261
 
1228
1262
  this.scrollGroupIntoView(groupIndex);
1229
1263
  // We only want to read expanded/collapsed state on user inputted expand/collapse