chrome-devtools-frontend 1.0.1516909 → 1.0.1518653
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/docs/checklist/README.md +2 -2
- package/docs/checklist/javascript.md +1 -1
- package/docs/contributing/README.md +1 -1
- package/docs/contributing/settings-experiments-features.md +9 -8
- package/docs/cookbook/devtools_on_devtools.md +2 -2
- package/docs/cookbook/localization.md +10 -10
- package/docs/devtools-protocol.md +9 -8
- package/docs/ecosystem/automatic_workspace_folders.md +3 -3
- package/docs/get_the_code.md +0 -2
- package/docs/styleguide/ux/components.md +166 -85
- package/docs/styleguide/ux/numbers.md +3 -4
- package/front_end/core/common/README.md +13 -12
- package/front_end/core/host/GdpClient.ts +16 -1
- package/front_end/core/host/UserMetrics.ts +4 -2
- package/front_end/core/root/Runtime.ts +13 -0
- package/front_end/core/sdk/CSSMatchedStyles.ts +5 -1
- package/front_end/entrypoints/main/MainImpl.ts +6 -3
- package/front_end/generated/InspectorBackendCommands.js +10 -7
- package/front_end/generated/SupportedCSSProperties.js +21 -7
- package/front_end/generated/protocol-mapping.d.ts +16 -1
- package/front_end/generated/protocol-proxy-api.d.ts +13 -1
- package/front_end/generated/protocol.ts +95 -0
- package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +166 -49
- package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.snapshot.txt +14 -181
- package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +13 -315
- package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +224 -50
- package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +310 -11
- package/front_end/models/ai_assistance/performance/AIContext.ts +15 -2
- package/front_end/models/ai_code_completion/AiCodeCompletion.ts +17 -11
- package/front_end/models/badges/Badge.ts +8 -3
- package/front_end/models/badges/CodeWhispererBadge.ts +2 -4
- package/front_end/models/badges/StarterBadge.ts +2 -2
- package/front_end/models/badges/UserBadges.ts +21 -3
- package/front_end/models/javascript_metadata/NativeFunctions.js +1 -1
- package/front_end/models/trace/README.md +28 -1
- package/front_end/models/trace/handlers/UserTimingsHandler.ts +1 -1
- package/front_end/models/trace/helpers/Trace.ts +99 -43
- package/front_end/models/trace/types/TraceEvents.ts +9 -0
- package/front_end/panels/accessibility/ARIAAttributesView.ts +113 -191
- package/front_end/panels/accessibility/AccessibilityNodeView.ts +9 -9
- package/front_end/panels/accessibility/AccessibilitySubPane.ts +6 -4
- package/front_end/panels/accessibility/accessibilityProperties.css +2 -0
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +16 -2
- package/front_end/panels/ai_assistance/components/ChatView.ts +9 -10
- package/front_end/panels/ai_assistance/components/PerformanceAgentMarkdownRenderer.ts +42 -0
- package/front_end/panels/common/AiCodeCompletionDisclaimer.ts +32 -9
- package/front_end/panels/common/AiCodeCompletionSummaryToolbar.ts +7 -1
- package/front_end/panels/common/BadgeNotification.ts +21 -5
- package/front_end/panels/common/GdpSignUpDialog.ts +18 -9
- package/front_end/panels/console/ConsolePrompt.ts +1 -1
- package/front_end/panels/console/ConsoleView.ts +6 -2
- package/front_end/panels/elements/ElementsPanel.ts +4 -0
- package/front_end/panels/elements/ElementsTreeElement.ts +18 -0
- package/front_end/panels/elements/ElementsTreeOutline.ts +13 -0
- package/front_end/panels/elements/StylePropertyTreeElement.ts +21 -6
- package/front_end/panels/media/TickingFlameChart.ts +1 -1
- package/front_end/panels/profiler/HeapSnapshotView.ts +34 -19
- package/front_end/panels/search/SearchResultsPane.ts +124 -128
- package/front_end/panels/search/SearchView.ts +24 -17
- package/front_end/panels/settings/components/SyncSection.ts +16 -8
- package/front_end/panels/sources/AiCodeCompletionPlugin.ts +6 -1
- package/front_end/panels/sources/SourcesPanel.ts +3 -0
- package/front_end/panels/timeline/AppenderUtils.ts +2 -2
- package/front_end/panels/timeline/ExtensionTrackAppender.ts +13 -4
- package/front_end/panels/timeline/GPUTrackAppender.ts +2 -1
- package/front_end/panels/timeline/InteractionsTrackAppender.ts +5 -1
- package/front_end/panels/timeline/LayoutShiftsTrackAppender.ts +2 -1
- package/front_end/panels/timeline/ThreadAppender.ts +12 -3
- package/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +9 -4
- package/front_end/panels/timeline/TimelinePanel.ts +3 -2
- package/front_end/panels/timeline/TimelineUIUtils.ts +5 -4
- package/front_end/panels/timeline/TimingsTrackAppender.ts +6 -1
- package/front_end/panels/timeline/components/CPUThrottlingSelector.ts +95 -82
- package/front_end/panels/timeline/components/LiveMetricsView.ts +2 -2
- package/front_end/panels/timeline/components/cpuThrottlingSelector.css +17 -15
- package/front_end/panels/timeline/components/insights/BaseInsightComponent.ts +3 -0
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/third_party/codemirror.next/chunk/codemirror.js +1 -1
- package/front_end/third_party/codemirror.next/chunk/codemirror.js.map +1 -1
- package/front_end/third_party/codemirror.next/codemirror.next.d.ts +6 -9
- package/front_end/third_party/codemirror.next/package.json +2 -1
- package/front_end/third_party/diff/README.chromium +1 -0
- package/front_end/ui/components/text_editor/config.ts +6 -7
- package/front_end/ui/components/tooltips/Tooltip.ts +70 -31
- package/front_end/ui/legacy/README.md +33 -24
- package/front_end/ui/legacy/SearchableView.ts +19 -26
- package/front_end/ui/legacy/TextPrompt.ts +166 -1
- package/front_end/ui/legacy/Treeoutline.ts +16 -2
- package/front_end/ui/legacy/UIUtils.ts +15 -2
- package/front_end/ui/legacy/XElement.ts +0 -43
- package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +20 -4
- package/front_end/ui/visual_logging/KnownContextValues.ts +19 -6
- package/front_end/ui/visual_logging/README.md +43 -27
- package/package.json +1 -1
@@ -17,6 +17,7 @@ import * as Buttons from '../../../ui/components/buttons/buttons.js';
|
|
17
17
|
import * as ComponentHelpers from '../../../ui/components/helpers/helpers.js';
|
18
18
|
import type * as SettingsComponents from '../../../ui/components/settings/settings.js';
|
19
19
|
import * as Lit from '../../../ui/lit/lit.js';
|
20
|
+
import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js';
|
20
21
|
import * as PanelCommon from '../../common/common.js';
|
21
22
|
import * as PanelUtils from '../../utils/utils.js';
|
22
23
|
|
@@ -212,7 +213,7 @@ export class SyncSection extends HTMLElement {
|
|
212
213
|
}
|
213
214
|
|
214
215
|
async #fetchGdpDetails(): Promise<void> {
|
215
|
-
if (!
|
216
|
+
if (!Host.GdpClient.isGdpProfilesAvailable()) {
|
216
217
|
return;
|
217
218
|
}
|
218
219
|
|
@@ -304,21 +305,25 @@ function renderGdpSectionIfNeeded({
|
|
304
305
|
gdpProfile?: Host.GdpClient.Profile,
|
305
306
|
isEligibleToCreateProfile?: boolean,
|
306
307
|
}): Lit.LitTemplate {
|
307
|
-
|
308
|
-
if (!Root.Runtime.hostConfig.devToolsGdpProfiles?.enabled || (!gdpProfile && !isEligibleToCreateProfile)) {
|
308
|
+
if (!Host.GdpClient.isGdpProfilesAvailable() || (!gdpProfile && !isEligibleToCreateProfile)) {
|
309
309
|
return Lit.nothing;
|
310
310
|
}
|
311
|
+
const hasReceiveBadgesCheckbox = receiveBadgesSetting &&
|
312
|
+
Host.GdpClient.getGdpProfilesEnterprisePolicy() === Root.Runtime.GdpProfilesEnterprisePolicyValue.ENABLED;
|
311
313
|
|
312
314
|
function renderBrand(): Lit.LitTemplate {
|
315
|
+
// clang-format off
|
313
316
|
return html`
|
314
317
|
<div class="gdp-profile-header">
|
315
318
|
<div class="gdp-logo" role="img" tabindex="0" aria-label="Google Developer Program"></div>
|
316
319
|
</div>
|
317
320
|
`;
|
321
|
+
// clang-format on
|
318
322
|
}
|
319
323
|
|
324
|
+
// clang-format off
|
320
325
|
return html`
|
321
|
-
<div class="gdp-profile-container">
|
326
|
+
<div class="gdp-profile-container" jslog=${VisualLogging.section().context('gdp-profile')}>
|
322
327
|
<div class="divider"></div>
|
323
328
|
${gdpProfile ? html`
|
324
329
|
<div class="gdp-profile-details-content">
|
@@ -326,10 +331,13 @@ function renderGdpSectionIfNeeded({
|
|
326
331
|
<div class="plan-details">
|
327
332
|
${getGdpSubscriptionText(gdpProfile)}
|
328
333
|
·
|
329
|
-
<x-link
|
334
|
+
<x-link
|
335
|
+
jslog=${VisualLogging.link().track({click: true, keydown:'Enter|Space'}).context('view-profile')}
|
336
|
+
class="link"
|
337
|
+
href=${Host.GdpClient.GOOGLE_DEVELOPER_PROGRAM_PROFILE_LINK}>
|
330
338
|
${i18nString(UIStrings.viewProfile)}
|
331
339
|
</x-link></div>
|
332
|
-
${
|
340
|
+
${hasReceiveBadgesCheckbox ? html`
|
333
341
|
<div class="setting-container" ${ref(receiveBadgesSettingContainerRef)}>
|
334
342
|
<setting-checkbox class="setting-checkbox" .data=${{setting: receiveBadgesSetting}} @change=${(e: Event) => {
|
335
343
|
const settingCheckbox = e.target as SettingsComponents.SettingCheckbox.SettingCheckbox;
|
@@ -351,7 +359,7 @@ function renderGdpSectionIfNeeded({
|
|
351
359
|
@click=${() => PanelCommon.GdpSignUpDialog.show({
|
352
360
|
onSuccess: onSignUpSuccess
|
353
361
|
})}
|
354
|
-
.jslogContext=${'
|
362
|
+
.jslogContext=${'open-sign-up-dialog'}
|
355
363
|
.variant=${Buttons.Button.Variant.OUTLINED}>
|
356
364
|
${i18nString(UIStrings.signUp)}
|
357
365
|
</devtools-button>
|
@@ -359,8 +367,8 @@ function renderGdpSectionIfNeeded({
|
|
359
367
|
`}
|
360
368
|
</div>
|
361
369
|
`;
|
370
|
+
// clang-format on
|
362
371
|
}
|
363
|
-
// clang-format on
|
364
372
|
|
365
373
|
customElements.define('devtools-sync-section', SyncSection);
|
366
374
|
|
@@ -18,6 +18,7 @@ import {Plugin} from './Plugin.js';
|
|
18
18
|
|
19
19
|
const AI_CODE_COMPLETION_CHARACTER_LIMIT = 20_000;
|
20
20
|
const DISCLAIMER_TOOLTIP_ID = 'sources-ai-code-completion-disclaimer-tooltip';
|
21
|
+
const SPINNER_TOOLTIP_ID = 'sources-ai-code-completion-spinner-tooltip';
|
21
22
|
const CITATIONS_TOOLTIP_ID = 'sources-ai-code-completion-citations-tooltip';
|
22
23
|
|
23
24
|
export class AiCodeCompletionPlugin extends Plugin {
|
@@ -212,8 +213,11 @@ export class AiCodeCompletionPlugin extends Plugin {
|
|
212
213
|
this.#teaser = undefined;
|
213
214
|
}
|
214
215
|
if (!this.#aiCodeCompletion) {
|
216
|
+
const contextFlavor = this.uiSourceCode.url().startsWith('snippet://') ?
|
217
|
+
AiCodeCompletion.AiCodeCompletion.ContextFlavor.CONSOLE :
|
218
|
+
AiCodeCompletion.AiCodeCompletion.ContextFlavor.SOURCES;
|
215
219
|
this.#aiCodeCompletion = new AiCodeCompletion.AiCodeCompletion.AiCodeCompletion(
|
216
|
-
{aidaClient: this.#aidaClient}, this.#editor,
|
220
|
+
{aidaClient: this.#aidaClient}, this.#editor, contextFlavor);
|
217
221
|
this.#aiCodeCompletion.addEventListener(
|
218
222
|
AiCodeCompletion.AiCodeCompletion.Events.REQUEST_TRIGGERED, this.#onAiRequestTriggered, this);
|
219
223
|
this.#aiCodeCompletion.addEventListener(
|
@@ -229,6 +233,7 @@ export class AiCodeCompletionPlugin extends Plugin {
|
|
229
233
|
}
|
230
234
|
this.#aiCodeCompletionDisclaimer = new PanelCommon.AiCodeCompletionDisclaimer();
|
231
235
|
this.#aiCodeCompletionDisclaimer.disclaimerTooltipId = DISCLAIMER_TOOLTIP_ID;
|
236
|
+
this.#aiCodeCompletionDisclaimer.spinnerTooltipId = SPINNER_TOOLTIP_ID;
|
232
237
|
this.#aiCodeCompletionDisclaimer.show(this.#aiCodeCompletionDisclaimerContainer, undefined, true);
|
233
238
|
}
|
234
239
|
|
@@ -38,6 +38,7 @@ import * as Platform from '../../core/platform/platform.js';
|
|
38
38
|
import * as Root from '../../core/root/root.js';
|
39
39
|
import * as SDK from '../../core/sdk/sdk.js';
|
40
40
|
import * as Protocol from '../../generated/protocol.js';
|
41
|
+
import * as Badges from '../../models/badges/badges.js';
|
41
42
|
import * as Bindings from '../../models/bindings/bindings.js';
|
42
43
|
import * as Breakpoints from '../../models/breakpoints/breakpoints.js';
|
43
44
|
import * as Extensions from '../../models/extensions/extensions.js';
|
@@ -484,6 +485,8 @@ export class SourcesPanel extends UI.Panel.Panel implements
|
|
484
485
|
} else if (!this.#paused) {
|
485
486
|
UI.Context.Context.instance().setFlavor(SDK.Target.Target, debuggerModel.target());
|
486
487
|
}
|
488
|
+
|
489
|
+
Badges.UserBadges.instance().recordAction(Badges.BadgeAction.DEBUGGER_PAUSED);
|
487
490
|
}
|
488
491
|
|
489
492
|
private debugInfoAttached(event: Common.EventTarget.EventTargetEvent<SDK.Script.Script>): void {
|
@@ -4,7 +4,7 @@
|
|
4
4
|
import type * as Common from '../../core/common/common.js';
|
5
5
|
import * as i18n from '../../core/i18n/i18n.js';
|
6
6
|
import * as Trace from '../../models/trace/trace.js';
|
7
|
-
import
|
7
|
+
import * as PerfUI from '../../ui/legacy/components/perf_ui/perf_ui.js';
|
8
8
|
import * as ThemeSupport from '../../ui/legacy/theme_support/theme_support.js';
|
9
9
|
|
10
10
|
import type {VisualLoggingTrackName} from './CompatibilityTracksAppender.js';
|
@@ -36,7 +36,7 @@ export function buildGroupStyle(extra?: Partial<PerfUI.FlameChart.GroupStyle>):
|
|
36
36
|
const defaultGroupStyle: PerfUI.FlameChart.GroupStyle = {
|
37
37
|
padding: 4,
|
38
38
|
height: 17,
|
39
|
-
collapsible:
|
39
|
+
collapsible: PerfUI.FlameChart.GroupCollapsibleState.ALWAYS,
|
40
40
|
color: ThemeSupport.ThemeSupport.instance().getComputedValue('--sys-color-on-surface'),
|
41
41
|
backgroundColor: ThemeSupport.ThemeSupport.instance().getComputedValue('--sys-color-cdt-base-container'),
|
42
42
|
nestingLevel: 0,
|
@@ -3,6 +3,7 @@
|
|
3
3
|
// found in the LICENSE file.
|
4
4
|
import * as i18n from '../../core/i18n/i18n.js';
|
5
5
|
import * as Trace from '../../models/trace/trace.js';
|
6
|
+
import * as PerfUI from '../../ui/legacy/components/perf_ui/perf_ui.js';
|
6
7
|
import * as ThemeSupport from '../../ui/legacy/theme_support/theme_support.js';
|
7
8
|
|
8
9
|
import {buildGroupStyle, buildTrackHeader, getDurationString} from './AppenderUtils.js';
|
@@ -42,7 +43,8 @@ export class ExtensionTrackAppender implements TrackAppender {
|
|
42
43
|
if (totalEntryCount === 0) {
|
43
44
|
return trackStartLevel;
|
44
45
|
}
|
45
|
-
this.#
|
46
|
+
const compact = !this.#extensionTopLevelTrack.isTrackGroup && totalEntryCount < 2;
|
47
|
+
this.#appendTopLevelHeaderAtLevel(trackStartLevel, compact, expanded);
|
46
48
|
return this.#appendExtensionTrackData(trackStartLevel);
|
47
49
|
}
|
48
50
|
|
@@ -52,8 +54,10 @@ export class ExtensionTrackAppender implements TrackAppender {
|
|
52
54
|
* header corresponds to the track name, in the latter it corresponds
|
53
55
|
* to the track group name.
|
54
56
|
*/
|
55
|
-
#appendTopLevelHeaderAtLevel(currentLevel: number, expanded?: boolean): void {
|
56
|
-
const style =
|
57
|
+
#appendTopLevelHeaderAtLevel(currentLevel: number, compact: boolean, expanded?: boolean): void {
|
58
|
+
const style = compact ?
|
59
|
+
buildGroupStyle({shareHeaderLine: true, collapsible: PerfUI.FlameChart.GroupCollapsibleState.NEVER}) :
|
60
|
+
buildGroupStyle({shareHeaderLine: false, collapsible: PerfUI.FlameChart.GroupCollapsibleState.ALWAYS});
|
57
61
|
const headerTitle = this.#extensionTopLevelTrack.name;
|
58
62
|
const jsLogContext = this.#extensionTopLevelTrack.name === '🅰️ Angular' ? VisualLoggingTrackName.ANGULAR_TRACK :
|
59
63
|
VisualLoggingTrackName.EXTENSION;
|
@@ -69,7 +73,12 @@ export class ExtensionTrackAppender implements TrackAppender {
|
|
69
73
|
* corresponds to the track name itself, instead of the track name.
|
70
74
|
*/
|
71
75
|
#appendSecondLevelHeader(trackStartLevel: number, headerTitle: string): void {
|
72
|
-
const style = buildGroupStyle({
|
76
|
+
const style = buildGroupStyle({
|
77
|
+
shareHeaderLine: false,
|
78
|
+
padding: 2,
|
79
|
+
nestingLevel: 1,
|
80
|
+
collapsible: PerfUI.FlameChart.GroupCollapsibleState.ALWAYS
|
81
|
+
});
|
73
82
|
const group = buildTrackHeader(
|
74
83
|
VisualLoggingTrackName.EXTENSION, trackStartLevel, headerTitle, style,
|
75
84
|
/* selectable= */ true);
|
@@ -3,6 +3,7 @@
|
|
3
3
|
// found in the LICENSE file.
|
4
4
|
import * as i18n from '../../core/i18n/i18n.js';
|
5
5
|
import * as Trace from '../../models/trace/trace.js';
|
6
|
+
import * as PerfUI from '../../ui/legacy/components/perf_ui/perf_ui.js';
|
6
7
|
import * as ThemeSupport from '../../ui/legacy/theme_support/theme_support.js';
|
7
8
|
|
8
9
|
import {buildGroupStyle, buildTrackHeader} from './AppenderUtils.js';
|
@@ -63,7 +64,7 @@ export class GPUTrackAppender implements TrackAppender {
|
|
63
64
|
* @param expanded whether the track should be rendered expanded.
|
64
65
|
*/
|
65
66
|
#appendTrackHeaderAtLevel(currentLevel: number, expanded?: boolean): void {
|
66
|
-
const style = buildGroupStyle({collapsible:
|
67
|
+
const style = buildGroupStyle({collapsible: PerfUI.FlameChart.GroupCollapsibleState.NEVER});
|
67
68
|
const group = buildTrackHeader(
|
68
69
|
VisualLoggingTrackName.GPU, currentLevel, i18nString(UIStrings.gpu), style, /* selectable= */ true, expanded);
|
69
70
|
this.#compatibilityBuilder.registerTrackForGroup(group, this);
|
@@ -69,7 +69,11 @@ export class InteractionsTrackAppender implements TrackAppender {
|
|
69
69
|
*/
|
70
70
|
#appendTrackHeaderAtLevel(currentLevel: number, expanded?: boolean): void {
|
71
71
|
const trackIsCollapsible = this.#parsedTrace.data.UserInteractions.interactionEvents.length > 0;
|
72
|
-
const style = buildGroupStyle({
|
72
|
+
const style = buildGroupStyle({
|
73
|
+
collapsible: trackIsCollapsible ? PerfUI.FlameChart.GroupCollapsibleState.ALWAYS :
|
74
|
+
PerfUI.FlameChart.GroupCollapsibleState.NEVER,
|
75
|
+
useDecoratorsForOverview: true,
|
76
|
+
});
|
73
77
|
const group = buildTrackHeader(
|
74
78
|
VisualLoggingTrackName.INTERACTIONS, currentLevel, i18nString(UIStrings.interactions), style,
|
75
79
|
/* selectable= */ true, expanded);
|
@@ -8,6 +8,7 @@ import * as i18n from '../../core/i18n/i18n.js';
|
|
8
8
|
import * as Geometry from '../../models/geometry/geometry.js';
|
9
9
|
import * as Trace from '../../models/trace/trace.js';
|
10
10
|
import * as ComponentHelpers from '../../ui/components/helpers/helpers.js';
|
11
|
+
import * as PerfUI from '../../ui/legacy/components/perf_ui/perf_ui.js';
|
11
12
|
import * as ThemeSupport from '../../ui/legacy/theme_support/theme_support.js';
|
12
13
|
|
13
14
|
import {buildGroupStyle, buildTrackHeader} from './AppenderUtils.js';
|
@@ -85,7 +86,7 @@ export class LayoutShiftsTrackAppender implements TrackAppender {
|
|
85
86
|
* appended.
|
86
87
|
*/
|
87
88
|
#appendTrackHeaderAtLevel(currentLevel: number, expanded?: boolean): void {
|
88
|
-
const style = buildGroupStyle({collapsible:
|
89
|
+
const style = buildGroupStyle({collapsible: PerfUI.FlameChart.GroupCollapsibleState.NEVER});
|
89
90
|
const group = buildTrackHeader(
|
90
91
|
VisualLoggingTrackName.LAYOUT_SHIFTS, currentLevel, i18nString(UIStrings.layoutShifts), style,
|
91
92
|
/* selectable= */ true, expanded);
|
@@ -262,7 +262,11 @@ export class ThreadAppender implements TrackAppender {
|
|
262
262
|
*/
|
263
263
|
#appendTrackHeaderAtLevel(currentLevel: number): void {
|
264
264
|
const trackIsCollapsible = this.#entries.length > 0;
|
265
|
-
const style = buildGroupStyle({
|
265
|
+
const style = buildGroupStyle({
|
266
|
+
shareHeaderLine: false,
|
267
|
+
collapsible: trackIsCollapsible ? PerfUI.FlameChart.GroupCollapsibleState.ALWAYS :
|
268
|
+
PerfUI.FlameChart.GroupCollapsibleState.NEVER,
|
269
|
+
});
|
266
270
|
if (this.#headerNestingLevel !== null) {
|
267
271
|
style.nestingLevel = this.#headerNestingLevel;
|
268
272
|
}
|
@@ -304,7 +308,11 @@ export class ThreadAppender implements TrackAppender {
|
|
304
308
|
const currentTrackCount = this.#compatibilityBuilder.getCurrentTrackCountForThreadType(threadType);
|
305
309
|
if (currentTrackCount === 0) {
|
306
310
|
const trackIsCollapsible = this.#entries.length > 0;
|
307
|
-
const headerStyle = buildGroupStyle({
|
311
|
+
const headerStyle = buildGroupStyle({
|
312
|
+
shareHeaderLine: false,
|
313
|
+
collapsible: trackIsCollapsible ? PerfUI.FlameChart.GroupCollapsibleState.ALWAYS :
|
314
|
+
PerfUI.FlameChart.GroupCollapsibleState.NEVER,
|
315
|
+
});
|
308
316
|
|
309
317
|
// Don't set any jslogcontext (first argument) because this is a shared
|
310
318
|
// header group. Each child will have its context set.
|
@@ -315,7 +323,8 @@ export class ThreadAppender implements TrackAppender {
|
|
315
323
|
|
316
324
|
// Nesting is set to 1 because the track is appended inside the
|
317
325
|
// header for all raster threads.
|
318
|
-
const titleStyle =
|
326
|
+
const titleStyle =
|
327
|
+
buildGroupStyle({padding: 2, nestingLevel: 1, collapsible: PerfUI.FlameChart.GroupCollapsibleState.NEVER});
|
319
328
|
const rasterizerTitle = this.threadType === Trace.Handlers.Threads.ThreadType.RASTERIZER ?
|
320
329
|
i18nString(UIStrings.rasterizerThreadS, {PH1: currentTrackCount + 1}) :
|
321
330
|
i18nString(UIStrings.threadPoolThreadS, {PH1: currentTrackCount + 1});
|
@@ -152,8 +152,12 @@ export class TimelineFlameChartDataProvider extends Common.ObjectWrapper.ObjectW
|
|
152
152
|
[this.droppedFramePattern, this.partialFramePattern] = this.preparePatternCanvas();
|
153
153
|
|
154
154
|
this.framesGroupStyle = this.buildGroupStyle({useFirstLineForOverview: true});
|
155
|
-
this.screenshotsGroupStyle =
|
156
|
-
|
155
|
+
this.screenshotsGroupStyle = this.buildGroupStyle({
|
156
|
+
useFirstLineForOverview: true,
|
157
|
+
nestingLevel: 1,
|
158
|
+
collapsible: PerfUI.FlameChart.GroupCollapsibleState.NEVER,
|
159
|
+
itemsHeight: 150
|
160
|
+
});
|
157
161
|
|
158
162
|
ThemeSupport.ThemeSupport.instance().addEventListener(ThemeSupport.ThemeChangeEvent.eventName, () => {
|
159
163
|
const headers = [
|
@@ -427,7 +431,7 @@ export class TimelineFlameChartDataProvider extends Common.ObjectWrapper.ObjectW
|
|
427
431
|
const defaultGroupStyle = {
|
428
432
|
padding: 4,
|
429
433
|
height: 17,
|
430
|
-
collapsible:
|
434
|
+
collapsible: PerfUI.FlameChart.GroupCollapsibleState.ALWAYS,
|
431
435
|
color: ThemeSupport.ThemeSupport.instance().getComputedValue('--sys-color-on-surface'),
|
432
436
|
backgroundColor: ThemeSupport.ThemeSupport.instance().getComputedValue('--sys-color-cdt-base-container'),
|
433
437
|
nestingLevel: 0,
|
@@ -774,7 +778,8 @@ export class TimelineFlameChartDataProvider extends Common.ObjectWrapper.ObjectW
|
|
774
778
|
return;
|
775
779
|
}
|
776
780
|
|
777
|
-
this.framesGroupStyle.collapsible =
|
781
|
+
this.framesGroupStyle.collapsible =
|
782
|
+
hasScreenshots ? PerfUI.FlameChart.GroupCollapsibleState.ALWAYS : PerfUI.FlameChart.GroupCollapsibleState.NEVER;
|
778
783
|
const expanded = Root.Runtime.Runtime.queryParam('flamechart-force-expand') === 'frames';
|
779
784
|
|
780
785
|
this.appendHeader(i18nString(UIStrings.frames), this.framesGroupStyle, false /* selectable */, expanded);
|
@@ -2242,7 +2242,7 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
|
|
2242
2242
|
}
|
2243
2243
|
|
2244
2244
|
UI.Context.Context.instance().setFlavor(
|
2245
|
-
AiAssistanceModel.AgentFocus, AiAssistanceModel.AgentFocus.
|
2245
|
+
AiAssistanceModel.AgentFocus, AiAssistanceModel.AgentFocus.fromParsedTrace(parsedTrace));
|
2246
2246
|
}
|
2247
2247
|
|
2248
2248
|
#onAnnotationModifiedEvent(e: Event): void {
|
@@ -3079,7 +3079,8 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
|
|
3079
3079
|
for (const modelName in insightsForNav.model) {
|
3080
3080
|
const model = modelName as keyof Trace.Insights.Types.InsightModelsType;
|
3081
3081
|
const insight = insightsForNav.model[model];
|
3082
|
-
const
|
3082
|
+
const focus = AiAssistanceModel.AgentFocus.fromParsedTrace(parsedTrace);
|
3083
|
+
const formatter = new AiAssistanceModel.PerformanceInsightFormatter(focus, insight);
|
3083
3084
|
if (!formatter.insightIsSupported()) {
|
3084
3085
|
// Not all Insights are integrated with "Ask AI" yet, let's avoid
|
3085
3086
|
// filling up the response with those ones because there will be no
|
@@ -376,9 +376,10 @@ const UIStrings = {
|
|
376
376
|
compositingFailedUnknownReason: 'Unknown Reason',
|
377
377
|
|
378
378
|
/**
|
379
|
-
* @description Text for the execution stack trace
|
379
|
+
* @description Text for the execution "stack trace". It is not technically a stack trace, because it points to the beginning of each function
|
380
|
+
* and not to each call site, so we call it a function stack instead to avoid confusion.
|
380
381
|
*/
|
381
|
-
|
382
|
+
functionStack: 'Function stack',
|
382
383
|
/**
|
383
384
|
* @description Text used to show any invalidations for a particular event that caused the browser to have to do more work to update the page.
|
384
385
|
* @example {2} PH1
|
@@ -1711,10 +1712,10 @@ export class TimelineUIUtils {
|
|
1711
1712
|
parsedTrace: Trace.TraceModel.ParsedTrace): Promise<void> {
|
1712
1713
|
const {startTime} = Trace.Helpers.Timing.eventTimingsMilliSeconds(event);
|
1713
1714
|
let initiatorStackLabel = i18nString(UIStrings.initiatorStackTrace);
|
1714
|
-
let stackLabel = i18nString(UIStrings.
|
1715
|
+
let stackLabel = i18nString(UIStrings.functionStack);
|
1715
1716
|
const stackTraceForEvent = Trace.Extras.StackTraceForEvent.get(event, parsedTrace.data);
|
1716
1717
|
if (stackTraceForEvent?.callFrames.length || stackTraceForEvent?.description || stackTraceForEvent?.parent) {
|
1717
|
-
contentHelper.addSection(i18nString(UIStrings.
|
1718
|
+
contentHelper.addSection(i18nString(UIStrings.functionStack));
|
1718
1719
|
contentHelper.createChildStackTraceElement(stackTraceForEvent);
|
1719
1720
|
// TODO(andoli): also build stack trace component for other events
|
1720
1721
|
// that have a stack trace using the StackTraceForEvent helper.
|
@@ -4,6 +4,7 @@
|
|
4
4
|
import type * as Common from '../../core/common/common.js';
|
5
5
|
import * as i18n from '../../core/i18n/i18n.js';
|
6
6
|
import * as Trace from '../../models/trace/trace.js';
|
7
|
+
import * as PerfUI from '../../ui/legacy/components/perf_ui/perf_ui.js';
|
7
8
|
|
8
9
|
import {buildGroupStyle, buildTrackHeader, getDurationString} from './AppenderUtils.js';
|
9
10
|
import {
|
@@ -103,7 +104,11 @@ export class TimingsTrackAppender implements TrackAppender {
|
|
103
104
|
*/
|
104
105
|
#appendTrackHeaderAtLevel(currentLevel: number, expanded?: boolean): void {
|
105
106
|
const trackIsCollapsible = this.#parsedTrace.data.UserTimings.performanceMeasures.length > 0;
|
106
|
-
const style = buildGroupStyle({
|
107
|
+
const style = buildGroupStyle({
|
108
|
+
useFirstLineForOverview: true,
|
109
|
+
collapsible: trackIsCollapsible ? PerfUI.FlameChart.GroupCollapsibleState.IF_MULTI_ROW :
|
110
|
+
PerfUI.FlameChart.GroupCollapsibleState.NEVER,
|
111
|
+
});
|
107
112
|
const group = buildTrackHeader(
|
108
113
|
VisualLoggingTrackName.TIMINGS, currentLevel, i18nString(UIStrings.timings), style, /* selectable= */ true,
|
109
114
|
expanded);
|
@@ -1,7 +1,6 @@
|
|
1
1
|
// Copyright 2024 The Chromium Authors
|
2
2
|
// Use of this source code is governed by a BSD-style license that can be
|
3
3
|
// found in the LICENSE file.
|
4
|
-
/* eslint-disable rulesdir/no-lit-render-outside-of-view */
|
5
4
|
|
6
5
|
import '../../../ui/components/icon_button/icon_button.js';
|
7
6
|
import '../../../ui/components/menus/menus.js';
|
@@ -9,15 +8,15 @@ import '../../../ui/components/menus/menus.js';
|
|
9
8
|
import * as Common from '../../../core/common/common.js';
|
10
9
|
import * as i18n from '../../../core/i18n/i18n.js';
|
11
10
|
import * as SDK from '../../../core/sdk/sdk.js';
|
12
|
-
import * as ComponentHelpers from '../../../ui/components/helpers/helpers.js';
|
13
11
|
import type * as Menus from '../../../ui/components/menus/menus.js';
|
12
|
+
import * as UI from '../../../ui/legacy/legacy.js';
|
14
13
|
import * as Lit from '../../../ui/lit/lit.js';
|
15
14
|
import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js';
|
16
15
|
import * as MobileThrottling from '../../mobile_throttling/mobile_throttling.js';
|
17
16
|
|
18
17
|
import cpuThrottlingSelectorStyles from './cpuThrottlingSelector.css.js';
|
19
18
|
|
20
|
-
const {html} = Lit;
|
19
|
+
const {render, html} = Lit;
|
21
20
|
|
22
21
|
const UIStrings = {
|
23
22
|
/**
|
@@ -62,37 +61,110 @@ interface CPUThrottlingGroup {
|
|
62
61
|
showCustomAddOption?: boolean;
|
63
62
|
}
|
64
63
|
|
65
|
-
|
66
|
-
|
64
|
+
interface ViewInput {
|
65
|
+
recommendedOption: SDK.CPUThrottlingManager.CPUThrottlingOption|null;
|
66
|
+
currentOption: SDK.CPUThrottlingManager.CPUThrottlingOption;
|
67
|
+
groups: CPUThrottlingGroup[];
|
68
|
+
throttling: SDK.CPUThrottlingManager.CalibratedCPUThrottling;
|
69
|
+
onMenuItemSelected: (event: Menus.SelectMenu.SelectMenuItemSelectedEvent) => void;
|
70
|
+
onCalibrateClick: () => void;
|
71
|
+
}
|
72
|
+
|
73
|
+
export const DEFAULT_VIEW = (input: ViewInput, _output: undefined, target: HTMLElement): void => {
|
74
|
+
let recommendedInfoEl;
|
75
|
+
if (input.recommendedOption && input.currentOption === SDK.CPUThrottlingManager.NoThrottlingOption) {
|
76
|
+
recommendedInfoEl = html`<devtools-icon
|
77
|
+
title=${i18nString(UIStrings.recommendedThrottlingReason)}
|
78
|
+
name=info></devtools-icon>`;
|
79
|
+
}
|
67
80
|
|
81
|
+
const selectionTitle = input.currentOption.title();
|
82
|
+
const hasCalibratedOnce = input.throttling.low || input.throttling.mid;
|
83
|
+
const calibrationLabel = hasCalibratedOnce ? i18nString(UIStrings.recalibrate) : i18nString(UIStrings.calibrate);
|
84
|
+
|
85
|
+
// clang-format off
|
86
|
+
/* eslint-disable rulesdir/no-deprecated-component-usages */
|
87
|
+
const template = html`
|
88
|
+
<style>${cpuThrottlingSelectorStyles}</style>
|
89
|
+
<devtools-select-menu
|
90
|
+
@selectmenuselected=${input.onMenuItemSelected}
|
91
|
+
.showDivider=${true}
|
92
|
+
.showArrow=${true}
|
93
|
+
.sideButton=${false}
|
94
|
+
.showSelectedItem=${true}
|
95
|
+
.jslogContext=${'cpu-throttling'}
|
96
|
+
.buttonTitle=${i18nString(UIStrings.cpu, {PH1: selectionTitle})}
|
97
|
+
.title=${i18nString(UIStrings.cpuThrottling, {PH1: selectionTitle})}
|
98
|
+
>
|
99
|
+
${input.groups.map(group => {
|
100
|
+
return html`
|
101
|
+
<devtools-menu-group .name=${group.name} .title=${group.name}>
|
102
|
+
${group.items.map(option => {
|
103
|
+
const title = option === input.recommendedOption ? i18nString(UIStrings.recommendedThrottling, {PH1: option.title()}) : option.title();
|
104
|
+
const rate = option.rate();
|
105
|
+
return html`
|
106
|
+
<devtools-menu-item
|
107
|
+
.value=${option.calibratedDeviceType ?? rate}
|
108
|
+
.selected=${input.currentOption === option}
|
109
|
+
.disabled=${rate === 0}
|
110
|
+
.title=${title}
|
111
|
+
jslog=${VisualLogging.item(option.jslogContext).track({click: true})}
|
112
|
+
>
|
113
|
+
${title}
|
114
|
+
</devtools-menu-item>
|
115
|
+
`;
|
116
|
+
})}
|
117
|
+
${group.name === 'Calibrated presets' ? html`<devtools-menu-item
|
118
|
+
.value=${-1 /* This won't be displayed unless it has some value. */}
|
119
|
+
.title=${calibrationLabel}
|
120
|
+
jslog=${VisualLogging.action('cpu-throttling-selector-calibrate').track({click: true})}
|
121
|
+
@click=${input.onCalibrateClick}
|
122
|
+
>
|
123
|
+
${calibrationLabel}
|
124
|
+
</devtools-menu-item>` : Lit.nothing}
|
125
|
+
</devtools-menu-group>`;
|
126
|
+
})}
|
127
|
+
</devtools-select-menu>
|
128
|
+
${recommendedInfoEl}
|
129
|
+
`;
|
130
|
+
// clang-format on
|
131
|
+
render(template, target);
|
132
|
+
};
|
133
|
+
|
134
|
+
type View = typeof DEFAULT_VIEW;
|
135
|
+
|
136
|
+
export class CPUThrottlingSelector extends UI.Widget.Widget {
|
68
137
|
#currentOption: SDK.CPUThrottlingManager.CPUThrottlingOption;
|
69
138
|
#recommendedOption: SDK.CPUThrottlingManager.CPUThrottlingOption|null = null;
|
70
139
|
#groups: CPUThrottlingGroup[] = [];
|
71
140
|
#calibratedThrottlingSetting: Common.Settings.Setting<SDK.CPUThrottlingManager.CalibratedCPUThrottling>;
|
141
|
+
readonly #view: View;
|
72
142
|
|
73
|
-
constructor() {
|
74
|
-
super();
|
143
|
+
constructor(element?: HTMLElement, view: View = DEFAULT_VIEW) {
|
144
|
+
super(element);
|
75
145
|
this.#currentOption = SDK.CPUThrottlingManager.CPUThrottlingManager.instance().cpuThrottlingOption();
|
76
146
|
this.#calibratedThrottlingSetting =
|
77
147
|
Common.Settings.Settings.instance().createSetting<SDK.CPUThrottlingManager.CalibratedCPUThrottling>(
|
78
148
|
'calibrated-cpu-throttling', {}, Common.Settings.SettingStorageType.GLOBAL);
|
79
149
|
this.#resetGroups();
|
80
|
-
this.#
|
150
|
+
this.#view = view;
|
81
151
|
}
|
82
152
|
|
83
153
|
set recommendedOption(recommendedOption: SDK.CPUThrottlingManager.CPUThrottlingOption|null) {
|
84
154
|
this.#recommendedOption = recommendedOption;
|
85
|
-
|
155
|
+
this.requestUpdate();
|
86
156
|
}
|
87
157
|
|
88
|
-
|
158
|
+
override wasShown(): void {
|
159
|
+
super.wasShown();
|
89
160
|
SDK.CPUThrottlingManager.CPUThrottlingManager.instance().addEventListener(
|
90
161
|
SDK.CPUThrottlingManager.Events.RATE_CHANGED, this.#onOptionChange, this);
|
91
162
|
this.#calibratedThrottlingSetting.addChangeListener(this.#onCalibratedSettingChanged, this);
|
92
163
|
this.#onOptionChange();
|
93
164
|
}
|
94
165
|
|
95
|
-
|
166
|
+
override willHide(): void {
|
167
|
+
super.willHide();
|
96
168
|
this.#calibratedThrottlingSetting.removeChangeListener(this.#onCalibratedSettingChanged, this);
|
97
169
|
SDK.CPUThrottlingManager.CPUThrottlingManager.instance().removeEventListener(
|
98
170
|
SDK.CPUThrottlingManager.Events.RATE_CHANGED, this.#onOptionChange, this);
|
@@ -101,12 +173,12 @@ export class CPUThrottlingSelector extends HTMLElement {
|
|
101
173
|
#onOptionChange(): void {
|
102
174
|
this.#currentOption = SDK.CPUThrottlingManager.CPUThrottlingManager.instance().cpuThrottlingOption();
|
103
175
|
|
104
|
-
|
176
|
+
this.requestUpdate();
|
105
177
|
}
|
106
178
|
|
107
179
|
#onCalibratedSettingChanged(): void {
|
108
180
|
this.#resetGroups();
|
109
|
-
|
181
|
+
this.requestUpdate();
|
110
182
|
}
|
111
183
|
|
112
184
|
#onMenuItemSelected(event: Menus.SelectMenu.SelectMenuItemSelectedEvent): void {
|
@@ -147,74 +219,15 @@ export class CPUThrottlingSelector extends HTMLElement {
|
|
147
219
|
];
|
148
220
|
}
|
149
221
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
const hasCalibratedOnce = setting.low || setting.mid;
|
161
|
-
const calibrationLabel = hasCalibratedOnce ? i18nString(UIStrings.recalibrate) : i18nString(UIStrings.calibrate);
|
162
|
-
|
163
|
-
// clang-format off
|
164
|
-
/* eslint-disable rulesdir/no-deprecated-component-usages */
|
165
|
-
const output = html`
|
166
|
-
<style>${cpuThrottlingSelectorStyles}</style>
|
167
|
-
<devtools-select-menu
|
168
|
-
@selectmenuselected=${this.#onMenuItemSelected}
|
169
|
-
.showDivider=${true}
|
170
|
-
.showArrow=${true}
|
171
|
-
.sideButton=${false}
|
172
|
-
.showSelectedItem=${true}
|
173
|
-
.jslogContext=${'cpu-throttling'}
|
174
|
-
.buttonTitle=${i18nString(UIStrings.cpu, {PH1: selectionTitle})}
|
175
|
-
.title=${i18nString(UIStrings.cpuThrottling, {PH1: selectionTitle})}
|
176
|
-
>
|
177
|
-
${this.#groups.map(group => {
|
178
|
-
return html`
|
179
|
-
<devtools-menu-group .name=${group.name} .title=${group.name}>
|
180
|
-
${group.items.map(option => {
|
181
|
-
const title = option === this.#recommendedOption ? i18nString(UIStrings.recommendedThrottling, {PH1: option.title()}) : option.title();
|
182
|
-
const rate = option.rate();
|
183
|
-
return html`
|
184
|
-
<devtools-menu-item
|
185
|
-
.value=${option.calibratedDeviceType ?? rate}
|
186
|
-
.selected=${this.#currentOption === option}
|
187
|
-
.disabled=${rate === 0}
|
188
|
-
.title=${title}
|
189
|
-
jslog=${VisualLogging.item(option.jslogContext).track({click: true})}
|
190
|
-
>
|
191
|
-
${title}
|
192
|
-
</devtools-menu-item>
|
193
|
-
`;
|
194
|
-
})}
|
195
|
-
${group.name === 'Calibrated presets' ? html`<devtools-menu-item
|
196
|
-
.value=${-1 /* This won't be displayed unless it has some value. */}
|
197
|
-
.title=${calibrationLabel}
|
198
|
-
jslog=${VisualLogging.action('cpu-throttling-selector-calibrate').track({click: true})}
|
199
|
-
@click=${this.#onCalibrateClick}
|
200
|
-
>
|
201
|
-
${calibrationLabel}
|
202
|
-
</devtools-menu-item>` : Lit.nothing}
|
203
|
-
</devtools-menu-group>`;
|
204
|
-
})}
|
205
|
-
</devtools-select-menu>
|
206
|
-
${recommendedInfoEl}
|
207
|
-
`;
|
208
|
-
/* eslint-enable rulesdir/no-deprecated-component-usages */
|
209
|
-
// clang-format on
|
210
|
-
Lit.render(output, this.#shadow, {host: this});
|
211
|
-
};
|
212
|
-
}
|
213
|
-
|
214
|
-
customElements.define('devtools-cpu-throttling-selector', CPUThrottlingSelector);
|
215
|
-
|
216
|
-
declare global {
|
217
|
-
interface HTMLElementTagNameMap {
|
218
|
-
'devtools-cpu-throttling-selector': CPUThrottlingSelector;
|
222
|
+
override async performUpdate(): Promise<void> {
|
223
|
+
const input: ViewInput = {
|
224
|
+
recommendedOption: this.#recommendedOption,
|
225
|
+
currentOption: this.#currentOption,
|
226
|
+
groups: this.#groups,
|
227
|
+
throttling: this.#calibratedThrottlingSetting.get(),
|
228
|
+
onMenuItemSelected: this.#onMenuItemSelected.bind(this),
|
229
|
+
onCalibrateClick: this.#onCalibrateClick.bind(this),
|
230
|
+
};
|
231
|
+
this.#view(input, undefined, this.contentElement);
|
219
232
|
}
|
220
233
|
}
|
@@ -5,7 +5,6 @@
|
|
5
5
|
/* eslint-disable rulesdir/no-lit-render-outside-of-view */
|
6
6
|
|
7
7
|
import '../../../ui/components/icon_button/icon_button.js';
|
8
|
-
import './CPUThrottlingSelector.js';
|
9
8
|
import './FieldSettingsDialog.js';
|
10
9
|
import './NetworkThrottlingSelector.js';
|
11
10
|
import '../../../ui/components/menus/menus.js';
|
@@ -30,6 +29,7 @@ import * as Lit from '../../../ui/lit/lit.js';
|
|
30
29
|
import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js';
|
31
30
|
import {getThrottlingRecommendations} from '../utils/Helpers.js';
|
32
31
|
|
32
|
+
import {CPUThrottlingSelector} from './CPUThrottlingSelector.js';
|
33
33
|
import {md} from './insights/Helpers.js';
|
34
34
|
import liveMetricsViewStyles from './liveMetricsView.css.js';
|
35
35
|
import type {MetricCardData} from './MetricCard.js';
|
@@ -638,7 +638,7 @@ export class LiveMetricsView extends LegacyWrapper.LegacyWrapper.WrappableCompon
|
|
638
638
|
</ul>
|
639
639
|
` : nothing}
|
640
640
|
<div class="environment-option">
|
641
|
-
<devtools-
|
641
|
+
<devtools-widget .widgetConfig=${UI.Widget.widgetConfig(CPUThrottlingSelector, {recommendedOption: recs.cpuOption})}></devtools-widget>
|
642
642
|
</div>
|
643
643
|
<div class="environment-option">
|
644
644
|
<devtools-network-throttling-selector .recommendedConditions=${recs.networkConditions}></devtools-network-throttling-selector>
|