Report: open in Lighthouse Viewer button (#1179)
This commit is contained in:
Родитель
96173a2302
Коммит
ec97c356a0
|
@ -19,9 +19,38 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Opens a new tab to the online viewer and sends the local page's JSON results
|
||||
* to the online viewer using postMessage.
|
||||
*/
|
||||
function sendJSONReport() {
|
||||
const VIEWER_ORIGIN = 'https://googlechrome.github.io';
|
||||
const VIEWER_URL = `${VIEWER_ORIGIN}/lighthouse/viewer/`;
|
||||
|
||||
// Chrome doesn't allow us to immediately postMessage to a popup right
|
||||
// after it's created. Normally, we could also listen for the popup window's
|
||||
// load event, however it is cross-domain and won't fire. Instead, listen
|
||||
// for a message from the target app saying "I'm open".
|
||||
window.addEventListener('message', function msgHandler(e) {
|
||||
if (e.origin !== VIEWER_ORIGIN) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.data.opened) {
|
||||
popup.postMessage({lhresults: window.lhresults}, VIEWER_ORIGIN);
|
||||
window.removeEventListener('message', msgHandler);
|
||||
}
|
||||
});
|
||||
|
||||
const popup = window.open(VIEWER_URL, '_blank');
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', _ => {
|
||||
const printButton = document.querySelector('.js-print');
|
||||
printButton.addEventListener('click', _ => {
|
||||
window.print();
|
||||
});
|
||||
|
||||
const openButton = document.querySelector('.js-open');
|
||||
openButton.addEventListener('click', sendJSONReport);
|
||||
});
|
||||
|
|
|
@ -134,6 +134,11 @@ body {
|
|||
background-size: contain;
|
||||
background-color: transparent;
|
||||
margin-left: 8px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.report-body__icon:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.report-body__icon.print {
|
||||
|
@ -143,6 +148,17 @@ body {
|
|||
.report-body__icon.share {
|
||||
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92 1.61 0 2.92-1.31 2.92-2.92s-1.31-2.92-2.92-2.92z"/></svg>');
|
||||
display: none;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.report-body__icon.copy {
|
||||
background-image: url('data:image/svg+xml;utf8,<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z" fill="none"/><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>');
|
||||
height: 20px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.report-body__icon.open {
|
||||
background-image: url('data:image/svg+xml;utf8,<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h4v-2H5V8h14v10h-4v2h4c1.1 0 2-.9 2-2V6c0-1.1-.89-2-2-2zm-7 6l-4 4h3v6h2v-6h3l-4-4z"/></svg>');
|
||||
}
|
||||
|
||||
.report-body__icon.copy {
|
||||
|
@ -159,6 +175,10 @@ body {
|
|||
animation: rotate 1000ms infinite;
|
||||
}
|
||||
|
||||
#lhresults-dump {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
from {
|
||||
transform: none;
|
||||
|
|
|
@ -24,9 +24,6 @@ limitations under the License.
|
|||
<link rel="shortcut icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAEfElEQVR4Aa2WA5B0RxDH/z3zuDp/tq2Lbdu2XUoxKMW2bdu2nZxte/dpplO3hTjZ2s0z5zftpkVrFXJYGCBAggEoUPY2p0XkOLoJNoE+yH5II3vLOQJyHL0bsgpiC9G1meishuiBzJEhchm9HXIL0fu5e949TuX1cvVbxplbiu6O3BjiP19HEFMpuNI9Z4O8XpIlKb6SbrnQOG0WeQGkKATAgAVdz3Sw/G6BenwoXAnls/JH1IqZ0fN70DeN2Q84b4AA0jBWkHewfXsUh0ykKSZTcVGamoincKR7xyrKpGH8uxDGv0vgmOqXZuf1o87ccP6wCpIkgb5ePZw2Yvj0icN+eshdMUcFIRVgAwIyGHTKUGqLEluUOWJqkpISRRi2S+BDgAuyAYHhwYsIxCACM/wMSQEBNXkLAjhvFVnEP2WMYzdvO6/4muDZMVCavSD8ujHDQkmxe90VP8y+5s1oznSoCJSfkRkC07yBhd3XRz+m1bd9/tN14XMd3kutI4/5s1qfnmEPjWgIcL5GZoCQUcbA56Cpw4g8TguxZBYiloomfGRCE1SAigKIxZKfG1rCW7cddOhIFIEEEWutIeLmyy+nXni8bMkMDiORv5uaxHW+NRSbFhbL0AeBI9MOyZyRbkpO2D2WVQ7lgSg/L6IsA8xJ0zMiX5GMpFnc37L+/bs3uX/d8rrPkYJQXFAuIgJ8UNwMLaest7Hy3Vs2e/S4OZ9dB4YV09CgAnOR0IwkzR5p2+SNSzd+9Jhp394M6YbJBRCuISYAFJpN5SQAy2q/SP1wg3JnRIlFDAIRokwi6If4XyoaQzohYgmAASGiCWv4W6QqR5asQgBNVGjBAWDLcSAug1Fj9FOvaH7tztd+fOhDzZXbYoiVKBwgkAiGMdYzPnVN1V6PfXLI9dUb9vHtZBwZ6RAYlHegAVkNBAjnzKzf4om6WVso2zFD3/HGyYZpGimjsJJJgBKUmOCarfdpXLYdAbY/QVplwVLHHMMEc4FGZhg2HBFZfppYg6GF4bkJGXiLWz6aYfg+CyoEoDVcC4ahmaGk6Ttx2x9b9t3Lmz9+9tp3Li5yw0CD8sumTExi8ujGoFwzbRuxgZ45tR/M+OlpZ+ATuHP9otluvxfqJBGzBmn6bwALhmQApAT5knyoYcRKsKC3Y+U3rxZ/8xzGPkNsLcwNSIcp3ZfqFpkxUNIgB7CZDQUAikjTHwEENjSYaFxiEIiACuYZozx1yC8dSZZ31U9cXl9W1bfvosDYkXRoMMcjuyjggcq37cGl0UBK95RQV5LasqOVACnNQiMSxKCFlSFFEp0EG3p1v15Rr+b/oqdWcVETOx0whpXwe3k+yAKHwB+63hR1JDjSUYnwp9PoPNG3TDSvkFVLxA/TMQTMBDsRLZrGPDVUO34TrX9LzfyQ3ToiQBVBJ6BtsCRGjEJiZqLfezDAabY0AaRAAeQE5CgjJG+u6NnS+HFH451NqSZO8879PNjpLl3xFnEMYQWUAzBIAxrEyHUhsJjcQBABzEEWA2Jkc/Ojk38Fe4MpHMjZ+XoAAAAASUVORK5CYII=">
|
||||
<title>Lighthouse report: {{ url }}</title>
|
||||
<style>{{{ css }}}</style>
|
||||
{{#each scripts }}
|
||||
<script>{{{ this }}}</script>
|
||||
{{/each}}
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
@ -47,10 +44,12 @@ limitations under the License.
|
|||
<a href="#" data-action="save-json">Save as JSON...</a>
|
||||
</ul>
|
||||
</span>
|
||||
<button class="report-body__icon share js-share" title="Share report on Github"></button>
|
||||
<button class="report-body__icon copy js-copy" title="Copy JSON report"></button>
|
||||
<button class="report-body__icon open js-open" title="Open in Lighthouse Viewer"></button>
|
||||
<button class="report-body__icon rerun-button js-rerun-button" title="Rerun this test"></button>
|
||||
<button class="report-body__icon share js-share"></button>
|
||||
{{#if_not_eq reportContext "viewer"}}
|
||||
<button class="report-body__icon print js-print"></button>
|
||||
<button class="report-body__icon print js-print" title="Print HTML report"></button>
|
||||
{{/if_not_eq}}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -177,3 +176,17 @@ limitations under the License.
|
|||
</footer>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{{#if (and lhresults scripts) }}
|
||||
{{!-- Use escaped results to prevent XSS from result details. --}}
|
||||
<div id="lhresults-dump">
|
||||
{{lhresults}}
|
||||
</div>
|
||||
<script>
|
||||
window.lhresults = JSON.parse(document.querySelector('#lhresults-dump').textContent);
|
||||
</script>
|
||||
{{/if}}
|
||||
|
||||
{{#each scripts }}
|
||||
<script>{{{ this }}}</script>
|
||||
{{/each}}
|
||||
|
|
|
@ -64,12 +64,30 @@ describe('Report', () => {
|
|||
const html = reportGenerator.generateHTML(sampleResults);
|
||||
|
||||
assert.ok(html.includes('<footer'), 'no footer tag found');
|
||||
assert.ok(html.includes('printButton = document.querySelector'),
|
||||
'lighthouse-report.js was not inlined');
|
||||
assert.ok(html.includes('.report-body {'), 'report.css was not inlined');
|
||||
assert.ok(!html.includes('"lighthouseVersion'), 'lhresults were not escaped');
|
||||
assert.ok(html.includes('<div id="lhresults-dump">'), 'report results were inlined');
|
||||
assert.ok(html.includes('window.lhresults = JSON.parse('), 'lhresults created');
|
||||
assert.ok(html.includes('.report-body {'), 'report.css inlined');
|
||||
assert.ok(html.includes('"lighthouseVersion'), 'lhresults were escaped');
|
||||
assert.ok(/Version: x\.x\.x/g.test(html), 'Version doesn\'t appear in report');
|
||||
assert.ok(html.includes('export-button'), 'page includes export button');
|
||||
|
||||
assert.ok(html.includes('printButton = document.querySelector'),
|
||||
'print button functionality attached');
|
||||
assert.ok(html.includes('openButton = document.querySelector'),
|
||||
'open button functionality attached');
|
||||
assert.ok(html.includes('share js-share'), 'has share button');
|
||||
assert.ok(html.includes('copy js-copy'), 'has copy button');
|
||||
assert.ok(html.includes('open js-open'), 'has open button');
|
||||
assert.ok(html.includes('print js-print'), 'has print button');
|
||||
});
|
||||
|
||||
it('does not include script for devtools', () => {
|
||||
const reportGenerator = new ReportGenerator();
|
||||
const html = reportGenerator.generateHTML(sampleResults, 'devtools');
|
||||
|
||||
assert.ok(!html.includes('<script'), 'script tag inlined');
|
||||
assert.ok(!html.includes('<div id="lhresults-dump">'), 'report results were inlined');
|
||||
assert.ok(!html.includes('window.lhresults = JSON.parse('), 'lhresults created');
|
||||
});
|
||||
|
||||
it('sanitizes JSON input', () => {
|
||||
|
@ -109,6 +127,5 @@ describe('Report', () => {
|
|||
'anchors are transformed');
|
||||
assert.ok(!html.includes(
|
||||
'<img src="test.gif" onerror="alert(10)">'), 'non-recognized HTML is sanitized');
|
||||
assert.ok(!html.includes('<b>html should not</b>'), 'non-recognized HTML is sanitized');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -57,6 +57,7 @@ class LighthouseViewerReport {
|
|||
|
||||
this.initUI();
|
||||
this.loadFromDeepLink();
|
||||
this.listenForMessages();
|
||||
}
|
||||
|
||||
initUI() {
|
||||
|
@ -67,9 +68,9 @@ class LighthouseViewerReport {
|
|||
// Disable the share button after the user shares the gist or if we're loading
|
||||
// a gist from Github. In both cases, the gist is already shared :)
|
||||
if (this._isNewReport) {
|
||||
this.enableShareButton();
|
||||
this.enableButton(this.shareButton);
|
||||
} else {
|
||||
this.disableShareButton();
|
||||
this.disableButton(this.shareButton);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,16 +89,21 @@ class LighthouseViewerReport {
|
|||
if (gistURLInput) {
|
||||
gistURLInput.addEventListener('change', this.onInputChange);
|
||||
}
|
||||
|
||||
// Disable "Open in Lighthouse Viewer" button. We're already in the viewer.
|
||||
this.disableButton(document.querySelector('.js-open'));
|
||||
}
|
||||
|
||||
enableShareButton() {
|
||||
this.shareButton.classList.remove('disable');
|
||||
this.shareButton.disabled = false;
|
||||
enableButton(button) {
|
||||
if (button) {
|
||||
button.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
disableShareButton() {
|
||||
this.shareButton.classList.add('disable');
|
||||
this.shareButton.disabled = true;
|
||||
disableButton(button) {
|
||||
if (button) {
|
||||
button.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
closeExportDropdown() {
|
||||
|
@ -214,7 +220,7 @@ class LighthouseViewerReport {
|
|||
return this.github.createGist(this.json).then(id => {
|
||||
ga('send', 'event', 'report', 'created');
|
||||
|
||||
this.disableShareButton();
|
||||
this.disableButton(this.shareButton);
|
||||
history.pushState({}, null, `${APP_URL}?gist=${id}`);
|
||||
|
||||
return id;
|
||||
|
@ -399,6 +405,23 @@ class LighthouseViewerReport {
|
|||
this.closeExportDropdown();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Initializes of a `message` listener to respond to postMessage events.
|
||||
*/
|
||||
listenForMessages() {
|
||||
window.addEventListener('message', e => {
|
||||
if (e.source === self.opener && e.data.lhresults) {
|
||||
this.replaceReportHTML(e.data.lhresults);
|
||||
ga('send', 'event', 'report', 'open in viewer');
|
||||
}
|
||||
});
|
||||
|
||||
// If the page was opened as a popup, tell the opening window we're ready.
|
||||
if (self.opener && !self.opener.closed) {
|
||||
self.opener.postMessage({opened: true}, '*');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LighthouseViewerReport;
|
||||
|
|
|
@ -116,7 +116,8 @@
|
|||
#log.show {
|
||||
transform: translateY(0);
|
||||
}
|
||||
.share:disabled {
|
||||
.share:disabled,
|
||||
.open:disabled {
|
||||
opacity: 0.2;
|
||||
cursor: default;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче