Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
45b289e
Report header
ebidel Apr 12, 2017
9f8014d
Reponsive styling. Use share icon instead of "Export..."
ebidel Apr 12, 2017
fef3a99
Runtime settings
ebidel Apr 12, 2017
8b4d2e7
Support report re-ender into page
ebidel Apr 12, 2017
7d9e20c
Add report functionality
ebidel Apr 12, 2017
6887af7
Split files
ebidel Apr 12, 2017
2b84229
Remove css focus stuff
ebidel Apr 12, 2017
b138885
Remove placeholders
ebidel Apr 12, 2017
c9633af
Fix tests
ebidel Apr 12, 2017
ee7fcb1
Feedback
ebidel Apr 13, 2017
3b7e90f
lighthouse -> lh
ebidel Apr 13, 2017
c717870
Tests for header/footer
ebidel Apr 13, 2017
85d6b8e
Tests for header/footer/formatter helps
ebidel Apr 13, 2017
2ff34f7
Add more tests
ebidel Apr 13, 2017
e9751c7
Fix rebase
ebidel Apr 13, 2017
2918469
Fix travis tests
ebidel Apr 14, 2017
2a60c3a
Feedback and leftnav
ebidel Apr 15, 2017
121fda3
Add 2nd arg
ebidel Apr 15, 2017
a445a6e
Styling tweaks
ebidel Apr 15, 2017
703043a
Leftnav tests
ebidel Apr 15, 2017
c237657
Disable open in viewer in ui
ebidel Apr 15, 2017
d7f3c29
Adjust passing audits summary
ebidel Apr 15, 2017
aa93acc
Merge branch 'master' into GoogleChrome-tablesclear
patrickhulce Apr 21, 2017
3899e47
Merge after rebase
ebidel Apr 25, 2017
33021e5
Fix up styling after merge
ebidel Apr 25, 2017
31a7385
Update dom usage and reset state of template stamping when exporting …
ebidel Apr 25, 2017
d33cc21
Closure cleanup
ebidel Apr 25, 2017
4bbba21
cleanup
ebidel Apr 26, 2017
a30c86e
Tests
ebidel Apr 26, 2017
d8ce09f
lh-analytics custom event
ebidel Apr 26, 2017
777adfe
fix travis
ebidel Apr 26, 2017
a1ffbf5
Travis fix?
ebidel Apr 26, 2017
e542539
bckenny feedback
ebidel Apr 28, 2017
8bf076f
3 big changes
ebidel Apr 28, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add report functionality
  • Loading branch information
ebidel committed Apr 15, 2017
commit 7d9e20c0464c3ea9d5b589d2a338ab2f701c5b4c
73 changes: 73 additions & 0 deletions lighthouse-core/report/v2/logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* @license
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

/* eslint-env browser */

/**
* Logs messages via a UI butter.
* @class
*/
class Logger {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a straight port from v1.

/**
* @param {!Element} element
*/
constructor(element) {
this.el = element;
}

/**
* Shows a butter bar.
* @param {!string} msg The message to show.
* @param {boolean=} optAutoHide True to hide the message after a duration.
* Default is true.
*/
log(msg, optAutoHide) {
const autoHide = typeof optAutoHide === 'undefined' ? true : optAutoHide;

clearTimeout(this._id);

this.el.textContent = msg;
this.el.classList.add('show');
if (autoHide) {
this._id = setTimeout(_ => {
this.el.classList.remove('show');
}, 7000);
}
}

warn(msg) {
this.log('Warning: ' + msg);
}

error(msg) {
this.log(msg);
}

/**
* Explicitly hides the butter bar.
*/
hide() {
clearTimeout(this._id);
this.el.classList.remove('show');
}
}

if (typeof module !== 'undefined' && module.exports) {
module.exports = Logger;
}
249 changes: 249 additions & 0 deletions lighthouse-core/report/v2/renderer/report-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,251 @@ function formatDateTime(date) {
return formatter.format(new Date(date));
}


class LighthouseReportFunctionality {

/**
* @param {!Document} document
* @param {ReportJSON=} report
*/
constructor(document, report) {
this.json = report || null;
this._document = document;
this._copyAttempt = false;

this.onCopy = this.onCopy.bind(this);
this.onExportButtonClick = this.onExportButtonClick.bind(this);
this.onExport = this.onExport.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);
this.printShortCutDetect = this.printShortCutDetect.bind(this);

this.logger = new Logger(this._document.querySelector('#lighthouse-log'));

this._addEventListeners();
}

_addEventListeners() {
this._setUpCollaspeDetailsAfterPrinting();

this.exportButton = this._document.querySelector('.lighthouse-export__button');
if (this.exportButton) {
this.exportButton.addEventListener('click', this.onExportButtonClick);
const dropdown = this._document.querySelector('.lighthouse-export__dropdown');
dropdown.addEventListener('click', this.onExport);

this._document.addEventListener('copy', this.onCopy);
}
this._document.addEventListener('keydown', this.printShortCutDetect);
}

/**
* Handler copy events.
*/
onCopy(e) {
// Only handle copy button presses (e.g. ignore the user copying page text).
if (this._copyAttempt) {
// We want to write our own data to the clipboard, not the user's text selection.
e.preventDefault();
e.clipboardData.setData('text/plain', JSON.stringify(this.json, null, 2));
this.logger.log('Report JSON copied to clipboard');
}

this._copyAttempt = false;
}

/**
* Copies the report JSON to the clipboard (if supported by the browser).
*/
onCopyButtonClick() {
if (window.ga) {
window.ga('send', 'event', 'report', 'copy');
}

try {
if (this._document.queryCommandSupported('copy')) {
this._copyAttempt = true;

// Note: In Safari 10.0.1, execCommand('copy') returns true if there's
// a valid text selection on the page. See http://caniuse.com/#feat=clipboard.
const successful = this._document.execCommand('copy');
if (!successful) {
this._copyAttempt = false; // Prevent event handler from seeing this as a copy attempt.
this.logger.warn('Your browser does not support copy to clipboard.');
}
}
} catch (err) {
this._copyAttempt = false;
this.logger.log(err.message);
}
}

closeExportDropdown() {
this.exportButton.classList.remove('active');
}

/**
* Click handler for export button.
*/
onExportButtonClick(e) {
e.preventDefault();
e.target.classList.toggle('active');
this._document.addEventListener('keydown', this.onKeyDown);
}

/**
* Handler for "export as" button.
*/
onExport(e) {
e.preventDefault();

if (!e.target.dataset.action) {
return;
}

switch (e.target.dataset.action) {
case 'copy':
this.onCopyButtonClick();
break;
case 'open-viewer':
this.sendJSONReport();
break;
case 'print':
this.expandDetailsWhenPrinting();
window.print();
break;
case 'save-json': {
const jsonStr = JSON.stringify(this.json, null, 2);
this._saveFile(new Blob([jsonStr], {type: 'application/json'}));
break;
}
case 'save-html': {
let htmlStr = '';

// Since Viewer generates its page HTML dynamically from report JSON,
// run the ReportGenerator. For everything else, the page's HTML is
// already the final product.
// if (e.target.dataset.context !== 'viewer') {
// htmlStr = this._document.documentElement.outerHTML;
// } else {
// const reportGenerator = new ReportGeneratorV2();
// htmlStr = reportGenerator.generateReportHTML(this.json);
// }
// TODO: fix viewer.
htmlStr = this._document.documentElement.outerHTML;

try {
this._saveFile(new Blob([htmlStr], {type: 'text/html'}));
} catch (err) {
this.logger.error('Could not export as HTML. ' + err.message);
}
break;
}
}

this.closeExportDropdown();
this._document.removeEventListener('keydown', this.onKeyDown);
}

/**
* Keydown handler for the document.
*/
onKeyDown(e) {
if (e.keyCode === 27) { // ESC
this.closeExportDropdown();
}
}

/**
* Opens a new tab to the online viewer and sends the local page's JSON results
* to the online viewer using postMessage.
*/
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: this.json}, VIEWER_ORIGIN);
window.removeEventListener('message', msgHandler);
}
}.bind(this));

const popup = window.open(VIEWER_URL, '_blank');
}

/**
* Expands details while user using short cut to print report
*/
printShortCutDetect(e) {
if ((e.ctrlKey || e.metaKey) && e.keyCode === 80) { // Ctrl+P
this.expandDetailsWhenPrinting();
}
}

/**
* Expands audit `<details>` when the user prints the page.
* Ideally, a print stylesheet could take care of this, but CSS has no way to
* open a `<details>` element.
*/
expandDetailsWhenPrinting() {
const reportContainer = this._document.querySelector('.lighthouse-categories');
const details = Array.from(reportContainer.querySelectorAll('details'));
details.map(detail => detail.open = true);
}

/**
* Sets up listeners to collapse audit `<details>` when the user closes the
* print dialog, all `<details>` are collapsed.
*/
_setUpCollaspeDetailsAfterPrinting() {
const details = Array.from(this._document.querySelectorAll('details'));

// FF and IE implement these old events.
if ('onbeforeprint' in window) {
window.addEventListener('afterprint', _ => {
details.map(detail => detail.open = false);
});
} else {
// Note: while FF has media listeners, it doesn't fire when matching 'print'.
window.matchMedia('print').addListener(mql => {
if (!mql.matches) {
details.map(detail => detail.open = mql.matches);
}
});
}
}
/**
* Downloads a file (blob) using a[download].
* @param {Blob|File} blob The file to save.
*/
_saveFile(blob) {
const filename = window.getFilenamePrefix({
url: this.json.url,
generatedTime: this.json.generatedTime
});

const ext = blob.type.match('json') ? '.json' : '.html';

const a = document.createElement('a');
a.download = `${filename}${ext}`;
a.href = URL.createObjectURL(blob);
this._document.body.appendChild(a); // Firefox requires anchor to be in the DOM.
a.click();

// cleanup.
this._document.body.removeChild(a);
setTimeout(_ => URL.revokeObjectURL(a.href), 500);
}
}

class ReportRenderer {
/**
* @param {!DOM} dom
Expand All @@ -97,6 +342,10 @@ class ReportRenderer {

try {
container.appendChild(this._renderReport(report));

// Hook in JS features and add page-level event listeners after the report
// has been added to the document.
new LighthouseReportFunctionality(this._document, report);
} catch (e) {
container.appendChild(this._renderException(e));
}
Expand Down
2 changes: 2 additions & 0 deletions lighthouse-core/report/v2/report-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ const REPORT_TEMPLATE = fs.readFileSync(__dirname + '/report-template.html', 'ut
const REPORT_JAVASCRIPT = [
fs.readFileSync(__dirname + '/renderer/dom.js', 'utf8'),
fs.readFileSync(__dirname + '/renderer/details-renderer.js', 'utf8'),
fs.readFileSync(__dirname + '/../../lib/file-namer.js', 'utf8'),
fs.readFileSync(__dirname + '/logger.js', 'utf8'),
fs.readFileSync(__dirname + '/renderer/report-renderer.js', 'utf8'),
].join(';\n');
const REPORT_CSS = fs.readFileSync(__dirname + '/report-styles.css', 'utf8');
Expand Down