chrome-devtools-frontend 1.0.1597448 → 1.0.1598808

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 (143) hide show
  1. package/docs/ui_engineering.md +6 -6
  2. package/front_end/core/host/AidaClient.ts +6 -0
  3. package/front_end/core/sdk/CPUThrottlingManager.ts +5 -1
  4. package/front_end/core/sdk/CSSMatchedStyles.ts +2 -0
  5. package/front_end/core/sdk/CSSPropertyParserMatchers.ts +28 -0
  6. package/front_end/models/ai_assistance/AiConversation.ts +40 -22
  7. package/front_end/models/ai_assistance/ChangeManager.ts +16 -0
  8. package/front_end/models/ai_assistance/ExtensionScope.ts +11 -3
  9. package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +127 -0
  10. package/front_end/models/ai_assistance/agents/AiAgent.ts +35 -6
  11. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +1 -1
  12. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +11 -8
  13. package/front_end/models/ai_assistance/agents/ConversationSummaryAgent.ts +109 -0
  14. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +30 -1
  15. package/front_end/models/ai_assistance/agents/StylingAgent.ts +34 -4
  16. package/front_end/models/ai_assistance/ai_assistance.ts +4 -0
  17. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +27 -0
  18. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +21 -0
  19. package/front_end/models/trace/Processor.ts +1 -0
  20. package/front_end/models/trace/handlers/PageLoadMetricsHandler.ts +33 -0
  21. package/front_end/models/trace/insights/CharacterSet.ts +172 -0
  22. package/front_end/models/trace/insights/Models.ts +1 -0
  23. package/front_end/models/trace/insights/types.ts +1 -0
  24. package/front_end/models/trace/types/TraceEvents.ts +17 -0
  25. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +22 -12
  26. package/front_end/panels/ai_assistance/PatchWidget.ts +1 -1
  27. package/front_end/panels/ai_assistance/components/ChatMessage.ts +131 -63
  28. package/front_end/panels/ai_assistance/components/ChatView.ts +1 -1
  29. package/front_end/panels/ai_assistance/components/MarkdownRendererWithCodeBlock.ts +18 -9
  30. package/front_end/panels/ai_assistance/components/chatMessage.css +11 -0
  31. package/front_end/panels/application/AppManifestView.ts +4 -5
  32. package/front_end/panels/application/CookieItemsView.ts +9 -15
  33. package/front_end/panels/application/DeviceBoundSessionsView.ts +20 -26
  34. package/front_end/panels/application/FrameDetailsView.ts +10 -16
  35. package/front_end/panels/application/OriginTrialTreeView.ts +2 -3
  36. package/front_end/panels/application/ReportingApiView.ts +14 -18
  37. package/front_end/panels/application/components/BackForwardCacheView.ts +3 -3
  38. package/front_end/panels/application/preloading/PreloadingView.ts +18 -3
  39. package/front_end/panels/application/preloading/components/UsedPreloadingView.ts +2 -3
  40. package/front_end/panels/browser_debugger/DOMBreakpointsSidebarPane.ts +3 -2
  41. package/front_end/panels/changes/ChangesView.ts +13 -15
  42. package/front_end/panels/console/ConsolePinPane.ts +3 -3
  43. package/front_end/panels/coverage/CoverageListView.ts +1 -1
  44. package/front_end/panels/coverage/CoverageView.ts +4 -4
  45. package/front_end/panels/css_overview/CSSOverviewCompletedView.ts +3 -3
  46. package/front_end/panels/css_overview/CSSOverviewPanel.ts +11 -15
  47. package/front_end/panels/developer_resources/DeveloperResourcesView.ts +3 -5
  48. package/front_end/panels/elements/ElementsTreeOutline.ts +34 -8
  49. package/front_end/panels/elements/EventListenersWidget.ts +3 -2
  50. package/front_end/panels/elements/StandaloneStylesContainer.ts +21 -6
  51. package/front_end/panels/elements/StylePropertiesSection.ts +129 -1
  52. package/front_end/panels/elements/StylePropertyHighlighter.ts +3 -0
  53. package/front_end/panels/elements/StylePropertyTreeElement.ts +49 -4
  54. package/front_end/panels/elements/stylesSidebarPane.css +34 -0
  55. package/front_end/panels/issues/AffectedSelectivePermissionsInterventionView.ts +3 -5
  56. package/front_end/panels/layer_viewer/LayerDetailsView.ts +2 -1
  57. package/front_end/panels/layer_viewer/Layers3DView.ts +5 -4
  58. package/front_end/panels/linear_memory_inspector/components/LinearMemoryInspector.ts +5 -6
  59. package/front_end/panels/linear_memory_inspector/components/LinearMemoryValueInterpreter.ts +6 -11
  60. package/front_end/panels/network/RequestConditionsDrawer.ts +4 -4
  61. package/front_end/panels/network/RequestCookiesView.ts +5 -6
  62. package/front_end/panels/network/RequestHeadersView.ts +4 -4
  63. package/front_end/panels/network/RequestInitiatorView.ts +7 -5
  64. package/front_end/panels/network/RequestResponseView.ts +10 -15
  65. package/front_end/panels/performance_monitor/PerformanceMonitor.ts +2 -2
  66. package/front_end/panels/protocol_monitor/ProtocolMonitor.ts +5 -6
  67. package/front_end/panels/recorder/components/RecordingView.ts +31 -36
  68. package/front_end/panels/recorder/components/StepEditor.ts +6 -7
  69. package/front_end/panels/recorder/components/StepView.ts +2 -1
  70. package/front_end/panels/search/SearchView.ts +3 -4
  71. package/front_end/panels/settings/WorkspaceSettingsTab.ts +2 -5
  72. package/front_end/panels/timeline/TimelineDetailsView.ts +8 -6
  73. package/front_end/panels/timeline/components/LiveMetricsView.ts +5 -8
  74. package/front_end/panels/timeline/components/SidebarSingleInsightSet.ts +2 -7
  75. package/front_end/panels/timeline/components/insights/Cache.ts +8 -10
  76. package/front_end/panels/timeline/components/insights/CharacterSet.ts +34 -0
  77. package/front_end/panels/timeline/components/insights/DOMSize.ts +16 -20
  78. package/front_end/panels/timeline/components/insights/DocumentLatency.ts +2 -6
  79. package/front_end/panels/timeline/components/insights/DuplicatedJavaScript.ts +3 -4
  80. package/front_end/panels/timeline/components/insights/EventRef.ts +2 -2
  81. package/front_end/panels/timeline/components/insights/FontDisplay.ts +3 -4
  82. package/front_end/panels/timeline/components/insights/ForcedReflow.ts +5 -7
  83. package/front_end/panels/timeline/components/insights/INPBreakdown.ts +3 -4
  84. package/front_end/panels/timeline/components/insights/ImageDelivery.ts +3 -4
  85. package/front_end/panels/timeline/components/insights/ImageRef.ts +2 -4
  86. package/front_end/panels/timeline/components/insights/InsightRenderer.ts +2 -0
  87. package/front_end/panels/timeline/components/insights/LCPBreakdown.ts +5 -7
  88. package/front_end/panels/timeline/components/insights/LCPDiscovery.ts +2 -4
  89. package/front_end/panels/timeline/components/insights/LegacyJavaScript.ts +3 -4
  90. package/front_end/panels/timeline/components/insights/ModernHTTP.ts +3 -4
  91. package/front_end/panels/timeline/components/insights/NetworkDependencyTree.ts +7 -11
  92. package/front_end/panels/timeline/components/insights/NodeLink.ts +2 -4
  93. package/front_end/panels/timeline/components/insights/RenderBlocking.ts +3 -4
  94. package/front_end/panels/timeline/components/insights/SlowCSSSelector.ts +7 -10
  95. package/front_end/panels/timeline/components/insights/ThirdParties.ts +5 -7
  96. package/front_end/panels/timeline/components/insights/insights.ts +2 -0
  97. package/front_end/panels/web_audio/WebAudioView.ts +3 -4
  98. package/front_end/panels/webauthn/WebauthnPane.ts +2 -2
  99. package/front_end/third_party/chromium/README.chromium +1 -1
  100. package/front_end/third_party/puppeteer/README.chromium +2 -2
  101. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Realm.js +1 -1
  102. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Realm.js.map +1 -1
  103. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/util.d.ts +4 -0
  104. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/util.d.ts.map +1 -1
  105. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/util.js +13 -0
  106. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/util.js.map +1 -1
  107. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
  108. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +3 -3
  109. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +3 -3
  110. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js.map +1 -1
  111. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
  112. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.d.ts +1 -1
  113. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.js +1 -1
  114. package/front_end/third_party/puppeteer/package/lib/cjs/third_party/mitt/mitt.js +2 -2
  115. package/front_end/third_party/puppeteer/package/lib/cjs/third_party/parsel-js/parsel-js.js +1 -1
  116. package/front_end/third_party/puppeteer/package/lib/cjs/third_party/rxjs/rxjs.js +446 -446
  117. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +166 -167
  118. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Realm.js +2 -2
  119. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Realm.js.map +1 -1
  120. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/util.d.ts +4 -0
  121. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/util.d.ts.map +1 -1
  122. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/util.js +12 -0
  123. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/util.js.map +1 -1
  124. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +3 -3
  125. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +3 -3
  126. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js.map +1 -1
  127. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.d.ts +1 -1
  128. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.js +1 -1
  129. package/front_end/third_party/puppeteer/package/lib/esm/third_party/mitt/mitt.js +1 -1
  130. package/front_end/third_party/puppeteer/package/lib/esm/third_party/parsel-js/parsel-js.js +1 -1
  131. package/front_end/third_party/puppeteer/package/lib/esm/third_party/rxjs/rxjs.js +97 -97
  132. package/front_end/third_party/puppeteer/package/package.json +1 -1
  133. package/front_end/third_party/puppeteer/package/src/bidi/Realm.ts +2 -2
  134. package/front_end/third_party/puppeteer/package/src/bidi/util.ts +17 -0
  135. package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
  136. package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
  137. package/front_end/ui/components/settings/SettingCheckbox.ts +2 -0
  138. package/front_end/ui/legacy/Widget.ts +42 -6
  139. package/front_end/ui/legacy/components/data_grid/DataGridElement.ts +3 -3
  140. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +8 -8
  141. package/front_end/ui/visual_logging/Debugging.ts +0 -32
  142. package/front_end/ui/visual_logging/KnownContextValues.ts +2 -0
  143. package/package.json +1 -1
@@ -0,0 +1,172 @@
1
+ // Copyright 2026 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 * as i18n from '../../../core/i18n/i18n.js';
6
+ import type * as Handlers from '../handlers/handlers.js';
7
+ import type * as Types from '../types/types.js';
8
+
9
+ import {
10
+ type Checklist,
11
+ InsightCategory,
12
+ InsightKeys,
13
+ type InsightModel,
14
+ type InsightSetContext,
15
+ InsightWarning,
16
+ type PartialInsightModel,
17
+ } from './types.js';
18
+
19
+ export const UIStrings = {
20
+ /**
21
+ * @description Title of an insight that checks whether the page declares a character encoding early enough.
22
+ */
23
+ title: 'Declare a character encoding',
24
+ /**
25
+ * @description Description of an insight that checks whether the page has a proper character encoding declaration via HTTP header or early meta tag.
26
+ */
27
+ description:
28
+ 'A character encoding declaration is required. It can be done with a meta charset tag in the first 1024 bytes of the HTML or in the Content-Type HTTP response header. [Learn more about declaring the character encoding](https://developer.chrome.com/docs/insights/charset/).',
29
+ /**
30
+ * @description Text to tell the user that the charset is declared in the Content-Type HTTP response header.
31
+ */
32
+ passingHttpHeader: 'Declares charset in HTTP header',
33
+ /**
34
+ * @description Text to tell the user that the charset is NOT declared in the Content-Type HTTP response header.
35
+ */
36
+ failedHttpHeader: 'Does not declare charset in HTTP header',
37
+ /**
38
+ * @description Text to tell the user that a meta charset tag was found in the first 1024 bytes of the HTML.
39
+ */
40
+ passingMetaCharsetEarly: 'Declares charset using a meta tag in the first 1024 bytes',
41
+ /**
42
+ * @description Text to tell the user that a meta charset tag was found, but too late in the HTML.
43
+ */
44
+ failedMetaCharsetLate: 'Declares charset using a meta tag after the first 1024 bytes',
45
+ /**
46
+ * @description Text to tell the user that no meta charset tag was found in the HTML.
47
+ */
48
+ failedMetaCharsetMissing: 'Does not declare charset using a meta tag',
49
+ /**
50
+ * @description Text to tell the user that trace data did not include the Blink signal for meta charset.
51
+ */
52
+ failedMetaCharsetUnknown: 'Could not determine meta charset declaration from trace',
53
+ } as const;
54
+
55
+ const str_ = i18n.i18n.registerUIStrings('models/trace/insights/CharacterSet.ts', UIStrings);
56
+ export const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
57
+
58
+ const CHARSET_HTTP_REGEX = /charset\s*=\s*[a-zA-Z0-9\-_:.()]{2,}/i;
59
+
60
+ export type CharacterSetInsightModel = InsightModel<typeof UIStrings, {
61
+ data?: {
62
+ hasHttpCharset: boolean,
63
+ checklist: Checklist<'httpCharset'|'metaCharset'>,
64
+ metaCharsetDisposition?: Types.Events.MetaCharsetDisposition,
65
+ documentRequest?: Types.Events.SyntheticNetworkRequest,
66
+ },
67
+ }>;
68
+
69
+ export function isCharacterSetInsight(model: InsightModel): model is CharacterSetInsightModel {
70
+ return model.insightKey === InsightKeys.CHARACTER_SET;
71
+ }
72
+
73
+ function finalize(partialModel: PartialInsightModel<CharacterSetInsightModel>): CharacterSetInsightModel {
74
+ let hasFailure = false;
75
+ if (partialModel.data) {
76
+ hasFailure = !partialModel.data.checklist.httpCharset.value && !partialModel.data.checklist.metaCharset.value;
77
+ }
78
+
79
+ return {
80
+ insightKey: InsightKeys.CHARACTER_SET,
81
+ strings: UIStrings,
82
+ title: i18nString(UIStrings.title),
83
+ description: i18nString(UIStrings.description),
84
+ docs: 'https://developer.chrome.com/docs/insights/charset/',
85
+ category: InsightCategory.ALL,
86
+ state: hasFailure ? 'fail' : 'pass',
87
+ ...partialModel,
88
+ };
89
+ }
90
+
91
+ function hasCharsetInContentType(request: Types.Events.SyntheticNetworkRequest): boolean {
92
+ if (!request.args.data.responseHeaders) {
93
+ return false;
94
+ }
95
+ for (const header of request.args.data.responseHeaders) {
96
+ if (header.name.toLowerCase() === 'content-type') {
97
+ return CHARSET_HTTP_REGEX.test(header.value);
98
+ }
99
+ }
100
+ return false;
101
+ }
102
+
103
+ function findMetaCharsetDisposition(
104
+ data: Handlers.Types.HandlerData,
105
+ context: InsightSetContext,
106
+ ): Types.Events.MetaCharsetDisposition|undefined {
107
+ if (!context.navigation) {
108
+ return undefined;
109
+ }
110
+ return data.PageLoadMetrics.metaCharsetCheckEventsByNavigation.get(context.navigation)
111
+ ?.at(-1)
112
+ ?.args.data?.disposition;
113
+ }
114
+
115
+ function metaCharsetLabel(disposition: Types.Events.MetaCharsetDisposition|undefined): ReturnType<typeof i18nString> {
116
+ switch (disposition) {
117
+ case 'found-in-first-1024-bytes':
118
+ return i18nString(UIStrings.passingMetaCharsetEarly);
119
+ case 'found-after-first-1024-bytes':
120
+ return i18nString(UIStrings.failedMetaCharsetLate);
121
+ case 'not-found':
122
+ return i18nString(UIStrings.failedMetaCharsetMissing);
123
+ default:
124
+ return i18nString(UIStrings.failedMetaCharsetUnknown);
125
+ }
126
+ }
127
+
128
+ export function generateInsight(
129
+ data: Handlers.Types.HandlerData, context: InsightSetContext): CharacterSetInsightModel {
130
+ if (!context.navigation) {
131
+ return finalize({});
132
+ }
133
+
134
+ const documentRequest = data.NetworkRequests.byId.get(context.navigationId);
135
+ if (!documentRequest) {
136
+ return finalize({warnings: [InsightWarning.NO_DOCUMENT_REQUEST]});
137
+ }
138
+
139
+ const hasHttpCharset = hasCharsetInContentType(documentRequest);
140
+ const metaCharsetDisposition = findMetaCharsetDisposition(data, context);
141
+ const hasMetaCharsetInFirst1024Bytes = metaCharsetDisposition === 'found-in-first-1024-bytes';
142
+
143
+ return finalize({
144
+ relatedEvents: [documentRequest],
145
+ data: {
146
+ hasHttpCharset,
147
+ metaCharsetDisposition,
148
+ documentRequest,
149
+ checklist: {
150
+ httpCharset: {
151
+ label: hasHttpCharset ? i18nString(UIStrings.passingHttpHeader) : i18nString(UIStrings.failedHttpHeader),
152
+ value: hasHttpCharset,
153
+ },
154
+ metaCharset: {
155
+ label: metaCharsetLabel(metaCharsetDisposition),
156
+ value: hasMetaCharsetInFirst1024Bytes,
157
+ },
158
+ },
159
+ },
160
+ });
161
+ }
162
+
163
+ export function createOverlays(model: CharacterSetInsightModel): Types.Overlays.Overlay[] {
164
+ if (!model.data?.documentRequest) {
165
+ return [];
166
+ }
167
+
168
+ return [{
169
+ type: 'ENTRY_SELECTED',
170
+ entry: model.data.documentRequest,
171
+ }];
172
+ }
@@ -3,6 +3,7 @@
3
3
  // found in the LICENSE file.
4
4
 
5
5
  export * as Cache from './Cache.js';
6
+ export * as CharacterSet from './CharacterSet.js';
6
7
  export * as CLSCulprits from './CLSCulprits.js';
7
8
  export * as DocumentLatency from './DocumentLatency.js';
8
9
  export * as DOMSize from './DOMSize.js';
@@ -164,4 +164,5 @@ export const enum InsightKeys {
164
164
  VIEWPORT = 'Viewport',
165
165
  MODERN_HTTP = 'ModernHTTP',
166
166
  CACHE = 'Cache',
167
+ CHARACTER_SET = 'CharacterSet',
167
168
  }
@@ -1360,6 +1360,22 @@ export function isParseMetaViewport(event: Event): event is ParseMetaViewport {
1360
1360
  return event.name === Name.PARSE_META_VIEWPORT;
1361
1361
  }
1362
1362
 
1363
+ export type MetaCharsetDisposition = 'found-in-first-1024-bytes'|'found-after-first-1024-bytes'|'not-found';
1364
+
1365
+ export interface MetaCharsetCheck extends Instant {
1366
+ name: Name.META_CHARSET_CHECK;
1367
+ args: Args&{
1368
+ data: {
1369
+ frame: string,
1370
+ disposition: MetaCharsetDisposition,
1371
+ },
1372
+ };
1373
+ }
1374
+
1375
+ export function isMetaCharsetCheck(event: Event): event is MetaCharsetCheck {
1376
+ return event.name === Name.META_CHARSET_CHECK;
1377
+ }
1378
+
1363
1379
  export interface LinkPreconnect extends Instant {
1364
1380
  name: Name.LINK_PRECONNECT;
1365
1381
  args: Args&{
@@ -3110,6 +3126,7 @@ export const enum Name {
3110
3126
  SELECTOR_STATS = 'SelectorStats',
3111
3127
  BEGIN_COMMIT_COMPOSITOR_FRAME = 'BeginCommitCompositorFrame',
3112
3128
  PARSE_META_VIEWPORT = 'ParseMetaViewport',
3129
+ META_CHARSET_CHECK = 'MetaCharsetCheck',
3113
3130
 
3114
3131
  /* Paint */
3115
3132
  SCROLL_LAYER = 'ScrollLayer',
@@ -50,6 +50,7 @@ import {
50
50
  import {isAiAssistancePatchingEnabled} from './PatchWidget.js';
51
51
 
52
52
  const {html} = Lit;
53
+ const {widget} = UI.Widget;
53
54
 
54
55
  const AI_ASSISTANCE_SEND_FEEDBACK = 'https://crbug.com/364805393' as Platform.DevToolsPath.UrlString;
55
56
  const AI_ASSISTANCE_HELP =
@@ -465,16 +466,12 @@ function defaultView(input: ViewInput, output: PanelViewOutput, target: HTMLElem
465
466
  ></devtools-ai-chat-view>`;
466
467
  }
467
468
  case ViewState.EXPLORE_VIEW:
468
- return html`<devtools-widget
469
- class="fill-panel"
470
- .widgetConfig=${UI.Widget.widgetConfig(ExploreWidget)}
471
- ></devtools-widget>`;
469
+ return html`<devtools-widget class="fill-panel" ${widget(ExploreWidget)}>
470
+ </devtools-widget>`;
472
471
 
473
472
  case ViewState.DISABLED_VIEW:
474
- return html`<devtools-widget
475
- class="fill-panel"
476
- .widgetConfig=${UI.Widget.widgetConfig(DisabledWidget, input.props)}
477
- ></devtools-widget>`;
473
+ return html`<devtools-widget class="fill-panel" ${widget(DisabledWidget, input.props)}>
474
+ </devtools-widget>`;
478
475
  }
479
476
  }
480
477
 
@@ -510,7 +507,7 @@ function defaultView(input: ViewInput, output: PanelViewOutput, target: HTMLElem
510
507
  </div>
511
508
  <div slot="sidebar" class="sidebar-view">
512
509
  ${shouldShowWalkthrough ? html`
513
- <devtools-widget .widgetConfig=${UI.Widget.widgetConfig(WalkthroughView, {
510
+ <devtools-widget ${widget(WalkthroughView, {
514
511
  message: input.props.walkthrough.activeMessage,
515
512
  isLoading: input.props.isLoading && walkthroughIsForLastMessage,
516
513
  markdownRenderer: input.props.markdownRenderer,
@@ -1699,8 +1696,14 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1699
1696
  parts: [],
1700
1697
  };
1701
1698
  this.#messages.push(systemMessage);
1702
- if (Greendev.Prototypes.instance().isEnabled('breakpointDebuggerAgent') &&
1703
- this.#conversation?.type === AiAssistanceModel.AiHistoryStorage.ConversationType.BREAKPOINT) {
1699
+ // If the walkthrough is currently expanded in the sidebar, we want to
1700
+ // automatically swap it to the newly created message's walkthrough.
1701
+ // This ensures that when a user asks a new question, the sidebar updates
1702
+ // immediately to show the "loading" state of the new walkthrough.
1703
+ const isSidebarWalkthroughOpen = this.#walkthrough.isExpanded && !this.#walkthrough.isInlined;
1704
+ if (isSidebarWalkthroughOpen ||
1705
+ (Greendev.Prototypes.instance().isEnabled('breakpointDebuggerAgent') &&
1706
+ this.#conversation?.type === AiAssistanceModel.AiHistoryStorage.ConversationType.BREAKPOINT)) {
1704
1707
  this.#openWalkthrough(systemMessage);
1705
1708
  }
1706
1709
  break;
@@ -1788,6 +1791,13 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1788
1791
  systemMessage.parts.push(newPart);
1789
1792
  }
1790
1793
 
1794
+ if (data.widgets && Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled) {
1795
+ systemMessage.parts.push({
1796
+ type: 'widget',
1797
+ widgets: data.widgets,
1798
+ });
1799
+ }
1800
+
1791
1801
  // When there is an answer without any thinking steps, we don't want to show the thinking step.
1792
1802
  // TODO(crbug.com/463323934): Remove specially handling this case.
1793
1803
  if (systemMessage.parts.length > 1) {
@@ -1881,7 +1891,7 @@ export function getResponseMarkdown(message: ModelChatMessage): string {
1881
1891
  for (const part of message.parts) {
1882
1892
  if (part.type === 'answer') {
1883
1893
  contentParts.push(`### Answer\n\n${part.text}`);
1884
- } else {
1894
+ } else if (part.type === 'step') {
1885
1895
  const step = part.step;
1886
1896
  if (step.title) {
1887
1897
  contentParts.push(`### ${step.title}`);
@@ -280,7 +280,7 @@ const DEFAULT_VIEW: View = (input, output, target) => {
280
280
 
281
281
  // clang-format off
282
282
  return html`<devtools-widget class="copy-to-prompt"
283
- .widgetConfig=${UI.Widget.widgetConfig(PanelCommon.CopyChangesToPrompt, {
283
+ ${widget(PanelCommon.CopyChangesToPrompt, {
284
284
  workspaceDiff: input.workspaceDiff,
285
285
  patchAgentCSSChange: changedCode,
286
286
  })}></devtools-widget>`;
@@ -13,7 +13,8 @@ import * as Root from '../../../core/root/root.js';
13
13
  import * as SDK from '../../../core/sdk/sdk.js';
14
14
  import type * as Protocol from '../../../generated/protocol.js';
15
15
  import type {
16
- AiWidget, ComputedStyleAiWidget, CoreVitalsAiWidget} from '../../../models/ai_assistance/agents/AiAgent.js';
16
+ AiWidget, ComputedStyleAiWidget, CoreVitalsAiWidget, DomTreeAiWidget, StylePropertiesAiWidget} from
17
+ '../../../models/ai_assistance/agents/AiAgent.js';
17
18
  import * as AiAssistanceModel from '../../../models/ai_assistance/ai_assistance.js';
18
19
  import * as ComputedStyle from '../../../models/computed_style/computed_style.js';
19
20
  import * as Marked from '../../../third_party/marked/marked.js';
@@ -101,6 +102,10 @@ const UIStringsNotTranslate = {
101
102
  * @description The error message when the LLM gets stuck in a loop (max steps reached).
102
103
  */
103
104
  maxStepsError: 'Seems like I am stuck with the investigation. It would be better if you start over.',
105
+ /**
106
+ * @description The error message when the LLM selects context from a different origin.
107
+ */
108
+ crossOriginError: 'I have selected the new context but you will have to start a new chat.',
104
109
  /**
105
110
  * @description Displayed when the user stop the response
106
111
  */
@@ -205,7 +210,12 @@ export interface StepPart {
205
210
  step: Step;
206
211
  }
207
212
 
208
- export type ModelMessagePart = AnswerPart|StepPart;
213
+ export interface WidgetPart {
214
+ type: 'widget';
215
+ widgets: AiWidget[];
216
+ }
217
+
218
+ export type ModelMessagePart = AnswerPart|StepPart|WidgetPart;
209
219
 
210
220
  export interface UserChatMessage {
211
221
  entity: ChatMessageEntity.USER;
@@ -327,6 +337,9 @@ export const DEFAULT_VIEW = (input: ChatMessageViewInput, output: ViewOutput, ta
327
337
  if (part.type === 'answer') {
328
338
  return html`<p>${renderTextAsMarkdown(part.text, input.markdownRenderer, { animate: !input.isReadOnly && input.isLoading && isLastPart && input.isLastMessage })}</p>`;
329
339
  }
340
+ if (part.type === 'widget') {
341
+ return html`${Lit.Directives.until(renderWidgets(part.widgets, {wrapperClass: 'main-widgets-wrapper'}))}`;
342
+ }
330
343
  if (!aiAssistanceV2 && part.type === 'step') {
331
344
  return renderStep({
332
345
  step: part.step,
@@ -602,7 +615,6 @@ export function renderStep({step, isLoading, markdownRenderer, isLast}: {
602
615
  markdownRenderer: MarkdownLitRenderer,
603
616
  isLast: boolean,
604
617
  }): Lit.LitTemplate {
605
- const shouldRenderWidgets = Boolean(step.widgets?.length && Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled);
606
618
  const stepClasses = Lit.Directives.classMap({
607
619
  step: true,
608
620
  empty: !step.thought && !step.code && !step.contextDetails && !step.requestApproval,
@@ -626,11 +638,8 @@ export function renderStep({step, isLoading, markdownRenderer, isLast}: {
626
638
  </summary>
627
639
  ${renderStepDetails({step, markdownRenderer, isLast})}
628
640
  </details>
629
- ${shouldRenderWidgets ? html`
630
- <div class="step-widgets-wrapper">
631
- ${Lit.Directives.until(renderStepWidgets(step))}
632
- </div>` : Lit.nothing
633
- }`;
641
+ ${Lit.Directives.until(renderWidgets(step.widgets, {wrapperClass: 'step-widgets-wrapper'}))}
642
+ `;
634
643
  // clang-format on
635
644
  }
636
645
 
@@ -639,55 +648,76 @@ interface WidgetMakerResponse {
639
648
  revealable: unknown;
640
649
  }
641
650
 
642
- const computedStyleNodeCache = new Map<Protocol.DOM.BackendNodeId, SDK.DOMModel.DOMNode>();
651
+ const nodeCache = new Map<Protocol.DOM.BackendNodeId, SDK.DOMModel.DOMNode>();
652
+
653
+ async function resolveNode(backendNodeId: Protocol.DOM.BackendNodeId): Promise<SDK.DOMModel.DOMNode|null> {
654
+ const cachedNode = nodeCache.get(backendNodeId);
655
+ if (cachedNode) {
656
+ return cachedNode;
657
+ }
658
+
659
+ const target = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
660
+ if (!target) {
661
+ return null;
662
+ }
663
+
664
+ const node = new SDK.DOMModel.DeferredDOMNode(target, backendNodeId);
665
+ const resolved = await node.resolvePromise();
666
+ if (resolved) {
667
+ nodeCache.set(backendNodeId, resolved);
668
+ }
669
+ return resolved;
670
+ }
643
671
 
644
672
  async function makeComputedStyleWidget(widgetData: ComputedStyleAiWidget): Promise<WidgetMakerResponse|null> {
645
- let domNodeForId = computedStyleNodeCache.get(widgetData.data.backendNodeId);
673
+ const domNodeForId = await resolveNode(widgetData.data.backendNodeId);
646
674
  if (!domNodeForId) {
647
- const target = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
648
- if (!target) {
649
- return null;
650
- }
651
- const node = new SDK.DOMModel.DeferredDOMNode(
652
- target,
653
- widgetData.data.backendNodeId,
654
- );
655
- const resolved = await node.resolvePromise();
656
- if (!resolved) {
657
- return null;
658
- }
659
- domNodeForId = resolved;
660
- computedStyleNodeCache.set(widgetData.data.backendNodeId, resolved);
675
+ return null;
661
676
  }
662
677
  const styles = new ComputedStyle.ComputedStyleModel.ComputedStyle(domNodeForId, widgetData.data.computedStyles);
663
678
 
664
- const widgetConfig = UI.Widget.widgetConfig(Elements.ComputedStyleWidget.ComputedStyleWidget, {
665
- nodeStyle: styles,
666
- matchedStyles: widgetData.data.matchedCascade,
667
- // This disables showing the nested traces and detailed information in the widget.
668
- propertyTraces: null,
669
- allowUserControl: false,
670
- filterText: new RegExp(widgetData.data.properties.join('|'), 'i')
671
- });
672
-
673
679
  // clang-format off
674
- const widget = html`<devtools-widget class="computed-styles-widget" .widgetConfig=${widgetConfig}></devtools-widget>`;
680
+ const renderedWidget = html`<devtools-widget
681
+ class="computed-styles-widget" ${widget(Elements.ComputedStyleWidget.ComputedStyleWidget, {
682
+ nodeStyle: styles,
683
+ matchedStyles: widgetData.data.matchedCascade,
684
+ // This disables showing the nested traces and detailed information in the widget.
685
+ propertyTraces: null,
686
+ allowUserControl: false,
687
+ filterText: new RegExp(widgetData.data.properties.join('|'), 'i')
688
+ })}></devtools-widget>`;
675
689
  // clang-format on
676
690
 
677
- return {renderedWidget: widget, revealable: new Elements.ElementsPanel.NodeComputedStyles(domNodeForId)};
691
+ return {renderedWidget, revealable: new Elements.ElementsPanel.NodeComputedStyles(domNodeForId)};
678
692
  }
679
693
 
680
694
  async function makeCoreVitalsWidget(widgetData: CoreVitalsAiWidget): Promise<WidgetMakerResponse|null> {
681
- const widgetConfig = UI.Widget.widgetConfig(TimelineComponents.CWVMetrics.CWVMetrics, {data: widgetData.data});
695
+ // clang-format off
696
+ const renderedWidget = html`<devtools-widget
697
+ class="core-vitals-widget" ${widget(TimelineComponents.CWVMetrics.CWVMetrics, {data: widgetData.data})}>
698
+ </devtools-widget>`;
699
+ // clang-format on
700
+
701
+ return {renderedWidget, revealable: new TimelineUtils.Helpers.RevealableCoreVitals(widgetData.data.insightSetKey)};
702
+ }
703
+
704
+ async function makeStylePropertiesWidget(widgetData: StylePropertiesAiWidget): Promise<WidgetMakerResponse|null> {
705
+ const domNodeForId = await resolveNode(widgetData.data.backendNodeId);
706
+ if (!domNodeForId) {
707
+ return null;
708
+ }
682
709
 
683
710
  // clang-format off
684
- const widget = html`<devtools-widget class="core-vitals-widget" .widgetConfig=${widgetConfig}></devtools-widget>`;
711
+ const renderedWidget = html`<devtools-widget
712
+ class="styling-preview-widget"
713
+ ${widget(Elements.StandaloneStylesContainer.StandaloneStylesContainer, {
714
+ domNode: domNodeForId,
715
+ filter: widgetData.data.selector ? new RegExp(widgetData.data.selector) : null,
716
+ })}>
717
+ </devtools-widget>`;
685
718
  // clang-format on
686
719
 
687
- return {
688
- renderedWidget: widget,
689
- revealable: new TimelineUtils.Helpers.RevealableCoreVitals(widgetData.data.insightSetKey)
690
- };
720
+ return {renderedWidget, revealable: domNodeForId};
691
721
  }
692
722
 
693
723
  function renderWidgetResponse(response: WidgetMakerResponse|null): Lit.LitTemplate {
@@ -720,39 +750,74 @@ function renderWidgetResponse(response: WidgetMakerResponse|null): Lit.LitTempla
720
750
  // clang-format on
721
751
  }
722
752
 
753
+ async function makeDomTreeWidget(widgetData: DomTreeAiWidget): Promise<WidgetMakerResponse|null> {
754
+ const root = widgetData.data.root;
755
+ if (!(root instanceof SDK.DOMModel.DOMNodeSnapshot)) {
756
+ return null;
757
+ }
758
+
759
+ const widgetConfig = UI.Widget.widgetConfig(Elements.ElementsTreeOutline.DOMTreeWidget, {
760
+ maxTreeDepth: 2,
761
+ enableContextMenu: false,
762
+ showComments: false,
763
+ showAIButton: false,
764
+ disableEdits: true,
765
+ expandRoot: true,
766
+ rootDOMNode: root,
767
+ visibleWidth: 400,
768
+ wrap: true,
769
+ });
770
+
771
+ // clang-format off
772
+ const widget = html`<devtools-widget class="dom-tree-widget" .widgetConfig=${widgetConfig}></devtools-widget>`;
773
+ // clang-format on
774
+
775
+ return {
776
+ renderedWidget: widget,
777
+ revealable: new SDK.DOMModel.DeferredDOMNode(root.domModel().target(), root.backendNodeId()),
778
+ };
779
+ }
780
+
723
781
  /**
724
- * Renders AI-defined UI widgets within a step.
725
- * When a ModelChatMessage contains a WidgetPart, the ChatMessage component
726
- * iterates through the `step.widgets` array. For each widget, it determines
727
- * the appropriate rendering logic based on the `widgetData.name`.
782
+ * Renders AI-defined UI widgets.
783
+ * When a ModelChatMessage contains a WidgetPart, or a Step has widgets,
784
+ * the ChatMessage component iterates through the \`widgets\` array.
785
+ * For each widget, it determines the appropriate rendering logic based on
786
+ * the \`widgetData.name\`.
728
787
  *
729
- * Currently, only 'COMPUTED_STYLES' and 'CORE_VITALS' widgets are supported. For these, the
730
- * `makeComputedStyleWidget` and `makeCoreVitalsWidget` functions are called to construct the necessary
731
- * data and configuration for the `Elements.ComputedStyleWidget.ComputedStyleWidget`
732
- * and `TimelineComponents.CWVMetrics.CWVMetrics`
733
- * components. The widget is then rendered using the `<devtools-widget>`
734
- * custom element, which dynamically instantiates and displays the specified
735
- * UI.Widget subclass with the provided configuration.
788
+ * Currently, 'COMPUTED_STYLES', 'CORE_VITALS' and 'STYLE_PROPERTIES' widgets are supported.
789
+ * For these, the corresponding \`make...Widget\` functions are called to construct the necessary
790
+ * data and configuration for the UI components. The widget is then rendered using the
791
+ * \`<devtools-widget>\` custom element, which dynamically instantiates and displays the
792
+ * specified UI.Widget subclass with the provided configuration.
736
793
  *
737
794
  * This allows for a flexible and extensible system where new widget types
738
795
  * can be added to the AI responses and rendered in DevTools by adding
739
- * corresponding `make...Widget` functions and handling them here.
796
+ * corresponding \`make...Widget\` functions and handling them here.
740
797
  */
741
- async function renderStepWidgets(step: Step): Promise<Lit.LitTemplate> {
742
- if (!step.widgets || step.widgets.length === 0) {
798
+ async function renderWidgets(
799
+ widgets: AiWidget[]|undefined, options: {wrapperClass?: string} = {}): Promise<Lit.LitTemplate> {
800
+ if (!Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled || !widgets || widgets.length === 0) {
743
801
  return Lit.nothing;
744
802
  }
745
- const ui = await Promise.all(step.widgets.map(async widgetData => {
803
+ const ui = await Promise.all(widgets.map(async widgetData => {
804
+ let response: WidgetMakerResponse|null = null;
746
805
  if (widgetData.name === 'COMPUTED_STYLES') {
747
- const response = await makeComputedStyleWidget(widgetData);
748
- return renderWidgetResponse(response);
749
- }
750
- if (widgetData.name === 'CORE_VITALS') {
751
- const response = await makeCoreVitalsWidget(widgetData);
752
- return renderWidgetResponse(response);
806
+ response = await makeComputedStyleWidget(widgetData);
807
+ } else if (widgetData.name === 'CORE_VITALS') {
808
+ response = await makeCoreVitalsWidget(widgetData);
809
+ } else if (widgetData.name === 'STYLE_PROPERTIES') {
810
+ response = await makeStylePropertiesWidget(widgetData);
811
+ } else if (widgetData.name === 'DOM_TREE') {
812
+ response = await makeDomTreeWidget(widgetData);
753
813
  }
754
- return Lit.nothing;
814
+ return renderWidgetResponse(response);
755
815
  }));
816
+
817
+ if (options.wrapperClass) {
818
+ return html`<div class=${options.wrapperClass}>${ui}</div>`;
819
+ }
820
+
756
821
  return html`${ui}`;
757
822
  }
758
823
 
@@ -807,6 +872,9 @@ function renderError(message: ModelChatMessage): Lit.LitTemplate {
807
872
  case AiAssistanceModel.AiAgent.ErrorType.MAX_STEPS:
808
873
  errorMessage = UIStringsNotTranslate.maxStepsError;
809
874
  break;
875
+ case AiAssistanceModel.AiAgent.ErrorType.CROSS_ORIGIN:
876
+ errorMessage = UIStringsNotTranslate.crossOriginError;
877
+ break;
810
878
  case AiAssistanceModel.AiAgent.ErrorType.ABORT:
811
879
  return html`<p class="aborted" jslog=${VisualLogging.section('aborted')}>${
812
880
  lockedString(UIStringsNotTranslate.stoppedResponse)}</p>`;
@@ -169,7 +169,7 @@ const DEFAULT_VIEW: View = (input, output, target) => {
169
169
  </div>
170
170
  </div>
171
171
  `}
172
- <devtools-widget class=${inputWidgetClasses} .widgetConfig=${UI.Widget.widgetConfig(ChatInput, {
172
+ <devtools-widget class=${inputWidgetClasses} ${widget(ChatInput, {
173
173
  isLoading: input.isLoading,
174
174
  blockedByCrossOrigin: input.blockedByCrossOrigin,
175
175
  isTextInputDisabled: input.isTextInputDisabled,
@@ -33,16 +33,21 @@ export class MarkdownRendererWithCodeBlock extends MarkdownView.MarkdownView.Mar
33
33
  }}>${Platform.StringUtilities.trimEndWithMaxLength(label, 100)}</devtools-link>`;
34
34
  }
35
35
 
36
- #renderLink(href: string): Lit.LitTemplate|null {
36
+ #renderLink(
37
+ href: string,
38
+ fallbackText: string,
39
+ ): Lit.LitTemplate|null {
37
40
  if (href.startsWith('#req-')) {
38
- const request =
39
- Logs.NetworkLog.NetworkLog.instance().requests().find(req => req.requestId() === href.substring(5));
41
+ const request = Logs.NetworkLog.NetworkLog.instance().requests().find(
42
+ req => req.requestId() === href.substring(5),
43
+ );
40
44
 
41
45
  if (request) {
42
46
  return this.#revealableLink(request, request.url());
43
47
  }
44
-
45
- } else if (href.startsWith('#file-')) {
48
+ return html`${fallbackText}`;
49
+ }
50
+ if (href.startsWith('#file-')) {
46
51
  const file = AiAssistanceModel.ContextSelectionAgent.ContextSelectionAgent.getUISourceCodes().find(
47
52
  file => AiAssistanceModel.ContextSelectionAgent.ContextSelectionAgent.uiSourceCodeId.get(file) ===
48
53
  Number(href.substring(6)));
@@ -50,13 +55,14 @@ export class MarkdownRendererWithCodeBlock extends MarkdownView.MarkdownView.Mar
50
55
  if (file) {
51
56
  return this.#revealableLink(file, file.name());
52
57
  }
58
+ return html`${fallbackText}`;
53
59
  }
54
60
  return null;
55
61
  }
56
62
 
57
63
  override templateForToken(token: Marked.Marked.MarkedToken): Lit.LitTemplate|null {
58
64
  if (token.type === 'link') {
59
- const link = this.#renderLink(token.href);
65
+ const link = this.#renderLink(token.href, token.text);
60
66
  if (link) {
61
67
  return link;
62
68
  }
@@ -73,9 +79,12 @@ export class MarkdownRendererWithCodeBlock extends MarkdownView.MarkdownView.Mar
73
79
  if (token.type === 'codespan') {
74
80
  // LLM likes outputting the link inside a codespan block.
75
81
  // Remove the codespan and render the link directly
76
- const matches = token.text.match(/^\[.*\]\((.+)\)$/);
77
- if (matches?.[1]) {
78
- const link = this.#renderLink(matches[1]);
82
+ const matches = token.text.match(/^\[(.*)\]\((.+)\)$/);
83
+ if (matches?.[2]) {
84
+ const link = this.#renderLink(
85
+ matches[2],
86
+ matches[1],
87
+ );
79
88
  if (link) {
80
89
  return link;
81
90
  }
@@ -354,6 +354,17 @@
354
354
  width: fit-content;
355
355
  }
356
356
 
357
+ .styling-preview-widget {
358
+ width: 100%;
359
+ min-height: 100px;
360
+ }
361
+
362
+ .main-widgets-wrapper {
363
+ display: flex;
364
+ flex-direction: column;
365
+ gap: var(--sys-size-5);
366
+ }
367
+
357
368
  .step-widgets-wrapper {
358
369
  width: fit-content;
359
370
  display: flex;