chrome-devtools-frontend 1.0.1555174 → 1.0.1556696

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 (156) hide show
  1. package/front_end/core/protocol_client/InspectorBackend.ts +1 -1
  2. package/front_end/core/root/Runtime.ts +0 -4
  3. package/front_end/core/sdk/DOMModel.ts +101 -7
  4. package/front_end/entrypoints/formatter_worker/FormatterActions.ts +2 -0
  5. package/front_end/entrypoints/formatter_worker/ScopeParser.ts +75 -7
  6. package/front_end/entrypoints/formatter_worker/Substitute.ts +1 -1
  7. package/front_end/generated/InspectorBackendCommands.ts +1 -1
  8. package/front_end/generated/protocol.ts +0 -1
  9. package/front_end/models/ai_assistance/AiConversation.ts +71 -10
  10. package/front_end/models/ai_assistance/ArtifactsManager.ts +67 -0
  11. package/front_end/models/ai_assistance/ConversationHandler.ts +3 -2
  12. package/front_end/models/ai_assistance/agents/AiAgent.ts +17 -27
  13. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +152 -4
  14. package/front_end/models/ai_assistance/agents/StylingAgent.ts +2 -2
  15. package/front_end/models/ai_assistance/ai_assistance.ts +2 -0
  16. package/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.ts +1 -1
  17. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +0 -2
  18. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +1 -1
  19. package/front_end/{ui/components → models}/annotations/AnnotationRepository.ts +4 -4
  20. package/front_end/models/annotations/README.md +7 -0
  21. package/front_end/models/bindings/DebuggerWorkspaceBinding.ts +8 -0
  22. package/front_end/models/greendev/Prototypes.ts +56 -0
  23. package/front_end/models/greendev/README.md +5 -0
  24. package/front_end/models/greendev/greendev.ts +5 -0
  25. package/front_end/models/stack_trace/StackTrace.ts +13 -2
  26. package/front_end/models/stack_trace/StackTraceImpl.ts +81 -6
  27. package/front_end/models/stack_trace/StackTraceModel.ts +35 -3
  28. package/front_end/models/trace/extras/TraceTree.ts +4 -2
  29. package/front_end/models/trace/insights/LCPDiscovery.ts +0 -2
  30. package/front_end/models/trace/types/TraceEvents.ts +0 -1
  31. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +116 -70
  32. package/front_end/panels/ai_assistance/aiAssistancePanel.css +16 -0
  33. package/front_end/panels/ai_assistance/components/ArtifactsViewer.ts +159 -0
  34. package/front_end/panels/ai_assistance/components/ChatView.ts +3 -2
  35. package/front_end/panels/ai_assistance/components/CollapsibleAssistanceContentWidget.ts +7 -8
  36. package/front_end/panels/ai_assistance/components/PerformanceAgentFlameChart.ts +15 -8
  37. package/front_end/panels/ai_assistance/components/PerformanceAgentMarkdownRenderer.ts +9 -9
  38. package/front_end/panels/ai_assistance/components/artifactsViewer.css +15 -0
  39. package/front_end/panels/ai_assistance/components/collapsibleAssistanceContentWidget.css +5 -6
  40. package/front_end/panels/application/AppManifestView.ts +263 -205
  41. package/front_end/panels/application/ApplicationPanelSidebar.ts +24 -57
  42. package/front_end/panels/application/OpenedWindowDetailsView.ts +2 -0
  43. package/front_end/panels/application/ServiceWorkersView.ts +2 -0
  44. package/front_end/panels/application/StorageView.ts +1 -0
  45. package/front_end/panels/application/appManifestView.css +48 -0
  46. package/front_end/panels/application/components/ProtocolHandlersView.ts +2 -2
  47. package/front_end/panels/application/preloading/PreloadingView.ts +12 -6
  48. package/front_end/panels/application/preloading/components/PreloadingDetailsReportView.ts +230 -237
  49. package/front_end/panels/application/preloading/components/PreloadingGrid.ts +96 -79
  50. package/front_end/panels/application/preloading/components/preloadingGrid.css +26 -29
  51. package/front_end/panels/application/preloading/preloadingView.css +6 -0
  52. package/front_end/panels/common/Annotation.ts +1 -1
  53. package/front_end/panels/common/AnnotationManager.ts +1 -1
  54. package/front_end/panels/common/ExtensionView.ts +1 -0
  55. package/front_end/panels/console/ConsoleContextSelector.ts +74 -9
  56. package/front_end/panels/console/consoleContextSelector.css +31 -29
  57. package/front_end/panels/coverage/coverageListView.css +59 -57
  58. package/front_end/panels/elements/ElementsPanel.ts +1 -1
  59. package/front_end/panels/elements/ElementsTreeElement.ts +39 -1
  60. package/front_end/panels/elements/ElementsTreeOutline.ts +23 -21
  61. package/front_end/panels/elements/TopLayerContainer.ts +26 -91
  62. package/front_end/panels/explain/components/ConsoleInsight.ts +3 -3
  63. package/front_end/panels/linear_memory_inspector/components/LinearMemoryInspector.ts +4 -8
  64. package/front_end/panels/linear_memory_inspector/components/LinearMemoryValueInterpreter.ts +148 -97
  65. package/front_end/panels/linear_memory_inspector/components/LinearMemoryViewer.ts +1 -1
  66. package/front_end/panels/linear_memory_inspector/components/linearMemoryValueInterpreter.css +37 -35
  67. package/front_end/panels/network/NetworkItemView.ts +1 -1
  68. package/front_end/panels/network/NetworkLogView.ts +1 -1
  69. package/front_end/panels/network/NetworkPanel.ts +1 -1
  70. package/front_end/panels/recorder/RecorderController.ts +0 -1
  71. package/front_end/panels/security/SecurityPanelSidebar.ts +5 -0
  72. package/front_end/panels/settings/SettingsScreen.ts +133 -1
  73. package/front_end/panels/settings/settings-meta.ts +24 -0
  74. package/front_end/panels/settings/settingsScreen.css +4 -0
  75. package/front_end/panels/sources/UISourceCodeFrame.ts +3 -17
  76. package/front_end/panels/timeline/TimelineUIUtils.ts +5 -8
  77. package/front_end/panels/timeline/components/TimelineSummary.ts +75 -54
  78. package/front_end/panels/timeline/components/insights/BaseInsightComponent.ts +18 -26
  79. package/front_end/panels/timeline/components/insights/Cache.ts +12 -8
  80. package/front_end/panels/timeline/components/insights/DOMSize.ts +25 -21
  81. package/front_end/panels/timeline/components/insights/DuplicatedJavaScript.ts +7 -7
  82. package/front_end/panels/timeline/components/insights/FontDisplay.ts +7 -5
  83. package/front_end/panels/timeline/components/insights/ForcedReflow.ts +11 -9
  84. package/front_end/panels/timeline/components/insights/INPBreakdown.ts +7 -6
  85. package/front_end/panels/timeline/components/insights/ImageDelivery.ts +7 -5
  86. package/front_end/panels/timeline/components/insights/InsightRenderer.ts +20 -18
  87. package/front_end/panels/timeline/components/insights/LCPBreakdown.ts +12 -12
  88. package/front_end/panels/timeline/components/insights/LegacyJavaScript.ts +7 -7
  89. package/front_end/panels/timeline/components/insights/ModernHTTP.ts +7 -5
  90. package/front_end/panels/timeline/components/insights/NetworkDependencyTree.ts +15 -13
  91. package/front_end/panels/timeline/components/insights/RenderBlocking.ts +2 -2
  92. package/front_end/panels/timeline/components/insights/SlowCSSSelector.ts +15 -14
  93. package/front_end/panels/timeline/components/insights/Table.ts +152 -130
  94. package/front_end/panels/timeline/components/insights/ThirdParties.ts +11 -9
  95. package/front_end/panels/timeline/components/timelineSummary.css +58 -57
  96. package/front_end/panels/timeline/thirdPartyTreeView.css +109 -0
  97. package/front_end/panels/timeline/timelineDetailsView.css +2 -4
  98. package/front_end/panels/timeline/timelinePanel.css +0 -110
  99. package/front_end/third_party/acorn/estree-legacy.d.ts +2 -0
  100. package/front_end/third_party/chromium/README.chromium +1 -1
  101. package/front_end/third_party/puppeteer/README.chromium +2 -2
  102. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/CDPSession.d.ts.map +1 -1
  103. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/CDPSession.js.map +1 -1
  104. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/ElementHandle.d.ts.map +1 -1
  105. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/ElementHandle.js.map +1 -1
  106. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Frame.d.ts.map +1 -1
  107. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Frame.js.map +1 -1
  108. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.d.ts.map +1 -1
  109. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.js.map +1 -1
  110. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Connection.d.ts.map +1 -1
  111. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/common/BrowserConnector.js +21 -7
  112. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/common/BrowserConnector.js.map +1 -1
  113. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/common/EventEmitter.d.ts.map +1 -1
  114. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
  115. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
  116. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.d.ts +1 -1
  117. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.js +1 -1
  118. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +15 -6
  119. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/CDPSession.d.ts.map +1 -1
  120. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/CDPSession.js.map +1 -1
  121. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/ElementHandle.d.ts.map +1 -1
  122. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/ElementHandle.js.map +1 -1
  123. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Frame.d.ts.map +1 -1
  124. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Frame.js.map +1 -1
  125. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.d.ts.map +1 -1
  126. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.js.map +1 -1
  127. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/Connection.d.ts.map +1 -1
  128. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/common/BrowserConnector.js +21 -7
  129. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/common/BrowserConnector.js.map +1 -1
  130. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/common/EventEmitter.d.ts.map +1 -1
  131. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.d.ts +1 -1
  132. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.js +1 -1
  133. package/front_end/third_party/puppeteer/package/package.json +2 -2
  134. package/front_end/third_party/puppeteer/package/src/api/CDPSession.ts +1 -2
  135. package/front_end/third_party/puppeteer/package/src/api/ElementHandle.ts +2 -4
  136. package/front_end/third_party/puppeteer/package/src/api/Frame.ts +2 -4
  137. package/front_end/third_party/puppeteer/package/src/api/Page.ts +2 -4
  138. package/front_end/third_party/puppeteer/package/src/bidi/core/Connection.ts +3 -2
  139. package/front_end/third_party/puppeteer/package/src/common/BrowserConnector.ts +29 -10
  140. package/front_end/third_party/puppeteer/package/src/common/EventEmitter.ts +3 -3
  141. package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
  142. package/front_end/ui/components/report_view/ReportView.docs.ts +37 -0
  143. package/front_end/ui/components/report_view/ReportView.ts +1 -4
  144. package/front_end/ui/components/settings/SettingCheckbox.ts +1 -1
  145. package/front_end/ui/legacy/Floaty.ts +5 -9
  146. package/front_end/ui/legacy/InspectorView.ts +2 -1
  147. package/front_end/ui/legacy/ReportView.ts +5 -4
  148. package/front_end/ui/legacy/TabbedPane.ts +1 -1
  149. package/front_end/ui/legacy/ViewManager.ts +2 -32
  150. package/front_end/ui/legacy/Widget.ts +7 -0
  151. package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +0 -1
  152. package/front_end/ui/legacy/reportView.css +0 -24
  153. package/front_end/ui/visual_logging/KnownContextValues.ts +7 -0
  154. package/package.json +1 -1
  155. /package/front_end/{ui/components → models}/annotations/AnnotationType.ts +0 -0
  156. /package/front_end/{ui/components → models}/annotations/annotations.ts +0 -0
@@ -7,7 +7,14 @@ import type * as Protocol from '../../generated/protocol.js';
7
7
 
8
8
  // eslint-disable-next-line @devtools/es-modules-import
9
9
  import * as StackTrace from './stack_trace.js';
10
- import {AsyncFragmentImpl, FragmentImpl, FrameImpl, StackTraceImpl} from './StackTraceImpl.js';
10
+ import {
11
+ type AnyStackTraceImpl,
12
+ AsyncFragmentImpl,
13
+ DebuggableFragmentImpl,
14
+ FragmentImpl,
15
+ FrameImpl,
16
+ StackTraceImpl
17
+ } from './StackTraceImpl.js';
11
18
  import {type FrameNode, type RawFrame, Trie} from './Trie.js';
12
19
 
13
20
  /**
@@ -45,10 +52,21 @@ export class StackTraceModel extends SDK.SDKModel.SDKModel<unknown> {
45
52
  return new StackTraceImpl(syncFragment, asyncFragments);
46
53
  }
47
54
 
55
+ async createFromDebuggerPaused(
56
+ pausedDetails: SDK.DebuggerModel.DebuggerPausedDetails,
57
+ rawFramesToUIFrames: TranslateRawFrames): Promise<StackTrace.StackTrace.DebuggableStackTrace> {
58
+ const [syncFragment, asyncFragments] = await Promise.all([
59
+ this.#createDebuggableFragment(pausedDetails, rawFramesToUIFrames),
60
+ this.#createAsyncFragments(pausedDetails, rawFramesToUIFrames),
61
+ ]);
62
+
63
+ return new StackTraceImpl(syncFragment, asyncFragments);
64
+ }
65
+
48
66
  /** Trigger re-translation of all fragments with the provide script in their call stack */
49
67
  async scriptInfoChanged(script: SDK.Script.Script, translateRawFrames: TranslateRawFrames): Promise<void> {
50
68
  const translatePromises: Array<Promise<unknown>> = [];
51
- let stackTracesToUpdate = new Set<StackTraceImpl>();
69
+ let stackTracesToUpdate = new Set<AnyStackTraceImpl>();
52
70
 
53
71
  for (const fragment of this.#affectedFragments(script)) {
54
72
  // We trigger re-translation only for fragments of leaf-nodes. Any fragment along the ancestor-chain
@@ -75,8 +93,22 @@ export class StackTraceModel extends SDK.SDKModel.SDKModel<unknown> {
75
93
  return fragment;
76
94
  }
77
95
 
96
+ async #createDebuggableFragment(
97
+ pausedDetails: SDK.DebuggerModel.DebuggerPausedDetails,
98
+ rawFramesToUIFrames: TranslateRawFrames): Promise<DebuggableFragmentImpl> {
99
+ const fragment = this.#createFragment(pausedDetails.callFrames.map(frame => ({
100
+ scriptId: frame.script.scriptId,
101
+ url: frame.script.sourceURL,
102
+ functionName: frame.functionName,
103
+ lineNumber: frame.location().lineNumber,
104
+ columnNumber: frame.location().columnNumber,
105
+ })));
106
+ await this.#translateFragment(fragment, rawFramesToUIFrames);
107
+ return new DebuggableFragmentImpl(fragment, pausedDetails.callFrames);
108
+ }
109
+
78
110
  async #createAsyncFragments(
79
- stackTraceOrPausedEvent: Protocol.Runtime.StackTrace|Protocol.Debugger.PausedEvent,
111
+ stackTraceOrPausedEvent: Protocol.Runtime.StackTrace|SDK.DebuggerModel.DebuggerPausedDetails,
80
112
  rawFramesToUIFrames: TranslateRawFrames): Promise<AsyncFragmentImpl[]> {
81
113
  const asyncFragments: AsyncFragmentImpl[] = [];
82
114
  const translatePromises: Array<Promise<unknown>> = [];
@@ -493,8 +493,10 @@ export class BottomUpRootNode extends Node {
493
493
  node.totalTime += totalTimeById.get(id) || 0;
494
494
  totalTimeById.delete(id);
495
495
  }
496
- // TODO: this may be wrong. See the skipped test in TraceTree.test.ts.
497
- if (firstNodeStack.length) {
496
+
497
+ // An item on this stack means that this current node has a caller. Therefore,
498
+ // in a bottom-up view it has children.
499
+ if (idStack.length > 0) {
498
500
  node.setHasChildren(true);
499
501
  }
500
502
  }
@@ -129,8 +129,6 @@ export function generateInsight(
129
129
  }
130
130
 
131
131
  const initiatorUrl = lcpRequest.args.data.initiator?.url;
132
- // TODO(b/372319476): Explore using trace event HTMLDocumentParser::FetchQueuedPreloads to determine if the request
133
- // is discovered by the preload scanner.
134
132
  const initiatedByMainDoc =
135
133
  lcpRequest?.args.data.initiator?.type === 'parser' && docRequest.args.data.url === initiatorUrl;
136
134
  const imgPreloadedOrFoundInHTML = lcpRequest?.args.data.isLinkPreload || initiatedByMainDoc;
@@ -1078,7 +1078,6 @@ export interface ResourceSendRequest extends Instant {
1078
1078
  resourceType?: Protocol.Network.ResourceType,
1079
1079
  /** Added Feb 2024. https://crrev.com/c/5297615 */
1080
1080
  fetchPriorityHint?: FetchPriorityHint,
1081
- // TODO(crbug.com/1457985): change requestMethod to enum when confirm in the backend code.
1082
1081
  requestMethod?: string,
1083
1082
  renderBlocking?: RenderBlocking,
1084
1083
  initiator?: Initiator,
@@ -12,10 +12,11 @@ import * as Root from '../../core/root/root.js';
12
12
  import * as SDK from '../../core/sdk/sdk.js';
13
13
  import * as Protocol from '../../generated/protocol.js';
14
14
  import * as AiAssistanceModel from '../../models/ai_assistance/ai_assistance.js';
15
+ import * as Annotations from '../../models/annotations/annotations.js';
15
16
  import * as Badges from '../../models/badges/badges.js';
17
+ import * as GreenDev from '../../models/greendev/greendev.js';
16
18
  import * as TextUtils from '../../models/text_utils/text_utils.js';
17
19
  import * as Workspace from '../../models/workspace/workspace.js';
18
- import * as Annotations from '../../ui/components/annotations/annotations.js';
19
20
  import * as Buttons from '../../ui/components/buttons/buttons.js';
20
21
  import * as Snackbars from '../../ui/components/snackbars/snackbars.js';
21
22
  import * as UIHelpers from '../../ui/helpers/helpers.js';
@@ -27,6 +28,7 @@ import * as NetworkPanel from '../network/network.js';
27
28
  import * as TimelinePanel from '../timeline/timeline.js';
28
29
 
29
30
  import aiAssistancePanelStyles from './aiAssistancePanel.css.js';
31
+ import {ArtifactsViewer} from './components/ArtifactsViewer.js';
30
32
  import {
31
33
  type AnswerPart,
32
34
  type ChatMessage,
@@ -224,10 +226,10 @@ function selectedElementFilter(maybeNode: SDK.DOMModel.DOMNode|null): SDK.DOMMod
224
226
  return null;
225
227
  }
226
228
 
227
- async function getEmptyStateSuggestions(
228
- context: AiAssistanceModel.AiAgent.ConversationContext<unknown>|null,
229
- conversation?: AiAssistanceModel.AiConversation.AiConversation):
229
+ async function getEmptyStateSuggestions(conversation?: AiAssistanceModel.AiConversation.AiConversation):
230
230
  Promise<AiAssistanceModel.AiAgent.ConversationSuggestion[]> {
231
+ const context = conversation?.selectedContext;
232
+
231
233
  if (context) {
232
234
  const specialSuggestions = await context.getSuggestions();
233
235
 
@@ -270,9 +272,10 @@ async function getEmptyStateSuggestions(
270
272
  }
271
273
  }
272
274
 
273
- function getMarkdownRenderer(
274
- context: AiAssistanceModel.AiAgent.ConversationContext<unknown>|null,
275
- conversation?: AiAssistanceModel.AiConversation.AiConversation): MarkdownRendererWithCodeBlock {
275
+ function getMarkdownRenderer(conversation?: AiAssistanceModel.AiConversation.AiConversation):
276
+ MarkdownRendererWithCodeBlock {
277
+ const context = conversation?.selectedContext;
278
+
276
279
  if (context instanceof AiAssistanceModel.PerformanceAgent.PerformanceTraceContext) {
277
280
  if (!context.external) {
278
281
  const focus = context.getItem();
@@ -294,6 +297,8 @@ interface ToolbarViewInput {
294
297
  onExportConversationClick: () => void;
295
298
  onHelpClick: () => void;
296
299
  onSettingsClick: () => void;
300
+ onArtifactsSidebarToggle: () => void;
301
+ artifactsSidebarVisible: boolean;
297
302
  isLoading: boolean;
298
303
  showChatActions: boolean;
299
304
  showActiveConversationActions: boolean;
@@ -386,6 +391,13 @@ function toolbarView(input: ToolbarViewInput): Lit.LitTemplate {
386
391
  .jslogContext=${'freestyler.settings'}
387
392
  .variant=${Buttons.Button.Variant.TOOLBAR}
388
393
  @click=${input.onSettingsClick}></devtools-button>
394
+ <!-- If the green experiment is enabled, render the artifacts sidebar toggle button -->
395
+ ${GreenDev.Prototypes.instance().isEnabled('artifactViewer') ? html`<devtools-button
396
+ title=${i18nString(UIStrings.settings)}
397
+ aria-label=${i18nString(UIStrings.settings)}
398
+ .iconName=${input.artifactsSidebarVisible ? 'left-panel-open' : 'left-panel-close'}
399
+ .variant=${Buttons.Button.Variant.TOOLBAR}
400
+ @click=${input.onArtifactsSidebarToggle}></devtools-button>` : Lit.nothing}
389
401
  </devtools-toolbar>
390
402
  </div>
391
403
  `;
@@ -396,8 +408,9 @@ function defaultView(input: ViewInput, output: PanelViewOutput, target: HTMLElem
396
408
  // clang-format off
397
409
  function renderState(): Lit.TemplateResult {
398
410
  switch (input.state) {
399
- case ViewState.CHAT_VIEW:
400
- return html`<devtools-ai-chat-view
411
+ case ViewState.CHAT_VIEW: {
412
+ return html`
413
+ <devtools-ai-chat-view
401
414
  .props=${input.props}
402
415
  ${Lit.Directives.ref((el: Element | undefined) => {
403
416
  if (!el || !(el instanceof ChatView)) {
@@ -407,6 +420,7 @@ function defaultView(input: ViewInput, output: PanelViewOutput, target: HTMLElem
407
420
  output.chatView = el;
408
421
  })}
409
422
  ></devtools-ai-chat-view>`;
423
+ }
410
424
  case ViewState.EXPLORE_VIEW:
411
425
  return html`<devtools-widget
412
426
  class="fill-panel"
@@ -421,13 +435,40 @@ function defaultView(input: ViewInput, output: PanelViewOutput, target: HTMLElem
421
435
  }
422
436
  }
423
437
 
424
- Lit.render(
425
- html`
426
- ${toolbarView(input)}
427
- <div class="ai-assistance-view-container">${renderState()}</div>
428
- `,
429
- target
430
- );
438
+ const panelWithToolbar = html`
439
+ ${toolbarView(input)}
440
+ <div class="ai-assistance-view-container">${renderState()}</div>`;
441
+
442
+ // If the green experiment is enabled, render the chat view inside
443
+ // a split view to also have an artifacts viewer sidebar.
444
+ if(GreenDev.Prototypes.instance().isEnabled('artifactViewer')) {
445
+ Lit.render(
446
+ html`
447
+ <devtools-split-view
448
+ direction="column"
449
+ sidebar-visibility=${input.artifactsSidebarVisible ? 'visible' : 'hidden'}
450
+ sidebar-position="second"
451
+ sidebar-initial-size="520"
452
+ style="width: 100%;">
453
+ <div slot="main" class="assistance-view-wrapper-with-sidebar">
454
+ ${panelWithToolbar}
455
+ </div>
456
+ <div slot="sidebar">
457
+ <div class="artifacts-toolbar-container" role="toolbar">
458
+ <div>Artifacts Viewer</div>
459
+ </div>
460
+ <devtools-widget
461
+ class="fill-panel"
462
+ .widgetConfig=${UI.Widget.widgetConfig(ArtifactsViewer)}
463
+ ></devtools-widget>
464
+ </div>
465
+ </devtools-split-view>
466
+ `,
467
+ target
468
+ );
469
+ } else {
470
+ Lit.render(panelWithToolbar, target);
471
+ }
431
472
  // clang-format on
432
473
  }
433
474
 
@@ -482,18 +523,12 @@ export class AiAssistancePanel extends UI.Panel.Panel {
482
523
  #selectedElement: AiAssistanceModel.StylingAgent.NodeContext|null = null;
483
524
  #selectedPerformanceTrace: AiAssistanceModel.PerformanceAgent.PerformanceTraceContext|null = null;
484
525
  #selectedRequest: AiAssistanceModel.NetworkAgent.RequestContext|null = null;
485
-
526
+ #isArtifactsSidebarOpen = false;
486
527
  // Messages displayed in the `ChatView` component.
487
528
  #messages: ChatMessage[] = [];
488
529
 
489
530
  // Whether the UI should show loading or not.
490
531
  #isLoading = false;
491
- // Selected conversation context. The reason we keep this as a
492
- // state field rather than using `#getConversationContext` is that,
493
- // there is a case where the context differs from the selectedElement (or other selected context type).
494
- // Specifically, it allows restoring the previous context when a new selection is cross-origin.
495
- // See `#onContextSelectionChanged` for details.
496
- #selectedContext: AiAssistanceModel.AiAgent.ConversationContext<unknown>|null = null;
497
532
  // Stores the availability status of the `AidaClient` and the reason for unavailability, if any.
498
533
  #aidaAvailability: Host.AidaClient.AidaAccessPreconditions;
499
534
  // Info of the currently logged in user.
@@ -530,6 +565,10 @@ export class AiAssistancePanel extends UI.Panel.Panel {
530
565
  }
531
566
  AiAssistanceModel.AiHistoryStorage.AiHistoryStorage.instance().addEventListener(
532
567
  AiAssistanceModel.AiHistoryStorage.Events.HISTORY_DELETED, this.#onHistoryDeleted, this);
568
+ if (GreenDev.Prototypes.instance().isEnabled('artifactViewer')) {
569
+ AiAssistanceModel.ArtifactsManager.ArtifactsManager.instance().addEventListener(
570
+ AiAssistanceModel.ArtifactsManager.ArtifactAddedEvent.eventName, this.#onArtifactAdded.bind(this));
571
+ }
533
572
  }
534
573
 
535
574
  async #getPanelViewInput(): Promise<PanelViewInput> {
@@ -546,16 +585,16 @@ export class AiAssistancePanel extends UI.Panel.Panel {
546
585
  }
547
586
 
548
587
  if (this.#conversation) {
549
- const emptyStateSuggestions = await getEmptyStateSuggestions(this.#selectedContext, this.#conversation);
550
- const markdownRenderer = getMarkdownRenderer(this.#selectedContext, this.#conversation);
588
+ const emptyStateSuggestions = await getEmptyStateSuggestions(this.#conversation);
589
+ const markdownRenderer = getMarkdownRenderer(this.#conversation);
551
590
  return {
552
591
  state: ViewState.CHAT_VIEW,
553
592
  props: {
554
593
  additionalFloatyContext: this.#additionalContextItemsFromFloaty,
555
- blockedByCrossOrigin: this.#blockedByCrossOrigin,
594
+ blockedByCrossOrigin: this.#conversation.isBlockedByOrigin,
556
595
  isLoading: this.#isLoading,
557
596
  messages: this.#messages,
558
- selectedContext: this.#selectedContext,
597
+ selectedContext: this.#conversation.selectedContext ?? null,
559
598
  conversationType: this.#conversation.type,
560
599
  isReadOnly: this.#conversation.isReadOnly ?? false,
561
600
  changeSummary: this.#getChangeSummary(),
@@ -574,6 +613,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
574
613
  uploadImageInputEnabled: isAiAssistanceMultimodalUploadInputEnabled() &&
575
614
  this.#conversation.type === AiAssistanceModel.AiHistoryStorage.ConversationType.STYLING,
576
615
  markdownRenderer,
616
+ isArtifactsSidebarOpen: this.#isArtifactsSidebarOpen,
577
617
  onTextSubmit: async (
578
618
  text: string, imageInput?: Host.AidaClient.Part,
579
619
  multimodalInputType?: AiAssistanceModel.AiAgent.MultimodalInputType) => {
@@ -726,6 +766,8 @@ export class AiAssistancePanel extends UI.Panel.Panel {
726
766
  this.#conversation = conversation;
727
767
  }
728
768
 
769
+ this.#conversation?.setContext(this.#getConversationContext(this.#conversation));
770
+
729
771
  this.requestUpdate();
730
772
  }
731
773
 
@@ -778,12 +820,19 @@ export class AiAssistancePanel extends UI.Panel.Panel {
778
820
 
779
821
  Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiAssistancePanelOpened);
780
822
 
781
- if (Root.Runtime.hostConfig.devToolsGreenDevUi?.enabled) {
823
+ if (GreenDev.Prototypes.instance().isEnabled('inDevToolsFloaty')) {
782
824
  UI.Context.Context.instance().addFlavorChangeListener(UI.Floaty.FloatyFlavor, this.#bindFloatyListener, this);
783
825
  this.#bindFloatyListener();
784
826
  }
785
827
  }
786
828
 
829
+ #onArtifactAdded(): void {
830
+ if (AiAssistanceModel.ArtifactsManager.ArtifactsManager.instance().artifacts.length > 0) {
831
+ this.#isArtifactsSidebarOpen = true;
832
+ this.requestUpdate();
833
+ }
834
+ }
835
+
787
836
  override willHide(): void {
788
837
  super.willHide();
789
838
  this.#aiAssistanceEnabledSetting?.removeChangeListener(this.requestUpdate, this);
@@ -927,6 +976,11 @@ export class AiAssistancePanel extends UI.Panel.Panel {
927
976
  onSettingsClick: () => {
928
977
  void UI.ViewManager.ViewManager.instance().showView('chrome-ai');
929
978
  },
979
+ onArtifactsSidebarToggle: () => {
980
+ this.#isArtifactsSidebarOpen = !this.#isArtifactsSidebarOpen;
981
+ this.requestUpdate();
982
+ },
983
+ artifactsSidebarVisible: this.#isArtifactsSidebarOpen,
930
984
  };
931
985
  }
932
986
 
@@ -958,13 +1012,15 @@ export class AiAssistancePanel extends UI.Panel.Panel {
958
1012
  #isTextInputDisabled(): boolean {
959
1013
  // If sending a new message is blocked by cross origin context
960
1014
  // the text input is disabled.
961
- if (this.#blockedByCrossOrigin) {
1015
+ if (this.#conversation && this.#conversation.isBlockedByOrigin) {
962
1016
  return true;
963
1017
  }
964
1018
 
965
- // If there is no current agent if there is no selected context
966
- // the text input is disabled.
967
- if (!this.#conversation || !this.#selectedContext) {
1019
+ if (!this.#conversation) {
1020
+ return true;
1021
+ }
1022
+
1023
+ if (!this.#conversation.selectedContext) {
968
1024
  return true;
969
1025
  }
970
1026
 
@@ -989,24 +1045,27 @@ export class AiAssistancePanel extends UI.Panel.Panel {
989
1045
  return i18nString(UIStrings.followTheSteps);
990
1046
  }
991
1047
 
992
- if (this.#blockedByCrossOrigin) {
1048
+ if (this.#conversation && this.#conversation.isBlockedByOrigin) {
993
1049
  return lockedString(UIStringsNotTranslate.crossOriginError);
994
1050
  }
995
1051
 
996
1052
  switch (this.#conversation.type) {
997
1053
  case AiAssistanceModel.AiHistoryStorage.ConversationType.STYLING:
998
- return this.#selectedContext ? lockedString(UIStringsNotTranslate.inputPlaceholderForStyling) :
999
- lockedString(UIStringsNotTranslate.inputPlaceholderForStylingNoContext);
1054
+ return this.#conversation.selectedContext ?
1055
+ lockedString(UIStringsNotTranslate.inputPlaceholderForStyling) :
1056
+ lockedString(UIStringsNotTranslate.inputPlaceholderForStylingNoContext);
1000
1057
  case AiAssistanceModel.AiHistoryStorage.ConversationType.FILE:
1001
- return this.#selectedContext ? lockedString(UIStringsNotTranslate.inputPlaceholderForFile) :
1002
- lockedString(UIStringsNotTranslate.inputPlaceholderForFileNoContext);
1058
+ return this.#conversation.selectedContext ?
1059
+ lockedString(UIStringsNotTranslate.inputPlaceholderForFile) :
1060
+ lockedString(UIStringsNotTranslate.inputPlaceholderForFileNoContext);
1003
1061
  case AiAssistanceModel.AiHistoryStorage.ConversationType.NETWORK:
1004
- return this.#selectedContext ? lockedString(UIStringsNotTranslate.inputPlaceholderForNetwork) :
1005
- lockedString(UIStringsNotTranslate.inputPlaceholderForNetworkNoContext);
1062
+ return this.#conversation.selectedContext ?
1063
+ lockedString(UIStringsNotTranslate.inputPlaceholderForNetwork) :
1064
+ lockedString(UIStringsNotTranslate.inputPlaceholderForNetworkNoContext);
1006
1065
  case AiAssistanceModel.AiHistoryStorage.ConversationType.PERFORMANCE: {
1007
1066
  const perfPanel = UI.Context.Context.instance().flavor(TimelinePanel.TimelinePanel.TimelinePanel);
1008
1067
  if (perfPanel?.hasActiveTrace()) {
1009
- return this.#selectedContext ?
1068
+ return this.#conversation.selectedContext ?
1010
1069
  lockedString(UIStringsNotTranslate.inputPlaceholderForPerformanceTrace) :
1011
1070
  lockedString(UIStringsNotTranslate.inputPlaceholderForPerformanceTraceNoContext);
1012
1071
  }
@@ -1066,7 +1125,11 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1066
1125
  }
1067
1126
 
1068
1127
  #handleContextClick(): void|Promise<void> {
1069
- const context = this.#selectedContext;
1128
+ if (!this.#conversation) {
1129
+ return;
1130
+ }
1131
+
1132
+ const context = this.#conversation.selectedContext;
1070
1133
  if (context instanceof AiAssistanceModel.NetworkAgent.RequestContext) {
1071
1134
  const requestLocation = NetworkForward.UIRequestLocation.UIRequestLocation.tab(
1072
1135
  context.getItem(), NetworkForward.UIRequestLocation.UIRequestTabs.HEADERS_COMPONENT);
@@ -1164,7 +1227,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1164
1227
  this.#imageInput = undefined;
1165
1228
  this.#isTextInputEmpty = true;
1166
1229
  Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiAssistanceQuerySubmitted);
1167
- if (this.#blockedByCrossOrigin) {
1230
+ if (this.#conversation && this.#conversation.isBlockedByOrigin) {
1168
1231
  this.#handleNewChatRequest();
1169
1232
  }
1170
1233
  await this.#startConversation(predefinedPrompt);
@@ -1358,41 +1421,21 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1358
1421
  this.#runAbortController = new AbortController();
1359
1422
  }
1360
1423
 
1361
- // Indicates whether the new conversation context is blocked due to cross-origin restrictions.
1362
- // This happens when the conversation's context has a different
1363
- // origin than the selected context.
1364
- get #blockedByCrossOrigin(): boolean {
1365
- if (!this.#conversation) {
1366
- return false;
1367
- }
1368
- this.#selectedContext = this.#getConversationContext(this.#conversation);
1369
- if (!this.#selectedContext) {
1370
- return false;
1371
- }
1372
- return !this.#selectedContext.isOriginAllowed(this.#conversation.origin);
1373
- }
1374
-
1375
1424
  #getConversationContext(conversation?: AiAssistanceModel.AiConversation.AiConversation):
1376
1425
  AiAssistanceModel.AiAgent.ConversationContext<unknown>|null {
1377
1426
  if (!conversation) {
1378
1427
  return null;
1379
1428
  }
1380
- let context: AiAssistanceModel.AiAgent.ConversationContext<unknown>|null;
1381
1429
  switch (conversation.type) {
1382
1430
  case AiAssistanceModel.AiHistoryStorage.ConversationType.STYLING:
1383
- context = this.#selectedElement;
1384
- break;
1431
+ return this.#selectedElement;
1385
1432
  case AiAssistanceModel.AiHistoryStorage.ConversationType.FILE:
1386
- context = this.#selectedFile;
1387
- break;
1433
+ return this.#selectedFile;
1388
1434
  case AiAssistanceModel.AiHistoryStorage.ConversationType.NETWORK:
1389
- context = this.#selectedRequest;
1390
- break;
1435
+ return this.#selectedRequest;
1391
1436
  case AiAssistanceModel.AiHistoryStorage.ConversationType.PERFORMANCE:
1392
- context = this.#selectedPerformanceTrace;
1393
- break;
1437
+ return this.#selectedPerformanceTrace;
1394
1438
  }
1395
- return context;
1396
1439
  }
1397
1440
 
1398
1441
  async #startConversation(
@@ -1407,8 +1450,10 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1407
1450
  this.#cancel();
1408
1451
  const signal = this.#runAbortController.signal;
1409
1452
  const context = this.#getConversationContext(this.#conversation);
1453
+ this.#conversation.setContext(context);
1454
+
1410
1455
  // If a different context is provided, it must be from the same origin.
1411
- if (context && !context.isOriginAllowed(this.#conversation.origin)) {
1456
+ if (this.#conversation.isBlockedByOrigin) {
1412
1457
  // This error should not be reached. If it happens, some
1413
1458
  // invariants do not hold anymore.
1414
1459
  throw new Error('cross-origin context data should not be included');
@@ -1427,12 +1472,13 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1427
1472
 
1428
1473
  await this.#doConversation(
1429
1474
  this.#conversation.run(
1430
- text, {
1475
+ text,
1476
+ {
1431
1477
  signal,
1432
- selected: context,
1433
1478
  extraContext: this.#additionalContextItemsFromFloaty,
1479
+ multimodalInput,
1434
1480
  },
1435
- multimodalInput),
1481
+ ),
1436
1482
  );
1437
1483
  }
1438
1484
 
@@ -13,6 +13,22 @@
13
13
  justify-content: space-between;
14
14
  }
15
15
 
16
+ .artifacts-toolbar-container {
17
+ display: flex;
18
+ align-items: center;
19
+ justify-content: flex-start;
20
+ height: 27px;
21
+ padding: 5px;
22
+ border-bottom: 1px solid var(--sys-color-divider);
23
+ }
24
+
25
+ .assistance-view-wrapper-with-sidebar {
26
+ display: flex;
27
+ flex-direction: column;
28
+ height: 100%;
29
+ overflow: hidden;
30
+ }
31
+
16
32
  .ai-assistance-view-container {
17
33
  display: flex;
18
34
  flex-direction: column;
@@ -0,0 +1,159 @@
1
+ // Copyright 2025 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+
5
+ import './CollapsibleAssistanceContentWidget.js';
6
+ import './PerformanceAgentFlameChart.js';
7
+
8
+ import * as AiAssistanceModel from '../../../models/ai_assistance/ai_assistance.js';
9
+ import * as Logs from '../../../models/logs/logs.js';
10
+ import * as NetworkTimeCalculator from '../../../models/network_time_calculator/network_time_calculator.js';
11
+ import * as Trace from '../../../models/trace/trace.js';
12
+ import * as UI from '../../../ui/legacy/legacy.js';
13
+ import * as Lit from '../../../ui/lit/lit.js';
14
+ import * as Network from '../../network/network.js';
15
+ import * as Insights from '../../timeline/components/insights/insights.js';
16
+
17
+ import artifactsViewerStyles from './artifactsViewer.css.js';
18
+ import type * as PerformanceAgentFlameChart from './PerformanceAgentFlameChart.js';
19
+
20
+ const {html, render} = Lit;
21
+
22
+ export interface ViewInput {
23
+ artifacts: AiAssistanceModel.ArtifactsManager.Artifact[];
24
+ parsedTrace: Trace.TraceModel.ParsedTrace;
25
+ }
26
+
27
+ export function renderArtifact(
28
+ artifact: AiAssistanceModel.ArtifactsManager.Artifact, parsedTrace: Trace.TraceModel.ParsedTrace): Lit.LitTemplate {
29
+ switch (artifact.type) {
30
+ // clang-format off
31
+ case 'insight': {
32
+ const insightRenderer = new Insights.InsightRenderer.InsightRenderer();
33
+ const componentName = artifact.insightType;
34
+ const insightSet = parsedTrace.insights?.values().next().value;
35
+ const insightModel = insightSet?.model[componentName as Trace.Insights.Types.InsightKeys];
36
+
37
+ if (!insightModel) {
38
+ return Lit.nothing;
39
+ }
40
+
41
+ return html`
42
+ <devtools-collapsible-assistance-content-widget .data=${{headerText: `Insight - ${componentName}`}}>
43
+ ${insightRenderer.renderInsightToWidgetElement(parsedTrace, insightSet, insightModel, componentName, {
44
+ selected: true,
45
+ isAIAssistanceContext: true,
46
+ })}
47
+ </devtools-collapsible-assistance-content-widget>`;
48
+ }
49
+ case 'network-request': {
50
+ const networkRequest = artifact.request;
51
+ if ('args' in networkRequest && Trace.Types.Events.isSyntheticNetworkRequest(networkRequest)) {
52
+ const calculator = new NetworkTimeCalculator.NetworkTimeCalculator(true);
53
+ const sdkRequest = Logs.NetworkLog.NetworkLog.instance()
54
+ .requestsForId(networkRequest.args.data.requestId)
55
+ .find(r => r.url() === networkRequest.args.data.url) ??
56
+ null;
57
+ if (!sdkRequest) {
58
+ return Lit.nothing;
59
+ }
60
+ return html`
61
+ <devtools-collapsible-assistance-content-widget
62
+ .data=${{headerText: `Network Request: ${
63
+ sdkRequest.url().length > 80 ? sdkRequest.url().slice(0, 80) + '...' : sdkRequest.url()}`}}>
64
+ <devtools-widget class="actions" .widgetConfig=${UI.Widget.widgetConfig(Network.RequestTimingView.RequestTimingView, {
65
+ request: sdkRequest,
66
+ calculator,
67
+ })}></devtools-widget>
68
+ </devtools-collapsible-assistance-content-widget>`;
69
+ }
70
+ return Lit.nothing;
71
+ }
72
+ case 'flamechart': {
73
+ return html`
74
+ <devtools-collapsible-assistance-content-widget .data=${{headerText: `Flamechart`}}>
75
+ <devtools-performance-agent-flame-chart .data=${{
76
+ parsedTrace,
77
+ start: artifact.start,
78
+ end: artifact.end,
79
+ } as PerformanceAgentFlameChart.PerformanceAgentFlameChartData}>
80
+ </devtools-performance-agent-flame-chart>
81
+ </devtools-collapsible-assistance-content-widget>`;
82
+ }
83
+ default:
84
+ return Lit.nothing;
85
+ // clang-format on
86
+ }
87
+ }
88
+
89
+ export const DEFAULT_VIEW = (
90
+ input: ViewInput,
91
+ _output: Record<string, unknown>,
92
+ target: HTMLElement,
93
+ ): void => {
94
+ // clang-format off
95
+ render(
96
+ html`
97
+ <style>${artifactsViewerStyles}</style>
98
+ <div class="artifacts-viewer">
99
+ ${input.artifacts.map(artifact => renderArtifact(artifact, input.parsedTrace))}
100
+ </div>
101
+ `,
102
+ target
103
+ );
104
+ // clang-format on
105
+ };
106
+
107
+ export type View = typeof DEFAULT_VIEW;
108
+
109
+ export class ArtifactsViewer extends UI.Widget.Widget {
110
+ #view: View;
111
+ #parsedTrace: Trace.TraceModel.ParsedTrace|null;
112
+ constructor(element?: HTMLElement, view = DEFAULT_VIEW) {
113
+ super(element);
114
+ this.#view = view;
115
+ this.#parsedTrace = null;
116
+ }
117
+
118
+ override wasShown(): void {
119
+ super.wasShown();
120
+ AiAssistanceModel.ArtifactsManager.ArtifactsManager.instance().addEventListener(
121
+ AiAssistanceModel.ArtifactsManager.ArtifactAddedEvent.eventName,
122
+ () => {
123
+ if (this.#parsedTrace) {
124
+ this.performUpdate();
125
+ }
126
+ },
127
+ );
128
+
129
+ UI.Context.Context.instance().addFlavorChangeListener(
130
+ AiAssistanceModel.AIContext.AgentFocus,
131
+ ({data}) => {
132
+ this.#parsedTrace = data.parsedTrace;
133
+ if (this.#parsedTrace) {
134
+ this.performUpdate();
135
+ }
136
+ },
137
+ );
138
+
139
+ const focus = UI.Context.Context.instance().flavor(AiAssistanceModel.AIContext.AgentFocus);
140
+ if (focus) {
141
+ this.#parsedTrace = focus.parsedTrace;
142
+ this.performUpdate();
143
+ }
144
+ }
145
+
146
+ override performUpdate(): void {
147
+ if (!this.#parsedTrace) {
148
+ return;
149
+ }
150
+ this.#view(
151
+ {
152
+ artifacts: AiAssistanceModel.ArtifactsManager.ArtifactsManager.instance().artifacts,
153
+ parsedTrace: this.#parsedTrace,
154
+ },
155
+ {},
156
+ this.contentElement,
157
+ );
158
+ }
159
+ }