lighthouse 9.0.0-dev.20211121 → 9.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +70 -0
- package/dist/report/bundle.esm.js +17 -3
- package/dist/report/flow.js +34 -23
- package/dist/report/standalone.js +3 -3
- package/flow-report/api.ts +18 -0
- package/flow-report/assets/standalone-flow-template.html +0 -1
- package/flow-report/{standalone-flow.tsx → clients/standalone.ts} +4 -5
- package/flow-report/src/app.tsx +20 -12
- package/flow-report/src/sidebar/flow.tsx +1 -3
- package/flow-report/src/sidebar/sidebar.tsx +1 -3
- package/flow-report/src/topbar.tsx +22 -13
- package/flow-report/src/util.ts +7 -0
- package/flow-report/src/wrappers/report.tsx +1 -0
- package/flow-report/src/wrappers/styles.tsx +16 -0
- package/flow-report/test/topbar-test.tsx +22 -6
- package/flow-report/types/flow-report.d.ts +4 -0
- package/lighthouse-cli/cli-flags.js +4 -4
- package/lighthouse-core/audits/audit.js +39 -7
- package/lighthouse-core/audits/byte-efficiency/legacy-javascript.js +1 -26
- package/lighthouse-core/audits/deprecations.js +10 -14
- package/lighthouse-core/audits/dobetterweb/geolocation-on-start.js +5 -4
- package/lighthouse-core/audits/dobetterweb/js-libraries.js +1 -1
- package/lighthouse-core/audits/dobetterweb/no-document-write.js +6 -4
- package/lighthouse-core/audits/dobetterweb/notification-on-start.js +6 -4
- package/lighthouse-core/audits/dobetterweb/uses-passive-event-listeners.js +6 -4
- package/lighthouse-core/audits/errors-in-console.js +7 -4
- package/lighthouse-core/audits/no-unload-listeners.js +9 -10
- package/lighthouse-core/audits/seo/canonical.js +0 -14
- package/lighthouse-core/audits/seo/font-size.js +4 -2
- package/lighthouse-core/audits/seo/is-crawlable.js +2 -2
- package/lighthouse-core/audits/third-party-summary.js +2 -2
- package/lighthouse-core/audits/valid-source-maps.js +1 -1
- package/lighthouse-core/audits/violation-audit.js +10 -3
- package/lighthouse-core/computed/js-bundles.js +1 -3
- package/lighthouse-core/fraggle-rock/gather/navigation-runner.js +3 -3
- package/lighthouse-core/gather/driver/execution-context.js +1 -1
- package/lighthouse-core/gather/gatherers/full-page-screenshot.js +7 -7
- package/lighthouse-core/gather/gatherers/stacks.js +0 -3
- package/lighthouse-core/lib/stack-packs.js +12 -8
- package/lighthouse-core/runner.js +7 -7
- package/package.json +4 -6
- package/report/generator/file-namer.js +12 -1
- package/report/generator/flow-report-assets.js +2 -1
- package/report/generator/report-assets.js +2 -5
- package/report/generator/report-generator.js +0 -1
- package/report/renderer/api.js +15 -1
- package/report/renderer/report-renderer.js +4 -1
- package/report/renderer/report-ui-features.js +1 -1
- package/report/renderer/swap-locale-feature.js +0 -5
- package/report/test/clients/bundle-test.js +2 -2
- package/report/test/renderer/report-ui-features-test.js +0 -1
- package/report/types/report-renderer.d.ts +2 -0
- package/shared/localization/locales/ar-XB.json +0 -3
- package/shared/localization/locales/ar.json +0 -3
- package/shared/localization/locales/bg.json +17 -20
- package/shared/localization/locales/ca.json +0 -3
- package/shared/localization/locales/cs.json +0 -3
- package/shared/localization/locales/da.json +16 -19
- package/shared/localization/locales/de.json +0 -3
- package/shared/localization/locales/el.json +0 -3
- package/shared/localization/locales/en-GB.json +19 -22
- package/shared/localization/locales/en-US.json +30 -3
- package/shared/localization/locales/en-XA.json +0 -3
- package/shared/localization/locales/en-XL.json +30 -3
- package/shared/localization/locales/es-419.json +17 -20
- package/shared/localization/locales/es.json +0 -3
- package/shared/localization/locales/fi.json +16 -19
- package/shared/localization/locales/fil.json +33 -36
- package/shared/localization/locales/fr.json +16 -19
- package/shared/localization/locales/he.json +16 -19
- package/shared/localization/locales/hi.json +0 -3
- package/shared/localization/locales/hr.json +16 -19
- package/shared/localization/locales/hu.json +16 -19
- package/shared/localization/locales/id.json +16 -19
- package/shared/localization/locales/it.json +37 -40
- package/shared/localization/locales/ja.json +16 -19
- package/shared/localization/locales/ko.json +16 -19
- package/shared/localization/locales/lt.json +17 -20
- package/shared/localization/locales/lv.json +0 -3
- package/shared/localization/locales/nl.json +16 -19
- package/shared/localization/locales/no.json +17 -20
- package/shared/localization/locales/pl.json +16 -19
- package/shared/localization/locales/pt-PT.json +37 -40
- package/shared/localization/locales/pt.json +16 -19
- package/shared/localization/locales/ro.json +0 -3
- package/shared/localization/locales/ru.json +16 -19
- package/shared/localization/locales/sk.json +0 -3
- package/shared/localization/locales/sl.json +0 -3
- package/shared/localization/locales/sr-Latn.json +16 -19
- package/shared/localization/locales/sr.json +16 -19
- package/shared/localization/locales/sv.json +17 -20
- package/shared/localization/locales/ta.json +0 -3
- package/shared/localization/locales/te.json +0 -3
- package/shared/localization/locales/th.json +0 -3
- package/shared/localization/locales/tr.json +0 -3
- package/shared/localization/locales/uk.json +17 -20
- package/shared/localization/locales/vi.json +0 -3
- package/shared/localization/locales/zh-HK.json +0 -3
- package/shared/localization/locales/zh-TW.json +0 -3
- package/shared/localization/locales/zh.json +0 -3
- package/types/externs.d.ts +5 -24
|
@@ -145,7 +145,7 @@ class s{constructor(e,t){this._document=e,this._lighthouseChannel="unknown",this
|
|
|
145
145
|
*
|
|
146
146
|
* Dummy text for ensuring report robustness: <\/script> pre$`post %%LIGHTHOUSE_JSON%%
|
|
147
147
|
* (this is handled by terser)
|
|
148
|
-
*/class N{constructor(e){this._dom=e,this._opts={}}renderReport(e,t,n){if(!this._dom.rootEl&&t){console.warn("Please adopt the new report API in renderer/api.js.");const e=t.closest(".lh-root");e?this._dom.rootEl=e:(t.classList.add("lh-root","lh-vars"),this._dom.rootEl=t)}else this._dom.rootEl&&t&&(this._dom.rootEl=t);n&&(this._opts=n),this._dom.setLighthouseChannel(e.configSettings.channel||"unknown");const r=l.prepareReportResult(e);return this._dom.rootEl.textContent="",this._dom.rootEl.appendChild(this._renderReport(r)),this._dom.rootEl}_renderReportTopbar(e){const t=this._dom.createComponent("topbar"),n=this._dom.find("a.lh-topbar__url",t);return n.textContent=e.finalUrl,n.title=e.finalUrl,this._dom.safelySetHref(n,e.finalUrl),t}_renderReportHeader(){const e=this._dom.createComponent("heading"),t=this._dom.createComponent("scoresWrapper");return this._dom.find(".lh-scores-wrapper-placeholder",e).replaceWith(t),e}_renderReportFooter(e){const t=this._dom.createComponent("footer");return this._renderMetaBlock(e,t),this._dom.find(".lh-footer__version_issue",t).textContent=l.i18n.strings.footerIssue,this._dom.find(".lh-footer__version",t).textContent=e.lighthouseVersion,t}_renderMetaBlock(e,t){const n=l.getEmulationDescriptions(e.configSettings||{}),r=e.userAgent.match(/(\w*Chrome\/[\d.]+)/),o=Array.isArray(r)?r[1].replace("/"," ").replace("Chrome","Chromium"):"Chromium",i=e.configSettings.channel,a=e.environment.benchmarkIndex.toFixed(0),s=e.environment.credits?.["axe-core"],c=[["date",`Captured at ${l.i18n.formatDateTime(e.fetchTime)}`],["devices",`${n.deviceEmulation} with Lighthouse ${e.lighthouseVersion}`,`${l.i18n.strings.runtimeSettingsBenchmark}: ${a}\n${l.i18n.strings.runtimeSettingsCPUThrottling}: ${n.cpuThrottling}`+(s?`\n${l.i18n.strings.runtimeSettingsAxeVersion}: ${s}`:"")],["samples-one",l.i18n.strings.runtimeSingleLoad,l.i18n.strings.runtimeSingleLoadTooltip],["stopwatch",l.i18n.strings.runtimeAnalysisWindow],["networkspeed",`${n.summary}`,`${l.i18n.strings.runtimeSettingsNetworkThrottling}: ${n.networkThrottling}`],["chrome",`Using ${o}`+(i?` with ${i}`:""),`${l.i18n.strings.runtimeSettingsUANetwork}: "${e.environment.networkUserAgent}"`]],d=this._dom.find(".lh-meta__items",t);for(const[e,t,n]of c){const r=this._dom.createChildOf(d,"li","lh-meta__item");if(r.textContent=t,n){r.classList.add("lh-tooltip-boundary");this._dom.createChildOf(r,"div","lh-tooltip").textContent=n}r.classList.add("lh-report-icon",`lh-report-icon--${e}`)}}_renderReportWarnings(e){if(!e.runWarnings||0===e.runWarnings.length)return this._dom.createElement("div");const t=this._dom.createComponent("warningsToplevel");this._dom.find(".lh-warnings__msg",t).textContent=l.i18n.strings.toplevelWarningsMessage;const n=this._dom.find("ul",t);for(const t of e.runWarnings){n.appendChild(this._dom.createElement("li")).appendChild(this._dom.convertMarkdownLinkSnippets(t))}return t}_renderScoreGauges(e,t,n){const r=[],o=[],i=[];for(const a of Object.values(e.categories)){const s=n[a.id]||t,c=s.renderCategoryScore(a,e.categoryGroups||{},{gatherMode:e.gatherMode}),d=this._dom.find("a.lh-gauge__wrapper, a.lh-fraction__wrapper",c);d&&(this._dom.safelySetHref(d,`#${a.id}`),d.addEventListener("click",(e=>{if(!d.matches('[href^="#"]'))return;const t=d.getAttribute("href"),n=this._dom.rootEl;if(!t||!n)return;const r=this._dom.find(t,n);e.preventDefault(),r.scrollIntoView()})),this._opts.onPageAnchorRendered?.(d)),l.isPluginCategory(a.id)?i.push(c):s.renderCategoryScore===t.renderCategoryScore?r.push(c):o.push(c)}return[...r,...o,...i]}_renderReport(e){const t=new L(e.configSettings.locale,{...l.UIStrings,...e.i18n.rendererFormattedStrings});l.i18n=t,l.reportJson=e;const n=e.audits["full-page-screenshot"]&&e.audits["full-page-screenshot"].details&&"full-page-screenshot"===e.audits["full-page-screenshot"].details.type?e.audits["full-page-screenshot"].details:void 0,r=new z(this._dom,{fullPageScreenshot:n}),o=new c(this._dom,r),i={performance:new M(this._dom,r),pwa:new F(this._dom,r)},a=this._dom.createElement("div");a.appendChild(this._renderReportHeader());const s=this._dom.createElement("div","lh-container"),d=this._dom.createElement("div","lh-report");let h;d.appendChild(this._renderReportWarnings(e));1===Object.keys(e.categories).length?a.classList.add("lh-header--solo-category"):h=this._dom.createElement("div","lh-scores-header");const p=this._dom.createElement("div");if(p.classList.add("lh-scorescale-wrap"),p.append(this._dom.createComponent("scorescale")),h){const t=this._dom.find(".lh-scores-container",a);h.append(...this._renderScoreGauges(e,o,i)),t.appendChild(h),t.appendChild(p);const n=this._dom.createElement("div","lh-sticky-header");n.append(...this._renderScoreGauges(e,o,i)),s.appendChild(n)}const u=d.appendChild(this._dom.createElement("div","lh-categories")),g={gatherMode:e.gatherMode};for(const t of Object.values(e.categories)){const n=i[t.id]||o;n.dom.createChildOf(u,"div","lh-category-wrapper").appendChild(n.render(t,e.categoryGroups,g))}o.injectFinalScreenshot(u,e.audits,p);const m=this._dom.createFragment();return m.append(this._dom.createComponent("styles")),this._opts.omitTopbar||m.appendChild(this._renderReportTopbar(e)),m.appendChild(s),s.appendChild(a),s.appendChild(d),d.appendChild(this._renderReportFooter(e)),n&&A.installFullPageScreenshot(this._dom.rootEl,n.screenshot),m}}
|
|
148
|
+
*/class N{constructor(e){this._dom=e,this._opts={}}renderReport(e,t,n){if(!this._dom.rootEl&&t){console.warn("Please adopt the new report API in renderer/api.js.");const e=t.closest(".lh-root");e?this._dom.rootEl=e:(t.classList.add("lh-root","lh-vars"),this._dom.rootEl=t)}else this._dom.rootEl&&t&&(this._dom.rootEl=t);n&&(this._opts=n),this._dom.setLighthouseChannel(e.configSettings.channel||"unknown");const r=l.prepareReportResult(e);return this._dom.rootEl.textContent="",this._dom.rootEl.appendChild(this._renderReport(r)),this._dom.rootEl}_renderReportTopbar(e){const t=this._dom.createComponent("topbar"),n=this._dom.find("a.lh-topbar__url",t);return n.textContent=e.finalUrl,n.title=e.finalUrl,this._dom.safelySetHref(n,e.finalUrl),t}_renderReportHeader(){const e=this._dom.createComponent("heading"),t=this._dom.createComponent("scoresWrapper");return this._dom.find(".lh-scores-wrapper-placeholder",e).replaceWith(t),e}_renderReportFooter(e){const t=this._dom.createComponent("footer");return this._renderMetaBlock(e,t),this._dom.find(".lh-footer__version_issue",t).textContent=l.i18n.strings.footerIssue,this._dom.find(".lh-footer__version",t).textContent=e.lighthouseVersion,t}_renderMetaBlock(e,t){const n=l.getEmulationDescriptions(e.configSettings||{}),r=e.userAgent.match(/(\w*Chrome\/[\d.]+)/),o=Array.isArray(r)?r[1].replace("/"," ").replace("Chrome","Chromium"):"Chromium",i=e.configSettings.channel,a=e.environment.benchmarkIndex.toFixed(0),s=e.environment.credits?.["axe-core"],c=[["date",`Captured at ${l.i18n.formatDateTime(e.fetchTime)}`],["devices",`${n.deviceEmulation} with Lighthouse ${e.lighthouseVersion}`,`${l.i18n.strings.runtimeSettingsBenchmark}: ${a}\n${l.i18n.strings.runtimeSettingsCPUThrottling}: ${n.cpuThrottling}`+(s?`\n${l.i18n.strings.runtimeSettingsAxeVersion}: ${s}`:"")],["samples-one",l.i18n.strings.runtimeSingleLoad,l.i18n.strings.runtimeSingleLoadTooltip],["stopwatch",l.i18n.strings.runtimeAnalysisWindow],["networkspeed",`${n.summary}`,`${l.i18n.strings.runtimeSettingsNetworkThrottling}: ${n.networkThrottling}`],["chrome",`Using ${o}`+(i?` with ${i}`:""),`${l.i18n.strings.runtimeSettingsUANetwork}: "${e.environment.networkUserAgent}"`]],d=this._dom.find(".lh-meta__items",t);for(const[e,t,n]of c){const r=this._dom.createChildOf(d,"li","lh-meta__item");if(r.textContent=t,n){r.classList.add("lh-tooltip-boundary");this._dom.createChildOf(r,"div","lh-tooltip").textContent=n}r.classList.add("lh-report-icon",`lh-report-icon--${e}`)}}_renderReportWarnings(e){if(!e.runWarnings||0===e.runWarnings.length)return this._dom.createElement("div");const t=this._dom.createComponent("warningsToplevel");this._dom.find(".lh-warnings__msg",t).textContent=l.i18n.strings.toplevelWarningsMessage;const n=this._dom.find("ul",t);for(const t of e.runWarnings){n.appendChild(this._dom.createElement("li")).appendChild(this._dom.convertMarkdownLinkSnippets(t))}return t}_renderScoreGauges(e,t,n){const r=[],o=[],i=[];for(const a of Object.values(e.categories)){const s=n[a.id]||t,c=s.renderCategoryScore(a,e.categoryGroups||{},{gatherMode:e.gatherMode}),d=this._dom.find("a.lh-gauge__wrapper, a.lh-fraction__wrapper",c);d&&(this._dom.safelySetHref(d,`#${a.id}`),d.addEventListener("click",(e=>{if(!d.matches('[href^="#"]'))return;const t=d.getAttribute("href"),n=this._dom.rootEl;if(!t||!n)return;const r=this._dom.find(t,n);e.preventDefault(),r.scrollIntoView()})),this._opts.onPageAnchorRendered?.(d)),l.isPluginCategory(a.id)?i.push(c):s.renderCategoryScore===t.renderCategoryScore?r.push(c):o.push(c)}return[...r,...o,...i]}_renderReport(e){const t=new L(e.configSettings.locale,{...l.UIStrings,...e.i18n.rendererFormattedStrings});l.i18n=t,l.reportJson=e;const n=e.audits["full-page-screenshot"]&&e.audits["full-page-screenshot"].details&&"full-page-screenshot"===e.audits["full-page-screenshot"].details.type?e.audits["full-page-screenshot"].details:void 0,r=new z(this._dom,{fullPageScreenshot:n}),o=new c(this._dom,r),i={performance:new M(this._dom,r),pwa:new F(this._dom,r)},a=this._dom.createElement("div");a.appendChild(this._renderReportHeader());const s=this._dom.createElement("div","lh-container"),d=this._dom.createElement("div","lh-report");let h;d.appendChild(this._renderReportWarnings(e));1===Object.keys(e.categories).length?a.classList.add("lh-header--solo-category"):h=this._dom.createElement("div","lh-scores-header");const p=this._dom.createElement("div");if(p.classList.add("lh-scorescale-wrap"),p.append(this._dom.createComponent("scorescale")),h){const t=this._dom.find(".lh-scores-container",a);h.append(...this._renderScoreGauges(e,o,i)),t.appendChild(h),t.appendChild(p);const n=this._dom.createElement("div","lh-sticky-header");n.append(...this._renderScoreGauges(e,o,i)),s.appendChild(n)}const u=d.appendChild(this._dom.createElement("div","lh-categories")),g={gatherMode:e.gatherMode};for(const t of Object.values(e.categories)){const n=i[t.id]||o;n.dom.createChildOf(u,"div","lh-category-wrapper").appendChild(n.render(t,e.categoryGroups,g))}o.injectFinalScreenshot(u,e.audits,p);const m=this._dom.createFragment();return this._opts.omitGlobalStyles||m.append(this._dom.createComponent("styles")),this._opts.omitTopbar||m.appendChild(this._renderReportTopbar(e)),m.appendChild(s),s.appendChild(a),s.appendChild(d),d.appendChild(this._renderReportFooter(e)),n&&A.installFullPageScreenshot(this._dom.rootEl,n.screenshot),m}}
|
|
149
149
|
/**
|
|
150
150
|
* @license Copyright 2021 The Lighthouse Authors. All Rights Reserved.
|
|
151
151
|
* 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
|
|
@@ -187,7 +187,7 @@ class O{constructor(e){this._dom=e,this._toggleEl,this._menuEl,this.onDocumentKe
|
|
|
187
187
|
* @license Copyright 2017 The Lighthouse Authors. All Rights Reserved.
|
|
188
188
|
* 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
|
|
189
189
|
* 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.
|
|
190
|
-
*/function V(e,t){const n=t?new Date(t):new Date,r=n.toLocaleTimeString("en-US",{hour12:!1}),o=n.toLocaleDateString("en-US",{year:"numeric",month:"2-digit",day:"2-digit"}).split("/");o.unshift(o.pop());return`${e}_${o.join("-")}_${r}`.replace(/[/?<>\\:*|"]/g,"-")}var G={getLhrFilenamePrefix:function(e){return V(new URL(e.finalUrl).hostname,e.fetchTime)},getFilenamePrefix:V};
|
|
190
|
+
*/function V(e,t){const n=t?new Date(t):new Date,r=n.toLocaleTimeString("en-US",{hour12:!1}),o=n.toLocaleDateString("en-US",{year:"numeric",month:"2-digit",day:"2-digit"}).split("/");o.unshift(o.pop());return`${e}_${o.join("-")}_${r}`.replace(/[/?<>\\:*|"]/g,"-")}var G={getLhrFilenamePrefix:function(e){return V(new URL(e.finalUrl).hostname,e.fetchTime)},getFilenamePrefix:V,getFlowResultFilenamePrefix:function(e){const t=e.steps[0].lhr;return V(e.name.replace(/\s/g,"-"),t.fetchTime)}};
|
|
191
191
|
/**
|
|
192
192
|
* @license
|
|
193
193
|
* Copyright 2017 The Lighthouse Authors. All Rights Reserved.
|
|
@@ -203,7 +203,7 @@ class O{constructor(e){this._dom=e,this._toggleEl,this._menuEl,this.onDocumentKe
|
|
|
203
203
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
204
204
|
* See the License for the specific language governing permissions and
|
|
205
205
|
* limitations under the License.
|
|
206
|
-
*/class j{constructor(e,t={}){this.json,this._dom=e,this._opts=t,this._topbar=t.omitTopbar?null:new I(this,e),this.onMediaQueryChange=this.onMediaQueryChange.bind(this)}initFeatures(e){this.json=e,this._topbar&&(this._topbar.enable(e),this._topbar.resetUIState()),this._setupMediaQueryListeners(),this._setupThirdPartyFilter(),this._setupElementScreenshotOverlay(this._dom.rootEl);let t=!1;!(this._dom.isDevTools()||this._opts.disableAutoDarkModeAndFireworks)&&window.matchMedia("(prefers-color-scheme: dark)").matches&&(t=!0);["performance","accessibility","best-practices","seo"].every((t=>{const n=e.categories[t];return n&&1===n.score}))&&(t=!0,this._enableFireworks()),t&&T(this._dom,!0);if(e.categories.performance&&e.categories.performance.auditRefs.some((t=>Boolean("metrics"===t.group&&e.audits[t.id].errorMessage)))){this._dom.find("input.lh-metrics-toggle__input",this._dom.rootEl).checked=!0}this.json.audits["script-treemap-data"]&&this.json.audits["script-treemap-data"].details&&this.addButton({text:l.i18n.strings.viewTreemapLabel,icon:"treemap",onClick:()=>function(e){if(!e.audits["script-treemap-data"].details)throw new Error("no script treemap data found");U({lhr:{requestedUrl:e.requestedUrl,finalUrl:e.finalUrl,audits:{"script-treemap-data":e.audits["script-treemap-data"]},configSettings:{locale:e.configSettings.locale}}},P()+"/treemap/","treemap-"+$(e))}(this.json)});for(const e of this._dom.findAll("[data-i18n]",this._dom.rootEl)){const t=e.getAttribute("data-i18n");e.textContent=l.i18n.strings[t]}}addButton(e){const t=this._dom.rootEl.querySelector(".lh-audit-group--metrics");if(!t)return;let n=t.querySelector(".lh-buttons");n||(n=this._dom.createChildOf(t,"div","lh-buttons"));const r=["lh-button"];e.icon&&(r.push("lh-report-icon"),r.push(`lh-report-icon--${e.icon}`));const o=this._dom.createChildOf(n,"button",r.join(" "));return o.textContent=e.text,o.addEventListener("click",e.onClick),o}getReportHtml(){return this._topbar&&this._topbar.resetUIState(),`<!doctype html><body>${this._dom.rootEl.outerHTML}`}saveAsGist(){throw new Error("Cannot save as gist from base report")}_enableFireworks(){const e=this._dom.find(".lh-scores-container",this._dom.rootEl);e.classList.add("lh-score100"),e.addEventListener("click",(t=>{e.classList.toggle("lh-fireworks-paused")}))}_setupMediaQueryListeners(){const e=self.matchMedia("(max-width: 500px)");e.addListener(this.onMediaQueryChange),this.onMediaQueryChange(e)}_resetUIState(){this._topbar&&this._topbar.resetUIState()}onMediaQueryChange(e){this._dom.rootEl.classList.toggle("lh-narrow",e.matches)}_setupThirdPartyFilter(){const e=["uses-rel-preconnect","third-party-facades"],t=["legacy-javascript"];Array.from(this._dom.rootEl.querySelectorAll("table.lh-table")).filter((e=>e.querySelector("td.lh-table-column--url, td.lh-table-column--source-location"))).filter((t=>{const n=t.closest(".lh-audit");if(!n)throw new Error(".lh-table not within audit");return!e.includes(n.id)})).forEach((e=>{const n=function(e){return Array.from(e.tBodies[0].rows)}(e),r=this._getThirdPartyRows(n,this.json.finalUrl),o=this._dom.createComponent("3pFilter"),i=this._dom.find("input",o);i.addEventListener("change",(e=>{const t=e.target instanceof HTMLInputElement&&!e.target.checked;let o=!0,i=n[0];for(;i;){const e=t&&r.includes(i);do{i.classList.toggle("lh-row--hidden",e),i.classList.toggle("lh-row--even",!e&&o),i.classList.toggle("lh-row--odd",!e&&!o),i=i.nextElementSibling}while(i&&i.classList.contains("lh-sub-item-row"));e||(o=!o)}})),this._dom.find(".lh-3p-filter-count",o).textContent=`${r.length}`,this._dom.find(".lh-3p-ui-string",o).textContent=l.i18n.strings.thirdPartyResourcesLabel;const a=r.length===n.length,s=!r.length;if((a||s)&&(this._dom.find("div.lh-3p-filter",o).hidden=!0),!e.parentNode)return;e.parentNode.insertBefore(o,e);const c=e.closest(".lh-audit");if(!c)throw new Error(".lh-table not within audit");t.includes(c.id)&&!a&&i.click()}))}_setupElementScreenshotOverlay(e){const t=this.json.audits["full-page-screenshot"]&&this.json.audits["full-page-screenshot"].details&&"full-page-screenshot"===this.json.audits["full-page-screenshot"].details.type&&this.json.audits["full-page-screenshot"].details;t&&A.installOverlayFeature({dom:this._dom,rootEl:e,overlayContainerEl:e,fullPageScreenshot:t})}_getThirdPartyRows(e,t){const n=[],r=l.getRootDomain(t);for(const t of e){if(t.classList.contains("lh-sub-item-row"))continue;const e=t.querySelector("div.lh-text__url");if(!e)continue;const o=e.dataset.url;if(!o)continue;l.getRootDomain(o)!==r&&n.push(t)}return n}_saveFile(e){const t=G.getLhrFilenamePrefix(this.json);this._dom.saveFile(e,t)}}
|
|
206
|
+
*/class j{constructor(e,t={}){this.json,this._dom=e,this._opts=t,this._topbar=t.omitTopbar?null:new I(this,e),this.onMediaQueryChange=this.onMediaQueryChange.bind(this)}initFeatures(e){this.json=e,this._topbar&&(this._topbar.enable(e),this._topbar.resetUIState()),this._setupMediaQueryListeners(),this._setupThirdPartyFilter(),this._setupElementScreenshotOverlay(this._dom.rootEl);let t=!1;!(this._dom.isDevTools()||this._opts.disableAutoDarkModeAndFireworks)&&window.matchMedia("(prefers-color-scheme: dark)").matches&&(t=!0);["performance","accessibility","best-practices","seo"].every((t=>{const n=e.categories[t];return n&&1===n.score}))&&(t=!0,this._enableFireworks()),t&&T(this._dom,!0);if(e.categories.performance&&e.categories.performance.auditRefs.some((t=>Boolean("metrics"===t.group&&e.audits[t.id].errorMessage)))){this._dom.find("input.lh-metrics-toggle__input",this._dom.rootEl).checked=!0}this.json.audits["script-treemap-data"]&&this.json.audits["script-treemap-data"].details&&this.addButton({text:l.i18n.strings.viewTreemapLabel,icon:"treemap",onClick:()=>function(e){if(!e.audits["script-treemap-data"].details)throw new Error("no script treemap data found");U({lhr:{requestedUrl:e.requestedUrl,finalUrl:e.finalUrl,audits:{"script-treemap-data":e.audits["script-treemap-data"]},configSettings:{locale:e.configSettings.locale}}},P()+"/treemap/","treemap-"+$(e))}(this.json)});for(const e of this._dom.findAll("[data-i18n]",this._dom.rootEl)){const t=e.getAttribute("data-i18n");e.textContent=l.i18n.strings[t]}}addButton(e){const t=this._dom.rootEl.querySelector(".lh-audit-group--metrics");if(!t)return;let n=t.querySelector(".lh-buttons");n||(n=this._dom.createChildOf(t,"div","lh-buttons"));const r=["lh-button"];e.icon&&(r.push("lh-report-icon"),r.push(`lh-report-icon--${e.icon}`));const o=this._dom.createChildOf(n,"button",r.join(" "));return o.textContent=e.text,o.addEventListener("click",e.onClick),o}getReportHtml(){return this._topbar&&this._topbar.resetUIState(),`<!doctype html><body style="margin: 0">${this._dom.rootEl.outerHTML}`}saveAsGist(){throw new Error("Cannot save as gist from base report")}_enableFireworks(){const e=this._dom.find(".lh-scores-container",this._dom.rootEl);e.classList.add("lh-score100"),e.addEventListener("click",(t=>{e.classList.toggle("lh-fireworks-paused")}))}_setupMediaQueryListeners(){const e=self.matchMedia("(max-width: 500px)");e.addListener(this.onMediaQueryChange),this.onMediaQueryChange(e)}_resetUIState(){this._topbar&&this._topbar.resetUIState()}onMediaQueryChange(e){this._dom.rootEl.classList.toggle("lh-narrow",e.matches)}_setupThirdPartyFilter(){const e=["uses-rel-preconnect","third-party-facades"],t=["legacy-javascript"];Array.from(this._dom.rootEl.querySelectorAll("table.lh-table")).filter((e=>e.querySelector("td.lh-table-column--url, td.lh-table-column--source-location"))).filter((t=>{const n=t.closest(".lh-audit");if(!n)throw new Error(".lh-table not within audit");return!e.includes(n.id)})).forEach((e=>{const n=function(e){return Array.from(e.tBodies[0].rows)}(e),r=this._getThirdPartyRows(n,this.json.finalUrl),o=this._dom.createComponent("3pFilter"),i=this._dom.find("input",o);i.addEventListener("change",(e=>{const t=e.target instanceof HTMLInputElement&&!e.target.checked;let o=!0,i=n[0];for(;i;){const e=t&&r.includes(i);do{i.classList.toggle("lh-row--hidden",e),i.classList.toggle("lh-row--even",!e&&o),i.classList.toggle("lh-row--odd",!e&&!o),i=i.nextElementSibling}while(i&&i.classList.contains("lh-sub-item-row"));e||(o=!o)}})),this._dom.find(".lh-3p-filter-count",o).textContent=`${r.length}`,this._dom.find(".lh-3p-ui-string",o).textContent=l.i18n.strings.thirdPartyResourcesLabel;const a=r.length===n.length,s=!r.length;if((a||s)&&(this._dom.find("div.lh-3p-filter",o).hidden=!0),!e.parentNode)return;e.parentNode.insertBefore(o,e);const c=e.closest(".lh-audit");if(!c)throw new Error(".lh-table not within audit");t.includes(c.id)&&!a&&i.click()}))}_setupElementScreenshotOverlay(e){const t=this.json.audits["full-page-screenshot"]&&this.json.audits["full-page-screenshot"].details&&"full-page-screenshot"===this.json.audits["full-page-screenshot"].details.type&&this.json.audits["full-page-screenshot"].details;t&&A.installOverlayFeature({dom:this._dom,rootEl:e,overlayContainerEl:e,fullPageScreenshot:t})}_getThirdPartyRows(e,t){const n=[],r=l.getRootDomain(t);for(const t of e){if(t.classList.contains("lh-sub-item-row"))continue;const e=t.querySelector("div.lh-text__url");if(!e)continue;const o=e.dataset.url;if(!o)continue;l.getRootDomain(o)!==r&&n.push(t)}return n}_saveFile(e){const t=G.getLhrFilenamePrefix(this.json);this._dom.saveFile(e,t)}}
|
|
207
207
|
/**
|
|
208
208
|
* @license Copyright 2021 The Lighthouse Authors. All Rights Reserved.
|
|
209
209
|
* 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
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright 2021 The Lighthouse Authors. All Rights Reserved.
|
|
3
|
+
* 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
|
|
4
|
+
* 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.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {render, h} from 'preact';
|
|
8
|
+
|
|
9
|
+
import {App} from './src/app';
|
|
10
|
+
|
|
11
|
+
export function renderFlowReport(
|
|
12
|
+
flowResult: LH.FlowResult,
|
|
13
|
+
root: HTMLElement,
|
|
14
|
+
options?: LH.FlowReportOptions
|
|
15
|
+
) {
|
|
16
|
+
root.classList.add('flow-vars', 'lh-vars', 'lh-root');
|
|
17
|
+
render(h(App, {flowResult, options}), root);
|
|
18
|
+
}
|
|
@@ -21,7 +21,6 @@ limitations under the License.
|
|
|
21
21
|
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
|
|
22
22
|
<link rel="icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEhklEQVR4AWJxL/BhIAesev1U5tcflpncgNrKIsqNIwzC9feMpDUzs70kOczMzMzJJcxwCTMzncPMnOwtzBwzMzPb0vRfeZPp0VhPS5I39V5fdiXV1/VD+9QC7OVn9BsyH1XIoEI1PfmJvLFowVV564+34DFUHudbmfDh4kVXh//7XwE+WjS/YfXZe3yr4j2rqj1AIhSB7hZ8ZtPZu/zw8cK523U4wE1/rvPfWrz4zs0m9ZdC9yUJAlASdBAgocRegfF/f3/h/PuaFsxMdwjAR0vm1+06eMMfIrhLqTWqdH4EumU2SPfMhigJAlRQbZrgrRsl9U+Y2DYDFCz3ILC9kiAiqSrMwbWT0nceEnR+9Kggc2zjOJCASDENkg0a5HfZZgDP81CM3CrQs2Z1+o7DJ6ePr8sK0AOCHv5Jjdt3evyYSaZ351VIStIxPRAUtrBYbxC6w+BZ0ivVSBKkIhJhemSyZpfB00EiPO2VjzYkxhcqXQqCWCShGplvi3y0QxqbuBurMjyJeWnkHZuAEgIQGsUBqwrfjZ+IlBgKyRJzVVYF8O6qFWdh86YzQzMrZigYmxAyfvHgLZQ/LC1CbeniW2Hkqr/PH16SgvGuf2/uzNMBwJA/njxizGPtSyAf7EziJCMGRDRdhoAC4PL1A/SrKQMAAQkEfpJAcRQdrBJ7gNwjSpJsdwK+CANBkqa1LgQB4IicV9nYUct7gaxuDJUErQIiEAiMxLVOFlKzIktPpT0ggpdpC/8YAHnxbgkUY4tAAFSR7AAXNyAAWHJrA/kHGjzg5nleuwFO7Nd/IoDw4Pm58+4jNLmYG0wRA5bErc2Mr3Y+dXTDW1VvwqbJkzMCHQ4S1GTCBOIgUHJrGdEwqzR+jAp/o2qAZelUDoQnruEEdDclJI6576AlNVfc+22XN/+Y1vnJD0Yind6UpEEvn/Hqq15EYjCW7jZCJEpnNvDgkyelDjs106kuux2AAXCSobULOWP8mLhYlpoDMK4qAFXJGk+grtH8YXVz5KJblqaG1+VUdTc0I290bmUQAriGITRbdQnom0aoFj8kx1+wMD2ifncAXUQE4SkDqN1hE0jEophs1SUwZAOhUAiMCLwRtamtTZtbbmZErSAUHbSysaoEmnrsakiMiUAURi283gN6wans9oX8rOCrj7/JP35DFD+iQ7Au/K2KE1jzx6ujjUnXFH9KjEq6ZlhsTBICrNLJf47Pv/pkHzvup1w4dmUbEei0+bcXRqJuh5kVARQ8byyYxOwNGr7A87xh1tp8sGT+uMInrwi++Xj7TQz2d27NvwEkrOflAFQGIDA5khASBCGdO2/Z/MnLPwYfv5TFhjW7QhVKAB6afwe2LpFlFsCnlQEosgQgDsdOG1/LKeNqJS4JCSPJ/i+TakwEARor7gER1Iva5JmPOJK0RUqmoPnnlzFCtmIAhAAQEIQRgDaiYPIauNXcnDlRIrWNFY3hm7PG9YRqr7IV7HrCgAC17befjEvRq2nGhAHtBqDpOuI/I1diUUAMYIxEdyejBJqLnNoszGZtfiX/CztGv2mq+sdaAAAAAElFTkSuQmCC">
|
|
23
23
|
<title>Lighthouse Flow Report</title>
|
|
24
|
-
<style>/*%%LIGHTHOUSE_CSS%%*/</style>
|
|
25
24
|
<style>/*%%LIGHTHOUSE_FLOW_CSS%%*/</style>
|
|
26
25
|
<style>body {margin: 0}</style>
|
|
27
26
|
</head>
|
|
@@ -9,15 +9,14 @@
|
|
|
9
9
|
* The renderer code is bundled and injected into the report HTML along with the JSON report.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
import {App} from './src/app';
|
|
12
|
+
import {renderFlowReport} from '../api';
|
|
15
13
|
|
|
16
14
|
function __initLighthouseFlowReport__() {
|
|
17
15
|
const container = document.body.querySelector('main');
|
|
18
16
|
if (!container) throw Error('Container element not found');
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
renderFlowReport(window.__LIGHTHOUSE_FLOW_JSON__, container, {
|
|
18
|
+
getReportHtml: () => document.documentElement.outerHTML,
|
|
19
|
+
});
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
window.__initLighthouseFlowReport__ = __initLighthouseFlowReport__;
|
package/flow-report/src/app.tsx
CHANGED
|
@@ -5,15 +5,16 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {FunctionComponent} from 'preact';
|
|
8
|
-
import {useLayoutEffect, useRef, useState} from 'preact/hooks';
|
|
8
|
+
import {useLayoutEffect, useMemo, useRef, useState} from 'preact/hooks';
|
|
9
9
|
|
|
10
10
|
import {Sidebar} from './sidebar/sidebar';
|
|
11
11
|
import {Summary} from './summary/summary';
|
|
12
|
-
import {classNames, FlowResultContext, useHashState} from './util';
|
|
12
|
+
import {classNames, FlowResultContext, OptionsContext, useHashState} from './util';
|
|
13
13
|
import {Report} from './wrappers/report';
|
|
14
14
|
import {Topbar} from './topbar';
|
|
15
15
|
import {Header} from './header';
|
|
16
16
|
import {I18nProvider} from './i18n/i18n';
|
|
17
|
+
import {Styles} from './wrappers/styles';
|
|
17
18
|
|
|
18
19
|
function getAnchorElement(hashState: LH.FlowResult.HashState|null) {
|
|
19
20
|
if (!hashState || !hashState.anchor) return null;
|
|
@@ -47,17 +48,24 @@ const Content: FunctionComponent = () => {
|
|
|
47
48
|
);
|
|
48
49
|
};
|
|
49
50
|
|
|
50
|
-
export const App: FunctionComponent<{
|
|
51
|
+
export const App: FunctionComponent<{
|
|
52
|
+
flowResult: LH.FlowResult,
|
|
53
|
+
options?: LH.FlowReportOptions
|
|
54
|
+
}> = ({flowResult, options}) => {
|
|
51
55
|
const [collapsed, setCollapsed] = useState(false);
|
|
56
|
+
const optionsValue = useMemo(() => options || {}, [options]);
|
|
52
57
|
return (
|
|
53
|
-
<
|
|
54
|
-
<
|
|
55
|
-
<
|
|
56
|
-
<
|
|
57
|
-
<
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
<OptionsContext.Provider value={optionsValue}>
|
|
59
|
+
<FlowResultContext.Provider value={flowResult}>
|
|
60
|
+
<I18nProvider>
|
|
61
|
+
<Styles/>
|
|
62
|
+
<div className={classNames('App', {'App--collapsed': collapsed})} data-testid="App">
|
|
63
|
+
<Topbar onMenuClick={() => setCollapsed(c => !c)} />
|
|
64
|
+
<Sidebar/>
|
|
65
|
+
<Content/>
|
|
66
|
+
</div>
|
|
67
|
+
</I18nProvider>
|
|
68
|
+
</FlowResultContext.Provider>
|
|
69
|
+
</OptionsContext.Provider>
|
|
62
70
|
);
|
|
63
71
|
};
|
|
@@ -48,8 +48,6 @@ export const SidebarFlow: FunctionComponent = () => {
|
|
|
48
48
|
{
|
|
49
49
|
flowResult.steps.map((step, index) => {
|
|
50
50
|
const {lhr, name} = step;
|
|
51
|
-
const url = new URL(location.href);
|
|
52
|
-
url.hash = `#index=${index}`;
|
|
53
51
|
return <>
|
|
54
52
|
{
|
|
55
53
|
lhr.gatherMode === 'navigation' && index !== 0 ?
|
|
@@ -59,7 +57,7 @@ export const SidebarFlow: FunctionComponent = () => {
|
|
|
59
57
|
<SidebarFlowStep
|
|
60
58
|
key={lhr.fetchTime}
|
|
61
59
|
mode={lhr.gatherMode}
|
|
62
|
-
href={
|
|
60
|
+
href={`#index=${index}`}
|
|
63
61
|
label={name}
|
|
64
62
|
isCurrent={index === (hashState && hashState.index)}
|
|
65
63
|
/>
|
|
@@ -17,11 +17,9 @@ const SidebarSummary: FunctionComponent = () => {
|
|
|
17
17
|
const hashState = useHashState();
|
|
18
18
|
const strings = useLocalizedStrings();
|
|
19
19
|
|
|
20
|
-
const url = new URL(location.href);
|
|
21
|
-
url.hash = '#';
|
|
22
20
|
return (
|
|
23
21
|
<a
|
|
24
|
-
href=
|
|
22
|
+
href="#"
|
|
25
23
|
className={classNames('SidebarSummary', {'Sidebar--current': hashState === null})}
|
|
26
24
|
data-testid="SidebarSummary"
|
|
27
25
|
>
|
|
@@ -8,20 +8,15 @@ import {FunctionComponent, JSX} from 'preact';
|
|
|
8
8
|
import {useState} from 'preact/hooks';
|
|
9
9
|
|
|
10
10
|
import {HelpDialog} from './help-dialog';
|
|
11
|
-
import {
|
|
11
|
+
import {getFlowResultFilenamePrefix} from '../../report/generator/file-namer';
|
|
12
12
|
import {useLocalizedStrings} from './i18n/i18n';
|
|
13
13
|
import {HamburgerIcon, InfoIcon} from './icons';
|
|
14
|
-
import {useFlowResult} from './util';
|
|
14
|
+
import {useFlowResult, useOptions} from './util';
|
|
15
15
|
import {saveFile} from '../../report/renderer/api';
|
|
16
16
|
|
|
17
|
-
function saveHtml(flowResult: LH.FlowResult) {
|
|
18
|
-
const htmlStr = document.documentElement.outerHTML;
|
|
17
|
+
function saveHtml(flowResult: LH.FlowResult, htmlStr: string) {
|
|
19
18
|
const blob = new Blob([htmlStr], {type: 'text/html'});
|
|
20
|
-
|
|
21
|
-
const lhr = flowResult.steps[0].lhr;
|
|
22
|
-
const name = flowResult.name.replace(/\s/g, '-');
|
|
23
|
-
const filename = getFilenamePrefix(name, lhr.fetchTime);
|
|
24
|
-
|
|
19
|
+
const filename = getFlowResultFilenamePrefix(flowResult);
|
|
25
20
|
saveFile(blob, filename);
|
|
26
21
|
}
|
|
27
22
|
|
|
@@ -78,6 +73,7 @@ export const Topbar: FunctionComponent<{onMenuClick: JSX.MouseEventHandler<HTMLB
|
|
|
78
73
|
const flowResult = useFlowResult();
|
|
79
74
|
const strings = useLocalizedStrings();
|
|
80
75
|
const [showHelpDialog, setShowHelpDialog] = useState(false);
|
|
76
|
+
const {getReportHtml, saveAsGist} = useOptions();
|
|
81
77
|
|
|
82
78
|
return (
|
|
83
79
|
<div className="Topbar">
|
|
@@ -88,10 +84,23 @@ export const Topbar: FunctionComponent<{onMenuClick: JSX.MouseEventHandler<HTMLB
|
|
|
88
84
|
<Logo/>
|
|
89
85
|
</div>
|
|
90
86
|
<div className="Topbar__title">{strings.title}</div>
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
87
|
+
{
|
|
88
|
+
getReportHtml &&
|
|
89
|
+
<TopbarButton
|
|
90
|
+
onClick={() => {
|
|
91
|
+
const htmlStr = getReportHtml(flowResult);
|
|
92
|
+
saveHtml(flowResult, htmlStr);
|
|
93
|
+
}}
|
|
94
|
+
label="Button that saves the report as HTML"
|
|
95
|
+
>{strings.save}</TopbarButton>
|
|
96
|
+
}
|
|
97
|
+
{
|
|
98
|
+
saveAsGist &&
|
|
99
|
+
<TopbarButton
|
|
100
|
+
onClick={() => saveAsGist(flowResult)}
|
|
101
|
+
label="Button that saves the report to a gist"
|
|
102
|
+
>{strings.dropdownSaveGist}</TopbarButton>
|
|
103
|
+
}
|
|
95
104
|
<div style={{flexGrow: 1}} />
|
|
96
105
|
<TopbarButton
|
|
97
106
|
onClick={() => setShowHelpDialog(previous => !previous)}
|
package/flow-report/src/util.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {useContext, useEffect, useLayoutEffect, useMemo, useRef, useState} from
|
|
|
10
10
|
import type {UIStringsType} from './i18n/ui-strings';
|
|
11
11
|
|
|
12
12
|
const FlowResultContext = createContext<LH.FlowResult|undefined>(undefined);
|
|
13
|
+
const OptionsContext = createContext<LH.FlowReportOptions>({});
|
|
13
14
|
|
|
14
15
|
function getHashParam(param: string): string|null {
|
|
15
16
|
const params = new URLSearchParams(location.hash.replace('#', '?'));
|
|
@@ -147,8 +148,13 @@ function useExternalRenderer<T extends Element>(
|
|
|
147
148
|
return ref;
|
|
148
149
|
}
|
|
149
150
|
|
|
151
|
+
function useOptions() {
|
|
152
|
+
return useContext(OptionsContext);
|
|
153
|
+
}
|
|
154
|
+
|
|
150
155
|
export {
|
|
151
156
|
FlowResultContext,
|
|
157
|
+
OptionsContext,
|
|
152
158
|
classNames,
|
|
153
159
|
getScreenDimensions,
|
|
154
160
|
getFullPageScreenshot,
|
|
@@ -158,4 +164,5 @@ export {
|
|
|
158
164
|
useHashParams,
|
|
159
165
|
useHashState,
|
|
160
166
|
useExternalRenderer,
|
|
167
|
+
useOptions,
|
|
161
168
|
};
|
|
@@ -35,6 +35,7 @@ export const Report: FunctionComponent<{hashState: LH.FlowResult.HashState}> =
|
|
|
35
35
|
return renderReport(hashState.currentLhr, {
|
|
36
36
|
disableAutoDarkModeAndFireworks: true,
|
|
37
37
|
omitTopbar: true,
|
|
38
|
+
omitGlobalStyles: true,
|
|
38
39
|
onPageAnchorRendered: link => convertAnchor(link, hashState.index),
|
|
39
40
|
});
|
|
40
41
|
}, [hashState]);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright 2021 The Lighthouse Authors. All Rights Reserved.
|
|
3
|
+
* 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
|
|
4
|
+
* 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.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {FunctionComponent} from 'preact';
|
|
8
|
+
|
|
9
|
+
import {createStylesElement} from '../../../report/renderer/api';
|
|
10
|
+
import {useExternalRenderer} from '../util';
|
|
11
|
+
|
|
12
|
+
export const Styles: FunctionComponent = () => {
|
|
13
|
+
const ref = useExternalRenderer<HTMLDivElement>(createStylesElement);
|
|
14
|
+
|
|
15
|
+
return <div ref={ref}/>;
|
|
16
|
+
};
|
|
@@ -8,7 +8,7 @@ import {jest} from '@jest/globals';
|
|
|
8
8
|
import {FunctionComponent} from 'preact';
|
|
9
9
|
import {act, render} from '@testing-library/preact';
|
|
10
10
|
|
|
11
|
-
import {FlowResultContext} from '../src/util';
|
|
11
|
+
import {FlowResultContext, OptionsContext} from '../src/util';
|
|
12
12
|
import {I18nProvider} from '../src/i18n/i18n';
|
|
13
13
|
|
|
14
14
|
const mockSaveFile = jest.fn();
|
|
@@ -31,19 +31,24 @@ const flowResult = {
|
|
|
31
31
|
} as any;
|
|
32
32
|
|
|
33
33
|
let wrapper: FunctionComponent;
|
|
34
|
+
let options: LH.FlowReportOptions;
|
|
34
35
|
|
|
35
36
|
beforeEach(() => {
|
|
36
37
|
mockSaveFile.mockReset();
|
|
38
|
+
options = {};
|
|
37
39
|
wrapper = ({children}) => (
|
|
38
|
-
<
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
<OptionsContext.Provider value={options}>
|
|
41
|
+
<FlowResultContext.Provider value={flowResult}>
|
|
42
|
+
<I18nProvider>
|
|
43
|
+
{children}
|
|
44
|
+
</I18nProvider>
|
|
45
|
+
</FlowResultContext.Provider>
|
|
46
|
+
</OptionsContext.Provider>
|
|
43
47
|
);
|
|
44
48
|
});
|
|
45
49
|
|
|
46
50
|
it('save button opens save dialog for HTML file', async () => {
|
|
51
|
+
options = {getReportHtml: () => '<html></html>'};
|
|
47
52
|
const root = render(<Topbar onMenuClick={() => {}}/>, {wrapper});
|
|
48
53
|
|
|
49
54
|
const saveButton = root.getByText('Save');
|
|
@@ -55,6 +60,17 @@ it('save button opens save dialog for HTML file', async () => {
|
|
|
55
60
|
);
|
|
56
61
|
});
|
|
57
62
|
|
|
63
|
+
it('provides save as gist option if defined', async () => {
|
|
64
|
+
const saveAsGist = jest.fn();
|
|
65
|
+
options = {saveAsGist};
|
|
66
|
+
const root = render(<Topbar onMenuClick={() => {}}/>, {wrapper});
|
|
67
|
+
|
|
68
|
+
const saveButton = root.getByText('Save as Gist');
|
|
69
|
+
saveButton.click();
|
|
70
|
+
|
|
71
|
+
expect(saveAsGist).toHaveBeenCalledWith(flowResult);
|
|
72
|
+
});
|
|
73
|
+
|
|
58
74
|
it('toggles help dialog', async () => {
|
|
59
75
|
const root = render(<Topbar onMenuClick={() => {}}/>, {wrapper});
|
|
60
76
|
|
|
@@ -20,6 +20,10 @@ declare global {
|
|
|
20
20
|
// Expose global types in LH namespace.
|
|
21
21
|
module LH {
|
|
22
22
|
export type ConfigSettings = Settings.ConfigSettings;
|
|
23
|
+
export interface FlowReportOptions {
|
|
24
|
+
getReportHtml?: (flowResult: FlowResult_) => string;
|
|
25
|
+
saveAsGist?: (flowResult: FlowResult_) => void;
|
|
26
|
+
}
|
|
23
27
|
|
|
24
28
|
export import FlowResult = FlowResult_;
|
|
25
29
|
}
|
|
@@ -225,7 +225,7 @@ function getFlags(manualArgv, options = {}) {
|
|
|
225
225
|
.options({
|
|
226
226
|
'output': {
|
|
227
227
|
type: 'array',
|
|
228
|
-
default: /** @type {
|
|
228
|
+
default: /** @type {const} */ (['html']),
|
|
229
229
|
coerce: coerceOutput,
|
|
230
230
|
describe: 'Reporter for the results, supports multiple values. choices: "json", "html", "csv"',
|
|
231
231
|
},
|
|
@@ -309,9 +309,9 @@ function getFlags(manualArgv, options = {}) {
|
|
|
309
309
|
})
|
|
310
310
|
|
|
311
311
|
// Choices added outside of `options()` and cast so tsc picks them up.
|
|
312
|
-
.choices('form-factor', /** @type {
|
|
313
|
-
.choices('throttling-method', /** @type {
|
|
314
|
-
.choices('preset', /** @type {
|
|
312
|
+
.choices('form-factor', /** @type {const} */ (['mobile', 'desktop']))
|
|
313
|
+
.choices('throttling-method', /** @type {const} */ (['devtools', 'provided', 'simulate']))
|
|
314
|
+
.choices('preset', /** @type {const} */ (['perf', 'experimental', 'desktop']))
|
|
315
315
|
|
|
316
316
|
.check(argv => {
|
|
317
317
|
// Lighthouse doesn't need a URL if...
|
|
@@ -233,21 +233,53 @@ class Audit {
|
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
/**
|
|
236
|
-
* @param {LH.Artifacts.
|
|
237
|
-
* @
|
|
236
|
+
* @param {LH.Artifacts.Bundle} bundle
|
|
237
|
+
* @param {number} generatedLine
|
|
238
|
+
* @param {number} generatedColumn
|
|
239
|
+
* @return {LH.Audit.Details.SourceLocationValue['original']}
|
|
238
240
|
*/
|
|
239
|
-
static
|
|
240
|
-
|
|
241
|
+
static _findOriginalLocation(bundle, generatedLine, generatedColumn) {
|
|
242
|
+
const entry = bundle?.map.findEntry(generatedLine, generatedColumn);
|
|
243
|
+
if (!entry) return;
|
|
241
244
|
|
|
245
|
+
return {
|
|
246
|
+
file: entry.sourceURL || '',
|
|
247
|
+
line: entry.sourceLineNumber || 0,
|
|
248
|
+
column: entry.sourceColumnNumber || 0,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* @param {string} url
|
|
254
|
+
* @param {number} line 0-indexed
|
|
255
|
+
* @param {number} column 0-indexed
|
|
256
|
+
* @param {LH.Artifacts.Bundle=} bundle
|
|
257
|
+
* @return {LH.Audit.Details.SourceLocationValue}
|
|
258
|
+
*/
|
|
259
|
+
static makeSourceLocation(url, line, column, bundle) {
|
|
242
260
|
return {
|
|
243
261
|
type: 'source-location',
|
|
244
|
-
url
|
|
262
|
+
url,
|
|
245
263
|
urlProvider: 'network',
|
|
246
|
-
line
|
|
247
|
-
column
|
|
264
|
+
line,
|
|
265
|
+
column,
|
|
266
|
+
original: bundle && this._findOriginalLocation(bundle, line, column),
|
|
248
267
|
};
|
|
249
268
|
}
|
|
250
269
|
|
|
270
|
+
/**
|
|
271
|
+
* @param {LH.Artifacts.ConsoleMessage} entry
|
|
272
|
+
* @param {LH.Artifacts.Bundle=} bundle
|
|
273
|
+
* @return {LH.Audit.Details.SourceLocationValue | undefined}
|
|
274
|
+
*/
|
|
275
|
+
static makeSourceLocationFromConsoleMessage(entry, bundle) {
|
|
276
|
+
if (!entry.url) return;
|
|
277
|
+
|
|
278
|
+
const line = entry.lineNumber || 0;
|
|
279
|
+
const column = entry.columnNumber || 0;
|
|
280
|
+
return this.makeSourceLocation(entry.url, line, column, bundle);
|
|
281
|
+
}
|
|
282
|
+
|
|
251
283
|
/**
|
|
252
284
|
* @param {number|null} score
|
|
253
285
|
* @param {LH.Audit.ScoreDisplayMode} scoreDisplayMode
|
|
@@ -397,23 +397,6 @@ class LegacyJavascript extends ByteEfficiencyAudit {
|
|
|
397
397
|
return transferRatio;
|
|
398
398
|
}
|
|
399
399
|
|
|
400
|
-
/**
|
|
401
|
-
* @param {LH.Artifacts.Bundle} bundle
|
|
402
|
-
* @param {number} generatedLine
|
|
403
|
-
* @param {number} generatedColumn
|
|
404
|
-
* @return {LH.Audit.Details.SourceLocationValue['original']}
|
|
405
|
-
*/
|
|
406
|
-
static _findOriginalLocation(bundle, generatedLine, generatedColumn) {
|
|
407
|
-
const entry = bundle && bundle.map.findEntry(generatedLine, generatedColumn);
|
|
408
|
-
if (!entry) return;
|
|
409
|
-
|
|
410
|
-
return {
|
|
411
|
-
file: entry.sourceURL || '',
|
|
412
|
-
line: entry.sourceLineNumber || 0,
|
|
413
|
-
column: entry.sourceColumnNumber || 0,
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
|
|
417
400
|
/**
|
|
418
401
|
* @param {LH.Artifacts} artifacts
|
|
419
402
|
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
|
|
@@ -456,18 +439,10 @@ class LegacyJavascript extends ByteEfficiencyAudit {
|
|
|
456
439
|
const bundle = bundles.find(bundle => bundle.script.src === url);
|
|
457
440
|
for (const match of matches) {
|
|
458
441
|
const {name, line, column} = match;
|
|
459
|
-
|
|
460
442
|
/** @type {SubItem} */
|
|
461
443
|
const subItem = {
|
|
462
444
|
signal: name,
|
|
463
|
-
location:
|
|
464
|
-
type: 'source-location',
|
|
465
|
-
url,
|
|
466
|
-
line,
|
|
467
|
-
column,
|
|
468
|
-
original: bundle && this._findOriginalLocation(bundle, line, column),
|
|
469
|
-
urlProvider: 'network',
|
|
470
|
-
},
|
|
445
|
+
location: ByteEfficiencyAudit.makeSourceLocation(url, line, column, bundle),
|
|
471
446
|
};
|
|
472
447
|
item.subItems.items.push(subItem);
|
|
473
448
|
}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
// TODO: when M97 is sufficiently old, drop support for console messages
|
|
15
15
|
|
|
16
16
|
const Audit = require('./audit.js');
|
|
17
|
+
const JsBundles = require('../computed/js-bundles.js');
|
|
17
18
|
const i18n = require('../lib/i18n/i18n.js');
|
|
18
19
|
|
|
19
20
|
const UIStrings = {
|
|
@@ -47,34 +48,29 @@ class Deprecations extends Audit {
|
|
|
47
48
|
title: str_(UIStrings.title),
|
|
48
49
|
failureTitle: str_(UIStrings.failureTitle),
|
|
49
50
|
description: str_(UIStrings.description),
|
|
50
|
-
requiredArtifacts: ['ConsoleMessages', 'InspectorIssues'],
|
|
51
|
+
requiredArtifacts: ['ConsoleMessages', 'InspectorIssues', 'SourceMaps', 'ScriptElements'],
|
|
51
52
|
};
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
/**
|
|
55
56
|
* @param {LH.Artifacts} artifacts
|
|
56
|
-
* @
|
|
57
|
+
* @param {LH.Audit.Context} context
|
|
58
|
+
* @return {Promise<LH.Audit.Product>}
|
|
57
59
|
*/
|
|
58
|
-
static audit(artifacts) {
|
|
60
|
+
static async audit(artifacts, context) {
|
|
59
61
|
const entries = artifacts.ConsoleMessages;
|
|
62
|
+
const bundles = await JsBundles.request(artifacts, context);
|
|
60
63
|
|
|
61
64
|
let deprecations;
|
|
62
|
-
|
|
63
65
|
if (artifacts.InspectorIssues.deprecations.length) {
|
|
64
66
|
deprecations = artifacts.InspectorIssues.deprecations
|
|
65
67
|
.map(deprecation => {
|
|
68
|
+
const {url, lineNumber, columnNumber} = deprecation.sourceCodeLocation;
|
|
69
|
+
const bundle = bundles.find(bundle => bundle.script.src === url);
|
|
66
70
|
return {
|
|
67
71
|
value: deprecation.message || '',
|
|
68
|
-
|
|
69
|
-
source:
|
|
70
|
-
type: 'source-location',
|
|
71
|
-
url: deprecation.sourceCodeLocation.url,
|
|
72
|
-
urlProvider: 'network',
|
|
73
|
-
line: deprecation.sourceCodeLocation.lineNumber,
|
|
74
|
-
// Protocol.Audits.SourceCodeLocation.columnNumber is 1-indexed,
|
|
75
|
-
// but we use 0-indexed.
|
|
76
|
-
column: deprecation.sourceCodeLocation.columnNumber - 1,
|
|
77
|
-
},
|
|
72
|
+
// Protocol.Audits.SourceCodeLocation.columnNumber is 1-indexed, but we use 0-indexed.
|
|
73
|
+
source: Audit.makeSourceLocation(url, lineNumber, columnNumber - 1, bundle),
|
|
78
74
|
};
|
|
79
75
|
});
|
|
80
76
|
} else {
|
|
@@ -38,17 +38,18 @@ class GeolocationOnStart extends ViolationAudit {
|
|
|
38
38
|
failureTitle: str_(UIStrings.failureTitle),
|
|
39
39
|
description: str_(UIStrings.description),
|
|
40
40
|
supportedModes: ['navigation'],
|
|
41
|
-
requiredArtifacts: ['ConsoleMessages'],
|
|
41
|
+
requiredArtifacts: ['ConsoleMessages', 'SourceMaps', 'ScriptElements'],
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
46
|
* @param {LH.Artifacts} artifacts
|
|
47
|
-
* @
|
|
47
|
+
* @param {LH.Audit.Context} context
|
|
48
|
+
* @return {Promise<LH.Audit.Product>}
|
|
48
49
|
*/
|
|
49
|
-
static audit(artifacts) {
|
|
50
|
+
static async audit(artifacts, context) {
|
|
50
51
|
// 'Only request geolocation information in response to a user gesture.'
|
|
51
|
-
const results = ViolationAudit.getViolationResults(artifacts, /geolocation/);
|
|
52
|
+
const results = await ViolationAudit.getViolationResults(artifacts, context, /geolocation/);
|
|
52
53
|
|
|
53
54
|
/** @type {LH.Audit.Details.Table['headings']} */
|
|
54
55
|
const headings = [
|
|
@@ -61,7 +61,7 @@ class JsLibrariesAudit extends Audit {
|
|
|
61
61
|
const details = Audit.makeTableDetails(headings, libDetails, {});
|
|
62
62
|
|
|
63
63
|
const debugData = {
|
|
64
|
-
type: /** @type {
|
|
64
|
+
type: /** @type {const} */ ('debugdata'),
|
|
65
65
|
stacks: artifacts.Stacks.map(stack => {
|
|
66
66
|
return {
|
|
67
67
|
id: stack.id,
|
|
@@ -54,16 +54,18 @@ class NoDocWriteAudit extends ViolationAudit {
|
|
|
54
54
|
title: str_(UIStrings.title),
|
|
55
55
|
failureTitle: str_(UIStrings.failureTitle),
|
|
56
56
|
description: str_(UIStrings.description),
|
|
57
|
-
requiredArtifacts: ['ConsoleMessages'],
|
|
57
|
+
requiredArtifacts: ['ConsoleMessages', 'SourceMaps', 'ScriptElements'],
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
62
|
* @param {LH.Artifacts} artifacts
|
|
63
|
-
* @
|
|
63
|
+
* @param {LH.Audit.Context} context
|
|
64
|
+
* @return {Promise<LH.Audit.Product>}
|
|
64
65
|
*/
|
|
65
|
-
static audit(artifacts) {
|
|
66
|
-
const results =
|
|
66
|
+
static async audit(artifacts, context) {
|
|
67
|
+
const results =
|
|
68
|
+
await ViolationAudit.getViolationResults(artifacts, context, /document\.write/);
|
|
67
69
|
|
|
68
70
|
/** @type {LH.Audit.Details.Table['headings']} */
|
|
69
71
|
const headings = [
|