blob: a7406c29beb2321363a058a6fc059285809d23ba [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() {
145 const flags = {};
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000146 for (const runtimeSetting of Audits.RuntimeSettings)
Patrick Hulcea087f622018-05-18 00:37:53 +0000147 runtimeSetting.setFlags(flags, runtimeSetting.setting.get());
148 return flags;
149 }
150
151 /**
152 * @return {!Array<string>}
153 */
154 getCategoryIDs() {
155 const categoryIDs = [];
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000156 for (const preset of Audits.Presets) {
Patrick Hulcea087f622018-05-18 00:37:53 +0000157 if (preset.setting.get())
158 categoryIDs.push(preset.configID);
159 }
160 return categoryIDs;
161 }
162
163 /**
164 * @param {{force: boolean}=} options
165 * @return {!Promise<string>}
166 */
167 async getInspectedURL(options) {
168 if (options && options.force || !this._inspectedURL)
169 this._inspectedURL = await this._evaluateInspectedURL();
170 return this._inspectedURL;
171 }
172
173 recomputePageAuditability() {
174 const hasActiveServiceWorker = this._hasActiveServiceWorker();
175 const hasAtLeastOneCategory = this._hasAtLeastOneCategory();
176 const unauditablePageMessage = this._unauditablePageMessage();
177
178 let helpText = '';
179 if (hasActiveServiceWorker) {
180 helpText = Common.UIString(
Lorne Mitchell7aa2c6c2019-04-03 03:50:10 +0000181 '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 +0000182 } else if (!hasAtLeastOneCategory) {
183 helpText = Common.UIString('At least one category must be selected.');
184 } else if (unauditablePageMessage) {
185 helpText = unauditablePageMessage;
186 }
187
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000188 this.dispatchEventToListeners(Audits.Events.PageAuditabilityChanged, {helpText});
Patrick Hulcea087f622018-05-18 00:37:53 +0000189 }
190};
191
192
Patrick Hulce05c18ce2018-05-24 00:34:56 +0000193/** @typedef {{setting: !Common.Setting, configID: string, title: string, description: string}} */
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000194Audits.Preset;
Patrick Hulcea087f622018-05-18 00:37:53 +0000195
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000196/** @type {!Array.<!Audits.Preset>} */
197Audits.Presets = [
Patrick Hulcea087f622018-05-18 00:37:53 +0000198 // configID maps to Lighthouse's Object.keys(config.categories)[0] value
199 {
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000200 setting: Common.settings.createSetting('audits.cat_perf', true),
Patrick Hulcea087f622018-05-18 00:37:53 +0000201 configID: 'performance',
Christy Chen38dccb52019-05-08 22:32:15 +0000202 title: ls`Performance`,
203 description: ls`How long does this app take to show content and become usable`
Patrick Hulcea087f622018-05-18 00:37:53 +0000204 },
205 {
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000206 setting: Common.settings.createSetting('audits.cat_pwa', true),
Patrick Hulcea087f622018-05-18 00:37:53 +0000207 configID: 'pwa',
Christy Chen38dccb52019-05-08 22:32:15 +0000208 title: ls`Progressive Web App`,
209 description: ls`Does this page meet the standard of a Progressive Web App`
Patrick Hulcea087f622018-05-18 00:37:53 +0000210 },
211 {
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000212 setting: Common.settings.createSetting('audits.cat_best_practices', true),
Patrick Hulcea087f622018-05-18 00:37:53 +0000213 configID: 'best-practices',
Christy Chen38dccb52019-05-08 22:32:15 +0000214 title: ls`Best practices`,
215 description: ls`Does this page follow best practices for modern web development`
Patrick Hulcea087f622018-05-18 00:37:53 +0000216 },
217 {
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000218 setting: Common.settings.createSetting('audits.cat_a11y', true),
Patrick Hulcea087f622018-05-18 00:37:53 +0000219 configID: 'accessibility',
Christy Chen38dccb52019-05-08 22:32:15 +0000220 title: ls`Accessibility`,
221 description: ls`Is this page usable by people with disabilities or impairments`
Patrick Hulcea087f622018-05-18 00:37:53 +0000222 },
223 {
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000224 setting: Common.settings.createSetting('audits.cat_seo', true),
Patrick Hulcea087f622018-05-18 00:37:53 +0000225 configID: 'seo',
Christy Chen38dccb52019-05-08 22:32:15 +0000226 title: ls`SEO`,
227 description: ls`Is this page optimized for search engine results ranking`
Patrick Hulcea087f622018-05-18 00:37:53 +0000228 },
229];
230
Patrick Hulce05c18ce2018-05-24 00:34:56 +0000231/** @typedef {{setting: !Common.Setting, description: string, setFlags: function(!Object, string), options: (!Array|undefined), title: (string|undefined)}} */
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000232Audits.RuntimeSetting;
Patrick Hulcea087f622018-05-18 00:37:53 +0000233
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000234/** @type {!Array.<!Audits.RuntimeSetting>} */
235Audits.RuntimeSettings = [
Patrick Hulcea087f622018-05-18 00:37:53 +0000236 {
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000237 setting: Common.settings.createSetting('audits.device_type', 'mobile'),
Patrick Hulce05c18ce2018-05-24 00:34:56 +0000238 description: ls`Apply mobile emulation during auditing`,
Patrick Hulcea087f622018-05-18 00:37:53 +0000239 setFlags: (flags, value) => {
cjamcl@google.comf2f8c092019-05-30 22:01:56 +0000240 flags.emulatedFormFactor = value;
Patrick Hulcea087f622018-05-18 00:37:53 +0000241 },
242 options: [
Patrick Hulce05c18ce2018-05-24 00:34:56 +0000243 {label: ls`Mobile`, value: 'mobile'},
244 {label: ls`Desktop`, value: 'desktop'},
Patrick Hulcea087f622018-05-18 00:37:53 +0000245 ],
246 },
247 {
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000248 setting: Common.settings.createSetting('audits.throttling', 'default'),
Patrick Hulcea087f622018-05-18 00:37:53 +0000249 setFlags: (flags, value) => {
Paul Irish8f1e33d2018-05-31 02:29:50 +0000250 switch (value) {
251 case 'devtools':
252 flags.throttlingMethod = 'devtools';
253 break;
254 case 'off':
255 flags.throttlingMethod = 'provided';
256 break;
257 default:
258 flags.throttlingMethod = 'simulate';
259 }
Patrick Hulcea087f622018-05-18 00:37:53 +0000260 },
261 options: [
Paul Irish8f1e33d2018-05-31 02:29:50 +0000262 {
cjamcl@google.combbf3f9b2019-04-19 19:25:18 +0000263 label: ls`Simulated Slow 4G, 4x CPU Slowdown`,
Paul Irish8f1e33d2018-05-31 02:29:50 +0000264 value: 'default',
Christy Chen38dccb52019-05-08 22:32:15 +0000265 title: ls`Throttling is simulated, resulting in faster audit runs with similar measurement accuracy`
Paul Irish8f1e33d2018-05-31 02:29:50 +0000266 },
267 {
cjamcl@google.combbf3f9b2019-04-19 19:25:18 +0000268 label: ls`Applied Slow 4G, 4x CPU Slowdown`,
Paul Irish8f1e33d2018-05-31 02:29:50 +0000269 value: 'devtools',
Christy Chen38dccb52019-05-08 22:32:15 +0000270 title: ls`Typical DevTools throttling, with actual traffic shaping and CPU slowdown applied`
Paul Irish8f1e33d2018-05-31 02:29:50 +0000271 },
272 {
273 label: ls`No throttling`,
274 value: 'off',
Christy Chen38dccb52019-05-08 22:32:15 +0000275 title: ls`No network or CPU throttling used. (Useful when not evaluating performance)`
Paul Irish8f1e33d2018-05-31 02:29:50 +0000276 },
Patrick Hulcea087f622018-05-18 00:37:53 +0000277 ],
278 },
279 {
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000280 setting: Common.settings.createSetting('audits.clear_storage', true),
Patrick Hulce05c18ce2018-05-24 00:34:56 +0000281 title: ls`Clear storage`,
Paul Irish8f1e33d2018-05-31 02:29:50 +0000282 description: ls`Reset storage (localStorage, IndexedDB, etc) before auditing. (Good for performance & PWA testing)`,
Patrick Hulcea087f622018-05-18 00:37:53 +0000283 setFlags: (flags, value) => {
Patrick Hulce05c18ce2018-05-24 00:34:56 +0000284 flags.disableStorageReset = !value;
Patrick Hulcea087f622018-05-18 00:37:53 +0000285 },
Patrick Hulcea087f622018-05-18 00:37:53 +0000286 },
287];
288
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000289Audits.Events = {
Trent Aptedba184a62018-05-25 02:13:48 +0000290 PageAuditabilityChanged: Symbol('PageAuditabilityChanged'),
291 AuditProgressChanged: Symbol('AuditProgressChanged'),
292 RequestAuditStart: Symbol('RequestAuditStart'),
293 RequestAuditCancel: Symbol('RequestAuditCancel'),
Paul Irish8f1e33d2018-05-31 02:29:50 +0000294};