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 = "a
b";
+ 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",
]