blob: 1ab901a13e9499635f2fa42d57badf4ee2c6f709 [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
cjamcl@google.comaa1532c2019-05-31 03:01:24 +000016 for (const preset of Audits.Presets)
Patrick Hulcea087f622018-05-18 00:37:53 +000017 preset.setting.addChangeListener(this.recomputePageAuditability.bind(this));
18
19 SDK.targetManager.observeModels(SDK.ServiceWorkerManager, this);
20 SDK.targetManager.addEventListener(
21 SDK.TargetManager.Events.InspectedURLChanged, this.recomputePageAuditability, this);
22 }
23
24 /**
25 * @override
26 * @param {!SDK.ServiceWorkerManager} serviceWorkerManager
27 */
28 modelAdded(serviceWorkerManager) {
29 if (this._manager)
30 return;
31
32 this._manager = serviceWorkerManager;
33 this._serviceWorkerListeners = [
34 this._manager.addEventListener(
35 SDK.ServiceWorkerManager.Events.RegistrationUpdated, this.recomputePageAuditability, this),
36 this._manager.addEventListener(
37 SDK.ServiceWorkerManager.Events.RegistrationDeleted, this.recomputePageAuditability, this),
38 ];
39
40 this.recomputePageAuditability();
41 }
42
43 /**
44 * @override
45 * @param {!SDK.ServiceWorkerManager} serviceWorkerManager
46 */
47 modelRemoved(serviceWorkerManager) {
48 if (this._manager !== serviceWorkerManager)
49 return;
50
51 Common.EventTarget.removeEventListeners(this._serviceWorkerListeners);
52 this._manager = null;
53 this.recomputePageAuditability();
54 }
55
56 /**
57 * @return {boolean}
58 */
59 _hasActiveServiceWorker() {
60 if (!this._manager)
61 return false;
62
63 const mainTarget = this._manager.target();
64 if (!mainTarget)
65 return false;
66
67 const inspectedURL = mainTarget.inspectedURL().asParsedURL();
68 const inspectedOrigin = inspectedURL && inspectedURL.securityOrigin();
69 for (const registration of this._manager.registrations().values()) {
70 if (registration.securityOrigin !== inspectedOrigin)
71 continue;
72
73 for (const version of registration.versions.values()) {
74 if (version.controlledClients.length > 1)
75 return true;
76 }
77 }
78
79 return false;
80 }
81
82 /**
83 * @return {boolean}
84 */
85 _hasAtLeastOneCategory() {
cjamcl@google.comaa1532c2019-05-31 03:01:24 +000086 return Audits.Presets.some(preset => preset.setting.get());
Patrick Hulcea087f622018-05-18 00:37:53 +000087 }
88
89 /**
90 * @return {?string}
91 */
92 _unauditablePageMessage() {
93 if (!this._manager)
94 return null;
95
96 const mainTarget = this._manager.target();
97 const inspectedURL = mainTarget && mainTarget.inspectedURL();
98 if (inspectedURL && !/^(http|chrome-extension)/.test(inspectedURL)) {
99 return Common.UIString(
Lorne Mitchell7aa2c6c2019-04-03 03:50:10 +0000100 '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 +0000101 }
102
Patrick Hulcea087f622018-05-18 00:37:53 +0000103 return null;
104 }
105
106 /**
107 * @return {!Promise<string>}
108 */
109 async _evaluateInspectedURL() {
110 const mainTarget = this._manager.target();
111 const runtimeModel = mainTarget.model(SDK.RuntimeModel);
112 const executionContext = runtimeModel && runtimeModel.defaultExecutionContext();
113 let inspectedURL = mainTarget.inspectedURL();
114 if (!executionContext)
115 return inspectedURL;
116
117 // Evaluate location.href for a more specific URL than inspectedURL provides so that SPA hash navigation routes
118 // will be respected and audited.
119 try {
120 const result = await executionContext.evaluate(
121 {
122 expression: 'window.location.href',
123 objectGroup: 'audits',
124 includeCommandLineAPI: false,
125 silent: false,
126 returnByValue: true,
127 generatePreview: false
128 },
129 /* userGesture */ false, /* awaitPromise */ false);
130 if (!result.exceptionDetails && result.object) {
131 inspectedURL = result.object.value;
132 result.object.release();
133 }
134 } catch (err) {
135 console.error(err);
136 }
137
138 return inspectedURL;
139 }
140
141 /**
142 * @return {!Object}
143 */
144 getFlags() {
Connor Clark3f700342019-07-25 02:10:41 +0000145 const flags = {
146 // DevTools handles all the emulation. This tells Lighthouse to not bother with emulation.
147 deviceScreenEmulationMethod: 'provided'
148 };
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000149 for (const runtimeSetting of Audits.RuntimeSettings)
Patrick Hulcea087f622018-05-18 00:37:53 +0000150 runtimeSetting.setFlags(flags, runtimeSetting.setting.get());
151 return flags;
152 }
153
154 /**
155 * @return {!Array<string>}
156 */
157 getCategoryIDs() {
158 const categoryIDs = [];
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000159 for (const preset of Audits.Presets) {
Patrick Hulcea087f622018-05-18 00:37:53 +0000160 if (preset.setting.get())
161 categoryIDs.push(preset.configID);
162 }
163 return categoryIDs;
164 }
165
166 /**
167 * @param {{force: boolean}=} options
168 * @return {!Promise<string>}
169 */
170 async getInspectedURL(options) {
171 if (options && options.force || !this._inspectedURL)
172 this._inspectedURL = await this._evaluateInspectedURL();
173 return this._inspectedURL;
174 }
175
176 recomputePageAuditability() {
177 const hasActiveServiceWorker = this._hasActiveServiceWorker();
178 const hasAtLeastOneCategory = this._hasAtLeastOneCategory();
179 const unauditablePageMessage = this._unauditablePageMessage();
180
181 let helpText = '';
182 if (hasActiveServiceWorker) {
183 helpText = Common.UIString(
Lorne Mitchell7aa2c6c2019-04-03 03:50:10 +0000184 '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 +0000185 } else if (!hasAtLeastOneCategory) {
186 helpText = Common.UIString('At least one category must be selected.');
187 } else if (unauditablePageMessage) {
188 helpText = unauditablePageMessage;
189 }
190
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000191 this.dispatchEventToListeners(Audits.Events.PageAuditabilityChanged, {helpText});
Patrick Hulcea087f622018-05-18 00:37:53 +0000192 }
193};
194
195
Patrick Hulce05c18ce2018-05-24 00:34:56 +0000196/** @typedef {{setting: !Common.Setting, configID: string, title: string, description: string}} */
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000197Audits.Preset;
Patrick Hulcea087f622018-05-18 00:37:53 +0000198
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000199/** @type {!Array.<!Audits.Preset>} */
200Audits.Presets = [
Patrick Hulcea087f622018-05-18 00:37:53 +0000201 // configID maps to Lighthouse's Object.keys(config.categories)[0] value
202 {
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000203 setting: Common.settings.createSetting('audits.cat_perf', true),
Patrick Hulcea087f622018-05-18 00:37:53 +0000204 configID: 'performance',
Christy Chen38dccb52019-05-08 22:32:15 +0000205 title: ls`Performance`,
206 description: ls`How long does this app take to show content and become usable`
Patrick Hulcea087f622018-05-18 00:37:53 +0000207 },
208 {
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000209 setting: Common.settings.createSetting('audits.cat_pwa', true),
Patrick Hulcea087f622018-05-18 00:37:53 +0000210 configID: 'pwa',
Christy Chen38dccb52019-05-08 22:32:15 +0000211 title: ls`Progressive Web App`,
212 description: ls`Does this page meet the standard of a Progressive Web App`
Patrick Hulcea087f622018-05-18 00:37:53 +0000213 },
214 {
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000215 setting: Common.settings.createSetting('audits.cat_best_practices', true),
Patrick Hulcea087f622018-05-18 00:37:53 +0000216 configID: 'best-practices',
Christy Chen38dccb52019-05-08 22:32:15 +0000217 title: ls`Best practices`,
218 description: ls`Does this page follow best practices for modern web development`
Patrick Hulcea087f622018-05-18 00:37:53 +0000219 },
220 {
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000221 setting: Common.settings.createSetting('audits.cat_a11y', true),
Patrick Hulcea087f622018-05-18 00:37:53 +0000222 configID: 'accessibility',
Christy Chen38dccb52019-05-08 22:32:15 +0000223 title: ls`Accessibility`,
224 description: ls`Is this page usable by people with disabilities or impairments`
Patrick Hulcea087f622018-05-18 00:37:53 +0000225 },
226 {
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000227 setting: Common.settings.createSetting('audits.cat_seo', true),
Patrick Hulcea087f622018-05-18 00:37:53 +0000228 configID: 'seo',
Christy Chen38dccb52019-05-08 22:32:15 +0000229 title: ls`SEO`,
230 description: ls`Is this page optimized for search engine results ranking`
Patrick Hulcea087f622018-05-18 00:37:53 +0000231 },
232];
233
Patrick Hulce05c18ce2018-05-24 00:34:56 +0000234/** @typedef {{setting: !Common.Setting, description: string, setFlags: function(!Object, string), options: (!Array|undefined), title: (string|undefined)}} */
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000235Audits.RuntimeSetting;
Patrick Hulcea087f622018-05-18 00:37:53 +0000236
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000237/** @type {!Array.<!Audits.RuntimeSetting>} */
238Audits.RuntimeSettings = [
Patrick Hulcea087f622018-05-18 00:37:53 +0000239 {
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000240 setting: Common.settings.createSetting('audits.device_type', 'mobile'),
Patrick Hulce05c18ce2018-05-24 00:34:56 +0000241 description: ls`Apply mobile emulation during auditing`,
Patrick Hulcea087f622018-05-18 00:37:53 +0000242 setFlags: (flags, value) => {
Paul Irishd8495012019-07-16 23:51:47 +0000243 // See Audits.AuditsPanel._setupEmulationAndProtocolConnection()
Connor Clark3f700342019-07-25 02:10:41 +0000244 flags.emulatedFormFactor = value;
Patrick Hulcea087f622018-05-18 00:37:53 +0000245 },
246 options: [
Patrick Hulce05c18ce2018-05-24 00:34:56 +0000247 {label: ls`Mobile`, value: 'mobile'},
248 {label: ls`Desktop`, value: 'desktop'},
Patrick Hulcea087f622018-05-18 00:37:53 +0000249 ],
250 },
251 {
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000252 setting: Common.settings.createSetting('audits.throttling', 'default'),
Patrick Hulcea087f622018-05-18 00:37:53 +0000253 setFlags: (flags, value) => {
Paul Irish8f1e33d2018-05-31 02:29:50 +0000254 switch (value) {
255 case 'devtools':
256 flags.throttlingMethod = 'devtools';
257 break;
258 case 'off':
259 flags.throttlingMethod = 'provided';
260 break;
261 default:
262 flags.throttlingMethod = 'simulate';
263 }
Patrick Hulcea087f622018-05-18 00:37:53 +0000264 },
265 options: [
Paul Irish8f1e33d2018-05-31 02:29:50 +0000266 {
cjamcl@google.combbf3f9b2019-04-19 19:25:18 +0000267 label: ls`Simulated Slow 4G, 4x CPU Slowdown`,
Paul Irish8f1e33d2018-05-31 02:29:50 +0000268 value: 'default',
Christy Chen38dccb52019-05-08 22:32:15 +0000269 title: ls`Throttling is simulated, resulting in faster audit runs with similar measurement accuracy`
Paul Irish8f1e33d2018-05-31 02:29:50 +0000270 },
271 {
cjamcl@google.combbf3f9b2019-04-19 19:25:18 +0000272 label: ls`Applied Slow 4G, 4x CPU Slowdown`,
Paul Irish8f1e33d2018-05-31 02:29:50 +0000273 value: 'devtools',
Christy Chen38dccb52019-05-08 22:32:15 +0000274 title: ls`Typical DevTools throttling, with actual traffic shaping and CPU slowdown applied`
Paul Irish8f1e33d2018-05-31 02:29:50 +0000275 },
276 {
277 label: ls`No throttling`,
278 value: 'off',
Christy Chen38dccb52019-05-08 22:32:15 +0000279 title: ls`No network or CPU throttling used. (Useful when not evaluating performance)`
Paul Irish8f1e33d2018-05-31 02:29:50 +0000280 },
Patrick Hulcea087f622018-05-18 00:37:53 +0000281 ],
282 },
283 {
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000284 setting: Common.settings.createSetting('audits.clear_storage', true),
Patrick Hulce05c18ce2018-05-24 00:34:56 +0000285 title: ls`Clear storage`,
Paul Irish8f1e33d2018-05-31 02:29:50 +0000286 description: ls`Reset storage (localStorage, IndexedDB, etc) before auditing. (Good for performance & PWA testing)`,
Patrick Hulcea087f622018-05-18 00:37:53 +0000287 setFlags: (flags, value) => {
Patrick Hulce05c18ce2018-05-24 00:34:56 +0000288 flags.disableStorageReset = !value;
Patrick Hulcea087f622018-05-18 00:37:53 +0000289 },
Patrick Hulcea087f622018-05-18 00:37:53 +0000290 },
291];
292
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000293Audits.Events = {
Trent Aptedba184a62018-05-25 02:13:48 +0000294 PageAuditabilityChanged: Symbol('PageAuditabilityChanged'),
295 AuditProgressChanged: Symbol('AuditProgressChanged'),
296 RequestAuditStart: Symbol('RequestAuditStart'),
297 RequestAuditCancel: Symbol('RequestAuditCancel'),
Paul Irish8f1e33d2018-05-31 02:29:50 +0000298};