blob: 64e2e0d2fbf754d70f7a376fc01cb2915145249d [file] [log] [blame]
Blink Reformat4c46d092018-04-07 15:32:371// 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
5Audits2.Audits2StatusView = class {
6 constructor() {
7 this._statusView = null;
8 this._progressWrapper = null;
9 this._progressBar = null;
10 this._statusText = null;
11
12 this._textChangedAt = 0;
13 this._fastFactsQueued = Audits2.Audits2StatusView.FastFacts.slice();
14 this._currentPhase = null;
15 this._scheduledTextChangeTimeout = null;
16 this._scheduledFastFactTimeout = null;
17 }
18
19 /**
20 * @param {!Element} parentElement
21 */
22 render(parentElement) {
23 this.reset();
24
25 this._statusView = parentElement.createChild('div', 'audits2-status vbox hidden');
26 this._progressWrapper = this._statusView.createChild('div', 'audits2-progress-wrapper');
27 this._progressBar = this._progressWrapper.createChild('div', 'audits2-progress-bar');
28 this._statusText = this._statusView.createChild('div', 'audits2-status-text');
29
30 this.updateStatus(Common.UIString('Loading...'));
31 }
32
33 reset() {
34 this._resetProgressBarClasses();
35 clearTimeout(this._scheduledFastFactTimeout);
36
37 this._textChangedAt = 0;
38 this._fastFactsQueued = Audits2.Audits2StatusView.FastFacts.slice();
39 this._currentPhase = null;
40 this._scheduledTextChangeTimeout = null;
41 this._scheduledFastFactTimeout = null;
42 }
43
44 /**
45 * @param {boolean} isVisible
46 */
47 setVisible(isVisible) {
48 this._statusView.classList.toggle('hidden', !isVisible);
49
50 if (!isVisible)
51 clearTimeout(this._scheduledFastFactTimeout);
52 }
53
54 /**
55 * @param {?string} message
56 */
57 updateStatus(message) {
58 if (!message || !this._statusText)
59 return;
60
61 if (message.startsWith('Cancel')) {
62 this._commitTextChange(Common.UIString('Cancelling\u2026'));
63 clearTimeout(this._scheduledFastFactTimeout);
64 return;
65 }
66
67 const nextPhase = this._getPhaseForMessage(message);
68 if (!nextPhase && !this._currentPhase) {
69 this._commitTextChange(Common.UIString('Lighthouse is warming up\u2026'));
70 clearTimeout(this._scheduledFastFactTimeout);
71 } else if (nextPhase && (!this._currentPhase || this._currentPhase.order < nextPhase.order)) {
72 this._currentPhase = nextPhase;
73 this._scheduleTextChange(this._getMessageForPhase(nextPhase));
74 this._scheduleFastFactCheck();
75 this._resetProgressBarClasses();
76 this._progressBar.classList.add(nextPhase.progressBarClass);
77 }
78 }
79
80 /**
81 * @param {!Audits2.Audits2StatusView.StatusPhases} phase
82 * @return {string}
83 */
84 _getMessageForPhase(phase) {
85 if (phase.message)
86 return Common.UIString(phase.message);
87
88 const deviceType =
89 Audits2.Audits2Panel.RuntimeSettings.find(item => item.setting.name === 'audits2.device_type').setting.get();
90 const throttling =
91 Audits2.Audits2Panel.RuntimeSettings.find(item => item.setting.name === 'audits2.throttling').setting.get();
92 const match = Audits2.Audits2StatusView.LoadingMessages.find(item => {
93 return item.deviceType === deviceType && item.throttling === throttling;
94 });
95
96 return match ? ls`${match.message}` : ls`Lighthouse is loading your page`;
97 }
98
99 /**
100 * @param {string} message
101 * @return {?Audits2.Audits2StatusView.StatusPhases}
102 */
103 _getPhaseForMessage(message) {
104 return Audits2.Audits2StatusView.StatusPhases.find(phase => message.startsWith(phase.statusMessagePrefix));
105 }
106
107 _resetProgressBarClasses() {
108 if (!this._progressBar)
109 return;
110
111 this._progressBar.className = 'audits2-progress-bar';
112 }
113
114 _scheduleFastFactCheck() {
115 if (!this._currentPhase || this._scheduledFastFactTimeout)
116 return;
117
118 this._scheduledFastFactTimeout = setTimeout(() => {
119 this._updateFastFactIfNecessary();
120 this._scheduledFastFactTimeout = null;
121
122 this._scheduleFastFactCheck();
123 }, 100);
124 }
125
126 _updateFastFactIfNecessary() {
127 const now = performance.now();
128 if (now - this._textChangedAt < Audits2.Audits2StatusView.fastFactRotationInterval)
129 return;
130 if (!this._fastFactsQueued.length)
131 return;
132
133 const fastFactIndex = Math.floor(Math.random() * this._fastFactsQueued.length);
134 this._scheduleTextChange(ls`\ud83d\udca1 ${this._fastFactsQueued[fastFactIndex]}`);
135 this._fastFactsQueued.splice(fastFactIndex, 1);
136 }
137
138 /**
139 * @param {string} text
140 */
141 _commitTextChange(text) {
142 if (!this._statusText)
143 return;
144 this._textChangedAt = performance.now();
145 this._statusText.textContent = text;
146 }
147
148 /**
149 * @param {string} text
150 */
151 _scheduleTextChange(text) {
152 if (this._scheduledTextChangeTimeout)
153 clearTimeout(this._scheduledTextChangeTimeout);
154
155 const msSinceLastChange = performance.now() - this._textChangedAt;
156 const msToTextChange = Audits2.Audits2StatusView.minimumTextVisibilityDuration - msSinceLastChange;
157
158 this._scheduledTextChangeTimeout = setTimeout(() => {
159 this._commitTextChange(text);
160 }, Math.max(msToTextChange, 0));
161 }
162
163 /**
164 * @param {!Error} err
165 * @param {string} auditURL
166 */
167 renderBugReport(err, auditURL) {
168 console.error(err);
169 clearTimeout(this._scheduledFastFactTimeout);
170 clearTimeout(this._scheduledTextChangeTimeout);
171 this._resetProgressBarClasses();
172 this._progressBar.classList.add('errored');
173
174 this._commitTextChange('');
175 this._statusText.createTextChild(Common.UIString('Ah, sorry! We ran into an error: '));
176 this._statusText.createChild('em').createTextChild(err.message);
177 if (Audits2.Audits2StatusView.KnownBugPatterns.some(pattern => pattern.test(err.message))) {
178 const message = Common.UIString(
179 'Try to navigate to the URL in a fresh Chrome profile without any other tabs or ' +
180 'extensions open and try again.');
181 this._statusText.createChild('p').createTextChild(message);
182 } else {
183 this._renderBugReportLink(err, auditURL);
184 }
185 }
186
187 /**
188 * @param {!Error} err
189 * @param {string} auditURL
190 */
191 _renderBugReportLink(err, auditURL) {
192 const baseURI = 'https://github.com/GoogleChrome/lighthouse/issues/new?';
193 const title = encodeURI('title=DevTools Error: ' + err.message.substring(0, 60));
194
195 const issueBody = `
196**Initial URL**: ${auditURL}
197**Chrome Version**: ${navigator.userAgent.match(/Chrome\/(\S+)/)[1]}
198**Error Message**: ${err.message}
199**Stack Trace**:
200\`\`\`
201${err.stack}
202\`\`\`
203 `;
204 const body = '&body=' + encodeURIComponent(issueBody.trim());
205 const reportErrorEl = UI.XLink.create(
206 baseURI + title + body, Common.UIString('Report this bug'), 'audits2-link audits2-report-error');
207 this._statusText.appendChild(reportErrorEl);
208 }
209};
210
211
212/** @type {!Array.<!RegExp>} */
213Audits2.Audits2StatusView.KnownBugPatterns = [
214 /PARSING_PROBLEM/,
215 /DOCUMENT_REQUEST/,
216 /READ_FAILED/,
217 /TRACING_ALREADY_STARTED/,
218 /^You must provide a url to the runner/,
219 /^You probably have multiple tabs open/,
220];
221
222/** @typedef {{message: string, progressBarClass: string, order: number}} */
223Audits2.Audits2StatusView.StatusPhases = [
224 {
225 id: 'loading',
226 progressBarClass: 'loading',
227 statusMessagePrefix: 'Loading page',
228 order: 10,
229 },
230 {
231 id: 'gathering',
232 progressBarClass: 'gathering',
233 message: 'Lighthouse is gathering information about the page to compute your score.',
234 statusMessagePrefix: 'Retrieving',
235 order: 20,
236 },
237 {
238 id: 'auditing',
239 progressBarClass: 'auditing',
240 message: 'Almost there! Lighthouse is now generating your own special pretty report!',
241 statusMessagePrefix: 'Evaluating',
242 order: 30,
243 }
244];
245
246/** @typedef {{message: string, deviceType: string, throttling: string}} */
247Audits2.Audits2StatusView.LoadingMessages = [
248 {
249 deviceType: 'mobile',
250 throttling: 'on',
251 message: 'Lighthouse is loading your page with throttling to measure performance on a mobile device on 3G.',
252 },
253 {
254 deviceType: 'desktop',
255 throttling: 'on',
256 message: 'Lighthouse is loading your page with throttling to measure performance on a slow desktop on 3G.',
257 },
258 {
259 deviceType: 'mobile',
260 throttling: 'off',
261 message: 'Lighthouse is loading your page with mobile emulation.',
262 },
263 {
264 deviceType: 'desktop',
265 throttling: 'off',
266 message: 'Lighthouse is loading your page.',
267 },
268];
269
270Audits2.Audits2StatusView.FastFacts = [
271 '1MB takes a minimum of 5 seconds to download on a typical 3G connection [Source: WebPageTest and DevTools 3G definition].',
272 'Rebuilding Pinterest pages for performance increased conversion rates by 15% [Source: WPO Stats]',
273 'BBC has seen a loss of 10% of their users for every extra second of page load [Source: WPO Stats]',
274 'By reducing the response size of JSON needed for displaying comments, Instagram saw increased impressions [Source: WPO Stats]',
275 'Walmart saw a 1% increase in revenue for every 100ms improvement in page load [Source: WPO Stats]',
276 '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]',
277 '75% of global mobile users in 2016 were on 2G or 3G [Source: GSMA Mobile]',
278 'The average user device costs less than 200 USD. [Source: International Data Corporation]',
279 '53% of all site visits are abandoned if page load takes more than 3 seconds [Source: Google DoubleClick blog]',
280 '19 seconds is the average time a mobile web page takes to load on a 3G connection [Source: Google DoubleClick blog]',
281 '14 seconds is the average time a mobile web page takes to load on a 4G connection [Source: Google DoubleClick blog]',
282 '70% of mobile pages take nearly 7 seconds for the visual content above the fold to display on the screen. [Source: Think with Google]',
283 '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]',
284 'As the number of elements on a page increases from 400 to 6,000, the probability of conversion drops 95%. [Source: Think with Google]',
285 '70% of mobile pages weigh over 1MB, 36% over 2MB, and 12% over 4MB. [Source: Think with Google]',
286 'Lighthouse only simulates mobile performance; to measure performance on a real device, try WebPageTest.org [Source: Lighthouse team]',
287];
288
289/** @const */
290Audits2.Audits2StatusView.fastFactRotationInterval = 6000;
291/** @const */
292Audits2.Audits2StatusView.minimumTextVisibilityDuration = 3000;