chrome-devtools-frontend 1.0.1571573 → 1.0.1573331

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 (158) hide show
  1. package/front_end/Images/geminiInDevTools.png +0 -0
  2. package/front_end/Images/geminiInDevTools_2x.png +0 -0
  3. package/front_end/Images/src/database-off.svg +1 -0
  4. package/front_end/Images/src/lock-person.svg +1 -1
  5. package/front_end/core/common/Debouncer.ts +10 -1
  6. package/front_end/core/common/SettingRegistration.ts +1 -1
  7. package/front_end/core/host/AidaClient.ts +8 -0
  8. package/front_end/core/host/InspectorFrontendHostAPI.ts +0 -6
  9. package/front_end/core/host/UserMetrics.ts +0 -30
  10. package/front_end/core/root/ExperimentNames.ts +30 -0
  11. package/front_end/core/root/Runtime.ts +12 -27
  12. package/front_end/core/root/root.ts +2 -0
  13. package/front_end/core/sdk/CSSModel.ts +36 -13
  14. package/front_end/core/sdk/CSSProperty.ts +1 -1
  15. package/front_end/core/sdk/CSSPropertyParserMatchers.ts +9 -0
  16. package/front_end/core/sdk/CookieModel.ts +2 -1
  17. package/front_end/core/sdk/DOMModel.ts +1 -1
  18. package/front_end/core/sdk/DebuggerModel.ts +1 -1
  19. package/front_end/core/sdk/IsolateManager.ts +0 -7
  20. package/front_end/core/sdk/OverlayModel.ts +1 -1
  21. package/front_end/core/sdk/SourceMap.ts +1 -1
  22. package/front_end/devtools_compatibility.js +0 -6
  23. package/front_end/entrypoints/main/MainImpl.ts +34 -25
  24. package/front_end/entrypoints/main/main-meta.ts +1 -1
  25. package/front_end/generated/SupportedCSSProperties.js +4 -8
  26. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +0 -180
  27. package/front_end/models/ai_assistance/agents/StylingAgent.snapshot.txt +7 -7
  28. package/front_end/models/ai_assistance/agents/StylingAgent.ts +4 -4
  29. package/front_end/models/ai_assistance/ai_assistance.ts +0 -2
  30. package/front_end/models/breakpoints/BreakpointManager.ts +3 -3
  31. package/front_end/models/issues_manager/ContrastCheckTrigger.ts +2 -2
  32. package/front_end/models/source_map_scopes/NamesResolver.ts +1 -1
  33. package/front_end/models/trace/EventsSerializer.ts +5 -1
  34. package/front_end/models/trace/types/TraceEvents.ts +4 -0
  35. package/front_end/panels/accessibility/AXBreadcrumbsPane.ts +1 -1
  36. package/front_end/panels/accessibility/AccessibilitySidebarView.ts +1 -1
  37. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +8 -63
  38. package/front_end/panels/ai_assistance/PatchWidget.ts +3 -4
  39. package/front_end/panels/ai_assistance/SelectWorkspaceDialog.ts +1 -1
  40. package/front_end/panels/ai_assistance/aiAssistancePanel.css +0 -16
  41. package/front_end/panels/ai_assistance/ai_assistance.ts +0 -1
  42. package/front_end/panels/ai_assistance/components/ChatView.ts +123 -108
  43. package/front_end/panels/ai_assistance/components/PerformanceAgentMarkdownRenderer.ts +1 -145
  44. package/front_end/panels/application/AppManifestView.ts +5 -5
  45. package/front_end/panels/application/DeviceBoundSessionsModel.ts +55 -4
  46. package/front_end/panels/application/DeviceBoundSessionsTreeElement.ts +46 -1
  47. package/front_end/panels/application/FrameDetailsView.ts +10 -10
  48. package/front_end/panels/application/components/BackForwardCacheView.ts +6 -8
  49. package/front_end/panels/application/components/BounceTrackingMitigationsView.ts +4 -3
  50. package/front_end/panels/application/components/ProtocolHandlersView.ts +5 -6
  51. package/front_end/panels/application/components/ReportsGrid.ts +8 -9
  52. package/front_end/panels/application/components/SharedStorageAccessGrid.ts +5 -4
  53. package/front_end/panels/application/components/TrustTokensView.ts +4 -6
  54. package/front_end/panels/application/preloading/PreloadingView.ts +9 -8
  55. package/front_end/panels/application/preloading/components/PreloadingDetailsReportView.ts +237 -230
  56. package/front_end/panels/application/preloading/components/PreloadingGrid.ts +3 -2
  57. package/front_end/panels/application/preloading/components/UsedPreloadingView.ts +6 -10
  58. package/front_end/panels/application/resourcesSidebar.css +8 -0
  59. package/front_end/panels/autofill/AutofillView.ts +4 -3
  60. package/front_end/panels/common/AiCodeCompletionSummaryToolbar.ts +1 -1
  61. package/front_end/panels/common/AiCodeCompletionTeaser.ts +1 -1
  62. package/front_end/panels/common/AiCodeGenerationTeaser.ts +35 -4
  63. package/front_end/panels/common/AiCodeGenerationUpgradeDialog.ts +2 -2
  64. package/front_end/panels/common/BadgeNotification.ts +18 -14
  65. package/front_end/panels/common/GdpSignUpDialog.ts +5 -6
  66. package/front_end/panels/common/GeminiRebrandPromoDialog.ts +192 -0
  67. package/front_end/panels/common/aiCodeGenerationTeaser.css +16 -1
  68. package/front_end/panels/common/common.ts +1 -0
  69. package/front_end/panels/common/geminiRebrandPromoDialog.css +47 -0
  70. package/front_end/panels/console/ConsoleInsightTeaser.ts +3 -3
  71. package/front_end/panels/console/ErrorStackParser.ts +11 -16
  72. package/front_end/panels/css_overview/CSSOverviewCompletedView.ts +2 -2
  73. package/front_end/panels/css_overview/CSSOverviewModel.ts +1 -1
  74. package/front_end/panels/elements/ComputedStyleModel.ts +7 -54
  75. package/front_end/panels/elements/ElementsPanel.ts +33 -6
  76. package/front_end/panels/elements/ElementsTreeOutline.ts +1 -1
  77. package/front_end/panels/elements/StylePropertiesSection.ts +16 -8
  78. package/front_end/panels/elements/StylePropertyTreeElement.ts +67 -32
  79. package/front_end/panels/elements/StylesSidebarPane.ts +28 -11
  80. package/front_end/panels/elements/components/CSSHintDetailsView.ts +3 -2
  81. package/front_end/panels/elements/components/CSSPropertyDocsView.ts +3 -2
  82. package/front_end/panels/elements/elements-meta.ts +1 -1
  83. package/front_end/panels/explain/components/ConsoleInsight.ts +7 -7
  84. package/front_end/panels/issues/IssueView.ts +6 -6
  85. package/front_end/panels/network/RequestConditionsDrawer.ts +8 -7
  86. package/front_end/panels/network/RequestCookiesView.ts +3 -4
  87. package/front_end/panels/network/RequestTimingView.ts +3 -8
  88. package/front_end/panels/network/components/HeaderSectionRow.ts +3 -2
  89. package/front_end/panels/network/components/RequestHeaderSection.ts +3 -1
  90. package/front_end/panels/network/components/RequestHeadersView.ts +2 -2
  91. package/front_end/panels/profiler/HeapProfileView.ts +3 -3
  92. package/front_end/panels/profiler/HeapSnapshotView.ts +1 -1
  93. package/front_end/panels/profiler/IsolateSelector.ts +1 -2
  94. package/front_end/panels/profiler/profiler-meta.ts +3 -3
  95. package/front_end/panels/protocol_monitor/protocol_monitor-meta.ts +1 -1
  96. package/front_end/panels/recorder/RecorderController.ts +4 -9
  97. package/front_end/panels/recorder/components/CreateRecordingView.ts +6 -6
  98. package/front_end/panels/recorder/components/RecordingView.ts +0 -38
  99. package/front_end/panels/recorder/components/ReplaySection.ts +0 -14
  100. package/front_end/panels/recorder/components/StepEditor.ts +2 -40
  101. package/front_end/panels/recorder/recorderController.css +3 -3
  102. package/front_end/panels/security/CookieReportView.ts +17 -17
  103. package/front_end/panels/security/cookieControlsView.css +1 -1
  104. package/front_end/panels/security/cookieReportView.css +1 -1
  105. package/front_end/panels/settings/AISettingsTab.ts +8 -10
  106. package/front_end/panels/settings/components/SyncSection.ts +1 -1
  107. package/front_end/panels/settings/emulation/components/UserAgentClientHintsForm.ts +4 -12
  108. package/front_end/panels/settings/settings-meta.ts +1 -1
  109. package/front_end/panels/sources/BreakpointEditDialog.ts +4 -3
  110. package/front_end/panels/sources/FilteredUISourceCodeListProvider.ts +1 -1
  111. package/front_end/panels/sources/NavigatorView.ts +4 -4
  112. package/front_end/panels/sources/SourcesPanel.ts +5 -4
  113. package/front_end/panels/sources/components/HeadersView.ts +2 -2
  114. package/front_end/panels/timeline/CompatibilityTracksAppender.ts +3 -2
  115. package/front_end/panels/timeline/ThreadAppender.ts +2 -1
  116. package/front_end/panels/timeline/TimelineController.ts +3 -3
  117. package/front_end/panels/timeline/TimelinePanel.ts +6 -4
  118. package/front_end/panels/timeline/TimelineUIUtils.ts +2 -2
  119. package/front_end/panels/timeline/components/ExportTraceOptions.ts +4 -4
  120. package/front_end/panels/timeline/components/FieldSettingsDialog.ts +13 -6
  121. package/front_end/panels/timeline/components/LiveMetricsView.ts +13 -9
  122. package/front_end/panels/timeline/components/exportTraceOptions.css +1 -1
  123. package/front_end/panels/timeline/components/fieldSettingsDialog.css +1 -1
  124. package/front_end/panels/timeline/components/insights/BaseInsightComponent.ts +2 -19
  125. package/front_end/panels/timeline/components/insights/InsightRenderer.ts +3 -4
  126. package/front_end/panels/timeline/components/insights/baseInsightComponent.css +0 -5
  127. package/front_end/panels/timeline/components/liveMetricsView.css +1 -1
  128. package/front_end/panels/whats_new/ReleaseNoteView.ts +1 -1
  129. package/front_end/third_party/chromium/README.chromium +1 -1
  130. package/front_end/ui/components/buttons/button.css +4 -0
  131. package/front_end/ui/components/markdown_view/CodeBlock.ts +1 -1
  132. package/front_end/ui/components/panel_feedback/PanelFeedback.ts +1 -1
  133. package/front_end/ui/components/panel_feedback/PreviewToggle.ts +5 -5
  134. package/front_end/ui/components/panel_feedback/previewToggle.css +2 -2
  135. package/front_end/ui/components/text_editor/AiCodeGenerationParser.ts +27 -8
  136. package/front_end/ui/components/text_editor/AiCodeGenerationProvider.ts +36 -6
  137. package/front_end/ui/components/text_editor/AutocompleteHistory.ts +33 -1
  138. package/front_end/ui/legacy/ActionRegistration.ts +1 -1
  139. package/front_end/ui/legacy/ContextMenu.ts +2 -2
  140. package/front_end/ui/legacy/EmptyWidget.ts +3 -3
  141. package/front_end/ui/legacy/LinkContextMenuProvider.ts +42 -0
  142. package/front_end/ui/legacy/ViewRegistration.ts +1 -1
  143. package/front_end/ui/legacy/XLink.ts +0 -27
  144. package/front_end/ui/legacy/components/color_picker/ContrastDetails.ts +1 -1
  145. package/front_end/ui/legacy/components/color_picker/ContrastOverlay.ts +4 -4
  146. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +2 -2
  147. package/front_end/ui/legacy/components/perf_ui/perf_ui-meta.ts +1 -1
  148. package/front_end/ui/legacy/inspectorCommon.css +2 -1
  149. package/front_end/ui/legacy/legacy.ts +3 -1
  150. package/front_end/ui/visual_logging/KnownContextValues.ts +2 -0
  151. package/front_end/ui/visual_logging/LoggingDriver.ts +3 -0
  152. package/package.json +1 -1
  153. package/front_end/models/ai_assistance/ArtifactsManager.ts +0 -67
  154. package/front_end/panels/ai_assistance/components/ArtifactsViewer.ts +0 -183
  155. package/front_end/panels/ai_assistance/components/CollapsibleAssistanceContentWidget.ts +0 -89
  156. package/front_end/panels/ai_assistance/components/PerformanceAgentFlameChart.ts +0 -133
  157. package/front_end/panels/ai_assistance/components/artifactsViewer.css +0 -15
  158. package/front_end/panels/ai_assistance/components/collapsibleAssistanceContentWidget.css +0 -32
@@ -2,8 +2,6 @@
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
 
5
- /* eslint-disable @devtools/no-lit-render-outside-of-view */
6
-
7
5
  import '../../../ui/components/spinners/spinners.js';
8
6
 
9
7
  import * as Host from '../../../core/host/host.js';
@@ -13,7 +11,7 @@ import type * as AiAssistanceModel from '../../../models/ai_assistance/ai_assist
13
11
  import * as Buttons from '../../../ui/components/buttons/buttons.js';
14
12
  import type {MarkdownLitRenderer} from '../../../ui/components/markdown_view/MarkdownView.js';
15
13
  import * as UI from '../../../ui/legacy/legacy.js';
16
- import * as Lit from '../../../ui/lit/lit.js';
14
+ import {Directives, html, nothing, render} from '../../../ui/lit/lit.js';
17
15
  import {PatchWidget} from '../PatchWidget.js';
18
16
 
19
17
  import {ChatInput} from './ChatInput.js';
@@ -22,7 +20,7 @@ import chatViewStyles from './chatView.css.js';
22
20
 
23
21
  export {ChatInput, type ImageInputData} from './ChatInput.js';
24
22
 
25
- const {html, Directives: {ref, repeat, createRef}} = Lit;
23
+ const {ref, repeat, classMap} = Directives;
26
24
 
27
25
  /*
28
26
  * Strings that don't need to be translated at this time.
@@ -38,6 +36,12 @@ const lockedString = i18n.i18n.lockedString;
38
36
 
39
37
  const SCROLL_ROUNDING_OFFSET = 1;
40
38
 
39
+ interface ViewOutput {
40
+ mainElement?: HTMLElement;
41
+ input?: UI.Widget.WidgetElement<ChatInput>;
42
+ }
43
+ type View = (input: ChatWidgetInput, output: ViewOutput, target: HTMLElement|ShadowRoot) => void;
44
+
41
45
  export interface Props {
42
46
  onTextSubmit:
43
47
  (text: string, imageInput?: Host.AidaClient.Part,
@@ -64,18 +68,110 @@ export interface Props {
64
68
  emptyStateSuggestions: AiAssistanceModel.AiAgent.ConversationSuggestion[];
65
69
  inputPlaceholder: Platform.UIString.LocalizedString;
66
70
  disclaimerText: Platform.UIString.LocalizedString;
67
- isArtifactsSidebarOpen: boolean;
68
71
  uploadImageInputEnabled?: boolean;
69
72
  markdownRenderer: MarkdownLitRenderer;
70
73
  additionalFloatyContext: UI.Floaty.FloatyContextSelection[];
71
74
  }
72
75
 
76
+ interface ChatWidgetInput extends Props {
77
+ handleScroll: (ev: Event) => void;
78
+ handleSuggestionClick: (title: string) => void;
79
+ handleMessageContainerRef: (el: Element|undefined) => void;
80
+ }
81
+
82
+ const DEFAULT_VIEW: View = (input, output, target) => {
83
+ const inputWidgetClasses = classMap({
84
+ 'chat-input-widget': true,
85
+ sticky: !input.isReadOnly,
86
+ });
87
+
88
+ // clang-format off
89
+ render(html`
90
+ <style>${chatViewStyles}</style>
91
+ <div class="chat-ui">
92
+ <main @scroll=${input.handleScroll} ${ref(element => { output.mainElement = element as HTMLElement; } )}>
93
+ ${input.messages.length > 0 ? html`
94
+ <div class="messages-container" ${ref(input.handleMessageContainerRef)}>
95
+ ${repeat(input.messages, message =>
96
+ html`<devtools-widget .widgetConfig=${UI.Widget.widgetConfig(ChatMessage, {
97
+ message,
98
+ isLoading: input.isLoading,
99
+ isReadOnly: input.isReadOnly,
100
+ canShowFeedbackForm: input.canShowFeedbackForm,
101
+ userInfo: input.userInfo,
102
+ markdownRenderer: input.markdownRenderer,
103
+ isLastMessage: input.messages.at(-1) === message,
104
+ onSuggestionClick: input.handleSuggestionClick,
105
+ onFeedbackSubmit: input.onFeedbackSubmit,
106
+ onCopyResponseClick: input.onCopyResponseClick,
107
+ })}></devtools-widget>`
108
+ )}
109
+ ${input.isLoading ? nothing : html`<devtools-widget
110
+ .widgetConfig=${UI.Widget.widgetConfig(PatchWidget, {
111
+ changeSummary: input.changeSummary ?? '',
112
+ changeManager: input.changeManager,
113
+ })}
114
+ ></devtools-widget>`}
115
+ </div>
116
+ ` : html`
117
+ <div class="empty-state-container">
118
+ <div class="header">
119
+ <div class="icon">
120
+ <devtools-icon
121
+ name="smart-assistant"
122
+ ></devtools-icon>
123
+ </div>
124
+ <h1>${lockedString(UIStringsNotTranslate.emptyStateText)}</h1>
125
+ </div>
126
+ <div class="empty-state-content">
127
+ ${input.emptyStateSuggestions.map(({title, jslogContext}) => {
128
+ return html`<devtools-button
129
+ class="suggestion"
130
+ @click=${() => input.handleSuggestionClick(title)}
131
+ .data=${
132
+ {
133
+ variant: Buttons.Button.Variant.OUTLINED,
134
+ size: Buttons.Button.Size.REGULAR,
135
+ title,
136
+ jslogContext: jslogContext ?? 'suggestion',
137
+ disabled: input.isTextInputDisabled,
138
+ } as Buttons.Button.ButtonData
139
+ }
140
+ >${title}</devtools-button>`;
141
+ })}
142
+ </div>
143
+ </div>
144
+ `}
145
+ <devtools-widget class=${inputWidgetClasses} .widgetConfig=${UI.Widget.widgetConfig(ChatInput, {
146
+ isLoading: input.isLoading,
147
+ blockedByCrossOrigin: input.blockedByCrossOrigin,
148
+ isTextInputDisabled: input.isTextInputDisabled,
149
+ inputPlaceholder: input.inputPlaceholder,
150
+ disclaimerText: input.disclaimerText,
151
+ selectedContext: input.selectedContext,
152
+ inspectElementToggled: input.inspectElementToggled,
153
+ multimodalInputEnabled: input.multimodalInputEnabled ?? false,
154
+ conversationType: input.conversationType,
155
+ uploadImageInputEnabled: input.uploadImageInputEnabled ?? false,
156
+ isReadOnly: input.isReadOnly,
157
+ additionalFloatyContext: input.additionalFloatyContext,
158
+ onContextClick: input.onContextClick,
159
+ onInspectElementClick: input.onInspectElementClick,
160
+ onTextSubmit: input.onTextSubmit,
161
+ onCancelClick: input.onCancelClick,
162
+ onNewConversation: input.onNewConversation,
163
+ })} ${ref(element => { output.input = element as UI.Widget.WidgetElement<ChatInput>; } )}></devtools-widget>
164
+ </main>
165
+ </div>
166
+ `, target);
167
+ // clang-format on
168
+ };
73
169
  export class ChatView extends HTMLElement {
74
170
  readonly #shadow = this.attachShadow({mode: 'open'});
75
171
  #scrollTop?: number;
76
172
  #props: Props;
77
173
  #messagesContainerElement?: Element;
78
- #mainElementRef = createRef<HTMLElement>();
174
+ #output: ViewOutput = {};
79
175
  #messagesContainerResizeObserver = new ResizeObserver(() => this.#handleMessagesContainerResize());
80
176
  /**
81
177
  * Indicates whether the chat scroll position should be pinned to the bottom.
@@ -94,11 +190,12 @@ export class ChatView extends HTMLElement {
94
190
  * whether to pin the content to the bottom.
95
191
  */
96
192
  #isProgrammaticScroll = false;
97
- #inputRef = createRef<UI.Widget.WidgetElement<ChatInput>>();
193
+ #view: View;
98
194
 
99
- constructor(props: Props) {
195
+ constructor(props: Props, view = DEFAULT_VIEW) {
100
196
  super();
101
197
  this.#props = props;
198
+ this.#view = view;
102
199
  }
103
200
 
104
201
  set props(props: Props) {
@@ -132,7 +229,7 @@ export class ChatView extends HTMLElement {
132
229
  return;
133
230
  }
134
231
 
135
- if (!this.#mainElementRef?.value) {
232
+ if (!this.#output.mainElement) {
136
233
  return;
137
234
  }
138
235
 
@@ -140,11 +237,11 @@ export class ChatView extends HTMLElement {
140
237
  }
141
238
 
142
239
  scrollToBottom(): void {
143
- if (!this.#mainElementRef?.value) {
240
+ if (!this.#output.mainElement) {
144
241
  return;
145
242
  }
146
243
 
147
- this.#setMainElementScrollTop(this.#mainElementRef.value.scrollHeight);
244
+ this.#setMainElementScrollTop(this.#output.mainElement.scrollHeight);
148
245
  }
149
246
 
150
247
  #handleMessagesContainerResize(): void {
@@ -152,26 +249,26 @@ export class ChatView extends HTMLElement {
152
249
  return;
153
250
  }
154
251
 
155
- if (!this.#mainElementRef?.value) {
252
+ if (!this.#output.mainElement) {
156
253
  return;
157
254
  }
158
255
 
159
256
  if (this.#pinScrollToBottom) {
160
- this.#setMainElementScrollTop(this.#mainElementRef.value.scrollHeight);
257
+ this.#setMainElementScrollTop(this.#output.mainElement.scrollHeight);
161
258
  }
162
259
  }
163
260
 
164
261
  #setMainElementScrollTop(scrollTop: number): void {
165
- if (!this.#mainElementRef?.value) {
262
+ if (!this.#output.mainElement) {
166
263
  return;
167
264
  }
168
265
 
169
266
  this.#scrollTop = scrollTop;
170
267
  this.#isProgrammaticScroll = true;
171
- this.#mainElementRef.value.scrollTop = scrollTop;
268
+ this.#output.mainElement.scrollTop = scrollTop;
172
269
  }
173
270
 
174
- #handleMessageContainerRef(el: Element|undefined): void {
271
+ #handleMessageContainerRef = (el: Element|undefined): void => {
175
272
  this.#messagesContainerElement = el;
176
273
 
177
274
  if (el) {
@@ -180,7 +277,7 @@ export class ChatView extends HTMLElement {
180
277
  this.#pinScrollToBottom = true;
181
278
  this.#messagesContainerResizeObserver.disconnect();
182
279
  }
183
- }
280
+ };
184
281
 
185
282
  #handleScroll = (ev: Event): void => {
186
283
  if (!ev.target || !(ev.target instanceof HTMLElement)) {
@@ -201,103 +298,21 @@ export class ChatView extends HTMLElement {
201
298
  };
202
299
 
203
300
  #handleSuggestionClick = (suggestion: string): void => {
204
- this.#inputRef.value?.getWidget()?.setInputValue(suggestion);
301
+ this.#output.input?.getWidget()?.setInputValue(suggestion);
205
302
  this.#render();
206
303
  this.focusTextInput();
207
304
  Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiAssistanceDynamicSuggestionClicked);
208
305
  };
209
306
 
210
307
  #render(): void {
211
- const inputWidgetClasses = Lit.Directives.classMap({
212
- 'chat-input-widget': true,
213
- sticky: !this.#props.isReadOnly,
214
- });
215
-
216
- // clang-format off
217
- Lit.render(html`
218
- <style>${chatViewStyles}</style>
219
- <div class="chat-ui">
220
- <main @scroll=${this.#handleScroll} ${ref(this.#mainElementRef)}>
221
- ${this.#props.messages.length > 0 ? html`
222
- <div class="messages-container" ${ref(this.#handleMessageContainerRef)}>
223
- ${repeat(this.#props.messages, message =>
224
- html`<devtools-widget .widgetConfig=${UI.Widget.widgetConfig(ChatMessage, {
225
- message,
226
- isLoading: this.#props.isLoading,
227
- isReadOnly: this.#props.isReadOnly,
228
- canShowFeedbackForm: this.#props.canShowFeedbackForm,
229
- userInfo: this.#props.userInfo,
230
- markdownRenderer: this.#props.markdownRenderer,
231
- isLastMessage: this.#props.messages.at(-1) === message,
232
- onSuggestionClick: this.#handleSuggestionClick,
233
- onFeedbackSubmit: this.#props.onFeedbackSubmit,
234
- onCopyResponseClick: this.#props.onCopyResponseClick,
235
- })}></devtools-widget>`
236
- )}
237
- ${this.#props.isLoading ? Lit.nothing : html`<devtools-widget
238
- .widgetConfig=${UI.Widget.widgetConfig(PatchWidget, {
239
- changeSummary: this.#props.changeSummary ?? '',
240
- changeManager: this.#props.changeManager,
241
- })}
242
- ></devtools-widget>`}
243
- </div>
244
- ` : html`
245
- <div class="empty-state-container">
246
- <div class="header">
247
- <div class="icon">
248
- <devtools-icon
249
- name="smart-assistant"
250
- ></devtools-icon>
251
- </div>
252
- <h1>${lockedString(UIStringsNotTranslate.emptyStateText)}</h1>
253
- </div>
254
- <div class="empty-state-content">
255
- ${this.#props.emptyStateSuggestions.map(({title, jslogContext}) => {
256
- return html`<devtools-button
257
- class="suggestion"
258
- @click=${() => this.#handleSuggestionClick(title)}
259
- .data=${
260
- {
261
- variant: Buttons.Button.Variant.OUTLINED,
262
- size: Buttons.Button.Size.REGULAR,
263
- title,
264
- jslogContext: jslogContext ?? 'suggestion',
265
- disabled: this.#props.isTextInputDisabled,
266
- } as Buttons.Button.ButtonData
267
- }
268
- >${title}</devtools-button>`;
269
- })}
270
- </div>
271
- </div>
272
- `}
273
- <devtools-widget class=${inputWidgetClasses} .widgetConfig=${UI.Widget.widgetConfig(ChatInput, {
274
- isLoading: this.#props.isLoading,
275
- blockedByCrossOrigin: this.#props.blockedByCrossOrigin,
276
- isTextInputDisabled: this.#props.isTextInputDisabled,
277
- inputPlaceholder: this.#props.inputPlaceholder,
278
- disclaimerText: this.#props.disclaimerText,
279
- selectedContext: this.#props.selectedContext,
280
- inspectElementToggled: this.#props.inspectElementToggled,
281
- multimodalInputEnabled: this.#props.multimodalInputEnabled ?? false,
282
- conversationType: this.#props.conversationType,
283
- uploadImageInputEnabled: this.#props.uploadImageInputEnabled ?? false,
284
- isReadOnly: this.#props.isReadOnly,
285
- additionalFloatyContext: this.#props.additionalFloatyContext,
286
- onContextClick: this.#props.onContextClick,
287
- onInspectElementClick: this.#props.onInspectElementClick,
288
- onTextSubmit: (
289
- text: string, imageInput?: Host.AidaClient.Part,
290
- multimodalInputType?: AiAssistanceModel.AiAgent.MultimodalInputType) => {
291
- this.#props.onTextSubmit(text, imageInput, multimodalInputType);
292
- this.#render();
293
- },
294
- onCancelClick: this.#props.onCancelClick,
295
- onNewConversation: this.#props.onNewConversation,
296
- })} ${ref(this.#inputRef)}></devtools-widget>
297
- </main>
298
- </div>
299
- `, this.#shadow, {host: this});
300
- // clang-format on
308
+ this.#view(
309
+ {
310
+ ...this.#props,
311
+ handleScroll: this.#handleScroll,
312
+ handleSuggestionClick: this.#handleSuggestionClick,
313
+ handleMessageContainerRef: this.#handleMessageContainerRef,
314
+ },
315
+ this.#output, this.#shadow);
301
316
  }
302
317
  }
303
318
 
@@ -2,171 +2,27 @@
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
 
5
- import '../../../models/trace/insights/insights.js';
6
- import '../../../panels/timeline/components/components.js';
7
- import './PerformanceAgentFlameChart.js';
8
- import './CollapsibleAssistanceContentWidget.js';
9
-
10
5
  import * as Common from '../../../core/common/common.js';
11
6
  import * as SDK from '../../../core/sdk/sdk.js';
12
7
  import type * as Protocol from '../../../generated/protocol.js';
13
- import * as GreenDev from '../../../models/greendev/greendev.js';
14
- import * as Logs from '../../../models/logs/logs.js';
15
- import * as NetworkTimeCalculator from '../../../models/network_time_calculator/network_time_calculator.js';
16
- import * as Helpers from '../../../models/trace/helpers/helpers.js';
17
8
  import * as Trace from '../../../models/trace/trace.js';
18
9
  import type * as Marked from '../../../third_party/marked/marked.js';
19
- import * as UI from '../../../ui/legacy/legacy.js';
20
10
  import * as Lit from '../../../ui/lit/lit.js';
21
11
  import * as PanelsCommon from '../../common/common.js';
22
- import * as NetworkForward from '../../network/forward/forward.js';
23
- import * as Network from '../../network/network.js';
24
- import * as TimelineComponents from '../../timeline/components/components.js';
25
- import * as Insights from '../../timeline/components/insights/insights.js';
26
- import * as Timeline from '../../timeline/timeline.js';
27
12
 
28
13
  import {MarkdownRendererWithCodeBlock} from './MarkdownRendererWithCodeBlock.js';
29
- import type * as PerformanceAgentFlameChart from './PerformanceAgentFlameChart.js';
30
14
 
31
15
  const {html} = Lit.StaticHtml;
32
16
  const {ref, createRef} = Lit.Directives;
33
- const {widgetConfig} = UI.Widget;
34
17
 
35
18
  export class PerformanceAgentMarkdownRenderer extends MarkdownRendererWithCodeBlock {
36
- #insightRenderer = new Insights.InsightRenderer.InsightRenderer();
37
-
38
19
  constructor(
39
20
  private mainFrameId = '',
40
- private lookupEvent: (key: Trace.Types.File.SerializableKey) => Trace.Types.Events.Event | null = () => null,
41
- private parsedTrace: Trace.TraceModel.ParsedTrace|null = null) {
21
+ private lookupEvent: (key: Trace.Types.File.SerializableKey) => Trace.Types.Events.Event | null = () => null) {
42
22
  super();
43
23
  }
44
24
 
45
25
  override templateForToken(token: Marked.Marked.MarkedToken): Lit.LitTemplate|null {
46
- if (!this.parsedTrace) {
47
- return null;
48
- }
49
-
50
- // NOTE: The custom tag handling below (e.g., <ai-insight>, <network-request-widget>)
51
- // is part of a prototype for the GreenDev project and is only rendered when the GreenDev
52
- // feature is enabled.
53
- if (token.type === 'html' && GreenDev.Prototypes.instance().isEnabled('inlineWidgets')) {
54
- if (token.text.includes('<flame-chart-widget')) {
55
- const startMatch = token.text.match(/start="?(\d+)"?/);
56
- const endMatch = token.text.match(/end="?(\d+)"?/);
57
- const start = startMatch ? Number(startMatch[1]) : this.parsedTrace.data.Meta.traceBounds.min;
58
- const end = endMatch ? Number(endMatch[1]) : this.parsedTrace.data.Meta.traceBounds.max;
59
- return html`<devtools-performance-agent-flame-chart .data=${{
60
- parsedTrace: this.parsedTrace,
61
- start,
62
- end,
63
- } as PerformanceAgentFlameChart.PerformanceAgentFlameChartData}
64
- }></devtools-performance-agent-flame-chart>`;
65
- }
66
-
67
- // Flexible regex to match the tag name and a value a.
68
- // match[1]: tagName (e.g., 'ai-insight', 'network-request-widget')
69
- // match[2]: value (value needed to display the widget)
70
- const regex = /<([a-z-]+)\s+value="([^"]+)">/;
71
- const match = token.text.match(regex);
72
-
73
- if (!match) {
74
- return null;
75
- }
76
-
77
- const tagName = match[1];
78
- const value = match[2];
79
-
80
- if (tagName === 'ai-insight' && value) {
81
- const componentName = value;
82
-
83
- const insightSet = this.parsedTrace.insights?.values().next().value;
84
- const insightM = insightSet?.model[componentName as Trace.Insights.Types.InsightKeys];
85
-
86
- if (!insightM) {
87
- return null;
88
- }
89
-
90
- return html`<devtools-collapsible-assistance-content-widget .data=${{
91
- headerText: `Insight - ${componentName}`, onReveal: () => {
92
- void UI.InspectorView.InspectorView.instance().showPanel('timeline').then(() => {
93
- Timeline.TimelinePanel.TimelinePanel.instance().revealInsight(insightM);
94
- });
95
- },
96
- }
97
- }>
98
- ${this.#insightRenderer.renderInsightToWidgetElement(this.parsedTrace, insightSet, insightM, componentName, {
99
- selected: true,
100
- isAIAssistanceContext: true
101
- })}
102
- </devtools-collapsible-assistance-content-widget>`;
103
- }
104
- if (tagName === 'network-request-widget' && value) {
105
- const rawTraceEvent =
106
- Helpers.SyntheticEvents.SyntheticEventsManager.getActiveManager().getRawTraceEvents().at(Number(value));
107
-
108
- // Rendering RequestTimingView widget only works for fresh traces where the network log is in sync.
109
- // If the trace is uploaded, we need to use the synthetic events and
110
- // render the network request tooltip that uses the synthetic events.
111
- if (rawTraceEvent && Trace.Types.Events.isSyntheticNetworkRequest(rawTraceEvent)) {
112
- const rawTraceEventId = rawTraceEvent?.args?.data?.requestId;
113
- const rawTraceEventUrl = rawTraceEvent?.args?.data?.url;
114
- const networkRequest = rawTraceEvent ? Logs.NetworkLog.NetworkLog.instance()
115
- .requestsForId(rawTraceEventId)
116
- .find(r => r.url() === rawTraceEventUrl) :
117
- null;
118
-
119
- if (networkRequest) {
120
- const calculator = new NetworkTimeCalculator.NetworkTimeCalculator(true);
121
- return html`<devtools-collapsible-assistance-content-widget
122
- .data=${{
123
- headerText: `Network Request: ${
124
- networkRequest.url().length > 80 ? networkRequest.url().slice(0, 80) + '...' : networkRequest.url()}`,
125
- onReveal: () => {
126
- void UI.InspectorView.InspectorView.instance().showPanel('network').then(() => {
127
- void Common.Revealer.reveal(NetworkForward.UIRequestLocation.UIRequestLocation.tab(
128
- networkRequest, NetworkForward.UIRequestLocation.UIRequestTabs.TIMING));
129
- });
130
- },
131
- }
132
- }>
133
- <devtools-widget class="actions" .widgetConfig=${
134
- UI.Widget.widgetConfig(Network.RequestTimingView.RequestTimingView, {
135
- request: networkRequest,
136
- calculator,
137
- })}></devtools-widget>
138
- </devtools-collapsible-assistance-content-widget>`;
139
- }
140
- }
141
-
142
- const syntheticRequest =
143
- Helpers.SyntheticEvents.SyntheticEventsManager.getActiveManager().syntheticEventForRawEventIndex(
144
- Number(value));
145
-
146
- let networkTooltip = null;
147
- if (syntheticRequest && Trace.Types.Events.isSyntheticNetworkRequest(syntheticRequest)) {
148
- // clang-format off
149
- networkTooltip = html`<devtools-widget .widgetConfig=${widgetConfig(TimelineComponents.NetworkRequestTooltip.NetworkRequestTooltip, {
150
- networkRequest: syntheticRequest,
151
- })}></devtools-widget>`;
152
- // clang-format on
153
- }
154
-
155
- return html`<devtools-collapsible-assistance-content-widget
156
- .data=${{
157
- headerText: 'Network Request', onReveal: () => {
158
- // eslint-disable-next-line no-console
159
- console.log('Reveal network request', value);
160
- },
161
- }
162
- }>
163
- ${networkTooltip}
164
- </devtools-collapsible-assistance-content-widget>`;
165
- }
166
-
167
- return null;
168
- }
169
-
170
26
  if (token.type === 'link' && token.href.startsWith('#')) {
171
27
  if (token.href.startsWith('#node-')) {
172
28
  const nodeId = Number(token.href.replace('#node-', '')) as Protocol.DOM.BackendNodeId;
@@ -644,7 +644,7 @@ function renderIdentity(identityData: IdentitySectionData, onCopy: () => void, o
644
644
  jslog=${VisualLogging.action('help').track({hover: true})}>
645
645
  </devtools-icon>
646
646
  <devtools-link href="https://developer.chrome.com/blog/pwa-manifest-id/"
647
- .jslogContext=${'learn-more'}
647
+ jslogcontext="learn-more"
648
648
  ${ref(setFocusOnSection(i18nString(UIStrings.identity), output))}>
649
649
  ${i18nString(UIStrings.learnMore)}
650
650
  </devtools-link>
@@ -766,7 +766,7 @@ function renderIcons(
766
766
  <div class="report-row">
767
767
  ${i18nTemplate(str_, UIStrings.needHelpReadOurS, {
768
768
  PH1: html`
769
- <devtools-link href="https://web.dev/maskable-icon/" .jslogContext=${'learn-more'}>
769
+ <devtools-link href="https://web.dev/maskable-icon/" jslogcontext="learn-more">
770
770
  ${i18nString(UIStrings.documentationOnMaskableIcons)}
771
771
  </devtools-link>`,
772
772
  })}
@@ -866,7 +866,7 @@ function renderWindowControlsSection(
866
866
  PH2: html`<code>
867
867
  <devtools-link
868
868
  href="https://developer.mozilla.org/en-US/docs/Web/Manifest/display_override"
869
- .jslogContext=${'display-override'}
869
+ jslogcontext="display-override"
870
870
  ${ref(setFocusOnSection(i18nString(UIStrings.windowControlsOverlay), output))}>
871
871
  display-override
872
872
  </devtools-link>
@@ -882,7 +882,7 @@ function renderWindowControlsSection(
882
882
  ${i18nTemplate(str_, UIStrings.wcoNotFound, {PH1: html`<code>
883
883
  <devtools-link
884
884
  href="https://developer.mozilla.org/en-US/docs/Web/Manifest/display_override"
885
- .jslogContext=${'display-override'}
885
+ jslogcontext="display-override"
886
886
  ${ref(setFocusOnSection(i18nString(UIStrings.windowControlsOverlay), output))}>
887
887
  display-override
888
888
  </devtools-link>
@@ -891,7 +891,7 @@ function renderWindowControlsSection(
891
891
  <div class="report-row">
892
892
  ${i18nTemplate(str_, UIStrings.wcoNeedHelpReadMore, {PH1: html`<devtools-link
893
893
  href="https://learn.microsoft.com/en-us/microsoft-edge/progressive-web-apps-chromium/how-to/window-controls-overlay"
894
- .jslogContext=${'customize-pwa-tittle-bar'}>
894
+ jslogcontext="customize-pwa-tittle-bar">
895
895
  ${i18nString(UIStrings.customizePwaTitleBar)}
896
896
  </devtools-link>`})}
897
897
  </div>
@@ -12,6 +12,8 @@ interface EventWithTimestamp {
12
12
  }
13
13
  export interface SessionAndEvents {
14
14
  session?: Protocol.Network.DeviceBoundSession;
15
+ isSessionTerminated: boolean;
16
+ hasErrors: boolean;
15
17
  eventsById: Map<string, EventWithTimestamp>;
16
18
  }
17
19
  type SessionIdToSessionMap = Map<string|undefined, SessionAndEvents>;
@@ -60,11 +62,21 @@ export class DeviceBoundSessionsModel extends Common.ObjectWrapper.ObjectWrapper
60
62
  return;
61
63
  }
62
64
  const emptySessions = new Map<string, Array<string|undefined>>();
65
+ const noLongerFailedSessions = new Map<string, Array<string|undefined>>();
63
66
  const emptySites = new Set<string>();
64
67
  for (const [site, sessionIdToSessionMap] of [...this.#siteSessions]) {
65
68
  let emptySessionsSiteEntry = emptySessions.get(site);
69
+ let noLongerFailedSessionsSiteEntry = noLongerFailedSessions.get(site);
66
70
  for (const [sessionId, sessionAndEvents] of sessionIdToSessionMap) {
67
71
  sessionAndEvents.eventsById.clear();
72
+ if (sessionAndEvents.hasErrors) {
73
+ sessionAndEvents.hasErrors = false;
74
+ if (!noLongerFailedSessionsSiteEntry) {
75
+ noLongerFailedSessionsSiteEntry = [];
76
+ noLongerFailedSessions.set(site, noLongerFailedSessionsSiteEntry);
77
+ }
78
+ noLongerFailedSessionsSiteEntry.push(sessionId);
79
+ }
68
80
  if (sessionAndEvents.session) {
69
81
  continue;
70
82
  }
@@ -84,13 +96,30 @@ export class DeviceBoundSessionsModel extends Common.ObjectWrapper.ObjectWrapper
84
96
  }
85
97
  }
86
98
 
87
- this.dispatchEventToListeners(DeviceBoundSessionModelEvents.CLEAR_EVENTS, {emptySessions, emptySites});
99
+ this.dispatchEventToListeners(
100
+ DeviceBoundSessionModelEvents.CLEAR_EVENTS, {emptySessions, emptySites, noLongerFailedSessions});
88
101
  }
89
102
 
90
103
  isSiteVisible(site: string): boolean {
91
104
  return this.#visibleSites.has(site);
92
105
  }
93
106
 
107
+ isSessionTerminated(site: string, sessionId?: string): boolean {
108
+ const session = this.getSession(site, sessionId);
109
+ if (session === undefined) {
110
+ return false;
111
+ }
112
+ return session.isSessionTerminated;
113
+ }
114
+
115
+ sessionHasErrors(site: string, sessionId?: string): boolean {
116
+ const session = this.getSession(site, sessionId);
117
+ if (session === undefined) {
118
+ return false;
119
+ }
120
+ return session.hasErrors;
121
+ }
122
+
94
123
  getSession(site: string, sessionId?: string): SessionAndEvents|undefined {
95
124
  return this.#siteSessions.get(site)?.get(sessionId);
96
125
  }
@@ -116,7 +145,12 @@ export class DeviceBoundSessionsModel extends Common.ObjectWrapper.ObjectWrapper
116
145
 
117
146
  let sessionAndEvent = sessionIdToSessionMap.get(sessionId);
118
147
  if (!sessionAndEvent) {
119
- sessionAndEvent = {session: undefined, eventsById: new Map<string, EventWithTimestamp>()};
148
+ sessionAndEvent = {
149
+ session: undefined,
150
+ isSessionTerminated: false,
151
+ hasErrors: false,
152
+ eventsById: new Map<string, EventWithTimestamp>()
153
+ };
120
154
  sessionIdToSessionMap.set(sessionId, sessionAndEvent);
121
155
  }
122
156
  return sessionAndEvent;
@@ -145,6 +179,20 @@ export class DeviceBoundSessionsModel extends Common.ObjectWrapper.ObjectWrapper
145
179
  sessionAndEvent.session.cachedChallenge = event.challengeEventDetails.challenge;
146
180
  }
147
181
 
182
+ // Set the session's terminated status based on the event.
183
+ if (event.succeeded) {
184
+ if (event.terminationEventDetails) {
185
+ sessionAndEvent.isSessionTerminated = true;
186
+ } else if (event.creationEventDetails) {
187
+ sessionAndEvent.isSessionTerminated = false;
188
+ }
189
+ }
190
+
191
+ // Set that the session has errors if the latest event failed.
192
+ if (!event.succeeded) {
193
+ sessionAndEvent.hasErrors = true;
194
+ }
195
+
148
196
  this.dispatchEventToListeners(
149
197
  DeviceBoundSessionModelEvents.EVENT_OCCURRED,
150
198
  {site: eventWithTimestamp.event.site, sessionId: eventWithTimestamp.event.sessionId});
@@ -164,6 +212,9 @@ export interface DeviceBoundSessionModelEventTypes {
164
212
  [DeviceBoundSessionModelEvents.ADD_VISIBLE_SITE]: {site: string};
165
213
  [DeviceBoundSessionModelEvents.CLEAR_VISIBLE_SITES]: void;
166
214
  [DeviceBoundSessionModelEvents.EVENT_OCCURRED]: {site: string, sessionId?: string};
167
- [DeviceBoundSessionModelEvents.CLEAR_EVENTS]:
168
- {emptySessions: Map<string, Array<string|undefined>>, emptySites: Set<string>};
215
+ [DeviceBoundSessionModelEvents.CLEAR_EVENTS]: {
216
+ emptySessions: Map<string, Array<string|undefined>>,
217
+ emptySites: Set<string>,
218
+ noLongerFailedSessions: Map<string, Array<string|undefined>>,
219
+ };
169
220
  }