chrome-devtools-frontend 1.0.1596260 → 1.0.1596535
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/front_end/core/common/Console.ts +6 -6
- package/front_end/core/common/Settings.ts +12 -8
- package/front_end/core/root/DevToolsContext.ts +13 -7
- package/front_end/core/sdk/CPUThrottlingManager.ts +12 -9
- package/front_end/core/sdk/FrameManager.ts +7 -8
- package/front_end/entrypoints/greendev_floaty/FloatyEntrypoint.ts +1 -1
- package/front_end/entrypoints/main/MainImpl.ts +1 -2
- package/front_end/foundation/Universe.ts +22 -11
- package/front_end/generated/InspectorBackendCommands.ts +7 -0
- package/front_end/generated/protocol-mapping.d.ts +16 -0
- package/front_end/generated/protocol-proxy-api.d.ts +25 -0
- package/front_end/generated/protocol.ts +71 -0
- package/front_end/models/ai_assistance/AiConversation.ts +6 -95
- package/front_end/models/ai_assistance/agents/AiAgent.ts +11 -1
- package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +14 -0
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +1 -0
- package/front_end/panels/ai_assistance/components/ChatMessage.ts +25 -4
- package/front_end/panels/ai_assistance/components/WalkthroughView.ts +2 -2
- package/front_end/panels/elements/StandaloneStylesContainer.ts +48 -12
- package/front_end/panels/elements/StylePropertiesSection.ts +97 -1
- package/front_end/panels/elements/StylePropertyTreeElement.ts +16 -0
- package/front_end/panels/elements/StylesContainer.ts +1 -0
- package/front_end/panels/elements/StylesSidebarPane.ts +48 -32
- package/front_end/panels/elements/stylePropertiesTreeOutline.css +1 -1
- package/front_end/panels/lighthouse/LighthouseController.ts +13 -5
- package/front_end/panels/lighthouse/LighthousePanel.ts +22 -5
- package/front_end/panels/recorder/components/StepView.ts +0 -2
- package/front_end/panels/recorder/components/stepView.css +13 -13
- package/front_end/panels/recorder/components/timelineSection.css +6 -7
- package/front_end/panels/timeline/TimelinePanel.ts +18 -0
- package/front_end/panels/timeline/components/CWVMetrics.ts +339 -0
- package/front_end/panels/timeline/components/Sidebar.ts +17 -0
- package/front_end/panels/timeline/components/SidebarInsightsTab.ts +10 -0
- package/front_end/panels/timeline/components/SidebarSingleInsightSet.ts +25 -267
- package/front_end/panels/timeline/components/components.ts +2 -0
- package/front_end/panels/timeline/components/cwvMetrics.css +107 -0
- package/front_end/panels/timeline/components/sidebarSingleInsightSet.css +0 -102
- package/front_end/panels/timeline/timeline-meta.ts +11 -0
- package/front_end/panels/timeline/utils/Helpers.ts +5 -1
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/third_party/puppeteer/README.chromium +2 -2
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.d.ts +7 -0
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Page.d.ts +1 -0
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Page.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Page.js +3 -0
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Page.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.d.ts +1 -0
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.js +6 -0
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.d.ts +1 -0
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.js +5 -0
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +2 -2
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +2 -2
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.d.ts +7 -0
- package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +14 -3
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.d.ts +7 -0
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Page.d.ts +1 -0
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Page.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Page.js +3 -0
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Page.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.d.ts +1 -0
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.js +6 -0
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.d.ts +1 -0
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.js +5 -0
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +2 -2
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +2 -2
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/types.d.ts +7 -0
- package/front_end/third_party/puppeteer/package/package.json +1 -1
- package/front_end/third_party/puppeteer/package/src/api/Page.ts +8 -0
- package/front_end/third_party/puppeteer/package/src/bidi/Page.ts +4 -0
- package/front_end/third_party/puppeteer/package/src/cdp/Browser.ts +7 -0
- package/front_end/third_party/puppeteer/package/src/cdp/Page.ts +6 -0
- package/front_end/third_party/puppeteer/package/src/revisions.ts +2 -2
- package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
- package/front_end/ui/components/text_editor/AutocompleteHistory.ts +89 -5
- package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
// Copyright 2026 The Chromium Authors
|
|
2
|
+
// Use of this source code is governed by a BSD-style license that can be
|
|
3
|
+
// found in the LICENSE file.
|
|
4
|
+
|
|
5
|
+
import * as i18n from '../../../core/i18n/i18n.js';
|
|
6
|
+
import * as Platform from '../../../core/platform/platform.js';
|
|
7
|
+
import * as CrUXManager from '../../../models/crux-manager/crux-manager.js';
|
|
8
|
+
import * as Trace from '../../../models/trace/trace.js';
|
|
9
|
+
import * as Buttons from '../../../ui/components/buttons/buttons.js';
|
|
10
|
+
import * as UI from '../../../ui/legacy/legacy.js';
|
|
11
|
+
import * as Lit from '../../../ui/lit/lit.js';
|
|
12
|
+
import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js';
|
|
13
|
+
|
|
14
|
+
import cwvMetricsStyles from './cwvMetrics.css.js';
|
|
15
|
+
import {md} from './insights/Helpers.js';
|
|
16
|
+
import * as Insights from './insights/insights.js';
|
|
17
|
+
import {isFieldWorseThanLocal, NumberWithUnit} from './Utils.js';
|
|
18
|
+
|
|
19
|
+
const {html} = Lit.StaticHtml;
|
|
20
|
+
|
|
21
|
+
const UIStrings = {
|
|
22
|
+
/**
|
|
23
|
+
* @description title used for a metric value to tell the user about its score classification
|
|
24
|
+
* @example {INP} PH1
|
|
25
|
+
* @example {1.2s} PH2
|
|
26
|
+
* @example {poor} PH3
|
|
27
|
+
*/
|
|
28
|
+
metricScore: '{PH1}: {PH2} {PH3} score',
|
|
29
|
+
/**
|
|
30
|
+
* @description title used for a metric value to tell the user that the data is unavailable
|
|
31
|
+
* @example {INP} PH1
|
|
32
|
+
*/
|
|
33
|
+
metricScoreUnavailable: '{PH1}: unavailable',
|
|
34
|
+
/**
|
|
35
|
+
* @description Label denoting that metrics were observed in the field, from real use data (CrUX). Also denotes if from URL or Origin dataset.
|
|
36
|
+
* @example {URL} PH1
|
|
37
|
+
*/
|
|
38
|
+
fieldScoreLabel: 'Field ({PH1})',
|
|
39
|
+
/**
|
|
40
|
+
* @description Label for an option that selects the page's specific URL as opposed to it's entire origin/domain.
|
|
41
|
+
*/
|
|
42
|
+
urlOption: 'URL',
|
|
43
|
+
/**
|
|
44
|
+
* @description Label for an option that selects the page's entire origin/domain as opposed to it's specific URL.
|
|
45
|
+
*/
|
|
46
|
+
originOption: 'Origin',
|
|
47
|
+
/**
|
|
48
|
+
* @description Title for button that closes a warning popup.
|
|
49
|
+
*/
|
|
50
|
+
dismissTitle: 'Dismiss',
|
|
51
|
+
/**
|
|
52
|
+
* @description Title shown in a warning dialog when field metrics (collected from real users) is worse than the locally observed metrics.
|
|
53
|
+
*/
|
|
54
|
+
fieldMismatchTitle: 'Field & local metrics mismatch',
|
|
55
|
+
/**
|
|
56
|
+
* @description Text shown in a warning dialog when field metrics (collected from real users) is worse than the locally observed metrics.
|
|
57
|
+
* Asks user to use features such as throttling and device emulation.
|
|
58
|
+
*/
|
|
59
|
+
fieldMismatchNotice:
|
|
60
|
+
'There are many reasons why local and field metrics [may not match](https://web.dev/articles/lab-and-field-data-differences). ' +
|
|
61
|
+
'Adjust [throttling settings and device emulation](https://developer.chrome.com/docs/devtools/device-mode) to analyze traces more similar to the average user\'s environment.',
|
|
62
|
+
} as const;
|
|
63
|
+
|
|
64
|
+
const str_ = i18n.i18n.registerUIStrings('panels/timeline/components/CWVMetrics.ts', UIStrings);
|
|
65
|
+
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
|
|
66
|
+
|
|
67
|
+
interface LocalMetrics {
|
|
68
|
+
lcp: {value: Trace.Types.Timing.Micro, event: Trace.Types.Events.AnyLargestContentfulPaintCandidate}|null;
|
|
69
|
+
cls: {value: number, worstClusterEvent: Trace.Types.Events.Event|null};
|
|
70
|
+
inp: {value: Trace.Types.Timing.Micro, event: Trace.Types.Events.SyntheticInteractionPair}|null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function getLocalMetrics(parsedTrace: Trace.TraceModel.ParsedTrace|null, insightSetKey: string|null): LocalMetrics|
|
|
74
|
+
null {
|
|
75
|
+
if (!parsedTrace || !insightSetKey) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const insightSet = parsedTrace.insights?.get(insightSetKey);
|
|
80
|
+
if (!insightSet) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const lcp = Trace.Insights.Common.getLCP(insightSet);
|
|
85
|
+
const cls = Trace.Insights.Common.getCLS(insightSet);
|
|
86
|
+
const inp = Trace.Insights.Common.getINP(insightSet);
|
|
87
|
+
|
|
88
|
+
return {lcp, cls, inp};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function getFieldMetrics(parsedTrace: Trace.TraceModel.ParsedTrace|null, insightSetKey: string|null):
|
|
92
|
+
Trace.Insights.Common.CrUXFieldMetricResults|null {
|
|
93
|
+
if (!parsedTrace || !parsedTrace.metadata?.cruxFieldData || !insightSetKey) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const insightSet = parsedTrace.insights?.get(insightSetKey);
|
|
98
|
+
if (!insightSet) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let scope: CrUXManager.Scope|null = null;
|
|
103
|
+
try {
|
|
104
|
+
scope = CrUXManager.CrUXManager.instance().getSelectedScope();
|
|
105
|
+
} catch {
|
|
106
|
+
// test environment
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const fieldMetricsResults =
|
|
110
|
+
Trace.Insights.Common.getFieldMetricsForInsightSet(insightSet, parsedTrace.metadata, scope);
|
|
111
|
+
if (!fieldMetricsResults) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return fieldMetricsResults;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
interface MetricsViewInput {
|
|
119
|
+
parsedTrace: Trace.TraceModel.ParsedTrace|null;
|
|
120
|
+
insightSetKey: string|null;
|
|
121
|
+
didDismissFieldMismatchNotice: boolean;
|
|
122
|
+
onDismisFieldMismatchNotice: () => void;
|
|
123
|
+
onClickMetric: (traceEvent: Trace.Types.Events.Event) => void;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
type MetricsView = (input: MetricsViewInput, output: undefined, target: HTMLElement) => void;
|
|
127
|
+
|
|
128
|
+
const CWV_METRICS_VIEW: MetricsView = (input, _output, target) => {
|
|
129
|
+
const {
|
|
130
|
+
parsedTrace,
|
|
131
|
+
insightSetKey,
|
|
132
|
+
didDismissFieldMismatchNotice,
|
|
133
|
+
onDismisFieldMismatchNotice,
|
|
134
|
+
onClickMetric,
|
|
135
|
+
} = input;
|
|
136
|
+
|
|
137
|
+
const local = getLocalMetrics(parsedTrace, insightSetKey);
|
|
138
|
+
const field = getFieldMetrics(parsedTrace, insightSetKey);
|
|
139
|
+
|
|
140
|
+
const localValues = {
|
|
141
|
+
lcp: local?.lcp?.value !== undefined ? Trace.Helpers.Timing.microToMilli(local?.lcp.value) : undefined,
|
|
142
|
+
inp: local?.inp?.value !== undefined ? Trace.Helpers.Timing.microToMilli(local?.inp.value) : undefined,
|
|
143
|
+
};
|
|
144
|
+
const fieldValues = field && {
|
|
145
|
+
lcp: field.lcp?.value !== undefined ? Trace.Helpers.Timing.microToMilli(field.lcp.value) : undefined,
|
|
146
|
+
inp: field.inp?.value !== undefined ? Trace.Helpers.Timing.microToMilli(field.inp.value) : undefined,
|
|
147
|
+
};
|
|
148
|
+
const showFieldMismatchNotice =
|
|
149
|
+
!didDismissFieldMismatchNotice && !!fieldValues && isFieldWorseThanLocal(localValues, fieldValues);
|
|
150
|
+
|
|
151
|
+
function renderMetricValue(
|
|
152
|
+
metric: 'LCP'|'CLS'|'INP', value: number|null, relevantEvent: Trace.Types.Events.Event|null): Lit.LitTemplate {
|
|
153
|
+
let valueText: string;
|
|
154
|
+
let valueDisplay: HTMLElement|string;
|
|
155
|
+
let classification;
|
|
156
|
+
if (value === null) {
|
|
157
|
+
valueText = valueDisplay = '-';
|
|
158
|
+
classification = Trace.Handlers.ModelHandlers.PageLoadMetrics.ScoreClassification.UNCLASSIFIED;
|
|
159
|
+
} else if (metric === 'LCP') {
|
|
160
|
+
const micros = value as Trace.Types.Timing.Micro;
|
|
161
|
+
const {text, element} = NumberWithUnit.formatMicroSecondsAsSeconds(micros);
|
|
162
|
+
valueText = text;
|
|
163
|
+
valueDisplay = element;
|
|
164
|
+
classification =
|
|
165
|
+
Trace.Handlers.ModelHandlers.PageLoadMetrics.scoreClassificationForLargestContentfulPaint(micros);
|
|
166
|
+
} else if (metric === 'CLS') {
|
|
167
|
+
valueText = valueDisplay = value ? value.toFixed(2) : '0';
|
|
168
|
+
classification = Trace.Handlers.ModelHandlers.LayoutShifts.scoreClassificationForLayoutShift(value);
|
|
169
|
+
} else if (metric === 'INP') {
|
|
170
|
+
const micros = value as Trace.Types.Timing.Micro;
|
|
171
|
+
const {text, element} = NumberWithUnit.formatMicroSecondsAsMillisFixed(micros);
|
|
172
|
+
valueText = text;
|
|
173
|
+
valueDisplay = element;
|
|
174
|
+
classification =
|
|
175
|
+
Trace.Handlers.ModelHandlers.UserInteractions.scoreClassificationForInteractionToNextPaint(micros);
|
|
176
|
+
} else {
|
|
177
|
+
Platform.TypeScriptUtilities.assertNever(metric, `Unexpected metric ${metric}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// NOTE: it is deliberate to use the same value for the title and
|
|
181
|
+
// aria-label; the aria-label is used to give more context to
|
|
182
|
+
// screen-readers, and the title is to aid users who may not know what
|
|
183
|
+
// the red/orange/green classification is, or those who are unable to
|
|
184
|
+
// easily distinguish the visual colour differences.
|
|
185
|
+
// clang-format off
|
|
186
|
+
const title = value !== null ?
|
|
187
|
+
i18nString(UIStrings.metricScore, {PH1: metric, PH2: valueText, PH3: classification}) :
|
|
188
|
+
i18nString(UIStrings.metricScoreUnavailable, {PH1: metric});
|
|
189
|
+
|
|
190
|
+
return html`
|
|
191
|
+
<button class="metric"
|
|
192
|
+
@click=${relevantEvent ? onClickMetric.bind(relevantEvent) : null}
|
|
193
|
+
title=${title}
|
|
194
|
+
aria-label=${title}
|
|
195
|
+
>
|
|
196
|
+
<div class="metric-value metric-value-${classification}">${valueDisplay}</div>
|
|
197
|
+
</button>
|
|
198
|
+
`;
|
|
199
|
+
// clang-format on
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const lcpEl = renderMetricValue('LCP', local?.lcp?.value ?? null, local?.lcp?.event ?? null);
|
|
203
|
+
const inpEl = renderMetricValue('INP', local?.inp?.value ?? null, local?.inp?.event ?? null);
|
|
204
|
+
const clsEl = renderMetricValue('CLS', local?.cls?.value ?? null, local?.cls?.worstClusterEvent ?? null);
|
|
205
|
+
|
|
206
|
+
const localMetricsTemplateResult = html`
|
|
207
|
+
<div class="metrics-row">
|
|
208
|
+
<span>${lcpEl}</span>
|
|
209
|
+
<span>${inpEl}</span>
|
|
210
|
+
<span>${clsEl}</span>
|
|
211
|
+
<span class="row-label">Local</span>
|
|
212
|
+
</div>
|
|
213
|
+
<span class="row-border"></span>
|
|
214
|
+
`;
|
|
215
|
+
|
|
216
|
+
let fieldMetricsTemplateResult;
|
|
217
|
+
if (field) {
|
|
218
|
+
const {lcp, inp, cls} = field;
|
|
219
|
+
|
|
220
|
+
const lcpEl = renderMetricValue('LCP', lcp?.value ?? null, null);
|
|
221
|
+
const inpEl = renderMetricValue('INP', inp?.value ?? null, null);
|
|
222
|
+
const clsEl = renderMetricValue('CLS', cls?.value ?? null, null);
|
|
223
|
+
|
|
224
|
+
let scope = i18nString(UIStrings.originOption);
|
|
225
|
+
if (lcp?.pageScope === 'url' || inp?.pageScope === 'url') {
|
|
226
|
+
scope = i18nString(UIStrings.urlOption);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// clang-format off
|
|
230
|
+
fieldMetricsTemplateResult = html`
|
|
231
|
+
<div class="metrics-row">
|
|
232
|
+
<span>${lcpEl}</span>
|
|
233
|
+
<span>${inpEl}</span>
|
|
234
|
+
<span>${clsEl}</span>
|
|
235
|
+
<span class="row-label">${i18nString(UIStrings.fieldScoreLabel, {PH1: scope})}</span>
|
|
236
|
+
</div>
|
|
237
|
+
<span class="row-border"></span>
|
|
238
|
+
`;
|
|
239
|
+
// clang-format on
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
let fieldIsDifferentEl;
|
|
243
|
+
if (showFieldMismatchNotice) {
|
|
244
|
+
// clang-format off
|
|
245
|
+
fieldIsDifferentEl = html`
|
|
246
|
+
<div class="field-mismatch-notice" jslog=${VisualLogging.section('timeline.insights.field-mismatch')}>
|
|
247
|
+
<h3>${i18nString(UIStrings.fieldMismatchTitle)}</h3>
|
|
248
|
+
<devtools-button
|
|
249
|
+
title=${i18nString(UIStrings.dismissTitle)}
|
|
250
|
+
.iconName=${'cross'}
|
|
251
|
+
.variant=${Buttons.Button.Variant.ICON}
|
|
252
|
+
.jslogContext=${'timeline.insights.dismiss-field-mismatch'}
|
|
253
|
+
@click=${onDismisFieldMismatchNotice}
|
|
254
|
+
></devtools-button>
|
|
255
|
+
<div class="field-mismatch-notice__body">${md(i18nString(UIStrings.fieldMismatchNotice))}</div>
|
|
256
|
+
</div>
|
|
257
|
+
`;
|
|
258
|
+
// clang-format on
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const classes = {metrics: true, 'metrics--field': Boolean(fieldMetricsTemplateResult)};
|
|
262
|
+
const metricsTableEl = html`<div class=${Lit.Directives.classMap(classes)}>
|
|
263
|
+
<div class="metrics-row">
|
|
264
|
+
<span class="metric-label">LCP</span>
|
|
265
|
+
<span class="metric-label">INP</span>
|
|
266
|
+
<span class="metric-label">CLS</span>
|
|
267
|
+
<span class="row-label"></span>
|
|
268
|
+
</div>
|
|
269
|
+
${localMetricsTemplateResult}
|
|
270
|
+
${fieldMetricsTemplateResult}
|
|
271
|
+
</div>`;
|
|
272
|
+
|
|
273
|
+
Lit.render(
|
|
274
|
+
html`
|
|
275
|
+
<style>${cwvMetricsStyles}</style>
|
|
276
|
+
${metricsTableEl}
|
|
277
|
+
${fieldIsDifferentEl}
|
|
278
|
+
`,
|
|
279
|
+
target);
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
export interface CWVMetricsData {
|
|
283
|
+
insightSetKey: Trace.Types.Events.NavigationId|null;
|
|
284
|
+
parsedTrace: Trace.TraceModel.ParsedTrace|null;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export class CWVMetrics extends UI.Widget.Widget {
|
|
288
|
+
#view: MetricsView;
|
|
289
|
+
#data: CWVMetricsData = {
|
|
290
|
+
insightSetKey: null,
|
|
291
|
+
parsedTrace: null,
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
#didDismissFieldMismatchNotice = false;
|
|
295
|
+
|
|
296
|
+
constructor(element?: HTMLElement, view: MetricsView = CWV_METRICS_VIEW) {
|
|
297
|
+
super(element, {useShadowDom: true});
|
|
298
|
+
this.#view = view;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
set data(data: CWVMetricsData) {
|
|
302
|
+
this.#data = data;
|
|
303
|
+
this.requestUpdate();
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
#onClickMetric(traceEvent: Trace.Types.Events.Event): void {
|
|
307
|
+
this.element.dispatchEvent(new Insights.EventRef.EventReferenceClick(traceEvent));
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
#onDismisFieldMismatchNotice(): void {
|
|
311
|
+
this.#didDismissFieldMismatchNotice = true;
|
|
312
|
+
this.requestUpdate();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
override performUpdate(): void {
|
|
316
|
+
const {
|
|
317
|
+
parsedTrace,
|
|
318
|
+
insightSetKey,
|
|
319
|
+
} = this.#data;
|
|
320
|
+
|
|
321
|
+
if (!parsedTrace?.insights || !insightSetKey || !(parsedTrace.insights instanceof Map)) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const insightSet = parsedTrace.insights.get(insightSetKey);
|
|
326
|
+
if (!insightSet) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const input: MetricsViewInput = {
|
|
331
|
+
parsedTrace,
|
|
332
|
+
insightSetKey,
|
|
333
|
+
didDismissFieldMismatchNotice: this.#didDismissFieldMismatchNotice,
|
|
334
|
+
onDismisFieldMismatchNotice: this.#onDismisFieldMismatchNotice.bind(this),
|
|
335
|
+
onClickMetric: this.#onClickMetric.bind(this),
|
|
336
|
+
};
|
|
337
|
+
this.#view(input, undefined, this.contentElement);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
@@ -163,6 +163,14 @@ export class SidebarWidget extends UI.Widget.VBox {
|
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
openInsightsTab(): void {
|
|
167
|
+
this.#tabbedPane.selectTab(SidebarTabs.INSIGHTS);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
setActiveInsightSet(insightSetKey: string): void {
|
|
171
|
+
this.#insightsView.setActiveInsightSet(insightSetKey);
|
|
172
|
+
}
|
|
173
|
+
|
|
166
174
|
/**
|
|
167
175
|
* True if the sidebar has been visible at least one time. This is persisted
|
|
168
176
|
* to the user settings so it persists across sessions. This is used because
|
|
@@ -213,6 +221,15 @@ class InsightsView extends UI.Widget.VBox {
|
|
|
213
221
|
});
|
|
214
222
|
}
|
|
215
223
|
}
|
|
224
|
+
|
|
225
|
+
setActiveInsightSet(insightSetKey: string): void {
|
|
226
|
+
const widget = this.#component.getWidget();
|
|
227
|
+
if (!widget) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
widget.setActiveInsightSet(insightSetKey);
|
|
232
|
+
}
|
|
216
233
|
}
|
|
217
234
|
|
|
218
235
|
class AnnotationsView extends UI.Widget.VBox {
|
|
@@ -200,6 +200,16 @@ export class SidebarInsightsTab extends UI.Widget.Widget {
|
|
|
200
200
|
this.requestUpdate();
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
+
setActiveInsightSet(insightSetKey: string): void {
|
|
204
|
+
if (this.#parsedTrace?.insights) {
|
|
205
|
+
const insightSet = this.#parsedTrace.insights.get(insightSetKey);
|
|
206
|
+
if (insightSet) {
|
|
207
|
+
this.#selectedInsightSet = insightSet;
|
|
208
|
+
this.requestUpdate();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
203
213
|
#onInsightSetToggled(insightSet: Trace.Insights.Types.InsightSet): void {
|
|
204
214
|
this.#selectedInsightSet = this.#selectedInsightSet === insightSet ? null : insightSet;
|
|
205
215
|
// Update the active insight set.
|