blob: b3d00eb1d736cf21e286c6895c377909aa632a12 [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';
7import * as HostModule from '../host/host.js';
Simon Zünd2c704cd2020-06-04 11:08:35 +02008import * as Platform from '../platform/platform.js';
Paul Lewisdaac1062020-03-05 14:37:10 +00009import * as SDK from '../sdk/sdk.js';
10import * as Timeline from '../timeline/timeline.js';
11import * as UI from '../ui/ui.js';
12
Paul Lewiscf2ef222019-11-22 14:55:35 +000013const MaxLengthForLinks = 40;
14
Patrick Hulcea087f622018-05-18 00:37:53 +000015/**
16 * @override
17 */
Connor Clark2bc3be22020-02-14 14:34:19 -080018export class LighthouseReportRenderer extends ReportRenderer {
Patrick Hulcea087f622018-05-18 00:37:53 +000019 /**
Paul Irish8f1e33d2018-05-31 02:29:50 +000020 * @param {!Element} el Parent element to render the report into.
21 * @param {!ReportRenderer.RunnerResultArtifacts=} artifacts
Patrick Hulcea087f622018-05-18 00:37:53 +000022 */
Paul Irish8f1e33d2018-05-31 02:29:50 +000023 static addViewTraceButton(el, artifacts) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000024 if (!artifacts || !artifacts.traces || !artifacts.traces.defaultPass) {
Paul Irish8f1e33d2018-05-31 02:29:50 +000025 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000026 }
Patrick Hulcea087f622018-05-18 00:37:53 +000027
Adam Rainef46d1582020-08-17 20:24:32 +000028 const simulated = artifacts.settings.throttlingMethod === 'simulate';
cjamcl@google.com21d2d222019-08-09 01:58:17 +000029 const container = el.querySelector('.lh-audit-group');
Paul Irishbf204682020-05-13 16:11:37 -070030 const disclaimerEl = container.querySelector('.lh-metrics__disclaimer');
31 // If it was a PWA-only run, we'd have a trace but no perf category to add the button to
32 if (!disclaimerEl) {
cjamcl@google.com21d2d222019-08-09 01:58:17 +000033 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000034 }
cjamcl@google.com21d2d222019-08-09 01:58:17 +000035
Paul Irish8f1e33d2018-05-31 02:29:50 +000036 const defaultPassTrace = artifacts.traces.defaultPass;
Adam Rainef46d1582020-08-17 20:24:32 +000037 const label = simulated ? Common.UIString.UIString('View Original Trace') : Common.UIString.UIString('View Trace');
38 const timelineButton = UI.UIUtils.createTextButton(label, onViewTraceClick, 'view-trace');
39 if (simulated) {
40 timelineButton.title = Common.UIString.UIString(
41 '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.');
42 }
Paul Irishbf204682020-05-13 16:11:37 -070043 container.insertBefore(timelineButton, disclaimerEl.nextSibling);
Paul Irish8f1e33d2018-05-31 02:29:50 +000044
45 async function onViewTraceClick() {
Paul Lewisdaac1062020-03-05 14:37:10 +000046 HostModule.userMetrics.actionTaken(Host.UserMetrics.Action.LighthouseViewTrace);
Paul Lewis0a7c6b62020-01-23 16:16:22 +000047 await self.UI.inspectorView.showPanel('timeline');
Paul Lewisdaac1062020-03-05 14:37:10 +000048 Timeline.TimelinePanel.TimelinePanel.instance().loadFromEvents(defaultPassTrace.traceEvents);
Paul Irish8f1e33d2018-05-31 02:29:50 +000049 }
Patrick Hulcea087f622018-05-18 00:37:53 +000050 }
cjamcl@google.comda0e4c62018-11-27 23:52:10 +000051
52 /**
53 * @param {!Element} el
54 */
55 static async linkifyNodeDetails(el) {
Paul Lewisdaac1062020-03-05 14:37:10 +000056 const mainTarget = SDK.SDKModel.TargetManager.instance().mainTarget();
57 const domModel = mainTarget.model(SDK.DOMModel.DOMModel);
cjamcl@google.comda0e4c62018-11-27 23:52:10 +000058
59 for (const origElement of el.getElementsByClassName('lh-node')) {
60 /** @type {!DetailsRenderer.NodeDetailsJSON} */
61 const detailsItem = origElement.dataset;
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000062 if (!detailsItem.path) {
cjamcl@google.com5c46b7e2018-12-04 14:44:47 +000063 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000064 }
cjamcl@google.comda0e4c62018-11-27 23:52:10 +000065
66 const nodeId = await domModel.pushNodeByPathToFrontend(detailsItem.path);
67
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000068 if (!nodeId) {
cjamcl@google.com5c46b7e2018-12-04 14:44:47 +000069 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000070 }
cjamcl@google.comda0e4c62018-11-27 23:52:10 +000071 const node = domModel.nodeForId(nodeId);
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000072 if (!node) {
cjamcl@google.com5c46b7e2018-12-04 14:44:47 +000073 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000074 }
cjamcl@google.comda0e4c62018-11-27 23:52:10 +000075
Paul Lewisdaac1062020-03-05 14:37:10 +000076 const element = await Common.Linkifier.Linkifier.linkify(node, {tooltip: detailsItem.snippet});
cjamcl@google.comda0e4c62018-11-27 23:52:10 +000077 origElement.title = '';
78 origElement.textContent = '';
79 origElement.appendChild(element);
80 }
81 }
cjamcl@google.comf2f8c092019-05-30 22:01:56 +000082
83 /**
84 * @param {!Element} el
85 */
Connor Clark0403a422019-11-18 18:03:18 -080086 static async linkifySourceLocationDetails(el) {
87 for (const origElement of el.getElementsByClassName('lh-source-location')) {
88 /** @type {!DetailsRenderer.SourceLocationDetailsJSON} */
89 const detailsItem = origElement.dataset;
90 if (!detailsItem.sourceUrl || !detailsItem.sourceLine || !detailsItem.sourceColumn) {
91 continue;
92 }
93 const url = detailsItem.sourceUrl;
94 const line = Number(detailsItem.sourceLine);
95 const column = Number(detailsItem.sourceColumn);
Paul Lewisdaac1062020-03-05 14:37:10 +000096 const element = await Components.Linkifier.Linkifier.linkifyURL(
97 url, {lineNumber: line, column, maxLength: MaxLengthForLinks});
Connor Clark0403a422019-11-18 18:03:18 -080098 origElement.title = '';
99 origElement.textContent = '';
100 origElement.appendChild(element);
101 }
102 }
103
104 /**
105 * @param {!Element} el
106 */
cjamcl@google.comf2f8c092019-05-30 22:01:56 +0000107 static handleDarkMode(el) {
Paul Lewis93d8e2c2020-01-24 16:34:55 +0000108 if (self.UI.themeSupport.themeName() === 'dark') {
cjamcl@google.comf2f8c092019-05-30 22:01:56 +0000109 el.classList.add('dark');
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000110 }
cjamcl@google.comf2f8c092019-05-30 22:01:56 +0000111 }
Paul Lewiscf2ef222019-11-22 14:55:35 +0000112}
cjamcl@google.comc5214af2019-06-25 20:31:21 +0000113
114/**
115 * @override
116 */
Connor Clark2bc3be22020-02-14 14:34:19 -0800117export class LighthouseReportUIFeatures extends ReportUIFeatures {
cjamcl@google.comc5214af2019-06-25 20:31:21 +0000118 /**
Connor Clark99508362019-08-20 19:52:23 +0000119 * @param {!DOM} dom
120 */
121 constructor(dom) {
122 super(dom);
123 this._beforePrint = null;
124 this._afterPrint = null;
125 }
126
127 /**
128 * @param {?function()} beforePrint
129 */
130 setBeforePrint(beforePrint) {
131 this._beforePrint = beforePrint;
132 }
133
134 /**
135 * @param {?function()} afterPrint
136 */
137 setAfterPrint(afterPrint) {
138 this._afterPrint = afterPrint;
139 }
140
141 /**
cjamcl@google.comc5214af2019-06-25 20:31:21 +0000142 * Returns the html that recreates this report.
143 * @return {string}
144 * @protected
145 */
146 getReportHtml() {
147 this.resetUIState();
148 return Lighthouse.ReportGenerator.generateReportHtml(this.json);
149 }
150
151 /**
152 * Downloads a file (blob) using the system dialog prompt.
153 * @param {!Blob|!File} blob The file to save.
154 */
155 async _saveFile(blob) {
Paul Lewisdaac1062020-03-05 14:37:10 +0000156 const domain = new Common.ParsedURL.ParsedURL(this.json.finalUrl).domain();
cjamcl@google.comc5214af2019-06-25 20:31:21 +0000157 const sanitizedDomain = domain.replace(/[^a-z0-9.-]+/gi, '_');
Simon Zünd2c704cd2020-06-04 11:08:35 +0200158 const timestamp = Platform.DateUtilities.toISO8601Compact(new Date(this.json.fetchTime));
cjamcl@google.comc5214af2019-06-25 20:31:21 +0000159 const ext = blob.type.match('json') ? '.json' : '.html';
160 const basename = `${sanitizedDomain}-${timestamp}${ext}`;
161 const text = await blob.text();
Paul Lewisdff48e42020-01-24 11:46:48 +0000162 self.Workspace.fileManager.save(basename, text, true /* forceSaveAs */);
cjamcl@google.comc5214af2019-06-25 20:31:21 +0000163 }
164
165 async _print() {
166 const document = this.getDocument();
167 const clonedReport = document.querySelector('.lh-root').cloneNode(true /* deep */);
168 const printWindow = window.open('', '_blank', 'channelmode=1,status=1,resizable=1');
169 const style = printWindow.document.createElement('style');
Tim van der Lippe6d51bf02020-03-18 12:15:14 +0000170 style.textContent = self.Runtime.cachedResources['third_party/lighthouse/report-assets/report.css'];
cjamcl@google.comc5214af2019-06-25 20:31:21 +0000171 printWindow.document.head.appendChild(style);
172 printWindow.document.body.replaceWith(clonedReport);
173 // Linkified nodes are shadow elements, which aren't exposed via `cloneNode`.
Connor Clark2bc3be22020-02-14 14:34:19 -0800174 await LighthouseReportRenderer.linkifyNodeDetails(clonedReport);
Connor Clark99508362019-08-20 19:52:23 +0000175
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000176 if (this._beforePrint) {
Connor Clark99508362019-08-20 19:52:23 +0000177 this._beforePrint();
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000178 }
cjamcl@google.comc5214af2019-06-25 20:31:21 +0000179 printWindow.focus();
180 printWindow.print();
181 printWindow.close();
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000182 if (this._afterPrint) {
Connor Clark99508362019-08-20 19:52:23 +0000183 this._afterPrint();
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000184 }
cjamcl@google.comc5214af2019-06-25 20:31:21 +0000185 }
186
187 /**
188 * @suppress {visibility}
189 * @return {!Document}
190 */
191 getDocument() {
192 return this._document;
193 }
194
195 /**
196 * @suppress {visibility}
197 */
198 resetUIState() {
199 this._resetUIState();
200 }
Paul Lewiscf2ef222019-11-22 14:55:35 +0000201}