blob: 3717a42851d416d10f27a9e08d79f7628542e88b [file] [log] [blame]
Blink Reformat4c46d092018-04-07 15:32:37 +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
Paul Lewiscf2ef222019-11-22 14:55:35 +00005export default class StatusView {
Patrick Hulcea087f622018-05-18 00:37:53 +00006 /**
cjamcl@google.comaa1532c2019-05-31 03:01:24 +00007 * @param {!Audits.AuditController} controller
Patrick Hulcea087f622018-05-18 00:37:53 +00008 */
9 constructor(controller) {
10 this._controller = controller;
11
Blink Reformat4c46d092018-04-07 15:32:37 +000012 this._statusView = null;
Patrick Hulcea087f622018-05-18 00:37:53 +000013 this._statusHeader = null;
Blink Reformat4c46d092018-04-07 15:32:37 +000014 this._progressWrapper = null;
15 this._progressBar = null;
16 this._statusText = null;
Connor Clark99508362019-08-20 19:52:23 +000017 this._cancelButton = null;
Blink Reformat4c46d092018-04-07 15:32:37 +000018
Patrick Hulcea087f622018-05-18 00:37:53 +000019 this._inspectedURL = '';
Blink Reformat4c46d092018-04-07 15:32:37 +000020 this._textChangedAt = 0;
Paul Lewiscf2ef222019-11-22 14:55:35 +000021 this._fastFactsQueued = FastFacts.slice();
Blink Reformat4c46d092018-04-07 15:32:37 +000022 this._currentPhase = null;
23 this._scheduledTextChangeTimeout = null;
24 this._scheduledFastFactTimeout = null;
Patrick Hulcea087f622018-05-18 00:37:53 +000025
26 this._dialog = new UI.Dialog();
Patrick Hulce8d387f12018-05-29 18:54:54 +000027 this._dialog.setDimmed(true);
28 this._dialog.setCloseOnEscape(false);
Patrick Hulcea087f622018-05-18 00:37:53 +000029 this._dialog.setOutsideClickCallback(event => event.consume(true));
30 this._render();
Blink Reformat4c46d092018-04-07 15:32:37 +000031 }
32
Patrick Hulcea087f622018-05-18 00:37:53 +000033 _render() {
cjamcl@google.comaa1532c2019-05-31 03:01:24 +000034 const dialogRoot = UI.createShadowRootWithCoreStyles(this._dialog.contentElement, 'audits/auditsDialog.css');
35 const auditsViewElement = dialogRoot.createChild('div', 'audits-view vbox');
Blink Reformat4c46d092018-04-07 15:32:37 +000036
Patrick Hulcea087f622018-05-18 00:37:53 +000037 const cancelButton = UI.createTextButton(ls`Cancel`, this._cancel.bind(this));
38 const fragment = UI.Fragment.build`
cjamcl@google.comaa1532c2019-05-31 03:01:24 +000039 <div class="audits-view vbox">
Patrick Hulcea087f622018-05-18 00:37:53 +000040 <h2 $="status-header">Auditing your web page\u2026</h2>
cjamcl@google.comaa1532c2019-05-31 03:01:24 +000041 <div class="audits-status vbox" $="status-view">
42 <div class="audits-progress-wrapper" $="progress-wrapper">
43 <div class="audits-progress-bar" $="progress-bar"></div>
Patrick Hulcea087f622018-05-18 00:37:53 +000044 </div>
cjamcl@google.comaa1532c2019-05-31 03:01:24 +000045 <div class="audits-status-text" $="status-text"></div>
Patrick Hulcea087f622018-05-18 00:37:53 +000046 </div>
47 ${cancelButton}
48 </div>
49 `;
Blink Reformat4c46d092018-04-07 15:32:37 +000050
Patrick Hulcea087f622018-05-18 00:37:53 +000051 auditsViewElement.appendChild(fragment.element());
Patrick Hulcea087f622018-05-18 00:37:53 +000052
53 this._statusView = fragment.$('status-view');
54 this._statusHeader = fragment.$('status-header');
55 this._progressWrapper = fragment.$('progress-wrapper');
56 this._progressBar = fragment.$('progress-bar');
57 this._statusText = fragment.$('status-text');
Connor Clark99508362019-08-20 19:52:23 +000058 this._cancelButton = cancelButton;
John Emau463280d2019-07-19 00:37:40 +000059 UI.ARIAUtils.markAsStatus(this._statusText);
Patrick Hulcea087f622018-05-18 00:37:53 +000060
61 this._dialog.setDefaultFocusedElement(cancelButton);
62 this._dialog.setSizeBehavior(UI.GlassPane.SizeBehavior.SetExactWidthMaxHeight);
63 this._dialog.setMaxContentSize(new UI.Size(500, 400));
Blink Reformat4c46d092018-04-07 15:32:37 +000064 }
65
Patrick Hulcea087f622018-05-18 00:37:53 +000066 _reset() {
Blink Reformat4c46d092018-04-07 15:32:37 +000067 this._resetProgressBarClasses();
68 clearTimeout(this._scheduledFastFactTimeout);
69
70 this._textChangedAt = 0;
Paul Lewiscf2ef222019-11-22 14:55:35 +000071 this._fastFactsQueued = FastFacts.slice();
Blink Reformat4c46d092018-04-07 15:32:37 +000072 this._currentPhase = null;
73 this._scheduledTextChangeTimeout = null;
74 this._scheduledFastFactTimeout = null;
75 }
76
77 /**
Patrick Hulcea087f622018-05-18 00:37:53 +000078 * @param {!Element} dialogRenderElement
Blink Reformat4c46d092018-04-07 15:32:37 +000079 */
Patrick Hulcea087f622018-05-18 00:37:53 +000080 show(dialogRenderElement) {
81 this._reset();
82 this.updateStatus(ls`Loading\u2026`);
Blink Reformat4c46d092018-04-07 15:32:37 +000083
Patrick Hulcea087f622018-05-18 00:37:53 +000084 const parsedURL = this._inspectedURL.asParsedURL();
85 const pageHost = parsedURL && parsedURL.host;
86 const statusHeader = pageHost ? ls`Auditing ${pageHost}` : ls`Auditing your web page`;
Connor Clark99508362019-08-20 19:52:23 +000087 this._renderStatusHeader(statusHeader);
Patrick Hulcea087f622018-05-18 00:37:53 +000088 this._dialog.show(dialogRenderElement);
89 }
90
Connor Clark99508362019-08-20 19:52:23 +000091 /**
92 * @param {string=} statusHeader
93 */
94 _renderStatusHeader(statusHeader) {
95 this._statusHeader.textContent = `${statusHeader}\u2026`;
96 }
97
Patrick Hulcea087f622018-05-18 00:37:53 +000098 hide() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000099 if (this._dialog.isShowing()) {
Patrick Hulcea087f622018-05-18 00:37:53 +0000100 this._dialog.hide();
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000101 }
Patrick Hulcea087f622018-05-18 00:37:53 +0000102 }
103
104 /**
105 * @param {string=} url
106 */
107 setInspectedURL(url = '') {
108 this._inspectedURL = url;
Blink Reformat4c46d092018-04-07 15:32:37 +0000109 }
110
111 /**
112 * @param {?string} message
113 */
114 updateStatus(message) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000115 if (!message || !this._statusText) {
Blink Reformat4c46d092018-04-07 15:32:37 +0000116 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000117 }
Blink Reformat4c46d092018-04-07 15:32:37 +0000118
119 if (message.startsWith('Cancel')) {
120 this._commitTextChange(Common.UIString('Cancelling\u2026'));
121 clearTimeout(this._scheduledFastFactTimeout);
122 return;
123 }
124
125 const nextPhase = this._getPhaseForMessage(message);
126 if (!nextPhase && !this._currentPhase) {
127 this._commitTextChange(Common.UIString('Lighthouse is warming up\u2026'));
128 clearTimeout(this._scheduledFastFactTimeout);
129 } else if (nextPhase && (!this._currentPhase || this._currentPhase.order < nextPhase.order)) {
130 this._currentPhase = nextPhase;
131 this._scheduleTextChange(this._getMessageForPhase(nextPhase));
132 this._scheduleFastFactCheck();
133 this._resetProgressBarClasses();
134 this._progressBar.classList.add(nextPhase.progressBarClass);
135 }
136 }
137
Patrick Hulcea087f622018-05-18 00:37:53 +0000138 _cancel() {
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000139 this._controller.dispatchEventToListeners(Audits.Events.RequestAuditCancel);
Patrick Hulcea087f622018-05-18 00:37:53 +0000140 }
141
Blink Reformat4c46d092018-04-07 15:32:37 +0000142 /**
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000143 * @param {!Audits.StatusView.StatusPhases} phase
Blink Reformat4c46d092018-04-07 15:32:37 +0000144 * @return {string}
145 */
146 _getMessageForPhase(phase) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000147 if (phase.message) {
Mandy Chenef16e332019-09-08 05:02:45 +0000148 return phase.message;
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000149 }
Blink Reformat4c46d092018-04-07 15:32:37 +0000150
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000151 const deviceType = Audits.RuntimeSettings.find(item => item.setting.name === 'audits.device_type').setting.get();
152 const throttling = Audits.RuntimeSettings.find(item => item.setting.name === 'audits.throttling').setting.get();
Paul Lewiscf2ef222019-11-22 14:55:35 +0000153 const match = LoadingMessages.find(item => {
Blink Reformat4c46d092018-04-07 15:32:37 +0000154 return item.deviceType === deviceType && item.throttling === throttling;
155 });
156
Mandy Chenef16e332019-09-08 05:02:45 +0000157 return match ? match.message : ls`Lighthouse is loading your page`;
Blink Reformat4c46d092018-04-07 15:32:37 +0000158 }
159
160 /**
161 * @param {string} message
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000162 * @return {?Audits.StatusView.StatusPhases}
Blink Reformat4c46d092018-04-07 15:32:37 +0000163 */
164 _getPhaseForMessage(message) {
Paul Lewiscf2ef222019-11-22 14:55:35 +0000165 return StatusPhases.find(phase => message.startsWith(phase.statusMessagePrefix));
Blink Reformat4c46d092018-04-07 15:32:37 +0000166 }
167
168 _resetProgressBarClasses() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000169 if (!this._progressBar) {
Blink Reformat4c46d092018-04-07 15:32:37 +0000170 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000171 }
Blink Reformat4c46d092018-04-07 15:32:37 +0000172
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000173 this._progressBar.className = 'audits-progress-bar';
Blink Reformat4c46d092018-04-07 15:32:37 +0000174 }
175
176 _scheduleFastFactCheck() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000177 if (!this._currentPhase || this._scheduledFastFactTimeout) {
Blink Reformat4c46d092018-04-07 15:32:37 +0000178 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000179 }
Blink Reformat4c46d092018-04-07 15:32:37 +0000180
181 this._scheduledFastFactTimeout = setTimeout(() => {
182 this._updateFastFactIfNecessary();
183 this._scheduledFastFactTimeout = null;
184
185 this._scheduleFastFactCheck();
186 }, 100);
187 }
188
189 _updateFastFactIfNecessary() {
190 const now = performance.now();
Paul Lewiscf2ef222019-11-22 14:55:35 +0000191 if (now - this._textChangedAt < fastFactRotationInterval) {
Blink Reformat4c46d092018-04-07 15:32:37 +0000192 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000193 }
194 if (!this._fastFactsQueued.length) {
Blink Reformat4c46d092018-04-07 15:32:37 +0000195 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000196 }
Blink Reformat4c46d092018-04-07 15:32:37 +0000197
198 const fastFactIndex = Math.floor(Math.random() * this._fastFactsQueued.length);
199 this._scheduleTextChange(ls`\ud83d\udca1 ${this._fastFactsQueued[fastFactIndex]}`);
200 this._fastFactsQueued.splice(fastFactIndex, 1);
201 }
202
203 /**
204 * @param {string} text
205 */
206 _commitTextChange(text) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000207 if (!this._statusText) {
Blink Reformat4c46d092018-04-07 15:32:37 +0000208 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000209 }
Blink Reformat4c46d092018-04-07 15:32:37 +0000210 this._textChangedAt = performance.now();
211 this._statusText.textContent = text;
212 }
213
214 /**
215 * @param {string} text
216 */
217 _scheduleTextChange(text) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000218 if (this._scheduledTextChangeTimeout) {
Blink Reformat4c46d092018-04-07 15:32:37 +0000219 clearTimeout(this._scheduledTextChangeTimeout);
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000220 }
Blink Reformat4c46d092018-04-07 15:32:37 +0000221
222 const msSinceLastChange = performance.now() - this._textChangedAt;
Paul Lewiscf2ef222019-11-22 14:55:35 +0000223 const msToTextChange = minimumTextVisibilityDuration - msSinceLastChange;
Blink Reformat4c46d092018-04-07 15:32:37 +0000224
225 this._scheduledTextChangeTimeout = setTimeout(() => {
226 this._commitTextChange(text);
227 }, Math.max(msToTextChange, 0));
228 }
229
230 /**
231 * @param {!Error} err
Blink Reformat4c46d092018-04-07 15:32:37 +0000232 */
Patrick Hulcea087f622018-05-18 00:37:53 +0000233 renderBugReport(err) {
Blink Reformat4c46d092018-04-07 15:32:37 +0000234 console.error(err);
235 clearTimeout(this._scheduledFastFactTimeout);
236 clearTimeout(this._scheduledTextChangeTimeout);
237 this._resetProgressBarClasses();
238 this._progressBar.classList.add('errored');
239
240 this._commitTextChange('');
Paul Irishf2659072019-03-24 19:08:52 +0000241 this._statusText.createChild('p').createTextChild(Common.UIString('Ah, sorry! We ran into an error.'));
Paul Lewiscf2ef222019-11-22 14:55:35 +0000242 if (KnownBugPatterns.some(pattern => pattern.test(err.message))) {
Blink Reformat4c46d092018-04-07 15:32:37 +0000243 const message = Common.UIString(
Lorne Mitchell7aa2c6c2019-04-03 03:50:10 +0000244 'Try to navigate to the URL in a fresh Chrome profile without any other tabs or extensions open and try again.');
Blink Reformat4c46d092018-04-07 15:32:37 +0000245 this._statusText.createChild('p').createTextChild(message);
246 } else {
Paul Irishf2659072019-03-24 19:08:52 +0000247 this._renderBugReportBody(err, this._inspectedURL);
Blink Reformat4c46d092018-04-07 15:32:37 +0000248 }
249 }
250
251 /**
Connor Clark99508362019-08-20 19:52:23 +0000252 * @param {string} statusHeader
253 * @param {string} text
254 */
255 renderText(statusHeader, text) {
256 this._renderStatusHeader(statusHeader);
257 this._commitTextChange(text);
258 }
259
260 /**
261 * @param {boolean} show
262 */
263 toggleCancelButton(show) {
264 this._cancelButton.style.visibility = show ? 'visible' : 'hidden';
265 }
266
267 /**
Blink Reformat4c46d092018-04-07 15:32:37 +0000268 * @param {!Error} err
269 * @param {string} auditURL
270 */
Paul Irishf2659072019-03-24 19:08:52 +0000271 _renderBugReportBody(err, auditURL) {
Blink Reformat4c46d092018-04-07 15:32:37 +0000272 const issueBody = `
Paul Irishf2659072019-03-24 19:08:52 +0000273${err.message}
Blink Reformat4c46d092018-04-07 15:32:37 +0000274\`\`\`
Paul Irishf2659072019-03-24 19:08:52 +0000275Channel: DevTools
276Initial URL: ${auditURL}
277Chrome Version: ${navigator.userAgent.match(/Chrome\/(\S+)/)[1]}
278Stack Trace: ${err.stack}
Blink Reformat4c46d092018-04-07 15:32:37 +0000279\`\`\`
Paul Irishf2659072019-03-24 19:08:52 +0000280`;
281 this._statusText.createChild('p').createTextChild(
282 ls`If this issue is reproducible, please report it at the Lighthouse GitHub repo.`);
283 this._statusText.createChild('code', 'monospace').createTextChild(issueBody.trim());
Blink Reformat4c46d092018-04-07 15:32:37 +0000284 }
Paul Lewiscf2ef222019-11-22 14:55:35 +0000285}
Blink Reformat4c46d092018-04-07 15:32:37 +0000286
Paul Lewiscf2ef222019-11-22 14:55:35 +0000287/** @const */
288export const fastFactRotationInterval = 6000;
289
290/** @const */
291export const minimumTextVisibilityDuration = 3000;
Blink Reformat4c46d092018-04-07 15:32:37 +0000292
293/** @type {!Array.<!RegExp>} */
Paul Lewiscf2ef222019-11-22 14:55:35 +0000294export const KnownBugPatterns = [
Blink Reformat4c46d092018-04-07 15:32:37 +0000295 /PARSING_PROBLEM/,
296 /DOCUMENT_REQUEST/,
297 /READ_FAILED/,
298 /TRACING_ALREADY_STARTED/,
299 /^You must provide a url to the runner/,
300 /^You probably have multiple tabs open/,
301];
302
303/** @typedef {{message: string, progressBarClass: string, order: number}} */
Paul Lewiscf2ef222019-11-22 14:55:35 +0000304export const StatusPhases = [
Blink Reformat4c46d092018-04-07 15:32:37 +0000305 {
306 id: 'loading',
307 progressBarClass: 'loading',
308 statusMessagePrefix: 'Loading page',
309 order: 10,
310 },
311 {
312 id: 'gathering',
313 progressBarClass: 'gathering',
Mandy Chenef16e332019-09-08 05:02:45 +0000314 message: ls`Lighthouse is gathering information about the page to compute your score.`,
cjamcl@google.comf2f8c092019-05-30 22:01:56 +0000315 statusMessagePrefix: 'Gathering',
Blink Reformat4c46d092018-04-07 15:32:37 +0000316 order: 20,
317 },
318 {
319 id: 'auditing',
320 progressBarClass: 'auditing',
Mandy Chenef16e332019-09-08 05:02:45 +0000321 message: ls`Almost there! Lighthouse is now generating your report.`,
cjamcl@google.comf2f8c092019-05-30 22:01:56 +0000322 statusMessagePrefix: 'Auditing',
Blink Reformat4c46d092018-04-07 15:32:37 +0000323 order: 30,
324 }
325];
326
327/** @typedef {{message: string, deviceType: string, throttling: string}} */
Paul Lewiscf2ef222019-11-22 14:55:35 +0000328export const LoadingMessages = [
Blink Reformat4c46d092018-04-07 15:32:37 +0000329 {
330 deviceType: 'mobile',
331 throttling: 'on',
Mandy Chenef16e332019-09-08 05:02:45 +0000332 message: ls`Lighthouse is loading your page with throttling to measure performance on a mobile device on 3G.`,
Blink Reformat4c46d092018-04-07 15:32:37 +0000333 },
334 {
335 deviceType: 'desktop',
336 throttling: 'on',
Mandy Chenef16e332019-09-08 05:02:45 +0000337 message: ls`Lighthouse is loading your page with throttling to measure performance on a slow desktop on 3G.`,
Blink Reformat4c46d092018-04-07 15:32:37 +0000338 },
339 {
340 deviceType: 'mobile',
341 throttling: 'off',
Mandy Chenef16e332019-09-08 05:02:45 +0000342 message: ls`Lighthouse is loading your page with mobile emulation.`,
Blink Reformat4c46d092018-04-07 15:32:37 +0000343 },
344 {
345 deviceType: 'desktop',
346 throttling: 'off',
Mandy Chenef16e332019-09-08 05:02:45 +0000347 message: ls`Lighthouse is loading your page.`,
Blink Reformat4c46d092018-04-07 15:32:37 +0000348 },
349];
350
Paul Lewiscf2ef222019-11-22 14:55:35 +0000351export const FastFacts = [
352 ls
353`1MB takes a minimum of 5 seconds to download on a typical 3G connection [Source: WebPageTest and DevTools 3G definition].`,
354 ls`Rebuilding Pinterest pages for performance increased conversion rates by 15% [Source: WPO Stats]`,
355 ls`BBC has seen a loss of 10% of their users for every extra second of page load [Source: WPO Stats]`, ls
356`By reducing the response size of JSON needed for displaying comments, Instagram saw increased impressions [Source: WPO Stats]`,
357 ls`Walmart saw a 1% increase in revenue for every 100ms improvement in page load [Source: WPO Stats]`, ls
358`If a site takes >1 second to become interactive, users lose attention, and their perception of completing the page task is broken [Source: Google Developers Blog]`,
359 ls`75% of global mobile users in 2016 were on 2G or 3G [Source: GSMA Mobile]`,
360 ls`The average user device costs less than 200 USD. [Source: International Data Corporation]`,
361 ls`53% of all site visits are abandoned if page load takes more than 3 seconds [Source: Google DoubleClick blog]`,
362 ls
363`19 seconds is the average time a mobile web page takes to load on a 3G connection [Source: Google DoubleClick blog]`,
364 ls
365`14 seconds is the average time a mobile web page takes to load on a 4G connection [Source: Google DoubleClick blog]`,
366 ls
367`70% of mobile pages take nearly 7 seconds for the visual content above the fold to display on the screen. [Source: Think with Google]`,
368 ls
369`As page load time increases from one second to seven seconds, the probability of a mobile site visitor bouncing increases 113%. [Source: Think with Google]`,
370 ls
371`As the number of elements on a page increases from 400 to 6,000, the probability of conversion drops 95%. [Source: Think with Google]`,
372 ls`70% of mobile pages weigh over 1MB, 36% over 2MB, and 12% over 4MB. [Source: Think with Google]`, ls
373 `Lighthouse only simulates mobile performance; to measure performance on a real device, try WebPageTest.org [Source: Lighthouse team]`,
Blink Reformat4c46d092018-04-07 15:32:37 +0000374];
375
Paul Lewiscf2ef222019-11-22 14:55:35 +0000376 /* Legacy exported object */
377 self.Audits = self.Audits || {};
378
379 /* Legacy exported object */
380 Audits = Audits || {};
381
382 /**
383 * @constructor
384 */
385 Audits.StatusView = StatusView;
386
387 Audits.StatusView.FastFacts = FastFacts;
388
389 /** @type {!Array.<!RegExp>} */
390 Audits.StatusView.KnownBugPatterns = KnownBugPatterns;
391
392 /** @typedef {{message: string, progressBarClass: string, order: number}} */
393 Audits.StatusView.StatusPhases = StatusPhases;
394
395 /** @typedef {{message: string, deviceType: string, throttling: string}} */
396 Audits.StatusView.LoadingMessages = LoadingMessages;