blob: 8b3e6df77f842bdf3f510900e01bbdc0bb88d2b6 [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
Paul Lewisdaac1062020-03-05 14:37:10 +00005import * as Common from '../common/common.js';
6import * as Components from '../components/components.js';
Tim van der Lippefe3cdc92020-11-06 15:23:13 +00007import * as Host from '../host/host.js';
vidorteg06840022020-11-20 21:18:03 -08008import * as i18n from '../i18n/i18n.js';
Simon Zünd2c704cd2020-06-04 11:08:35 +02009import * as Platform from '../platform/platform.js';
Tim van der Lippe5df64b22020-09-11 13:04:24 +010010import * as Root from '../root/root.js';
Paul Lewisdaac1062020-03-05 14:37:10 +000011import * as SDK from '../sdk/sdk.js';
Paul Lewisca569a52020-09-09 17:11:51 +010012import * as ThemeSupport from '../theme_support/theme_support.js';
Paul Lewisdaac1062020-03-05 14:37:10 +000013import * as Timeline from '../timeline/timeline.js';
14import * as UI from '../ui/ui.js';
Tim van der Lippe696c9262020-08-26 15:39:32 +010015import * as Workspace from '../workspace/workspace.js';
Paul Lewisdaac1062020-03-05 14:37:10 +000016
Tim van der Lippe1e10f852020-10-30 14:35:01 +000017import * as ReportRenderer from './LighthouseReporterTypes.js'; // eslint-disable-line no-unused-vars
18
vidorteg06840022020-11-20 21:18:03 -080019export const UIStrings = {
20 /**
21 *@description Label for view trace button when simulated throttling is enabled
22 */
23 viewOriginalTrace: 'View Original Trace',
24 /**
25 *@description Text of the timeline button in Lighthouse Report Renderer
26 */
27 viewTrace: 'View Trace',
28 /**
29 *@description Help text for 'View Trace' button
30 */
31 thePerformanceMetricsAboveAre:
32 'The performance metrics above are simulated and won\'t match the timings found in this trace. Disable simulated throttling in "Lighthouse Settings" if you want the timings to match.',
33};
34const str_ = i18n.i18n.registerUIStrings('lighthouse/LighthouseReportRenderer.js', UIStrings);
35const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
Paul Lewiscf2ef222019-11-22 14:55:35 +000036const MaxLengthForLinks = 40;
37
Patrick Hulcea087f622018-05-18 00:37:53 +000038/**
39 * @override
Tim van der Lippefe3cdc92020-11-06 15:23:13 +000040 * @extends {ReportRenderer.ReportRenderer}
Patrick Hulcea087f622018-05-18 00:37:53 +000041 */
Tim van der Lippefe3cdc92020-11-06 15:23:13 +000042// @ts-ignore https://github.com/GoogleChrome/lighthouse/issues/11628
Tim van der Lippe1e10f852020-10-30 14:35:01 +000043export class LighthouseReportRenderer extends self.ReportRenderer {
Patrick Hulcea087f622018-05-18 00:37:53 +000044 /**
Tim van der Lippe4df32c92020-11-06 12:35:05 +000045 * @param {!DOM} dom
46 */
47 constructor(dom) {
48 super(dom);
49 }
50 /**
Paul Irish8f1e33d2018-05-31 02:29:50 +000051 * @param {!Element} el Parent element to render the report into.
52 * @param {!ReportRenderer.RunnerResultArtifacts=} artifacts
Patrick Hulcea087f622018-05-18 00:37:53 +000053 */
Paul Irish8f1e33d2018-05-31 02:29:50 +000054 static addViewTraceButton(el, artifacts) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000055 if (!artifacts || !artifacts.traces || !artifacts.traces.defaultPass) {
Paul Irish8f1e33d2018-05-31 02:29:50 +000056 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000057 }
Patrick Hulcea087f622018-05-18 00:37:53 +000058
Adam Rainef46d1582020-08-17 20:24:32 +000059 const simulated = artifacts.settings.throttlingMethod === 'simulate';
cjamcl@google.com21d2d222019-08-09 01:58:17 +000060 const container = el.querySelector('.lh-audit-group');
Tim van der Lippefe3cdc92020-11-06 15:23:13 +000061 if (!container) {
62 return;
63 }
Paul Irishbf204682020-05-13 16:11:37 -070064 const disclaimerEl = container.querySelector('.lh-metrics__disclaimer');
65 // If it was a PWA-only run, we'd have a trace but no perf category to add the button to
66 if (!disclaimerEl) {
cjamcl@google.com21d2d222019-08-09 01:58:17 +000067 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000068 }
cjamcl@google.com21d2d222019-08-09 01:58:17 +000069
Paul Irish8f1e33d2018-05-31 02:29:50 +000070 const defaultPassTrace = artifacts.traces.defaultPass;
vidorteg06840022020-11-20 21:18:03 -080071 const label = simulated ? i18nString(UIStrings.viewOriginalTrace) : i18nString(UIStrings.viewTrace);
Adam Rainef46d1582020-08-17 20:24:32 +000072 const timelineButton = UI.UIUtils.createTextButton(label, onViewTraceClick, 'view-trace');
73 if (simulated) {
vidorteg06840022020-11-20 21:18:03 -080074 timelineButton.title = i18nString(UIStrings.thePerformanceMetricsAboveAre);
Adam Rainef46d1582020-08-17 20:24:32 +000075 }
Paul Irishbf204682020-05-13 16:11:37 -070076 container.insertBefore(timelineButton, disclaimerEl.nextSibling);
Paul Irish8f1e33d2018-05-31 02:29:50 +000077
78 async function onViewTraceClick() {
Tim van der Lippefe3cdc92020-11-06 15:23:13 +000079 Host.userMetrics.actionTaken(Host.UserMetrics.Action.LighthouseViewTrace);
Tim van der Lippe80d82652020-08-27 14:53:44 +010080 await UI.InspectorView.InspectorView.instance().showPanel('timeline');
Paul Lewisdaac1062020-03-05 14:37:10 +000081 Timeline.TimelinePanel.TimelinePanel.instance().loadFromEvents(defaultPassTrace.traceEvents);
Paul Irish8f1e33d2018-05-31 02:29:50 +000082 }
Patrick Hulcea087f622018-05-18 00:37:53 +000083 }
cjamcl@google.comda0e4c62018-11-27 23:52:10 +000084
85 /**
86 * @param {!Element} el
87 */
88 static async linkifyNodeDetails(el) {
Paul Lewisdaac1062020-03-05 14:37:10 +000089 const mainTarget = SDK.SDKModel.TargetManager.instance().mainTarget();
Tim van der Lippefe3cdc92020-11-06 15:23:13 +000090 if (!mainTarget) {
91 return;
92 }
Paul Lewisdaac1062020-03-05 14:37:10 +000093 const domModel = mainTarget.model(SDK.DOMModel.DOMModel);
Tim van der Lippefe3cdc92020-11-06 15:23:13 +000094 if (!domModel) {
95 return;
96 }
cjamcl@google.comda0e4c62018-11-27 23:52:10 +000097
98 for (const origElement of el.getElementsByClassName('lh-node')) {
Tim van der Lippefe3cdc92020-11-06 15:23:13 +000099 const origHTMLElement = /** @type {!HTMLElement} */ (origElement);
100 const detailsItem = /** @type {!ReportRenderer.NodeDetailsJSON} */ (origHTMLElement.dataset);
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000101 if (!detailsItem.path) {
cjamcl@google.com5c46b7e2018-12-04 14:44:47 +0000102 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000103 }
cjamcl@google.comda0e4c62018-11-27 23:52:10 +0000104
105 const nodeId = await domModel.pushNodeByPathToFrontend(detailsItem.path);
106
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000107 if (!nodeId) {
cjamcl@google.com5c46b7e2018-12-04 14:44:47 +0000108 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000109 }
cjamcl@google.comda0e4c62018-11-27 23:52:10 +0000110 const node = domModel.nodeForId(nodeId);
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000111 if (!node) {
cjamcl@google.com5c46b7e2018-12-04 14:44:47 +0000112 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000113 }
cjamcl@google.comda0e4c62018-11-27 23:52:10 +0000114
Tim van der Lippefe3cdc92020-11-06 15:23:13 +0000115 const element = await Common.Linkifier.Linkifier.linkify(
116 node, {tooltip: detailsItem.snippet, preventKeyboardFocus: undefined});
117 origHTMLElement.title = '';
118 origHTMLElement.textContent = '';
119 origHTMLElement.appendChild(element);
cjamcl@google.comda0e4c62018-11-27 23:52:10 +0000120 }
121 }
cjamcl@google.comf2f8c092019-05-30 22:01:56 +0000122
123 /**
124 * @param {!Element} el
125 */
Connor Clark0403a422019-11-18 18:03:18 -0800126 static async linkifySourceLocationDetails(el) {
127 for (const origElement of el.getElementsByClassName('lh-source-location')) {
Tim van der Lippefe3cdc92020-11-06 15:23:13 +0000128 const origHTMLElement = /** @type {!HTMLElement} */ (origElement);
129 const detailsItem = /** @type {!ReportRenderer.SourceLocationDetailsJSON} */ (origHTMLElement.dataset);
Connor Clark0403a422019-11-18 18:03:18 -0800130 if (!detailsItem.sourceUrl || !detailsItem.sourceLine || !detailsItem.sourceColumn) {
131 continue;
132 }
133 const url = detailsItem.sourceUrl;
134 const line = Number(detailsItem.sourceLine);
135 const column = Number(detailsItem.sourceColumn);
Tim van der Lippefe3cdc92020-11-06 15:23:13 +0000136 const element = await Components.Linkifier.Linkifier.linkifyURL(url, {
137 lineNumber: line,
138 columnNumber: column,
139 maxLength: MaxLengthForLinks,
140 bypassURLTrimming: undefined,
141 className: undefined,
142 preventClick: undefined,
143 tabStop: undefined,
144 text: undefined
145 });
146 origHTMLElement.title = '';
147 origHTMLElement.textContent = '';
148 origHTMLElement.appendChild(element);
Connor Clark0403a422019-11-18 18:03:18 -0800149 }
150 }
151
152 /**
153 * @param {!Element} el
154 */
cjamcl@google.comf2f8c092019-05-30 22:01:56 +0000155 static handleDarkMode(el) {
Paul Lewisca569a52020-09-09 17:11:51 +0100156 if (ThemeSupport.ThemeSupport.instance().themeName() === 'dark') {
cjamcl@google.comf2f8c092019-05-30 22:01:56 +0000157 el.classList.add('dark');
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000158 }
cjamcl@google.comf2f8c092019-05-30 22:01:56 +0000159 }
Paul Lewiscf2ef222019-11-22 14:55:35 +0000160}
cjamcl@google.comc5214af2019-06-25 20:31:21 +0000161
162/**
163 * @override
Tim van der Lippefe3cdc92020-11-06 15:23:13 +0000164 * @extends {ReportRenderer.ReportUIFeatures}
cjamcl@google.comc5214af2019-06-25 20:31:21 +0000165 */
Tim van der Lippefe3cdc92020-11-06 15:23:13 +0000166// @ts-ignore https://github.com/GoogleChrome/lighthouse/issues/11628
Tim van der Lippe1e10f852020-10-30 14:35:01 +0000167export class LighthouseReportUIFeatures extends self.ReportUIFeatures {
cjamcl@google.comc5214af2019-06-25 20:31:21 +0000168 /**
Connor Clark99508362019-08-20 19:52:23 +0000169 * @param {!DOM} dom
170 */
171 constructor(dom) {
172 super(dom);
173 this._beforePrint = null;
174 this._afterPrint = null;
175 }
176
177 /**
Tim van der Lippe4df32c92020-11-06 12:35:05 +0000178 * @override
179 * @param {?function():void} beforePrint
Connor Clark99508362019-08-20 19:52:23 +0000180 */
181 setBeforePrint(beforePrint) {
182 this._beforePrint = beforePrint;
183 }
184
185 /**
Tim van der Lippe4df32c92020-11-06 12:35:05 +0000186 * @override
187 * @param {?function():void} afterPrint
Connor Clark99508362019-08-20 19:52:23 +0000188 */
189 setAfterPrint(afterPrint) {
190 this._afterPrint = afterPrint;
191 }
192
193 /**
cjamcl@google.comc5214af2019-06-25 20:31:21 +0000194 * Returns the html that recreates this report.
195 * @return {string}
196 * @protected
197 */
198 getReportHtml() {
199 this.resetUIState();
Tim van der Lippefe3cdc92020-11-06 15:23:13 +0000200 // @ts-ignore https://github.com/GoogleChrome/lighthouse/issues/11628
cjamcl@google.comc5214af2019-06-25 20:31:21 +0000201 return Lighthouse.ReportGenerator.generateReportHtml(this.json);
202 }
203
204 /**
205 * Downloads a file (blob) using the system dialog prompt.
206 * @param {!Blob|!File} blob The file to save.
207 */
208 async _saveFile(blob) {
Paul Lewisdaac1062020-03-05 14:37:10 +0000209 const domain = new Common.ParsedURL.ParsedURL(this.json.finalUrl).domain();
cjamcl@google.comc5214af2019-06-25 20:31:21 +0000210 const sanitizedDomain = domain.replace(/[^a-z0-9.-]+/gi, '_');
Simon Zünd2c704cd2020-06-04 11:08:35 +0200211 const timestamp = Platform.DateUtilities.toISO8601Compact(new Date(this.json.fetchTime));
cjamcl@google.comc5214af2019-06-25 20:31:21 +0000212 const ext = blob.type.match('json') ? '.json' : '.html';
213 const basename = `${sanitizedDomain}-${timestamp}${ext}`;
214 const text = await blob.text();
Tim van der Lippe696c9262020-08-26 15:39:32 +0100215 Workspace.FileManager.FileManager.instance().save(basename, text, true /* forceSaveAs */);
cjamcl@google.comc5214af2019-06-25 20:31:21 +0000216 }
217
218 async _print() {
219 const document = this.getDocument();
Tim van der Lippefe3cdc92020-11-06 15:23:13 +0000220 const clonedReport = /** @type {!HTMLElement} */ (document.querySelector('.lh-root')).cloneNode(true /* deep */);
cjamcl@google.comc5214af2019-06-25 20:31:21 +0000221 const printWindow = window.open('', '_blank', 'channelmode=1,status=1,resizable=1');
Tim van der Lippefe3cdc92020-11-06 15:23:13 +0000222 if (!printWindow) {
223 return;
224 }
cjamcl@google.comc5214af2019-06-25 20:31:21 +0000225 const style = printWindow.document.createElement('style');
Tim van der Lippefe3cdc92020-11-06 15:23:13 +0000226 style.textContent = Root.Runtime.cachedResources.get('third_party/lighthouse/report-assets/report.css') || '';
cjamcl@google.comc5214af2019-06-25 20:31:21 +0000227 printWindow.document.head.appendChild(style);
228 printWindow.document.body.replaceWith(clonedReport);
229 // Linkified nodes are shadow elements, which aren't exposed via `cloneNode`.
Tim van der Lippefe3cdc92020-11-06 15:23:13 +0000230 await LighthouseReportRenderer.linkifyNodeDetails(/** @type {!HTMLElement} */ (clonedReport));
Connor Clark99508362019-08-20 19:52:23 +0000231
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000232 if (this._beforePrint) {
Connor Clark99508362019-08-20 19:52:23 +0000233 this._beforePrint();
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000234 }
cjamcl@google.comc5214af2019-06-25 20:31:21 +0000235 printWindow.focus();
236 printWindow.print();
237 printWindow.close();
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000238 if (this._afterPrint) {
Connor Clark99508362019-08-20 19:52:23 +0000239 this._afterPrint();
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000240 }
cjamcl@google.comc5214af2019-06-25 20:31:21 +0000241 }
242
243 /**
244 * @suppress {visibility}
245 * @return {!Document}
246 */
247 getDocument() {
248 return this._document;
249 }
250
251 /**
252 * @suppress {visibility}
253 */
254 resetUIState() {
255 this._resetUIState();
256 }
Paul Lewiscf2ef222019-11-22 14:55:35 +0000257}