chrome-devtools-frontend 1.0.1643855 → 1.0.1646286

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 (95) hide show
  1. package/.agents/skills/devtools-source-maps/SKILL.md +124 -0
  2. package/docs/README.md +1 -0
  3. package/docs/using_source_maps.md +159 -0
  4. package/front_end/core/host/AidaClientTypes.ts +2 -0
  5. package/front_end/core/host/UserMetrics.ts +5 -3
  6. package/front_end/core/root/Runtime.ts +10 -0
  7. package/front_end/core/sdk/CSSPropertyParserMatchers.ts +2 -3
  8. package/front_end/core/sdk/DebuggerModel.ts +7 -9
  9. package/front_end/core/sdk/NetworkRequest.ts +0 -25
  10. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshot.ts +37 -0
  11. package/front_end/generated/InspectorBackendCommands.ts +2 -2
  12. package/front_end/generated/SupportedCSSProperties.js +79 -2
  13. package/front_end/generated/protocol.ts +0 -5
  14. package/front_end/models/ai_assistance/AiAgent2.ts +52 -15
  15. package/front_end/models/ai_assistance/AiConversation.ts +4 -2
  16. package/front_end/models/ai_assistance/AiOrigins.ts +63 -2
  17. package/front_end/models/ai_assistance/README.md +20 -8
  18. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +2 -2
  19. package/front_end/models/ai_assistance/agents/FileAgent.ts +9 -42
  20. package/front_end/models/ai_assistance/agents/NetworkAgent.snapshot.txt +2 -2
  21. package/front_end/models/ai_assistance/agents/NetworkAgent.ts +9 -133
  22. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +25 -2
  23. package/front_end/models/ai_assistance/agents/README.md +64 -0
  24. package/front_end/models/ai_assistance/agents/StylingAgent.ts +26 -3
  25. package/front_end/models/ai_assistance/ai_assistance.ts +4 -0
  26. package/front_end/models/ai_assistance/contexts/FileContext.ts +45 -0
  27. package/front_end/models/ai_assistance/contexts/RequestContext.snapshot.txt +48 -0
  28. package/front_end/models/ai_assistance/contexts/RequestContext.ts +116 -0
  29. package/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.ts +2 -1
  30. package/front_end/models/ai_assistance/tools/ExecuteJavaScript.ts +9 -12
  31. package/front_end/models/ai_assistance/tools/GetStyles.ts +19 -12
  32. package/front_end/models/ai_assistance/tools/README.md +45 -0
  33. package/front_end/models/ai_assistance/tools/Tool.ts +74 -11
  34. package/front_end/models/ai_assistance/tools/ToolRegistry.ts +21 -5
  35. package/front_end/models/heap_snapshot/HeapSnapshotModel.ts +18 -2
  36. package/front_end/models/heap_snapshot/HeapSnapshotProxy.ts +4 -0
  37. package/front_end/models/stack_trace/DetailedErrorStackParser.ts +17 -4
  38. package/front_end/models/stack_trace/StackTraceModel.ts +34 -1
  39. package/front_end/models/trace/Styles.ts +29 -7
  40. package/front_end/models/trace/handlers/NetworkRequestsHandler.ts +15 -11
  41. package/front_end/models/trace/handlers/PageLoadMetricsHandler.ts +33 -4
  42. package/front_end/models/trace/helpers/Timing.ts +10 -0
  43. package/front_end/models/trace/types/TraceEvents.ts +22 -2
  44. package/front_end/models/web_mcp/WebMCPModel.ts +8 -48
  45. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +21 -15
  46. package/front_end/panels/ai_assistance/ai_assistance-meta.ts +16 -0
  47. package/front_end/panels/ai_assistance/components/ChatInput.ts +4 -4
  48. package/front_end/panels/application/ApplicationPanelSidebar.ts +69 -0
  49. package/front_end/panels/application/ServiceWorkersView.ts +2 -2
  50. package/front_end/panels/application/WebMCPView.ts +40 -71
  51. package/front_end/panels/application/components/AdsView.ts +31 -28
  52. package/front_end/panels/application/components/BackForwardCacheView.ts +1 -2
  53. package/front_end/panels/application/components/adsView.css +6 -0
  54. package/front_end/panels/common/ExtensionServer.ts +5 -0
  55. package/front_end/panels/console/ConsoleView.ts +6 -1
  56. package/front_end/panels/console/ConsoleViewMessage.ts +46 -213
  57. package/front_end/panels/console/SymbolizedErrorWidget.ts +4 -1
  58. package/front_end/panels/elements/AdoptedStyleSheetTreeElement.ts +0 -1
  59. package/front_end/panels/elements/ElementsTreeElement.ts +0 -2
  60. package/front_end/panels/elements/PropertyRenderer.ts +0 -1
  61. package/front_end/panels/elements/StylesSidebarPane.ts +9 -2
  62. package/front_end/panels/issues/AffectedResourcesView.ts +1 -1
  63. package/front_end/panels/issues/AffectedSourcesView.ts +1 -1
  64. package/front_end/panels/lighthouse/LighthouseReportRenderer.ts +0 -1
  65. package/front_end/panels/mobile_throttling/ThrottlingSettingsTab.ts +10 -0
  66. package/front_end/panels/network/NetworkDataGridNode.ts +1 -2
  67. package/front_end/panels/network/NetworkLogView.ts +34 -7
  68. package/front_end/panels/profiler/HeapProfileView.ts +0 -1
  69. package/front_end/panels/profiler/HeapSnapshotGridNodes.ts +0 -1
  70. package/front_end/panels/profiler/HeapSnapshotView.ts +1 -1
  71. package/front_end/panels/profiler/IsolateSelector.ts +4 -2
  72. package/front_end/panels/profiler/ProfileLauncherView.ts +194 -126
  73. package/front_end/panels/profiler/ProfilesPanel.ts +1 -3
  74. package/front_end/panels/settings/components/SyncSection.ts +1 -1
  75. package/front_end/panels/timeline/TimelineFlameChartView.ts +5 -4
  76. package/front_end/panels/timeline/TimelinePanel.ts +7 -0
  77. package/front_end/panels/timeline/TimelineUIUtils.ts +13 -14
  78. package/front_end/panels/timeline/TimingsTrackAppender.ts +7 -5
  79. package/front_end/panels/timeline/components/LayoutShiftDetails.ts +0 -1
  80. package/front_end/panels/timeline/components/NetworkRequestDetails.ts +0 -2
  81. package/front_end/panels/timeline/components/insights/ForcedReflow.ts +0 -1
  82. package/front_end/panels/timeline/components/insights/NodeLink.ts +0 -1
  83. package/front_end/panels/timeline/overlays/OverlaysImpl.ts +2 -0
  84. package/front_end/third_party/chromium/README.chromium +1 -1
  85. package/front_end/ui/helpers/OpenInNewTab.ts +3 -3
  86. package/front_end/ui/kit/link/Link.ts +16 -2
  87. package/front_end/ui/legacy/InspectorDrawerView.ts +14 -5
  88. package/front_end/ui/legacy/InspectorView.ts +4 -1
  89. package/front_end/ui/legacy/PlusButton.ts +6 -1
  90. package/front_end/ui/legacy/Widget.ts +19 -1
  91. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +95 -31
  92. package/front_end/ui/legacy/components/utils/JSPresentationUtils.ts +0 -1
  93. package/front_end/ui/legacy/components/utils/Linkifier.ts +2 -16
  94. package/front_end/ui/visual_logging/KnownContextValues.ts +6 -0
  95. package/package.json +1 -1
@@ -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)';
@@ -213,6 +213,14 @@ export function handleEvent(event: Types.Events.Event): void {
213
213
 
214
214
  export async function finalize(): Promise<void> {
215
215
  const {rendererProcessesByFrame} = metaHandlerData();
216
+
217
+ const allowedProtocols = [
218
+ 'blob:',
219
+ 'file:',
220
+ 'filesystem:',
221
+ 'http:',
222
+ 'https:',
223
+ ];
216
224
  for (const [requestId, request] of requestMap.entries()) {
217
225
  // If we have an incomplete set of events here, we choose to drop the network
218
226
  // request rather than attempt to synthesize the missing data.
@@ -248,7 +256,7 @@ export async function finalize(): Promise<void> {
248
256
  }
249
257
 
250
258
  redirects.push({
251
- url: sendRequest.args.data.url,
259
+ url: allowedProtocols.some(p => sendRequest.args.data.url.startsWith(p)) ? sendRequest.args.data.url : '',
252
260
  priority: sendRequest.args.data.priority,
253
261
  requestMethod: sendRequest.args.data.requestMethod,
254
262
  ts,
@@ -360,15 +368,7 @@ export async function finalize(): Promise<void> {
360
368
  }
361
369
  }
362
370
 
363
- // TODO: consider allowing chrome / about.
364
- const allowedProtocols = [
365
- 'blob:',
366
- 'file:',
367
- 'filesystem:',
368
- 'http:',
369
- 'https:',
370
- ];
371
- if (!allowedProtocols.some(p => firstSendRequest.args.data.url.startsWith(p))) {
371
+ if (!allowedProtocols.some(p => finalSendRequest.args.data.url.startsWith(p))) {
372
372
  continue;
373
373
  }
374
374
 
@@ -572,7 +572,11 @@ export async function finalize(): Promise<void> {
572
572
  responseHeaders: request.receiveResponse?.args.data.headers ?? null,
573
573
  fetchPriorityHint: finalSendRequest.args.data.fetchPriorityHint ?? 'auto',
574
574
  initiator: finalSendRequest.args.data.initiator,
575
- stackTrace: finalSendRequest.args.data.stackTrace,
575
+ stackTrace: finalSendRequest.args.data.stackTrace?.map(
576
+ frame => ({
577
+ ...frame,
578
+ url: allowedProtocols.some(p => frame.url.startsWith(p)) ? frame.url : '',
579
+ })),
576
580
  timing,
577
581
  lrServerResponseTime,
578
582
  url,
@@ -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',
@@ -9,7 +9,7 @@ import * as SDK from '../../core/sdk/sdk.js';
9
9
  import type * as ProtocolProxyApi from '../../generated/protocol-proxy-api.js';
10
10
  import type * as Protocol from '../../generated/protocol.js';
11
11
  import * as Bindings from '../bindings/bindings.js';
12
- import * as StackTrace from '../stack_trace/stack_trace.js';
12
+ import type * as StackTrace from '../stack_trace/stack_trace.js';
13
13
 
14
14
  export const enum Events {
15
15
  TOOLS_ADDED = 'ToolsAdded',
@@ -18,20 +18,13 @@ export const enum Events {
18
18
  TOOL_RESPONDED = 'ToolResponded',
19
19
  }
20
20
 
21
- export interface ExceptionDetails {
22
- readonly error: SDK.RemoteObject.RemoteObject;
23
- readonly description: string;
24
- readonly frames: StackTrace.ErrorStackParser.ParsedErrorFrame[];
25
- readonly cause?: ExceptionDetails;
26
- }
27
-
28
21
  export class Result {
29
22
  readonly status: Protocol.WebMCP.InvocationStatus;
30
23
  readonly output?: unknown;
31
24
  readonly errorText?: string;
32
25
  // TODO(crbug.com/494516094) Clean this up if the target disappears?
33
26
  readonly #exception?: SDK.RemoteObject.RemoteObject;
34
- #exceptionDetails?: Promise<ExceptionDetails|undefined>;
27
+ #symbolizedError?: Promise<Bindings.SymbolizedError.SymbolizedError|null>;
35
28
 
36
29
  constructor(
37
30
  status: Protocol.WebMCP.InvocationStatus, output: unknown|undefined, errorText: string|undefined,
@@ -42,46 +35,13 @@ export class Result {
42
35
  this.output = output;
43
36
  }
44
37
 
45
- get exceptionDetails(): Promise<ExceptionDetails|undefined>|undefined {
46
- if (!this.#exceptionDetails) {
47
- this.#exceptionDetails = this.#resolveExceptionDetails(this.#exception);
48
- }
49
- return this.#exceptionDetails;
50
- }
51
-
52
- async #resolveExceptionDetails(errorObj: SDK.RemoteObject.RemoteObject|undefined):
53
- Promise<ExceptionDetails|undefined> {
54
- if (!errorObj) {
55
- return undefined;
56
- }
57
- const error = SDK.RemoteObject.RemoteError.objectAsError(errorObj);
58
- const [details, cause] = await Promise.all([error.exceptionDetails(), error.cause()]);
59
- const description = error.errorStack;
60
-
61
- const frames =
62
- StackTrace.ErrorStackParser.parseSourcePositionsFromErrorStack(errorObj.runtimeModel(), error.errorStack) || [];
63
- if (details?.stackTrace) {
64
- StackTrace.ErrorStackParser.augmentErrorStackWithScriptIds(frames, details.stackTrace);
38
+ get symbolizedError(): Promise<Bindings.SymbolizedError.SymbolizedError|null>|undefined {
39
+ if (!this.#symbolizedError) {
40
+ this.#symbolizedError = this.#exception ?
41
+ Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance().createSymbolizedError(this.#exception) :
42
+ Promise.resolve(null);
65
43
  }
66
-
67
- if (cause?.subtype === 'error') {
68
- return {error: errorObj, description, frames, cause: await this.#resolveExceptionDetails(cause)};
69
- }
70
-
71
- if (cause?.type === 'string') {
72
- return {
73
- error: errorObj,
74
- description,
75
- frames,
76
- cause: {
77
- error: cause,
78
- description: cause.value as string,
79
- frames: [],
80
- }
81
- };
82
- }
83
-
84
- return {error: errorObj, description, frames};
44
+ return this.#symbolizedError;
85
45
  }
86
46
  }
87
47
 
@@ -576,12 +576,12 @@ function createDOMNodeContext(node: SDK.DOMModel.DOMNode|null): AiAssistanceMode
576
576
  return new AiAssistanceModel.DOMNodeContext.DOMNodeContext(node);
577
577
  }
578
578
 
579
- function createFileContext(file: Workspace.UISourceCode.UISourceCode|null): AiAssistanceModel.FileAgent.FileContext|
579
+ function createFileContext(file: Workspace.UISourceCode.UISourceCode|null): AiAssistanceModel.FileContext.FileContext|
580
580
  null {
581
581
  if (!file) {
582
582
  return null;
583
583
  }
584
- return new AiAssistanceModel.FileAgent.FileContext(file);
584
+ return new AiAssistanceModel.FileContext.FileContext(file);
585
585
  }
586
586
 
587
587
  function createAccessibilityContext(report: LighthousePanel.LighthousePanel.ActiveLighthouseReport|null):
@@ -592,13 +592,13 @@ function createAccessibilityContext(report: LighthousePanel.LighthousePanel.Acti
592
592
  return new AiAssistanceModel.AccessibilityAgent.AccessibilityContext(report.report);
593
593
  }
594
594
 
595
- function createRequestContext(request: SDK.NetworkRequest.NetworkRequest|null):
596
- AiAssistanceModel.NetworkAgent.RequestContext|null {
595
+ function createRequestContext(request: SDK.NetworkRequest.NetworkRequest|
596
+ null): AiAssistanceModel.RequestContext.RequestContext|null {
597
597
  if (!request) {
598
598
  return null;
599
599
  }
600
600
  const calculator = NetworkPanel.NetworkPanel.NetworkPanel.instance().networkLogView.timeCalculator();
601
- return new AiAssistanceModel.NetworkAgent.RequestContext(request, calculator);
601
+ return new AiAssistanceModel.RequestContext.RequestContext(request, calculator);
602
602
  }
603
603
 
604
604
  function createPerformanceTraceContext(focus: AiAssistanceModel.AIContext.AgentFocus|null):
@@ -666,10 +666,10 @@ export class AiAssistancePanel extends UI.Panel.Panel {
666
666
 
667
667
  #conversation?: AiAssistanceModel.AiConversation.AiConversation;
668
668
 
669
- #selectedFile: AiAssistanceModel.FileAgent.FileContext|null = null;
669
+ #selectedFile: AiAssistanceModel.FileContext.FileContext|null = null;
670
670
  #selectedElement: AiAssistanceModel.DOMNodeContext.DOMNodeContext|null = null;
671
671
  #selectedPerformanceTrace: AiAssistanceModel.PerformanceAgent.PerformanceTraceContext|null = null;
672
- #selectedRequest: AiAssistanceModel.NetworkAgent.RequestContext|null = null;
672
+ #selectedRequest: AiAssistanceModel.RequestContext.RequestContext|null = null;
673
673
 
674
674
  #selectedAccessibility: AiAssistanceModel.AccessibilityAgent.AccessibilityContext|null = null;
675
675
  #selectedStorage: AiAssistanceModel.StorageAgent.StorageContext|null = null;
@@ -1254,7 +1254,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1254
1254
 
1255
1255
  if (Boolean(ev.data)) {
1256
1256
  const calculator = NetworkPanel.NetworkPanel.NetworkPanel.instance().networkLogView.timeCalculator();
1257
- this.#selectedRequest = new AiAssistanceModel.NetworkAgent.RequestContext(ev.data, calculator);
1257
+ this.#selectedRequest = new AiAssistanceModel.RequestContext.RequestContext(ev.data, calculator);
1258
1258
  } else {
1259
1259
  this.#selectedRequest = null;
1260
1260
  }
@@ -1280,7 +1280,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1280
1280
  if (!newFile || this.#selectedFile?.getItem() === newFile) {
1281
1281
  return;
1282
1282
  }
1283
- this.#selectedFile = new AiAssistanceModel.FileAgent.FileContext(ev.data);
1283
+ this.#selectedFile = new AiAssistanceModel.FileContext.FileContext(ev.data);
1284
1284
  this.#updateConversationState(this.#conversation);
1285
1285
  };
1286
1286
 
@@ -1488,12 +1488,12 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1488
1488
  }
1489
1489
 
1490
1490
  const context = this.#conversation.selectedContext;
1491
- if (context instanceof AiAssistanceModel.NetworkAgent.RequestContext) {
1491
+ if (context instanceof AiAssistanceModel.RequestContext.RequestContext) {
1492
1492
  const requestLocation = NetworkForward.UIRequestLocation.UIRequestLocation.tab(
1493
1493
  context.getItem(), NetworkForward.UIRequestLocation.UIRequestTabs.HEADERS_COMPONENT);
1494
1494
  return Common.Revealer.reveal(requestLocation);
1495
1495
  }
1496
- if (context instanceof AiAssistanceModel.FileAgent.FileContext) {
1496
+ if (context instanceof AiAssistanceModel.FileContext.FileContext) {
1497
1497
  return Common.Revealer.reveal(context.getItem().uiLocation(0, 0));
1498
1498
  }
1499
1499
  if (context instanceof AiAssistanceModel.PerformanceAgent.PerformanceTraceContext) {
@@ -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
  }
@@ -1728,11 +1733,11 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1728
1733
  }
1729
1734
 
1730
1735
  #handleConversationContextChange = (data: unknown): void => {
1731
- if (data instanceof AiAssistanceModel.FileAgent.FileContext) {
1736
+ if (data instanceof AiAssistanceModel.FileContext.FileContext) {
1732
1737
  this.#selectedFile = data;
1733
1738
  } else if (data instanceof AiAssistanceModel.DOMNodeContext.DOMNodeContext) {
1734
1739
  this.#selectedElement = data;
1735
- } else if (data instanceof AiAssistanceModel.NetworkAgent.RequestContext) {
1740
+ } else if (data instanceof AiAssistanceModel.RequestContext.RequestContext) {
1736
1741
  this.#selectedRequest = data;
1737
1742
  } else if (data instanceof AiAssistanceModel.PerformanceAgent.PerformanceTraceContext) {
1738
1743
  this.#selectedPerformanceTrace = data;
@@ -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
+ });
@@ -171,13 +171,13 @@ export type ViewOutput = undefined;
171
171
 
172
172
  function getContextRemoveLabel(context: AiAssistanceModel.AiAgent.ConversationContext<unknown>):
173
173
  Platform.UIString.LocalizedString {
174
- if (context instanceof AiAssistanceModel.FileAgent.FileContext) {
174
+ if (context instanceof AiAssistanceModel.FileContext.FileContext) {
175
175
  return lockedString(UIStringsNotTranslate.removeContextFile);
176
176
  }
177
177
  if (context instanceof AiAssistanceModel.DOMNodeContext.DOMNodeContext) {
178
178
  return lockedString(UIStringsNotTranslate.removeContextElement);
179
179
  }
180
- if (context instanceof AiAssistanceModel.NetworkAgent.RequestContext) {
180
+ if (context instanceof AiAssistanceModel.RequestContext.RequestContext) {
181
181
  return lockedString(UIStringsNotTranslate.removeContextRequest);
182
182
  }
183
183
  if (context instanceof AiAssistanceModel.PerformanceAgent.PerformanceTraceContext) {
@@ -351,9 +351,9 @@ export const DEFAULT_VIEW = (input: ViewInput, _output: ViewOutput, target: HTML
351
351
  })}
352
352
  ></devtools-widget>` :
353
353
  html`
354
- ${input.context instanceof AiAssistanceModel.NetworkAgent.RequestContext ?
354
+ ${input.context instanceof AiAssistanceModel.RequestContext.RequestContext ?
355
355
  PanelUtils.PanelUtils.getIconForNetworkRequest(input.context.getItem()) :
356
- input.context instanceof AiAssistanceModel.FileAgent.FileContext ?
356
+ input.context instanceof AiAssistanceModel.FileContext.FileContext ?
357
357
  PanelUtils.PanelUtils.getIconForSourceFile(input.context.getItem()) :
358
358
  input.context instanceof AiAssistanceModel.AccessibilityAgent.AccessibilityContext ?
359
359
  html`<devtools-icon class="icon" name="performance" title="Lighthouse"></devtools-icon>` :
@@ -52,6 +52,7 @@ import {BackForwardCacheTreeElement} from './BackForwardCacheTreeElement.js';
52
52
  import {BackgroundServiceModel} from './BackgroundServiceModel.js';
53
53
  import {BackgroundServiceView} from './BackgroundServiceView.js';
54
54
  import {BounceTrackingMitigationsTreeElement} from './BounceTrackingMitigationsTreeElement.js';
55
+ import * as ApplicationComponents from './components/components.js';
55
56
  import {DeviceBoundSessionsModel} from './DeviceBoundSessionsModel.js';
56
57
  import {RootTreeElement as DeviceBoundSessionsRootTreeElement} from './DeviceBoundSessionsTreeElement.js';
57
58
  import {
@@ -94,10 +95,26 @@ import {TrustTokensTreeElement} from './TrustTokensTreeElement.js';
94
95
  import {WebMCPTreeElement} from './WebMCPTreeElement.js';
95
96
 
96
97
  const UIStrings = {
98
+ /**
99
+ * @description Text of a context menu item to start a chat with AI
100
+ */
101
+ startAChat: 'Start a chat',
102
+ /**
103
+ * @description Text of a context menu item to explain contents of a local/session storage bucket with AI
104
+ */
105
+ explainStorage: 'Explain storage',
106
+ /**
107
+ * @description Text of a context menu item to explain web cookies with AI
108
+ */
109
+ explainCookies: 'Explain cookies',
97
110
  /**
98
111
  * @description Text in Application Panel Sidebar of the Application panel
99
112
  */
100
113
  application: 'Application',
114
+ /**
115
+ * @description Text in Application Panel Sidebar of the Application panel
116
+ */
117
+ ads: 'Ads',
101
118
  /**
102
119
  * @description Text in Application Panel Sidebar of the Application panel
103
120
  */
@@ -342,6 +359,7 @@ export class ApplicationPanelSidebar extends UI.Widget.VBox implements SDK.Targe
342
359
  pushMessagingTreeElement: BackgroundServiceTreeElement;
343
360
  reportingApiTreeElement: ReportingApiTreeElement;
344
361
  webMcpTreeElement?: WebMCPTreeElement;
362
+ adsTreeElement?: ApplicationPanelTreeElement;
345
363
  deviceBoundSessionsRootTreeElement: DeviceBoundSessionsRootTreeElement|undefined;
346
364
  deviceBoundSessionsModel: DeviceBoundSessionsModel|undefined;
347
365
  preloadingSummaryTreeElement: PreloadingSummaryTreeElement|undefined;
@@ -391,6 +409,25 @@ export class ApplicationPanelSidebar extends UI.Widget.VBox implements SDK.Targe
391
409
  this.applicationTreeElement.appendChild(this.webMcpTreeElement);
392
410
  }
393
411
 
412
+ if (Root.Runtime.hostConfig.devToolsAdsPanel?.enabled) {
413
+ const adsTreeElement = new ApplicationPanelTreeElement(panel, i18nString(UIStrings.ads), false, 'ads');
414
+ const icon = createIcon('experiment');
415
+ adsTreeElement.setLeadingIcons([icon]);
416
+ adsTreeElement.itemURL = 'ads://' as Platform.DevToolsPath.UrlString;
417
+ let adsView: ApplicationComponents.AdsView.AdsView;
418
+ adsTreeElement.onselect = (selectedByUser?: boolean): boolean => {
419
+ ApplicationPanelTreeElement.prototype.onselect.call(adsTreeElement, selectedByUser);
420
+ if (!adsView) {
421
+ adsView = new ApplicationComponents.AdsView.AdsView();
422
+ }
423
+ adsTreeElement.showView(adsView);
424
+ UI.UIUserMetrics.UIUserMetrics.instance().panelShown('ads');
425
+ return false;
426
+ };
427
+ this.adsTreeElement = adsTreeElement;
428
+ this.applicationTreeElement.appendChild(this.adsTreeElement);
429
+ }
430
+
394
431
  const storageSectionTitle = i18nString(UIStrings.storage);
395
432
  const storageTreeElement = this.addSidebarSection(storageSectionTitle, 'storage');
396
433
  this.localStorageListTreeElement = new ExpandableApplicationPanelTreeElement(
@@ -1792,6 +1829,22 @@ export class DOMStorageTreeElement extends ApplicationPanelTreeElement {
1792
1829
  const contextMenu = new UI.ContextMenu.ContextMenu(event);
1793
1830
  contextMenu.defaultSection().appendItem(
1794
1831
  i18nString(UIStrings.clear), () => this.domStorage.clear(), {jslogContext: 'clear'});
1832
+
1833
+ const storageItem = this.#getStorageItem();
1834
+ if (storageItem) {
1835
+ const openAiAssistanceId = 'ai-assistance.application-panel-context';
1836
+ if (UI.ActionRegistry.ActionRegistry.instance().hasAction(openAiAssistanceId)) {
1837
+ UI.Context.Context.instance().setFlavor(AiAssistance.StorageItem.StorageItem, storageItem);
1838
+ const action = UI.ActionRegistry.ActionRegistry.instance().getAction(openAiAssistanceId);
1839
+ const submenu = contextMenu.footerSection().appendSubMenuItem(action.title(), false, openAiAssistanceId);
1840
+ submenu.defaultSection().appendAction(openAiAssistanceId, i18nString(UIStrings.startAChat));
1841
+ submenu.defaultSection().appendItem(
1842
+ i18nString(UIStrings.explainStorage),
1843
+ () => action.execute({prompt: 'What is the purpose of this storage bucket?'}),
1844
+ {disabled: !action.enabled(), jslogContext: openAiAssistanceId + '.storage'});
1845
+ }
1846
+ }
1847
+
1795
1848
  void contextMenu.show();
1796
1849
  }
1797
1850
  }
@@ -1900,6 +1953,22 @@ export class CookieTreeElement extends ApplicationPanelTreeElement {
1900
1953
  contextMenu.defaultSection().appendItem(
1901
1954
  i18nString(UIStrings.clear), () => this.resourcesPanel.clearCookies(this.target, this.#cookieDomain),
1902
1955
  {jslogContext: 'clear'});
1956
+
1957
+ const storageItem = this.#getStorageItem();
1958
+ if (storageItem) {
1959
+ const openAiAssistanceId = 'ai-assistance.application-panel-context';
1960
+ if (UI.ActionRegistry.ActionRegistry.instance().hasAction(openAiAssistanceId)) {
1961
+ UI.Context.Context.instance().setFlavor(AiAssistance.StorageItem.StorageItem, storageItem);
1962
+ const action = UI.ActionRegistry.ActionRegistry.instance().getAction(openAiAssistanceId);
1963
+ const submenu = contextMenu.footerSection().appendSubMenuItem(action.title(), false, openAiAssistanceId);
1964
+ submenu.defaultSection().appendAction(openAiAssistanceId, i18nString(UIStrings.startAChat));
1965
+ submenu.defaultSection().appendItem(
1966
+ i18nString(UIStrings.explainCookies),
1967
+ () => action.execute({prompt: 'What is the purpose of these cookies?'}),
1968
+ {disabled: !action.enabled(), jslogContext: openAiAssistanceId + '.cookies'});
1969
+ }
1970
+ }
1971
+
1903
1972
  void contextMenu.show();
1904
1973
  }
1905
1974