diff --git a/lighthouse-core/report/html/html-report-assets.js b/lighthouse-core/report/html/html-report-assets.js index a60a18bdade6..118de51e8421 100644 --- a/lighthouse-core/report/html/html-report-assets.js +++ b/lighthouse-core/report/html/html-report-assets.js @@ -11,6 +11,9 @@ const REPORT_TEMPLATE = fs.readFileSync(__dirname + '/report-template.html', 'ut const REPORT_JAVASCRIPT = [ fs.readFileSync(__dirname + '/renderer/util.js', 'utf8'), fs.readFileSync(__dirname + '/renderer/dom.js', 'utf8'), + // COMPAT: Remove when Microsoft Edge supports
/ + // https://developer.microsoft.com/en-us/microsoft-edge/platform/status/detailssummary/?q=details + fs.readFileSync(__dirname + '/third_party/details-element-polyfill.js', 'utf8'), fs.readFileSync(__dirname + '/renderer/details-renderer.js', 'utf8'), fs.readFileSync(__dirname + '/renderer/crc-details-renderer.js', 'utf8'), fs.readFileSync(__dirname + '/../../lib/file-namer.js', 'utf8'), diff --git a/lighthouse-core/report/html/readme.md b/lighthouse-core/report/html/readme.md index c9e9ebc41c1c..af2edcc059b9 100644 --- a/lighthouse-core/report/html/readme.md +++ b/lighthouse-core/report/html/readme.md @@ -34,3 +34,7 @@ The renderer was designed to be portable across various environments. 1. _LH CLI_: It [creates the HTML as the runner finishes up](https://github.com/GoogleChrome/lighthouse/blob/440155cdda377c458c0efce006bc3a69ce2a351c/lighthouse-core/runner.js#L137-L138) and [saves it to disk](https://github.com/GoogleChrome/lighthouse/blob/440155cdda377c458c0efce006bc3a69ce2a351c/lighthouse-cli/printer.js#L71-L92). 1. _Chrome DevTools Audits Panel_: The `renderer` files are rolled into the Chromium repo, and they execute within the DevTools context. The audits panel [receives the LHR object from a WebWorker](https://github.com/ChromeDevTools/devtools-frontend/blob/master/front_end/audits2/Audits2ProtocolService.js#L27-L35), through a `postMessage` and then runs [the renderer within DevTools UI](https://github.com/ChromeDevTools/devtools-frontend/blob/fee00605cada877c1f8e3aae758a0f8d05b64476/front_end/audits2/Audits2Panel.js#L519-L542), making a few upgrades ([one](https://github.com/ChromeDevTools/devtools-frontend/blob/fee00605cada877c1f8e3aae758a0f8d05b64476/front_end/audits2/Audits2Panel.js#L570-L583), [two](https://github.com/ChromeDevTools/devtools-frontend/blob/fee00605cada877c1f8e3aae758a0f8d05b64476/front_end/audits2/Audits2Panel.js#L596-L637)) and [simplifications](https://github.com/ChromeDevTools/devtools-frontend/blob/fee00605cada877c1f8e3aae758a0f8d05b64476/front_end/audits2/Audits2Panel.js#L275-L304). 1. _Hosted [Lighthouse Viewer](https://googlechrome.github.io/lighthouse/viewer/)_: It's a webapp that has the renderer (along with some [additional features](https://github.com/GoogleChrome/lighthouse/blob/master/lighthouse-core/report/html/renderer/report-ui-features.js)) all compiled into a [`viewer.js`](https://googlechrome.github.io/lighthouse/viewer/src/viewer.js) file. Same [basic approach](https://github.com/GoogleChrome/lighthouse/blob/440155cdda377c458c0efce006bc3a69ce2a351c/lighthouse-viewer/app/src/lighthouse-report-viewer.js#L116-L117) there. + +### Polyfills + +The `details-element-polyfill` is pulled in to provide [support](https://caniuse.com/#feat=details) for Microsoft Edge. It's built [unminified](https://github.com/javan/details-element-polyfill/issues/12) and pulled into third_party. diff --git a/lighthouse-core/report/html/report-styles.css b/lighthouse-core/report/html/report-styles.css index ad9fa10d6cee..44efd2618d23 100644 --- a/lighthouse-core/report/html/report-styles.css +++ b/lighthouse-core/report/html/report-styles.css @@ -148,6 +148,11 @@ display: none !important; } +/* Edge doesn't recognize these, and the polyfill doesn't set block. https://github.com/javan/details-element-polyfill/issues/13 */ +details, summary { + display: block; +} + .lh-root details > summary { cursor: pointer; } @@ -310,7 +315,8 @@ background-color: #F8F9FA; } -/* Hide the expandable arrow icon, cross-browser */ +/* Hide the expandable arrow icon, three ways: via the CSS Counter Styles spec, for webkit/blink browsers, hiding the polyfilled icon */ +/* https://github.com/javan/details-element-polyfill/blob/master/src/details-element-polyfill/polyfill.sass */ .lh-audit-group > summary, .lh-expandable-details > summary { list-style-type: none; @@ -319,6 +325,10 @@ .lh-expandable-details > summary::-webkit-details-marker { display: none; } +.lh-audit-group > summary:before, +.lh-expandable-details > summary:before { + display: none; +} /* Perf Metric */ diff --git a/lighthouse-core/report/html/third_party/LICENSE b/lighthouse-core/report/html/third_party/LICENSE new file mode 100644 index 000000000000..1695fd0a060d --- /dev/null +++ b/lighthouse-core/report/html/third_party/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2018 Javan Makhmali + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/lighthouse-core/report/html/third_party/details-element-polyfill.js b/lighthouse-core/report/html/third_party/details-element-polyfill.js new file mode 100644 index 000000000000..9ce5eb588be9 --- /dev/null +++ b/lighthouse-core/report/html/third_party/details-element-polyfill.js @@ -0,0 +1,295 @@ +/* +Details Element Polyfill 2.0.4 +Copyright © 2018 Javan Makhmali + */ + +// COMPAT: Remove when Edge supports
/ + +(function() { + var addAttributesForSummary, eventIsSignificant, findClosestElementWithTagName, findElementsWithTagName, onTogglingTrigger, polyfillFocusAndARIA, polyfillProperties, polyfillStyles, polyfillToggle, polyfillToggleEvent, support, triggerToggle, triggerToggleIfToggled; + + // Exit early if we're not in a page + // This line added by Lighthouse + if (typeof document === 'undefined' || !('createElement' in document)) return; + + support = { + element: (function() { + var closedHeight, element, openedHeight, parent, ref; + element = document.createElement("details"); + if (!("open" in element)) { + return false; + } + element.innerHTML = "ab"; + element.setAttribute("style", "position: absolute; left: -9999px"); + parent = (ref = document.body) != null ? ref : document.documentElement; + parent.appendChild(element); + closedHeight = element.offsetHeight; + element.open = true; + openedHeight = element.offsetHeight; + parent.removeChild(element); + return closedHeight !== openedHeight; + })(), + toggleEvent: (function() { + var element; + element = document.createElement("details"); + return "ontoggle" in element; + })() + }; + + if (support.element && support.toggleEvent) { + return; + } + + polyfillStyles = function() { + return document.head.insertAdjacentHTML("afterbegin", ""); + }; + + polyfillProperties = function() { + var open, prototype, removeAttribute, setAttribute; + prototype = document.createElement("details").constructor.prototype; + setAttribute = prototype.setAttribute, removeAttribute = prototype.removeAttribute; + open = Object.getOwnPropertyDescriptor(prototype, "open"); + return Object.defineProperties(prototype, { + open: { + get: function() { + var ref; + if (this.tagName === "DETAILS") { + return this.hasAttribute("open"); + } else { + return open != null ? (ref = open.get) != null ? ref.call(this) : void 0 : void 0; + } + }, + set: function(value) { + var ref; + if (this.tagName === "DETAILS") { + if (value) { + this.setAttribute("open", ""); + } else { + this.removeAttribute("open"); + } + return value; + } else { + return open != null ? (ref = open.set) != null ? ref.call(this, value) : void 0 : void 0; + } + } + }, + setAttribute: { + value: function(name, value) { + return triggerToggleIfToggled(this, (function(_this) { + return function() { + return setAttribute.call(_this, name, value); + }; + })(this)); + } + }, + removeAttribute: { + value: function(name) { + return triggerToggleIfToggled(this, (function(_this) { + return function() { + return removeAttribute.call(_this, name); + }; + })(this)); + } + } + }); + }; + + polyfillToggle = function() { + return onTogglingTrigger(function(element) { + var summary; + summary = element.querySelector("summary"); + if (element.hasAttribute("open")) { + element.removeAttribute("open"); + return summary.setAttribute("aria-expanded", false); + } else { + element.setAttribute("open", ""); + return summary.setAttribute("aria-expanded", true); + } + }); + }; + + polyfillFocusAndARIA = function() { + var i, len, observer, ref, summary; + ref = findElementsWithTagName(document.documentElement, "SUMMARY"); + for (i = 0, len = ref.length; i < len; i++) { + summary = ref[i]; + addAttributesForSummary(summary); + } + if (typeof MutationObserver !== "undefined" && MutationObserver !== null) { + observer = new MutationObserver(function(mutations) { + var addedNodes, j, len1, results, target; + results = []; + for (j = 0, len1 = mutations.length; j < len1; j++) { + addedNodes = mutations[j].addedNodes; + results.push((function() { + var k, len2, results1; + results1 = []; + for (k = 0, len2 = addedNodes.length; k < len2; k++) { + target = addedNodes[k]; + results1.push((function() { + var l, len3, ref1, results2; + ref1 = findElementsWithTagName(target, "SUMMARY"); + results2 = []; + for (l = 0, len3 = ref1.length; l < len3; l++) { + summary = ref1[l]; + results2.push(addAttributesForSummary(summary)); + } + return results2; + })()); + } + return results1; + })()); + } + return results; + }); + return observer.observe(document.documentElement, { + subtree: true, + childList: true + }); + } else { + return document.addEventListener("DOMNodeInserted", function(event) { + var j, len1, ref1, results; + ref1 = findElementsWithTagName(event.target, "SUMMARY"); + results = []; + for (j = 0, len1 = ref1.length; j < len1; j++) { + summary = ref1[j]; + results.push(addAttributesForSummary(summary)); + } + return results; + }); + } + }; + + addAttributesForSummary = function(summary, details) { + if (details == null) { + details = findClosestElementWithTagName(summary, "DETAILS"); + } + summary.setAttribute("aria-expanded", details.hasAttribute("open")); + if (!summary.hasAttribute("tabindex")) { + summary.setAttribute("tabindex", "0"); + } + if (!summary.hasAttribute("role")) { + return summary.setAttribute("role", "button"); + } + }; + + polyfillToggleEvent = function() { + var observer; + if (typeof MutationObserver !== "undefined" && MutationObserver !== null) { + observer = new MutationObserver(function(mutations) { + var attributeName, i, len, ref, results, target; + results = []; + for (i = 0, len = mutations.length; i < len; i++) { + ref = mutations[i], target = ref.target, attributeName = ref.attributeName; + if (target.tagName === "DETAILS" && attributeName === "open") { + results.push(triggerToggle(target)); + } else { + results.push(void 0); + } + } + return results; + }); + return observer.observe(document.documentElement, { + attributes: true, + subtree: true + }); + } else { + return onTogglingTrigger(function(element) { + var open; + open = element.getAttribute("open"); + return setTimeout(function() { + if (element.getAttribute("open") !== open) { + return triggerToggle(element); + } + }, 1); + }); + } + }; + + eventIsSignificant = function(event) { + return !(event.defaultPrevented || event.ctrlKey || event.metaKey || event.shiftKey || event.target.isContentEditable); + }; + + onTogglingTrigger = function(callback) { + addEventListener("click", function(event) { + var element, ref; + if (eventIsSignificant(event) && event.which <= 1) { + if (element = findClosestElementWithTagName(event.target, "SUMMARY")) { + if (((ref = element.parentElement) != null ? ref.tagName : void 0) === "DETAILS") { + return callback(element.parentElement); + } + } + } + }, false); + return addEventListener("keydown", function(event) { + var element, ref, ref1; + if (eventIsSignificant(event) && ((ref = event.keyCode) === 13 || ref === 32)) { + if (element = findClosestElementWithTagName(event.target, "SUMMARY")) { + if (((ref1 = element.parentElement) != null ? ref1.tagName : void 0) === "DETAILS") { + callback(element.parentElement); + return event.preventDefault(); + } + } + } + }, false); + }; + + findElementsWithTagName = function(root, tagName) { + var elements; + elements = []; + if (root.nodeType === Node.ELEMENT_NODE) { + if (root.tagName === tagName) { + elements.push(root); + } + elements.push.apply(elements, root.getElementsByTagName(tagName)); + } + return elements; + }; + + findClosestElementWithTagName = (function() { + if (typeof Element.prototype.closest === "function") { + return function(element, tagName) { + return element.closest(tagName); + }; + } else { + return function(element, tagName) { + while (element) { + if (element.tagName === tagName) { + return element; + } else { + element = element.parentNode; + } + } + }; + } + })(); + + triggerToggle = function(element) { + var event; + event = document.createEvent("Events"); + event.initEvent("toggle", true, false); + return element.dispatchEvent(event); + }; + + triggerToggleIfToggled = function(element, fn) { + var open, result; + open = element.getAttribute("open"); + result = fn(); + if (element.getAttribute("open") !== open) { + triggerToggle(element); + } + return result; + }; + + if (!support.element) { + polyfillStyles(); + polyfillProperties(); + polyfillToggle(); + polyfillFocusAndARIA(); + } + + if (support.element && !support.toggleEvent) { + polyfillToggleEvent(); + } + +}).call(this); diff --git a/tsconfig.json b/tsconfig.json index 88bcb7e99c61..76b4e9b74c0c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,6 +25,7 @@ ], "exclude": [ "lighthouse-cli/test/**/*.js", + "lighthouse-core/report/html/third_party/", "lighthouse-core/test/**/*.js", "clients/test/**/*.js", ]