chrome-devtools-frontend 1.0.1643099 → 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.
Files changed (99) hide show
  1. package/eslint.config.mjs +3 -1
  2. package/extension-api/ExtensionAPI.d.ts +83 -12
  3. package/front_end/core/host/UserMetrics.ts +3 -3
  4. package/front_end/core/root/ExperimentNames.ts +0 -1
  5. package/front_end/core/sdk/CSSPropertyParserMatchers.ts +2 -3
  6. package/front_end/core/sdk/ConsoleModel.ts +4 -0
  7. package/front_end/core/sdk/NetworkRequest.ts +12 -1
  8. package/front_end/core/sdk/SourceMap.ts +15 -18
  9. package/front_end/entrypoints/greendev_floaty/FloatyEntrypoint.ts +0 -2
  10. package/front_end/entrypoints/greendev_floaty/greendev_floaty.ts +0 -2
  11. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshot.ts +37 -0
  12. package/front_end/entrypoints/main/MainImpl.ts +0 -6
  13. package/front_end/generated/SupportedCSSProperties.js +4 -2
  14. package/front_end/models/ai_assistance/AiAgent2.ts +23 -12
  15. package/front_end/models/ai_assistance/README.md +5 -4
  16. package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +22 -16
  17. package/front_end/models/ai_assistance/agents/AiAgent.ts +19 -6
  18. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +5 -5
  19. package/front_end/models/ai_assistance/agents/ExecuteJavascript.ts +1 -94
  20. package/front_end/models/ai_assistance/agents/NetworkAgent.ts +25 -0
  21. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +37 -0
  22. package/front_end/models/ai_assistance/agents/README.md +57 -0
  23. package/front_end/models/ai_assistance/agents/StorageAgent.ts +54 -1
  24. package/front_end/models/ai_assistance/agents/StylingAgent.snapshot.txt +1 -2
  25. package/front_end/models/ai_assistance/agents/StylingAgent.ts +31 -3
  26. package/front_end/models/ai_assistance/tools/ExecuteJavaScript.ts +12 -21
  27. package/front_end/models/ai_assistance/tools/GetStyles.ts +19 -12
  28. package/front_end/models/ai_assistance/tools/README.md +45 -0
  29. package/front_end/models/ai_assistance/tools/Tool.ts +78 -9
  30. package/front_end/models/ai_assistance/tools/ToolRegistry.ts +21 -5
  31. package/front_end/models/bindings/DebuggerWorkspaceBinding.ts +6 -9
  32. package/front_end/models/bindings/DefaultScriptMapping.ts +2 -1
  33. package/front_end/models/bindings/SymbolizedError.ts +45 -35
  34. package/front_end/models/extensions/ExtensionAPI.ts +138 -47
  35. package/front_end/models/har/Importer.ts +1 -0
  36. package/front_end/models/heap_snapshot/HeapSnapshotModel.ts +18 -2
  37. package/front_end/models/heap_snapshot/HeapSnapshotProxy.ts +4 -0
  38. package/front_end/models/source_map_scopes/FunctionCodeResolver.ts +12 -2
  39. package/front_end/models/stack_trace/DetailedErrorStackParser.ts +58 -52
  40. package/front_end/models/stack_trace/StackTrace.ts +7 -0
  41. package/front_end/models/stack_trace/StackTraceImpl.ts +13 -4
  42. package/front_end/models/stack_trace/StackTraceModel.ts +43 -9
  43. package/front_end/models/trace/Styles.ts +29 -7
  44. package/front_end/models/trace/handlers/PageLoadMetricsHandler.ts +33 -4
  45. package/front_end/models/trace/helpers/Timing.ts +10 -0
  46. package/front_end/models/trace/types/TraceEvents.ts +22 -2
  47. package/front_end/panels/accessibility/AccessibilitySidebarView.ts +2 -1
  48. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +9 -2
  49. package/front_end/panels/ai_assistance/ai_assistance-meta.ts +16 -0
  50. package/front_end/panels/application/ApplicationPanelSidebar.ts +83 -0
  51. package/front_end/panels/application/ApplicationPanelTreeElement.ts +39 -0
  52. package/front_end/panels/application/CookieItemsView.ts +2 -2
  53. package/front_end/panels/application/ServiceWorkersView.ts +2 -2
  54. package/front_end/panels/application/WebMCPView.ts +0 -1
  55. package/front_end/panels/application/components/BackForwardCacheView.ts +1 -2
  56. package/front_end/panels/application/resourcesSidebar.css +11 -0
  57. package/front_end/panels/console/ConsoleView.ts +6 -1
  58. package/front_end/panels/console/ConsoleViewMessage.ts +46 -213
  59. package/front_end/panels/console/SymbolizedErrorWidget.ts +14 -8
  60. package/front_end/panels/elements/AdoptedStyleSheetTreeElement.ts +0 -1
  61. package/front_end/panels/elements/ElementsTreeElement.ts +0 -2
  62. package/front_end/panels/elements/PropertyRenderer.ts +0 -1
  63. package/front_end/panels/elements/StylesSidebarPane.ts +9 -2
  64. package/front_end/panels/issues/AffectedResourcesView.ts +1 -1
  65. package/front_end/panels/issues/AffectedSourcesView.ts +1 -1
  66. package/front_end/panels/lighthouse/LighthouseReportRenderer.ts +0 -1
  67. package/front_end/panels/mobile_throttling/ThrottlingSettingsTab.ts +10 -0
  68. package/front_end/panels/network/NetworkDataGridNode.ts +1 -2
  69. package/front_end/panels/network/NetworkLogView.ts +34 -7
  70. package/front_end/panels/profiler/HeapProfileView.ts +0 -1
  71. package/front_end/panels/profiler/HeapSnapshotGridNodes.ts +0 -1
  72. package/front_end/panels/profiler/HeapSnapshotView.ts +1 -1
  73. package/front_end/panels/settings/components/SyncSection.ts +1 -1
  74. package/front_end/panels/settings/emulation/DevicesSettingsTab.ts +1 -0
  75. package/front_end/panels/sources/SourcesPanel.ts +2 -1
  76. package/front_end/panels/timeline/TimelineFlameChartView.ts +5 -4
  77. package/front_end/panels/timeline/TimelinePanel.ts +7 -0
  78. package/front_end/panels/timeline/TimelineUIUtils.ts +13 -14
  79. package/front_end/panels/timeline/TimingsTrackAppender.ts +7 -5
  80. package/front_end/panels/timeline/components/LayoutShiftDetails.ts +0 -1
  81. package/front_end/panels/timeline/components/NetworkRequestDetails.ts +0 -2
  82. package/front_end/panels/timeline/components/insights/ForcedReflow.ts +0 -1
  83. package/front_end/panels/timeline/components/insights/NodeLink.ts +0 -1
  84. package/front_end/panels/timeline/overlays/OverlaysImpl.ts +2 -0
  85. package/front_end/third_party/chromium/README.chromium +1 -1
  86. package/front_end/ui/helpers/OpenInNewTab.ts +3 -3
  87. package/front_end/ui/kit/link/Link.ts +16 -2
  88. package/front_end/ui/legacy/InspectorDrawerView.ts +14 -5
  89. package/front_end/ui/legacy/InspectorView.ts +4 -1
  90. package/front_end/ui/legacy/PlusButton.ts +6 -1
  91. package/front_end/ui/legacy/StackedPane.ts +229 -0
  92. package/front_end/ui/legacy/ViewManager.ts +59 -169
  93. package/front_end/ui/legacy/Widget.ts +19 -1
  94. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +95 -31
  95. package/front_end/ui/legacy/components/utils/JSPresentationUtils.ts +0 -1
  96. package/front_end/ui/legacy/components/utils/Linkifier.ts +2 -16
  97. package/front_end/ui/legacy/legacy.ts +3 -1
  98. package/front_end/ui/visual_logging/KnownContextValues.ts +5 -0
  99. package/package.json +1 -1
@@ -203,7 +203,14 @@ function createFunctionCode(
203
203
  }
204
204
 
205
205
  /**
206
- * The input location may be a source mapped location or a raw location.
206
+ * Resolves the function code and its surrounding context for a given location.
207
+ *
208
+ * The input location (line, column) may be either an authored (source-mapped)
209
+ * location or a raw location. The function will attempt to resolve it to a
210
+ * raw location regardless. This is necessary because callers (such as AI
211
+ * assistance) may work with either format.
212
+ *
213
+ * We filter projects by `target` to prevent cross-origin leaks.
207
214
  */
208
215
  export async function getFunctionCodeFromLocation(
209
216
  target: SDK.Target.Target, url: Platform.DevToolsPath.UrlString, line: number, column: number,
@@ -213,10 +220,13 @@ export async function getFunctionCodeFromLocation(
213
220
  throw new Error('missing debugger model');
214
221
  }
215
222
 
216
- let uiSourceCode;
223
+ let uiSourceCode: Workspace.UISourceCode.UISourceCode|null = null;
217
224
  const debuggerWorkspaceBinding = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance();
218
225
  const projects = debuggerWorkspaceBinding.workspace.projectsForType(Workspace.Workspace.projectTypes.Network);
219
226
  for (const project of projects) {
227
+ if (Bindings.NetworkProject.NetworkProject.getTargetForProject(project) !== target) {
228
+ continue;
229
+ }
220
230
  uiSourceCode = project.uiSourceCodeForURL(url);
221
231
  if (uiSourceCode) {
222
232
  break;
@@ -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[] = [];
@@ -62,60 +64,70 @@ export function parseRawFramesFromErrorStack(stack: string): RawFrame[]|null {
62
64
  let evalOrigin: RawFrame|undefined;
63
65
 
64
66
  const openParenIndex = lineContent.indexOf(' (');
67
+ let location = '';
65
68
  if (lineContent.endsWith(')') && openParenIndex !== -1) {
66
69
  functionName = lineContent.substring(0, openParenIndex).trim();
67
- let location = lineContent.substring(openParenIndex + 2, lineContent.length - 1);
68
-
69
- if (location.startsWith('eval at ')) {
70
- isEval = true;
71
- const commaIndex = location.lastIndexOf(', ');
72
- let evalOriginStr = location;
73
- if (commaIndex !== -1) {
74
- evalOriginStr = location.substring(0, commaIndex);
75
- location = location.substring(commaIndex + 2);
76
- } else {
77
- location = '';
78
- }
70
+ location = lineContent.substring(openParenIndex + 2, lineContent.length - 1);
71
+ } else if (lineContent.startsWith('(') && lineContent.endsWith(')')) {
72
+ location = lineContent.substring(1, lineContent.length - 1);
73
+ } else {
74
+ location = lineContent;
75
+ }
79
76
 
80
- if (evalOriginStr.startsWith('eval at ')) {
81
- evalOriginStr = evalOriginStr.substring(8);
82
- }
83
- const innerOpenParen = evalOriginStr.indexOf(' (');
84
- let evalFunctionName = evalOriginStr;
85
- let evalLocation = '';
86
- if (innerOpenParen !== -1) {
87
- evalFunctionName = evalOriginStr.substring(0, innerOpenParen).trim();
88
- evalLocation = evalOriginStr.substring(innerOpenParen + 2, evalOriginStr.length - 1);
89
- evalOrigin = parseRawFramesFromErrorStack(` at ${evalFunctionName} (${evalLocation})`)?.[0];
90
- } else {
91
- evalOrigin = parseRawFramesFromErrorStack(` at ${evalFunctionName}`)?.[0];
92
- }
77
+ if (location.startsWith('eval at ')) {
78
+ isEval = true;
79
+ const commaIndex = location.lastIndexOf(', ');
80
+ let evalOriginStr = location;
81
+ if (commaIndex !== -1) {
82
+ evalOriginStr = location.substring(0, commaIndex);
83
+ location = location.substring(commaIndex + 2);
84
+ } else {
85
+ location = '';
93
86
  }
94
87
 
95
- if (location.startsWith('index ')) {
96
- promiseIndex = parseInt(location.substring(6), 10);
97
- url = '';
98
- } else if (location === '<anonymous>' || location === 'native') {
99
- url = '';
100
- } else if (location.includes(':wasm-function[')) {
101
- isWasm = true;
102
- const wasmMatch = /^(.*):wasm-function\[(\d+)\]:(0x[0-9a-fA-F]+)$/.exec(location);
103
- if (wasmMatch) {
104
- url = wasmMatch[1];
105
- wasmFunctionIndex = parseInt(wasmMatch[2], 10);
106
- columnNumber = parseInt(wasmMatch[3], 16);
107
- }
88
+ if (evalOriginStr.startsWith('eval at ')) {
89
+ evalOriginStr = evalOriginStr.substring(8);
90
+ }
91
+ const innerOpenParen = evalOriginStr.indexOf(' (');
92
+ let evalFunctionName = evalOriginStr;
93
+ let evalLocation = '';
94
+ if (innerOpenParen !== -1) {
95
+ evalFunctionName = evalOriginStr.substring(0, innerOpenParen).trim();
96
+ evalLocation = evalOriginStr.substring(innerOpenParen + 2, evalOriginStr.length - 1);
97
+ evalOrigin = parseRawFramesFromErrorStack(` at ${evalFunctionName} (${evalLocation})`, resolveURL)?.[0];
108
98
  } else {
109
- const splitResult = Common.ParsedURL.ParsedURL.splitLineAndColumn(location);
110
- url = splitResult.url;
111
- lineNumber = splitResult.lineNumber ?? -1;
112
- columnNumber = splitResult.columnNumber ?? -1;
99
+ evalOrigin = parseRawFramesFromErrorStack(` at ${evalFunctionName}`, resolveURL)?.[0];
113
100
  }
114
- } else {
115
- const splitResult = Common.ParsedURL.ParsedURL.splitLineAndColumn(lineContent);
116
- url = splitResult.url;
101
+ }
102
+
103
+ if (location.startsWith('index ')) {
104
+ promiseIndex = parseInt(location.substring(6), 10);
105
+ url = '';
106
+ } else if (location === '<anonymous>' || location === 'native') {
107
+ url = '';
108
+ } else if (location.includes(':wasm-function[')) {
109
+ isWasm = true;
110
+ const wasmMatch = /^(.*):wasm-function\[(\d+)\]:(0x[0-9a-fA-F]+)$/.exec(location);
111
+ if (wasmMatch) {
112
+ url = wasmMatch[1];
113
+ wasmFunctionIndex = parseInt(wasmMatch[2], 10);
114
+ columnNumber = parseInt(wasmMatch[3], 16);
115
+ lineNumber = 0;
116
+ }
117
+ } else if (location) {
118
+ const splitResult = Common.ParsedURL.ParsedURL.splitLineAndColumn(location);
117
119
  lineNumber = splitResult.lineNumber ?? -1;
118
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
+ }
119
131
  }
120
132
 
121
133
  // Handle "typeName.methodName [as alias]"
@@ -181,13 +193,7 @@ export function parseMessage(stack: string): string {
181
193
  export function augmentRawFramesWithScriptIds(
182
194
  rawFrames: RawFrame[], protocolStackTrace: Protocol.Runtime.StackTrace): void {
183
195
  function augmentFrame(rawFrame: RawFrame): void {
184
- const isWasm = rawFrame.isWasm;
185
196
  const protocolFrame = protocolStackTrace.callFrames.find(frame => {
186
- if (isWasm) {
187
- // The parser parses Wasm offsets into the `columnNumber` field. The `lineNumber` is always -1.
188
- // In the protocol trace, the `lineNumber` is 0 (for Wasm) and `columnNumber` is the bytecode offset.
189
- return rawFrame.url === frame.url && rawFrame.columnNumber === frame.columnNumber;
190
- }
191
197
  return rawFrame.url === frame.url && rawFrame.lineNumber === frame.lineNumber &&
192
198
  rawFrame.columnNumber === frame.columnNumber;
193
199
  });
@@ -48,6 +48,13 @@ export interface Frame {
48
48
  * Whether the corresponding raw frame is JS or WASM.
49
49
  */
50
50
  readonly isWasm?: boolean;
51
+ /**
52
+ * Whether this frame is an inlined frame. Used by SymbolizedErrorWidget
53
+ * to render the translated name (i.e. `name`) for inlined frames, and
54
+ * the physical name (i.e. `rawName`) for normal frames to preserve existing
55
+ * behavior.
56
+ */
57
+ readonly isInline?: boolean;
51
58
  }
52
59
 
53
60
  export interface ParsedErrorStackFrame extends Frame {
@@ -89,11 +89,12 @@ export class FrameImpl implements StackTrace.StackTrace.Frame {
89
89
  readonly missingDebugInfo?: StackTrace.StackTrace.MissingDebugInfo;
90
90
  readonly rawName?: string;
91
91
  readonly isWasm?: boolean;
92
+ readonly isInline?: boolean;
92
93
 
93
- constructor(
94
- url: string|undefined, uiSourceCode: Workspace.UISourceCode.UISourceCode|undefined, name: string|undefined,
95
- line: number, column: number, missingDebugInfo?: StackTrace.StackTrace.MissingDebugInfo, rawName?: string,
96
- isWasm?: boolean) {
94
+ constructor(url: string|undefined, uiSourceCode: Workspace.UISourceCode.UISourceCode|undefined,
95
+ name: string|undefined, line: number, column: number,
96
+ missingDebugInfo?: StackTrace.StackTrace.MissingDebugInfo, rawName?: string, isWasm?: boolean,
97
+ isInline?: boolean) {
97
98
  this.url = url;
98
99
  this.uiSourceCode = uiSourceCode;
99
100
  this.name = name;
@@ -102,6 +103,7 @@ export class FrameImpl implements StackTrace.StackTrace.Frame {
102
103
  this.missingDebugInfo = missingDebugInfo;
103
104
  this.rawName = rawName;
104
105
  this.isWasm = isWasm;
106
+ this.isInline = isInline;
105
107
  }
106
108
  }
107
109
 
@@ -197,6 +199,9 @@ export class ParsedErrorStackFrameImpl implements StackTrace.StackTrace.ParsedEr
197
199
  get isWasm(): boolean|undefined {
198
200
  return this.#frame.isWasm;
199
201
  }
202
+ get isInline(): boolean|undefined {
203
+ return this.#frame.isInline;
204
+ }
200
205
  get wasmModuleName(): string|undefined {
201
206
  return this.#parsedFrameInfo?.wasmModuleName;
202
207
  }
@@ -291,6 +296,10 @@ export class DebuggableFrameImpl implements StackTrace.StackTrace.DebuggableFram
291
296
  return this.#frame.isWasm;
292
297
  }
293
298
 
299
+ get isInline(): boolean|undefined {
300
+ return this.#frame.isInline;
301
+ }
302
+
294
303
  get sdkFrame(): SDK.DebuggerModel.CallFrame {
295
304
  return this.#sdkFrame;
296
305
  }
@@ -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 rawFrames = parseRawFramesFromErrorStack(stack);
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
  }
@@ -211,10 +222,11 @@ export class StackTraceModel extends SDK.SDKModel.SDKModel<unknown> {
211
222
  let i = 0;
212
223
  let evalI = 0;
213
224
  for (const node of fragment.node.getCallStack()) {
214
- node.frames = uiFrames[i++].map(
215
- frame => new FrameImpl(
216
- frame.url, frame.uiSourceCode, frame.name, frame.line, frame.column, frame.missingDebugInfo,
217
- node.rawFrame.functionName, node.rawFrame.isWasm));
225
+ const group = uiFrames[i++];
226
+ node.frames =
227
+ group.map((frame, index) => new FrameImpl(frame.url, frame.uiSourceCode, frame.name, frame.line, frame.column,
228
+ frame.missingDebugInfo, node.rawFrame.functionName,
229
+ node.rawFrame.isWasm, index < group.length - 1));
218
230
 
219
231
  if (node.parsedFrameInfo?.evalOrigin) {
220
232
  node.evalOrigin = evalOrigins[evalI++];
@@ -253,10 +265,10 @@ async function translateEvalOrigin(
253
265
  rawFrame: RawFrame, rawFramesToUIFrames: TranslateRawFrames,
254
266
  target: SDK.Target.Target): Promise<EvalOrigin|undefined> {
255
267
  const uiFrames = await rawFramesToUIFrames([rawFrame], target);
256
- const frames = uiFrames[0].map(
257
- frame => new FrameImpl(
258
- frame.url, frame.uiSourceCode, frame.name, frame.line, frame.column, frame.missingDebugInfo,
259
- rawFrame.functionName, rawFrame.isWasm));
268
+ const group = uiFrames[0];
269
+ const frames = group.map((frame, index) => new FrameImpl(frame.url, frame.uiSourceCode, frame.name, frame.line,
270
+ frame.column, frame.missingDebugInfo, rawFrame.functionName,
271
+ rawFrame.isWasm, index < group.length - 1));
260
272
 
261
273
  let parentEvalOrigin: EvalOrigin|undefined;
262
274
  if (rawFrame.parsedFrameInfo?.evalOrigin) {
@@ -266,4 +278,26 @@ async function translateEvalOrigin(
266
278
  return new EvalOrigin(frames, parentEvalOrigin);
267
279
  }
268
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
+
269
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]: new TimelineRecordStyle(
1028
- i18nString(UIStrings.layoutShift), defaultCategoryStyles.experience,
1029
- /* Mark LayoutShifts as hidden; in the timeline we render
1030
- * SyntheticLayoutShifts so those are the ones visible to the user */
1031
- true),
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.isFirstContentfulPaint(event)) {
1146
+ if (Types.Events.isAnyFirstContentfulPaint(event)) {
1127
1147
  color = 'var(--sys-color-green-bright)';
1128
- title = Handlers.ModelHandlers.PageLoadMetrics.MetricName.FCP;
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.isFirstContentfulPaint(event)) {
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.isFirstContentfulPaint(event) || Types.Events.isInteractiveTime(event) ||
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.isFirstContentfulPaint(event) || Types.Events.isAnyLargestContentfulPaintCandidate(event) ||
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 = [...allFinalLCPEvents, ...allEventsButLCP].filter(Types.Events.isMarkerEvent);
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 = FirstContentfulPaint|MarkDOMContent|InteractiveTime|AnyLargestContentfulPaintCandidate|
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
- isFirstContentfulPaint,
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',
@@ -33,7 +33,7 @@ export class AccessibilitySidebarView extends UI.Widget.VBox {
33
33
  #node: SDK.DOMModel.DOMNode|null;
34
34
  #axNode: SDK.AccessibilityModel.AccessibilityNode|null;
35
35
  private skipNextPullNode: boolean;
36
- private readonly sidebarPaneStack: UI.View.ViewLocation;
36
+ private readonly sidebarPaneStack: UI.ViewManager.StackLocation;
37
37
  private readonly ariaSubPane: ARIAAttributesPane;
38
38
  private readonly axNodeSubPane: AXNodeSubPane;
39
39
  private readonly sourceOrderSubPane: SourceOrderPane;
@@ -159,6 +159,7 @@ export class AccessibilitySidebarView extends UI.Widget.VBox {
159
159
 
160
160
  private updateToggle(): void {
161
161
  const isToggled = this.toggleAction.toggled();
162
+ this.sidebarPaneStack.notifyVisibilityChanged(isToggled);
162
163
  // eslint-disable-next-line @devtools/no-lit-render-outside-of-view
163
164
  render(
164
165
  html`
@@ -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.AiAssistanceOpenedFromStoragePanelFloatingButton);
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
  }
@@ -2115,7 +2120,9 @@ export class ActionDelegate implements UI.ActionRegistration.ActionDelegate {
2115
2120
  case 'drjones.network-panel-context':
2116
2121
  case 'drjones.performance-panel-context':
2117
2122
  case 'drjones.sources-floating-button':
2118
- case 'drjones.sources-panel-context': {
2123
+ case 'drjones.sources-panel-context':
2124
+ case 'ai-assistance.storage-floating-button':
2125
+ case 'ai-assistance.application-panel-context': {
2119
2126
  void (async () => {
2120
2127
  const view = UI.ViewManager.ViewManager.instance().view(
2121
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
+ });