chrome-devtools-frontend 1.0.1595925 → 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/.stylelintrc.json +3 -1
- package/docs/ui_engineering.md +0 -36
- package/front_end/core/common/Console.ts +6 -6
- package/front_end/core/common/Settings.ts +12 -8
- package/front_end/core/host/UserMetrics.ts +0 -1
- package/front_end/core/root/DevToolsContext.ts +13 -7
- package/front_end/core/root/ExperimentNames.ts +0 -1
- package/front_end/core/sdk/CPUThrottlingManager.ts +12 -9
- package/front_end/core/sdk/DOMModel.ts +206 -0
- package/front_end/core/sdk/FrameManager.ts +7 -8
- package/front_end/core/sdk/SourceMapScopesInfo.ts +1 -1
- package/front_end/entrypoints/greendev_floaty/FloatyEntrypoint.ts +1 -1
- package/front_end/entrypoints/greendev_floaty/floaty.css +2 -2
- package/front_end/entrypoints/main/MainImpl.ts +1 -7
- 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/ConversationHandler.ts +1 -1
- 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/models/ai_code_completion/AiCodeCompletion.ts +1 -1
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +3 -0
- package/front_end/panels/ai_assistance/components/ChatMessage.ts +73 -35
- package/front_end/panels/ai_assistance/components/WalkthroughView.ts +30 -6
- package/front_end/panels/ai_assistance/components/chatMessage.css +4 -2
- package/front_end/panels/application/IndexedDBModel.ts +2 -4
- package/front_end/panels/application/ServiceWorkersView.ts +3 -11
- package/front_end/panels/developer_resources/DeveloperResourcesListView.ts +1 -1
- package/front_end/panels/elements/ComputedStyleWidget.ts +25 -16
- package/front_end/panels/elements/ElementsPanel.ts +0 -1
- 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 +27 -5
- package/front_end/panels/elements/StylesContainer.ts +1 -0
- package/front_end/panels/elements/StylesSidebarPane.ts +48 -32
- package/front_end/panels/elements/nodeStackTraceWidget.css +1 -1
- package/front_end/panels/elements/stylePropertiesTreeOutline.css +1 -1
- package/front_end/panels/emulation/DeviceModeToolbar.ts +171 -89
- package/front_end/panels/greendev/GreenDevPanel.css +2 -2
- package/front_end/panels/issues/AffectedPermissionElementsView.ts +9 -6
- package/front_end/panels/lighthouse/LighthouseController.ts +13 -5
- package/front_end/panels/lighthouse/LighthousePanel.ts +22 -5
- package/front_end/panels/profiler/HeapSnapshotGridNodes.ts +11 -3
- 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/settings/components/SyncSection.ts +4 -13
- package/front_end/panels/timeline/CompatibilityTracksAppender.ts +2 -15
- package/front_end/panels/timeline/RecordingMetadata.ts +1 -1
- package/front_end/panels/timeline/TimelineDetailsView.ts +2 -2
- package/front_end/panels/timeline/TimelinePanel.ts +18 -0
- package/front_end/panels/timeline/TimelineUIUtils.ts +7 -4
- 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/Utils.ts +2 -2
- 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/panels/webauthn/webauthnPane.css +1 -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/components/tree_outline/TreeOutline.ts +1 -1
- package/front_end/ui/legacy/ProgressIndicator.ts +1 -1
- package/front_end/ui/legacy/Toolbar.ts +1 -1
- package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +14 -7
- package/front_end/ui/legacy/legacy.ts +0 -2
- package/front_end/ui/visual_logging/KnownContextValues.ts +5 -0
- package/inspector_overlay/loadCSS.rollup.js +2 -2
- package/inspector_overlay/tool_source_order.css +1 -0
- package/package.json +1 -1
- package/front_end/ui/legacy/Fragment.ts +0 -234
|
@@ -161,11 +161,6 @@ const UIStrings = {
|
|
|
161
161
|
* @description Title of button in the Device Mode Toolbar which rotates the device 90 degrees.
|
|
162
162
|
*/
|
|
163
163
|
rotate: 'Rotate',
|
|
164
|
-
/**
|
|
165
|
-
* @description Fallback/default text used for the name of a custom device when no name has been
|
|
166
|
-
* provided by the user.
|
|
167
|
-
*/
|
|
168
|
-
none: 'None',
|
|
169
164
|
/**
|
|
170
165
|
* @description Tooltip of the rotate/screen orientation button.
|
|
171
166
|
*/
|
|
@@ -185,6 +180,10 @@ const UIStrings = {
|
|
|
185
180
|
* posture e.g. Continuous, Folded.
|
|
186
181
|
*/
|
|
187
182
|
devicePosture: 'Device posture',
|
|
183
|
+
/**
|
|
184
|
+
* @description Title of the network throttling selection in the Device Mode Toolbar.
|
|
185
|
+
*/
|
|
186
|
+
throttling: 'Throttling',
|
|
188
187
|
} as const;
|
|
189
188
|
const str_ = i18n.i18n.registerUIStrings('panels/emulation/DeviceModeToolbar.ts', UIStrings);
|
|
190
189
|
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
|
|
@@ -213,18 +212,17 @@ export class DeviceModeToolbar {
|
|
|
213
212
|
private readonly emulatedDevicesList: EmulationModel.EmulatedDevices.EmulatedDevicesList;
|
|
214
213
|
private readonly persistenceSetting: Common.Settings.Setting<{device: string, orientation: string, mode: string}>;
|
|
215
214
|
private spanButton!: UI.Toolbar.ToolbarButton;
|
|
216
|
-
private postureItem!: UI.Toolbar.
|
|
215
|
+
private postureItem!: UI.Toolbar.ToolbarComboBox;
|
|
217
216
|
private modeButton!: UI.Toolbar.ToolbarButton;
|
|
218
217
|
private widthInput: EmulationComponents.DeviceSizeInputElement.SizeInputElement;
|
|
219
218
|
private heightInput: EmulationComponents.DeviceSizeInputElement.SizeInputElement;
|
|
220
|
-
private deviceScaleItem!: UI.Toolbar.
|
|
221
|
-
private deviceSelectItem!: UI.Toolbar.
|
|
222
|
-
private scaleItem!: UI.Toolbar.
|
|
223
|
-
private uaItem!: UI.Toolbar.
|
|
219
|
+
private deviceScaleItem!: UI.Toolbar.ToolbarComboBox;
|
|
220
|
+
private deviceSelectItem!: UI.Toolbar.ToolbarComboBox;
|
|
221
|
+
private scaleItem!: UI.Toolbar.ToolbarComboBox;
|
|
222
|
+
private uaItem!: UI.Toolbar.ToolbarComboBox;
|
|
224
223
|
private cachedDeviceScale!: number|null;
|
|
225
224
|
private cachedUaType!: string|null;
|
|
226
225
|
private xItem?: UI.Toolbar.ToolbarItem;
|
|
227
|
-
private throttlingConditionsItem?: UI.Toolbar.ToolbarMenuButton;
|
|
228
226
|
private cachedModelType?: EmulationModel.DeviceModeModel.Type;
|
|
229
227
|
private cachedScale?: number;
|
|
230
228
|
private cachedModelDevice?: EmulationModel.EmulatedDevices.EmulatedDevice|null;
|
|
@@ -275,6 +273,12 @@ export class DeviceModeToolbar {
|
|
|
275
273
|
this.model.toolbarControlsEnabledSetting().addChangeListener(updateToolbarsEnabled);
|
|
276
274
|
updateToolbarsEnabled();
|
|
277
275
|
|
|
276
|
+
this.updateDeviceMenuItems();
|
|
277
|
+
this.updateScaleMenuItems();
|
|
278
|
+
this.updateDeviceScaleMenuItems();
|
|
279
|
+
this.updateUserAgentMenuItems();
|
|
280
|
+
this.updateDevicePostureItems();
|
|
281
|
+
|
|
278
282
|
function updateToolbarsEnabled(): void {
|
|
279
283
|
const enabled = model.toolbarControlsEnabledSetting().get();
|
|
280
284
|
mainToolbar.setEnabled(enabled);
|
|
@@ -292,10 +296,9 @@ export class DeviceModeToolbar {
|
|
|
292
296
|
const mainToolbar = this.#element.createChild('devtools-toolbar', 'main-toolbar');
|
|
293
297
|
|
|
294
298
|
mainToolbar.appendToolbarItem(new UI.Toolbar.ToolbarItem(this.createEmptyToolbarElement()));
|
|
295
|
-
this.deviceSelectItem =
|
|
296
|
-
|
|
299
|
+
this.deviceSelectItem = new UI.Toolbar.ToolbarComboBox(
|
|
300
|
+
this.onDeviceChange.bind(this), i18nString(UIStrings.deviceType), 'dark-text', 'device');
|
|
297
301
|
this.deviceSelectItem.turnShrinkable();
|
|
298
|
-
this.deviceSelectItem.setDarkText();
|
|
299
302
|
const dimensionsSpan =
|
|
300
303
|
uiI18n.getFormatLocalizedString(str_, UIStrings.dimensions, {PH1: this.deviceSelectItem.element});
|
|
301
304
|
mainToolbar.append(...dimensionsSpan.childNodes);
|
|
@@ -325,10 +328,8 @@ export class DeviceModeToolbar {
|
|
|
325
328
|
|
|
326
329
|
mainToolbar.appendToolbarItem(new UI.Toolbar.ToolbarItem(this.createEmptyToolbarElement()));
|
|
327
330
|
this.scaleItem =
|
|
328
|
-
new UI.Toolbar.
|
|
329
|
-
setTitleForButton(this.scaleItem, i18nString(UIStrings.zoom));
|
|
331
|
+
new UI.Toolbar.ToolbarComboBox(this.onScaleChange.bind(this), i18nString(UIStrings.zoom), 'dark-text', 'scale');
|
|
330
332
|
this.scaleItem.turnShrinkable();
|
|
331
|
-
this.scaleItem.setDarkText();
|
|
332
333
|
mainToolbar.appendToolbarItem(this.scaleItem);
|
|
333
334
|
|
|
334
335
|
const autoAdjustScaleButton = new UI.Toolbar.ToolbarSettingToggle(
|
|
@@ -337,28 +338,22 @@ export class DeviceModeToolbar {
|
|
|
337
338
|
|
|
338
339
|
mainToolbar.appendToolbarItem(new UI.Toolbar.ToolbarItem(this.createEmptyToolbarElement()));
|
|
339
340
|
|
|
340
|
-
this.deviceScaleItem = new UI.Toolbar.
|
|
341
|
-
this.
|
|
341
|
+
this.deviceScaleItem = new UI.Toolbar.ToolbarComboBox(
|
|
342
|
+
this.onDeviceScaleChange.bind(this), i18nString(UIStrings.devicePixelRatio), 'dark-text', 'device-pixel-ratio');
|
|
342
343
|
this.deviceScaleItem.turnShrinkable();
|
|
343
344
|
this.deviceScaleItem.setVisible(this.showDeviceScaleFactorSetting.get());
|
|
344
|
-
setTitleForButton(this.deviceScaleItem, i18nString(UIStrings.devicePixelRatio));
|
|
345
|
-
this.deviceScaleItem.setDarkText();
|
|
346
345
|
const deviceScaleSpan = uiI18n.getFormatLocalizedString(str_, UIStrings.dpr, {PH1: this.deviceScaleItem.element});
|
|
347
346
|
mainToolbar.append(...deviceScaleSpan.childNodes);
|
|
348
347
|
mainToolbar.appendToolbarItem(this.deviceScaleItem);
|
|
349
348
|
mainToolbar.appendToolbarItem(new UI.Toolbar.ToolbarItem(this.createEmptyToolbarElement()));
|
|
350
|
-
this.uaItem =
|
|
351
|
-
|
|
349
|
+
this.uaItem = new UI.Toolbar.ToolbarComboBox(
|
|
350
|
+
this.onUAChange.bind(this), i18nString(UIStrings.deviceType), 'dark-text', 'device-type');
|
|
352
351
|
this.uaItem.turnShrinkable();
|
|
353
352
|
this.uaItem.setVisible(this.showUserAgentTypeSetting.get());
|
|
354
|
-
setTitleForButton(this.uaItem, i18nString(UIStrings.deviceType));
|
|
355
|
-
this.uaItem.setDarkText();
|
|
356
353
|
mainToolbar.appendToolbarItem(this.uaItem);
|
|
357
354
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
this.throttlingConditionsItem.turnShrinkable();
|
|
361
|
-
mainToolbar.appendToolbarItem(this.throttlingConditionsItem);
|
|
355
|
+
MobileThrottling.NetworkThrottlingSelector.NetworkThrottlingSelect.createForGlobalConditions(
|
|
356
|
+
mainToolbar, i18nString(UIStrings.throttling));
|
|
362
357
|
const saveDataItem = MobileThrottling.ThrottlingManager.throttlingManager().createSaveDataOverrideSelector();
|
|
363
358
|
saveDataItem.turnShrinkable();
|
|
364
359
|
mainToolbar.appendToolbarItem(saveDataItem);
|
|
@@ -375,11 +370,9 @@ export class DeviceModeToolbar {
|
|
|
375
370
|
|
|
376
371
|
// Show posture toolbar menu for foldable devices.
|
|
377
372
|
mainToolbar.appendToolbarItem(new UI.Toolbar.ToolbarItem(this.createEmptyToolbarElement()));
|
|
378
|
-
this.postureItem = new UI.Toolbar.
|
|
379
|
-
this.
|
|
373
|
+
this.postureItem = new UI.Toolbar.ToolbarComboBox(
|
|
374
|
+
this.onPostureChange.bind(this), i18nString(UIStrings.devicePosture), 'dark-text', 'device-posture');
|
|
380
375
|
this.postureItem.turnShrinkable();
|
|
381
|
-
this.postureItem.setDarkText();
|
|
382
|
-
setTitleForButton(this.postureItem, i18nString(UIStrings.devicePosture));
|
|
383
376
|
mainToolbar.appendToolbarItem(this.postureItem);
|
|
384
377
|
|
|
385
378
|
return mainToolbar;
|
|
@@ -401,11 +394,22 @@ export class DeviceModeToolbar {
|
|
|
401
394
|
return ['Continuous', 'Folded'].map(title => ({title, value: title, selected: currentPosture === title}));
|
|
402
395
|
}
|
|
403
396
|
|
|
404
|
-
private
|
|
397
|
+
private updateDevicePostureItems(): void {
|
|
398
|
+
this.postureItem.removeOptions();
|
|
405
399
|
for (const option of this.getDevicePostureOptions()) {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
400
|
+
this.postureItem.createOption(option.title, option.value, option.title.toLowerCase());
|
|
401
|
+
}
|
|
402
|
+
const currentPosture = this.currentDevicePosture();
|
|
403
|
+
if (this.postureItem.element.value !== currentPosture) {
|
|
404
|
+
this.postureItem.element.value = currentPosture;
|
|
405
|
+
}
|
|
406
|
+
this.resizeItem(this.postureItem);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
private onPostureChange(): void {
|
|
410
|
+
const value = this.postureItem.element.value;
|
|
411
|
+
if (value !== this.currentDevicePosture()) {
|
|
412
|
+
this.spanClicked();
|
|
409
413
|
}
|
|
410
414
|
}
|
|
411
415
|
|
|
@@ -451,15 +455,21 @@ export class DeviceModeToolbar {
|
|
|
451
455
|
});
|
|
452
456
|
}
|
|
453
457
|
|
|
454
|
-
private
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
458
|
+
private updateScaleMenuItems(): void {
|
|
459
|
+
this.scaleItem.removeOptions();
|
|
460
|
+
const options = this.getScaleOptions();
|
|
461
|
+
for (const option of options) {
|
|
462
|
+
this.scaleItem.createOption(option.title, String(option.value), option.jslogContext);
|
|
463
|
+
}
|
|
464
|
+
const selected = options.find(o => o.selected);
|
|
465
|
+
if (selected) {
|
|
466
|
+
this.scaleItem.element.value = String(selected.value);
|
|
459
467
|
}
|
|
468
|
+
this.resizeItem(this.scaleItem, true);
|
|
460
469
|
}
|
|
461
470
|
|
|
462
|
-
private
|
|
471
|
+
private onScaleChange(): void {
|
|
472
|
+
const value = Number(this.scaleItem.selectedOption()?.value);
|
|
463
473
|
this.model.scaleSetting().set(value);
|
|
464
474
|
}
|
|
465
475
|
|
|
@@ -493,13 +503,22 @@ export class DeviceModeToolbar {
|
|
|
493
503
|
});
|
|
494
504
|
}
|
|
495
505
|
|
|
496
|
-
private
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
{checked: option.selected, jslogContext: option.jslogContext});
|
|
506
|
+
private updateDeviceScaleMenuItems(): void {
|
|
507
|
+
this.deviceScaleItem.removeOptions();
|
|
508
|
+
const options = this.getDeviceScaleFactorOptions();
|
|
509
|
+
for (const option of options) {
|
|
510
|
+
this.deviceScaleItem.createOption(option.title, String(option.value), option.jslogContext);
|
|
502
511
|
}
|
|
512
|
+
const selected = options.find(o => o.selected);
|
|
513
|
+
if (selected) {
|
|
514
|
+
this.deviceScaleItem.element.value = String(selected.value);
|
|
515
|
+
}
|
|
516
|
+
this.resizeItem(this.deviceScaleItem, true);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
private onDeviceScaleChange(): void {
|
|
520
|
+
const value = Number(this.deviceScaleItem.selectedOption()?.value);
|
|
521
|
+
this.model.deviceScaleFactorSetting().set(value);
|
|
503
522
|
}
|
|
504
523
|
|
|
505
524
|
private getUserAgentOptions():
|
|
@@ -519,13 +538,23 @@ export class DeviceModeToolbar {
|
|
|
519
538
|
}));
|
|
520
539
|
}
|
|
521
540
|
|
|
522
|
-
private
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
{checked: option.selected, jslogContext: option.jslogContext});
|
|
541
|
+
private updateUserAgentMenuItems(): void {
|
|
542
|
+
this.uaItem.removeOptions();
|
|
543
|
+
const options = this.getUserAgentOptions();
|
|
544
|
+
for (const option of options) {
|
|
545
|
+
this.uaItem.createOption(option.title, option.value, option.jslogContext);
|
|
528
546
|
}
|
|
547
|
+
const selected = options.find(o => o.selected);
|
|
548
|
+
if (selected) {
|
|
549
|
+
this.uaItem.element.value = selected.value;
|
|
550
|
+
}
|
|
551
|
+
this.resizeItem(this.uaItem);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
private onUAChange(): void {
|
|
555
|
+
const value = this.uaItem.selectedOption()?.value as EmulationModel.DeviceModeModel.UA;
|
|
556
|
+
this.model.uaSetting().set(value);
|
|
557
|
+
this.resizeItem(this.uaItem);
|
|
529
558
|
}
|
|
530
559
|
|
|
531
560
|
private appendOptionsMenuItems(contextMenu: UI.ContextMenu.ContextMenu): void {
|
|
@@ -650,40 +679,66 @@ export class DeviceModeToolbar {
|
|
|
650
679
|
};
|
|
651
680
|
}
|
|
652
681
|
|
|
653
|
-
private
|
|
682
|
+
private updateDeviceMenuItems(): void {
|
|
683
|
+
this.deviceSelectItem.removeOptions();
|
|
654
684
|
const options = this.getDeviceModeOptions();
|
|
655
685
|
|
|
656
|
-
|
|
657
|
-
options.responsive.title, this.switchToResponsive.bind(this),
|
|
658
|
-
{checked: options.responsive.selected, jslogContext: options.responsive.jslogContext});
|
|
686
|
+
this.deviceSelectItem.createOption(options.responsive.title, 'Responsive', options.responsive.jslogContext);
|
|
659
687
|
|
|
660
|
-
appendGroup.call(this, options.standard);
|
|
661
|
-
appendGroup.call(this, options.custom);
|
|
688
|
+
appendGroup.call(this, options.standard, 'Standard');
|
|
689
|
+
appendGroup.call(this, options.custom, 'Custom');
|
|
662
690
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
function appendGroup(
|
|
668
|
-
|
|
669
|
-
title: string,
|
|
670
|
-
selected: boolean,
|
|
671
|
-
jslogContext: string,
|
|
672
|
-
}>): void {
|
|
691
|
+
this.deviceSelectItem.createOption(options.edit.title, 'Edit', options.edit.jslogContext);
|
|
692
|
+
|
|
693
|
+
this.updateDeviceSelection();
|
|
694
|
+
|
|
695
|
+
function appendGroup(
|
|
696
|
+
this: DeviceModeToolbar, group: Array<{title: string, jslogContext: string}>, label: string): void {
|
|
673
697
|
if (!group.length) {
|
|
674
698
|
return;
|
|
675
699
|
}
|
|
676
|
-
const
|
|
700
|
+
const optGroup = document.createElement('optgroup');
|
|
701
|
+
optGroup.label = label;
|
|
677
702
|
for (const item of group) {
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
});
|
|
703
|
+
const option = document.createElement('option');
|
|
704
|
+
option.value = item.title;
|
|
705
|
+
option.text = item.title;
|
|
706
|
+
option.setAttribute('jslog', `${VisualLogging.item(item.jslogContext).track({click: true})}`);
|
|
707
|
+
optGroup.appendChild(option);
|
|
708
|
+
}
|
|
709
|
+
this.deviceSelectItem.addOption(optGroup);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
private updateDeviceSelection(): void {
|
|
714
|
+
const device = this.model.device();
|
|
715
|
+
if (this.model.type() === EmulationModel.DeviceModeModel.Type.Responsive) {
|
|
716
|
+
this.deviceSelectItem.element.value = 'Responsive';
|
|
717
|
+
} else if (device) {
|
|
718
|
+
this.deviceSelectItem.element.value = device.title;
|
|
719
|
+
}
|
|
720
|
+
this.resizeItem(this.deviceSelectItem);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
private onDeviceChange(): void {
|
|
724
|
+
const value = this.deviceSelectItem.selectedOption()?.value;
|
|
725
|
+
if (value === 'Edit') {
|
|
726
|
+
this.emulatedDevicesList.revealCustomSetting();
|
|
727
|
+
this.updateDeviceSelection();
|
|
728
|
+
} else if (value === 'Responsive') {
|
|
729
|
+
this.switchToResponsive();
|
|
730
|
+
} else {
|
|
731
|
+
for (const device of this.allDevices()) {
|
|
732
|
+
if (device.title === value) {
|
|
733
|
+
this.emulateDevice(device);
|
|
734
|
+
break;
|
|
735
|
+
}
|
|
682
736
|
}
|
|
683
737
|
}
|
|
684
738
|
}
|
|
685
739
|
|
|
686
740
|
private deviceListChanged(): void {
|
|
741
|
+
this.updateDeviceMenuItems();
|
|
687
742
|
const device = this.model.device();
|
|
688
743
|
if (!device) {
|
|
689
744
|
return;
|
|
@@ -709,7 +764,11 @@ export class DeviceModeToolbar {
|
|
|
709
764
|
|
|
710
765
|
private updateUserAgentTypeVisibility(): void {
|
|
711
766
|
if (this.uaItem) {
|
|
712
|
-
this.
|
|
767
|
+
const visible = this.showUserAgentTypeSetting.get();
|
|
768
|
+
this.uaItem.setVisible(visible);
|
|
769
|
+
if (visible) {
|
|
770
|
+
this.resizeItem(this.uaItem);
|
|
771
|
+
}
|
|
713
772
|
}
|
|
714
773
|
}
|
|
715
774
|
|
|
@@ -854,31 +913,27 @@ export class DeviceModeToolbar {
|
|
|
854
913
|
this.heightInput.placeholder = String(size.height);
|
|
855
914
|
|
|
856
915
|
if (this.model.scale() !== this.cachedScale) {
|
|
857
|
-
this.
|
|
916
|
+
this.updateScaleMenuItems();
|
|
858
917
|
this.cachedScale = this.model.scale();
|
|
859
918
|
}
|
|
860
919
|
|
|
861
920
|
const deviceScale = this.model.appliedDeviceScaleFactor();
|
|
862
921
|
if (deviceScale !== this.cachedDeviceScale) {
|
|
863
|
-
this.deviceScaleItem.
|
|
922
|
+
this.deviceScaleItem.element.value = String(deviceScale);
|
|
864
923
|
this.cachedDeviceScale = deviceScale;
|
|
924
|
+
this.resizeItem(this.deviceScaleItem, true);
|
|
865
925
|
}
|
|
866
926
|
|
|
867
927
|
const uaType = this.model.appliedUserAgentType();
|
|
868
928
|
if (uaType !== this.cachedUaType) {
|
|
869
|
-
this.uaItem.
|
|
929
|
+
this.uaItem.element.value = uaType;
|
|
870
930
|
this.cachedUaType = uaType;
|
|
931
|
+
this.updateDeviceScaleMenuItems();
|
|
932
|
+
this.resizeItem(this.uaItem);
|
|
871
933
|
}
|
|
872
934
|
|
|
873
|
-
let deviceItemTitle: string = i18nString(UIStrings.none);
|
|
874
|
-
if (this.model.type() === EmulationModel.DeviceModeModel.Type.Responsive) {
|
|
875
|
-
deviceItemTitle = i18nString(UIStrings.responsive);
|
|
876
|
-
}
|
|
877
935
|
const device = this.model.device();
|
|
878
|
-
|
|
879
|
-
deviceItemTitle = device.title;
|
|
880
|
-
}
|
|
881
|
-
this.deviceSelectItem.setText(deviceItemTitle);
|
|
936
|
+
this.updateDeviceSelection();
|
|
882
937
|
|
|
883
938
|
if (this.model.device() !== this.cachedModelDevice) {
|
|
884
939
|
const device = this.model.device();
|
|
@@ -898,7 +953,7 @@ export class DeviceModeToolbar {
|
|
|
898
953
|
} else if (device?.isFoldableScreen) {
|
|
899
954
|
this.spanButton.setVisible(false);
|
|
900
955
|
this.postureItem.setVisible(true);
|
|
901
|
-
this.
|
|
956
|
+
this.updateDevicePostureItems();
|
|
902
957
|
} else {
|
|
903
958
|
this.spanButton.setVisible(false);
|
|
904
959
|
this.postureItem.setVisible(false);
|
|
@@ -962,4 +1017,31 @@ export class DeviceModeToolbar {
|
|
|
962
1017
|
|
|
963
1018
|
this.model.emulate(EmulationModel.DeviceModeModel.Type.Responsive, null, null);
|
|
964
1019
|
}
|
|
1020
|
+
|
|
1021
|
+
private resizeItem(item: UI.Toolbar.ToolbarComboBox, stripParens = false): void {
|
|
1022
|
+
const selectedOption = item.selectedOption();
|
|
1023
|
+
if (!selectedOption) {
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
const dummySelect = item.element.cloneNode(false) as HTMLSelectElement;
|
|
1028
|
+
const dummyOption = selectedOption.cloneNode(true) as HTMLOptionElement;
|
|
1029
|
+
|
|
1030
|
+
if (stripParens) {
|
|
1031
|
+
const parensIndex = dummyOption.text.indexOf(' (');
|
|
1032
|
+
if (parensIndex >= 0) {
|
|
1033
|
+
dummyOption.text = dummyOption.text.substring(0, parensIndex);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
dummySelect.appendChild(dummyOption);
|
|
1038
|
+
dummySelect.style.width = 'fit-content';
|
|
1039
|
+
dummySelect.style.position = 'absolute';
|
|
1040
|
+
dummySelect.style.visibility = 'hidden';
|
|
1041
|
+
dummySelect.style.pointerEvents = 'none';
|
|
1042
|
+
|
|
1043
|
+
item.element.parentElement?.appendChild(dummySelect);
|
|
1044
|
+
item.element.style.width = dummySelect.offsetWidth + 'px';
|
|
1045
|
+
dummySelect.remove();
|
|
1046
|
+
}
|
|
965
1047
|
}
|
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
margin-top: 8px;
|
|
118
118
|
padding-top: 8px;
|
|
119
119
|
border-top: 1px solid #eee;
|
|
120
|
-
font-family: monospace;
|
|
120
|
+
font-family: var(--monospace-font-family);
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
.thought, .action {
|
|
@@ -169,7 +169,7 @@
|
|
|
169
169
|
|
|
170
170
|
.green-dev-floaty-dialog-node-description {
|
|
171
171
|
font-size: 11px;
|
|
172
|
-
font-family: monospace;
|
|
172
|
+
font-family: var(--monospace-font-family);
|
|
173
173
|
color: #0b57d0;
|
|
174
174
|
background-color: #d3e3fd;
|
|
175
175
|
padding: 2px 8px;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import * as i18n from '../../core/i18n/i18n.js';
|
|
6
6
|
import type * as Platform from '../../core/platform/platform.js';
|
|
7
7
|
import type * as IssuesManager from '../../models/issues_manager/issues_manager.js';
|
|
8
|
-
import
|
|
8
|
+
import {html, render} from '../../ui/lit/lit.js';
|
|
9
9
|
|
|
10
10
|
import {AffectedElementsView} from './AffectedElementsView.js';
|
|
11
11
|
|
|
@@ -31,16 +31,19 @@ export class AffectedPermissionElementsView extends AffectedElementsView {
|
|
|
31
31
|
async #appendAffectedElements(issues: Iterable<IssuesManager.PermissionElementIssue.PermissionElementIssue>):
|
|
32
32
|
Promise<void> {
|
|
33
33
|
let count = 0;
|
|
34
|
+
// clang-format off
|
|
35
|
+
const templates = [];
|
|
34
36
|
for (const issue of issues) {
|
|
35
37
|
for (const element of issue.elements()) {
|
|
36
|
-
const rowElement = UI.Fragment.html`
|
|
37
|
-
<tr>
|
|
38
|
-
${await this.createElementCell(element, this.issue.getCategory())}
|
|
39
|
-
</tr>`;
|
|
40
|
-
this.affectedResources.appendChild(rowElement);
|
|
41
38
|
count++;
|
|
39
|
+
templates.push(html`<tr>
|
|
40
|
+
${await this.createElementCell(element, this.issue.getCategory())}
|
|
41
|
+
</tr>`);
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
+
// clang-format on
|
|
45
|
+
// eslint-disable-next-line @devtools/no-lit-render-outside-of-view
|
|
46
|
+
render(html`${templates}`, this.affectedResources);
|
|
44
47
|
this.updateAffectedResourceCount(count);
|
|
45
48
|
}
|
|
46
49
|
}
|
|
@@ -12,6 +12,7 @@ import * as EmulationModel from '../../models/emulation/emulation.js';
|
|
|
12
12
|
import type * as LighthouseModel from '../../models/lighthouse/lighthouse.js';
|
|
13
13
|
import * as Emulation from '../emulation/emulation.js';
|
|
14
14
|
|
|
15
|
+
import type {RunOverrides} from './LighthousePanel.js';
|
|
15
16
|
import type {LighthouseRun as LighthouseRunType, ProtocolService} from './LighthouseProtocolService.js';
|
|
16
17
|
|
|
17
18
|
const UIStrings = {
|
|
@@ -567,9 +568,9 @@ export class LighthouseController extends Common.ObjectWrapper.ObjectWrapper<Eve
|
|
|
567
568
|
};
|
|
568
569
|
}
|
|
569
570
|
|
|
570
|
-
getCategoryIDs():
|
|
571
|
+
getCategoryIDs(): CategoryId[] {
|
|
571
572
|
const {mode} = this.getFlags();
|
|
572
|
-
const categoryIDs = [];
|
|
573
|
+
const categoryIDs: CategoryId[] = [];
|
|
573
574
|
for (const preset of Presets) {
|
|
574
575
|
if (mode && !preset.supportedModes.includes(mode)) {
|
|
575
576
|
continue;
|
|
@@ -639,7 +640,12 @@ export class LighthouseController extends Common.ObjectWrapper.ObjectWrapper<Eve
|
|
|
639
640
|
}
|
|
640
641
|
}
|
|
641
642
|
|
|
642
|
-
|
|
643
|
+
/**
|
|
644
|
+
* Starts a LH run. By default it will use the categories based on what the
|
|
645
|
+
* user has selected in the UI, but these can be overridden by passing in the
|
|
646
|
+
* category IDs, in which case these take priority.
|
|
647
|
+
*/
|
|
648
|
+
async startLighthouse(overrides?: RunOverrides): Promise<void> {
|
|
643
649
|
if (this.lastAction) {
|
|
644
650
|
await this.lastAction;
|
|
645
651
|
}
|
|
@@ -651,7 +657,7 @@ export class LighthouseController extends Common.ObjectWrapper.ObjectWrapper<Eve
|
|
|
651
657
|
}
|
|
652
658
|
|
|
653
659
|
const inspectedURL = await this.getInspectedURL({force: true});
|
|
654
|
-
const categoryIDs = this.getCategoryIDs();
|
|
660
|
+
const categoryIDs = overrides?.categoryIds ?? this.getCategoryIDs();
|
|
655
661
|
const flags = this.getFlags();
|
|
656
662
|
|
|
657
663
|
this.recordMetrics(flags, categoryIDs);
|
|
@@ -861,9 +867,11 @@ export interface EventTypes {
|
|
|
861
867
|
[Events.AuditProgressChanged]: AuditProgressChangedEvent;
|
|
862
868
|
}
|
|
863
869
|
|
|
870
|
+
export type CategoryId = 'performance'|'accessibility'|'best-practices'|'seo';
|
|
871
|
+
|
|
864
872
|
export interface Preset {
|
|
865
873
|
setting: Common.Settings.Setting<boolean>;
|
|
866
|
-
configID:
|
|
874
|
+
configID: CategoryId;
|
|
867
875
|
title: () => Common.UIString.LocalizedString;
|
|
868
876
|
description: () => Common.UIString.LocalizedString;
|
|
869
877
|
supportedModes: string[];
|
|
@@ -13,10 +13,11 @@ import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
|
|
|
13
13
|
|
|
14
14
|
import {
|
|
15
15
|
type AuditProgressChangedEvent,
|
|
16
|
+
type CategoryId,
|
|
16
17
|
Events,
|
|
17
18
|
LighthouseController,
|
|
18
19
|
type PageAuditabilityChangedEvent,
|
|
19
|
-
type PageWarningsChangedEvent
|
|
20
|
+
type PageWarningsChangedEvent
|
|
20
21
|
} from './LighthouseController.js';
|
|
21
22
|
import lighthousePanelStyles from './lighthousePanel.css.js';
|
|
22
23
|
import {ProtocolService} from './LighthouseProtocolService.js';
|
|
@@ -70,7 +71,6 @@ export class LighthousePanel extends UI.Panel.Panel {
|
|
|
70
71
|
private warningText: Nullable<string>;
|
|
71
72
|
private unauditableExplanation: Nullable<string>;
|
|
72
73
|
private readonly cachedRenderedReports: Map<LighthouseModel.ReporterTypes.ReportJSON, HTMLElement>;
|
|
73
|
-
private readonly dropTarget: UI.DropTarget.DropTarget;
|
|
74
74
|
private readonly auditResultsElement: HTMLElement;
|
|
75
75
|
private clearButton!: UI.Toolbar.ToolbarButton;
|
|
76
76
|
private newButton!: UI.Toolbar.ToolbarButton;
|
|
@@ -94,7 +94,7 @@ export class LighthousePanel extends UI.Panel.Panel {
|
|
|
94
94
|
this.unauditableExplanation = null;
|
|
95
95
|
this.cachedRenderedReports = new Map();
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
new UI.DropTarget.DropTarget(
|
|
98
98
|
this.contentElement, [UI.DropTarget.Type.File], i18nString(UIStrings.dropLighthouseJsonHere),
|
|
99
99
|
this.handleDrop.bind(this));
|
|
100
100
|
|
|
@@ -147,14 +147,17 @@ export class LighthousePanel extends UI.Panel.Panel {
|
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
async handleCompleteRun():
|
|
150
|
+
async handleCompleteRun(overrides?: RunOverrides):
|
|
151
|
+
Promise<{report: LighthouseModel.ReporterTypes.ReportJSON | null}> {
|
|
151
152
|
try {
|
|
152
|
-
await this.controller.startLighthouse();
|
|
153
|
+
await this.controller.startLighthouse(overrides);
|
|
153
154
|
this.renderStatusView();
|
|
154
155
|
const {lhr, artifacts} = await this.controller.collectLighthouseResults();
|
|
155
156
|
this.buildReportUI(lhr, artifacts);
|
|
157
|
+
return {report: lhr};
|
|
156
158
|
} catch (err) {
|
|
157
159
|
this.handleError(err);
|
|
160
|
+
return {report: null};
|
|
158
161
|
}
|
|
159
162
|
}
|
|
160
163
|
|
|
@@ -379,4 +382,18 @@ export class LighthousePanel extends UI.Panel.Panel {
|
|
|
379
382
|
event.handled = true;
|
|
380
383
|
}
|
|
381
384
|
}
|
|
385
|
+
|
|
386
|
+
static async executeLighthouseRecording(
|
|
387
|
+
overrides?: RunOverrides,
|
|
388
|
+
): Promise<LighthouseModel.ReporterTypes.ReportJSON|null> {
|
|
389
|
+
const panel = LighthousePanel.instance();
|
|
390
|
+
await UI.ViewManager.ViewManager.instance().showView('lighthouse');
|
|
391
|
+
|
|
392
|
+
const {report} = await panel.handleCompleteRun(overrides);
|
|
393
|
+
return report;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export interface RunOverrides {
|
|
398
|
+
categoryIds: CategoryId[];
|
|
382
399
|
}
|
|
@@ -906,8 +906,15 @@ export class HeapSnapshotObjectNode extends HeapSnapshotGenericObjectNode {
|
|
|
906
906
|
if (this.cycledWithAncestorGridNode) {
|
|
907
907
|
div.classList.add('cycled-ancestor-node');
|
|
908
908
|
}
|
|
909
|
-
|
|
910
|
-
|
|
909
|
+
const property = document.createElement('span');
|
|
910
|
+
property.classList.add('property-name', nameClass);
|
|
911
|
+
property.textContent = name;
|
|
912
|
+
|
|
913
|
+
const separator = document.createElement('span');
|
|
914
|
+
separator.classList.add('grayed');
|
|
915
|
+
separator.textContent = this.edgeNodeSeparator();
|
|
916
|
+
|
|
917
|
+
div.prepend(property, separator);
|
|
911
918
|
}
|
|
912
919
|
|
|
913
920
|
edgeNodeSeparator(): string {
|
|
@@ -1170,7 +1177,8 @@ export class HeapSnapshotConstructorNode extends HeapSnapshotGridNode {
|
|
|
1170
1177
|
override createCell(columnId: string): HTMLElement {
|
|
1171
1178
|
const cell = columnId === 'object' ? super.createCell(columnId) : this.createValueCell(columnId);
|
|
1172
1179
|
if (columnId === 'object' && this.count > 1) {
|
|
1173
|
-
|
|
1180
|
+
const template = html`<span class="objects-count">×${this.data.count}</span>`;
|
|
1181
|
+
render(template, cell);
|
|
1174
1182
|
}
|
|
1175
1183
|
return cell;
|
|
1176
1184
|
}
|
|
@@ -362,7 +362,6 @@ function renderStepActions(input: ViewInput): Lit.TemplateResult|null {
|
|
|
362
362
|
}}
|
|
363
363
|
jslog=${VisualLogging.dropDown('step-actions').track({click: true})}
|
|
364
364
|
.iconName=${'dots-vertical'}
|
|
365
|
-
}
|
|
366
365
|
></devtools-menu-button>
|
|
367
366
|
`;
|
|
368
367
|
// clang-format on
|
|
@@ -459,7 +458,6 @@ export const DEFAULT_VIEW = (input: ViewInput, _output: ViewOutput, target: HTML
|
|
|
459
458
|
<div class="subtitle" title=${subtitle}>${subtitle}</div>
|
|
460
459
|
</div>
|
|
461
460
|
</div>
|
|
462
|
-
<div class="filler"></div>
|
|
463
461
|
${renderStepActions(input)}
|
|
464
462
|
</div>
|
|
465
463
|
<div class="details">
|