chrome-devtools-frontend 1.0.1558690 → 1.0.1559913

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 (51) hide show
  1. package/front_end/Images/src/container.svg +4 -0
  2. package/front_end/core/common/Gzip.ts +15 -0
  3. package/front_end/core/sdk/CSSMetadata.ts +6 -6
  4. package/front_end/core/sdk/CSSModel.ts +2 -2
  5. package/front_end/core/sdk/DOMModel.ts +7 -3
  6. package/front_end/generated/InspectorBackendCommands.ts +2 -1
  7. package/front_end/generated/SupportedCSSProperties.js +64 -32
  8. package/front_end/generated/protocol-mapping.d.ts +9 -0
  9. package/front_end/generated/protocol-proxy-api.d.ts +7 -0
  10. package/front_end/generated/protocol.ts +14 -1
  11. package/front_end/models/ai_assistance/agents/StylingAgent.ts +1 -1
  12. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +11 -7
  13. package/front_end/models/trace/LanternComputationData.ts +4 -3
  14. package/front_end/models/trace/Processor.ts +6 -5
  15. package/front_end/models/trace/Styles.ts +10 -1
  16. package/front_end/models/trace/handlers/LargestImagePaintHandler.ts +2 -2
  17. package/front_end/models/trace/handlers/MetaHandler.ts +14 -0
  18. package/front_end/models/trace/handlers/PageLoadMetricsHandler.ts +54 -34
  19. package/front_end/models/trace/helpers/Timing.ts +8 -1
  20. package/front_end/models/trace/insights/Common.ts +1 -1
  21. package/front_end/models/trace/insights/LCPBreakdown.ts +4 -4
  22. package/front_end/models/trace/insights/LCPDiscovery.ts +3 -3
  23. package/front_end/models/trace/insights/RenderBlocking.ts +1 -1
  24. package/front_end/models/trace/insights/types.ts +1 -1
  25. package/front_end/models/trace/types/TraceEvents.ts +62 -10
  26. package/front_end/panels/common/AiCodeGenerationTeaser.ts +48 -12
  27. package/front_end/panels/common/aiCodeGenerationTeaser.css +14 -0
  28. package/front_end/panels/common/common.ts +1 -1
  29. package/front_end/panels/console/consoleView.css +1 -1
  30. package/front_end/panels/elements/CSSRuleValidator.ts +38 -0
  31. package/front_end/panels/elements/ElementsTreeElement.ts +79 -58
  32. package/front_end/panels/elements/ElementsTreeOutline.ts +0 -17
  33. package/front_end/panels/elements/StylesSidebarPane.ts +15 -4
  34. package/front_end/panels/timeline/StatusDialog.ts +4 -3
  35. package/front_end/panels/timeline/TimelineFlameChartNetworkDataProvider.ts +3 -16
  36. package/front_end/panels/timeline/TimelineFlameChartView.ts +64 -21
  37. package/front_end/panels/timeline/TimelinePanel.ts +71 -24
  38. package/front_end/panels/timeline/TimelineUIUtils.ts +28 -2
  39. package/front_end/panels/timeline/TimingsTrackAppender.ts +3 -1
  40. package/front_end/panels/timeline/components/SidebarSingleInsightSet.ts +1 -1
  41. package/front_end/panels/timeline/components/insights/RenderBlocking.ts +6 -4
  42. package/front_end/panels/timeline/overlays/OverlaysImpl.ts +4 -0
  43. package/front_end/panels/timeline/timelinePanel.css +8 -1
  44. package/front_end/panels/timeline/utils/EntryNodes.ts +2 -1
  45. package/front_end/third_party/chromium/README.chromium +1 -1
  46. package/front_end/ui/components/text_editor/AiCodeGenerationProvider.ts +70 -28
  47. package/front_end/ui/legacy/SearchableView.ts +11 -5
  48. package/front_end/ui/legacy/SplitWidget.ts +1 -1
  49. package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +43 -9
  50. package/front_end/ui/visual_logging/KnownContextValues.ts +13 -0
  51. package/package.json +1 -1
@@ -232,6 +232,18 @@ const UIStrings = {
232
232
  * @description Text in Timeline Panel of the Performance panel
233
233
  */
234
234
  initializingTracing: 'Initializing tracing…',
235
+ /**
236
+ * @description Text in Timeline Panel of the Performance panel. Shown to the user after they request to download the trace.
237
+ */
238
+ preparingTraceForDownload: 'Preparing…',
239
+ /**
240
+ * @description Text in Timeline Panel of the Performance panel. Shown to the user after they request to download the trace.
241
+ */
242
+ compressingTraceForDownload: 'Compressing…',
243
+ /**
244
+ * @description Text in Timeline Panel of the Performance panel. Shown to the user after they request to download the trace.
245
+ */
246
+ encodingTraceForDownload: 'Encoding…',
235
247
  /**
236
248
  * @description Tooltip description for a checkbox that toggles the visibility of data added by extensions of this panel (Performance).
237
249
  */
@@ -452,7 +464,6 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
452
464
  content: adornerContent,
453
465
  };
454
466
  this.#traceEngineModel = traceModel || this.#instantiateNewModel();
455
- this.#listenForProcessingProgress();
456
467
 
457
468
  this.element.addEventListener('contextmenu', this.contextMenu.bind(this), false);
458
469
  this.dropTarget = new UI.DropTarget.DropTarget(
@@ -675,7 +686,7 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
675
686
  #setActiveInsight(insight: TimelineComponents.Sidebar.ActiveInsight|null, opts: {
676
687
  highlightInsight: boolean,
677
688
  } = {highlightInsight: false}): void {
678
- if (insight) {
689
+ if (insight && this.#splitWidget.showMode() !== UI.SplitWidget.ShowMode.BOTH) {
679
690
  this.#splitWidget.showBoth();
680
691
  }
681
692
  this.#sideBar.setActiveInsight(insight, {highlight: opts.highlightInsight});
@@ -732,7 +743,25 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
732
743
  config.includeRuntimeCallStats = Root.Runtime.experiments.isEnabled('timeline-v8-runtime-call-stats');
733
744
  config.debugMode = Root.Runtime.experiments.isEnabled(Root.Runtime.ExperimentName.TIMELINE_DEBUG_MODE);
734
745
 
735
- return Trace.TraceModel.Model.createWithAllHandlers(config);
746
+ const traceEngineModel = Trace.TraceModel.Model.createWithAllHandlers(config);
747
+
748
+ traceEngineModel.addEventListener(Trace.TraceModel.ModelUpdateEvent.eventName, e => {
749
+ const updateEvent = e as Trace.TraceModel.ModelUpdateEvent;
750
+ const str = i18nString(UIStrings.processed);
751
+
752
+ // Trace Engine will report progress from [0...1] but we still have more work to do. So, scale them down a bit.
753
+ const traceParseMaxProgress = 0.7;
754
+
755
+ if (updateEvent.data.type === Trace.TraceModel.ModelUpdateType.COMPLETE) {
756
+ this.statusDialog?.updateProgressBar(str, 100 * traceParseMaxProgress);
757
+ } else if (updateEvent.data.type === Trace.TraceModel.ModelUpdateType.PROGRESS_UPDATE) {
758
+ const data = updateEvent.data.data;
759
+ this.statusDialog?.updateProgressBar(str, data.percent * 100 * traceParseMaxProgress);
760
+ }
761
+ });
762
+
763
+ this.#traceEngineModel = traceEngineModel;
764
+ return this.#traceEngineModel;
736
765
  }
737
766
 
738
767
  static extensionDataVisibilitySetting(): Common.Settings.Setting<boolean> {
@@ -1455,6 +1484,9 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
1455
1484
  }
1456
1485
 
1457
1486
  this.#showExportTraceErrorDialog(error);
1487
+ } finally {
1488
+ this.statusDialog?.remove();
1489
+ this.statusDialog = null;
1458
1490
  }
1459
1491
  }
1460
1492
 
@@ -1464,6 +1496,24 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
1464
1496
  addModifications: boolean,
1465
1497
  shouldCompress: boolean,
1466
1498
  }): Promise<void> {
1499
+ this.statusDialog = new StatusDialog(
1500
+ {
1501
+ hideStopButton: true,
1502
+ showProgress: true,
1503
+ },
1504
+ async () => {
1505
+ this.statusDialog?.remove();
1506
+ this.statusDialog = null;
1507
+ });
1508
+ this.statusDialog.showPane(this.statusPaneContainer, 'tinted');
1509
+ this.statusDialog.updateStatus(i18nString(UIStrings.preparingTraceForDownload));
1510
+ this.statusDialog.updateProgressBar(i18nString(UIStrings.preparingTraceForDownload), 0);
1511
+ this.statusDialog.requestUpdate();
1512
+ await this.statusDialog.updateComplete;
1513
+ // Not sure why the above isn't sufficient.
1514
+ await new Promise(resolve => requestAnimationFrame(resolve));
1515
+ await new Promise(resolve => requestAnimationFrame(resolve));
1516
+
1467
1517
  // Base the filename on the trace's time of recording
1468
1518
  const isoDate =
1469
1519
  Platform.DateUtilities.toISO8601Compact(metadata.startTime ? new Date(metadata.startTime) : new Date());
@@ -1499,8 +1549,16 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
1499
1549
  let blob = new Blob(blobParts, {type: 'application/json'});
1500
1550
 
1501
1551
  if (config.shouldCompress) {
1552
+ this.statusDialog.updateStatus(i18nString(UIStrings.compressingTraceForDownload));
1553
+ this.statusDialog.updateProgressBar(i18nString(UIStrings.compressingTraceForDownload), 0);
1554
+
1502
1555
  fileName = `${fileName}.gz` as Platform.DevToolsPath.RawPathString;
1503
- const gzStream = Common.Gzip.compressStream(blob.stream());
1556
+ const inputSize = blob.size;
1557
+ const monitoredStream = Common.Gzip.createMonitoredStream(blob.stream(), bytesRead => {
1558
+ this.statusDialog?.updateProgressBar(
1559
+ i18nString(UIStrings.compressingTraceForDownload), bytesRead / inputSize * 100);
1560
+ });
1561
+ const gzStream = Common.Gzip.compressStream(monitoredStream);
1504
1562
  blob = await new Response(gzStream, {
1505
1563
  headers: {'Content-Type': 'application/gzip'},
1506
1564
  }).blob();
@@ -1515,9 +1573,12 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
1515
1573
  try {
1516
1574
  // The maximum string length in v8 is `2 ** 29 - 23`, aka 538 MB.
1517
1575
  // If the gzipped&base64-encoded trace is larger than that, this'll throw a RangeError.
1576
+ this.statusDialog.updateStatus(i18nString(UIStrings.encodingTraceForDownload));
1577
+ this.statusDialog.updateProgressBar(i18nString(UIStrings.encodingTraceForDownload), 100);
1518
1578
  bytesAsB64 = await Common.Base64.encode(blob);
1519
1579
  } catch {
1520
1580
  }
1581
+
1521
1582
  if (bytesAsB64?.length) {
1522
1583
  const contentData = new TextUtils.ContentData.ContentData(bytesAsB64, /* isBase64=*/ true, blob.type);
1523
1584
  await Workspace.FileManager.FileManager.instance().save(fileName, contentData, /* forceSaveAs=*/ true);
@@ -1531,6 +1592,9 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
1531
1592
  a.click();
1532
1593
  URL.revokeObjectURL(url);
1533
1594
  }
1595
+
1596
+ this.statusDialog.remove();
1597
+ this.statusDialog = null;
1534
1598
  }
1535
1599
 
1536
1600
  async handleSaveToFileAction(): Promise<void> {
@@ -1965,7 +2029,7 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
1965
2029
  {
1966
2030
  description: error,
1967
2031
  buttonText: i18nString(UIStrings.close),
1968
- hideStopButton: true,
2032
+ hideStopButton: false,
1969
2033
  showProgress: undefined,
1970
2034
  showTimer: undefined,
1971
2035
  },
@@ -2055,7 +2119,7 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
2055
2119
 
2056
2120
  private onClearButton(): void {
2057
2121
  this.#historyManager.clear();
2058
- this.#traceEngineModel = this.#instantiateNewModel();
2122
+ this.#instantiateNewModel();
2059
2123
  ModificationsManager.reset();
2060
2124
  this.#uninstallSourceMapsResolver();
2061
2125
  this.flameChart.getMainDataProvider().reset();
@@ -2467,23 +2531,6 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
2467
2531
  this.statusDialog?.updateStatus(i18nString(UIStrings.processingTrace));
2468
2532
  }
2469
2533
 
2470
- #listenForProcessingProgress(): void {
2471
- this.#traceEngineModel.addEventListener(Trace.TraceModel.ModelUpdateEvent.eventName, e => {
2472
- const updateEvent = e as Trace.TraceModel.ModelUpdateEvent;
2473
- const str = i18nString(UIStrings.processed);
2474
-
2475
- // Trace Engine will report progress from [0...1] but we still have more work to do. So, scale them down a bit.
2476
- const traceParseMaxProgress = 0.7;
2477
-
2478
- if (updateEvent.data.type === Trace.TraceModel.ModelUpdateType.COMPLETE) {
2479
- this.statusDialog?.updateProgressBar(str, 100 * traceParseMaxProgress);
2480
- } else if (updateEvent.data.type === Trace.TraceModel.ModelUpdateType.PROGRESS_UPDATE) {
2481
- const data = updateEvent.data.data;
2482
- this.statusDialog?.updateProgressBar(str, data.percent * 100 * traceParseMaxProgress);
2483
- }
2484
- });
2485
- }
2486
-
2487
2534
  #onSourceMapsNodeNamesResolved(): void {
2488
2535
  // Source maps can change the way calls hierarchies should look in
2489
2536
  // the flame chart (f.e. if some calls are ignore listed after
@@ -3061,7 +3108,7 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
3061
3108
  * 3. Flash the Insight with the highlight colour we use in other panels.
3062
3109
  */
3063
3110
  revealInsight(insightModel: Trace.Insights.Types.InsightModel): void {
3064
- const insightSetKey = insightModel.navigationId ?? Trace.Types.Events.NO_NAVIGATION;
3111
+ const insightSetKey = insightModel.navigation?.args.data?.navigationId ?? Trace.Types.Events.NO_NAVIGATION;
3065
3112
  this.#setActiveInsight({model: insightModel, insightSetKey}, {highlightInsight: true});
3066
3113
  }
3067
3114
 
@@ -824,6 +824,10 @@ export class TimelineUIUtils {
824
824
  link = 'https://web.dev/lcp/';
825
825
  name = 'largest contentful paint';
826
826
  break;
827
+ case Trace.Types.Events.Name.MARK_LCP_CANDIDATE_FOR_SOFT_NAVIGATION:
828
+ link = 'https://developer.chrome.com/docs/web-platform/soft-navigations-experiment';
829
+ name = 'largest contentful paint (soft navigation)';
830
+ break;
827
831
  case Trace.Types.Events.Name.MARK_FCP:
828
832
  link = 'https://web.dev/first-contentful-paint/';
829
833
  name = 'first contentful paint';
@@ -1001,6 +1005,20 @@ export class TimelineUIUtils {
1001
1005
  return contentHelper.fragment;
1002
1006
  }
1003
1007
 
1008
+ if (Trace.Types.Events.isNavigationStart(event)) {
1009
+ url = (event.args.data?.documentLoaderURL ?? event.args.data?.url) as Platform.DevToolsPath.UrlString;
1010
+ if (url) {
1011
+ contentHelper.appendElementRow(i18nString(UIStrings.url), LegacyComponents.Linkifier.Linkifier.linkifyURL(url));
1012
+ }
1013
+ }
1014
+
1015
+ if (Trace.Types.Events.isSoftNavigationStart(event)) {
1016
+ url = event.args.context.URL as Platform.DevToolsPath.UrlString;
1017
+ if (url) {
1018
+ contentHelper.appendElementRow(i18nString(UIStrings.url), LegacyComponents.Linkifier.Linkifier.linkifyURL(url));
1019
+ }
1020
+ }
1021
+
1004
1022
  if (Trace.Types.Events.isV8Compile(event)) {
1005
1023
  url = event.args.data?.url as Platform.DevToolsPath.UrlString;
1006
1024
  if (url) {
@@ -1428,6 +1446,7 @@ export class TimelineUIUtils {
1428
1446
  break;
1429
1447
  }
1430
1448
 
1449
+ case Trace.Types.Events.Name.MARK_LCP_CANDIDATE_FOR_SOFT_NAVIGATION:
1431
1450
  // @ts-expect-error Fall-through intended.
1432
1451
  case Trace.Types.Events.Name.MARK_LCP_CANDIDATE: {
1433
1452
  contentHelper.appendTextRow(i18nString(UIStrings.type), String(unsafeEventData['type']));
@@ -2244,6 +2263,10 @@ export class TimelineUIUtils {
2244
2263
  color = 'var(--color-text-primary)';
2245
2264
  tall = true;
2246
2265
  break;
2266
+ case Trace.Types.Events.Name.SOFT_NAVIGATION_START:
2267
+ color = 'var(--sys-color-blue)';
2268
+ tall = true;
2269
+ break;
2247
2270
  case Trace.Types.Events.Name.FRAME_STARTED_LOADING:
2248
2271
  color = 'green';
2249
2272
  tall = true;
@@ -2264,6 +2287,7 @@ export class TimelineUIUtils {
2264
2287
  color = 'var(--sys-color-green-bright)';
2265
2288
  tall = true;
2266
2289
  break;
2290
+ case Trace.Types.Events.Name.MARK_LCP_CANDIDATE_FOR_SOFT_NAVIGATION:
2267
2291
  case Trace.Types.Events.Name.MARK_LCP_CANDIDATE:
2268
2292
  color = 'var(--sys-color-green)';
2269
2293
  tall = true;
@@ -2509,6 +2533,7 @@ export function timeStampForEventAdjustedForClosestNavigationIfPossible(
2509
2533
  event,
2510
2534
  parsedTrace.data.Meta.traceBounds,
2511
2535
  parsedTrace.data.Meta.navigationsByNavigationId,
2536
+ parsedTrace.data.Meta.softNavigationsById,
2512
2537
  parsedTrace.data.Meta.navigationsByFrameId,
2513
2538
  );
2514
2539
  return Trace.Helpers.Timing.microToMilli(time);
@@ -2523,7 +2548,8 @@ export function timeStampForEventAdjustedForClosestNavigationIfPossible(
2523
2548
  export function isMarkerEvent(parsedTrace: Trace.TraceModel.ParsedTrace, event: Trace.Types.Events.Event): boolean {
2524
2549
  const {Name} = Trace.Types.Events;
2525
2550
 
2526
- if (event.name === Name.TIME_STAMP || event.name === Name.NAVIGATION_START) {
2551
+ if (event.name === Name.TIME_STAMP || event.name === Name.NAVIGATION_START ||
2552
+ event.name === Name.SOFT_NAVIGATION_START) {
2527
2553
  return true;
2528
2554
  }
2529
2555
 
@@ -2532,7 +2558,7 @@ export function isMarkerEvent(parsedTrace: Trace.TraceModel.ParsedTrace, event:
2532
2558
  }
2533
2559
 
2534
2560
  if (Trace.Types.Events.isMarkDOMContent(event) || Trace.Types.Events.isMarkLoad(event) ||
2535
- Trace.Types.Events.isLargestContentfulPaintCandidate(event)) {
2561
+ Trace.Types.Events.isAnyLargestContentfulPaintCandidate(event)) {
2536
2562
  // isOutermostMainFrame was added in 2022, so we fallback to isMainFrame
2537
2563
  // for older traces.
2538
2564
  if (!event.args.data) {
@@ -42,6 +42,7 @@ export const SORT_ORDER_PAGE_LOAD_MARKERS: Readonly<Record<string, number>> = {
42
42
  [Trace.Types.Events.Name.MARK_FIRST_PAINT]: 2,
43
43
  [Trace.Types.Events.Name.MARK_DOM_CONTENT]: 3,
44
44
  [Trace.Types.Events.Name.MARK_LCP_CANDIDATE]: 4,
45
+ [Trace.Types.Events.Name.MARK_LCP_CANDIDATE_FOR_SOFT_NAVIGATION]: 5,
45
46
  };
46
47
 
47
48
  export class TimingsTrackAppender implements TrackAppender {
@@ -182,7 +183,7 @@ export class TimingsTrackAppender implements TrackAppender {
182
183
  color = '#1A6937';
183
184
  title = Trace.Handlers.ModelHandlers.PageLoadMetrics.MetricName.FCP;
184
185
  }
185
- if (Trace.Types.Events.isLargestContentfulPaintCandidate(markerEvent)) {
186
+ if (Trace.Types.Events.isAnyLargestContentfulPaintCandidate(markerEvent)) {
186
187
  color = '#1A3422';
187
188
  title = Trace.Handlers.ModelHandlers.PageLoadMetrics.MetricName.LCP;
188
189
  }
@@ -282,6 +283,7 @@ export class TimingsTrackAppender implements TrackAppender {
282
283
  event,
283
284
  this.#parsedTrace.data.Meta.traceBounds,
284
285
  this.#parsedTrace.data.Meta.navigationsByNavigationId,
286
+ this.#parsedTrace.data.Meta.softNavigationsById,
285
287
  this.#parsedTrace.data.Meta.navigationsByFrameId,
286
288
  );
287
289
  info.formattedTime = getDurationString(timeOfEvent);
@@ -84,7 +84,7 @@ interface InsightData {
84
84
  }
85
85
 
86
86
  interface LocalMetrics {
87
- lcp: {value: Trace.Types.Timing.Micro, event: Trace.Types.Events.LargestContentfulPaintCandidate}|null;
87
+ lcp: {value: Trace.Types.Timing.Micro, event: Trace.Types.Events.AnyLargestContentfulPaintCandidate}|null;
88
88
  cls: {value: number, worstClusterEvent: Trace.Types.Events.Event|null};
89
89
  inp: {value: Trace.Types.Timing.Micro, event: Trace.Types.Events.SyntheticInteractionPair}|null;
90
90
  }
@@ -5,15 +5,17 @@
5
5
  import * as i18n from '../../../../core/i18n/i18n.js';
6
6
  import type {RenderBlockingInsightModel} from '../../../../models/trace/insights/RenderBlocking.js';
7
7
  import * as Trace from '../../../../models/trace/trace.js';
8
+ import * as UI from '../../../../ui/legacy/legacy.js';
8
9
  import * as Lit from '../../../../ui/lit/lit.js';
9
10
 
10
11
  import {BaseInsightComponent} from './BaseInsightComponent.js';
11
12
  import {eventRef} from './EventRef.js';
12
- import {createLimitedRows, renderOthersLabel, type TableDataRow} from './Table.js';
13
+ import {createLimitedRows, renderOthersLabel, Table, type TableDataRow} from './Table.js';
13
14
 
14
15
  const {UIStrings, i18nString, createOverlayForRequest} = Trace.Insights.Models.RenderBlocking;
15
16
 
16
17
  const {html} = Lit;
18
+ const {widgetConfig} = UI.Widget;
17
19
 
18
20
  export class RenderBlocking extends BaseInsightComponent<RenderBlockingInsightModel> {
19
21
  override internalName = 'render-blocking-requests';
@@ -58,12 +60,12 @@ export class RenderBlocking extends BaseInsightComponent<RenderBlockingInsightMo
58
60
  // clang-format off
59
61
  return html`
60
62
  <div class="insight-section">
61
- <devtools-widget
62
- .data=${{
63
+ <devtools-widget .widgetConfig=${widgetConfig(Table, {
64
+ data: {
63
65
  insight: this,
64
66
  headers: [i18nString(UIStrings.renderBlockingRequest), i18nString(UIStrings.duration)],
65
67
  rows,
66
- }}>
68
+ }})}>
67
69
  </devtools-widget>
68
70
  </div>
69
71
  `;
@@ -1616,6 +1616,10 @@ export class Overlays extends EventTarget {
1616
1616
  #mouseMoveOverlay(
1617
1617
  e: MouseEvent, event: Trace.Types.Events.PageLoadEvent, name: string, overlay: Trace.Types.Overlays.TimingsMarker,
1618
1618
  markers: HTMLElement, marker: HTMLElement): void {
1619
+ if (Trace.Types.Events.isSoftNavigationStart(event)) {
1620
+ name = 'Soft Nav';
1621
+ }
1622
+
1619
1623
  const fieldResult = overlay.entryToFieldResult.get(event);
1620
1624
  const popoverElement = this.#createOverlayPopover(overlay.adjustedTimestamp, name, fieldResult);
1621
1625
  this.#lastMouseOffsetX = e.offsetX + (markers.offsetLeft || 0) + (marker.offsetLeft || 0);
@@ -90,11 +90,18 @@
90
90
  pointer-events: none;
91
91
  }
92
92
 
93
- .timeline.panel .status-pane-container.tinted {
93
+ .timeline.panel .status-pane-container.opaque {
94
94
  background-color: var(--sys-color-cdt-base-container);
95
95
  pointer-events: auto;
96
96
  }
97
97
 
98
+ .timeline.panel .status-pane-container.tinted {
99
+ /* stylelint-disable-next-line plugin/use_theme_colors */
100
+ background-color: #0005;
101
+ background-blend-mode: multiply;
102
+ pointer-events: auto;
103
+ }
104
+
98
105
  .timeline-landing-page.legacy > div > p {
99
106
  flex: none;
100
107
  white-space: pre-line;
@@ -29,7 +29,8 @@ export function nodeIdsForEvent(
29
29
  } else if (Trace.Types.Events.isSyntheticLayoutShift(event) && event.args.data?.impacted_nodes) {
30
30
  event.args.data.impacted_nodes.forEach(node => foundIds.add(node.node_id));
31
31
  } else if (
32
- Trace.Types.Events.isLargestContentfulPaintCandidate(event) && typeof event.args.data?.nodeId !== 'undefined') {
32
+ Trace.Types.Events.isAnyLargestContentfulPaintCandidate(event) &&
33
+ typeof event.args.data?.nodeId !== 'undefined') {
33
34
  foundIds.add(event.args.data.nodeId);
34
35
  } else if (Trace.Types.Events.isPaint(event) && typeof event.args.data.nodeId !== 'undefined') {
35
36
  foundIds.add(event.args.data.nodeId);
@@ -1,7 +1,7 @@
1
1
  Name: Dependencies sourced from the upstream `chromium` repository
2
2
  URL: https://chromium.googlesource.com/chromium/src
3
3
  Version: N/A
4
- Revision: 62c1a660407ec8f7589a271587ffee39bc215751
4
+ Revision: cb5e3196dab34d7c2e8e58809202c6858ad441dd
5
5
  Update Mechanism: Manual (https://crbug.com/428069060)
6
6
  License: BSD-3-Clause
7
7
  License File: LICENSE
@@ -49,7 +49,7 @@ export class AiCodeGenerationProvider {
49
49
  #devtoolsLocale: string;
50
50
  #aiCodeCompletionSetting = Common.Settings.Settings.instance().createSetting('ai-code-completion-enabled', false);
51
51
  #generationTeaserCompartment = new CodeMirror.Compartment();
52
- #generationTeaser: PanelCommon.AiCodeGenerationTeaser;
52
+ #generationTeaser: PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaser;
53
53
  #editor?: TextEditor;
54
54
  #aiCodeGenerationConfig: AiCodeGenerationConfig;
55
55
  #aiCodeGeneration?: AiCodeGeneration.AiCodeGeneration.AiCodeGeneration;
@@ -63,7 +63,7 @@ export class AiCodeGenerationProvider {
63
63
  if (!AiCodeGeneration.AiCodeGeneration.AiCodeGeneration.isAiCodeGenerationEnabled(this.#devtoolsLocale)) {
64
64
  throw new Error('AI code generation feature is not enabled.');
65
65
  }
66
- this.#generationTeaser = new PanelCommon.AiCodeGenerationTeaser();
66
+ this.#generationTeaser = new PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaser();
67
67
  this.#aiCodeGenerationConfig = aiCodeGenerationConfig;
68
68
  }
69
69
 
@@ -73,7 +73,8 @@ export class AiCodeGenerationProvider {
73
73
 
74
74
  extension(): CodeMirror.Extension[] {
75
75
  return [
76
- CodeMirror.EditorView.updateListener.of(update => this.activateTeaser(update)),
76
+ CodeMirror.EditorView.updateListener.of(update => this.#activateTeaser(update)),
77
+ CodeMirror.EditorView.updateListener.of(update => this.#abortGenerationDuringUpdate(update)),
77
78
  aiAutoCompleteSuggestion,
78
79
  aiAutoCompleteSuggestionState,
79
80
  aiCodeGenerationTeaserModeState,
@@ -141,7 +142,9 @@ export class AiCodeGenerationProvider {
141
142
  });
142
143
  return true;
143
144
  }
144
- if (this.#generationTeaser.isShowing() && this.#generationTeaser.loading) {
145
+ const generationTeaserIsLoading = this.#generationTeaser.displayState ===
146
+ PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaserDisplayState.LOADING;
147
+ if (this.#generationTeaser.isShowing() && generationTeaserIsLoading) {
145
148
  this.#controller.abort();
146
149
  this.#controller = new AbortController();
147
150
  this.#dismissTeaser();
@@ -187,11 +190,11 @@ export class AiCodeGenerationProvider {
187
190
  }
188
191
 
189
192
  #dismissTeaser(): void {
190
- this.#generationTeaser.loading = false;
193
+ this.#generationTeaser.displayState = PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaserDisplayState.TRIGGER;
191
194
  this.#editor?.dispatch({effects: setAiCodeGenerationTeaserMode.of(AiCodeGenerationTeaserMode.DISMISSED)});
192
195
  }
193
196
 
194
- async activateTeaser(update: CodeMirror.ViewUpdate): Promise<void> {
197
+ #activateTeaser(update: CodeMirror.ViewUpdate): void {
195
198
  const currentTeaserMode = update.state.field(aiCodeGenerationTeaserModeState);
196
199
  if (currentTeaserMode === AiCodeGenerationTeaserMode.ACTIVE) {
197
200
  return;
@@ -202,12 +205,35 @@ export class AiCodeGenerationProvider {
202
205
  update.view.dispatch({effects: setAiCodeGenerationTeaserMode.of(AiCodeGenerationTeaserMode.ACTIVE)});
203
206
  }
204
207
 
208
+ /**
209
+ * Monitors editor changes to cancel an ongoing AI generation.
210
+ * We abort the request and dismiss the teaser if the user modifies the
211
+ * document or moves their cursor/selection. These actions indicate the user
212
+ * is no longer focused on the current generation point or has manually
213
+ * resumed editing, making the pending suggestion irrelevant.
214
+ */
215
+ #abortGenerationDuringUpdate(update: CodeMirror.ViewUpdate): void {
216
+ if (!update.docChanged && update.state.selection.main.head === update.startState.selection.main.head) {
217
+ return;
218
+ }
219
+ const currentTeaserMode = update.state.field(aiCodeGenerationTeaserModeState);
220
+ const generationTeaserIsLoading = this.#generationTeaser.displayState ===
221
+ PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaserDisplayState.LOADING;
222
+ // Generation should be in progress
223
+ if (currentTeaserMode === AiCodeGenerationTeaserMode.DISMISSED || !generationTeaserIsLoading) {
224
+ return;
225
+ }
226
+ this.#controller.abort();
227
+ this.#controller = new AbortController();
228
+ this.#dismissTeaser();
229
+ }
230
+
205
231
  async #triggerAiCodeGeneration(options?: {signal?: AbortSignal}): Promise<void> {
206
232
  if (!this.#editor || !this.#aiCodeGeneration) {
207
233
  return;
208
234
  }
209
235
 
210
- this.#generationTeaser.loading = true;
236
+ this.#generationTeaser.displayState = PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaserDisplayState.LOADING;
211
237
  const cursor = this.#editor.state.selection.main.head;
212
238
  // TODO(b/445899453): Detect all types of comments
213
239
  const query = this.#editor.state.doc.lineAt(cursor).text;
@@ -265,44 +291,60 @@ export class AiCodeGenerationProvider {
265
291
  }
266
292
  }
267
293
 
268
- // TODO(b/445899453): Handle teaser's discovery mode
269
- function aiCodeGenerationTeaserExtension(teaser: PanelCommon.AiCodeGenerationTeaser): CodeMirror.Extension {
294
+ function aiCodeGenerationTeaserExtension(teaser: PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaser):
295
+ CodeMirror.Extension {
270
296
  return CodeMirror.ViewPlugin.fromClass(class {
271
- #teaserMode: AiCodeGenerationTeaserMode;
297
+ #view: CodeMirror.EditorView;
272
298
 
273
- constructor(readonly view: CodeMirror.EditorView) {
274
- this.#teaserMode = view.state.field(aiCodeGenerationTeaserModeState);
299
+ constructor(view: CodeMirror.EditorView) {
300
+ this.#view = view;
301
+ this.#updateTeaserState(view.state);
275
302
  }
276
303
 
277
304
  update(update: CodeMirror.ViewUpdate): void {
278
- const currentTeaserMode = update.state.field(aiCodeGenerationTeaserModeState);
279
- if (currentTeaserMode !== this.#teaserMode) {
280
- this.#teaserMode = currentTeaserMode;
281
- }
282
305
  if (!update.docChanged && update.state.selection.main.head === update.startState.selection.main.head) {
283
306
  return;
284
307
  }
285
- if (teaser.loading) {
286
- teaser.loading = false;
287
- }
308
+ this.#updateTeaserState(update.state);
288
309
  }
289
310
 
290
311
  get decorations(): CodeMirror.DecorationSet {
291
- if (this.#teaserMode === AiCodeGenerationTeaserMode.DISMISSED) {
312
+ const teaserMode = this.#view.state.field(aiCodeGenerationTeaserModeState);
313
+ if (teaserMode === AiCodeGenerationTeaserMode.DISMISSED) {
292
314
  return CodeMirror.Decoration.none;
293
315
  }
294
- const cursorPosition = this.view.state.selection.main.head;
295
- const line = this.view.state.doc.lineAt(cursorPosition);
316
+
317
+ const cursorPosition = this.#view.state.selection.main.head;
318
+ const line = this.#view.state.doc.lineAt(cursorPosition);
319
+
320
+ const isEmptyLine = line.length === 0;
296
321
  // TODO(b/445899453): Detect all types of comments
297
322
  const isComment = line.text.startsWith('//');
298
323
  const isCursorAtEndOfLine = cursorPosition >= line.to;
299
- if (!isComment || !isCursorAtEndOfLine) {
300
- return CodeMirror.Decoration.none;
324
+
325
+ if ((isEmptyLine) || (isComment && isCursorAtEndOfLine)) {
326
+ return CodeMirror.Decoration.set([
327
+ CodeMirror.Decoration.widget({widget: new AiCodeCompletionTeaserPlaceholder(teaser), side: 1})
328
+ .range(cursorPosition),
329
+ ]);
330
+ }
331
+ return CodeMirror.Decoration.none;
332
+ }
333
+
334
+ #updateTeaserState(state: CodeMirror.EditorState): void {
335
+ // Only handle non loading states, as updates during generation are handled by
336
+ // #abortGenerationDuringUpdate in AiCodeGenerationProvider
337
+ if (teaser.displayState === PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaserDisplayState.LOADING) {
338
+ return;
339
+ }
340
+ const cursorPosition = state.selection.main.head;
341
+ const line = state.doc.lineAt(cursorPosition);
342
+ const isEmptyLine = line.length === 0;
343
+ if (isEmptyLine) {
344
+ teaser.displayState = PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaserDisplayState.DISCOVERY;
345
+ } else {
346
+ teaser.displayState = PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaserDisplayState.TRIGGER;
301
347
  }
302
- return CodeMirror.Decoration.set([
303
- CodeMirror.Decoration.widget({widget: new AiCodeCompletionTeaserPlaceholder(teaser), side: 1})
304
- .range(cursorPosition),
305
- ]);
306
348
  }
307
349
  }, {
308
350
  decorations: v => v.decorations,
@@ -150,6 +150,7 @@ export class SearchableView extends VBox {
150
150
  private replaceToggleButton: ToolbarToggle;
151
151
  private searchInputElement: HTMLInputElement;
152
152
  private matchesElement: HTMLElement;
153
+ private matchesElementValue: HTMLElement;
153
154
  private searchNavigationPrevElement: ToolbarButton;
154
155
  private searchNavigationNextElement: ToolbarButton;
155
156
  private readonly replaceInputElement: HTMLInputElement;
@@ -314,6 +315,9 @@ export class SearchableView extends VBox {
314
315
  this.matchesElement.style.color = 'var(--sys-color-on-surface-subtle)';
315
316
  this.matchesElement.style.padding = '0 var(--sys-size-3)';
316
317
  this.matchesElement.classList.add('search-results-matches');
318
+ ARIAUtils.markAsPoliteLiveRegion(this.matchesElement, false);
319
+ this.matchesElementValue = this.matchesElement.createChild('span');
320
+ ARIAUtils.setHidden(this.matchesElementValue, true);
317
321
  toolbar.appendToolbarItem(matchesText);
318
322
 
319
323
  const cancelButtonElement = new Buttons.Button.Button();
@@ -455,7 +459,7 @@ export class SearchableView extends VBox {
455
459
  resetSearch(): void {
456
460
  this.clearSearch();
457
461
  this.updateReplaceVisibility();
458
- this.matchesElement.textContent = '';
462
+ this.matchesElementValue.textContent = '';
459
463
  }
460
464
 
461
465
  refreshSearch(): void {
@@ -504,15 +508,17 @@ export class SearchableView extends VBox {
504
508
 
505
509
  private updateSearchMatchesCountAndCurrentMatchIndex(matches: number, currentMatchIndex: number): void {
506
510
  if (!this.currentQuery) {
507
- this.matchesElement.textContent = '';
511
+ this.matchesElementValue.textContent = '';
508
512
  } else if (matches === 0 || currentMatchIndex >= 0) {
509
- this.matchesElement.textContent = i18nString(UIStrings.dOfD, {PH1: currentMatchIndex + 1, PH2: matches});
513
+ this.matchesElementValue.textContent = i18nString(UIStrings.dOfD, {PH1: currentMatchIndex + 1, PH2: matches});
510
514
  ARIAUtils.setLabel(
511
515
  this.matchesElement, i18nString(UIStrings.accessibledOfD, {PH1: currentMatchIndex + 1, PH2: matches}));
512
516
  } else if (matches === 1) {
513
- this.matchesElement.textContent = i18nString(UIStrings.matchString);
517
+ this.matchesElementValue.textContent = i18nString(UIStrings.matchString);
518
+ ARIAUtils.setLabel(this.matchesElement, i18nString(UIStrings.matchString));
514
519
  } else {
515
- this.matchesElement.textContent = i18nString(UIStrings.dMatches, {PH1: matches});
520
+ this.matchesElementValue.textContent = i18nString(UIStrings.dMatches, {PH1: matches});
521
+ ARIAUtils.setLabel(this.matchesElement, i18nString(UIStrings.dMatches, {PH1: matches}));
516
522
  }
517
523
  this.updateSearchNavigationButtonState(matches > 0);
518
524
  }
@@ -236,7 +236,7 @@ export class SplitWidget extends Common.ObjectWrapper.eventMixin<EventTypes, typ
236
236
  this.#restoreAndApplyShowModeFromSettings();
237
237
  }
238
238
 
239
- showMode(): string {
239
+ showMode(): ShowMode {
240
240
  return this.#showMode;
241
241
  }
242
242