blob: 2011ad2d1d0f10933c288bf4881fe6662322b515 [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';
8import * as SDK from '../sdk/sdk.js';
9import * as Timeline from '../timeline/timeline.js';
10import * as UI from '../ui/ui.js';
11
Paul Lewiscf2ef222019-11-22 14:55:35 +000012const MaxLengthForLinks = 40;
13
Patrick Hulcea087f622018-05-18 00:37:53 +000014/**
15 * @override
16 */
Connor Clark2bc3be22020-02-14 14:34:19 -080017export class LighthouseReportRenderer extends ReportRenderer {
Patrick Hulcea087f622018-05-18 00:37:53 +000018 /**
Paul Irish8f1e33d2018-05-31 02:29:50 +000019 * @param {!Element} el Parent element to render the report into.
20 * @param {!ReportRenderer.RunnerResultArtifacts=} artifacts
Patrick Hulcea087f622018-05-18 00:37:53 +000021 */
Paul Irish8f1e33d2018-05-31 02:29:50 +000022 static addViewTraceButton(el, artifacts) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000023 if (!artifacts || !artifacts.traces || !artifacts.traces.defaultPass) {
Paul Irish8f1e33d2018-05-31 02:29:50 +000024 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000025 }
Patrick Hulcea087f622018-05-18 00:37:53 +000026
cjamcl@google.com21d2d222019-08-09 01:58:17 +000027 const container = el.querySelector('.lh-audit-group');
28 const columnsEl = container.querySelector('.lh-columns');
29 // There will be no columns if just the PWA category.
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000030 if (!columnsEl) {
cjamcl@google.com21d2d222019-08-09 01:58:17 +000031 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000032 }
cjamcl@google.com21d2d222019-08-09 01:58:17 +000033
Paul Irish8f1e33d2018-05-31 02:29:50 +000034 const defaultPassTrace = artifacts.traces.defaultPass;
Paul Lewisdaac1062020-03-05 14:37:10 +000035 const timelineButton =
36 UI.UIUtils.createTextButton(Common.UIString.UIString('View Trace'), onViewTraceClick, 'view-trace');
cjamcl@google.com21d2d222019-08-09 01:58:17 +000037 container.insertBefore(timelineButton, columnsEl.nextSibling);
Paul Irish8f1e33d2018-05-31 02:29:50 +000038
39 async function onViewTraceClick() {
Paul Lewisdaac1062020-03-05 14:37:10 +000040 HostModule.userMetrics.actionTaken(Host.UserMetrics.Action.LighthouseViewTrace);
Paul Lewis0a7c6b62020-01-23 16:16:22 +000041 await self.UI.inspectorView.showPanel('timeline');
Paul Lewisdaac1062020-03-05 14:37:10 +000042 Timeline.TimelinePanel.TimelinePanel.instance().loadFromEvents(defaultPassTrace.traceEvents);
Paul Irish8f1e33d2018-05-31 02:29:50 +000043 }
Patrick Hulcea087f622018-05-18 00:37:53 +000044 }
cjamcl@google.comda0e4c62018-11-27 23:52:10 +000045
46 /**
47 * @param {!Element} el
48 */
49 static async linkifyNodeDetails(el) {
Paul Lewisdaac1062020-03-05 14:37:10 +000050 const mainTarget = SDK.SDKModel.TargetManager.instance().mainTarget();
51 const domModel = mainTarget.model(SDK.DOMModel.DOMModel);
cjamcl@google.comda0e4c62018-11-27 23:52:10 +000052
53 for (const origElement of el.getElementsByClassName('lh-node')) {
54 /** @type {!DetailsRenderer.NodeDetailsJSON} */
55 const detailsItem = origElement.dataset;
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000056 if (!detailsItem.path) {
cjamcl@google.com5c46b7e2018-12-04 14:44:47 +000057 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000058 }
cjamcl@google.comda0e4c62018-11-27 23:52:10 +000059
60 const nodeId = await domModel.pushNodeByPathToFrontend(detailsItem.path);
61
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000062 if (!nodeId) {
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 const node = domModel.nodeForId(nodeId);
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000066 if (!node) {
cjamcl@google.com5c46b7e2018-12-04 14:44:47 +000067 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +000068 }
cjamcl@google.comda0e4c62018-11-27 23:52:10 +000069
Paul Lewisdaac1062020-03-05 14:37:10 +000070 const element = await Common.Linkifier.Linkifier.linkify(node, {tooltip: detailsItem.snippet});
cjamcl@google.comda0e4c62018-11-27 23:52:10 +000071 origElement.title = '';
72 origElement.textContent = '';
73 origElement.appendChild(element);
74 }
75 }
cjamcl@google.comf2f8c092019-05-30 22:01:56 +000076
77 /**
78 * @param {!Element} el
79 */
Connor Clark0403a422019-11-18 18:03:18 -080080 static async linkifySourceLocationDetails(el) {
81 for (const origElement of el.getElementsByClassName('lh-source-location')) {
82 /** @type {!DetailsRenderer.SourceLocationDetailsJSON} */
83 const detailsItem = origElement.dataset;
84 if (!detailsItem.sourceUrl || !detailsItem.sourceLine || !detailsItem.sourceColumn) {
85 continue;
86 }
87 const url = detailsItem.sourceUrl;
88 const line = Number(detailsItem.sourceLine);
89 const column = Number(detailsItem.sourceColumn);
Paul Lewisdaac1062020-03-05 14:37:10 +000090 const element = await Components.Linkifier.Linkifier.linkifyURL(
91 url, {lineNumber: line, column, maxLength: MaxLengthForLinks});
Connor Clark0403a422019-11-18 18:03:18 -080092 origElement.title = '';
93 origElement.textContent = '';
94 origElement.appendChild(element);
95 }
96 }
97
98 /**
99 * @param {!Element} el
100 */
cjamcl@google.comf2f8c092019-05-30 22:01:56 +0000101 static handleDarkMode(el) {
Paul Lewis93d8e2c2020-01-24 16:34:55 +0000102 if (self.UI.themeSupport.themeName() === 'dark') {
cjamcl@google.comf2f8c092019-05-30 22:01:56 +0000103 el.classList.add('dark');
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000104 }
cjamcl@google.comf2f8c092019-05-30 22:01:56 +0000105 }
Paul Lewiscf2ef222019-11-22 14:55:35 +0000106}
cjamcl@google.comc5214af2019-06-25 20:31:21 +0000107
108/**
109 * @override
110 */
Connor Clark2bc3be22020-02-14 14:34:19 -0800111export class LighthouseReportUIFeatures extends ReportUIFeatures {
cjamcl@google.comc5214af2019-06-25 20:31:21 +0000112 /**
Connor Clark99508362019-08-20 19:52:23 +0000113 * @param {!DOM} dom
114 */
115 constructor(dom) {
116 super(dom);
117 this._beforePrint = null;
118 this._afterPrint = null;
119 }
120
121 /**
122 * @param {?function()} beforePrint
123 */
124 setBeforePrint(beforePrint) {
125 this._beforePrint = beforePrint;
126 }
127
128 /**
129 * @param {?function()} afterPrint
130 */
131 setAfterPrint(afterPrint) {
132 this._afterPrint = afterPrint;
133 }
134
135 /**
cjamcl@google.comc5214af2019-06-25 20:31:21 +0000136 * Returns the html that recreates this report.
137 * @return {string}
138 * @protected
139 */
140 getReportHtml() {
141 this.resetUIState();
142 return Lighthouse.ReportGenerator.generateReportHtml(this.json);
143 }
144
145 /**
146 * Downloads a file (blob) using the system dialog prompt.
147 * @param {!Blob|!File} blob The file to save.
148 */
149 async _saveFile(blob) {
Paul Lewisdaac1062020-03-05 14:37:10 +0000150 const domain = new Common.ParsedURL.ParsedURL(this.json.finalUrl).domain();
cjamcl@google.comc5214af2019-06-25 20:31:21 +0000151 const sanitizedDomain = domain.replace(/[^a-z0-9.-]+/gi, '_');
152 const timestamp = new Date(this.json.fetchTime).toISO8601Compact();
153 const ext = blob.type.match('json') ? '.json' : '.html';
154 const basename = `${sanitizedDomain}-${timestamp}${ext}`;
155 const text = await blob.text();
Paul Lewisdff48e42020-01-24 11:46:48 +0000156 self.Workspace.fileManager.save(basename, text, true /* forceSaveAs */);
cjamcl@google.comc5214af2019-06-25 20:31:21 +0000157 }
158
159 async _print() {
160 const document = this.getDocument();
161 const clonedReport = document.querySelector('.lh-root').cloneNode(true /* deep */);
162 const printWindow = window.open('', '_blank', 'channelmode=1,status=1,resizable=1');
163 const style = printWindow.document.createElement('style');
Tim van der Lippe6d51bf02020-03-18 12:15:14 +0000164 style.textContent = self.Runtime.cachedResources['third_party/lighthouse/report-assets/report.css'];
cjamcl@google.comc5214af2019-06-25 20:31:21 +0000165 printWindow.document.head.appendChild(style);
166 printWindow.document.body.replaceWith(clonedReport);
167 // Linkified nodes are shadow elements, which aren't exposed via `cloneNode`.
Connor Clark2bc3be22020-02-14 14:34:19 -0800168 await LighthouseReportRenderer.linkifyNodeDetails(clonedReport);
Connor Clark99508362019-08-20 19:52:23 +0000169
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000170 if (this._beforePrint) {
Connor Clark99508362019-08-20 19:52:23 +0000171 this._beforePrint();
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000172 }
cjamcl@google.comc5214af2019-06-25 20:31:21 +0000173 printWindow.focus();
174 printWindow.print();
175 printWindow.close();
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000176 if (this._afterPrint) {
Connor Clark99508362019-08-20 19:52:23 +0000177 this._afterPrint();
Tim van der Lippe1d6e57a2019-09-30 11:55:34 +0000178 }
cjamcl@google.comc5214af2019-06-25 20:31:21 +0000179 }
180
181 /**
182 * @suppress {visibility}
183 * @return {!Document}
184 */
185 getDocument() {
186 return this._document;
187 }
188
189 /**
190 * @suppress {visibility}
191 */
192 resetUIState() {
193 this._resetUIState();
194 }
Paul Lewiscf2ef222019-11-22 14:55:35 +0000195}