chrome-devtools-frontend 1.0.1581449 → 1.0.1581708

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 (54) hide show
  1. package/agents/prompts/merging-devtools-module.md +144 -0
  2. package/agents/prompts/ui-widgets.md +351 -0
  3. package/agents/prompts/verification.md +2 -1
  4. package/docs/contributing/README.md +5 -6
  5. package/docs/contributing/changes.md +1 -2
  6. package/docs/styleguide/ux/README.md +1 -1
  7. package/front_end/core/sdk/OverlayModel.ts +4 -2
  8. package/front_end/core/sdk/StorageKeyManager.ts +6 -1
  9. package/front_end/core/sdk/Target.ts +4 -2
  10. package/front_end/entrypoints/greendev_floaty/FloatyEntrypoint.ts +22 -16
  11. package/front_end/entrypoints/greendev_floaty/floaty.css +41 -1
  12. package/front_end/entrypoints/greendev_floaty/floaty.html +8 -1
  13. package/front_end/entrypoints/greendev_floaty/greendev_floaty.ts +4 -4
  14. package/front_end/entrypoints/node_app/app/NodeMain.ts +19 -1
  15. package/front_end/entrypoints/node_app/node_app.ts +34 -0
  16. package/front_end/models/ai_assistance/AiConversation.ts +10 -0
  17. package/front_end/models/ai_assistance/agents/AiAgent.ts +2 -0
  18. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +22 -0
  19. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +71 -1
  20. package/front_end/models/computed_style/ComputedStyleModel.ts +26 -0
  21. package/front_end/models/issues_manager/CookieIssue.ts +0 -28
  22. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +87 -6
  23. package/front_end/panels/ai_assistance/components/ChatInput.ts +1 -1
  24. package/front_end/panels/application/ApplicationPanelSidebar.ts +13 -11
  25. package/front_end/panels/application/DOMStorageModel.ts +1 -1
  26. package/front_end/panels/application/ResourcesPanel.ts +10 -5
  27. package/front_end/panels/common/AiCodeCompletionTeaser.ts +13 -3
  28. package/front_end/panels/common/aiCodeCompletionTeaser.css +6 -0
  29. package/front_end/panels/console_counters/WarningErrorCounter.ts +16 -10
  30. package/front_end/panels/elements/ComputedStyleWidget.ts +55 -37
  31. package/front_end/panels/elements/PlatformFontsWidget.ts +23 -10
  32. package/front_end/panels/greendev/GreenDevPanel.css +42 -1
  33. package/front_end/panels/greendev/GreenDevPanel.ts +30 -1
  34. package/front_end/panels/network/RequestInitiatorView.ts +8 -11
  35. package/front_end/panels/network/RequestTimingView.ts +1 -1
  36. package/front_end/panels/settings/KeybindsSettingsTab.ts +4 -3
  37. package/front_end/panels/sources/OutlineQuickOpen.ts +19 -0
  38. package/front_end/panels/timeline/TimelinePanel.ts +25 -0
  39. package/front_end/third_party/lighthouse/README.chromium +2 -2
  40. package/front_end/third_party/lighthouse/lighthouse-dt-bundle.js +145 -144
  41. package/front_end/third_party/lighthouse/report/bundle.js +12 -5
  42. package/front_end/third_party/lighthouse/report-assets/report-generator.mjs +2 -2
  43. package/front_end/ui/legacy/ListControl.ts +28 -1
  44. package/front_end/ui/legacy/Treeoutline.ts +1 -1
  45. package/front_end/ui/legacy/UIUtils.ts +17 -7
  46. package/front_end/ui/legacy/components/utils/JSPresentationUtils.ts +4 -2
  47. package/front_end/ui/visual_logging/KnownContextValues.ts +2 -0
  48. package/inspector_overlay/main.ts +18 -3
  49. package/inspector_overlay/tool_green_dev_anchors.css +54 -0
  50. package/inspector_overlay/tool_green_dev_anchors.ts +164 -0
  51. package/inspector_overlay/tool_persistent.ts +14 -0
  52. package/package.json +1 -1
  53. package/docs/contributing/design.md +0 -166
  54. package/docs/design_guidelines.md +0 -1
@@ -251,8 +251,48 @@ html, body {
251
251
  }
252
252
 
253
253
  .green-dev-floaty-disclaimer {
254
- font-size: 13px;
254
+ font-size: 16px;
255
255
  color: #666;
256
256
  text-align: center;
257
257
  margin-top: 8px;
258
+ position: relative; /* For tooltip positioning */
259
+ }
260
+
261
+ .disclaimer-link {
262
+ color: #666; /* Match text color */
263
+ text-decoration: underline;
264
+ cursor: pointer;
265
+ }
266
+
267
+ .disclaimer-tooltip {
268
+ display: none;
269
+ position: absolute;
270
+ bottom: 100%; /* Position above */
271
+ left: 50%;
272
+ transform: translateX(-50%);
273
+ width: 300px;
274
+ padding: 12px;
275
+ background-color: #fff;
276
+ border: 1px solid #ccc;
277
+ border-radius: 8px;
278
+ box-shadow: 0 2px 10px rgb(0 0 0 / 20%);
279
+ z-index: 1000;
280
+ text-align: left;
281
+ font-size: 14px;
282
+ line-height: 1.4;
283
+ color: #333;
284
+ }
285
+
286
+ .disclaimer-link:hover + .disclaimer-tooltip,
287
+ .disclaimer-tooltip:hover {
288
+ display: block;
289
+ }
290
+
291
+ .learn-more-link {
292
+ color: #0b57d0;
293
+ text-decoration: none;
294
+ }
295
+
296
+ .learn-more-link:hover {
297
+ text-decoration: underline;
258
298
  }
@@ -23,7 +23,14 @@
23
23
  <input type="text" class="green-dev-floaty-dialog-text-field" placeholder="Why is this not centered?">
24
24
  <button class="green-dev-floaty-dialog-play-button"></button>
25
25
  </div>
26
- <div class="green-dev-floaty-disclaimer">Relevant data is sent to Google</div>
26
+ <div class="green-dev-floaty-disclaimer">
27
+ <span class="disclaimer-link">Relevant data</span> is sent to Google
28
+ <div class="disclaimer-tooltip">
29
+ Chat messages and any data the inspected page can access via Web APIs are sent to Google and may be seen by human reviewers to improve this feature. This is an experimental AI feature and won't always get it right.
30
+ <br><br>
31
+ <a href="https://developer.chrome.com/docs/devtools/ai-assistance" class="learn-more-link">Learn about AI in DevTools</a>
32
+ </div>
33
+ </div>
27
34
  </div>
28
35
  </div>
29
36
  </div>
@@ -66,8 +66,8 @@ class GreenDevFloaty {
66
66
  // Refresh the anchor by re-sending the show command.
67
67
  const msg = JSON.stringify({
68
68
  id: 9999,
69
- method: 'Overlay.setShowGreenDevFloatyAnchor',
70
- params: {greenDevFloatyHighlightConfig: {backendNodeId: this.#backendNodeId}}
69
+ method: 'Overlay.setShowInspectedElementAnchor',
70
+ params: {inspectedElementAnchorConfig: {backendNodeId: this.#backendNodeId}}
71
71
  });
72
72
  Host.InspectorFrontendHost.InspectorFrontendHostInstance.sendMessageToBackend(msg);
73
73
  }
@@ -403,8 +403,8 @@ async function init(): Promise<void> {
403
403
  if (backendNodeId) {
404
404
  const msg = JSON.stringify({
405
405
  id: 9999,
406
- method: 'Overlay.setShowGreenDevFloatyAnchor',
407
- params: {greenDevFloatyHighlightConfig: {backendNodeId}}
406
+ method: 'Overlay.setShowInspectedElementAnchor',
407
+ params: {inspectedElementAnchorConfig: {backendNodeId}}
408
408
  });
409
409
  Host.InspectorFrontendHost.InspectorFrontendHostInstance.sendMessageToBackend(msg);
410
410
  }
@@ -107,7 +107,7 @@ export class NodeChildTargetManager extends SDK.SDKModel.SDKModel<void> implemen
107
107
  targetDestroyed(_event: Protocol.Target.TargetDestroyedEvent): void {
108
108
  }
109
109
 
110
- attachedToTarget({sessionId, targetInfo}: Protocol.Target.AttachedToTargetEvent): void {
110
+ async attachedToTarget({sessionId, targetInfo}: Protocol.Target.AttachedToTargetEvent): Promise<void> {
111
111
  let target: SDK.Target.Target;
112
112
  if (targetInfo.type === 'node_worker') {
113
113
  target = this.#targetManager.createTarget(
@@ -124,6 +124,24 @@ export class NodeChildTargetManager extends SDK.SDKModel.SDKModel<void> implemen
124
124
  }
125
125
  this.#childTargets.set(sessionId, target);
126
126
  void target.runtimeAgent().invoke_runIfWaitingForDebugger();
127
+ await this.#initializeStorage(target);
128
+ }
129
+
130
+ async #initializeStorage(target: SDK.Target.Target): Promise<void> {
131
+ const storageAgent = target.storageAgent();
132
+ const response = await storageAgent.invoke_getStorageKey({});
133
+
134
+ const storageKey = response.storageKey;
135
+ if (response.getError() || !storageKey) {
136
+ console.error(`Failed to get storage key for target ${target.id()}: ${response.getError()}`);
137
+ return;
138
+ }
139
+
140
+ const storageKeyManager = target.model(SDK.StorageKeyManager.StorageKeyManager);
141
+ if (storageKeyManager) {
142
+ storageKeyManager.setMainStorageKey(storageKey);
143
+ storageKeyManager.updateStorageKeys(new Set([storageKey]));
144
+ }
127
145
  }
128
146
 
129
147
  detachedFromTarget({sessionId}: Protocol.Target.DetachedFromTargetEvent): void {
@@ -9,6 +9,7 @@ import '../../panels/network/network-meta.js';
9
9
  import * as Common from '../../core/common/common.js';
10
10
  import * as i18n from '../../core/i18n/i18n.js';
11
11
  import * as Root from '../../core/root/root.js';
12
+ import type * as Resources from '../../panels/application/application.js';
12
13
  import type * as Sources from '../../panels/sources/sources.js';
13
14
  import * as UI from '../../ui/legacy/legacy.js';
14
15
  import * as Main from '../main/main.js';
@@ -39,6 +40,14 @@ const UIStrings = {
39
40
  * @description Command for showing the 'Node' tool in the Network Navigator View, which is part of the Sources tool
40
41
  */
41
42
  showNode: 'Show Node',
43
+ /**
44
+ * @description Text in Application Panel Sidebar of the Application panel
45
+ */
46
+ application: 'Application',
47
+ /**
48
+ * @description Command for showing the 'Application' tool
49
+ */
50
+ showApplication: 'Show Application',
42
51
  } as const;
43
52
 
44
53
  const str_ = i18n.i18n.registerUIStrings('entrypoints/node_app/node_app.ts', UIStrings);
@@ -78,6 +87,31 @@ UI.ViewManager.registerViewExtension({
78
87
  },
79
88
  });
80
89
 
90
+ let loadedResourcesModule: (typeof Resources|undefined);
91
+
92
+ async function loadResourcesModule(): Promise<typeof Resources> {
93
+ if (!loadedResourcesModule) {
94
+ loadedResourcesModule = await import('../../panels/application/application.js');
95
+ }
96
+ return loadedResourcesModule;
97
+ }
98
+
99
+ UI.ViewManager.registerViewExtension({
100
+ location: UI.ViewManager.ViewLocationValues.PANEL,
101
+ id: 'resources',
102
+ title: i18nLazyString(UIStrings.application),
103
+ commandPrompt: i18nLazyString(UIStrings.showApplication),
104
+ order: 70,
105
+ async loadView() {
106
+ const Resources = await loadResourcesModule();
107
+ return Resources.ResourcesPanel.ResourcesPanel.instance({
108
+ forceNew: true,
109
+ mode: 'node',
110
+ });
111
+ },
112
+ tags: [],
113
+ });
114
+
81
115
  // @ts-expect-error Exposed for legacy layout tests
82
116
  self.runtime = Root.Runtime.Runtime.instance({forceNew: true});
83
117
  Common.Runnable.registerEarlyInitializationRunnable(NodeMainImpl.instance);
@@ -55,6 +55,7 @@ export class AiConversation {
55
55
  undefined,
56
56
  undefined,
57
57
  serializedConversation.isExternal,
58
+ undefined,
58
59
  );
59
60
  }
60
61
 
@@ -74,6 +75,9 @@ export class AiConversation {
74
75
 
75
76
  #contexts: Array<ConversationContext<unknown>> = [];
76
77
 
78
+ #performanceRecordAndReload?: () => Promise<Trace.TraceModel.ParsedTrace>;
79
+ #onInspectElement?: () => Promise<SDK.DOMModel.DOMNode|null>;
80
+
77
81
  constructor(
78
82
  type: ConversationType,
79
83
  data: ResponseData[] = [],
@@ -82,9 +86,13 @@ export class AiConversation {
82
86
  aidaClient: Host.AidaClient.AidaClient = new Host.AidaClient.AidaClient(),
83
87
  changeManager?: ChangeManager,
84
88
  isExternal = false,
89
+ performanceRecordAndReload?: () => Promise<Trace.TraceModel.ParsedTrace>,
90
+ onInspectElement?: () => Promise<SDK.DOMModel.DOMNode|null>,
85
91
  ) {
86
92
  this.#changeManager = changeManager;
87
93
  this.#aidaClient = aidaClient;
94
+ this.#performanceRecordAndReload = performanceRecordAndReload;
95
+ this.#onInspectElement = onInspectElement;
88
96
 
89
97
  this.id = id;
90
98
  this.#isReadOnly = isReadOnly;
@@ -278,6 +286,8 @@ export class AiConversation {
278
286
  serverSideLoggingEnabled: isAiAssistanceServerSideLoggingEnabled(),
279
287
  sessionId: this.id,
280
288
  changeManager: this.#changeManager,
289
+ performanceRecordAndReload: this.#performanceRecordAndReload,
290
+ onInspectElement: this.#onInspectElement,
281
291
  };
282
292
  switch (type) {
283
293
  case ConversationType.STYLING: {
@@ -4,6 +4,7 @@
4
4
 
5
5
  import * as Host from '../../../core/host/host.js';
6
6
  import * as Root from '../../../core/root/root.js';
7
+ import type * as SDK from '../../../core/sdk/sdk.js';
7
8
  import {debugLog, isStructuredLogEnabled} from '../debug.js';
8
9
 
9
10
  export const enum ResponseType {
@@ -133,6 +134,7 @@ export interface AgentOptions {
133
134
  serverSideLoggingEnabled?: boolean;
134
135
  sessionId?: string;
135
136
  confirmSideEffectForTest?: typeof Promise.withResolvers;
137
+ onInspectElement?: () => Promise<SDK.DOMModel.DOMNode|null>;
136
138
  }
137
139
 
138
140
  export interface ParsedAnswer {
@@ -88,6 +88,28 @@ Content:
88
88
  }
89
89
  }
90
90
  }
91
+ },
92
+ {
93
+ "name": "performanceRecordAndReload",
94
+ "description": "Start a new performance recording and reload the page.",
95
+ "parameters": {
96
+ "type": 6,
97
+ "description": "",
98
+ "nullable": true,
99
+ "required": [],
100
+ "properties": {}
101
+ }
102
+ },
103
+ {
104
+ "name": "inspectDom",
105
+ "description": "Prompts user to select a DOM element from the page.",
106
+ "parameters": {
107
+ "type": 6,
108
+ "description": "",
109
+ "nullable": true,
110
+ "required": [],
111
+ "properties": {}
112
+ }
91
113
  }
92
114
  ],
93
115
  "options": {},
@@ -9,7 +9,9 @@ import * as Platform from '../../../core/platform/platform.js';
9
9
  import * as Root from '../../../core/root/root.js';
10
10
  import * as SDK from '../../../core/sdk/sdk.js';
11
11
  import * as Logs from '../../logs/logs.js';
12
+ import type * as Trace from '../../trace/trace.js';
12
13
  import * as Workspace from '../../workspace/workspace.js';
14
+ import {AgentFocus} from '../performance/AIContext.js';
13
15
 
14
16
  import {
15
17
  type AgentOptions,
@@ -71,8 +73,16 @@ export class ContextSelectionAgent extends AiAgent<never> {
71
73
  };
72
74
  }
73
75
 
74
- constructor(opts: AgentOptions) {
76
+ readonly #performanceRecordAndReload?: () => Promise<Trace.TraceModel.ParsedTrace>;
77
+ readonly #onInspectElement?: () => Promise<SDK.DOMModel.DOMNode|null>;
78
+
79
+ constructor(opts: AgentOptions&{
80
+ performanceRecordAndReload?: () => Promise<Trace.TraceModel.ParsedTrace>,
81
+ onInspectElement?: () => Promise<SDK.DOMModel.DOMNode|null>,
82
+ }) {
75
83
  super(opts);
84
+ this.#performanceRecordAndReload = opts.performanceRecordAndReload;
85
+ this.#onInspectElement = opts.onInspectElement;
76
86
 
77
87
  this.declareFunction<Record<string, never>>('listNetworkRequests', {
78
88
  description: `Gives a list of network requests including URL, status code, and duration in ms`,
@@ -213,6 +223,66 @@ export class ContextSelectionAgent extends AiAgent<never> {
213
223
  return {error: 'Unable to find file.'};
214
224
  },
215
225
  });
226
+
227
+ this.declareFunction('performanceRecordAndReload', {
228
+ description: 'Start a new performance recording and reload the page.',
229
+ parameters: {
230
+ type: Host.AidaClient.ParametersTypes.OBJECT,
231
+ description: '',
232
+ nullable: true,
233
+ required: [],
234
+ properties: {},
235
+ },
236
+ displayInfoFromArgs: () => {
237
+ return {
238
+ title: 'Recording a performance trace…',
239
+ action: 'performanceRecordAndReload()',
240
+ };
241
+ },
242
+ handler: async () => {
243
+ if (!this.#performanceRecordAndReload) {
244
+ return {
245
+ error: 'Performance recording is not available.',
246
+ };
247
+ }
248
+ const result = await this.#performanceRecordAndReload();
249
+
250
+ return {
251
+ context: AgentFocus.fromParsedTrace(result),
252
+ };
253
+ }
254
+ });
255
+
256
+ this.declareFunction<Record<string, never>>('inspectDom', {
257
+ description: `Prompts user to select a DOM element from the page.`,
258
+ parameters: {
259
+ type: Host.AidaClient.ParametersTypes.OBJECT,
260
+ description: '',
261
+ nullable: true,
262
+ required: [],
263
+ properties: {},
264
+ },
265
+ displayInfoFromArgs: () => {
266
+ return {
267
+ title: lockedString('Please select an element on the page...'),
268
+ action: 'selectElement()',
269
+ };
270
+ },
271
+ handler: async () => {
272
+ if (!this.#onInspectElement) {
273
+ return {error: 'The inspect element action is not available.'};
274
+ }
275
+ const node = await this.#onInspectElement();
276
+ if (node) {
277
+ return {
278
+ context: node,
279
+ };
280
+ }
281
+ return {
282
+ error: 'Unable to select element.',
283
+ };
284
+ },
285
+ });
216
286
  }
217
287
 
218
288
  #getUISourceCodes = (): Iterable<Workspace.UISourceCode.UISourceCode> => {
@@ -137,6 +137,32 @@ export class ComputedStyleModel extends Common.ObjectWrapper.ObjectWrapper<Event
137
137
  null as ComputedStyle | null;
138
138
  }
139
139
  }
140
+
141
+ private async fetchMatchedCascade(): Promise<SDK.CSSMatchedStyles.CSSMatchedStyles|null> {
142
+ const node = this.node;
143
+ if (!node || !this.cssModel()) {
144
+ return null;
145
+ }
146
+
147
+ const cssModel = this.cssModel();
148
+ if (!cssModel) {
149
+ return null;
150
+ }
151
+
152
+ const matchedStyles = await cssModel.cachedMatchedCascadeForNode(node);
153
+ if (!matchedStyles) {
154
+ return null;
155
+ }
156
+ return matchedStyles.node() === this.node ? matchedStyles : null;
157
+ }
158
+
159
+ async fetchAllComputedStyleInfo(): Promise<{
160
+ computedStyle: ComputedStyle | null,
161
+ matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles|null,
162
+ }> {
163
+ const [computedStyle, matchedStyles] = await Promise.all([this.fetchComputedStyle(), this.fetchMatchedCascade()]);
164
+ return {computedStyle, matchedStyles};
165
+ }
140
166
  }
141
167
 
142
168
  export const enum Events {
@@ -8,7 +8,6 @@ import * as i18n from '../../core/i18n/i18n.js';
8
8
  import type * as Platform from '../../core/platform/platform.js';
9
9
  import * as SDK from '../../core/sdk/sdk.js';
10
10
  import * as Protocol from '../../generated/protocol.js';
11
- import * as ThirdPartyWeb from '../../third_party/third-party-web/third-party-web.js';
12
11
 
13
12
  import {Issue, IssueCategory, IssueKind} from './Issue.js';
14
13
  import {
@@ -55,15 +54,6 @@ export const enum CookieStatus {
55
54
  ALLOWED_BY_HEURISTICS = 3,
56
55
  }
57
56
 
58
- export interface CookieReportInfo {
59
- name: string;
60
- domain: string;
61
- type?: string;
62
- platform?: string;
63
- status: CookieStatus;
64
- insight?: Protocol.Audits.CookieIssueInsight;
65
- }
66
-
67
57
  export class CookieIssue extends Issue<Protocol.Audits.CookieIssueDetails> {
68
58
  cookieId(): string {
69
59
  const details = this.details();
@@ -246,24 +236,6 @@ export class CookieIssue extends Issue<Protocol.Audits.CookieIssueDetails> {
246
236
  return IssueKind.BREAKING_CHANGE;
247
237
  }
248
238
 
249
- makeCookieReportEntry(): CookieReportInfo|undefined {
250
- const status = CookieIssue.getCookieStatus(this.details());
251
- const details = this.details();
252
- if (details.cookie && details.cookieUrl && status !== undefined) {
253
- const entity = ThirdPartyWeb.ThirdPartyWeb.getEntity(details.cookieUrl);
254
- return {
255
- name: details.cookie.name,
256
- domain: details.cookie.domain,
257
- type: entity?.category,
258
- platform: entity?.name,
259
- status,
260
- insight: this.details().insight,
261
- };
262
- }
263
-
264
- return;
265
- }
266
-
267
239
  static getCookieStatus(cookieIssueDetails: Protocol.Audits.CookieIssueDetails): CookieStatus|undefined {
268
240
  if (cookieIssueDetails.cookieExclusionReasons.includes(
269
241
  Protocol.Audits.CookieExclusionReason.ExcludeThirdPartyPhaseout)) {
@@ -15,6 +15,7 @@ import * as Annotations from '../../models/annotations/annotations.js';
15
15
  import * as Badges from '../../models/badges/badges.js';
16
16
  import * as GreenDev from '../../models/greendev/greendev.js';
17
17
  import * as TextUtils from '../../models/text_utils/text_utils.js';
18
+ import type * as Trace from '../../models/trace/trace.js';
18
19
  import * as Workspace from '../../models/workspace/workspace.js';
19
20
  import * as Buttons from '../../ui/components/buttons/buttons.js';
20
21
  import * as Snackbars from '../../ui/components/snackbars/snackbars.js';
@@ -503,6 +504,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
503
504
  #selectedRequest: AiAssistanceModel.NetworkAgent.RequestContext|null = null;
504
505
  // Messages displayed in the `ChatView` component.
505
506
  #messages: Message[] = [];
507
+ #isContextAutoSelectionSuspended = false;
506
508
 
507
509
  // Whether the UI should show loading or not.
508
510
  #isLoading = false;
@@ -672,6 +674,10 @@ export class AiAssistancePanel extends UI.Panel.Panel {
672
674
  this.requestUpdate();
673
675
  }
674
676
 
677
+ async #handlePerformanceRecordAndReload(): Promise<Trace.TraceModel.ParsedTrace> {
678
+ return await TimelinePanel.TimelinePanel.TimelinePanel.executeRecordAndReload();
679
+ }
680
+
675
681
  #getDefaultConversationType(): AiAssistanceModel.AiHistoryStorage.ConversationType|undefined {
676
682
  const {hostConfig} = Root.Runtime;
677
683
  const viewManager = UI.ViewManager.ViewManager.instance();
@@ -715,7 +721,8 @@ export class AiAssistancePanel extends UI.Panel.Panel {
715
721
  }
716
722
  const conversation = targetConversationType ?
717
723
  new AiAssistanceModel.AiConversation.AiConversation(
718
- targetConversationType, [], undefined, false, this.#aidaClient, this.#changeManager) :
724
+ targetConversationType, [], undefined, false, this.#aidaClient, this.#changeManager, false,
725
+ this.#handlePerformanceRecordAndReload.bind(this), this.#handleInspectElement.bind(this)) :
719
726
  undefined;
720
727
 
721
728
  this.#updateConversationState(conversation);
@@ -735,16 +742,28 @@ export class AiAssistancePanel extends UI.Panel.Panel {
735
742
  const conversationType = this.#getDefaultConversationType();
736
743
  if (conversationType) {
737
744
  conversation = new AiAssistanceModel.AiConversation.AiConversation(
738
- conversationType, [], undefined, false, this.#aidaClient, this.#changeManager);
745
+ conversationType,
746
+ [],
747
+ undefined,
748
+ false,
749
+ this.#aidaClient,
750
+ this.#changeManager,
751
+ false,
752
+ this.#handlePerformanceRecordAndReload.bind(this),
753
+ this.#handleInspectElement.bind(this),
754
+ );
739
755
  }
740
756
  }
741
757
 
742
758
  this.#conversation = conversation;
759
+ this.#isContextAutoSelectionSuspended = false;
743
760
  }
744
761
 
745
- this.#conversation?.setContext(this.#getConversationContext(
746
- isAiAssistanceContextSelectionAgentEnabled() ? this.#getDefaultConversationType() :
747
- (this.#conversation?.type ?? null)));
762
+ if (!this.#isContextAutoSelectionSuspended) {
763
+ this.#conversation?.setContext(this.#getConversationContext(
764
+ isAiAssistanceContextSelectionAgentEnabled() ? this.#getDefaultConversationType() :
765
+ (this.#conversation?.type ?? null)));
766
+ }
748
767
 
749
768
  this.requestUpdate();
750
769
  }
@@ -1116,10 +1135,12 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1116
1135
 
1117
1136
  #handleContextRemoved(): void {
1118
1137
  this.#conversation?.setContext(null);
1138
+ this.#isContextAutoSelectionSuspended = true;
1119
1139
  this.requestUpdate();
1120
1140
  }
1121
1141
 
1122
1142
  #handleContextAdd(): void {
1143
+ this.#isContextAutoSelectionSuspended = false;
1123
1144
  this.#conversation?.setContext(this.#getConversationContext(this.#getDefaultConversationType()));
1124
1145
  this.requestUpdate();
1125
1146
  }
@@ -1141,6 +1162,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1141
1162
  return;
1142
1163
  }
1143
1164
 
1165
+ this.#isContextAutoSelectionSuspended = false;
1144
1166
  let targetConversationType: AiAssistanceModel.AiHistoryStorage.ConversationType|undefined;
1145
1167
  switch (actionId) {
1146
1168
  case 'freestyler.elements-floating-button': {
@@ -1187,7 +1209,16 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1187
1209
  let conversation = this.#conversation;
1188
1210
  if (!this.#conversation || this.#conversation.type !== targetConversationType || this.#conversation.isEmpty) {
1189
1211
  conversation = new AiAssistanceModel.AiConversation.AiConversation(
1190
- targetConversationType, [], undefined, false, this.#aidaClient, this.#changeManager);
1212
+ targetConversationType,
1213
+ [],
1214
+ undefined,
1215
+ false,
1216
+ this.#aidaClient,
1217
+ this.#changeManager,
1218
+ false,
1219
+ this.#handlePerformanceRecordAndReload.bind(this),
1220
+ this.#handleInspectElement.bind(this),
1221
+ );
1191
1222
  }
1192
1223
  this.#updateConversationState(conversation);
1193
1224
  const predefinedPrompt = opts?.['prompt'];
@@ -1334,11 +1365,61 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1334
1365
  this.#conversation?.setContext(context);
1335
1366
  }
1336
1367
 
1368
+ this.#isContextAutoSelectionSuspended = false;
1369
+
1337
1370
  void VisualLogging.logFunctionCall(`context-change-${this.#conversation?.type}`);
1338
1371
 
1339
1372
  this.requestUpdate();
1340
1373
  };
1341
1374
 
1375
+ async #handleInspectElement(): Promise<SDK.DOMModel.DOMNode|null> {
1376
+ if (!this.#toggleSearchElementAction) {
1377
+ return null;
1378
+ }
1379
+
1380
+ const result = new Promise<SDK.DOMModel.DOMNode|null>(resolve => {
1381
+ // Track the new flavor change for dom node.
1382
+ const handleDOMNodeFlavorChange = (ev: Common.EventTarget.EventTargetEvent<SDK.DOMModel.DOMNode>): void => {
1383
+ if (!ev.data) {
1384
+ return;
1385
+ }
1386
+ resolve(selectedElementFilter(ev.data));
1387
+ removeListeners();
1388
+ };
1389
+
1390
+ // If the inspect mode is toggled, we want to resolve null.
1391
+ const handleInspectModeToggled = (ev: Common.EventTarget.EventTargetEvent<boolean>): void => {
1392
+ if (!ev.data) {
1393
+ // The inspect element is toggled off
1394
+ // before the flavor change event fires
1395
+ // so we need to wait a bit to see if the flavor changed.
1396
+ window.setTimeout(() => {
1397
+ resolve((selectedElementFilter(UI.Context.Context.instance().flavor(SDK.DOMModel.DOMNode))));
1398
+ removeListeners();
1399
+ }, 50);
1400
+ }
1401
+ };
1402
+
1403
+ const removeListeners = (): void => {
1404
+ UI.Context.Context.instance().removeFlavorChangeListener(SDK.DOMModel.DOMNode, handleDOMNodeFlavorChange);
1405
+ this.#toggleSearchElementAction?.removeEventListener(
1406
+ UI.ActionRegistration.Events.TOGGLED, handleInspectModeToggled);
1407
+ };
1408
+
1409
+ UI.Context.Context.instance().addFlavorChangeListener(SDK.DOMModel.DOMNode, handleDOMNodeFlavorChange);
1410
+ this.#toggleSearchElementAction?.addEventListener(UI.ActionRegistration.Events.TOGGLED, handleInspectModeToggled);
1411
+
1412
+ // Clean-up listeners in case of abort.
1413
+ this.#runAbortController.signal.addEventListener('abort', () => {
1414
+ resolve(null);
1415
+ removeListeners();
1416
+ }, {once: true});
1417
+ });
1418
+
1419
+ void this.#toggleSearchElementAction.execute();
1420
+ return await result;
1421
+ }
1422
+
1342
1423
  async #startConversation(
1343
1424
  text: string,
1344
1425
  imageInput?: Host.AidaClient.Part,
@@ -392,7 +392,7 @@ export const
392
392
  class="add-context"
393
393
  .iconName=${'plus'}
394
394
  .size=${Buttons.Button.Size.SMALL}
395
- .jslogContext=${'context-add'}
395
+ .jslogContext=${'context-added'}
396
396
  .variant=${Buttons.Button.Variant.ICON}
397
397
  @click=${input.onContextAdd}></devtools-button>` : Lit.nothing}
398
398
  </div>
@@ -1916,17 +1916,19 @@ export class ResourcesSection implements SDK.TargetManager.Observer {
1916
1916
  frameManager.addEventListener(
1917
1917
  SDK.FrameManager.Events.RESOURCE_ADDED, event => this.resourceAdded(event.data.resource), this);
1918
1918
 
1919
- SDK.TargetManager.TargetManager.instance().addModelListener(
1920
- SDK.ChildTargetManager.ChildTargetManager, SDK.ChildTargetManager.Events.TARGET_CREATED, this.windowOpened,
1921
- this, {scoped: true});
1922
- SDK.TargetManager.TargetManager.instance().addModelListener(
1923
- SDK.ChildTargetManager.ChildTargetManager, SDK.ChildTargetManager.Events.TARGET_INFO_CHANGED,
1924
- this.windowChanged, this, {scoped: true});
1925
- SDK.TargetManager.TargetManager.instance().addModelListener(
1926
- SDK.ChildTargetManager.ChildTargetManager, SDK.ChildTargetManager.Events.TARGET_DESTROYED, this.windowDestroyed,
1927
- this, {scoped: true});
1928
-
1929
- SDK.TargetManager.TargetManager.instance().observeTargets(this, {scoped: true});
1919
+ if (this.panel.mode !== 'node') {
1920
+ SDK.TargetManager.TargetManager.instance().addModelListener(
1921
+ SDK.ChildTargetManager.ChildTargetManager, SDK.ChildTargetManager.Events.TARGET_CREATED, this.windowOpened,
1922
+ this, {scoped: true});
1923
+ SDK.TargetManager.TargetManager.instance().addModelListener(
1924
+ SDK.ChildTargetManager.ChildTargetManager, SDK.ChildTargetManager.Events.TARGET_INFO_CHANGED,
1925
+ this.windowChanged, this, {scoped: true});
1926
+ SDK.TargetManager.TargetManager.instance().addModelListener(
1927
+ SDK.ChildTargetManager.ChildTargetManager, SDK.ChildTargetManager.Events.TARGET_DESTROYED,
1928
+ this.windowDestroyed, this, {scoped: true});
1929
+
1930
+ SDK.TargetManager.TargetManager.instance().observeTargets(this, {scoped: true});
1931
+ }
1930
1932
  }
1931
1933
 
1932
1934
  private initialize(): void {
@@ -250,7 +250,7 @@ export class DOMStorageModel extends SDK.SDKModel.SDKModel<EventTypes> {
250
250
  }
251
251
  }
252
252
 
253
- SDK.SDKModel.SDKModel.register(DOMStorageModel, {capabilities: SDK.Target.Capability.DOM, autostart: false});
253
+ SDK.SDKModel.SDKModel.register(DOMStorageModel, {capabilities: SDK.Target.Capability.DOM_STORAGE, autostart: false});
254
254
 
255
255
  export const enum Events {
256
256
  DOM_STORAGE_ADDED = 'DOMStorageAdded',