chrome-devtools-frontend 1.0.1643855 → 1.0.1645245
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/front_end/core/host/UserMetrics.ts +3 -2
- package/front_end/core/sdk/CSSPropertyParserMatchers.ts +2 -3
- package/front_end/core/sdk/NetworkRequest.ts +0 -1
- package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshot.ts +37 -0
- package/front_end/generated/SupportedCSSProperties.js +4 -2
- package/front_end/models/ai_assistance/AiAgent2.ts +23 -13
- package/front_end/models/ai_assistance/README.md +5 -4
- package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +15 -0
- package/front_end/models/ai_assistance/agents/README.md +57 -0
- package/front_end/models/ai_assistance/agents/StylingAgent.ts +26 -3
- package/front_end/models/ai_assistance/tools/ExecuteJavaScript.ts +9 -12
- package/front_end/models/ai_assistance/tools/GetStyles.ts +19 -12
- package/front_end/models/ai_assistance/tools/README.md +45 -0
- package/front_end/models/ai_assistance/tools/Tool.ts +74 -11
- package/front_end/models/ai_assistance/tools/ToolRegistry.ts +21 -5
- package/front_end/models/heap_snapshot/HeapSnapshotModel.ts +18 -2
- package/front_end/models/heap_snapshot/HeapSnapshotProxy.ts +4 -0
- package/front_end/models/stack_trace/DetailedErrorStackParser.ts +17 -4
- package/front_end/models/stack_trace/StackTraceModel.ts +34 -1
- package/front_end/models/trace/Styles.ts +29 -7
- package/front_end/models/trace/handlers/PageLoadMetricsHandler.ts +33 -4
- package/front_end/models/trace/helpers/Timing.ts +10 -0
- package/front_end/models/trace/types/TraceEvents.ts +22 -2
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +8 -2
- package/front_end/panels/ai_assistance/ai_assistance-meta.ts +16 -0
- package/front_end/panels/application/ApplicationPanelSidebar.ts +44 -0
- package/front_end/panels/application/ServiceWorkersView.ts +2 -2
- package/front_end/panels/application/WebMCPView.ts +0 -1
- package/front_end/panels/application/components/BackForwardCacheView.ts +1 -2
- package/front_end/panels/console/ConsoleView.ts +6 -1
- package/front_end/panels/console/ConsoleViewMessage.ts +46 -213
- package/front_end/panels/console/SymbolizedErrorWidget.ts +4 -1
- package/front_end/panels/elements/AdoptedStyleSheetTreeElement.ts +0 -1
- package/front_end/panels/elements/ElementsTreeElement.ts +0 -2
- package/front_end/panels/elements/PropertyRenderer.ts +0 -1
- package/front_end/panels/elements/StylesSidebarPane.ts +9 -2
- package/front_end/panels/issues/AffectedResourcesView.ts +1 -1
- package/front_end/panels/issues/AffectedSourcesView.ts +1 -1
- package/front_end/panels/lighthouse/LighthouseReportRenderer.ts +0 -1
- package/front_end/panels/mobile_throttling/ThrottlingSettingsTab.ts +10 -0
- package/front_end/panels/network/NetworkDataGridNode.ts +1 -2
- package/front_end/panels/network/NetworkLogView.ts +34 -7
- package/front_end/panels/profiler/HeapProfileView.ts +0 -1
- package/front_end/panels/profiler/HeapSnapshotGridNodes.ts +0 -1
- package/front_end/panels/profiler/HeapSnapshotView.ts +1 -1
- package/front_end/panels/settings/components/SyncSection.ts +1 -1
- package/front_end/panels/timeline/TimelineFlameChartView.ts +5 -4
- package/front_end/panels/timeline/TimelinePanel.ts +7 -0
- package/front_end/panels/timeline/TimelineUIUtils.ts +13 -14
- package/front_end/panels/timeline/TimingsTrackAppender.ts +7 -5
- package/front_end/panels/timeline/components/LayoutShiftDetails.ts +0 -1
- package/front_end/panels/timeline/components/NetworkRequestDetails.ts +0 -2
- package/front_end/panels/timeline/components/insights/ForcedReflow.ts +0 -1
- package/front_end/panels/timeline/components/insights/NodeLink.ts +0 -1
- package/front_end/panels/timeline/overlays/OverlaysImpl.ts +2 -0
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/ui/helpers/OpenInNewTab.ts +3 -3
- package/front_end/ui/kit/link/Link.ts +16 -2
- package/front_end/ui/legacy/InspectorDrawerView.ts +14 -5
- package/front_end/ui/legacy/InspectorView.ts +4 -1
- package/front_end/ui/legacy/PlusButton.ts +6 -1
- package/front_end/ui/legacy/Widget.ts +19 -1
- package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +95 -31
- package/front_end/ui/legacy/components/utils/JSPresentationUtils.ts +0 -1
- package/front_end/ui/legacy/components/utils/Linkifier.ts +2 -16
- package/front_end/ui/visual_logging/KnownContextValues.ts +5 -0
- package/package.json +1 -1
|
@@ -9,22 +9,76 @@ import type {executeJsCode} from '../agents/ExecuteJavascript.js';
|
|
|
9
9
|
import type {ChangeManager} from '../ChangeManager.js';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* Base capability for all tool contexts, providing access to the conversation context.
|
|
13
13
|
*/
|
|
14
|
-
export interface
|
|
14
|
+
export interface BaseToolCapability {
|
|
15
|
+
/**
|
|
16
|
+
* The active context for the current conversation step, if any.
|
|
17
|
+
*/
|
|
15
18
|
conversationContext: ConversationContext<unknown>|null;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Capability for tools that need to execute JavaScript code on the inspected page.
|
|
23
|
+
*/
|
|
24
|
+
export interface PageExecutionCapability {
|
|
25
|
+
/**
|
|
26
|
+
* Function to execute JavaScript code in the page context.
|
|
27
|
+
*/
|
|
28
|
+
execJs: typeof executeJsCode;
|
|
29
|
+
|
|
21
30
|
/**
|
|
22
31
|
* Returns the DOM node that acts as the execution context (i.e. `$0` inside the execution context)
|
|
23
32
|
* for running JavaScript.
|
|
24
33
|
*/
|
|
25
|
-
getExecutionContextNode
|
|
34
|
+
getExecutionContextNode(): SDK.DOMModel.DOMNode|null;
|
|
26
35
|
}
|
|
27
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Capability for tools that need to manage and apply style mutations to the page.
|
|
39
|
+
*/
|
|
40
|
+
export interface StyleMutationCapability {
|
|
41
|
+
/**
|
|
42
|
+
* The change manager for tracking and applying style changes.
|
|
43
|
+
*/
|
|
44
|
+
changeManager: ChangeManager;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Creates an extension scope for applying changes, ensuring they can be uninstalled when done.
|
|
48
|
+
*/
|
|
49
|
+
createExtensionScope(changes: ChangeManager): {
|
|
50
|
+
install(): Promise<void>,
|
|
51
|
+
uninstall(): Promise<void>,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Capability for tools that need access to the current SDK Target of the inspected page.
|
|
57
|
+
*/
|
|
58
|
+
export interface TargetCapability {
|
|
59
|
+
/**
|
|
60
|
+
* Returns the current SDK Target for the inspected page.
|
|
61
|
+
*/
|
|
62
|
+
getTarget(): SDK.Target.Target|null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Capability for tools that need to enforce origin locking for security.
|
|
67
|
+
*/
|
|
68
|
+
export interface OriginLockCapability {
|
|
69
|
+
/**
|
|
70
|
+
* Returns the origin that the current conversation is locked to, if any.
|
|
71
|
+
*/
|
|
72
|
+
getEstablishedOrigin(): string|undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Unified context interface providing all capabilities available in the project.
|
|
77
|
+
* Used by the agent to pass a complete context to any tool type-safely.
|
|
78
|
+
*/
|
|
79
|
+
export type AllToolsContext =
|
|
80
|
+
BaseToolCapability&PageExecutionCapability&StyleMutationCapability&TargetCapability&OriginLockCapability;
|
|
81
|
+
|
|
28
82
|
/**
|
|
29
83
|
* Base argument type for AI Tools.
|
|
30
84
|
*/
|
|
@@ -50,21 +104,30 @@ export interface BaseTool {
|
|
|
50
104
|
|
|
51
105
|
/**
|
|
52
106
|
* Main generic interface for defining a Tool.
|
|
53
|
-
* Binds the parameter schema properties and the handler implementation to a strict `Args` contract.
|
|
107
|
+
* Binds the parameter schema properties and the handler implementation to a strict `Args` and `ContextType` contract.
|
|
54
108
|
*
|
|
55
109
|
* @template Args - The expected object type for tool arguments. Must be an object type.
|
|
56
110
|
* @template ReturnType - The type of data returned by the handler function.
|
|
111
|
+
* @template ContextType - The interface defining the capabilities this tool requires. Defaults to `BaseToolCapability`.
|
|
57
112
|
*/
|
|
58
|
-
export interface Tool<Args extends ToolArgs = ToolArgs, ReturnType = unknown,
|
|
113
|
+
export interface Tool<Args extends ToolArgs = ToolArgs, ReturnType = unknown,
|
|
114
|
+
ContextType extends BaseToolCapability = BaseToolCapability> extends BaseTool {
|
|
59
115
|
readonly parameters: Host.AidaClient.FunctionObjectParam<keyof Args>;
|
|
60
116
|
readonly displayInfoFromArgs?: (
|
|
61
117
|
args: Args,
|
|
62
118
|
) => {
|
|
63
119
|
title?: string, thought?: string, action?: string, suggestions?: [string, ...string[]],
|
|
64
120
|
};
|
|
121
|
+
/**
|
|
122
|
+
* The implementation function called when the AI invokes this tool.
|
|
123
|
+
*
|
|
124
|
+
* @param args The arguments provided by the AI model matching the tool's parameter schema.
|
|
125
|
+
* @param context The context object providing the capabilities requested by `ContextType`.
|
|
126
|
+
* @param options Additional runtime options for the handler execution.
|
|
127
|
+
*/
|
|
65
128
|
handler(
|
|
66
129
|
args: Args,
|
|
67
|
-
context:
|
|
130
|
+
context: ContextType,
|
|
68
131
|
options?: FunctionHandlerOptions,
|
|
69
132
|
): Promise<FunctionCallHandlerResult<ReturnType>>;
|
|
70
133
|
}
|
|
@@ -4,11 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
import {ExecuteJavaScriptTool} from './ExecuteJavaScript.js';
|
|
6
6
|
import {GetStylesTool} from './GetStyles.js';
|
|
7
|
-
import {type Tool, ToolName} from './Tool.js';
|
|
7
|
+
import {type AllToolsContext, type Tool, type ToolArgs, ToolName} from './Tool.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Plain object registry containing concrete instantiated tools.
|
|
11
|
-
*
|
|
11
|
+
*
|
|
12
|
+
* This object is deliberately declared as a plain object without an explicit type annotation
|
|
13
|
+
* (like `Record<ToolName, Tool>`) to preserve the exact concrete type of each registered tool.
|
|
14
|
+
* This is required to support compile-time type safety and inference in the overloaded
|
|
15
|
+
* `ToolRegistry.get()` method, which maps a literal `ToolName` key to its specific class type.
|
|
12
16
|
*/
|
|
13
17
|
export const TOOLS = {
|
|
14
18
|
[ToolName.EXECUTE_JAVASCRIPT]: new ExecuteJavaScriptTool(),
|
|
@@ -23,14 +27,26 @@ export class ToolRegistry {
|
|
|
23
27
|
* Retrieves a tool by its literal name with 100% type safety.
|
|
24
28
|
*
|
|
25
29
|
* @template K - A key from the `TOOLS` registry.
|
|
30
|
+
* @param name The literal name of the tool to retrieve.
|
|
26
31
|
* @returns The concrete class type of the requested tool.
|
|
27
32
|
*/
|
|
28
33
|
static get<K extends keyof typeof TOOLS>(name: K): typeof TOOLS[K];
|
|
29
34
|
/**
|
|
30
35
|
* Fallback retrieval signature for general or runtime string lookups.
|
|
36
|
+
*
|
|
37
|
+
* @param name The string name of the tool to retrieve, used when the tool name is only known at runtime.
|
|
38
|
+
* @returns The generic Tool interface, or undefined if not found.
|
|
31
39
|
*/
|
|
32
|
-
static get(name: string): Tool
|
|
33
|
-
static get(name: string): Tool
|
|
34
|
-
|
|
40
|
+
static get(name: string): Tool<ToolArgs, unknown, AllToolsContext>|undefined;
|
|
41
|
+
static get(name: string): Tool<ToolArgs, unknown, AllToolsContext>|undefined {
|
|
42
|
+
// We use a double assertion (`as unknown as Tool<...>`) here. TypeScript's variance
|
|
43
|
+
// rules prevent direct casting from specific concrete tools (which have narrowed,
|
|
44
|
+
// capability-specific contexts) to the generic `Tool` signature that uses `AllToolsContext`.
|
|
45
|
+
// This cast is runtime-safe because any capability requested by a specific tool is
|
|
46
|
+
// guaranteed to be satisfied by `AllToolsContext`, and the handler will only access
|
|
47
|
+
// the capabilities it expects.
|
|
48
|
+
return Object.prototype.hasOwnProperty.call(TOOLS, name) ?
|
|
49
|
+
TOOLS[name as keyof typeof TOOLS] as unknown as Tool<ToolArgs, unknown, AllToolsContext>:
|
|
50
|
+
undefined;
|
|
35
51
|
}
|
|
36
52
|
}
|
|
@@ -136,10 +136,16 @@ export class Diff {
|
|
|
136
136
|
removedCount = 0;
|
|
137
137
|
addedSize = 0;
|
|
138
138
|
removedSize = 0;
|
|
139
|
-
deletedIndexes: number[] = [];
|
|
140
|
-
addedIndexes: number[] = [];
|
|
141
139
|
countDelta!: number;
|
|
142
140
|
sizeDelta!: number;
|
|
141
|
+
// Data about added nodes
|
|
142
|
+
addedIndexes: number[] = [];
|
|
143
|
+
addedIds: number[] = [];
|
|
144
|
+
addedSelfSizes: number[] = [];
|
|
145
|
+
// Data about deleted nodes
|
|
146
|
+
deletedIndexes: number[] = [];
|
|
147
|
+
deletedIds: number[] = [];
|
|
148
|
+
deletedSelfSizes: number[] = [];
|
|
143
149
|
constructor(name: string) {
|
|
144
150
|
this.name = name;
|
|
145
151
|
}
|
|
@@ -287,3 +293,13 @@ export interface RetainingPaths {
|
|
|
287
293
|
siblings?: boolean,
|
|
288
294
|
};
|
|
289
295
|
}
|
|
296
|
+
|
|
297
|
+
export interface DominatorNode {
|
|
298
|
+
nodeId: number;
|
|
299
|
+
nodeIndex: number;
|
|
300
|
+
nodeName: string;
|
|
301
|
+
retainedSize: number;
|
|
302
|
+
selfSize: number;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export type DominatorChain = DominatorNode[];
|
|
@@ -370,6 +370,10 @@ export class HeapSnapshotProxy extends HeapSnapshotProxyObject {
|
|
|
370
370
|
return this.callMethodPromise('getRetainingPaths', nodeIndex, maxDepth, maxNodes, maxSiblings);
|
|
371
371
|
}
|
|
372
372
|
|
|
373
|
+
getDominatorsOf(nodeIndex: number): Promise<HeapSnapshotModel.DominatorChain> {
|
|
374
|
+
return this.callMethodPromise('getDominatorsOf', nodeIndex);
|
|
375
|
+
}
|
|
376
|
+
|
|
373
377
|
unignoreNodeInRetainersView(nodeIndex: number): Promise<void> {
|
|
374
378
|
return this.callMethodPromise('unignoreNodeInRetainersView', nodeIndex);
|
|
375
379
|
}
|
|
@@ -10,13 +10,15 @@ import type {RawFrame} from './Trie.js';
|
|
|
10
10
|
|
|
11
11
|
const CALL_FRAME_REGEX = /^\s*at\s+/;
|
|
12
12
|
|
|
13
|
+
export type ResolveURLCallback = (url: Platform.DevToolsPath.UrlString) => Platform.DevToolsPath.UrlString|null;
|
|
14
|
+
|
|
13
15
|
/**
|
|
14
16
|
* Takes a V8 Error#stack string and extracts structured information.
|
|
15
17
|
*
|
|
16
18
|
* @returns Null if the provided string has an unexpected format. A
|
|
17
19
|
* populated `RawFrame[]` otherwise.
|
|
18
20
|
*/
|
|
19
|
-
export function parseRawFramesFromErrorStack(stack: string): RawFrame[]|null {
|
|
21
|
+
export function parseRawFramesFromErrorStack(stack: string, resolveURL?: ResolveURLCallback): RawFrame[]|null {
|
|
20
22
|
const lines = stack.split('\n');
|
|
21
23
|
const firstAtLineIndex = findFramesStartLine(lines);
|
|
22
24
|
const rawFrames: RawFrame[] = [];
|
|
@@ -66,6 +68,8 @@ export function parseRawFramesFromErrorStack(stack: string): RawFrame[]|null {
|
|
|
66
68
|
if (lineContent.endsWith(')') && openParenIndex !== -1) {
|
|
67
69
|
functionName = lineContent.substring(0, openParenIndex).trim();
|
|
68
70
|
location = lineContent.substring(openParenIndex + 2, lineContent.length - 1);
|
|
71
|
+
} else if (lineContent.startsWith('(') && lineContent.endsWith(')')) {
|
|
72
|
+
location = lineContent.substring(1, lineContent.length - 1);
|
|
69
73
|
} else {
|
|
70
74
|
location = lineContent;
|
|
71
75
|
}
|
|
@@ -90,9 +94,9 @@ export function parseRawFramesFromErrorStack(stack: string): RawFrame[]|null {
|
|
|
90
94
|
if (innerOpenParen !== -1) {
|
|
91
95
|
evalFunctionName = evalOriginStr.substring(0, innerOpenParen).trim();
|
|
92
96
|
evalLocation = evalOriginStr.substring(innerOpenParen + 2, evalOriginStr.length - 1);
|
|
93
|
-
evalOrigin = parseRawFramesFromErrorStack(` at ${evalFunctionName} (${evalLocation})
|
|
97
|
+
evalOrigin = parseRawFramesFromErrorStack(` at ${evalFunctionName} (${evalLocation})`, resolveURL)?.[0];
|
|
94
98
|
} else {
|
|
95
|
-
evalOrigin = parseRawFramesFromErrorStack(` at ${evalFunctionName}
|
|
99
|
+
evalOrigin = parseRawFramesFromErrorStack(` at ${evalFunctionName}`, resolveURL)?.[0];
|
|
96
100
|
}
|
|
97
101
|
}
|
|
98
102
|
|
|
@@ -112,9 +116,18 @@ export function parseRawFramesFromErrorStack(stack: string): RawFrame[]|null {
|
|
|
112
116
|
}
|
|
113
117
|
} else if (location) {
|
|
114
118
|
const splitResult = Common.ParsedURL.ParsedURL.splitLineAndColumn(location);
|
|
115
|
-
url = splitResult.url;
|
|
116
119
|
lineNumber = splitResult.lineNumber ?? -1;
|
|
117
120
|
columnNumber = splitResult.columnNumber ?? -1;
|
|
121
|
+
|
|
122
|
+
if (resolveURL && splitResult.url !== '<anonymous>' && splitResult.url !== 'native') {
|
|
123
|
+
const resolved = resolveURL(splitResult.url);
|
|
124
|
+
if (!resolved) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
url = resolved;
|
|
128
|
+
} else {
|
|
129
|
+
url = splitResult.url;
|
|
130
|
+
}
|
|
118
131
|
}
|
|
119
132
|
|
|
120
133
|
// Handle "typeName.methodName [as alias]"
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// found in the LICENSE file.
|
|
4
4
|
|
|
5
5
|
import * as Common from '../../core/common/common.js';
|
|
6
|
+
import type * as Platform from '../../core/platform/platform.js';
|
|
6
7
|
import * as SDK from '../../core/sdk/sdk.js';
|
|
7
8
|
import type * as Protocol from '../../generated/protocol.js';
|
|
8
9
|
|
|
@@ -65,7 +66,17 @@ export class StackTraceModel extends SDK.SDKModel.SDKModel<unknown> {
|
|
|
65
66
|
async createFromErrorStackLikeString(
|
|
66
67
|
stack: string, rawFramesToUIFrames: TranslateRawFrames,
|
|
67
68
|
exceptionDetails?: Protocol.Runtime.ExceptionDetails): Promise<StackTrace.StackTrace.ParsedErrorStackTrace|null> {
|
|
68
|
-
const
|
|
69
|
+
const debuggerModel = this.target().model(SDK.DebuggerModel.DebuggerModel) as SDK.DebuggerModel.DebuggerModel;
|
|
70
|
+
const baseURL = this.target().inspectedURL();
|
|
71
|
+
const resolveURL = (url: Platform.DevToolsPath.UrlString): Platform.DevToolsPath.UrlString|null => {
|
|
72
|
+
let urlWithScheme = parseOrScriptMatch(debuggerModel, url);
|
|
73
|
+
if (!urlWithScheme && Common.ParsedURL.ParsedURL.isRelativeURL(url)) {
|
|
74
|
+
urlWithScheme = parseOrScriptMatch(debuggerModel, Common.ParsedURL.ParsedURL.completeURL(baseURL, url));
|
|
75
|
+
}
|
|
76
|
+
return urlWithScheme;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const rawFrames = parseRawFramesFromErrorStack(stack, resolveURL);
|
|
69
80
|
if (!rawFrames) {
|
|
70
81
|
return null;
|
|
71
82
|
}
|
|
@@ -267,4 +278,26 @@ async function translateEvalOrigin(
|
|
|
267
278
|
return new EvalOrigin(frames, parentEvalOrigin);
|
|
268
279
|
}
|
|
269
280
|
|
|
281
|
+
function parseOrScriptMatch(debuggerModel: SDK.DebuggerModel.DebuggerModel,
|
|
282
|
+
url: Platform.DevToolsPath.UrlString|null): Platform.DevToolsPath.UrlString|null {
|
|
283
|
+
if (!url) {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
if (Common.ParsedURL.ParsedURL.isValidUrlString(url)) {
|
|
287
|
+
return url;
|
|
288
|
+
}
|
|
289
|
+
if (debuggerModel.scriptsForSourceURL(url).length) {
|
|
290
|
+
return url;
|
|
291
|
+
}
|
|
292
|
+
// nodejs stack traces contain (absolute) file paths, but v8 reports them as file: urls.
|
|
293
|
+
try {
|
|
294
|
+
const fileUrl = new URL(url, 'file://');
|
|
295
|
+
if (debuggerModel.scriptsForSourceURL(fileUrl.href as Platform.DevToolsPath.UrlString).length) {
|
|
296
|
+
return fileUrl.href as Platform.DevToolsPath.UrlString;
|
|
297
|
+
}
|
|
298
|
+
} catch {
|
|
299
|
+
}
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
|
|
270
303
|
SDK.SDKModel.SDKModel.register(StackTraceModel, {capabilities: SDK.Target.Capability.NONE, autostart: false});
|
|
@@ -291,6 +291,10 @@ const UIStrings = {
|
|
|
291
291
|
* @description Text in Timeline UIUtils of the Performance panel
|
|
292
292
|
*/
|
|
293
293
|
frameStartedLoading: 'Frame started loading',
|
|
294
|
+
/**
|
|
295
|
+
* @description Text in Timeline UIUtils of the Performance panel
|
|
296
|
+
*/
|
|
297
|
+
softNavigationStart: 'Soft navigation start',
|
|
294
298
|
/**
|
|
295
299
|
* @description Text in Timeline UIUtils of the Performance panel
|
|
296
300
|
*/
|
|
@@ -307,6 +311,10 @@ const UIStrings = {
|
|
|
307
311
|
* @description Text in Timeline UIUtils of the Performance panel
|
|
308
312
|
*/
|
|
309
313
|
firstContentfulPaint: 'First Contentful Paint',
|
|
314
|
+
/**
|
|
315
|
+
* @description Text in Timeline UIUtils of the Performance panel
|
|
316
|
+
*/
|
|
317
|
+
softFirstContentfulPaint: 'Soft First Contentful Paint',
|
|
310
318
|
/**
|
|
311
319
|
* @description Text in Timeline UIUtils of the Performance panel
|
|
312
320
|
*/
|
|
@@ -866,6 +874,12 @@ export function maybeInitSylesMap(): EventStylesMap {
|
|
|
866
874
|
true,
|
|
867
875
|
),
|
|
868
876
|
|
|
877
|
+
[Types.Events.Name.SOFT_NAVIGATION_START]: new TimelineRecordStyle(
|
|
878
|
+
i18nString(UIStrings.softNavigationStart),
|
|
879
|
+
defaultCategoryStyles.loading,
|
|
880
|
+
true,
|
|
881
|
+
),
|
|
882
|
+
|
|
869
883
|
[Types.Events.Name.MARK_FIRST_PAINT]: new TimelineRecordStyle(
|
|
870
884
|
i18nString(UIStrings.firstPaint),
|
|
871
885
|
defaultCategoryStyles.painting,
|
|
@@ -878,6 +892,12 @@ export function maybeInitSylesMap(): EventStylesMap {
|
|
|
878
892
|
true,
|
|
879
893
|
),
|
|
880
894
|
|
|
895
|
+
[Types.Events.Name.MARK_SOFT_FCP]: new TimelineRecordStyle(
|
|
896
|
+
i18nString(UIStrings.softFirstContentfulPaint),
|
|
897
|
+
defaultCategoryStyles.rendering,
|
|
898
|
+
true,
|
|
899
|
+
),
|
|
900
|
+
|
|
881
901
|
[Types.Events.Name.MARK_LCP_CANDIDATE]: new TimelineRecordStyle(
|
|
882
902
|
i18nString(UIStrings.largestContentfulPaint),
|
|
883
903
|
defaultCategoryStyles.rendering,
|
|
@@ -1024,11 +1044,11 @@ export function maybeInitSylesMap(): EventStylesMap {
|
|
|
1024
1044
|
[Types.Events.Name.ASYNC_TASK]:
|
|
1025
1045
|
new TimelineRecordStyle(i18nString(UIStrings.asyncTask), defaultCategoryStyles.async),
|
|
1026
1046
|
|
|
1027
|
-
[Types.Events.Name.LAYOUT_SHIFT]:
|
|
1028
|
-
i18nString(UIStrings.layoutShift), defaultCategoryStyles.experience,
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1047
|
+
[Types.Events.Name.LAYOUT_SHIFT]:
|
|
1048
|
+
new TimelineRecordStyle(i18nString(UIStrings.layoutShift), defaultCategoryStyles.experience,
|
|
1049
|
+
/* Mark LayoutShifts as hidden; in the timeline we render
|
|
1050
|
+
* SyntheticLayoutShifts so those are the ones visible to the user */
|
|
1051
|
+
true),
|
|
1032
1052
|
|
|
1033
1053
|
[Types.Events.Name.SYNTHETIC_LAYOUT_SHIFT]:
|
|
1034
1054
|
new TimelineRecordStyle(i18nString(UIStrings.layoutShift), defaultCategoryStyles.experience),
|
|
@@ -1123,9 +1143,11 @@ export function markerDetailsForEvent(event: Types.Events.Event): {
|
|
|
1123
1143
|
} {
|
|
1124
1144
|
let title = '';
|
|
1125
1145
|
let color = 'var(--color-text-primary)';
|
|
1126
|
-
if (Types.Events.
|
|
1146
|
+
if (Types.Events.isAnyFirstContentfulPaint(event)) {
|
|
1127
1147
|
color = 'var(--sys-color-green-bright)';
|
|
1128
|
-
title =
|
|
1148
|
+
title = (Types.Events.isSoftFirstContentfulPaint(event)) ?
|
|
1149
|
+
Handlers.ModelHandlers.PageLoadMetrics.MetricName.SOFT_FCP :
|
|
1150
|
+
Handlers.ModelHandlers.PageLoadMetrics.MetricName.FCP;
|
|
1129
1151
|
}
|
|
1130
1152
|
if (Types.Events.isAnyLargestContentfulPaintCandidate(event)) {
|
|
1131
1153
|
color = 'var(--sys-color-green)';
|
|
@@ -75,6 +75,28 @@ export function handleEvent(event: Types.Events.Event): void {
|
|
|
75
75
|
return;
|
|
76
76
|
}
|
|
77
77
|
pageLoadEventsArray.push(event);
|
|
78
|
+
|
|
79
|
+
// A soft nav entry includes the Soft FCP details but we want to process both
|
|
80
|
+
// so push a separate Soft FCP event
|
|
81
|
+
if (Types.Events.isSoftNavigationStart(event) && event.args?.context?.firstContentfulPaint) {
|
|
82
|
+
const syntheticSoftFcpEvent = Helpers.SyntheticEvents.SyntheticEventsManager
|
|
83
|
+
.registerSyntheticEvent<Types.Events.SyntheticSoftFirstContentfulPaint>({
|
|
84
|
+
name: Types.Events.Name.MARK_SOFT_FCP,
|
|
85
|
+
ph: Types.Events.Phase.MARK,
|
|
86
|
+
rawSourceEvent: event,
|
|
87
|
+
pid: event.pid,
|
|
88
|
+
tid: event.tid,
|
|
89
|
+
ts: Types.Timing.Micro(event.args.context.firstContentfulPaint),
|
|
90
|
+
cat: event.cat,
|
|
91
|
+
args: {
|
|
92
|
+
frame: event.args.frame,
|
|
93
|
+
context: {
|
|
94
|
+
...event.args.context,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
pageLoadEventsArray.push(syntheticSoftFcpEvent);
|
|
99
|
+
}
|
|
78
100
|
}
|
|
79
101
|
|
|
80
102
|
function storePageLoadMetricAgainstNavigationId(
|
|
@@ -101,7 +123,7 @@ function storePageLoadMetricAgainstNavigationId(
|
|
|
101
123
|
return;
|
|
102
124
|
}
|
|
103
125
|
|
|
104
|
-
if (Types.Events.
|
|
126
|
+
if (Types.Events.isAnyFirstContentfulPaint(event)) {
|
|
105
127
|
const fcpTime = Types.Timing.Micro(event.ts - navigation.ts);
|
|
106
128
|
const classification = scoreClassificationForFirstContentfulPaint(fcpTime);
|
|
107
129
|
const metricScore = {event, metricName: MetricName.FCP, classification, navigation, timing: fcpTime};
|
|
@@ -226,7 +248,7 @@ function storeMetricScore(frameId: string, navigation: AnyNavigationStart, metri
|
|
|
226
248
|
}
|
|
227
249
|
|
|
228
250
|
export function getFrameIdForPageLoadEvent(event: Types.Events.PageLoadEvent): string {
|
|
229
|
-
if (Types.Events.
|
|
251
|
+
if (Types.Events.isAnyFirstContentfulPaint(event) || Types.Events.isInteractiveTime(event) ||
|
|
230
252
|
Types.Events.isAnyLargestContentfulPaintCandidate(event) || Types.Events.isNavigationStart(event) ||
|
|
231
253
|
Types.Events.isSoftNavigationStart(event) || Types.Events.isLayoutShift(event) ||
|
|
232
254
|
Types.Events.isFirstPaint(event)) {
|
|
@@ -243,7 +265,7 @@ export function getFrameIdForPageLoadEvent(event: Types.Events.PageLoadEvent): s
|
|
|
243
265
|
}
|
|
244
266
|
|
|
245
267
|
function getNavigationForPageLoadEvent(event: Types.Events.PageLoadEvent): AnyNavigationStart|null {
|
|
246
|
-
if (Types.Events.
|
|
268
|
+
if (Types.Events.isAnyFirstContentfulPaint(event) || Types.Events.isAnyLargestContentfulPaintCandidate(event) ||
|
|
247
269
|
Types.Events.isFirstPaint(event)) {
|
|
248
270
|
const {navigationsByNavigationId, softNavigationsById} = metaHandlerData();
|
|
249
271
|
|
|
@@ -255,6 +277,12 @@ function getNavigationForPageLoadEvent(event: Types.Events.PageLoadEvent): AnyNa
|
|
|
255
277
|
// The most recent soft navigation must have been before the trace started.
|
|
256
278
|
return null;
|
|
257
279
|
}
|
|
280
|
+
} else if (Types.Events.isSoftFirstContentfulPaint(event) && event.args.context?.performanceTimelineNavigationId) {
|
|
281
|
+
navigation = softNavigationsById.get(event.args.context.performanceTimelineNavigationId);
|
|
282
|
+
if (!navigation) {
|
|
283
|
+
// The most recent soft navigation must have been before the trace started.
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
258
286
|
} else {
|
|
259
287
|
const navigationId = event.args.data?.navigationId;
|
|
260
288
|
if (!navigationId) {
|
|
@@ -428,7 +456,7 @@ export async function finalize(): Promise<void> {
|
|
|
428
456
|
// Filter out LCP candidates to use only definitive LCP values
|
|
429
457
|
const allEventsButLCP =
|
|
430
458
|
pageLoadEventsArray.filter(event => !Types.Events.isAnyLargestContentfulPaintCandidate(event));
|
|
431
|
-
const markerEvents = [...
|
|
459
|
+
const markerEvents = [...allEventsButLCP, ...allFinalLCPEvents].filter(Types.Events.isMarkerEvent);
|
|
432
460
|
// Filter by main frame and sort.
|
|
433
461
|
allMarkerEvents =
|
|
434
462
|
markerEvents.filter(event => getFrameIdForPageLoadEvent(event) === mainFrame).sort((a, b) => a.ts - b.ts);
|
|
@@ -495,6 +523,7 @@ export const enum MetricName {
|
|
|
495
523
|
NAV = 'Nav',
|
|
496
524
|
// Soft Navigation and Soft Metrics
|
|
497
525
|
SOFT_NAV = 'Nav*',
|
|
526
|
+
SOFT_FCP = 'FCP*',
|
|
498
527
|
SOFT_LCP = 'LCP*',
|
|
499
528
|
// Note: INP is handled in UserInteractionsHandler
|
|
500
529
|
}
|
|
@@ -32,6 +32,16 @@ export function timeStampForEventAdjustedByClosestNavigation(
|
|
|
32
32
|
if (navigationForEvent) {
|
|
33
33
|
eventTimeStamp = event.ts - navigationForEvent.ts;
|
|
34
34
|
}
|
|
35
|
+
} else if (Types.Events.isSoftFirstContentfulPaint(event) && event.args?.context?.performanceTimelineNavigationId) {
|
|
36
|
+
const navigationForEvent = softNavigationsById.get(event.args.context.performanceTimelineNavigationId);
|
|
37
|
+
if (navigationForEvent) {
|
|
38
|
+
eventTimeStamp = event.ts - navigationForEvent.ts;
|
|
39
|
+
}
|
|
40
|
+
} else if (Types.Events.isSoftNavigationStart(event)) {
|
|
41
|
+
const navigationForEvent = getNavigationForTraceEvent(event, event.args.frame, navigationsByFrameId);
|
|
42
|
+
if (navigationForEvent) {
|
|
43
|
+
eventTimeStamp = event.ts - navigationForEvent.ts;
|
|
44
|
+
}
|
|
35
45
|
} else if (event.args?.data?.navigationId) {
|
|
36
46
|
const navigationForEvent = navigationsByNavigationId.get(event.args.data.navigationId);
|
|
37
47
|
if (navigationForEvent) {
|
|
@@ -737,6 +737,16 @@ export interface FirstContentfulPaint extends Mark {
|
|
|
737
737
|
};
|
|
738
738
|
}
|
|
739
739
|
|
|
740
|
+
// Soft FCP is basically a copy of SoftNavigationStart but with a different name
|
|
741
|
+
// and a different ts.
|
|
742
|
+
export interface SyntheticSoftFirstContentfulPaint extends Omit<SoftNavigationStart, 'name'|'ph'>,
|
|
743
|
+
Omit<SyntheticBased, 'name'|'ph'|'args'> {
|
|
744
|
+
name: Name.MARK_SOFT_FCP;
|
|
745
|
+
ph: Phase.MARK;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
export type AnyFirstContentfulPaint = FirstContentfulPaint|SyntheticSoftFirstContentfulPaint;
|
|
749
|
+
|
|
740
750
|
export interface FirstPaint extends Mark {
|
|
741
751
|
name: Name.MARK_FIRST_PAINT;
|
|
742
752
|
args: Args&{
|
|
@@ -747,14 +757,14 @@ export interface FirstPaint extends Mark {
|
|
|
747
757
|
};
|
|
748
758
|
}
|
|
749
759
|
|
|
750
|
-
export type PageLoadEvent =
|
|
760
|
+
export type PageLoadEvent = AnyFirstContentfulPaint|MarkDOMContent|InteractiveTime|AnyLargestContentfulPaintCandidate|
|
|
751
761
|
LayoutShift|FirstPaint|MarkLoad|NavigationStart|SoftNavigationStart;
|
|
752
762
|
|
|
753
763
|
const markerTypeGuards = [
|
|
754
764
|
isMarkDOMContent,
|
|
755
765
|
isMarkLoad,
|
|
756
766
|
isFirstPaint,
|
|
757
|
-
|
|
767
|
+
isAnyFirstContentfulPaint,
|
|
758
768
|
isAnyLargestContentfulPaintCandidate,
|
|
759
769
|
isNavigationStart,
|
|
760
770
|
isSoftNavigationStart,
|
|
@@ -765,6 +775,7 @@ export const MarkerName = [
|
|
|
765
775
|
Name.MARK_LOAD,
|
|
766
776
|
Name.MARK_FIRST_PAINT,
|
|
767
777
|
Name.MARK_FCP,
|
|
778
|
+
Name.MARK_SOFT_FCP,
|
|
768
779
|
Name.MARK_LCP_CANDIDATE,
|
|
769
780
|
Name.MARK_LCP_CANDIDATE_FOR_SOFT_NAVIGATION,
|
|
770
781
|
Name.NAVIGATION_START,
|
|
@@ -2285,6 +2296,14 @@ export function isFirstContentfulPaint(event: Event): event is FirstContentfulPa
|
|
|
2285
2296
|
return event.name === Name.MARK_FCP;
|
|
2286
2297
|
}
|
|
2287
2298
|
|
|
2299
|
+
export function isSoftFirstContentfulPaint(event: Event): event is SyntheticSoftFirstContentfulPaint {
|
|
2300
|
+
return event.name === Name.MARK_SOFT_FCP;
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
export function isAnyFirstContentfulPaint(event: Event): event is AnyFirstContentfulPaint {
|
|
2304
|
+
return event.name === Name.MARK_FCP || event.name === Name.MARK_SOFT_FCP;
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2288
2307
|
export function isAnyLargestContentfulPaintCandidate(event: Event): event is AnyLargestContentfulPaintCandidate {
|
|
2289
2308
|
return event.name === Name.MARK_LCP_CANDIDATE || event.name === Name.MARK_LCP_CANDIDATE_FOR_SOFT_NAVIGATION;
|
|
2290
2309
|
}
|
|
@@ -3168,6 +3187,7 @@ export const enum Name {
|
|
|
3168
3187
|
MARK_DOM_CONTENT = 'MarkDOMContent',
|
|
3169
3188
|
MARK_FIRST_PAINT = 'firstPaint',
|
|
3170
3189
|
MARK_FCP = 'firstContentfulPaint',
|
|
3190
|
+
MARK_SOFT_FCP = 'SyntheticSoftFirstContentfulPaint',
|
|
3171
3191
|
MARK_LCP_CANDIDATE = 'largestContentfulPaint::Candidate',
|
|
3172
3192
|
MARK_LCP_CANDIDATE_FOR_SOFT_NAVIGATION = 'largestContentfulPaint::CandidateForSoftNavigation',
|
|
3173
3193
|
MARK_LCP_INVALIDATE = 'largestContentfulPaint::Invalidate',
|
|
@@ -1575,7 +1575,12 @@ export class AiAssistancePanel extends UI.Panel.Panel {
|
|
|
1575
1575
|
break;
|
|
1576
1576
|
}
|
|
1577
1577
|
case 'ai-assistance.storage-floating-button': {
|
|
1578
|
-
Host.userMetrics.actionTaken(Host.UserMetrics.Action.
|
|
1578
|
+
Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiAssistanceOpenedFromApplicationPanelFloatingButton);
|
|
1579
|
+
targetConversationType = AiAssistanceModel.AiHistoryStorage.ConversationType.STORAGE;
|
|
1580
|
+
break;
|
|
1581
|
+
}
|
|
1582
|
+
case 'ai-assistance.application-panel-context': {
|
|
1583
|
+
Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiAssistanceOpenedFromApplicationPanel);
|
|
1579
1584
|
targetConversationType = AiAssistanceModel.AiHistoryStorage.ConversationType.STORAGE;
|
|
1580
1585
|
break;
|
|
1581
1586
|
}
|
|
@@ -2116,7 +2121,8 @@ export class ActionDelegate implements UI.ActionRegistration.ActionDelegate {
|
|
|
2116
2121
|
case 'drjones.performance-panel-context':
|
|
2117
2122
|
case 'drjones.sources-floating-button':
|
|
2118
2123
|
case 'drjones.sources-panel-context':
|
|
2119
|
-
case 'ai-assistance.storage-floating-button':
|
|
2124
|
+
case 'ai-assistance.storage-floating-button':
|
|
2125
|
+
case 'ai-assistance.application-panel-context': {
|
|
2120
2126
|
void (async () => {
|
|
2121
2127
|
const view = UI.ViewManager.ViewManager.instance().view(
|
|
2122
2128
|
AiAssistancePanel.panelName,
|
|
@@ -313,3 +313,19 @@ UI.ActionRegistration.registerActionExtension({
|
|
|
313
313
|
condition: config =>
|
|
314
314
|
isStorageAgentFeatureAvailable(config) && !isPolicyRestricted(config) && !isGeoRestricted(config),
|
|
315
315
|
});
|
|
316
|
+
|
|
317
|
+
UI.ActionRegistration.registerActionExtension({
|
|
318
|
+
actionId: 'ai-assistance.application-panel-context',
|
|
319
|
+
contextTypes(): [] {
|
|
320
|
+
return [];
|
|
321
|
+
},
|
|
322
|
+
category: UI.ActionRegistration.ActionCategory.GLOBAL,
|
|
323
|
+
title: i18nAiBrandedString(UIStrings.debugWithGemini, UIStrings.debugWithAi),
|
|
324
|
+
configurableBindings: false,
|
|
325
|
+
async loadActionDelegate() {
|
|
326
|
+
const AiAssistance = await loadAiAssistanceModule();
|
|
327
|
+
return new AiAssistance.ActionDelegate();
|
|
328
|
+
},
|
|
329
|
+
condition: config =>
|
|
330
|
+
isStorageAgentFeatureAvailable(config) && !isPolicyRestricted(config) && !isGeoRestricted(config),
|
|
331
|
+
});
|