DevTools: Revamp Audits2 UI
- Restructures Audits2 file organization
- Revamps design to match latest mocks
Change-Id: I302ff348ae939b4e48e7b79883fe77ffb97825cf
Reviewed-on: https://chromium-review.googlesource.com/1058708
Commit-Queue: Patrick Hulce <phulce@chromium.org>
Reviewed-by: Paul Irish <paulirish@chromium.org>
Reviewed-by: Dmitry Gozman <dgozman@chromium.org>
Cr-Original-Commit-Position: refs/heads/master@{#559754}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 3f2dc028ad0011e5d68b17bf41870a9296181f28
diff --git a/front_end/audits2/Audits2Controller.js b/front_end/audits2/Audits2Controller.js
new file mode 100644
index 0000000..6a58490
--- /dev/null
+++ b/front_end/audits2/Audits2Controller.js
@@ -0,0 +1,285 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @implements {SDK.SDKModelObserver<!SDK.ServiceWorkerManager>}
+ * @unrestricted
+ */
+Audits2.AuditController = class extends Common.Object {
+ constructor(protocolService) {
+ super();
+
+ protocolService.registerStatusCallback(
+ message => this.dispatchEventToListeners(Audits2.Events.AuditProgressChanged, {message}));
+
+ for (const preset of Audits2.Presets)
+ preset.setting.addChangeListener(this.recomputePageAuditability.bind(this));
+
+ SDK.targetManager.observeModels(SDK.ServiceWorkerManager, this);
+ SDK.targetManager.addEventListener(
+ SDK.TargetManager.Events.InspectedURLChanged, this.recomputePageAuditability, this);
+ }
+
+ /**
+ * @override
+ * @param {!SDK.ServiceWorkerManager} serviceWorkerManager
+ */
+ modelAdded(serviceWorkerManager) {
+ if (this._manager)
+ return;
+
+ this._manager = serviceWorkerManager;
+ this._serviceWorkerListeners = [
+ this._manager.addEventListener(
+ SDK.ServiceWorkerManager.Events.RegistrationUpdated, this.recomputePageAuditability, this),
+ this._manager.addEventListener(
+ SDK.ServiceWorkerManager.Events.RegistrationDeleted, this.recomputePageAuditability, this),
+ ];
+
+ this.recomputePageAuditability();
+ }
+
+ /**
+ * @override
+ * @param {!SDK.ServiceWorkerManager} serviceWorkerManager
+ */
+ modelRemoved(serviceWorkerManager) {
+ if (this._manager !== serviceWorkerManager)
+ return;
+
+ Common.EventTarget.removeEventListeners(this._serviceWorkerListeners);
+ this._manager = null;
+ this.recomputePageAuditability();
+ }
+
+ /**
+ * @return {boolean}
+ */
+ _hasActiveServiceWorker() {
+ if (!this._manager)
+ return false;
+
+ const mainTarget = this._manager.target();
+ if (!mainTarget)
+ return false;
+
+ const inspectedURL = mainTarget.inspectedURL().asParsedURL();
+ const inspectedOrigin = inspectedURL && inspectedURL.securityOrigin();
+ for (const registration of this._manager.registrations().values()) {
+ if (registration.securityOrigin !== inspectedOrigin)
+ continue;
+
+ for (const version of registration.versions.values()) {
+ if (version.controlledClients.length > 1)
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @return {boolean}
+ */
+ _hasAtLeastOneCategory() {
+ return Audits2.Presets.some(preset => preset.setting.get());
+ }
+
+ /**
+ * @return {?string}
+ */
+ _unauditablePageMessage() {
+ if (!this._manager)
+ return null;
+
+ const mainTarget = this._manager.target();
+ const inspectedURL = mainTarget && mainTarget.inspectedURL();
+ if (inspectedURL && !/^(http|chrome-extension)/.test(inspectedURL)) {
+ return Common.UIString(
+ 'Can only audit HTTP/HTTPS pages and Chrome extensions. ' +
+ 'Navigate to a different page to start an audit.');
+ }
+
+ // Audits don't work on most undockable targets (extension popup pages, remote debugging, etc).
+ // However, the tests run in a content shell which is not dockable yet audits just fine,
+ // so disable this check when under test.
+ if (!Host.isUnderTest() && !Runtime.queryParam('can_dock'))
+ return Common.UIString('Can only audit tabs. Navigate to this page in a separate tab to start an audit.');
+
+ return null;
+ }
+
+ /**
+ * @return {!Promise<string>}
+ */
+ async _evaluateInspectedURL() {
+ const mainTarget = this._manager.target();
+ const runtimeModel = mainTarget.model(SDK.RuntimeModel);
+ const executionContext = runtimeModel && runtimeModel.defaultExecutionContext();
+ let inspectedURL = mainTarget.inspectedURL();
+ if (!executionContext)
+ return inspectedURL;
+
+ // Evaluate location.href for a more specific URL than inspectedURL provides so that SPA hash navigation routes
+ // will be respected and audited.
+ try {
+ const result = await executionContext.evaluate(
+ {
+ expression: 'window.location.href',
+ objectGroup: 'audits',
+ includeCommandLineAPI: false,
+ silent: false,
+ returnByValue: true,
+ generatePreview: false
+ },
+ /* userGesture */ false, /* awaitPromise */ false);
+ if (!result.exceptionDetails && result.object) {
+ inspectedURL = result.object.value;
+ result.object.release();
+ }
+ } catch (err) {
+ console.error(err);
+ }
+
+ return inspectedURL;
+ }
+
+ /**
+ * @return {!Object}
+ */
+ getFlags() {
+ const flags = {};
+ for (const runtimeSetting of Audits2.RuntimeSettings)
+ runtimeSetting.setFlags(flags, runtimeSetting.setting.get());
+ return flags;
+ }
+
+ /**
+ * @return {!Array<string>}
+ */
+ getCategoryIDs() {
+ const categoryIDs = [];
+ for (const preset of Audits2.Presets) {
+ if (preset.setting.get())
+ categoryIDs.push(preset.configID);
+ }
+ return categoryIDs;
+ }
+
+ /**
+ * @param {{force: boolean}=} options
+ * @return {!Promise<string>}
+ */
+ async getInspectedURL(options) {
+ if (options && options.force || !this._inspectedURL)
+ this._inspectedURL = await this._evaluateInspectedURL();
+ return this._inspectedURL;
+ }
+
+ recomputePageAuditability() {
+ const hasActiveServiceWorker = this._hasActiveServiceWorker();
+ const hasAtLeastOneCategory = this._hasAtLeastOneCategory();
+ const unauditablePageMessage = this._unauditablePageMessage();
+
+ let helpText = '';
+ if (hasActiveServiceWorker) {
+ helpText = Common.UIString(
+ 'Multiple tabs are being controlled by the same service worker. ' +
+ 'Close your other tabs on the same origin to audit this page.');
+ } else if (!hasAtLeastOneCategory) {
+ helpText = Common.UIString('At least one category must be selected.');
+ } else if (unauditablePageMessage) {
+ helpText = unauditablePageMessage;
+ }
+
+ this.dispatchEventToListeners(Audits2.Events.PageAuditabilityChanged, {helpText});
+ }
+};
+
+
+/** @typedef {{type: string, setting: !Common.Setting, configID: string, title: string, description: string}} */
+Audits2.Preset;
+
+/** @type {!Array.<!Audits2.Preset>} */
+Audits2.Presets = [
+ // configID maps to Lighthouse's Object.keys(config.categories)[0] value
+ {
+ setting: Common.settings.createSetting('audits2.cat_perf', true),
+ configID: 'performance',
+ title: 'Performance',
+ description: 'How long does this app take to show content and become usable'
+ },
+ {
+ setting: Common.settings.createSetting('audits2.cat_pwa', true),
+ configID: 'pwa',
+ title: 'Progressive Web App',
+ description: 'Does this page meet the standard of a Progressive Web App'
+ },
+ {
+ setting: Common.settings.createSetting('audits2.cat_best_practices', true),
+ configID: 'best-practices',
+ title: 'Best practices',
+ description: 'Does this page follow best practices for modern web development'
+ },
+ {
+ setting: Common.settings.createSetting('audits2.cat_a11y', true),
+ configID: 'accessibility',
+ title: 'Accessibility',
+ description: 'Is this page usable by people with disabilities or impairments'
+ },
+ {
+ setting: Common.settings.createSetting('audits2.cat_seo', true),
+ configID: 'seo',
+ title: 'SEO',
+ description: 'Is this page optimized for search engine results ranking'
+ },
+];
+
+/** @typedef {{setting: !Common.Setting, description: string, setFlags: function(!Object, string), options: !Array}} */
+Audits2.RuntimeSetting;
+
+/** @type {!Array.<!Audits2.RuntimeSetting>} */
+Audits2.RuntimeSettings = [
+ {
+ setting: Common.settings.createSetting('audits2.device_type', 'mobile'),
+ description: Common.UIString('Apply mobile emulation during auditing'),
+ setFlags: (flags, value) => {
+ flags.disableDeviceEmulation = value === 'desktop';
+ },
+ options: [
+ {label: Common.UIString('Mobile'), value: 'mobile'},
+ {label: Common.UIString('Desktop'), value: 'desktop'},
+ ],
+ },
+ {
+ setting: Common.settings.createSetting('audits2.throttling', 'default'),
+ description: Common.UIString('Apply network and CPU throttling during performance auditing'),
+ setFlags: (flags, value) => {
+ flags.disableNetworkThrottling = value === 'off';
+ flags.disableCpuThrottling = value === 'off';
+ },
+ options: [
+ {label: Common.UIString('3G w/ CPU slowdown'), value: 'default'},
+ {label: Common.UIString('No throttling'), value: 'off'},
+ ],
+ },
+ {
+ setting: Common.settings.createSetting('audits2.storage_reset', 'on'),
+ description: Common.UIString('Reset storage (localStorage, IndexedDB, etc) to a clean baseline before auditing'),
+ setFlags: (flags, value) => {
+ flags.disableStorageReset = value === 'off';
+ },
+ options: [
+ {label: Common.UIString('Clear storage'), value: 'on'},
+ {label: Common.UIString('Preserve storage'), value: 'off'},
+ ],
+ },
+];
+
+Audits2.Events = {
+ PageAuditabilityChanged: Symbol('PageAuditabilityChanged'),
+ AuditProgressChanged: Symbol('AuditProgressChanged'),
+ RequestAuditStart: Symbol('RequestAuditStart'),
+ RequestAuditCancel: Symbol('RequestAuditCancel'),
+};
\ No newline at end of file
diff --git a/front_end/audits2/Audits2Dialog.js b/front_end/audits2/Audits2Dialog.js
deleted file mode 100644
index ecee4da..0000000
--- a/front_end/audits2/Audits2Dialog.js
+++ /dev/null
@@ -1,262 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @unrestricted
- */
-Audits2.Audits2Dialog = class {
- constructor(onAuditComplete, protocolService) {
- this._onAuditComplete = onAuditComplete;
- this._protocolService = protocolService;
- this._protocolService.registerStatusCallback(msg => this._updateStatus(Common.UIString(msg)));
- }
-
- /**
- * @param {!Element} dialogRenderElement
- */
- render(dialogRenderElement) {
- this._dialog = new UI.Dialog();
- this._dialog.setOutsideClickCallback(event => event.consume(true));
- const root = UI.createShadowRootWithCoreStyles(this._dialog.contentElement, 'audits2/audits2Dialog.css');
- const auditsViewElement = root.createChild('div', 'audits2-view vbox');
-
- const closeButton = auditsViewElement.createChild('div', 'dialog-close-button', 'dt-close-button');
- closeButton.addEventListener('click', () => this._cancelAndClose());
-
- const uiElement = auditsViewElement.createChild('div', 'vbox launcher-container');
- const headerElement = uiElement.createChild('header');
- this._headerTitleElement = headerElement.createChild('p');
- this._headerTitleElement.textContent = Common.UIString('Audits to perform');
- uiElement.appendChild(headerElement);
-
- this._auditSelectorForm = uiElement.createChild('form', 'audits2-form');
-
- for (const preset of Audits2.Audits2Panel.Presets) {
- preset.setting.setTitle(preset.title);
- const checkbox = new UI.ToolbarSettingCheckbox(preset.setting);
- const row = this._auditSelectorForm.createChild('div', 'vbox audits2-launcher-row');
- row.appendChild(checkbox.element);
- row.createChild('span', 'audits2-launcher-description dimmed').textContent = preset.description;
- }
-
- this._statusView = new Audits2.Audits2StatusView();
- this._statusView.render(uiElement);
- this._dialogHelpText = uiElement.createChild('div', 'audits2-dialog-help-text');
-
- const buttonsRow = uiElement.createChild('div', 'audits2-dialog-buttons hbox');
- this._startButton =
- UI.createTextButton(Common.UIString('Run audit'), this._start.bind(this), '', true /* primary */);
- buttonsRow.appendChild(this._startButton);
- this._cancelButton = UI.createTextButton(Common.UIString('Cancel'), this._cancel.bind(this));
- buttonsRow.appendChild(this._cancelButton);
-
- auditsViewElement.tabIndex = 0;
- this._dialog.setDefaultFocusedElement(this._startButton);
- this._dialog.setSizeBehavior(UI.GlassPane.SizeBehavior.SetExactWidthMaxHeight);
- this._dialog.setMaxContentSize(new UI.Size(500, 400));
- this._dialog.show(dialogRenderElement);
- }
-
- hide() {
- if (!this._dialog)
- return;
-
- this._dialog.hide();
-
- delete this._dialog;
- delete this._statusView;
- delete this._startButton;
- delete this._cancelButton;
- delete this._auditSelectorForm;
- delete this._headerTitleElement;
- delete this._emulationEnabledBefore;
- delete this._emulationOutlineEnabledBefore;
- }
-
- /**
- * @param {boolean} isEnabled
- */
- setStartEnabled(isEnabled) {
- if (this._dialogHelpText)
- this._dialogHelpText.classList.toggle('hidden', isEnabled);
-
- if (this._startButton)
- this._startButton.disabled = !isEnabled;
- }
-
- /**
- * @param {string} text
- */
- setHelpText(text) {
- if (this._dialogHelpText)
- this._dialogHelpText.textContent = text;
- }
-
- /**
- * @param {string} message
- */
- _updateStatus(message) {
- if (!this._statusView)
- return;
- this._statusView.updateStatus(message);
- }
-
- _cancelAndClose() {
- this._cancel();
- this.hide();
- }
-
- async _cancel() {
- if (this._auditRunning) {
- this._updateStatus(Common.UIString('Cancelling\u2026'));
- await this._stopAndReattach();
-
- if (this._statusView)
- this._statusView.reset();
- } else {
- this.hide();
- }
- }
-
- /**
- * @return {!Promise<undefined>}
- */
- _getInspectedURL() {
- const mainTarget = SDK.targetManager.mainTarget();
- const runtimeModel = mainTarget.model(SDK.RuntimeModel);
- const executionContext = runtimeModel && runtimeModel.defaultExecutionContext();
- let inspectedURL = mainTarget.inspectedURL();
- if (!executionContext)
- return Promise.resolve(inspectedURL);
-
- // Evaluate location.href for a more specific URL than inspectedURL provides so that SPA hash navigation routes
- // will be respected and audited.
- return executionContext
- .evaluate(
- {
- expression: 'window.location.href',
- objectGroup: 'audits',
- includeCommandLineAPI: false,
- silent: false,
- returnByValue: true,
- generatePreview: false
- },
- /* userGesture */ false, /* awaitPromise */ false)
- .then(result => {
- if (!result.exceptionDetails && result.object) {
- inspectedURL = result.object.value;
- result.object.release();
- }
-
- return inspectedURL;
- });
- }
-
- /**
- * @return {!Object}
- */
- _getFlags() {
- const flags = {};
- for (const runtimeSetting of Audits2.Audits2Panel.RuntimeSettings)
- runtimeSetting.setFlags(flags, runtimeSetting.setting.get());
- return flags;
- }
-
- async _start() {
- const flags = this._getFlags();
-
- const emulationModel = self.singleton(Emulation.DeviceModeModel);
- this._emulationEnabledBefore = emulationModel.enabledSetting().get();
- this._emulationOutlineEnabledBefore = emulationModel.deviceOutlineSetting().get();
- emulationModel.toolbarControlsEnabledSetting().set(false);
-
- if (flags.disableDeviceEmulation) {
- emulationModel.enabledSetting().set(false);
- emulationModel.deviceOutlineSetting().set(false);
- emulationModel.emulate(Emulation.DeviceModeModel.Type.None, null, null);
- } else {
- emulationModel.enabledSetting().set(true);
- emulationModel.deviceOutlineSetting().set(true);
-
- for (const device of Emulation.EmulatedDevicesList.instance().standard()) {
- if (device.title === 'Nexus 5X')
- emulationModel.emulate(Emulation.DeviceModeModel.Type.Device, device, device.modes[0], 1);
- }
- }
-
- this._dialog.setCloseOnEscape(false);
-
- const categoryIDs = [];
- for (const preset of Audits2.Audits2Panel.Presets) {
- if (preset.setting.get())
- categoryIDs.push(preset.configID);
- }
- Host.userMetrics.actionTaken(Host.UserMetrics.Action.Audits2Started);
-
- try {
- this._auditURL = await this._getInspectedURL();
- await this._protocolService.attach();
-
- this._auditRunning = true;
- this._updateButton(this._auditURL);
- this._updateStatus(Common.UIString('Loading\u2026'));
-
- const lighthouseResult = await this._protocolService.startLighthouse(this._auditURL, categoryIDs, flags);
- if (lighthouseResult && lighthouseResult.fatal) {
- const error = new Error(lighthouseResult.message);
- error.stack = lighthouseResult.stack;
- throw error;
- }
-
- if (!lighthouseResult)
- throw new Error('Auditing failed to produce a result');
-
- await this._stopAndReattach();
- await this._onAuditComplete(lighthouseResult);
- } catch (err) {
- if (err instanceof Error)
- this._statusView.renderBugReport(err, this._auditURL);
- }
- }
-
- /**
- * @return {!Promise<undefined>}
- */
- async _stopAndReattach() {
- await this._protocolService.detach();
-
- const emulationModel = self.singleton(Emulation.DeviceModeModel);
- emulationModel.enabledSetting().set(this._emulationEnabledBefore);
- emulationModel.deviceOutlineSetting().set(this._emulationOutlineEnabledBefore);
- emulationModel.toolbarControlsEnabledSetting().set(true);
- Emulation.InspectedPagePlaceholder.instance().update(true);
-
- Host.userMetrics.actionTaken(Host.UserMetrics.Action.Audits2Finished);
- const resourceTreeModel = SDK.targetManager.mainTarget().model(SDK.ResourceTreeModel);
- // reload to reset the page state
- await resourceTreeModel.navigate(this._auditURL);
- this._auditRunning = false;
- this._updateButton(this._auditURL);
- }
-
- /**
- * @param {string} auditURL
- */
- _updateButton(auditURL) {
- if (!this._dialog)
- return;
- this._startButton.classList.toggle('hidden', this._auditRunning);
- this._startButton.disabled = this._auditRunning;
- this._statusView.setVisible(this._auditRunning);
- this._auditSelectorForm.classList.toggle('hidden', this._auditRunning);
- if (this._auditRunning) {
- const parsedURL = (auditURL || '').asParsedURL();
- const pageHost = parsedURL && parsedURL.host;
- this._headerTitleElement.textContent =
- pageHost ? ls`Auditing ${pageHost}\u2026` : ls`Auditing your web page\u2026`;
- } else {
- this._headerTitleElement.textContent = Common.UIString('Audits to perform');
- }
- }
-};
diff --git a/front_end/audits2/Audits2Panel.js b/front_end/audits2/Audits2Panel.js
index cb97841..012d222 100644
--- a/front_end/audits2/Audits2Panel.js
+++ b/front_end/audits2/Audits2Panel.js
@@ -3,7 +3,6 @@
// found in the LICENSE file.
/**
- * @implements {SDK.SDKModelObserver<!SDK.ServiceWorkerManager>}
* @unrestricted
*/
Audits2.Audits2Panel = class extends UI.Panel {
@@ -13,132 +12,43 @@
this.registerRequiredCSS('audits2/audits2Panel.css');
this._protocolService = new Audits2.ProtocolService();
+ this._controller = new Audits2.AuditController(this._protocolService);
+ this._startView = new Audits2.StartView(this._controller);
+ this._statusView = new Audits2.StatusView(this._controller);
- this._renderToolbar();
+ this._unauditableExplanation = null;
+ this._cachedRenderedReports = new Map();
- this._auditResultsElement = this.contentElement.createChild('div', 'audits2-results-container');
this._dropTarget = new UI.DropTarget(
this.contentElement, [UI.DropTarget.Type.File], Common.UIString('Drop audit file here'),
this._handleDrop.bind(this));
- for (const preset of Audits2.Audits2Panel.Presets)
- preset.setting.addChangeListener(this._refreshDialogUI.bind(this));
+ this._controller.addEventListener(Audits2.Events.PageAuditabilityChanged, this._refreshStartAuditUI.bind(this));
+ this._controller.addEventListener(Audits2.Events.AuditProgressChanged, this._refreshStatusUI.bind(this));
+ this._controller.addEventListener(Audits2.Events.RequestAuditStart, this._startAudit.bind(this));
+ this._controller.addEventListener(Audits2.Events.RequestAuditCancel, this._cancelAudit.bind(this));
- this._showLandingPage();
- SDK.targetManager.observeModels(SDK.ServiceWorkerManager, this);
- SDK.targetManager.addEventListener(SDK.TargetManager.Events.InspectedURLChanged, this._refreshDialogUI, this);
+ this._renderToolbar();
+ this._auditResultsElement = this.contentElement.createChild('div', 'audits2-results-container');
+ this._renderStartView();
+
+ this._controller.recomputePageAuditability();
}
/**
- * @override
- * @param {!SDK.ServiceWorkerManager} serviceWorkerManager
+ * @param {!Common.Event} evt
*/
- modelAdded(serviceWorkerManager) {
- if (this._manager)
- return;
-
- this._manager = serviceWorkerManager;
- this._serviceWorkerListeners = [
- this._manager.addEventListener(SDK.ServiceWorkerManager.Events.RegistrationUpdated, this._refreshDialogUI, this),
- this._manager.addEventListener(SDK.ServiceWorkerManager.Events.RegistrationDeleted, this._refreshDialogUI, this),
- ];
-
- this._refreshDialogUI();
+ _refreshStartAuditUI(evt) {
+ this._unauditableExplanation = evt.data.helpText;
+ this._startView.setUnauditableExplanation(evt.data.helpText);
+ this._startView.setStartButtonEnabled(!evt.data.helpText);
}
/**
- * @override
- * @param {!SDK.ServiceWorkerManager} serviceWorkerManager
+ * @param {!Common.Event} evt
*/
- modelRemoved(serviceWorkerManager) {
- if (!this._manager || this._manager !== serviceWorkerManager)
- return;
-
- Common.EventTarget.removeEventListeners(this._serviceWorkerListeners);
- this._manager = null;
- this._serviceWorkerListeners = null;
- this._refreshDialogUI();
- }
-
- /**
- * @return {boolean}
- */
- _hasActiveServiceWorker() {
- if (!this._manager)
- return false;
-
- const mainTarget = SDK.targetManager.mainTarget();
- if (!mainTarget)
- return false;
-
- const inspectedURL = mainTarget.inspectedURL().asParsedURL();
- const inspectedOrigin = inspectedURL && inspectedURL.securityOrigin();
- for (const registration of this._manager.registrations().values()) {
- if (registration.securityOrigin !== inspectedOrigin)
- continue;
-
- for (const version of registration.versions.values()) {
- if (version.controlledClients.length > 1)
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * @return {boolean}
- */
- _hasAtLeastOneCategory() {
- return Audits2.Audits2Panel.Presets.some(preset => preset.setting.get());
- }
-
- /**
- * @return {?string}
- */
- _unauditablePageMessage() {
- if (!this._manager)
- return null;
-
- const mainTarget = SDK.targetManager.mainTarget();
- const inspectedURL = mainTarget && mainTarget.inspectedURL();
- if (inspectedURL && !/^(http|chrome-extension)/.test(inspectedURL)) {
- return Common.UIString(
- 'Can only audit HTTP/HTTPS pages and Chrome extensions. ' +
- 'Navigate to a different page to start an audit.');
- }
-
- // Audits don't work on most undockable targets (extension popup pages, remote debugging, etc).
- // However, the tests run in a content shell which is not dockable yet audits just fine,
- // so disable this check when under test.
- if (!Host.isUnderTest() && !Runtime.queryParam('can_dock'))
- return Common.UIString('Can only audit tabs. Navigate to this page in a separate tab to start an audit.');
-
- return null;
- }
-
- _refreshDialogUI() {
- if (!this._dialog)
- return;
-
- const hasActiveServiceWorker = this._hasActiveServiceWorker();
- const hasAtLeastOneCategory = this._hasAtLeastOneCategory();
- const unauditablePageMessage = this._unauditablePageMessage();
- const isDisabled = hasActiveServiceWorker || !hasAtLeastOneCategory || !!unauditablePageMessage;
-
- let helpText = '';
- if (hasActiveServiceWorker) {
- helpText = Common.UIString(
- 'Multiple tabs are being controlled by the same service worker. ' +
- 'Close your other tabs on the same origin to audit this page.');
- } else if (!hasAtLeastOneCategory) {
- helpText = Common.UIString('At least one category must be selected.');
- } else if (unauditablePageMessage) {
- helpText = unauditablePageMessage;
- }
-
- this._dialog.setHelpText(helpText);
- this._dialog.setStartEnabled(!isDisabled);
+ _refreshStatusUI(evt) {
+ this._statusView.updateStatus(evt.data.message);
}
_refreshToolbarUI() {
@@ -148,7 +58,7 @@
_clearAll() {
this._reportSelector.clearAll();
- this._showLandingPage();
+ this._renderStartView();
this._refreshToolbarUI();
}
@@ -161,7 +71,7 @@
this._newButton = new UI.ToolbarButton(Common.UIString('Perform an audit\u2026'), 'largeicon-add');
toolbar.appendToolbarItem(this._newButton);
- this._newButton.addEventListener(UI.ToolbarButton.Events.Click, this._showDialog.bind(this));
+ this._newButton.addEventListener(UI.ToolbarButton.Events.Click, this._renderStartView.bind(this));
this._downloadButton = new UI.ToolbarButton(Common.UIString('Download report'), 'largeicon-download');
toolbar.appendToolbarItem(this._downloadButton);
@@ -169,62 +79,71 @@
toolbar.appendSeparator();
- this._reportSelector = new Audits2.ReportSelector();
+ this._reportSelector = new Audits2.ReportSelector(() => this._renderStartView());
toolbar.appendToolbarItem(this._reportSelector.comboBox());
this._clearButton = new UI.ToolbarButton(Common.UIString('Clear all'), 'largeicon-clear');
toolbar.appendToolbarItem(this._clearButton);
this._clearButton.addEventListener(UI.ToolbarButton.Events.Click, this._clearAll.bind(this));
- toolbar.appendSeparator();
+ this._refreshToolbarUI();
+ }
- toolbar.appendText(ls`Emulation: `);
- for (const runtimeSetting of Audits2.Audits2Panel.RuntimeSettings) {
- const control = new UI.ToolbarSettingComboBox(runtimeSetting.options, runtimeSetting.setting);
- control.element.title = runtimeSetting.description;
- toolbar.appendToolbarItem(control);
+ _renderStartView() {
+ this._auditResultsElement.removeChildren();
+ this._statusView.hide();
+
+ this._reportSelector.selectNewAudit();
+ this.contentElement.classList.toggle('in-progress', false);
+
+ this._startView.show(this.contentElement);
+ this._startView.setUnauditableExplanation(this._unauditableExplanation);
+ this._startView.setStartButtonEnabled(!this._unauditableExplanation);
+ this._refreshToolbarUI();
+ }
+
+ /**
+ * @param {string} inspectedURL
+ */
+ _renderStatusView(inspectedURL) {
+ this.contentElement.classList.toggle('in-progress', true);
+ this._statusView.setInspectedURL(inspectedURL);
+ this._statusView.show(this.contentElement);
+ }
+
+ /**
+ * @param {!ReportRenderer.ReportJSON} lighthouseResult
+ */
+ _renderReport(lighthouseResult) {
+ this.contentElement.classList.toggle('in-progress', false);
+ this._startView.hideWidget();
+ this._statusView.hide();
+ this._auditResultsElement.removeChildren();
+ this._refreshToolbarUI();
+
+ const cachedRenderedReport = this._cachedRenderedReports.get(lighthouseResult);
+ if (cachedRenderedReport) {
+ this._auditResultsElement.appendChild(cachedRenderedReport);
+ return;
}
- this._refreshToolbarUI();
- }
+ const reportContainer = this._auditResultsElement.createChild('div', 'lh-vars lh-root lh-devtools');
- _showLandingPage() {
- if (this._reportSelector.hasCurrentSelection())
+ const dom = new DOM(/** @type {!Document} */ (this._auditResultsElement.ownerDocument));
+ const detailsRenderer = new Audits2.DetailsRenderer(dom);
+ const categoryRenderer = new Audits2.CategoryRenderer(dom, detailsRenderer);
+ categoryRenderer.setTraceArtifact(lighthouseResult);
+ const renderer = new Audits2.ReportRenderer(dom, categoryRenderer);
+
+ const templatesHTML = Runtime.cachedResources['audits2/lighthouse/templates.html'];
+ const templatesDOM = new DOMParser().parseFromString(templatesHTML, 'text/html');
+ if (!templatesDOM)
return;
- this._auditResultsElement.removeChildren();
- const landingPage = this._auditResultsElement.createChild('div', 'vbox audits2-landing-page');
- const landingCenter = landingPage.createChild('div', 'vbox audits2-landing-center');
- landingCenter.createChild('div', 'audits2-logo');
- const text = landingCenter.createChild('div', 'audits2-landing-text');
- text.createChild('span', 'audits2-landing-bold-text').textContent = Common.UIString('Audits');
- text.createChild('span').textContent = Common.UIString(
- ' help you identify and fix common problems that affect' +
- ' your site\'s performance, accessibility, and user experience. ');
- const link = text.createChild('span', 'link');
- link.textContent = Common.UIString('Learn more');
- link.addEventListener(
- 'click', () => InspectorFrontendHost.openInNewTab('https://developers.google.com/web/tools/lighthouse/'));
+ renderer.setTemplateContext(templatesDOM);
+ renderer.renderReport(lighthouseResult, reportContainer);
- const newButton = UI.createTextButton(
- Common.UIString('Perform an audit\u2026'), this._showDialog.bind(this), '', true /* primary */);
- landingCenter.appendChild(newButton);
- this.setDefaultFocusedElement(newButton);
- this._refreshToolbarUI();
- }
-
- _showDialog() {
- this._dialog = new Audits2.Audits2Dialog(result => this._buildReportUI(result), this._protocolService);
- this._dialog.render(this._auditResultsElement);
- this._refreshDialogUI();
- }
-
- _hideDialog() {
- if (!this._dialog)
- return;
-
- this._dialog.hide();
- delete this._dialog;
+ this._cachedRenderedReports.set(lighthouseResult, reportContainer);
}
/**
@@ -234,11 +153,11 @@
if (lighthouseResult === null)
return;
- const optionElement =
- new Audits2.ReportSelector.Item(lighthouseResult, this._auditResultsElement, this._showLandingPage.bind(this));
+ const optionElement = new Audits2.ReportSelector.Item(
+ lighthouseResult, () => this._renderReport(lighthouseResult), this._renderStartView.bind(this));
this._reportSelector.prepend(optionElement);
- this._hideDialog();
this._refreshToolbarUI();
+ this._renderReport(lighthouseResult);
}
/**
@@ -262,377 +181,96 @@
}
/**
- * @param {string} profile
+ * @param {string} report
*/
- _loadedFromFile(profile) {
- const data = JSON.parse(profile);
+ _loadedFromFile(report) {
+ const data = JSON.parse(report);
if (!data['lighthouseVersion'])
return;
this._buildReportUI(/** @type {!ReportRenderer.ReportJSON} */ (data));
}
-};
-/**
- * @override
- */
-Audits2.Audits2Panel.ReportRenderer = class extends ReportRenderer {
- /**
- * Provides empty element for left nav
- * @override
- * @returns {!DocumentFragment}
- */
- _renderReportNav() {
- return createDocumentFragment();
- }
+ async _startAudit() {
+ Host.userMetrics.actionTaken(Host.UserMetrics.Action.Audits2Started);
- /**
- * @param {!ReportRenderer.ReportJSON} report
- * @override
- * @return {!DocumentFragment}
- */
- _renderReportHeader(report) {
- return createDocumentFragment();
- }
-};
+ try {
+ const inspectedURL = await this._controller.getInspectedURL({force: true});
+ const categoryIDs = this._controller.getCategoryIDs();
+ const flags = this._controller.getFlags();
-class ReportUIFeatures {
- /**
- * @param {!ReportRenderer.ReportJSON} report
- */
- initFeatures(report) {
- }
-}
+ await this._setupEmulationAndProtocolConnection();
-/** @typedef {{type: string, setting: !Common.Setting, configID: string, title: string, description: string}} */
-Audits2.Audits2Panel.Preset;
+ this._renderStatusView(inspectedURL);
-/** @type {!Array.<!Audits2.Audits2Panel.Preset>} */
-Audits2.Audits2Panel.Presets = [
- // configID maps to Lighthouse's Object.keys(config.categories)[0] value
- {
- setting: Common.settings.createSetting('audits2.cat_perf', true),
- configID: 'performance',
- title: 'Performance',
- description: 'How long does this app take to show content and become usable'
- },
- {
- setting: Common.settings.createSetting('audits2.cat_pwa', true),
- configID: 'pwa',
- title: 'Progressive Web App',
- description: 'Does this page meet the standard of a Progressive Web App'
- },
- {
- setting: Common.settings.createSetting('audits2.cat_best_practices', true),
- configID: 'best-practices',
- title: 'Best practices',
- description: 'Does this page follow best practices for modern web development'
- },
- {
- setting: Common.settings.createSetting('audits2.cat_a11y', true),
- configID: 'accessibility',
- title: 'Accessibility',
- description: 'Is this page usable by people with disabilities or impairments'
- },
- {
- setting: Common.settings.createSetting('audits2.cat_seo', true),
- configID: 'seo',
- title: 'SEO',
- description: 'Is this page optimized for search engine results ranking'
- },
-];
+ const lighthouseResult = await this._protocolService.startLighthouse(inspectedURL, categoryIDs, flags);
-/** @typedef {{setting: !Common.Setting, description: string, setFlags: function(!Object, string), options: !Array}} */
-Audits2.Audits2Panel.RuntimeSetting;
+ if (lighthouseResult && lighthouseResult.fatal) {
+ const error = new Error(lighthouseResult.message);
+ error.stack = lighthouseResult.stack;
+ throw error;
+ }
-/** @type {!Array.<!Audits2.Audits2Panel.RuntimeSetting>} */
-Audits2.Audits2Panel.RuntimeSettings = [
- {
- setting: Common.settings.createSetting('audits2.device_type', 'mobile'),
- description: Common.UIString('Apply mobile emulation during auditing'),
- setFlags: (flags, value) => {
- flags.disableDeviceEmulation = value === 'desktop';
- },
- options: [
- {label: Common.UIString('Mobile'), value: 'mobile'},
- {label: Common.UIString('Desktop'), value: 'desktop'},
- ],
- },
- {
- setting: Common.settings.createSetting('audits2.throttling', 'default'),
- description: Common.UIString('Apply network and CPU throttling during performance auditing'),
- setFlags: (flags, value) => {
- flags.disableNetworkThrottling = value === 'off';
- flags.disableCpuThrottling = value === 'off';
- },
- options: [
- {label: Common.UIString('3G w/ CPU slowdown'), value: 'default'},
- {label: Common.UIString('No throttling'), value: 'off'},
- ],
- },
- {
- setting: Common.settings.createSetting('audits2.storage_reset', 'on'),
- description: Common.UIString('Reset storage (localStorage, IndexedDB, etc) to a clean baseline before auditing'),
- setFlags: (flags, value) => {
- flags.disableStorageReset = value === 'off';
- },
- options: [
- {label: Common.UIString('Clear storage'), value: 'on'},
- {label: Common.UIString('Preserve storage'), value: 'off'},
- ],
- },
-];
+ if (!lighthouseResult)
+ throw new Error('Auditing failed to produce a result');
-Audits2.ReportSelector = class {
- constructor() {
- this._emptyItem = null;
- this._comboBox = new UI.ToolbarComboBox(this._handleChange.bind(this), 'audits2-report');
- this._comboBox.setMaxWidth(180);
- this._comboBox.setMinWidth(140);
- this._itemByOptionElement = new Map();
- this._setPlaceholderState();
- }
+ Host.userMetrics.actionTaken(Host.UserMetrics.Action.Audits2Finished);
- _setPlaceholderState() {
- this._comboBox.setEnabled(false);
- this._emptyItem = createElement('option');
- this._emptyItem.label = Common.UIString('(no reports)');
- this._comboBox.selectElement().appendChild(this._emptyItem);
- this._comboBox.select(this._emptyItem);
- }
-
- /**
- * @param {!Event} event
- */
- _handleChange(event) {
- const item = this._selectedItem();
- if (item)
- item.select();
- }
-
- /**
- * @return {!Audits2.ReportSelector.Item}
- */
- _selectedItem() {
- const option = this._comboBox.selectedOption();
- return this._itemByOptionElement.get(option);
- }
-
- /**
- * @return {boolean}
- */
- hasCurrentSelection() {
- return !!this._selectedItem();
- }
-
- /**
- * @return {boolean}
- */
- hasItems() {
- return this._itemByOptionElement.size > 0;
- }
-
- /**
- * @return {!UI.ToolbarComboBox}
- */
- comboBox() {
- return this._comboBox;
- }
-
- /**
- * @param {!Audits2.ReportSelector.Item} item
- */
- prepend(item) {
- if (this._emptyItem) {
- this._emptyItem.remove();
- delete this._emptyItem;
- }
-
- const optionEl = item.optionElement();
- const selectEl = this._comboBox.selectElement();
-
- this._itemByOptionElement.set(optionEl, item);
- selectEl.insertBefore(optionEl, selectEl.firstElementChild);
- this._comboBox.setEnabled(true);
- this._comboBox.select(optionEl);
- item.select();
- }
-
- clearAll() {
- for (const elem of this._comboBox.options()) {
- this._itemByOptionElement.get(elem).delete();
- this._itemByOptionElement.delete(elem);
- }
-
- this._setPlaceholderState();
- }
-
- downloadSelected() {
- const item = this._selectedItem();
- item.download();
- }
-};
-
-Audits2.ReportSelector.Item = class {
- /**
- * @param {!ReportRenderer.ReportJSON} lighthouseResult
- * @param {!Element} resultsView
- * @param {function()} showLandingCallback
- */
- constructor(lighthouseResult, resultsView, showLandingCallback) {
- this._lighthouseResult = lighthouseResult;
- this._resultsView = resultsView;
- this._showLandingCallback = showLandingCallback;
- /** @type {?Element} */
- this._reportContainer = null;
-
-
- const url = new Common.ParsedURL(lighthouseResult.url);
- const timestamp = lighthouseResult.generatedTime;
- this._element = createElement('option');
- this._element.label = `${new Date(timestamp).toLocaleTimeString()} - ${url.domain()}`;
- }
-
- select() {
- this._renderReport();
- }
-
- /**
- * @return {!Element}
- */
- optionElement() {
- return this._element;
- }
-
- delete() {
- if (this._element)
- this._element.remove();
- this._showLandingCallback();
- }
-
- download() {
- const url = new Common.ParsedURL(this._lighthouseResult.url).domain();
- const timestamp = this._lighthouseResult.generatedTime;
- const fileName = `${url}-${new Date(timestamp).toISO8601Compact()}.json`;
- Workspace.fileManager.save(fileName, JSON.stringify(this._lighthouseResult), true);
- }
-
- _renderReport() {
- this._resultsView.removeChildren();
- if (this._reportContainer) {
- this._resultsView.appendChild(this._reportContainer);
- return;
- }
-
- this._reportContainer = this._resultsView.createChild('div', 'lh-vars lh-root lh-devtools');
-
- const dom = new DOM(/** @type {!Document} */ (this._resultsView.ownerDocument));
- const detailsRenderer = new Audits2.DetailsRenderer(dom);
- const categoryRenderer = new Audits2.CategoryRenderer(dom, detailsRenderer);
- categoryRenderer.setTraceArtifact(this._lighthouseResult);
- const renderer = new Audits2.Audits2Panel.ReportRenderer(dom, categoryRenderer);
-
- const templatesHTML = Runtime.cachedResources['audits2/lighthouse/templates.html'];
- const templatesDOM = new DOMParser().parseFromString(templatesHTML, 'text/html');
- if (!templatesDOM)
- return;
-
- renderer.setTemplateContext(templatesDOM);
- renderer.renderReport(this._lighthouseResult, this._reportContainer);
- }
-};
-
-Audits2.CategoryRenderer = class extends CategoryRenderer {
- /**
- * @override
- * @param {!DOM} dom
- * @param {!DetailsRenderer} detailsRenderer
- */
- constructor(dom, detailsRenderer) {
- super(dom, detailsRenderer);
- this._defaultPassTrace = null;
- }
-
- /**
- * @param {!ReportRenderer.ReportJSON} lhr
- */
- setTraceArtifact(lhr) {
- if (!lhr.artifacts || !lhr.artifacts.traces || !lhr.artifacts.traces.defaultPass)
- return;
- this._defaultPassTrace = lhr.artifacts.traces.defaultPass;
- }
-
- /**
- * @override
- * @param {!ReportRenderer.CategoryJSON} category
- * @param {!Object<string, !ReportRenderer.GroupJSON>} groups
- * @return {!Element}
- */
- renderPerformanceCategory(category, groups) {
- const defaultPassTrace = this._defaultPassTrace;
- const element = super.renderPerformanceCategory(category, groups);
- if (!defaultPassTrace)
- return element;
-
- const timelineButton = UI.createTextButton(Common.UIString('View Trace'), onViewTraceClick, 'view-trace');
- element.querySelector('.lh-audit-group').prepend(timelineButton);
- return element;
-
- async function onViewTraceClick() {
- await UI.inspectorView.showPanel('timeline');
- Timeline.TimelinePanel.instance().loadFromEvents(defaultPassTrace.traceEvents);
+ await this._resetEmulationAndProtocolConnection();
+ this._buildReportUI(lighthouseResult);
+ } catch (err) {
+ if (err instanceof Error)
+ this._statusView.renderBugReport(err);
}
}
-};
-Audits2.DetailsRenderer = class extends DetailsRenderer {
- /**
- * @param {!DOM} dom
- */
- constructor(dom) {
- super(dom);
- this._onLoadPromise = null;
+ async _cancelAudit() {
+ this._statusView.updateStatus(ls`Cancelling`);
+ await this._resetEmulationAndProtocolConnection();
+ this._renderStartView();
}
- /**
- * @override
- * @param {!DetailsRenderer.NodeDetailsJSON} item
- * @return {!Element}
- */
- renderNode(item) {
- const element = super.renderNode(item);
- this._replaceWithDeferredNodeBlock(element, item);
- return element;
- }
+ async _setupEmulationAndProtocolConnection() {
+ const flags = this._controller.getFlags();
- /**
- * @param {!Element} origElement
- * @param {!DetailsRenderer.NodeDetailsJSON} detailsItem
- */
- async _replaceWithDeferredNodeBlock(origElement, detailsItem) {
- const mainTarget = SDK.targetManager.mainTarget();
- if (!this._onLoadPromise) {
- const resourceTreeModel = mainTarget.model(SDK.ResourceTreeModel);
- this._onLoadPromise = resourceTreeModel.once(SDK.ResourceTreeModel.Events.Load);
+ const emulationModel = self.singleton(Emulation.DeviceModeModel);
+ this._emulationEnabledBefore = emulationModel.enabledSetting().get();
+ this._emulationOutlineEnabledBefore = emulationModel.deviceOutlineSetting().get();
+ emulationModel.toolbarControlsEnabledSetting().set(false);
+
+ if (flags.disableDeviceEmulation) {
+ emulationModel.enabledSetting().set(false);
+ emulationModel.deviceOutlineSetting().set(false);
+ emulationModel.emulate(Emulation.DeviceModeModel.Type.None, null, null);
+ } else {
+ emulationModel.enabledSetting().set(true);
+ emulationModel.deviceOutlineSetting().set(true);
+
+ for (const device of Emulation.EmulatedDevicesList.instance().standard()) {
+ if (device.title === 'Nexus 5X')
+ emulationModel.emulate(Emulation.DeviceModeModel.Type.Device, device, device.modes[0], 1);
+ }
}
- await this._onLoadPromise;
+ await this._protocolService.attach();
+ this._isLHAttached = true;
+ }
- const domModel = mainTarget.model(SDK.DOMModel);
- if (!detailsItem.path)
+ async _resetEmulationAndProtocolConnection() {
+ if (!this._isLHAttached)
return;
- const nodeId = await domModel.pushNodeByPathToFrontend(detailsItem.path);
+ this._isLHAttached = false;
+ await this._protocolService.detach();
- if (!nodeId)
- return;
- const node = domModel.nodeForId(nodeId);
- if (!node)
- return;
+ const emulationModel = self.singleton(Emulation.DeviceModeModel);
+ emulationModel.enabledSetting().set(this._emulationEnabledBefore);
+ emulationModel.deviceOutlineSetting().set(this._emulationOutlineEnabledBefore);
+ emulationModel.toolbarControlsEnabledSetting().set(true);
+ Emulation.InspectedPagePlaceholder.instance().update(true);
- const element =
- await Common.Linkifier.linkify(node, /** @type {!Common.Linkifier.Options} */ ({title: detailsItem.snippet}));
- origElement.title = '';
- origElement.textContent = '';
- origElement.appendChild(element);
+ const resourceTreeModel = SDK.targetManager.mainTarget().model(SDK.ResourceTreeModel);
+ // reload to reset the page state
+ const inspectedURL = await this._controller.getInspectedURL();
+ await resourceTreeModel.navigate(inspectedURL);
}
};
diff --git a/front_end/audits2/Audits2ReportRenderer.js b/front_end/audits2/Audits2ReportRenderer.js
new file mode 100644
index 0000000..0460011
--- /dev/null
+++ b/front_end/audits2/Audits2ReportRenderer.js
@@ -0,0 +1,130 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @override
+ */
+Audits2.ReportRenderer = class extends ReportRenderer {
+ /**
+ * Provides empty element for left nav
+ * @override
+ * @returns {!DocumentFragment}
+ */
+ _renderReportNav() {
+ return createDocumentFragment();
+ }
+
+ /**
+ * @param {!ReportRenderer.ReportJSON} report
+ * @override
+ * @return {!DocumentFragment}
+ */
+ _renderReportHeader(report) {
+ return createDocumentFragment();
+ }
+};
+
+class ReportUIFeatures {
+ /**
+ * @param {!ReportRenderer.ReportJSON} report
+ */
+ initFeatures(report) {
+ }
+}
+
+Audits2.CategoryRenderer = class extends CategoryRenderer {
+ /**
+ * @override
+ * @param {!DOM} dom
+ * @param {!DetailsRenderer} detailsRenderer
+ */
+ constructor(dom, detailsRenderer) {
+ super(dom, detailsRenderer);
+ this._defaultPassTrace = null;
+ }
+
+ /**
+ * @param {!ReportRenderer.ReportJSON} lhr
+ */
+ setTraceArtifact(lhr) {
+ if (!lhr.artifacts || !lhr.artifacts.traces || !lhr.artifacts.traces.defaultPass)
+ return;
+ this._defaultPassTrace = lhr.artifacts.traces.defaultPass;
+ }
+
+ /**
+ * @override
+ * @param {!ReportRenderer.CategoryJSON} category
+ * @param {!Object<string, !ReportRenderer.GroupJSON>} groups
+ * @return {!Element}
+ */
+ renderPerformanceCategory(category, groups) {
+ const defaultPassTrace = this._defaultPassTrace;
+ const element = super.renderPerformanceCategory(category, groups);
+ if (!defaultPassTrace)
+ return element;
+
+ const timelineButton = UI.createTextButton(Common.UIString('View Trace'), onViewTraceClick, 'view-trace');
+ element.querySelector('.lh-audit-group').prepend(timelineButton);
+ return element;
+
+ async function onViewTraceClick() {
+ await UI.inspectorView.showPanel('timeline');
+ Timeline.TimelinePanel.instance().loadFromEvents(defaultPassTrace.traceEvents);
+ }
+ }
+};
+
+Audits2.DetailsRenderer = class extends DetailsRenderer {
+ /**
+ * @param {!DOM} dom
+ */
+ constructor(dom) {
+ super(dom);
+ this._onLoadPromise = null;
+ }
+
+ /**
+ * @override
+ * @param {!DetailsRenderer.NodeDetailsJSON} item
+ * @return {!Element}
+ */
+ renderNode(item) {
+ const element = super.renderNode(item);
+ this._replaceWithDeferredNodeBlock(element, item);
+ return element;
+ }
+
+ /**
+ * @param {!Element} origElement
+ * @param {!DetailsRenderer.NodeDetailsJSON} detailsItem
+ */
+ async _replaceWithDeferredNodeBlock(origElement, detailsItem) {
+ const mainTarget = SDK.targetManager.mainTarget();
+ if (!this._onLoadPromise) {
+ const resourceTreeModel = mainTarget.model(SDK.ResourceTreeModel);
+ this._onLoadPromise = resourceTreeModel.once(SDK.ResourceTreeModel.Events.Load);
+ }
+
+ await this._onLoadPromise;
+
+ const domModel = mainTarget.model(SDK.DOMModel);
+ if (!detailsItem.path)
+ return;
+
+ const nodeId = await domModel.pushNodeByPathToFrontend(detailsItem.path);
+
+ if (!nodeId)
+ return;
+ const node = domModel.nodeForId(nodeId);
+ if (!node)
+ return;
+
+ const element =
+ await Common.Linkifier.linkify(node, /** @type {!Common.Linkifier.Options} */ ({title: detailsItem.snippet}));
+ origElement.title = '';
+ origElement.textContent = '';
+ origElement.appendChild(element);
+ }
+};
diff --git a/front_end/audits2/Audits2ReportSelector.js b/front_end/audits2/Audits2ReportSelector.js
new file mode 100644
index 0000000..fda21c7
--- /dev/null
+++ b/front_end/audits2/Audits2ReportSelector.js
@@ -0,0 +1,143 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+Audits2.ReportSelector = class {
+ constructor(renderNewAuditView) {
+ this._renderNewAuditView = renderNewAuditView;
+ this._newAuditItem = createElement('option');
+ this._comboBox = new UI.ToolbarComboBox(this._handleChange.bind(this), 'audits2-report');
+ this._comboBox.setMaxWidth(180);
+ this._comboBox.setMinWidth(140);
+ this._itemByOptionElement = new Map();
+ this._setEmptyState();
+ }
+
+ _setEmptyState() {
+ this._comboBox.selectElement().removeChildren();
+
+ this._comboBox.setEnabled(false);
+ this._newAuditItem = createElement('option');
+ this._newAuditItem.label = Common.UIString('(new audit)');
+ this._comboBox.selectElement().appendChild(this._newAuditItem);
+ this._comboBox.select(this._newAuditItem);
+ }
+
+ /**
+ * @param {!Event} event
+ */
+ _handleChange(event) {
+ const item = this._selectedItem();
+ if (item)
+ item.select();
+ else
+ this._renderNewAuditView();
+ }
+
+ /**
+ * @return {!Audits2.ReportSelector.Item}
+ */
+ _selectedItem() {
+ const option = this._comboBox.selectedOption();
+ return this._itemByOptionElement.get(option);
+ }
+
+ /**
+ * @return {boolean}
+ */
+ hasCurrentSelection() {
+ return !!this._selectedItem();
+ }
+
+ /**
+ * @return {boolean}
+ */
+ hasItems() {
+ return this._itemByOptionElement.size > 0;
+ }
+
+ /**
+ * @return {!UI.ToolbarComboBox}
+ */
+ comboBox() {
+ return this._comboBox;
+ }
+
+ /**
+ * @param {!Audits2.ReportSelector.Item} item
+ */
+ prepend(item) {
+ const optionEl = item.optionElement();
+ const selectEl = this._comboBox.selectElement();
+
+ this._itemByOptionElement.set(optionEl, item);
+ selectEl.insertBefore(optionEl, selectEl.firstElementChild);
+ this._comboBox.setEnabled(true);
+ this._comboBox.select(optionEl);
+ item.select();
+ }
+
+ clearAll() {
+ for (const elem of this._comboBox.options()) {
+ if (elem === this._newAuditItem)
+ continue;
+
+ this._itemByOptionElement.get(elem).delete();
+ this._itemByOptionElement.delete(elem);
+ }
+
+ this._setEmptyState();
+ }
+
+ downloadSelected() {
+ const item = this._selectedItem();
+ if (item)
+ item.download();
+ }
+
+ selectNewAudit() {
+ this._comboBox.select(this._newAuditItem);
+ }
+};
+
+Audits2.ReportSelector.Item = class {
+ /**
+ * @param {!ReportRenderer.ReportJSON} lighthouseResult
+ * @param {function()} renderReport
+ * @param {function()} showLandingCallback
+ */
+ constructor(lighthouseResult, renderReport, showLandingCallback) {
+ this._lighthouseResult = lighthouseResult;
+ this._renderReport = renderReport;
+ this._showLandingCallback = showLandingCallback;
+
+ const url = new Common.ParsedURL(lighthouseResult.url);
+ const timestamp = lighthouseResult.generatedTime;
+ this._element = createElement('option');
+ this._element.label = `${new Date(timestamp).toLocaleTimeString()} - ${url.domain()}`;
+ }
+
+ select() {
+ this._renderReport();
+ }
+
+ /**
+ * @return {!Element}
+ */
+ optionElement() {
+ return this._element;
+ }
+
+ delete() {
+ if (this._element)
+ this._element.remove();
+ this._showLandingCallback();
+ }
+
+ download() {
+ const url = new Common.ParsedURL(this._lighthouseResult.url).domain();
+ const timestamp = this._lighthouseResult.generatedTime;
+ const fileName = `${url}-${new Date(timestamp).toISO8601Compact()}.json`;
+ Workspace.fileManager.save(fileName, JSON.stringify(this._lighthouseResult), true);
+ }
+};
\ No newline at end of file
diff --git a/front_end/audits2/Audits2StartView.js b/front_end/audits2/Audits2StartView.js
new file mode 100644
index 0000000..f62804f
--- /dev/null
+++ b/front_end/audits2/Audits2StartView.js
@@ -0,0 +1,145 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @unrestricted
+ */
+Audits2.StartView = class extends UI.Widget {
+ /**
+ * @param {!Audits2.AuditController} controller
+ */
+ constructor(controller) {
+ super();
+ this.registerRequiredCSS('audits2/audits2StartView.css');
+ this._controller = controller;
+ this._render();
+ }
+
+ /**
+ * @param {string} settingName
+ * @param {!Element} parentElement
+ */
+ _populateRuntimeSettingAsComboBox(settingName, parentElement) {
+ const runtimeSetting = Audits2.RuntimeSettings.find(item => item.setting.name === settingName);
+ const control = new UI.ToolbarSettingComboBox(runtimeSetting.options, runtimeSetting.setting);
+ control.element.title = runtimeSetting.description;
+ parentElement.appendChild(control.element);
+ }
+
+ /**
+ * @param {!UI.Fragment} fragment
+ */
+ _populateFormControls(fragment) {
+ // Populate the device type
+ const deviceTypeFormElements = fragment.$('device-type-form-elements');
+ this._populateRuntimeSettingAsComboBox('audits2.device_type', deviceTypeFormElements);
+
+ // Populate the audit categories
+ const categoryFormElements = fragment.$('categories-form-elements');
+ for (const preset of Audits2.Presets) {
+ preset.setting.setTitle(preset.title);
+ const checkbox = new UI.ToolbarSettingCheckbox(preset.setting);
+ const row = categoryFormElements.createChild('div', 'vbox audits2-launcher-row');
+ row.appendChild(checkbox.element);
+ row.createChild('span', 'audits2-launcher-description dimmed').textContent = preset.description;
+ }
+
+ // Populate the throttling
+ const throttlingFormElements = fragment.$('throttling-form-elements');
+ this._populateRuntimeSettingAsComboBox('audits2.throttling', throttlingFormElements);
+
+
+ // Populate other settings
+ const otherFormElements = fragment.$('other-form-elements');
+ this._populateRuntimeSettingAsComboBox('audits2.storage_reset', otherFormElements);
+ }
+
+ _render() {
+ this._startButton = UI.createTextButton(
+ ls`Run audit`, () => this._controller.dispatchEventToListeners(Audits2.Events.RequestAuditStart),
+ 'audits2-start-button', true /* primary */);
+ const deviceIcon = UI.Icon.create('largeicon-phone');
+ const categoriesIcon = UI.Icon.create('largeicon-checkmark');
+ const throttlingIcon = UI.Icon.create('largeicon-settings-gear');
+
+ const fragment = UI.Fragment.build`
+ <div class="vbox audits2-start-view">
+ <div class="audits2-dialog-overlay"></div>
+ <header>
+ <div class="audits2-logo"></div>
+ <div class="audits2-start-view-text">
+ <h2>Audits</h2>
+ <p>
+ Identify and fix common problems that affect your site's performance, accessibility, and user experience.
+ <span class="link" $="learn-more">Learn more.</a>
+ </p>
+ </div>
+ </header>
+ <form>
+ <div class="audits2-form-section">
+ <div class="audits2-form-section-label">
+ <i>${deviceIcon}</i>
+ <div class="audits2-icon-label">Device</div>
+ </div>
+ <div class="audits2-form-elements" $="device-type-form-elements"></div>
+ </div>
+ <div class="audits2-form-section">
+ <div class="audits2-form-section-label">
+ <i>${categoriesIcon}</i>
+ <div class="audits2-icon-label">Audits</div>
+ </div>
+ <div class="audits2-form-elements" $="categories-form-elements"></div>
+ </div>
+ <div class="audits2-form-section">
+ <div class="audits2-form-section-label">
+ <i>${throttlingIcon}</i>
+ <div class="audits2-icon-label">Throttling</div>
+ </div>
+ <div class="audits2-form-elements" $="throttling-form-elements"></div>
+ </div>
+ <div class="audits2-form-section">
+ <div class="audits2-form-section-label"></div>
+ <div class="audits2-form-elements" $="other-form-elements"></div>
+ </div>
+ <div class="audits2-form-section">
+ <div class="audits2-form-section-label"></div>
+ <div class="audits2-form-elements audits2-start-button-container hbox">
+ ${this._startButton}
+ <div $="help-text" class="audits2-help-text hidden"></div>
+ </div>
+ </div>
+ </form>
+ </div>
+ `;
+
+ this._helpText = fragment.$('help-text');
+
+ const learnMoreLink = fragment.$('learn-more');
+ learnMoreLink.addEventListener(
+ 'click', () => InspectorFrontendHost.openInNewTab('https://developers.google.com/web/tools/lighthouse/'));
+
+ this._populateFormControls(fragment);
+ this.contentElement.appendChild(fragment.element());
+ this.contentElement.style.overflow = 'auto';
+ }
+
+ /**
+ * @param {boolean} isEnabled
+ */
+ setStartButtonEnabled(isEnabled) {
+ if (this._helpText)
+ this._helpText.classList.toggle('hidden', isEnabled);
+
+ if (this._startButton)
+ this._startButton.disabled = !isEnabled;
+ }
+
+ /**
+ * @param {?string} text
+ */
+ setUnauditableExplanation(text) {
+ if (this._helpText)
+ this._helpText.textContent = text;
+ }
+};
diff --git a/front_end/audits2/Audits2StatusView.js b/front_end/audits2/Audits2StatusView.js
index 64e2e0d..98cfc67 100644
--- a/front_end/audits2/Audits2StatusView.js
+++ b/front_end/audits2/Audits2StatusView.js
@@ -2,53 +2,98 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-Audits2.Audits2StatusView = class {
- constructor() {
+Audits2.StatusView = class {
+ /**
+ * @param {!Audits2.AuditController} controller
+ */
+ constructor(controller) {
+ this._controller = controller;
+
this._statusView = null;
+ this._statusHeader = null;
this._progressWrapper = null;
this._progressBar = null;
this._statusText = null;
+ this._inspectedURL = '';
this._textChangedAt = 0;
- this._fastFactsQueued = Audits2.Audits2StatusView.FastFacts.slice();
+ this._fastFactsQueued = Audits2.StatusView.FastFacts.slice();
this._currentPhase = null;
this._scheduledTextChangeTimeout = null;
this._scheduledFastFactTimeout = null;
+
+ this._dialog = new UI.Dialog();
+ this._dialog.setOutsideClickCallback(event => event.consume(true));
+ this._render();
}
- /**
- * @param {!Element} parentElement
- */
- render(parentElement) {
- this.reset();
+ _render() {
+ const dialogRoot = UI.createShadowRootWithCoreStyles(this._dialog.contentElement, 'audits2/audits2Dialog.css');
+ const auditsViewElement = dialogRoot.createChild('div', 'audits2-view vbox');
- this._statusView = parentElement.createChild('div', 'audits2-status vbox hidden');
- this._progressWrapper = this._statusView.createChild('div', 'audits2-progress-wrapper');
- this._progressBar = this._progressWrapper.createChild('div', 'audits2-progress-bar');
- this._statusText = this._statusView.createChild('div', 'audits2-status-text');
+ const cancelButton = UI.createTextButton(ls`Cancel`, this._cancel.bind(this));
+ const fragment = UI.Fragment.build`
+ <div class="audits2-view vbox">
+ <h2 $="status-header">Auditing your web page\u2026</h2>
+ <div class="audits2-status vbox" $="status-view">
+ <div class="audits2-progress-wrapper" $="progress-wrapper">
+ <div class="audits2-progress-bar" $="progress-bar"></div>
+ </div>
+ <div class="audits2-status-text" $="status-text"></div>
+ </div>
+ ${cancelButton}
+ </div>
+ `;
- this.updateStatus(Common.UIString('Loading...'));
+ auditsViewElement.appendChild(fragment.element());
+ auditsViewElement.tabIndex = 0;
+
+ this._statusView = fragment.$('status-view');
+ this._statusHeader = fragment.$('status-header');
+ this._progressWrapper = fragment.$('progress-wrapper');
+ this._progressBar = fragment.$('progress-bar');
+ this._statusText = fragment.$('status-text');
+
+ this._dialog.setDefaultFocusedElement(cancelButton);
+ this._dialog.setSizeBehavior(UI.GlassPane.SizeBehavior.SetExactWidthMaxHeight);
+ this._dialog.setMaxContentSize(new UI.Size(500, 400));
}
- reset() {
+ _reset() {
this._resetProgressBarClasses();
clearTimeout(this._scheduledFastFactTimeout);
this._textChangedAt = 0;
- this._fastFactsQueued = Audits2.Audits2StatusView.FastFacts.slice();
+ this._fastFactsQueued = Audits2.StatusView.FastFacts.slice();
this._currentPhase = null;
this._scheduledTextChangeTimeout = null;
this._scheduledFastFactTimeout = null;
}
/**
- * @param {boolean} isVisible
+ * @param {!Element} dialogRenderElement
*/
- setVisible(isVisible) {
- this._statusView.classList.toggle('hidden', !isVisible);
+ show(dialogRenderElement) {
+ this._reset();
+ this.updateStatus(ls`Loading\u2026`);
- if (!isVisible)
- clearTimeout(this._scheduledFastFactTimeout);
+ const parsedURL = this._inspectedURL.asParsedURL();
+ const pageHost = parsedURL && parsedURL.host;
+ const statusHeader = pageHost ? ls`Auditing ${pageHost}` : ls`Auditing your web page`;
+ this._statusHeader.textContent = `${statusHeader}\u2026`;
+ this._dialog.show(dialogRenderElement);
+ }
+
+ hide() {
+ if (this._dialog.isShowing())
+ this._dialog.hide();
+ }
+
+ /**
+ * @param {string=} url
+ */
+ setInspectedURL(url = '') {
+ this._inspectedURL = url;
}
/**
@@ -77,19 +122,21 @@
}
}
+ _cancel() {
+ this._controller.dispatchEventToListeners(Audits2.Events.RequestAuditCancel);
+ }
+
/**
- * @param {!Audits2.Audits2StatusView.StatusPhases} phase
+ * @param {!Audits2.StatusView.StatusPhases} phase
* @return {string}
*/
_getMessageForPhase(phase) {
if (phase.message)
return Common.UIString(phase.message);
- const deviceType =
- Audits2.Audits2Panel.RuntimeSettings.find(item => item.setting.name === 'audits2.device_type').setting.get();
- const throttling =
- Audits2.Audits2Panel.RuntimeSettings.find(item => item.setting.name === 'audits2.throttling').setting.get();
- const match = Audits2.Audits2StatusView.LoadingMessages.find(item => {
+ const deviceType = Audits2.RuntimeSettings.find(item => item.setting.name === 'audits2.device_type').setting.get();
+ const throttling = Audits2.RuntimeSettings.find(item => item.setting.name === 'audits2.throttling').setting.get();
+ const match = Audits2.StatusView.LoadingMessages.find(item => {
return item.deviceType === deviceType && item.throttling === throttling;
});
@@ -98,10 +145,10 @@
/**
* @param {string} message
- * @return {?Audits2.Audits2StatusView.StatusPhases}
+ * @return {?Audits2.StatusView.StatusPhases}
*/
_getPhaseForMessage(message) {
- return Audits2.Audits2StatusView.StatusPhases.find(phase => message.startsWith(phase.statusMessagePrefix));
+ return Audits2.StatusView.StatusPhases.find(phase => message.startsWith(phase.statusMessagePrefix));
}
_resetProgressBarClasses() {
@@ -125,7 +172,7 @@
_updateFastFactIfNecessary() {
const now = performance.now();
- if (now - this._textChangedAt < Audits2.Audits2StatusView.fastFactRotationInterval)
+ if (now - this._textChangedAt < Audits2.StatusView.fastFactRotationInterval)
return;
if (!this._fastFactsQueued.length)
return;
@@ -153,7 +200,7 @@
clearTimeout(this._scheduledTextChangeTimeout);
const msSinceLastChange = performance.now() - this._textChangedAt;
- const msToTextChange = Audits2.Audits2StatusView.minimumTextVisibilityDuration - msSinceLastChange;
+ const msToTextChange = Audits2.StatusView.minimumTextVisibilityDuration - msSinceLastChange;
this._scheduledTextChangeTimeout = setTimeout(() => {
this._commitTextChange(text);
@@ -162,9 +209,8 @@
/**
* @param {!Error} err
- * @param {string} auditURL
*/
- renderBugReport(err, auditURL) {
+ renderBugReport(err) {
console.error(err);
clearTimeout(this._scheduledFastFactTimeout);
clearTimeout(this._scheduledTextChangeTimeout);
@@ -174,13 +220,13 @@
this._commitTextChange('');
this._statusText.createTextChild(Common.UIString('Ah, sorry! We ran into an error: '));
this._statusText.createChild('em').createTextChild(err.message);
- if (Audits2.Audits2StatusView.KnownBugPatterns.some(pattern => pattern.test(err.message))) {
+ if (Audits2.StatusView.KnownBugPatterns.some(pattern => pattern.test(err.message))) {
const message = Common.UIString(
'Try to navigate to the URL in a fresh Chrome profile without any other tabs or ' +
'extensions open and try again.');
this._statusText.createChild('p').createTextChild(message);
} else {
- this._renderBugReportLink(err, auditURL);
+ this._renderBugReportLink(err, this._inspectedURL);
}
}
@@ -210,7 +256,7 @@
/** @type {!Array.<!RegExp>} */
-Audits2.Audits2StatusView.KnownBugPatterns = [
+Audits2.StatusView.KnownBugPatterns = [
/PARSING_PROBLEM/,
/DOCUMENT_REQUEST/,
/READ_FAILED/,
@@ -220,7 +266,7 @@
];
/** @typedef {{message: string, progressBarClass: string, order: number}} */
-Audits2.Audits2StatusView.StatusPhases = [
+Audits2.StatusView.StatusPhases = [
{
id: 'loading',
progressBarClass: 'loading',
@@ -244,7 +290,7 @@
];
/** @typedef {{message: string, deviceType: string, throttling: string}} */
-Audits2.Audits2StatusView.LoadingMessages = [
+Audits2.StatusView.LoadingMessages = [
{
deviceType: 'mobile',
throttling: 'on',
@@ -267,7 +313,7 @@
},
];
-Audits2.Audits2StatusView.FastFacts = [
+Audits2.StatusView.FastFacts = [
'1MB takes a minimum of 5 seconds to download on a typical 3G connection [Source: WebPageTest and DevTools 3G definition].',
'Rebuilding Pinterest pages for performance increased conversion rates by 15% [Source: WPO Stats]',
'BBC has seen a loss of 10% of their users for every extra second of page load [Source: WPO Stats]',
@@ -287,6 +333,6 @@
];
/** @const */
-Audits2.Audits2StatusView.fastFactRotationInterval = 6000;
+Audits2.StatusView.fastFactRotationInterval = 6000;
/** @const */
-Audits2.Audits2StatusView.minimumTextVisibilityDuration = 3000;
+Audits2.StatusView.minimumTextVisibilityDuration = 3000;
diff --git a/front_end/audits2/audits2Dialog.css b/front_end/audits2/audits2Dialog.css
index ae4b406..d5468b0 100644
--- a/front_end/audits2/audits2Dialog.css
+++ b/front_end/audits2/audits2Dialog.css
@@ -13,37 +13,17 @@
max-width: 500px;
}
-.launcher-container {
- width: 100%;
-}
-
-.audits2-view .dialog-close-button {
- position: absolute;
- top: 0px;
- right: -14px;
-}
-
-header {
+.audits2-view h2 {
color: #666;
font-weight: bold;
font-size: 14px;
flex: none;
-}
-
-.audits2-form {
- flex: auto;
- overflow-y: auto;
-}
-
-.audits2-form label {
- display: flex;
-}
-
-.audits2-form label div {
- display: inline;
+ width: 100%;
+ text-align: left;
}
.audits2-status {
+ width: 100%;
flex: auto;
align-items: center;
color: #666;
@@ -119,25 +99,3 @@
display: block;
margin-top: 5px;
}
-
-button {
- margin: 15px 10px;
-}
-
-.audits2-launcher-row {
- padding: 6px;
-}
-
-.audits2-launcher-description {
- padding: 3px 0 0 22px;
-}
-
-.audits2-dialog-help-text {
- color: red;
- margin-top: 10px;
-}
-
-.audits2-dialog-buttons {
- justify-content: center;
- min-height: 40px;
-}
diff --git a/front_end/audits2/audits2Panel.css b/front_end/audits2/audits2Panel.css
index 22f9278..01305bb 100644
--- a/front_end/audits2/audits2Panel.css
+++ b/front_end/audits2/audits2Panel.css
@@ -9,44 +9,6 @@
border-bottom: var(--divider-border);
}
-.audits2-logo {
- width: 210px;
- height: 200px;
- flex-shrink: 0;
- background-repeat: no-repeat;
- background-size: contain;
- margin-top: 10px;
- background-image: url(Images/audits_logo.svg);
-}
-
-.audits2-landing-page {
- display: flex;
- align-items: center;
- justify-content: center;
- overflow: auto;
-}
-
-
-.audits2-landing-center {
- display: flex;
- align-items: center;
- justify-content: center;
- max-width: 400px;
- margin: 50px;
-}
-
-.audits2-landing-center > * {
- flex-shrink: 0;
-}
-
-.audits2-landing-text {
- color: #666;
-}
-
-.audits2-landing-bold-text {
- font-weight: bold;
-}
-
.lh-root {
--report-menu-width: 0;
user-select: initial;
@@ -82,10 +44,6 @@
right: 0;
}
-.audits2-landing-center button {
- margin-top: 20px;
-}
-
.audits2-results-container {
overflow-y: scroll;
position: relative;
diff --git a/front_end/audits2/audits2StartView.css b/front_end/audits2/audits2StartView.css
new file mode 100644
index 0000000..3d43ca9
--- /dev/null
+++ b/front_end/audits2/audits2StartView.css
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2018 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+.audits2-start-view {
+ font-family: Roboto, sans-serif;
+}
+
+.audits2-dialog-overlay {
+ position: fixed;
+ width: 100%;
+ height: 100%;
+ display: none;
+ background: rgba(0,0,0,.3);
+ z-index: 100;
+}
+
+.in-progress .audits2-dialog-overlay {
+ display: block;
+}
+
+.audits2-start-view header {
+ padding: 10px;
+ display: flex;
+}
+
+.audits2-logo {
+ width: 150px;
+ height: 150px;
+ flex-shrink: 0;
+ background-repeat: no-repeat;
+ background-size: contain;
+ margin-right: 18px;
+ background-image: url(Images/audits_logo.svg);
+}
+
+.audits2-start-view-text {
+ color: #757575;
+ margin-top: 24px;
+}
+
+.audits2-start-view-text h2 {
+ color: black;
+ font-weight: normal;
+}
+
+.audits2-form-section {
+ border-top: 1px solid #e8e8e8;
+ display: flex;
+ padding: 20px;
+}
+
+.audits2-form-section-label {
+ flex: 1;
+ display: flex;
+}
+
+.audits2-form-section-label i {
+ width: 30px;
+ height: 30px;
+ border-radius: 50%;
+ background: #e6e6e6;
+ display: block;
+ text-align: center;
+ padding-top: 3px;
+}
+
+.audits2-icon-label {
+ margin: 7px;
+ font-size: 1.2em;
+}
+
+.audits2-form-section-label span.largeicon-checkmark {
+ transform: scale(1.3);
+ position: relative;
+ right: -2px;
+}
+
+.audits2-form-elements {
+ flex: 3;
+}
+
+.audits2-start-button-container {
+ align-items: center;
+}
+
+.audits2-start-button {
+ max-width: 100px;
+}
+
+.audits2-start-view .toolbar-dropdown-arrow {
+ display: none;
+}
+
+.audits2-launcher-row {
+ padding-bottom: 10px;
+}
+
+.audits2-launcher-row:last-of-type {
+ padding-bottom: 0;
+}
+
+.audits2-launcher-row .dimmed {
+ padding-left: 22px;
+}
+
+.audits2-help-text {
+ color: red;
+ font-weight: bold;
+ padding-left: 10px;
+}
\ No newline at end of file
diff --git a/front_end/audits2/module.json b/front_end/audits2/module.json
index 2ec939f..49fb323 100644
--- a/front_end/audits2/module.json
+++ b/front_end/audits2/module.json
@@ -26,12 +26,16 @@
"lighthouse/renderer/crc-details-renderer.js",
"lighthouse/renderer/report-renderer.js",
"Audits2Panel.js",
- "Audits2Dialog.js",
+ "Audits2Controller.js",
+ "Audits2ReportSelector.js",
+ "Audits2ReportRenderer.js",
+ "Audits2StartView.js",
"Audits2StatusView.js",
"Audits2ProtocolService.js"
],
"resources": [
"audits2Dialog.css",
+ "audits2StartView.css",
"audits2Panel.css",
"lighthouse/report-styles.css",
"lighthouse/templates.html"
diff --git a/front_end/audits2_test_runner/Audits2TestRunner.js b/front_end/audits2_test_runner/Audits2TestRunner.js
index 4d0261b..4dbddf6 100644
--- a/front_end/audits2_test_runner/Audits2TestRunner.js
+++ b/front_end/audits2_test_runner/Audits2TestRunner.js
@@ -17,6 +17,13 @@
/**
* @return {?Element}
*/
+Audits2TestRunner.getContainerElement = function() {
+ return Audits2TestRunner._panel().contentElement;
+};
+
+/**
+ * @return {?Element}
+ */
Audits2TestRunner.getResultsElement = function() {
return Audits2TestRunner._panel()._auditResultsElement;
};
@@ -25,14 +32,14 @@
* @return {?Element}
*/
Audits2TestRunner.getDialogElement = function() {
- return Audits2TestRunner._panel()._dialog._dialog.contentElement.shadowRoot.querySelector('.audits2-view');
+ return Audits2TestRunner._panel()._statusView._dialog.contentElement.shadowRoot.querySelector('.audits2-view');
};
/**
* @return {?Element}
*/
Audits2TestRunner.getRunButton = function() {
- const dialog = Audits2TestRunner.getDialogElement();
+ const dialog = Audits2TestRunner.getContainerElement();
return dialog && dialog.querySelectorAll('button')[0];
};
@@ -41,19 +48,18 @@
*/
Audits2TestRunner.getCancelButton = function() {
const dialog = Audits2TestRunner.getDialogElement();
- return dialog && dialog.querySelectorAll('button')[1];
+ return dialog && dialog.querySelectorAll('button')[0];
};
-Audits2TestRunner.openDialog = function() {
- const resultsElement = Audits2TestRunner.getResultsElement();
- resultsElement.querySelector('button').click();
+Audits2TestRunner.openStartAudit = function() {
+ Audits2TestRunner._panel()._renderStartView();
};
/**
* @param {function(string)} onMessage
*/
Audits2TestRunner.addStatusListener = function(onMessage) {
- TestRunner.addSniffer(Audits2.Audits2Dialog.prototype, '_updateStatus', onMessage, true);
+ TestRunner.addSniffer(Audits2.StatusView.prototype, 'updateStatus', onMessage, true);
};
/**
@@ -65,6 +71,10 @@
});
};
+Audits2TestRunner.forcePageAuditabilityCheck = function() {
+ Audits2TestRunner._panel()._controller.recomputePageAuditability();
+};
+
/**
* @param {?Element} checkboxContainer
* @return {string}
@@ -91,23 +101,18 @@
return `${button.textContent}: ${enabledLabel} ${hiddenLabel}`;
};
-Audits2TestRunner.dumpDialogState = function() {
- TestRunner.addResult('\n========== Audits2 Dialog State ==========');
- const dialog = Audits2TestRunner._panel()._dialog && Audits2TestRunner._panel()._dialog._dialog;
- TestRunner.addResult(dialog ? 'Dialog is visible\n' : 'No dialog');
- if (!dialog)
- return;
+Audits2TestRunner.dumpStartAuditState = function() {
+ TestRunner.addResult('\n========== Audits2 Start Audit State ==========');
- const dialogElement = Audits2TestRunner.getDialogElement();
- const checkboxes = [...dialogElement.querySelectorAll('.checkbox')];
+ const containerElement = Audits2TestRunner.getContainerElement();
+ const checkboxes = [...containerElement.querySelectorAll('.checkbox')];
checkboxes.forEach(element => {
TestRunner.addResult(Audits2TestRunner._checkboxStateLabel(element));
});
- const helpText = dialogElement.querySelector('.audits2-dialog-help-text');
+ const helpText = containerElement.querySelector('.audits2-help-text');
if (!helpText.classList.contains('hidden'))
TestRunner.addResult(`Help text: ${helpText.textContent}`);
TestRunner.addResult(Audits2TestRunner._buttonStateLabel(Audits2TestRunner.getRunButton()));
- TestRunner.addResult(Audits2TestRunner._buttonStateLabel(Audits2TestRunner.getCancelButton()) + '\n');
-};
\ No newline at end of file
+};