blob: 2f0be02d34d3b7969c2d299ff97574975f4710d9 [file] [log] [blame]
Patrick Hulcea087f622018-05-18 00:37:53 +00001// Copyright 2018 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/**
6 * @implements {SDK.SDKModelObserver<!SDK.ServiceWorkerManager>}
7 * @unrestricted
8 */
cjamcl@google.comaa1532c2019-05-31 03:01:24 +00009Audits.AuditController = class extends Common.Object {
Patrick Hulcea087f622018-05-18 00:37:53 +000010 constructor(protocolService) {
11 super();
12
13 protocolService.registerStatusCallback(
cjamcl@google.comaa1532c2019-05-31 03:01:24 +000014 message => this.dispatchEventToListeners(Audits.Events.AuditProgressChanged, {message}));
Patrick Hulcea087f622018-05-18 00:37:53 +000015
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000016 for (const preset of Audits.Presets) {
Patrick Hulcea087f622018-05-18 00:37:53 +000017 preset.setting.addChangeListener(this.recomputePageAuditability.bind(this));
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000018 }
Patrick Hulcea087f622018-05-18 00:37:53 +000019
20 SDK.targetManager.observeModels(SDK.ServiceWorkerManager, this);
21 SDK.targetManager.addEventListener(
22 SDK.TargetManager.Events.InspectedURLChanged, this.recomputePageAuditability, this);
23 }
24
25 /**
26 * @override
27 * @param {!SDK.ServiceWorkerManager} serviceWorkerManager
28 */
29 modelAdded(serviceWorkerManager) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000030 if (this._manager) {
Patrick Hulcea087f622018-05-18 00:37:53 +000031 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000032 }
Patrick Hulcea087f622018-05-18 00:37:53 +000033
34 this._manager = serviceWorkerManager;
35 this._serviceWorkerListeners = [
36 this._manager.addEventListener(
37 SDK.ServiceWorkerManager.Events.RegistrationUpdated, this.recomputePageAuditability, this),
38 this._manager.addEventListener(
39 SDK.ServiceWorkerManager.Events.RegistrationDeleted, this.recomputePageAuditability, this),
40 ];
41
42 this.recomputePageAuditability();
43 }
44
45 /**
46 * @override
47 * @param {!SDK.ServiceWorkerManager} serviceWorkerManager
48 */
49 modelRemoved(serviceWorkerManager) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000050 if (this._manager !== serviceWorkerManager) {
Patrick Hulcea087f622018-05-18 00:37:53 +000051 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000052 }
Patrick Hulcea087f622018-05-18 00:37:53 +000053
54 Common.EventTarget.removeEventListeners(this._serviceWorkerListeners);
55 this._manager = null;
56 this.recomputePageAuditability();
57 }
58
59 /**
60 * @return {boolean}
61 */
62 _hasActiveServiceWorker() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000063 if (!this._manager) {
Patrick Hulcea087f622018-05-18 00:37:53 +000064 return false;
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000065 }
Patrick Hulcea087f622018-05-18 00:37:53 +000066
67 const mainTarget = this._manager.target();
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000068 if (!mainTarget) {
Patrick Hulcea087f622018-05-18 00:37:53 +000069 return false;
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000070 }
Patrick Hulcea087f622018-05-18 00:37:53 +000071
72 const inspectedURL = mainTarget.inspectedURL().asParsedURL();
73 const inspectedOrigin = inspectedURL && inspectedURL.securityOrigin();
74 for (const registration of this._manager.registrations().values()) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000075 if (registration.securityOrigin !== inspectedOrigin) {
Patrick Hulcea087f622018-05-18 00:37:53 +000076 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000077 }
Patrick Hulcea087f622018-05-18 00:37:53 +000078
79 for (const version of registration.versions.values()) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000080 if (version.controlledClients.length > 1) {
Patrick Hulcea087f622018-05-18 00:37:53 +000081 return true;
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000082 }
Patrick Hulcea087f622018-05-18 00:37:53 +000083 }
84 }
85
86 return false;
87 }
88
89 /**
90 * @return {boolean}
91 */
92 _hasAtLeastOneCategory() {
cjamcl@google.comaa1532c2019-05-31 03:01:24 +000093 return Audits.Presets.some(preset => preset.setting.get());
Patrick Hulcea087f622018-05-18 00:37:53 +000094 }
95
96 /**
97 * @return {?string}
98 */
99 _unauditablePageMessage() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000100 if (!this._manager) {
Patrick Hulcea087f622018-05-18 00:37:53 +0000101 return null;
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000102 }
Patrick Hulcea087f622018-05-18 00:37:53 +0000103
104 const mainTarget = this._manager.target();
105 const inspectedURL = mainTarget && mainTarget.inspectedURL();
106 if (inspectedURL && !/^(http|chrome-extension)/.test(inspectedURL)) {
107 return Common.UIString(
Lorne Mitchell7aa2c6c2019-04-03 03:50:10 +0000108 'Can only audit HTTP/HTTPS pages and Chrome extensions. Navigate to a different page to start an audit.');
Patrick Hulcea087f622018-05-18 00:37:53 +0000109 }
110
Patrick Hulcea087f622018-05-18 00:37:53 +0000111 return null;
112 }
113
114 /**
115 * @return {!Promise<string>}
116 */
117 async _evaluateInspectedURL() {
118 const mainTarget = this._manager.target();
119 const runtimeModel = mainTarget.model(SDK.RuntimeModel);
120 const executionContext = runtimeModel && runtimeModel.defaultExecutionContext();
121 let inspectedURL = mainTarget.inspectedURL();
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000122 if (!executionContext) {
Patrick Hulcea087f622018-05-18 00:37:53 +0000123 return inspectedURL;
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000124 }
Patrick Hulcea087f622018-05-18 00:37:53 +0000125
126 // Evaluate location.href for a more specific URL than inspectedURL provides so that SPA hash navigation routes
127 // will be respected and audited.
128 try {
129 const result = await executionContext.evaluate(
130 {
131 expression: 'window.location.href',
132 objectGroup: 'audits',
133 includeCommandLineAPI: false,
134 silent: false,
135 returnByValue: true,
136 generatePreview: false
137 },
138 /* userGesture */ false, /* awaitPromise */ false);
139 if (!result.exceptionDetails && result.object) {
140 inspectedURL = result.object.value;
141 result.object.release();
142 }
143 } catch (err) {
144 console.error(err);
145 }
146
147 return inspectedURL;
148 }
149
150 /**
151 * @return {!Object}
152 */
153 getFlags() {
Connor Clark3f700342019-07-25 02:10:41 +0000154 const flags = {
155 // DevTools handles all the emulation. This tells Lighthouse to not bother with emulation.
Connor Clark3ee5ac72019-11-07 15:11:58 -0800156 internalDisableDeviceScreenEmulation: true
Connor Clark3f700342019-07-25 02:10:41 +0000157 };
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000158 for (const runtimeSetting of Audits.RuntimeSettings) {
Patrick Hulcea087f622018-05-18 00:37:53 +0000159 runtimeSetting.setFlags(flags, runtimeSetting.setting.get());
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000160 }
Patrick Hulcea087f622018-05-18 00:37:53 +0000161 return flags;
162 }
163
164 /**
165 * @return {!Array<string>}
166 */
167 getCategoryIDs() {
168 const categoryIDs = [];
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000169 for (const preset of Audits.Presets) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000170 if (preset.setting.get()) {
Patrick Hulcea087f622018-05-18 00:37:53 +0000171 categoryIDs.push(preset.configID);
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000172 }
Patrick Hulcea087f622018-05-18 00:37:53 +0000173 }
174 return categoryIDs;
175 }
176
177 /**
178 * @param {{force: boolean}=} options
179 * @return {!Promise<string>}
180 */
181 async getInspectedURL(options) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000182 if (options && options.force || !this._inspectedURL) {
Patrick Hulcea087f622018-05-18 00:37:53 +0000183 this._inspectedURL = await this._evaluateInspectedURL();
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000184 }
Patrick Hulcea087f622018-05-18 00:37:53 +0000185 return this._inspectedURL;
186 }
187
188 recomputePageAuditability() {
189 const hasActiveServiceWorker = this._hasActiveServiceWorker();
190 const hasAtLeastOneCategory = this._hasAtLeastOneCategory();
191 const unauditablePageMessage = this._unauditablePageMessage();
192
193 let helpText = '';
194 if (hasActiveServiceWorker) {
195 helpText = Common.UIString(
Lorne Mitchell7aa2c6c2019-04-03 03:50:10 +0000196 'Multiple tabs are being controlled by the same service worker. Close your other tabs on the same origin to audit this page.');
Patrick Hulcea087f622018-05-18 00:37:53 +0000197 } else if (!hasAtLeastOneCategory) {
198 helpText = Common.UIString('At least one category must be selected.');
199 } else if (unauditablePageMessage) {
200 helpText = unauditablePageMessage;
201 }
202
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000203 this.dispatchEventToListeners(Audits.Events.PageAuditabilityChanged, {helpText});
Patrick Hulcea087f622018-05-18 00:37:53 +0000204 }
205};
206
207
Patrick Hulce05c18ce2018-05-24 00:34:56 +0000208/** @typedef {{setting: !Common.Setting, configID: string, title: string, description: string}} */
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000209Audits.Preset;
Patrick Hulcea087f622018-05-18 00:37:53 +0000210
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000211/** @type {!Array.<!Audits.Preset>} */
212Audits.Presets = [
Patrick Hulcea087f622018-05-18 00:37:53 +0000213 // configID maps to Lighthouse's Object.keys(config.categories)[0] value
214 {
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000215 setting: Common.settings.createSetting('audits.cat_perf', true),
Patrick Hulcea087f622018-05-18 00:37:53 +0000216 configID: 'performance',
Christy Chen38dccb52019-05-08 22:32:15 +0000217 title: ls`Performance`,
218 description: ls`How long does this app take to show content and become usable`
Patrick Hulcea087f622018-05-18 00:37:53 +0000219 },
220 {
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000221 setting: Common.settings.createSetting('audits.cat_pwa', true),
Patrick Hulcea087f622018-05-18 00:37:53 +0000222 configID: 'pwa',
Christy Chen38dccb52019-05-08 22:32:15 +0000223 title: ls`Progressive Web App`,
224 description: ls`Does this page meet the standard of a Progressive Web App`
Patrick Hulcea087f622018-05-18 00:37:53 +0000225 },
226 {
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000227 setting: Common.settings.createSetting('audits.cat_best_practices', true),
Patrick Hulcea087f622018-05-18 00:37:53 +0000228 configID: 'best-practices',
Christy Chen38dccb52019-05-08 22:32:15 +0000229 title: ls`Best practices`,
230 description: ls`Does this page follow best practices for modern web development`
Patrick Hulcea087f622018-05-18 00:37:53 +0000231 },
232 {
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000233 setting: Common.settings.createSetting('audits.cat_a11y', true),
Patrick Hulcea087f622018-05-18 00:37:53 +0000234 configID: 'accessibility',
Christy Chen38dccb52019-05-08 22:32:15 +0000235 title: ls`Accessibility`,
236 description: ls`Is this page usable by people with disabilities or impairments`
Patrick Hulcea087f622018-05-18 00:37:53 +0000237 },
238 {
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000239 setting: Common.settings.createSetting('audits.cat_seo', true),
Patrick Hulcea087f622018-05-18 00:37:53 +0000240 configID: 'seo',
Christy Chen38dccb52019-05-08 22:32:15 +0000241 title: ls`SEO`,
242 description: ls`Is this page optimized for search engine results ranking`
Patrick Hulcea087f622018-05-18 00:37:53 +0000243 },
Connor Clark3ee5ac72019-11-07 15:11:58 -0800244 {
245 setting: Common.settings.createSetting('audits.cat_pubads', false),
246 plugin: true,
247 configID: 'lighthouse-plugin-publisher-ads',
248 title: ls`Publisher Ads`,
249 description: ls`Is this page optimized for ad speed and quality`
250 },
Patrick Hulcea087f622018-05-18 00:37:53 +0000251];
252
Patrick Hulce05c18ce2018-05-24 00:34:56 +0000253/** @typedef {{setting: !Common.Setting, description: string, setFlags: function(!Object, string), options: (!Array|undefined), title: (string|undefined)}} */
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000254Audits.RuntimeSetting;
Patrick Hulcea087f622018-05-18 00:37:53 +0000255
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000256/** @type {!Array.<!Audits.RuntimeSetting>} */
257Audits.RuntimeSettings = [
Patrick Hulcea087f622018-05-18 00:37:53 +0000258 {
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000259 setting: Common.settings.createSetting('audits.device_type', 'mobile'),
Patrick Hulce05c18ce2018-05-24 00:34:56 +0000260 description: ls`Apply mobile emulation during auditing`,
Patrick Hulcea087f622018-05-18 00:37:53 +0000261 setFlags: (flags, value) => {
Paul Irishd8495012019-07-16 23:51:47 +0000262 // See Audits.AuditsPanel._setupEmulationAndProtocolConnection()
Connor Clark3f700342019-07-25 02:10:41 +0000263 flags.emulatedFormFactor = value;
Patrick Hulcea087f622018-05-18 00:37:53 +0000264 },
265 options: [
Patrick Hulce05c18ce2018-05-24 00:34:56 +0000266 {label: ls`Mobile`, value: 'mobile'},
267 {label: ls`Desktop`, value: 'desktop'},
Patrick Hulcea087f622018-05-18 00:37:53 +0000268 ],
269 },
270 {
Connor Clarke66080e2019-11-06 16:35:51 -0800271 // This setting is disabled, but we keep it around to show in the UI.
272 setting: Common.settings.createSetting('audits.throttling', true),
273 title: ls`Simulated throttling`,
274 // We will disable this when we have a Lantern trace viewer within DevTools.
275 learnMore:
276 'https://github.com/GoogleChrome/lighthouse/blob/master/docs/throttling.md#devtools-audits-panel-throttling',
Patrick Hulcea087f622018-05-18 00:37:53 +0000277 setFlags: (flags, value) => {
Connor Clarke66080e2019-11-06 16:35:51 -0800278 flags.throttlingMethod = value ? 'simulate' : 'devtools';
Patrick Hulcea087f622018-05-18 00:37:53 +0000279 },
Patrick Hulcea087f622018-05-18 00:37:53 +0000280 },
281 {
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000282 setting: Common.settings.createSetting('audits.clear_storage', true),
Patrick Hulce05c18ce2018-05-24 00:34:56 +0000283 title: ls`Clear storage`,
Paul Irish8f1e33d2018-05-31 02:29:50 +0000284 description: ls`Reset storage (localStorage, IndexedDB, etc) before auditing. (Good for performance & PWA testing)`,
Patrick Hulcea087f622018-05-18 00:37:53 +0000285 setFlags: (flags, value) => {
Patrick Hulce05c18ce2018-05-24 00:34:56 +0000286 flags.disableStorageReset = !value;
Patrick Hulcea087f622018-05-18 00:37:53 +0000287 },
Patrick Hulcea087f622018-05-18 00:37:53 +0000288 },
289];
290
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000291Audits.Events = {
Trent Aptedba184a62018-05-25 02:13:48 +0000292 PageAuditabilityChanged: Symbol('PageAuditabilityChanged'),
293 AuditProgressChanged: Symbol('AuditProgressChanged'),
294 RequestAuditStart: Symbol('RequestAuditStart'),
295 RequestAuditCancel: Symbol('RequestAuditCancel'),
Paul Irish8f1e33d2018-05-31 02:29:50 +0000296};