chrome-devtools-frontend 1.0.1696961 → 1.0.1697595

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 (94) hide show
  1. package/.geminiignore +0 -1
  2. package/front_end/core/common/Color.ts +5 -2
  3. package/front_end/core/host/InspectorFrontendHostAPI.ts +16 -3
  4. package/front_end/core/host/InspectorFrontendHostStub.ts +3 -2
  5. package/front_end/core/host/ResourceLoader.ts +1 -2
  6. package/front_end/core/platform/api/HostRuntime.ts +1 -0
  7. package/front_end/core/platform/browser/HostRuntime.ts +4 -0
  8. package/front_end/core/platform/node/HostRuntime.ts +5 -0
  9. package/front_end/core/root/Runtime.ts +5 -0
  10. package/front_end/core/sdk/DOMModel.ts +13 -0
  11. package/front_end/core/sdk/SourceMapScopeChainEntry.ts +9 -6
  12. package/front_end/core/sdk/SourceMapScopesInfo.ts +3 -1
  13. package/front_end/foundation/Universe.ts +8 -0
  14. package/front_end/generated/InspectorBackendCommands.ts +3 -0
  15. package/front_end/generated/protocol-mapping.d.ts +19 -0
  16. package/front_end/generated/protocol-proxy-api.d.ts +15 -0
  17. package/front_end/generated/protocol.ts +35 -0
  18. package/front_end/models/ai_assistance/AiAgent2.ts +20 -18
  19. package/front_end/models/ai_assistance/AiConversation.ts +38 -12
  20. package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +9 -2
  21. package/front_end/models/ai_assistance/agents/StylingAgent.ts +13 -4
  22. package/front_end/models/ai_assistance/tools/CookieUtils.ts +10 -8
  23. package/front_end/models/ai_assistance/tools/ExecuteJavaScript.ts +1 -1
  24. package/front_end/models/ai_assistance/tools/GetElementAccessibilityDetails.ts +1 -3
  25. package/front_end/models/ai_assistance/tools/GetNetworkRequestDetails.ts +9 -8
  26. package/front_end/models/ai_assistance/tools/GetSourceContent.ts +5 -6
  27. package/front_end/models/ai_assistance/tools/GetStorageValues.ts +6 -25
  28. package/front_end/models/ai_assistance/tools/GetStyles.ts +1 -3
  29. package/front_end/models/ai_assistance/tools/ListNetworkRequests.ts +7 -6
  30. package/front_end/models/ai_assistance/tools/ListPageOrigins.ts +8 -5
  31. package/front_end/models/ai_assistance/tools/ListSources.ts +8 -10
  32. package/front_end/models/ai_assistance/tools/ListStorageKeys.ts +6 -25
  33. package/front_end/models/ai_assistance/tools/README.md +5 -3
  34. package/front_end/models/ai_assistance/tools/ResolveDevtoolsNodePath.ts +1 -2
  35. package/front_end/models/ai_assistance/tools/Tool.ts +52 -14
  36. package/front_end/models/change_tracker/ChangeTracker.ts +89 -0
  37. package/front_end/models/change_tracker/change_tracker.ts +9 -0
  38. package/front_end/models/issues_manager/MarkdownIssueDescription.ts +2 -2
  39. package/front_end/models/live-metrics/LiveMetrics.ts +1 -2
  40. package/front_end/models/trace/ModelImpl.ts +0 -6
  41. package/front_end/models/trace/Processor.ts +4 -7
  42. package/front_end/models/trace/helpers/Timing.ts +2 -1
  43. package/front_end/models/trace/types/Overlays.ts +12 -1
  44. package/front_end/panels/accessibility/AccessibilityAnnouncementRecordingView.ts +67 -19
  45. package/front_end/panels/application/IndexedDBViews.ts +1 -1
  46. package/front_end/panels/elements/DOMTreeContextMenu.ts +16 -23
  47. package/front_end/panels/elements/ElementsTreeElement.ts +286 -59
  48. package/front_end/panels/elements/ElementsTreeOutline.ts +273 -42
  49. package/front_end/panels/network/NetworkPanel.ts +137 -52
  50. package/front_end/panels/network/RequestHeadersView.ts +65 -32
  51. package/front_end/panels/network/components/RequestHeadersView.css +7 -1
  52. package/front_end/panels/network/forward/BackendLinking.ts +49 -0
  53. package/front_end/panels/network/forward/forward.ts +2 -0
  54. package/front_end/panels/settings/BackendLinkingSettingsTab.ts +303 -0
  55. package/front_end/panels/settings/backendLinkingSettingsTab.css +78 -0
  56. package/front_end/panels/settings/settings-meta.ts +25 -0
  57. package/front_end/panels/settings/settings.ts +2 -0
  58. package/front_end/panels/timeline/README.md +3 -2
  59. package/front_end/panels/timeline/TimelineFlameChartView.ts +23 -2
  60. package/front_end/panels/timeline/overlays/OverlaysImpl.ts +64 -4
  61. package/front_end/panels/timeline/timelineFlameChartView.css +47 -0
  62. package/front_end/panels/whats_new/ReleaseNoteText.ts +8 -8
  63. package/front_end/panels/whats_new/resources/WNDT.md +6 -6
  64. package/front_end/third_party/chromium/README.chromium +1 -1
  65. package/front_end/third_party/puppeteer/README.chromium +2 -2
  66. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.d.ts +6 -0
  67. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +5 -8
  68. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Coverage.d.ts +6 -0
  69. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Coverage.d.ts.map +1 -1
  70. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Coverage.js.map +1 -1
  71. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/ScreenRecording.d.ts.map +1 -1
  72. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/ScreenRecording.js +1 -5
  73. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/ScreenRecording.js.map +1 -1
  74. package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.d.ts +3 -3
  75. package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.js +3 -3
  76. package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.js.map +1 -1
  77. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.d.ts +1 -1
  78. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.js +1 -1
  79. package/front_end/third_party/puppeteer/package/lib/types.d.ts +6 -0
  80. package/front_end/third_party/puppeteer/package/package.json +2 -2
  81. package/front_end/third_party/puppeteer/package/src/cdp/Coverage.ts +6 -0
  82. package/front_end/third_party/puppeteer/package/src/cdp/ScreenRecording.ts +3 -7
  83. package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
  84. package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
  85. package/front_end/third_party/web-vitals/README.chromium +2 -2
  86. package/front_end/third_party/web-vitals/package/dist/modules/attribution/onINP.js +8 -5
  87. package/front_end/third_party/web-vitals/package/package.json +1 -1
  88. package/front_end/third_party/web-vitals/package/src/attribution/onINP.ts +8 -4
  89. package/front_end/third_party/web-vitals/rebuild.sh +2 -2
  90. package/front_end/ui/legacy/ContextMenu.ts +19 -0
  91. package/front_end/ui/legacy/SoftContextMenu.ts +6 -0
  92. package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +62 -22
  93. package/front_end/ui/visual_logging/KnownContextValues.ts +4 -0
  94. package/package.json +5 -4
package/.geminiignore CHANGED
@@ -2,5 +2,4 @@ node_modules/
2
2
  /third_party/
3
3
  front_end/core/i18n/locales/*.json
4
4
  front_end/third_party/lighthouse/locales/*.json
5
- front_end/third_party/puppeteer/package/lib/cjs/
6
5
  front_end/panels/timeline/fixtures/traces/
@@ -613,12 +613,15 @@ export function findFgColorForContrastAPCA(fgColor: Legacy, bgColor: Legacy, req
613
613
  const fgIsLighter = fgLuminance >= bgLuminance;
614
614
  const desiredLuminance = desiredLuminanceAPCA(bgLuminance, requiredContrast, fgIsLighter);
615
615
 
616
+ const meetsRequiredContrast = (candidate: Legacy): boolean =>
617
+ Math.round(Math.abs(contrastRatioAPCA(candidate.rgba(), bgColor.rgba()))) >= requiredContrast;
618
+
616
619
  const saturationComponentIndex = 1;
617
620
  const valueComponentIndex = 2;
618
621
 
619
622
  if (approachColorValue(candidateHSVA, valueComponentIndex, desiredLuminance, candidateLuminance)) {
620
623
  const candidate = Legacy.fromHSVA(candidateHSVA);
621
- if (Math.abs(contrastRatioAPCA(bgColor.rgba(), candidate.rgba())) >= requiredContrast) {
624
+ if (meetsRequiredContrast(candidate)) {
622
625
  return candidate;
623
626
  }
624
627
  }
@@ -626,7 +629,7 @@ export function findFgColorForContrastAPCA(fgColor: Legacy, bgColor: Legacy, req
626
629
  candidateHSVA[valueComponentIndex] = 1;
627
630
  if (approachColorValue(candidateHSVA, saturationComponentIndex, desiredLuminance, candidateLuminance)) {
628
631
  const candidate = Legacy.fromHSVA(candidateHSVA);
629
- if (Math.abs(contrastRatioAPCA(bgColor.rgba(), candidate.rgba())) >= requiredContrast) {
632
+ if (meetsRequiredContrast(candidate)) {
630
633
  return candidate;
631
634
  }
632
635
  }
@@ -6,6 +6,11 @@ import type * as Platform from '../../core/platform/platform.js';
6
6
  import type * as Common from '../common/common.js';
7
7
  import type * as Root from '../root/root.js';
8
8
 
9
+ declare global {
10
+ // eslint-disable-next-line @typescript-eslint/naming-convention
11
+ var InspectorFrontendHost: InspectorFrontendHostAPI;
12
+ }
13
+
9
14
  /**
10
15
  * This values should match the one getting called from Chromium
11
16
  */
@@ -227,6 +232,14 @@ export interface FunctionCallEvent {
227
232
  * Please note that the "dispatch" side can't be type-checked as the dispatch is
228
233
  * done dynamically.
229
234
  **/
235
+ export interface DevicesDiscoveryConfig {
236
+ discoverUsbDevices: boolean;
237
+ portForwardingEnabled: boolean;
238
+ portForwardingConfig: Record<string, string>;
239
+ networkDiscoveryEnabled: boolean;
240
+ networkDiscoveryConfig: string[];
241
+ }
242
+
230
243
  export interface EventTypes {
231
244
  [Events.AppendedToURL]: Platform.DevToolsPath.RawPathString|Platform.DevToolsPath.UrlString;
232
245
  [Events.CanceledSaveURL]: Platform.DevToolsPath.UrlString;
@@ -234,7 +247,7 @@ export interface EventTypes {
234
247
  [Events.ContextMenuCleared]: void;
235
248
  [Events.ContextMenuItemSelected]: number;
236
249
  [Events.DeviceCountUpdated]: number;
237
- [Events.DevicesDiscoveryConfigChanged]: Adb.Config;
250
+ [Events.DevicesDiscoveryConfigChanged]: DevicesDiscoveryConfig;
238
251
  [Events.DevicesPortForwardingStatusChanged]: void;
239
252
  [Events.DevicesUpdated]: void;
240
253
  [Events.DispatchMessage]: string;
@@ -399,7 +412,7 @@ export interface InspectorFrontendHostAPI {
399
412
 
400
413
  sendMessageToBackend(message: string): void;
401
414
 
402
- setDevicesDiscoveryConfig(config: Adb.Config): void;
415
+ setDevicesDiscoveryConfig(config: DevicesDiscoveryConfig): void;
403
416
 
404
417
  setDevicesUpdatesEnabled(enabled: boolean): void;
405
418
 
@@ -423,7 +436,7 @@ export interface InspectorFrontendHostAPI {
423
436
 
424
437
  resetZoom(): void;
425
438
 
426
- showContextMenuAtPoint(x: number, y: number, items: ContextMenuDescriptor[], document: Document): void;
439
+ showContextMenuAtPoint(x: number, y: number, items: ContextMenuDescriptor[], document: object): void;
427
440
 
428
441
  reattach(callback: () => void): void;
429
442
 
@@ -14,6 +14,7 @@ import {
14
14
  type ChangeEvent,
15
15
  type ClickEvent,
16
16
  type ContextMenuDescriptor,
17
+ type DevicesDiscoveryConfig,
17
18
  type DispatchHttpRequestRequest,
18
19
  type DispatchHttpRequestResult,
19
20
  type DoAidaConversationResult,
@@ -460,7 +461,7 @@ export class InspectorFrontendHostStub implements InspectorFrontendHostAPI {
460
461
  setOpenNewWindowForPopups(_value: boolean): void {
461
462
  }
462
463
 
463
- setDevicesDiscoveryConfig(_config: Adb.Config): void {
464
+ setDevicesDiscoveryConfig(_config: DevicesDiscoveryConfig): void {
464
465
  }
465
466
 
466
467
  setDevicesUpdatesEnabled(_enabled: boolean): void {
@@ -472,7 +473,7 @@ export class InspectorFrontendHostStub implements InspectorFrontendHostAPI {
472
473
  openNodeFrontend(): void {
473
474
  }
474
475
 
475
- showContextMenuAtPoint(_x: number, _y: number, _items: ContextMenuDescriptor[], _document: Document): void {
476
+ showContextMenuAtPoint(_x: number, _y: number, _items: ContextMenuDescriptor[], _document: object): void {
476
477
  throw new Error('Soft context menu should be used');
477
478
  }
478
479
 
@@ -7,7 +7,6 @@
7
7
  import * as Common from '../common/common.js';
8
8
  import * as i18n from '../i18n/i18n.js';
9
9
 
10
- import {InspectorFrontendHostInstance} from './InspectorFrontendHost.js';
11
10
  import type {LoadNetworkResourceResult} from './InspectorFrontendHostAPI.js';
12
11
 
13
12
  const UIStrings = {
@@ -248,7 +247,7 @@ export const loadAsStream = function(
248
247
  rawHeaders.push(key + ': ' + headers[key]);
249
248
  }
250
249
  }
251
- InspectorFrontendHostInstance.loadNetworkResource(url, rawHeaders.join('\r\n'), streamId, finishedCallback);
250
+ globalThis.InspectorFrontendHost.loadNetworkResource(url, rawHeaders.join('\r\n'), streamId, finishedCallback);
252
251
 
253
252
  function finishedCallback(response: LoadNetworkResourceResult): void {
254
253
  if (callback) {
@@ -26,6 +26,7 @@ export interface HostRuntime {
26
26
  getDevicePixelRatio(): number;
27
27
  saveScreenshot(options: ScreenshotOptions): Promise<void>;
28
28
  revokeLastScreenshotUrl(): void;
29
+ loadTextFile(url: URL): Promise<string>;
29
30
  }
30
31
 
31
32
  /**
@@ -150,4 +150,8 @@ export const HOST_RUNTIME: Api.HostRuntime.HostRuntime = {
150
150
  },
151
151
  saveScreenshot,
152
152
  revokeLastScreenshotUrl,
153
+ async loadTextFile(url: URL): Promise<string> {
154
+ const response = await fetch(url);
155
+ return await response.text();
156
+ },
153
157
  };
@@ -2,6 +2,8 @@
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 * as Fs from 'node:fs';
6
+ import * as Url from 'node:url';
5
7
  import * as WorkerThreads from 'node:worker_threads';
6
8
 
7
9
  import type * as Api from '../api/api.js';
@@ -101,4 +103,7 @@ export const HOST_RUNTIME: Api.HostRuntime.HostRuntime = {
101
103
  },
102
104
  async saveScreenshot(_options: Api.HostRuntime.ScreenshotOptions): Promise<void>{},
103
105
  revokeLastScreenshotUrl(): void{},
106
+ async loadTextFile(url: URL): Promise<string> {
107
+ return await Fs.promises.readFile(Url.fileURLToPath(url), 'utf-8');
108
+ },
104
109
  };
@@ -545,6 +545,10 @@ interface DevToolsMobileSafeAreaEmulation {
545
545
  enabled: boolean;
546
546
  }
547
547
 
548
+ interface DevToolsNetworkBackendLinking {
549
+ enabled: boolean;
550
+ }
551
+
548
552
  /**
549
553
  * The host configuration that we expect from the DevTools back-end.
550
554
  *
@@ -602,6 +606,7 @@ export type HostConfig = Platform.TypeScriptUtilities.RecursivePartial<{
602
606
  devToolsAriaLiveRecording: DevToolsAriaLiveRecording,
603
607
  devToolsInstrumentationBreakpoints: DevToolsInstrumentationBreakpoints,
604
608
  devToolsMobileSafeAreaEmulation: DevToolsMobileSafeAreaEmulation,
609
+ devToolsNetworkBackendLinking: DevToolsNetworkBackendLinking,
605
610
  extensionsOnChromeUrls: ExtensionsOnChromeUrls,
606
611
  devToolsComments: HostConfigDevToolsComments,
607
612
  }>;
@@ -1427,6 +1427,19 @@ export class DOMNode extends Common.ObjectWrapper.ObjectWrapper<DOMNodeEventType
1427
1427
  return this.domModel().nodeForId(response.nodeId);
1428
1428
  }
1429
1429
 
1430
+ async getImplicitAnchorCandidates(): Promise<DeferredDOMNode[]> {
1431
+ const response = await this.#agent.invoke_getImplicitAnchorCandidates({
1432
+ nodeId: this.id,
1433
+ });
1434
+
1435
+ if (response.getError() || !response.backendNodeIds) {
1436
+ return [];
1437
+ }
1438
+
1439
+ const target = this.domModel().target();
1440
+ return response.backendNodeIds.map(backendNodeId => new DeferredDOMNode(target, backendNodeId));
1441
+ }
1442
+
1430
1443
  async takeSnapshot(ownerDocumentSnapshot?: DOMDocument): Promise<DOMNode> {
1431
1444
  const snapshot = (this instanceof DOMDocument) ? new DOMDocumentSnapshot(this.domModel(), {
1432
1445
  nodeId: this.id,
@@ -73,11 +73,13 @@ export class SourceMapScopeChainEntry implements ScopeChainEntry {
73
73
  }
74
74
 
75
75
  type(): string {
76
- switch (this.#scope.kind) {
76
+ if (this.#scope.isStackFrame) {
77
+ return this.#isInnerMostFunction ? Protocol.Debugger.ScopeType.Local : Protocol.Debugger.ScopeType.Closure;
78
+ }
79
+ // `kind` is a free-form label. The spec encourages 'Global'/'Block' but doesn't mandate the casing.
80
+ switch (this.#scope.kind?.toLowerCase()) {
77
81
  case 'global':
78
82
  return Protocol.Debugger.ScopeType.Global;
79
- case 'function':
80
- return this.#isInnerMostFunction ? Protocol.Debugger.ScopeType.Local : Protocol.Debugger.ScopeType.Closure;
81
83
  case 'block':
82
84
  return Protocol.Debugger.ScopeType.Block;
83
85
  }
@@ -85,11 +87,12 @@ export class SourceMapScopeChainEntry implements ScopeChainEntry {
85
87
  }
86
88
 
87
89
  typeName(): string {
88
- switch (this.#scope.kind) {
90
+ if (this.#scope.isStackFrame) {
91
+ return this.#isInnerMostFunction ? i18nString(UIStrings.local) : i18nString(UIStrings.closure);
92
+ }
93
+ switch (this.#scope.kind?.toLowerCase()) {
89
94
  case 'global':
90
95
  return i18nString(UIStrings.global);
91
- case 'function':
92
- return this.#isInnerMostFunction ? i18nString(UIStrings.local) : i18nString(UIStrings.closure);
93
96
  case 'block':
94
97
  return i18nString(UIStrings.block);
95
98
  }
@@ -359,7 +359,9 @@ export class SourceMapScopesInfo {
359
359
  // Walk the original scope chain outwards and try to find the corresponding generated range along the way.
360
360
  for (let originalScope = rangeChain.at(-1)?.originalScope; originalScope; originalScope = originalScope.parent) {
361
361
  const range = rangeChain.findLast(r => r.originalScope === originalScope);
362
- const isFunctionScope = originalScope.kind === 'function';
362
+ // `kind` is just a label for scope UI views and has no semantic significance, so `isStackFrame`
363
+ // decides whether this scope is a function scope.
364
+ const isFunctionScope = originalScope.isStackFrame;
363
365
  const isInnerMostFunction = isFunctionScope && !seenFunctionScope;
364
366
  const returnValue = isInnerMostFunction ? callFrame.returnValue() : null;
365
367
  const scopeNumber = range ? findMatchingScopeNumber(callFrame, range) : undefined;
@@ -11,6 +11,7 @@ import * as AutofillManager from '../models/autofill_manager/autofill_manager.js
11
11
  import * as Badges from '../models/badges/badges.js';
12
12
  import * as Bindings from '../models/bindings/bindings.js';
13
13
  import * as Breakpoints from '../models/breakpoints/breakpoints.js';
14
+ import * as ChangeTracker from '../models/change_tracker/change_tracker.js';
14
15
  import * as CommentManager from '../models/comment_manager/comment_manager.js';
15
16
  import * as CrUXManager from '../models/crux-manager/crux-manager.js';
16
17
  import * as Emulation from '../models/emulation/emulation.js';
@@ -238,6 +239,9 @@ export class Universe {
238
239
  context.set(CommentManager.CD4ABridge.CD4ABridge, this.cd4aBridge);
239
240
  }
240
241
 
242
+ const changeTracker = new ChangeTracker.ChangeTracker.ChangeTracker(commentManager);
243
+ context.set(ChangeTracker.ChangeTracker.ChangeTracker, changeTracker);
244
+
241
245
  this.autofillManager = new AutofillManager.AutofillManager.AutofillManager(targetManager, frameManager);
242
246
  context.set(AutofillManager.AutofillManager.AutofillManager, this.autofillManager);
243
247
  }
@@ -275,6 +279,10 @@ export class Universe {
275
279
  return this.context.get(Breakpoints.BreakpointManager.BreakpointManager);
276
280
  }
277
281
 
282
+ get changeTracker(): ChangeTracker.ChangeTracker.ChangeTracker {
283
+ return this.context.get(ChangeTracker.ChangeTracker.ChangeTracker);
284
+ }
285
+
278
286
  get commentManager(): CommentManager.CommentManager.CommentManager {
279
287
  return this.context.get(CommentManager.CommentManager.CommentManager);
280
288
  }
@@ -444,6 +444,9 @@ inspectorBackend.registerCommand("DOM.getAnchorElement", [{"name": "nodeId", "ty
444
444
  inspectorBackend.registerCommand("DOM.forceShowPopover", [{"name": "nodeId", "type": "number", "optional": false, "description": "Id of the popover HTMLElement", "typeRef": "DOM.NodeId"}, {"name": "enable", "type": "boolean", "optional": false, "description": "If true, opens the popover and keeps it open. If false, closes the popover if it was previously force-opened.", "typeRef": null}, {"name": "invokerNodeId", "type": "number", "optional": true, "description": "Optional ID of the element invoking this popover, used to establish the implicit anchor. If not provided, it will fall back to the first invoker in the document, preferring elements with a popovertarget attribute over those with a commandfor attribute. Note that if there are multiple invokers, this is just an estimate.", "typeRef": "DOM.BackendNodeId"}], ["nodeIds"], "When enabling, this API force-opens the popover identified by nodeId and keeps it open until disabled.");
445
445
  inspectorBackend.registerCommand("DOM.getImplicitAnchorCandidates", [{"name": "nodeId", "type": "number", "optional": false, "description": "Id of the popover HTMLElement.", "typeRef": "DOM.NodeId"}], ["backendNodeIds"], "Returns candidate nodes that are configured as triggers for the given popover.");
446
446
  inspectorBackend.registerCommand("DOM.forceShowInterest", [{"name": "nodeId", "type": "number", "optional": false, "description": "Id of the interest invoker HTMLElement.", "typeRef": "DOM.NodeId"}, {"name": "enable", "type": "boolean", "optional": false, "description": "If true, opens and holds interest. If false, releases forced interest.", "typeRef": null}], [], "When enabling, this API forces an element to gain interest in its target, keeping interest active until disabled.");
447
+ inspectorBackend.registerEnum("DOM.SetTextMarkerRequestType", {Spelling: "spelling", Grammar: "grammar"});
448
+ inspectorBackend.registerCommand("DOM.setTextMarker", [{"name": "nodeId", "type": "number", "optional": true, "description": "Identifier of the node.", "typeRef": "DOM.NodeId"}, {"name": "backendNodeId", "type": "number", "optional": true, "description": "Identifier of the backend node.", "typeRef": "DOM.BackendNodeId"}, {"name": "objectId", "type": "string", "optional": true, "description": "JavaScript object id of the node wrapper.", "typeRef": "Runtime.RemoteObjectId"}, {"name": "type", "type": "string", "optional": false, "description": "The type of marker to set on the given range of text.", "typeRef": "DOM.SetTextMarkerRequestType"}, {"name": "start", "type": "number", "optional": false, "description": "Start offset into the element's rendered text in UTF-16 code units. For a text control, an offset into the control's value. Offsets count text in DOM order and do not enter shadow trees. To mark text inside a shadow tree, pass the element inside the shadow tree.", "typeRef": null}, {"name": "end", "type": "number", "optional": false, "description": "End offset (exclusive) in the same units and space as start.", "typeRef": null}], [], "Sets a spelling or grammar error marker on the given range of text. See https://github.com/Igalia/explainers/blob/main/force-spelling-grammar-markers/README.md Note: exactly one between nodeId, backendNodeId and objectId should be passed to identify the node.");
449
+ inspectorBackend.registerCommand("DOM.clearTextMarkers", [], [], "Clears the spelling and grammar error text markers overlapping the ranges set by setTextMarker in this session. These markers are also removed when the DOM domain is disabled or the session ends.");
447
450
  inspectorBackend.registerType("DOM.BackendNode", [{"name": "nodeType", "type": "number", "optional": false, "description": "`Node`'s nodeType.", "typeRef": null}, {"name": "nodeName", "type": "string", "optional": false, "description": "`Node`'s nodeName.", "typeRef": null}, {"name": "backendNodeId", "type": "number", "optional": false, "description": "", "typeRef": "DOM.BackendNodeId"}]);
448
451
  inspectorBackend.registerType("DOM.Node", [{"name": "nodeId", "type": "number", "optional": false, "description": "Node identifier that is passed into the rest of the DOM messages as the `nodeId`. Backend will only push node with given `id` once. It is aware of all requested nodes and will only fire DOM events for nodes known to the client.", "typeRef": "DOM.NodeId"}, {"name": "parentId", "type": "number", "optional": true, "description": "The id of the parent node if any.", "typeRef": "DOM.NodeId"}, {"name": "backendNodeId", "type": "number", "optional": false, "description": "The BackendNodeId for this node.", "typeRef": "DOM.BackendNodeId"}, {"name": "nodeType", "type": "number", "optional": false, "description": "`Node`'s nodeType.", "typeRef": null}, {"name": "nodeName", "type": "string", "optional": false, "description": "`Node`'s nodeName.", "typeRef": null}, {"name": "localName", "type": "string", "optional": false, "description": "`Node`'s localName.", "typeRef": null}, {"name": "nodeValue", "type": "string", "optional": false, "description": "`Node`'s nodeValue.", "typeRef": null}, {"name": "childNodeCount", "type": "number", "optional": true, "description": "Child count for `Container` nodes.", "typeRef": null}, {"name": "children", "type": "array", "optional": true, "description": "Child nodes of this node when requested with children.", "typeRef": "DOM.Node"}, {"name": "attributes", "type": "array", "optional": true, "description": "Attributes of the `Element` node in the form of flat array `[name1, value1, name2, value2]`.", "typeRef": "string"}, {"name": "documentURL", "type": "string", "optional": true, "description": "Document URL that `Document` or `FrameOwner` node points to.", "typeRef": null}, {"name": "baseURL", "type": "string", "optional": true, "description": "Base URL that `Document` or `FrameOwner` node uses for URL completion.", "typeRef": null}, {"name": "publicId", "type": "string", "optional": true, "description": "`DocumentType`'s publicId.", "typeRef": null}, {"name": "systemId", "type": "string", "optional": true, "description": "`DocumentType`'s systemId.", "typeRef": null}, {"name": "internalSubset", "type": "string", "optional": true, "description": "`DocumentType`'s internalSubset.", "typeRef": null}, {"name": "xmlVersion", "type": "string", "optional": true, "description": "`Document`'s XML version in case of XML documents.", "typeRef": null}, {"name": "name", "type": "string", "optional": true, "description": "`Attr`'s name.", "typeRef": null}, {"name": "value", "type": "string", "optional": true, "description": "`Attr`'s value.", "typeRef": null}, {"name": "pseudoType", "type": "string", "optional": true, "description": "Pseudo element type for this node.", "typeRef": "DOM.PseudoType"}, {"name": "pseudoIdentifier", "type": "string", "optional": true, "description": "Pseudo element identifier for this node. Only present if there is a valid pseudoType.", "typeRef": null}, {"name": "shadowRootType", "type": "string", "optional": true, "description": "Shadow root type.", "typeRef": "DOM.ShadowRootType"}, {"name": "frameId", "type": "string", "optional": true, "description": "Frame ID for frame owner elements.", "typeRef": "Page.FrameId"}, {"name": "contentDocument", "type": "object", "optional": true, "description": "Content document for frame owner elements.", "typeRef": "DOM.Node"}, {"name": "shadowRoots", "type": "array", "optional": true, "description": "Shadow root list for given element host.", "typeRef": "DOM.Node"}, {"name": "templateContent", "type": "object", "optional": true, "description": "Content document fragment for template elements.", "typeRef": "DOM.Node"}, {"name": "pseudoElements", "type": "array", "optional": true, "description": "Pseudo elements associated with this node.", "typeRef": "DOM.Node"}, {"name": "importedDocument", "type": "object", "optional": true, "description": "Deprecated, as the HTML Imports API has been removed (crbug.com/937746). This property used to return the imported document for the HTMLImport links. The property is always undefined now.", "typeRef": "DOM.Node"}, {"name": "distributedNodes", "type": "array", "optional": true, "description": "Distributed nodes for given insertion point.", "typeRef": "DOM.BackendNode"}, {"name": "isSVG", "type": "boolean", "optional": true, "description": "Whether the node is SVG.", "typeRef": null}, {"name": "compatibilityMode", "type": "string", "optional": true, "description": "", "typeRef": "DOM.CompatibilityMode"}, {"name": "assignedSlot", "type": "object", "optional": true, "description": "", "typeRef": "DOM.BackendNode"}, {"name": "isScrollable", "type": "boolean", "optional": true, "description": "", "typeRef": null}, {"name": "affectedByStartingStyles", "type": "boolean", "optional": true, "description": "", "typeRef": null}, {"name": "adoptedStyleSheets", "type": "array", "optional": true, "description": "", "typeRef": "DOM.StyleSheetId"}, {"name": "adProvenance", "type": "object", "optional": true, "description": "", "typeRef": "Network.AdProvenance"}]);
449
452
  inspectorBackend.registerType("DOM.DetachedElementInfo", [{"name": "treeNode", "type": "object", "optional": false, "description": "", "typeRef": "DOM.Node"}, {"name": "retainedNodeIds", "type": "array", "optional": false, "description": "", "typeRef": "DOM.NodeId"}]);
@@ -2312,6 +2312,25 @@ export namespace ProtocolMapping {
2312
2312
  paramsType: [Protocol.DOM.ForceShowInterestRequest];
2313
2313
  returnType: void;
2314
2314
  };
2315
+ /**
2316
+ * Sets a spelling or grammar error marker on the given range of text.
2317
+ * See https://github.com/Igalia/explainers/blob/main/force-spelling-grammar-markers/README.md
2318
+ * Note: exactly one between nodeId, backendNodeId and objectId should be passed
2319
+ * to identify the node.
2320
+ */
2321
+ 'DOM.setTextMarker': {
2322
+ paramsType: [Protocol.DOM.SetTextMarkerRequest];
2323
+ returnType: void;
2324
+ };
2325
+ /**
2326
+ * Clears the spelling and grammar error text markers overlapping the ranges
2327
+ * set by setTextMarker in this session. These markers are also removed when
2328
+ * the DOM domain is disabled or the session ends.
2329
+ */
2330
+ 'DOM.clearTextMarkers': {
2331
+ paramsType: [];
2332
+ returnType: void;
2333
+ };
2315
2334
  /**
2316
2335
  * Returns event listeners of the given object.
2317
2336
  */
@@ -1406,6 +1406,21 @@ declare namespace ProtocolProxyApi {
1406
1406
  */
1407
1407
  invoke_forceShowInterest(params: Protocol.DOM.ForceShowInterestRequest): Promise<Protocol.ProtocolResponseWithError>;
1408
1408
 
1409
+ /**
1410
+ * Sets a spelling or grammar error marker on the given range of text.
1411
+ * See https://github.com/Igalia/explainers/blob/main/force-spelling-grammar-markers/README.md
1412
+ * Note: exactly one between nodeId, backendNodeId and objectId should be passed
1413
+ * to identify the node.
1414
+ */
1415
+ invoke_setTextMarker(params: Protocol.DOM.SetTextMarkerRequest): Promise<Protocol.ProtocolResponseWithError>;
1416
+
1417
+ /**
1418
+ * Clears the spelling and grammar error text markers overlapping the ranges
1419
+ * set by setTextMarker in this session. These markers are also removed when
1420
+ * the DOM domain is disabled or the session ends.
1421
+ */
1422
+ invoke_clearTextMarkers(): Promise<Protocol.ProtocolResponseWithError>;
1423
+
1409
1424
  }
1410
1425
  export interface DOMDispatcher {
1411
1426
  /**
@@ -5990,6 +5990,41 @@ export namespace DOM {
5990
5990
  enable: boolean;
5991
5991
  }
5992
5992
 
5993
+ export const enum SetTextMarkerRequestType {
5994
+ Spelling = 'spelling',
5995
+ Grammar = 'grammar',
5996
+ }
5997
+
5998
+ export interface SetTextMarkerRequest {
5999
+ /**
6000
+ * Identifier of the node.
6001
+ */
6002
+ nodeId?: NodeId;
6003
+ /**
6004
+ * Identifier of the backend node.
6005
+ */
6006
+ backendNodeId?: BackendNodeId;
6007
+ /**
6008
+ * JavaScript object id of the node wrapper.
6009
+ */
6010
+ objectId?: Runtime.RemoteObjectId;
6011
+ /**
6012
+ * The type of marker to set on the given range of text.
6013
+ */
6014
+ type: SetTextMarkerRequestType;
6015
+ /**
6016
+ * Start offset into the element's rendered text in UTF-16 code units.
6017
+ * For a text control, an offset into the control's value.
6018
+ * Offsets count text in DOM order and do not enter shadow trees.
6019
+ * To mark text inside a shadow tree, pass the element inside the shadow tree.
6020
+ */
6021
+ start: integer;
6022
+ /**
6023
+ * End offset (exclusive) in the same units and space as start.
6024
+ */
6025
+ end: integer;
6026
+ }
6027
+
5993
6028
  /**
5994
6029
  * Fired when `Element`'s attribute is modified.
5995
6030
  */
@@ -10,7 +10,6 @@ import type * as Trace from '../trace/trace.js';
10
10
 
11
11
  import {
12
12
  AiAgent,
13
- type AllowedOriginResult,
14
13
  type ContextResponse,
15
14
  type ConversationContext,
16
15
  type MultimodalInputType,
@@ -26,7 +25,13 @@ import {debugLog} from './debug.js';
26
25
  import {ExtensionScope} from './ExtensionScope.js';
27
26
  import type {Skill, SkillName} from './skills/Skill.js';
28
27
  import {SKILLS} from './skills/SkillRegistry.js';
29
- import {type AllToolsCapabilities, isOriginAllowedByLock, type Tool, type ToolArgs} from './tools/Tool.js';
28
+ import {
29
+ type AllToolsCapabilities,
30
+ isOriginAllowedByLock,
31
+ type OriginLockState,
32
+ type Tool,
33
+ type ToolArgs,
34
+ } from './tools/Tool.js';
30
35
  import {ToolRegistry} from './tools/ToolRegistry.js';
31
36
 
32
37
  const SKILL_DISPLAY_NAMES: Record<SkillName, string> = {
@@ -71,6 +76,10 @@ If the user asks a question that requires an investigation or debugging, use thi
71
76
  * **CRITICAL**: Do not expose raw, internal system identifiers (such as database IDs, internal node paths, or event keys) directly to the user. Use descriptive names instead.`;
72
77
 
73
78
  export interface AiAgent2Options extends ExecuteJsAgentOptions {
79
+ /**
80
+ * Supplies the origin lock state for the conversation.
81
+ */
82
+ originLock: () => OriginLockState;
74
83
  lighthouseRecording?: (overrides?: LHModel.RunTypes.RunOverrides) => Promise<LHModel.ReporterTypes.ReportJSON|null>;
75
84
  performanceRecordAndReload?: () => Promise<Trace.TraceModel.ParsedTrace>;
76
85
  }
@@ -85,7 +94,7 @@ export class AiAgent2 extends AiAgent<unknown> {
85
94
 
86
95
  #changes: ChangeManager;
87
96
  #execJs: typeof executeJsCode;
88
- readonly #allowedOrigin?: () => AllowedOriginResult;
97
+ readonly #originLock: () => OriginLockState;
89
98
  readonly #lighthouseRecording?:
90
99
  (overrides?: LHModel.RunTypes.RunOverrides) => Promise<LHModel.ReporterTypes.ReportJSON|null>;
91
100
  readonly #performanceRecordAndReload?: () => Promise<Trace.TraceModel.ParsedTrace>;
@@ -102,11 +111,10 @@ export class AiAgent2 extends AiAgent<unknown> {
102
111
  }
103
112
 
104
113
  const target = this.targetManager.primaryPageTarget();
105
- const establishedOrigin = this.#getConversationOrigin();
114
+ const originLock = this.#originLock();
106
115
  // Avoid fetching or caching top-level documents across origins or when
107
116
  // the origin lock is blocked/uninitialized.
108
- // Note: b/559642568 tracks making the tri-state ('blocked' | 'uninitialized' | 'locked') explicit.
109
- const isTargetAllowed = target && isOriginAllowedByLock(establishedOrigin, target.inspectedSecurityOrigin());
117
+ const isTargetAllowed = target && isOriginAllowedByLock(originLock, target.inspectedSecurityOrigin());
110
118
  const domModel = isTargetAllowed ? target.model(SDK.DOMModel.DOMModel) : null;
111
119
  // Ensure the DOM document is requested and cached in DOMModel so that
112
120
  // subsequent synchronous lookups via domModel.existingDocument() (e.g.,
@@ -138,7 +146,7 @@ export class AiAgent2 extends AiAgent<unknown> {
138
146
  this.#lighthouseRecording = opts.lighthouseRecording;
139
147
  this.#performanceRecordAndReload = opts.performanceRecordAndReload;
140
148
  this.#execJs = opts.execJs ?? executeJsCode;
141
- this.#allowedOrigin = opts.allowedOrigin;
149
+ this.#originLock = opts.originLock;
142
150
  this.#declaredTools.add('learnSkills');
143
151
  this.declareFunction<{skills: SkillName[]}>('learnSkills', {
144
152
  description: () => {
@@ -297,7 +305,7 @@ User query: ${enhancedQuery}`;
297
305
  execJs: this.#execJs,
298
306
  getExecutionContextNode: () => this.#getExecutionContextNode(),
299
307
  getTarget: () => this.#getTarget(),
300
- getEstablishedOrigin: () => this.#getConversationOrigin(),
308
+ getOriginLock: () => this.#originLock(),
301
309
  getLighthouseReport: () => (this.context instanceof AccessibilityContext ? this.context.getItem() : null),
302
310
  runLighthouse: async overrides => await (this.#lighthouseRecording?.(overrides) ?? null),
303
311
  getPerformanceTraceContext: () => (this.context instanceof PerformanceTraceContext ? this.context : null),
@@ -318,8 +326,7 @@ User query: ${enhancedQuery}`;
318
326
  * perform their own origin checks on the resolved entities.
319
327
  */
320
328
  #getTarget(): SDK.Target.Target|null {
321
- const allowed = this.#allowedOrigin?.();
322
- if (allowed && 'blocked' in allowed) {
329
+ if (this.#originLock().status === 'BLOCKED_BY_NAVIGATION') {
323
330
  return null;
324
331
  }
325
332
  return this.targetManager.primaryPageTarget();
@@ -329,7 +336,7 @@ User query: ${enhancedQuery}`;
329
336
  * For non-DOM contexts (e.g., Lighthouse accessibility reports or storage items),
330
337
  * there is no user-selected DOM node. We fall back to the document body as the
331
338
  * default execution context node so scripts have a valid `$0` target.
332
- * Fails closed and returns null if the conversation origin is not established or
339
+ * Returns null if the conversation origin is not established or
333
340
  * does not match the primary page document's security origin.
334
341
  */
335
342
  #getDocumentBodyNode(): SDK.DOMModel.DOMNode|null {
@@ -338,18 +345,13 @@ User query: ${enhancedQuery}`;
338
345
  if (!document) {
339
346
  return null;
340
347
  }
341
- const establishedOrigin = this.#getConversationOrigin();
342
- if (!isOriginAllowedByLock(establishedOrigin, document.securityOrigin())) {
348
+ const originLock = this.#originLock();
349
+ if (!isOriginAllowedByLock(originLock, document.securityOrigin())) {
343
350
  return null;
344
351
  }
345
352
  return document.body ?? null;
346
353
  }
347
354
 
348
- #getConversationOrigin(): SDK.SecurityOrigin.SecurityOrigin|undefined {
349
- const allowed = this.#allowedOrigin?.();
350
- return allowed && 'origin' in allowed ? allowed.origin : undefined;
351
- }
352
-
353
355
  get activeSkills(): Set<SkillName> {
354
356
  return this.#activeSkills;
355
357
  }
@@ -38,7 +38,7 @@ import {FileContext} from './contexts/FileContext.js';
38
38
  import {PerformanceTraceContext} from './contexts/PerformanceTraceContext.js';
39
39
  import {RequestContext} from './contexts/RequestContext.js';
40
40
  import {StorageContext} from './contexts/StorageContext.js';
41
- import {ToolAnnotation} from './tools/Tool.js';
41
+ import {type OriginLockState, ToolAnnotation} from './tools/Tool.js';
42
42
  import {ToolRegistry} from './tools/ToolRegistry.js';
43
43
 
44
44
  export const NOT_FOUND_IMAGE_DATA = '';
@@ -381,7 +381,7 @@ export class AiConversation {
381
381
  const isTransitioningFromStorage = previousType === ConversationType.STORAGE && type !== ConversationType.STORAGE;
382
382
  const history = isTransitioningFromStorage ? [] : this.#filterHistoryForNewAgent();
383
383
 
384
- const options = {
384
+ const baseOptions = {
385
385
  aidaClient: this.#aidaClient,
386
386
  serverSideLoggingAllowed: isAiAssistanceServerSideLoggingAllowed(),
387
387
  sessionId: this.id,
@@ -390,13 +390,18 @@ export class AiConversation {
390
390
  onInspectElement: this.#onInspectElement,
391
391
  networkTimeCalculator: this.#networkTimeCalculator,
392
392
  lighthouseRecording: this.#lighthouseRecording,
393
- allowedOrigin: this.allowedOrigin,
394
393
  history,
395
394
  targetManager: this.#targetManager,
396
395
  };
397
396
 
398
- this.#agent = Root.Runtime.hostConfig.devToolsAiV2Architecture?.enabled ? new AiAgent2(options) :
399
- this.#createV1Agent(type, options);
397
+ this.#agent = Root.Runtime.hostConfig.devToolsAiV2Architecture?.enabled ? new AiAgent2({
398
+ ...baseOptions,
399
+ originLock: this.getOriginLock,
400
+ }) :
401
+ this.#createV1Agent(type, {
402
+ ...baseOptions,
403
+ allowedOrigin: this.allowedOrigin,
404
+ });
400
405
  }
401
406
 
402
407
  #createV1Agent(type: ConversationType, options: AgentOptions): AiAgent<unknown> {
@@ -553,19 +558,40 @@ export class AiConversation {
553
558
  }
554
559
 
555
560
  /**
556
- * Returns the permitted origin for agent tool execution, or blocks execution
557
- * if an unapproved cross-origin navigation occurred during the current run.
561
+ * Returns the conversation's origin-locking state:
562
+ * - `BLOCKED_BY_NAVIGATION`: An unapproved cross-origin navigation occurred during the active run.
563
+ * - `ESTABLISHED_ORIGIN`: The conversation is locked to the established origin.
564
+ * - `UNINITIALIZED`: No origin lock has been established yet.
558
565
  */
559
- allowedOrigin = (): AllowedOriginResult => {
566
+ getOriginLock = (): OriginLockState => {
560
567
  if (this.#navigationOccurredDuringRun) {
561
- return {blocked: true};
568
+ return {status: 'BLOCKED_BY_NAVIGATION'};
562
569
  }
563
570
  if (this.#origin) {
564
- return {origin: this.#origin};
571
+ return {status: 'ESTABLISHED_ORIGIN', origin: this.#origin};
572
+ }
573
+ const pageOrigin = getPrimaryPageSecurityOrigin(this.#targetManager);
574
+ if (pageOrigin) {
575
+ this.#origin = pageOrigin;
576
+ return {status: 'ESTABLISHED_ORIGIN', origin: this.#origin};
565
577
  }
566
- this.#origin = getPrimaryPageSecurityOrigin(this.#targetManager);
567
578
 
568
- return {origin: this.#origin};
579
+ return {status: 'UNINITIALIZED'};
580
+ };
581
+
582
+ /**
583
+ * Returns the permitted origin for legacy V1 agent tool execution.
584
+ * Maps the OriginLockState to the AllowedOriginResult format expected by V1 agents.
585
+ */
586
+ allowedOrigin = (): AllowedOriginResult => {
587
+ const lock = this.getOriginLock();
588
+ if (lock.status === 'BLOCKED_BY_NAVIGATION') {
589
+ return {blocked: true};
590
+ }
591
+ if (lock.status === 'ESTABLISHED_ORIGIN') {
592
+ return {origin: lock.origin};
593
+ }
594
+ return {origin: undefined};
569
595
  };
570
596
  }
571
597
 
@@ -11,7 +11,10 @@ import {ChangeManager} from '../ChangeManager.js';
11
11
  import {LighthouseFormatter} from '../data_formatters/LighthouseFormatter.js';
12
12
  import {debugLog} from '../debug.js';
13
13
  import {ExtensionScope} from '../ExtensionScope.js';
14
- import {ToolName} from '../tools/Tool.js';
14
+ import {
15
+ type OriginLockState,
16
+ ToolName,
17
+ } from '../tools/Tool.js';
15
18
  import {ToolRegistry} from '../tools/ToolRegistry.js';
16
19
 
17
20
  import {
@@ -253,7 +256,11 @@ export class AccessibilityAgent extends AiAgent<LHModel.ReporterTypes.ReportJSON
253
256
  createExtensionScope: this.#createExtensionScope.bind(this),
254
257
  execJs: this.#execJs,
255
258
  getExecutionContextNode: () => this.#getDocumentBodyNode(),
256
- getEstablishedOrigin: () => this.context?.getOrigin(),
259
+ getOriginLock: (): OriginLockState => {
260
+ // V1 AccessibilityAgent adapts the selected node's security origin to OriginLockState.
261
+ const origin = this.context?.getOrigin();
262
+ return origin ? {status: 'ESTABLISHED_ORIGIN', origin} : {status: 'UNINITIALIZED'};
263
+ },
257
264
  },
258
265
  options,
259
266
  );