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.
- package/front_end/Images/src/container.svg +4 -0
- package/front_end/core/common/Gzip.ts +15 -0
- package/front_end/core/sdk/CSSMetadata.ts +6 -6
- package/front_end/core/sdk/CSSModel.ts +2 -2
- package/front_end/core/sdk/DOMModel.ts +7 -3
- package/front_end/generated/InspectorBackendCommands.ts +2 -1
- package/front_end/generated/SupportedCSSProperties.js +64 -32
- package/front_end/generated/protocol-mapping.d.ts +9 -0
- package/front_end/generated/protocol-proxy-api.d.ts +7 -0
- package/front_end/generated/protocol.ts +14 -1
- package/front_end/models/ai_assistance/agents/StylingAgent.ts +1 -1
- package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +11 -7
- package/front_end/models/trace/LanternComputationData.ts +4 -3
- package/front_end/models/trace/Processor.ts +6 -5
- package/front_end/models/trace/Styles.ts +10 -1
- package/front_end/models/trace/handlers/LargestImagePaintHandler.ts +2 -2
- package/front_end/models/trace/handlers/MetaHandler.ts +14 -0
- package/front_end/models/trace/handlers/PageLoadMetricsHandler.ts +54 -34
- package/front_end/models/trace/helpers/Timing.ts +8 -1
- package/front_end/models/trace/insights/Common.ts +1 -1
- package/front_end/models/trace/insights/LCPBreakdown.ts +4 -4
- package/front_end/models/trace/insights/LCPDiscovery.ts +3 -3
- package/front_end/models/trace/insights/RenderBlocking.ts +1 -1
- package/front_end/models/trace/insights/types.ts +1 -1
- package/front_end/models/trace/types/TraceEvents.ts +62 -10
- package/front_end/panels/common/AiCodeGenerationTeaser.ts +48 -12
- package/front_end/panels/common/aiCodeGenerationTeaser.css +14 -0
- package/front_end/panels/common/common.ts +1 -1
- package/front_end/panels/console/consoleView.css +1 -1
- package/front_end/panels/elements/CSSRuleValidator.ts +38 -0
- package/front_end/panels/elements/ElementsTreeElement.ts +79 -58
- package/front_end/panels/elements/ElementsTreeOutline.ts +0 -17
- package/front_end/panels/elements/StylesSidebarPane.ts +15 -4
- package/front_end/panels/timeline/StatusDialog.ts +4 -3
- package/front_end/panels/timeline/TimelineFlameChartNetworkDataProvider.ts +3 -16
- package/front_end/panels/timeline/TimelineFlameChartView.ts +64 -21
- package/front_end/panels/timeline/TimelinePanel.ts +71 -24
- package/front_end/panels/timeline/TimelineUIUtils.ts +28 -2
- package/front_end/panels/timeline/TimingsTrackAppender.ts +3 -1
- package/front_end/panels/timeline/components/SidebarSingleInsightSet.ts +1 -1
- package/front_end/panels/timeline/components/insights/RenderBlocking.ts +6 -4
- package/front_end/panels/timeline/overlays/OverlaysImpl.ts +4 -0
- package/front_end/panels/timeline/timelinePanel.css +8 -1
- package/front_end/panels/timeline/utils/EntryNodes.ts +2 -1
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/ui/components/text_editor/AiCodeGenerationProvider.ts +70 -28
- package/front_end/ui/legacy/SearchableView.ts +11 -5
- package/front_end/ui/legacy/SplitWidget.ts +1 -1
- package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +43 -9
- package/front_end/ui/visual_logging/KnownContextValues.ts +13 -0
- 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
|
-
|
|
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
|
|
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:
|
|
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.#
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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:
|
|
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
|
|
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
|
-
|
|
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.
|
|
193
|
+
this.#generationTeaser.displayState = PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaserDisplayState.TRIGGER;
|
|
191
194
|
this.#editor?.dispatch({effects: setAiCodeGenerationTeaserMode.of(AiCodeGenerationTeaserMode.DISMISSED)});
|
|
192
195
|
}
|
|
193
196
|
|
|
194
|
-
|
|
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.
|
|
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
|
-
|
|
269
|
-
|
|
294
|
+
function aiCodeGenerationTeaserExtension(teaser: PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaser):
|
|
295
|
+
CodeMirror.Extension {
|
|
270
296
|
return CodeMirror.ViewPlugin.fromClass(class {
|
|
271
|
-
#
|
|
297
|
+
#view: CodeMirror.EditorView;
|
|
272
298
|
|
|
273
|
-
constructor(
|
|
274
|
-
this.#
|
|
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
|
-
|
|
286
|
-
teaser.loading = false;
|
|
287
|
-
}
|
|
308
|
+
this.#updateTeaserState(update.state);
|
|
288
309
|
}
|
|
289
310
|
|
|
290
311
|
get decorations(): CodeMirror.DecorationSet {
|
|
291
|
-
|
|
312
|
+
const teaserMode = this.#view.state.field(aiCodeGenerationTeaserModeState);
|
|
313
|
+
if (teaserMode === AiCodeGenerationTeaserMode.DISMISSED) {
|
|
292
314
|
return CodeMirror.Decoration.none;
|
|
293
315
|
}
|
|
294
|
-
|
|
295
|
-
const
|
|
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
|
-
|
|
300
|
-
|
|
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.
|
|
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.
|
|
511
|
+
this.matchesElementValue.textContent = '';
|
|
508
512
|
} else if (matches === 0 || currentMatchIndex >= 0) {
|
|
509
|
-
this.
|
|
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.
|
|
517
|
+
this.matchesElementValue.textContent = i18nString(UIStrings.matchString);
|
|
518
|
+
ARIAUtils.setLabel(this.matchesElement, i18nString(UIStrings.matchString));
|
|
514
519
|
} else {
|
|
515
|
-
this.
|
|
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
|
}
|