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.
- package/.geminiignore +0 -1
- package/front_end/core/common/Color.ts +5 -2
- package/front_end/core/host/InspectorFrontendHostAPI.ts +16 -3
- package/front_end/core/host/InspectorFrontendHostStub.ts +3 -2
- package/front_end/core/host/ResourceLoader.ts +1 -2
- package/front_end/core/platform/api/HostRuntime.ts +1 -0
- package/front_end/core/platform/browser/HostRuntime.ts +4 -0
- package/front_end/core/platform/node/HostRuntime.ts +5 -0
- package/front_end/core/root/Runtime.ts +5 -0
- package/front_end/core/sdk/DOMModel.ts +13 -0
- package/front_end/core/sdk/SourceMapScopeChainEntry.ts +9 -6
- package/front_end/core/sdk/SourceMapScopesInfo.ts +3 -1
- package/front_end/foundation/Universe.ts +8 -0
- package/front_end/generated/InspectorBackendCommands.ts +3 -0
- package/front_end/generated/protocol-mapping.d.ts +19 -0
- package/front_end/generated/protocol-proxy-api.d.ts +15 -0
- package/front_end/generated/protocol.ts +35 -0
- package/front_end/models/ai_assistance/AiAgent2.ts +20 -18
- package/front_end/models/ai_assistance/AiConversation.ts +38 -12
- package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +9 -2
- package/front_end/models/ai_assistance/agents/StylingAgent.ts +13 -4
- package/front_end/models/ai_assistance/tools/CookieUtils.ts +10 -8
- package/front_end/models/ai_assistance/tools/ExecuteJavaScript.ts +1 -1
- package/front_end/models/ai_assistance/tools/GetElementAccessibilityDetails.ts +1 -3
- package/front_end/models/ai_assistance/tools/GetNetworkRequestDetails.ts +9 -8
- package/front_end/models/ai_assistance/tools/GetSourceContent.ts +5 -6
- package/front_end/models/ai_assistance/tools/GetStorageValues.ts +6 -25
- package/front_end/models/ai_assistance/tools/GetStyles.ts +1 -3
- package/front_end/models/ai_assistance/tools/ListNetworkRequests.ts +7 -6
- package/front_end/models/ai_assistance/tools/ListPageOrigins.ts +8 -5
- package/front_end/models/ai_assistance/tools/ListSources.ts +8 -10
- package/front_end/models/ai_assistance/tools/ListStorageKeys.ts +6 -25
- package/front_end/models/ai_assistance/tools/README.md +5 -3
- package/front_end/models/ai_assistance/tools/ResolveDevtoolsNodePath.ts +1 -2
- package/front_end/models/ai_assistance/tools/Tool.ts +52 -14
- package/front_end/models/change_tracker/ChangeTracker.ts +89 -0
- package/front_end/models/change_tracker/change_tracker.ts +9 -0
- package/front_end/models/issues_manager/MarkdownIssueDescription.ts +2 -2
- package/front_end/models/live-metrics/LiveMetrics.ts +1 -2
- package/front_end/models/trace/ModelImpl.ts +0 -6
- package/front_end/models/trace/Processor.ts +4 -7
- package/front_end/models/trace/helpers/Timing.ts +2 -1
- package/front_end/models/trace/types/Overlays.ts +12 -1
- package/front_end/panels/accessibility/AccessibilityAnnouncementRecordingView.ts +67 -19
- package/front_end/panels/application/IndexedDBViews.ts +1 -1
- package/front_end/panels/elements/DOMTreeContextMenu.ts +16 -23
- package/front_end/panels/elements/ElementsTreeElement.ts +286 -59
- package/front_end/panels/elements/ElementsTreeOutline.ts +273 -42
- package/front_end/panels/network/NetworkPanel.ts +137 -52
- package/front_end/panels/network/RequestHeadersView.ts +65 -32
- package/front_end/panels/network/components/RequestHeadersView.css +7 -1
- package/front_end/panels/network/forward/BackendLinking.ts +49 -0
- package/front_end/panels/network/forward/forward.ts +2 -0
- package/front_end/panels/settings/BackendLinkingSettingsTab.ts +303 -0
- package/front_end/panels/settings/backendLinkingSettingsTab.css +78 -0
- package/front_end/panels/settings/settings-meta.ts +25 -0
- package/front_end/panels/settings/settings.ts +2 -0
- package/front_end/panels/timeline/README.md +3 -2
- package/front_end/panels/timeline/TimelineFlameChartView.ts +23 -2
- package/front_end/panels/timeline/overlays/OverlaysImpl.ts +64 -4
- package/front_end/panels/timeline/timelineFlameChartView.css +47 -0
- package/front_end/panels/whats_new/ReleaseNoteText.ts +8 -8
- package/front_end/panels/whats_new/resources/WNDT.md +6 -6
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/third_party/puppeteer/README.chromium +2 -2
- package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.d.ts +6 -0
- package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +5 -8
- package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Coverage.d.ts +6 -0
- package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Coverage.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Coverage.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/ScreenRecording.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/ScreenRecording.js +1 -5
- package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/ScreenRecording.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.d.ts +3 -3
- package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.js +3 -3
- package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/types.d.ts +6 -0
- package/front_end/third_party/puppeteer/package/package.json +2 -2
- package/front_end/third_party/puppeteer/package/src/cdp/Coverage.ts +6 -0
- package/front_end/third_party/puppeteer/package/src/cdp/ScreenRecording.ts +3 -7
- package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
- package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
- package/front_end/third_party/web-vitals/README.chromium +2 -2
- package/front_end/third_party/web-vitals/package/dist/modules/attribution/onINP.js +8 -5
- package/front_end/third_party/web-vitals/package/package.json +1 -1
- package/front_end/third_party/web-vitals/package/src/attribution/onINP.ts +8 -4
- package/front_end/third_party/web-vitals/rebuild.sh +2 -2
- package/front_end/ui/legacy/ContextMenu.ts +19 -0
- package/front_end/ui/legacy/SoftContextMenu.ts +6 -0
- package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +62 -22
- package/front_end/ui/visual_logging/KnownContextValues.ts +4 -0
- package/package.json +5 -4
|
@@ -8,7 +8,10 @@ import type * as SDK from '../../../core/sdk/sdk.js';
|
|
|
8
8
|
import {ChangeManager} from '../ChangeManager.js';
|
|
9
9
|
import {ExtensionScope} from '../ExtensionScope.js';
|
|
10
10
|
import {AI_ASSISTANCE_CSS_CLASS_NAME} from '../injected.js';
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
type OriginLockState,
|
|
13
|
+
ToolName,
|
|
14
|
+
} from '../tools/Tool.js';
|
|
12
15
|
import {ToolRegistry} from '../tools/ToolRegistry.js';
|
|
13
16
|
|
|
14
17
|
import {
|
|
@@ -154,8 +157,10 @@ export class StylingAgent extends AiAgent<SDK.DOMModel.DOMNode> {
|
|
|
154
157
|
}
|
|
155
158
|
return await getStylesTool.handler(args, {
|
|
156
159
|
getTarget: () => this.targetManager.primaryPageTarget() ?? context.getItem().domModel().target(),
|
|
157
|
-
|
|
158
|
-
|
|
160
|
+
getOriginLock: (): OriginLockState => {
|
|
161
|
+
// V1 StylingAgent adapts the selected DOM node's security origin to OriginLockState.
|
|
162
|
+
const origin = context.getOrigin();
|
|
163
|
+
return origin ? {status: 'ESTABLISHED_ORIGIN', origin} : {status: 'UNINITIALIZED'};
|
|
159
164
|
},
|
|
160
165
|
});
|
|
161
166
|
},
|
|
@@ -176,7 +181,11 @@ export class StylingAgent extends AiAgent<SDK.DOMModel.DOMNode> {
|
|
|
176
181
|
createExtensionScope: this.#createExtensionScope.bind(this),
|
|
177
182
|
execJs: this.#execJs,
|
|
178
183
|
getExecutionContextNode: () => this.context?.getItem() ?? null,
|
|
179
|
-
|
|
184
|
+
getOriginLock: (): OriginLockState => {
|
|
185
|
+
// V1 StylingAgent adapts the selected DOM node's security origin to OriginLockState.
|
|
186
|
+
const origin = this.context?.getOrigin();
|
|
187
|
+
return origin ? {status: 'ESTABLISHED_ORIGIN', origin} : {status: 'UNINITIALIZED'};
|
|
188
|
+
},
|
|
180
189
|
},
|
|
181
190
|
options,
|
|
182
191
|
),
|
|
@@ -6,7 +6,7 @@ import * as SDK from '../../../core/sdk/sdk.js';
|
|
|
6
6
|
import type * as Protocol from '../../../generated/protocol.js';
|
|
7
7
|
|
|
8
8
|
import {MAX_TARGET_ORIGINS} from './DOMStorageUtils.js';
|
|
9
|
-
import type
|
|
9
|
+
import {isOriginAllowedByLock, type OriginLockCapability, resolveOriginFromLock} from './Tool.js';
|
|
10
10
|
|
|
11
11
|
export interface CookieDetails {
|
|
12
12
|
name: string;
|
|
@@ -25,14 +25,14 @@ export interface CookieDetails {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
* Resolves and validates target origins against the
|
|
28
|
+
* Resolves and validates target origins against the conversation's origin lock state and primary page target.
|
|
29
29
|
*
|
|
30
30
|
* When `requestedOrigins` is empty or omitted, defaults to the established context origin.
|
|
31
31
|
* Rejects opaque origins, mismatches with the primary page origin, and cross-origin targets.
|
|
32
32
|
* Limits results to at most `MAX_TARGET_ORIGINS` unique origins.
|
|
33
33
|
*
|
|
34
34
|
* @param requestedOrigins Optional list of origin URLs to validate.
|
|
35
|
-
* @param context The origin lock capability
|
|
35
|
+
* @param context The origin lock capability providing access to the conversation's origin lock state.
|
|
36
36
|
* @param targetManager The target manager used to resolve the primary page target.
|
|
37
37
|
* @returns An object with validated target origins and the primary page target, or an error object.
|
|
38
38
|
*/
|
|
@@ -44,10 +44,12 @@ export function resolveAllowedTargetOrigins(
|
|
|
44
44
|
error: string,
|
|
45
45
|
}
|
|
46
46
|
{
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
const originLock = context.getOriginLock();
|
|
48
|
+
const originResult = resolveOriginFromLock(originLock);
|
|
49
|
+
if ('error' in originResult) {
|
|
50
|
+
return originResult;
|
|
50
51
|
}
|
|
52
|
+
const establishedOrigin = originResult.origin;
|
|
51
53
|
|
|
52
54
|
const primaryPageTarget = targetManager.primaryPageTarget();
|
|
53
55
|
if (!primaryPageTarget) {
|
|
@@ -55,7 +57,7 @@ export function resolveAllowedTargetOrigins(
|
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
const pageOrigin = primaryPageTarget.inspectedSecurityOrigin();
|
|
58
|
-
if (!pageOrigin
|
|
60
|
+
if (!isOriginAllowedByLock(originLock, pageOrigin)) {
|
|
59
61
|
return {error: 'Page origin does not match allowed origin.'};
|
|
60
62
|
}
|
|
61
63
|
|
|
@@ -65,7 +67,7 @@ export function resolveAllowedTargetOrigins(
|
|
|
65
67
|
[establishedOrigin];
|
|
66
68
|
|
|
67
69
|
const validOrigins =
|
|
68
|
-
candidateOrigins.filter(origin => origin
|
|
70
|
+
candidateOrigins.filter(origin => isOriginAllowedByLock(originLock, origin)).map(origin => origin.siteId());
|
|
69
71
|
|
|
70
72
|
const targetOrigins = Array.from(new Set(validOrigins)).slice(0, MAX_TARGET_ORIGINS);
|
|
71
73
|
if (targetOrigins.length === 0) {
|
|
@@ -131,7 +131,7 @@ const data = {
|
|
|
131
131
|
return {error: 'Error: Could not find the context node for execution.'};
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
if (!isOriginAllowedByLock(context.
|
|
134
|
+
if (!isOriginAllowedByLock(context.getOriginLock(), executionNode.securityOrigin())) {
|
|
135
135
|
return {error: 'Error: Cannot execute JavaScript on cross-origin target.'};
|
|
136
136
|
}
|
|
137
137
|
|
|
@@ -80,8 +80,6 @@ export class GetElementAccessibilityDetailsTool implements
|
|
|
80
80
|
params: GetElementAccessibilityDetailsArgs,
|
|
81
81
|
context: BaseToolCapability&TargetCapability&OriginLockCapability,
|
|
82
82
|
): Promise<DataHandlerResult<string>> {
|
|
83
|
-
const establishedOrigin = context.getEstablishedOrigin();
|
|
84
|
-
|
|
85
83
|
const target = context.getTarget();
|
|
86
84
|
if (!target) {
|
|
87
85
|
return {error: 'Error: Inspected target not found.'};
|
|
@@ -99,7 +97,7 @@ export class GetElementAccessibilityDetailsTool implements
|
|
|
99
97
|
// Security check: Ensure the element matches the active conversation's origin lock.
|
|
100
98
|
// Because getTarget() returns the primary page target to support resolving elements
|
|
101
99
|
// across frames, origin validation must be enforced directly on the resolved node.
|
|
102
|
-
if (!isOriginAllowedByLock(
|
|
100
|
+
if (!isOriginAllowedByLock(context.getOriginLock(), resolved.securityOrigin())) {
|
|
103
101
|
return {error: 'Error: Node does not belong to the current origin.'};
|
|
104
102
|
}
|
|
105
103
|
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
type DataTool,
|
|
15
15
|
isOriginAllowedByLock,
|
|
16
16
|
type OriginLockCapability,
|
|
17
|
+
resolveOriginFromLock,
|
|
17
18
|
type ToolArgs,
|
|
18
19
|
ToolName,
|
|
19
20
|
} from './Tool.js';
|
|
@@ -75,9 +76,12 @@ export class GetNetworkRequestDetailsTool implements
|
|
|
75
76
|
args: GetNetworkRequestDetailsArgs,
|
|
76
77
|
context: BaseToolCapability&OriginLockCapability,
|
|
77
78
|
): Promise<DataHandlerResult<unknown>> {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
const originLock = context.getOriginLock();
|
|
80
|
+
const originResult = resolveOriginFromLock(originLock);
|
|
81
|
+
if ('error' in originResult) {
|
|
82
|
+
return originResult;
|
|
83
|
+
}
|
|
84
|
+
const establishedOrigin = originResult.origin;
|
|
81
85
|
|
|
82
86
|
// eslint-disable-next-line @devtools/no-instance-of-migrated-singletons
|
|
83
87
|
const networkLog = this.#networkLog ?? Logs.NetworkLog.NetworkLog.instance();
|
|
@@ -87,13 +91,10 @@ export class GetNetworkRequestDetailsTool implements
|
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
// If the conversation is locked to an origin, only allow accessing requests from that origin.
|
|
90
|
-
return isOriginAllowedByLock(
|
|
94
|
+
return isOriginAllowedByLock(originLock, req.initiatorSecurityOrigin());
|
|
91
95
|
});
|
|
92
96
|
|
|
93
|
-
|
|
94
|
-
// so find() will never return a request. We check establishedOrigin here as a defensive
|
|
95
|
-
// guard and to narrow the type for NetworkRequestFormatter below.
|
|
96
|
-
if (!establishedOrigin || !request) {
|
|
97
|
+
if (!request) {
|
|
97
98
|
return {
|
|
98
99
|
error: 'No request found',
|
|
99
100
|
};
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
type DataHandlerResult,
|
|
14
14
|
type DataTool,
|
|
15
15
|
type OriginLockCapability,
|
|
16
|
+
resolveOriginFromLock,
|
|
16
17
|
type ToolArgs,
|
|
17
18
|
ToolName,
|
|
18
19
|
} from './Tool.js';
|
|
@@ -64,14 +65,12 @@ export class GetSourceContentTool implements
|
|
|
64
65
|
args: GetSourceContentArgs,
|
|
65
66
|
context: BaseToolCapability&OriginLockCapability,
|
|
66
67
|
): Promise<DataHandlerResult<{content: string}>> {
|
|
67
|
-
const
|
|
68
|
-
if (
|
|
69
|
-
return
|
|
70
|
-
error: 'Unable to find file.',
|
|
71
|
-
};
|
|
68
|
+
const originResult = resolveOriginFromLock(context.getOriginLock());
|
|
69
|
+
if ('error' in originResult) {
|
|
70
|
+
return originResult;
|
|
72
71
|
}
|
|
73
72
|
|
|
74
|
-
const file = ListSourcesTool.getSourceById(args.id,
|
|
73
|
+
const file = ListSourcesTool.getSourceById(args.id, originResult.origin);
|
|
75
74
|
|
|
76
75
|
if (!file) {
|
|
77
76
|
return {
|
|
@@ -8,7 +8,8 @@ import * as Platform from '../../../core/platform/platform.js';
|
|
|
8
8
|
import * as SDK from '../../../core/sdk/sdk.js';
|
|
9
9
|
import type {FunctionHandlerOptions} from '../agents/AiAgent.js';
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import {resolveAllowedTargetOrigins} from './CookieUtils.js';
|
|
12
|
+
import {resolveDOMStorages} from './DOMStorageUtils.js';
|
|
12
13
|
import {
|
|
13
14
|
type BaseToolCapability,
|
|
14
15
|
type DataHandlerResult,
|
|
@@ -112,31 +113,11 @@ export class GetStorageValuesTool implements DataTool<GetStorageValuesArgs, GetS
|
|
|
112
113
|
|
|
113
114
|
// eslint-disable-next-line @devtools/no-instance-of-migrated-singletons
|
|
114
115
|
const targetManager = SDK.TargetManager.TargetManager.instance();
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (!establishedOrigin || establishedOrigin.isOpaque()) {
|
|
119
|
-
return {error: 'No origin available or not allowed.'};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (!primaryPageTarget) {
|
|
123
|
-
return {error: 'No origin available or not allowed.'};
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const pageOrigin = primaryPageTarget.inspectedSecurityOrigin();
|
|
127
|
-
if (!pageOrigin.isSameOriginWith(establishedOrigin)) {
|
|
128
|
-
return {error: 'No origin available or not allowed.'};
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const candidateOrigins: SDK.SecurityOrigin.SecurityOrigin[] = (args.origins && args.origins.length > 0) ?
|
|
132
|
-
args.origins.map(origin => SDK.SecurityOrigin.SecurityOrigin.create(origin)) :
|
|
133
|
-
[establishedOrigin];
|
|
134
|
-
const validOrigins =
|
|
135
|
-
candidateOrigins.filter(origin => origin.isSameOriginWith(establishedOrigin)).map(origin => origin.siteId());
|
|
136
|
-
const targetOrigins = Array.from(new Set(validOrigins)).slice(0, MAX_TARGET_ORIGINS);
|
|
137
|
-
if (targetOrigins.length === 0) {
|
|
138
|
-
return {error: 'No valid origins found.'};
|
|
116
|
+
const targetOriginsResult = resolveAllowedTargetOrigins(args.origins, context, targetManager);
|
|
117
|
+
if ('error' in targetOriginsResult) {
|
|
118
|
+
return {error: targetOriginsResult.error};
|
|
139
119
|
}
|
|
120
|
+
const {targetOrigins, primaryPageTarget} = targetOriginsResult;
|
|
140
121
|
|
|
141
122
|
const storageKey = (targetOrigins.length === 1 && args.storageKey) ? args.storageKey : undefined;
|
|
142
123
|
|
|
@@ -97,8 +97,6 @@ export class GetStylesTool implements
|
|
|
97
97
|
return {error: 'Error: Could not find the inspected page.'};
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
const establishedOrigin = context.getEstablishedOrigin();
|
|
101
|
-
|
|
102
100
|
for (const uid of params.elements) {
|
|
103
101
|
result[uid] = {computed: {}, authored: {}};
|
|
104
102
|
const node = new SDK.DOMModel.DeferredDOMNode(target, uid as Protocol.DOM.BackendNodeId);
|
|
@@ -109,7 +107,7 @@ export class GetStylesTool implements
|
|
|
109
107
|
// Security check: Ensure the resolved element belongs to the locked origin.
|
|
110
108
|
// Because getTarget() returns the primary page target to support resolving elements
|
|
111
109
|
// across frames, origin validation must be enforced directly on the resolved node.
|
|
112
|
-
if (!isOriginAllowedByLock(
|
|
110
|
+
if (!isOriginAllowedByLock(context.getOriginLock(), resolved.securityOrigin())) {
|
|
113
111
|
return {error: 'Error: Node does not belong to the current origin.'};
|
|
114
112
|
}
|
|
115
113
|
const styles = await resolved.domModel().cssModel().getComputedStyle(resolved.id);
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
type DataTool,
|
|
15
15
|
isOriginAllowedByLock,
|
|
16
16
|
type OriginLockCapability,
|
|
17
|
+
resolveOriginFromLock,
|
|
17
18
|
ToolName,
|
|
18
19
|
} from './Tool.js';
|
|
19
20
|
|
|
@@ -76,12 +77,12 @@ export class ListNetworkRequestsTool implements
|
|
|
76
77
|
const requests: NetworkRequestSummary[] = [];
|
|
77
78
|
// A conversation is locked to an origin once the first query is made.
|
|
78
79
|
// We only allow inspecting requests matching the conversation's established origin.
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
};
|
|
80
|
+
const originLock = context.getOriginLock();
|
|
81
|
+
const originResult = resolveOriginFromLock(originLock);
|
|
82
|
+
if ('error' in originResult) {
|
|
83
|
+
return originResult;
|
|
84
84
|
}
|
|
85
|
+
const establishedOrigin = originResult.origin;
|
|
85
86
|
|
|
86
87
|
// eslint-disable-next-line @devtools/no-instance-of-migrated-singletons
|
|
87
88
|
const networkLog = this.#networkLog ?? Logs.NetworkLog.NetworkLog.instance();
|
|
@@ -89,7 +90,7 @@ export class ListNetworkRequestsTool implements
|
|
|
89
90
|
const requestsToShow: SDK.NetworkRequest.NetworkRequest[] = [];
|
|
90
91
|
for (const request of networkLog.requests()) {
|
|
91
92
|
// If the request's initiator origin does not match the locked origin, skip it.
|
|
92
|
-
if (!isOriginAllowedByLock(
|
|
93
|
+
if (!isOriginAllowedByLock(originLock, request.initiatorSecurityOrigin())) {
|
|
93
94
|
hasCrossOriginRequest = true;
|
|
94
95
|
continue;
|
|
95
96
|
}
|
|
@@ -10,7 +10,9 @@ import {
|
|
|
10
10
|
type BaseToolCapability,
|
|
11
11
|
type DataHandlerResult,
|
|
12
12
|
type DataTool,
|
|
13
|
+
isOriginAllowedByLock,
|
|
13
14
|
type OriginLockCapability,
|
|
15
|
+
resolveOriginFromLock,
|
|
14
16
|
ToolName,
|
|
15
17
|
} from './Tool.js';
|
|
16
18
|
|
|
@@ -57,14 +59,15 @@ export class ListPageOriginsTool implements
|
|
|
57
59
|
const targetManager = SDK.TargetManager.TargetManager.instance();
|
|
58
60
|
const primaryPageTarget = targetManager.primaryPageTarget();
|
|
59
61
|
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
const originLock = context.getOriginLock();
|
|
63
|
+
const originResult = resolveOriginFromLock(originLock);
|
|
64
|
+
if ('error' in originResult) {
|
|
65
|
+
return originResult;
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
const pageOrigin =
|
|
66
69
|
primaryPageTarget ? SDK.SecurityOrigin.SecurityOrigin.create(primaryPageTarget.inspectedURL()) : null;
|
|
67
|
-
if (!pageOrigin || !pageOrigin
|
|
70
|
+
if (!pageOrigin || !isOriginAllowedByLock(originLock, pageOrigin)) {
|
|
68
71
|
return {error: 'No origin available or not allowed.'};
|
|
69
72
|
}
|
|
70
73
|
|
|
@@ -77,7 +80,7 @@ export class ListPageOriginsTool implements
|
|
|
77
80
|
// Filter out frames that are not same-origin to the page's allowed origin.
|
|
78
81
|
// Under site isolation, frames can be hosted on different targets/processes,
|
|
79
82
|
// so we check the security origin of the frame directly instead of the target.
|
|
80
|
-
if (!frameOrigin
|
|
83
|
+
if (!isOriginAllowedByLock(originLock, frameOrigin)) {
|
|
81
84
|
continue;
|
|
82
85
|
}
|
|
83
86
|
if (!origins.some(existing => existing.isSameOriginWith(frameOrigin))) {
|
|
@@ -14,6 +14,8 @@ import {
|
|
|
14
14
|
type DataTool,
|
|
15
15
|
isOriginAllowedByLock,
|
|
16
16
|
type OriginLockCapability,
|
|
17
|
+
type OriginLockState,
|
|
18
|
+
resolveOriginFromLock,
|
|
17
19
|
ToolName,
|
|
18
20
|
} from './Tool.js';
|
|
19
21
|
|
|
@@ -75,8 +77,9 @@ export class ListSourcesTool implements
|
|
|
75
77
|
}
|
|
76
78
|
}
|
|
77
79
|
|
|
80
|
+
const originLock: OriginLockState = {status: 'ESTABLISHED_ORIGIN', origin: establishedOrigin};
|
|
78
81
|
return [...uiSourceCodes.values()].filter(
|
|
79
|
-
file => isOriginAllowedByLock(
|
|
82
|
+
file => isOriginAllowedByLock(originLock, FileContext.originForUISourceCode(file)));
|
|
80
83
|
}
|
|
81
84
|
|
|
82
85
|
static getSourceById(
|
|
@@ -85,9 +88,6 @@ export class ListSourcesTool implements
|
|
|
85
88
|
// eslint-disable-next-line @devtools/no-instance-of-migrated-singletons
|
|
86
89
|
workspace: Workspace.Workspace.WorkspaceImpl = Workspace.Workspace.WorkspaceImpl.instance(),
|
|
87
90
|
): Workspace.UISourceCode.UISourceCode|undefined {
|
|
88
|
-
if (establishedOrigin.isOpaque()) {
|
|
89
|
-
return undefined;
|
|
90
|
-
}
|
|
91
91
|
return ListSourcesTool.getUISourceCodes(establishedOrigin, workspace)
|
|
92
92
|
.find(file => ListSourcesTool.uiSourceCodeId.get(file) === id);
|
|
93
93
|
}
|
|
@@ -114,14 +114,12 @@ export class ListSourcesTool implements
|
|
|
114
114
|
_params: Record<string, never>,
|
|
115
115
|
context: BaseToolCapability&OriginLockCapability,
|
|
116
116
|
): Promise<DataHandlerResult<{files: SourceSummary[]}>> {
|
|
117
|
-
const
|
|
118
|
-
if (
|
|
119
|
-
return
|
|
120
|
-
error: 'Opaque origin not allowed',
|
|
121
|
-
};
|
|
117
|
+
const originResult = resolveOriginFromLock(context.getOriginLock());
|
|
118
|
+
if ('error' in originResult) {
|
|
119
|
+
return originResult;
|
|
122
120
|
}
|
|
123
121
|
|
|
124
|
-
const files = ListSourcesTool.getUISourceCodes(
|
|
122
|
+
const files = ListSourcesTool.getUISourceCodes(originResult.origin);
|
|
125
123
|
|
|
126
124
|
return {
|
|
127
125
|
result: {
|
|
@@ -7,7 +7,8 @@ import * as i18n from '../../../core/i18n/i18n.js';
|
|
|
7
7
|
import * as Platform from '../../../core/platform/platform.js';
|
|
8
8
|
import * as SDK from '../../../core/sdk/sdk.js';
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import {resolveAllowedTargetOrigins} from './CookieUtils.js';
|
|
11
|
+
import {resolveDOMStorages} from './DOMStorageUtils.js';
|
|
11
12
|
import {
|
|
12
13
|
type BaseToolCapability,
|
|
13
14
|
type DataHandlerResult,
|
|
@@ -98,31 +99,11 @@ export class ListStorageKeysTool implements DataTool<ListStorageKeysArgs, ListSt
|
|
|
98
99
|
|
|
99
100
|
// eslint-disable-next-line @devtools/no-instance-of-migrated-singletons
|
|
100
101
|
const targetManager = SDK.TargetManager.TargetManager.instance();
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (!establishedOrigin || establishedOrigin.isOpaque()) {
|
|
105
|
-
return {error: 'No origin available or not allowed.'};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (!primaryPageTarget) {
|
|
109
|
-
return {error: 'No origin available or not allowed.'};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const pageOrigin = primaryPageTarget.inspectedSecurityOrigin();
|
|
113
|
-
if (!pageOrigin.isSameOriginWith(establishedOrigin)) {
|
|
114
|
-
return {error: 'No origin available or not allowed.'};
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const candidateOrigins: SDK.SecurityOrigin.SecurityOrigin[] = (args.origins && args.origins.length > 0) ?
|
|
118
|
-
args.origins.map(origin => SDK.SecurityOrigin.SecurityOrigin.create(origin)) :
|
|
119
|
-
[establishedOrigin];
|
|
120
|
-
const validOrigins =
|
|
121
|
-
candidateOrigins.filter(origin => origin.isSameOriginWith(establishedOrigin)).map(origin => origin.siteId());
|
|
122
|
-
const targetOrigins = Array.from(new Set(validOrigins)).slice(0, MAX_TARGET_ORIGINS);
|
|
123
|
-
if (targetOrigins.length === 0) {
|
|
124
|
-
return {error: 'No valid origins found.'};
|
|
102
|
+
const targetOriginsResult = resolveAllowedTargetOrigins(args.origins, context, targetManager);
|
|
103
|
+
if ('error' in targetOriginsResult) {
|
|
104
|
+
return {error: targetOriginsResult.error};
|
|
125
105
|
}
|
|
106
|
+
const {targetOrigins, primaryPageTarget} = targetOriginsResult;
|
|
126
107
|
|
|
127
108
|
const storageKey = (targetOrigins.length === 1 && args.storageKey) ? args.storageKey : undefined;
|
|
128
109
|
const storageKeysByOrigin: ListStorageKeysResult['storageKeysByOrigin'] = {};
|
|
@@ -16,10 +16,12 @@ Instead of passing a monolithic "grab-bag" context object to all tool handlers,
|
|
|
16
16
|
- `BaseToolCapability`: Base interface providing access to the top-level conversation context step.
|
|
17
17
|
- `PageExecutionCapability`: For tools executing JavaScript code on the inspected page.
|
|
18
18
|
- `StyleMutationCapability`: For tools managing and applying style mutations via a `ChangeManager`.
|
|
19
|
-
- `OriginLockCapability`: Enforces origin boundaries for tools that access inspected page state or resources
|
|
20
|
-
-
|
|
19
|
+
- `OriginLockCapability`: Enforces origin boundaries for tools that access inspected page state or resources via `getOriginLock(): OriginLockState` (`ESTABLISHED_ORIGIN`, `BLOCKED_BY_NAVIGATION`, or `UNINITIALIZED`).
|
|
20
|
+
- Tools check for `BLOCKED_BY_NAVIGATION` (cross-origin navigation during the run) and `UNINITIALIZED` (no lock established yet) states and return appropriate errors.
|
|
21
|
+
- For DOM nodes, source files, and execution contexts: validate target origins using `isOriginAllowedByLock(originLock, targetOrigin)`.
|
|
21
22
|
- For cookies and DOM storage: validate target origins using `resolveAllowedTargetOrigins()`.
|
|
22
|
-
- For network requests: compare `request.initiatorSecurityOrigin()
|
|
23
|
+
- For network requests: compare request initiators using `isOriginAllowedByLock(originLock, request.initiatorSecurityOrigin())`.
|
|
24
|
+
- Transition from legacy V1: Tools no longer inspect context wrappers (such as `DOMNodeContext.isOriginAllowed()`). Tools validate `SecurityOrigin` instances directly against `OriginLockState`. Legacy V1 agents provide an adapter callback in their tool invocation contexts to satisfy `OriginLockCapability`.
|
|
23
25
|
|
|
24
26
|
### Unified Context
|
|
25
27
|
|
|
@@ -108,10 +108,9 @@ export class ResolveDevtoolsNodePathTool implements DataTool<ResolveDevtoolsNode
|
|
|
108
108
|
return {error: 'Error: Could not retrieve resolved node.'};
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
const establishedOrigin = context.getEstablishedOrigin();
|
|
112
111
|
// Security check: Ensure the resolved node belongs to the same origin
|
|
113
112
|
// that this AI assistance session is locked to, preventing cross-origin access.
|
|
114
|
-
if (!isOriginAllowedByLock(
|
|
113
|
+
if (!isOriginAllowedByLock(context.getOriginLock(), node.securityOrigin())) {
|
|
115
114
|
return {error: 'Error: Node does not belong to the current origin.'};
|
|
116
115
|
}
|
|
117
116
|
|
|
@@ -113,43 +113,81 @@ export interface TargetCapability {
|
|
|
113
113
|
* so tools can resolve DOM nodes and frame hierarchies across frames.
|
|
114
114
|
*
|
|
115
115
|
* Tools that consume this target must independently validate the security origin of
|
|
116
|
-
* any resolved entities (e.g. via `node.securityOrigin()`) against `
|
|
116
|
+
* any resolved entities (e.g. via `node.securityOrigin()`) against `getOriginLock()`.
|
|
117
117
|
*/
|
|
118
118
|
getTarget(): SDK.Target.Target|null;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Origin-locking state for AI Assistance:
|
|
123
|
+
* - `ESTABLISHED_ORIGIN`: The conversation is bound to a specific origin. Operations are
|
|
124
|
+
* limited to this origin.
|
|
125
|
+
* - `BLOCKED_BY_NAVIGATION`: An unapproved cross-origin navigation occurred during the
|
|
126
|
+
* active run. Origin-restricted operations are blocked and must return an error.
|
|
127
|
+
* - `UNINITIALIZED`: No origin lock has been established yet. Operations requiring a locked
|
|
128
|
+
* origin must return an error.
|
|
129
|
+
*/
|
|
130
|
+
export type OriginLockState = {
|
|
131
|
+
status: 'ESTABLISHED_ORIGIN',
|
|
132
|
+
origin: SDK.SecurityOrigin.SecurityOrigin,
|
|
133
|
+
}|{
|
|
134
|
+
status: 'BLOCKED_BY_NAVIGATION',
|
|
135
|
+
}|{
|
|
136
|
+
status: 'UNINITIALIZED',
|
|
137
|
+
};
|
|
138
|
+
|
|
121
139
|
/**
|
|
122
140
|
* Capability for tools that enforce conversation origin boundaries.
|
|
123
141
|
*/
|
|
124
142
|
export interface OriginLockCapability {
|
|
125
143
|
/**
|
|
126
|
-
* Returns the
|
|
127
|
-
*
|
|
128
|
-
* TODO: When V1 agents (StylingAgent, AccessibilityAgent) are removed,
|
|
129
|
-
* simplify getEstablishedOrigin() to return SDK.SecurityOrigin.SecurityOrigin
|
|
130
|
-
* non-optionally.
|
|
131
|
-
*
|
|
132
|
-
* @returns The established {@link SDK.SecurityOrigin.SecurityOrigin}, or `undefined`
|
|
133
|
-
* if the conversation is not yet locked to an origin (e.g. before the first query).
|
|
144
|
+
* Returns the current origin-locking state for the active conversation.
|
|
134
145
|
*/
|
|
135
|
-
|
|
146
|
+
getOriginLock(): OriginLockState;
|
|
136
147
|
}
|
|
137
148
|
|
|
138
149
|
/**
|
|
139
150
|
* Checks whether a target origin matches the established conversation origin lock.
|
|
140
|
-
*
|
|
151
|
+
* Returns `false` if the lock is not established, either origin is opaque, or the
|
|
152
|
+
* target origin does not match the established origin.
|
|
141
153
|
*/
|
|
142
154
|
export function isOriginAllowedByLock(
|
|
143
|
-
|
|
155
|
+
originLock: OriginLockState,
|
|
144
156
|
targetOrigin: SDK.SecurityOrigin.SecurityOrigin|null|undefined,
|
|
145
157
|
): boolean {
|
|
146
|
-
if (
|
|
158
|
+
if (originLock.status !== 'ESTABLISHED_ORIGIN') {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
if (originLock.origin.isOpaque()) {
|
|
147
162
|
return false;
|
|
148
163
|
}
|
|
149
164
|
if (!targetOrigin || targetOrigin.isOpaque()) {
|
|
150
165
|
return false;
|
|
151
166
|
}
|
|
152
|
-
return targetOrigin.isSameOriginWith(
|
|
167
|
+
return targetOrigin.isSameOriginWith(originLock.origin);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Resolves the conversation's established origin from the origin lock state.
|
|
172
|
+
* Returns an error object if origin access is blocked by navigation, the lock
|
|
173
|
+
* is uninitialized, or the established origin is opaque.
|
|
174
|
+
*/
|
|
175
|
+
export function resolveOriginFromLock(
|
|
176
|
+
originLock: OriginLockState,
|
|
177
|
+
): {origin: SDK.SecurityOrigin.SecurityOrigin}|{
|
|
178
|
+
error: string,
|
|
179
|
+
}
|
|
180
|
+
{
|
|
181
|
+
if (originLock.status === 'BLOCKED_BY_NAVIGATION') {
|
|
182
|
+
return {error: 'Cross-origin access blocked due to navigation.'};
|
|
183
|
+
}
|
|
184
|
+
if (originLock.status === 'UNINITIALIZED') {
|
|
185
|
+
return {error: 'No origin established for this conversation.'};
|
|
186
|
+
}
|
|
187
|
+
if (originLock.origin.isOpaque()) {
|
|
188
|
+
return {error: 'No origin available or not allowed.'};
|
|
189
|
+
}
|
|
190
|
+
return {origin: originLock.origin};
|
|
153
191
|
}
|
|
154
192
|
|
|
155
193
|
/**
|
|
@@ -0,0 +1,89 @@
|
|
|
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 Root from '../../core/root/root.js';
|
|
6
|
+
import type * as CommentManager from '../comment_manager/comment_manager.js';
|
|
7
|
+
|
|
8
|
+
export type ChangeRecord = CommentManager.CommentManager.ChangeRecord;
|
|
9
|
+
|
|
10
|
+
export const MAX_RECORDS = 1000;
|
|
11
|
+
|
|
12
|
+
export class ChangeTracker {
|
|
13
|
+
readonly #records = new Map<string, ChangeRecord>();
|
|
14
|
+
readonly #commentManager: CommentManager.CommentManager.CommentManager;
|
|
15
|
+
readonly #maxRecords: number;
|
|
16
|
+
#lastRecord?: ChangeRecord;
|
|
17
|
+
|
|
18
|
+
constructor(commentManager: CommentManager.CommentManager.CommentManager, maxRecords: number = MAX_RECORDS) {
|
|
19
|
+
this.#commentManager = commentManager;
|
|
20
|
+
this.#maxRecords = Math.max(1, maxRecords);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get maxRecords(): number {
|
|
24
|
+
return this.#maxRecords;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get isTracking(): boolean {
|
|
28
|
+
return Boolean(Root.Runtime.hostConfig.devToolsComments?.enabled);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
trackChange(
|
|
32
|
+
description: string,
|
|
33
|
+
anchor: CommentManager.CommentManager.CommentAnchorSignature,
|
|
34
|
+
): ChangeRecord|null {
|
|
35
|
+
if (!this.isTracking) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const record: ChangeRecord = {
|
|
39
|
+
id: crypto.randomUUID(),
|
|
40
|
+
description,
|
|
41
|
+
timestamp: Date.now(),
|
|
42
|
+
};
|
|
43
|
+
while (this.#records.size >= this.#maxRecords) {
|
|
44
|
+
this.#evictOldestRecord();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const thread = this.#commentManager.createCommentThread(anchor, undefined, undefined, [record]);
|
|
48
|
+
this.#records.set(thread.id, record);
|
|
49
|
+
this.#lastRecord = record;
|
|
50
|
+
return record;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
#evictOldestRecord(): void {
|
|
54
|
+
// Delete the oldest record for which the comment thread has no user comments.
|
|
55
|
+
// If all comment threads have user comments, delete the oldest record.
|
|
56
|
+
for (const threadId of this.#records.keys()) {
|
|
57
|
+
const thread = this.#commentManager.getCommentThread(threadId);
|
|
58
|
+
if (!thread || thread.comments.length === 0) {
|
|
59
|
+
this.#commentManager.removeCommentThread(threadId);
|
|
60
|
+
this.#records.delete(threadId);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const oldestThreadId = this.#records.keys().next().value;
|
|
66
|
+
if (oldestThreadId) {
|
|
67
|
+
this.#records.delete(oldestThreadId);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
getChanges(): ChangeRecord[] {
|
|
72
|
+
return Array.from(this.#records.values());
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
getLastChange(): ChangeRecord|undefined {
|
|
76
|
+
return this.#lastRecord;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
clear(): void {
|
|
80
|
+
for (const threadId of this.#records.keys()) {
|
|
81
|
+
const thread = this.#commentManager.getCommentThread(threadId);
|
|
82
|
+
if (!thread || thread.comments.length === 0) {
|
|
83
|
+
this.#commentManager.removeCommentThread(threadId);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
this.#records.clear();
|
|
87
|
+
this.#lastRecord = undefined;
|
|
88
|
+
}
|
|
89
|
+
}
|