lighthouse 8.6.0-dev.20211101 → 8.6.0-dev.20211105
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/dist/report/flow.js +14 -14
- package/dist/report/standalone.js +41 -35
- package/flow-report/.eslintrc.cjs +2 -0
- package/flow-report/assets/standalone-flow-template.html +3 -2
- package/flow-report/assets/styles.css +1 -1
- package/flow-report/src/common.tsx +11 -4
- package/flow-report/src/i18n/i18n.tsx +10 -6
- package/flow-report/src/icons.tsx +30 -9
- package/flow-report/src/sidebar/sidebar.tsx +27 -8
- package/flow-report/src/summary/category.tsx +7 -2
- package/flow-report/src/summary/summary.tsx +9 -3
- package/flow-report/src/util.ts +21 -9
- package/flow-report/src/wrappers/report-renderer.tsx +10 -3
- package/flow-report/src/wrappers/report.tsx +9 -5
- package/flow-report/standalone-flow.tsx +4 -3
- package/flow-report/test/setup/env-setup.ts +1 -1
- package/flow-report/test/sidebar/sidebar-test.tsx +43 -1
- package/lighthouse-cli/.eslintrc.cjs +2 -0
- package/lighthouse-cli/cli-flags.js +1 -1
- package/lighthouse-core/audits/audit.js +1 -1
- package/lighthouse-core/computed/resource-summary.js +1 -1
- package/lighthouse-core/config/default-config.js +0 -2
- package/lighthouse-core/gather/connections/cri.js +1 -1
- package/lighthouse-core/lib/proto-preprocessor.js +14 -2
- package/lighthouse-core/lib/url-shim.js +1 -1
- package/lighthouse-core/util-commonjs.js +24 -9
- package/package.json +3 -3
- package/report/.eslintrc.cjs +2 -0
- package/report/assets/standalone-template.html +2 -3
- package/report/assets/styles.css +227 -251
- package/report/assets/templates.html +19 -29
- package/report/clients/bundle.js +1 -0
- package/report/clients/standalone.js +6 -13
- package/report/renderer/api.js +32 -0
- package/report/renderer/category-renderer.js +22 -18
- package/report/renderer/components.js +46 -79
- package/report/renderer/crc-details-renderer.js +15 -12
- package/report/renderer/dom.js +16 -1
- package/report/renderer/drop-down-menu.js +2 -2
- package/report/renderer/element-screenshot-renderer.js +6 -6
- package/report/renderer/features-util.js +1 -1
- package/report/renderer/open-tab.js +9 -3
- package/report/renderer/performance-category-renderer.js +31 -19
- package/report/renderer/report-renderer.js +34 -10
- package/report/renderer/report-ui-features.js +36 -33
- package/report/renderer/swap-locale-feature.js +3 -3
- package/report/renderer/topbar-features.js +52 -48
- package/report/renderer/util.js +25 -9
- package/report/test/clients/bundle-test.js +8 -0
- package/report/test/renderer/category-renderer-test.js +2 -2
- package/report/test/renderer/performance-category-renderer-test.js +1 -1
- package/report/test/renderer/report-renderer-axe-test.js +5 -2
- package/report/test/renderer/report-renderer-test.js +13 -10
- package/report/test/renderer/report-ui-features-test.js +17 -10
- package/report/test-assets/faux-psi-template.html +2 -7
- package/report/test-assets/faux-psi.js +20 -39
- package/report/types/html-renderer.d.ts +2 -0
- package/report/types/report-renderer.d.ts +23 -0
- package/shared/localization/locales/ar-XB.json +0 -15
- package/shared/localization/locales/ar.json +0 -15
- package/shared/localization/locales/bg.json +0 -15
- package/shared/localization/locales/ca.json +0 -15
- package/shared/localization/locales/cs.json +0 -15
- package/shared/localization/locales/da.json +0 -15
- package/shared/localization/locales/de.json +0 -15
- package/shared/localization/locales/el.json +0 -15
- package/shared/localization/locales/en-GB.json +0 -15
- package/shared/localization/locales/en-US.json +18 -15
- package/shared/localization/locales/en-XA.json +0 -15
- package/shared/localization/locales/en-XL.json +18 -15
- package/shared/localization/locales/es-419.json +0 -15
- package/shared/localization/locales/es.json +0 -15
- package/shared/localization/locales/fi.json +0 -15
- package/shared/localization/locales/fil.json +0 -15
- package/shared/localization/locales/fr.json +0 -15
- package/shared/localization/locales/he.json +0 -15
- package/shared/localization/locales/hi.json +0 -15
- package/shared/localization/locales/hr.json +0 -15
- package/shared/localization/locales/hu.json +0 -15
- package/shared/localization/locales/id.json +0 -15
- package/shared/localization/locales/it.json +0 -15
- package/shared/localization/locales/ja.json +0 -15
- package/shared/localization/locales/ko.json +0 -15
- package/shared/localization/locales/lt.json +0 -15
- package/shared/localization/locales/lv.json +0 -15
- package/shared/localization/locales/nl.json +0 -15
- package/shared/localization/locales/no.json +0 -15
- package/shared/localization/locales/pl.json +0 -15
- package/shared/localization/locales/pt-PT.json +0 -15
- package/shared/localization/locales/pt.json +0 -15
- package/shared/localization/locales/ro.json +0 -15
- package/shared/localization/locales/ru.json +0 -15
- package/shared/localization/locales/sk.json +0 -15
- package/shared/localization/locales/sl.json +0 -15
- package/shared/localization/locales/sr-Latn.json +0 -15
- package/shared/localization/locales/sr.json +0 -15
- package/shared/localization/locales/sv.json +0 -15
- package/shared/localization/locales/ta.json +0 -15
- package/shared/localization/locales/te.json +0 -15
- package/shared/localization/locales/th.json +0 -15
- package/shared/localization/locales/tr.json +0 -15
- package/shared/localization/locales/uk.json +0 -15
- package/shared/localization/locales/vi.json +0 -15
- package/shared/localization/locales/zh-HK.json +0 -15
- package/shared/localization/locales/zh-TW.json +0 -15
- package/shared/localization/locales/zh.json +0 -15
- package/lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js +0 -96
|
@@ -21,12 +21,21 @@
|
|
|
21
21
|
* critical request chains network tree.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
+
import {Util} from './util.js';
|
|
25
|
+
|
|
24
26
|
/** @typedef {import('./dom.js').DOM} DOM */
|
|
25
27
|
/** @typedef {import('./details-renderer.js').DetailsRenderer} DetailsRenderer */
|
|
28
|
+
/**
|
|
29
|
+
* @typedef CRCSegment
|
|
30
|
+
* @property {LH.Audit.Details.SimpleCriticalRequestNode[string]} node
|
|
31
|
+
* @property {boolean} isLastChild
|
|
32
|
+
* @property {boolean} hasChildren
|
|
33
|
+
* @property {number} startTime
|
|
34
|
+
* @property {number} transferSize
|
|
35
|
+
* @property {boolean[]} treeMarkers
|
|
36
|
+
*/
|
|
26
37
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
export class CriticalRequestChainRenderer {
|
|
38
|
+
class CriticalRequestChainRenderer {
|
|
30
39
|
/**
|
|
31
40
|
* Create render context for critical-request-chain tree display.
|
|
32
41
|
* @param {LH.Audit.Details.SimpleCriticalRequestNode} tree
|
|
@@ -191,12 +200,6 @@ export class CriticalRequestChainRenderer {
|
|
|
191
200
|
// Alias b/c the name is really long.
|
|
192
201
|
const CRCRenderer = CriticalRequestChainRenderer;
|
|
193
202
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
hasChildren: boolean,
|
|
198
|
-
startTime: number,
|
|
199
|
-
transferSize: number,
|
|
200
|
-
treeMarkers: Array<boolean>
|
|
201
|
-
}} CRCSegment
|
|
202
|
-
*/
|
|
203
|
+
export {
|
|
204
|
+
CriticalRequestChainRenderer,
|
|
205
|
+
};
|
package/report/renderer/dom.js
CHANGED
|
@@ -27,14 +27,18 @@ import {createComponent} from './components.js';
|
|
|
27
27
|
export class DOM {
|
|
28
28
|
/**
|
|
29
29
|
* @param {Document} document
|
|
30
|
+
* @param {HTMLElement} rootEl
|
|
30
31
|
*/
|
|
31
|
-
constructor(document) {
|
|
32
|
+
constructor(document, rootEl) {
|
|
32
33
|
/** @type {Document} */
|
|
33
34
|
this._document = document;
|
|
34
35
|
/** @type {string} */
|
|
35
36
|
this._lighthouseChannel = 'unknown';
|
|
36
37
|
/** @type {Map<string, DocumentFragment>} */
|
|
37
38
|
this._componentCache = new Map();
|
|
39
|
+
/** @type {HTMLElement} */
|
|
40
|
+
// For legacy Report API users, this'll be undefined, but set in renderReport
|
|
41
|
+
this.rootEl = rootEl;
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
/**
|
|
@@ -76,6 +80,15 @@ export class DOM {
|
|
|
76
80
|
return this._document.createDocumentFragment();
|
|
77
81
|
}
|
|
78
82
|
|
|
83
|
+
/**
|
|
84
|
+
* @param {string} data
|
|
85
|
+
* @return {!Node}
|
|
86
|
+
*/
|
|
87
|
+
createTextNode(data) {
|
|
88
|
+
return this._document.createTextNode(data);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
79
92
|
/**
|
|
80
93
|
* @template {string} T
|
|
81
94
|
* @param {Element} parentElem
|
|
@@ -216,6 +229,8 @@ export class DOM {
|
|
|
216
229
|
}
|
|
217
230
|
|
|
218
231
|
/**
|
|
232
|
+
* ONLY use if `dom.rootEl` isn't sufficient for your needs. `dom.rootEl` is preferred
|
|
233
|
+
* for all scoping, because a document can have multiple reports within it.
|
|
219
234
|
* @return {Document}
|
|
220
235
|
*/
|
|
221
236
|
document() {
|
|
@@ -36,11 +36,11 @@ export class DropDownMenu {
|
|
|
36
36
|
* @param {function(MouseEvent): any} menuClickHandler
|
|
37
37
|
*/
|
|
38
38
|
setup(menuClickHandler) {
|
|
39
|
-
this._toggleEl = this._dom.find('button.lh-tools__button', this._dom.
|
|
39
|
+
this._toggleEl = this._dom.find('.lh-topbar button.lh-tools__button', this._dom.rootEl);
|
|
40
40
|
this._toggleEl.addEventListener('click', this.onToggleClick);
|
|
41
41
|
this._toggleEl.addEventListener('keydown', this.onToggleKeydown);
|
|
42
42
|
|
|
43
|
-
this._menuEl = this._dom.find('div.lh-tools__dropdown', this._dom.
|
|
43
|
+
this._menuEl = this._dom.find('.lh-topbar div.lh-tools__dropdown', this._dom.rootEl);
|
|
44
44
|
this._menuEl.addEventListener('keydown', this.onMenuKeydown);
|
|
45
45
|
this._menuEl.addEventListener('click', menuClickHandler);
|
|
46
46
|
}
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
/**
|
|
19
19
|
* @typedef InstallOverlayFeatureParams
|
|
20
20
|
* @property {DOM} dom
|
|
21
|
-
* @property {Element}
|
|
21
|
+
* @property {Element} rootEl
|
|
22
22
|
* @property {Element} overlayContainerEl
|
|
23
23
|
* @property {LH.Audit.Details.FullPageScreenshot} fullPageScreenshot
|
|
24
24
|
*/
|
|
@@ -135,7 +135,7 @@ export class ElementScreenshotRenderer {
|
|
|
135
135
|
* @param {LH.Audit.Details.FullPageScreenshot['screenshot']} screenshot
|
|
136
136
|
*/
|
|
137
137
|
static installFullPageScreenshot(el, screenshot) {
|
|
138
|
-
el.style.setProperty('--element-screenshot-url', `url(${screenshot.data})`);
|
|
138
|
+
el.style.setProperty('--element-screenshot-url', `url('${screenshot.data}')`);
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
/**
|
|
@@ -143,14 +143,14 @@ export class ElementScreenshotRenderer {
|
|
|
143
143
|
* @param {InstallOverlayFeatureParams} opts
|
|
144
144
|
*/
|
|
145
145
|
static installOverlayFeature(opts) {
|
|
146
|
-
const {dom,
|
|
146
|
+
const {dom, rootEl, overlayContainerEl, fullPageScreenshot} = opts;
|
|
147
147
|
const screenshotOverlayClass = 'lh-screenshot-overlay--enabled';
|
|
148
148
|
// Don't install the feature more than once.
|
|
149
|
-
if (
|
|
150
|
-
|
|
149
|
+
if (rootEl.classList.contains(screenshotOverlayClass)) return;
|
|
150
|
+
rootEl.classList.add(screenshotOverlayClass);
|
|
151
151
|
|
|
152
152
|
// Add a single listener to the provided element to handle all clicks within (event delegation).
|
|
153
|
-
|
|
153
|
+
rootEl.addEventListener('click', e => {
|
|
154
154
|
const target = /** @type {?HTMLElement} */ (e.target);
|
|
155
155
|
if (!target) return;
|
|
156
156
|
// Only activate the overlay for clicks on the screenshot *preview* of an element, not the full-size too.
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* @param {boolean} [force]
|
|
15
15
|
*/
|
|
16
16
|
export function toggleDarkTheme(dom, force) {
|
|
17
|
-
const el = dom.
|
|
17
|
+
const el = dom.rootEl;
|
|
18
18
|
// This seems unnecessary, but in DevTools, passing "undefined" as the second
|
|
19
19
|
// parameter acts like passing "false".
|
|
20
20
|
// https://github.com/ChromeDevTools/devtools-frontend/blob/dd6a6d4153647c2a4203c327c595692c5e0a4256/front_end/dom_extension/DOMExtension.js#L809-L819
|
|
@@ -90,7 +90,7 @@ async function openTabWithUrlData(data, url_, windowName) {
|
|
|
90
90
|
* @param {LH.Result} lhr
|
|
91
91
|
* @protected
|
|
92
92
|
*/
|
|
93
|
-
|
|
93
|
+
async function openViewer(lhr) {
|
|
94
94
|
const windowName = 'viewer-' + computeWindowNameSuffix(lhr);
|
|
95
95
|
const url = getAppsOrigin() + '/viewer/';
|
|
96
96
|
await openTabWithUrlData({lhr}, url, windowName);
|
|
@@ -101,7 +101,7 @@ export async function openViewer(lhr) {
|
|
|
101
101
|
* @param {LH.Result} lhr
|
|
102
102
|
* @protected
|
|
103
103
|
*/
|
|
104
|
-
|
|
104
|
+
async function openViewerAndSendData(lhr) {
|
|
105
105
|
const windowName = 'viewer-' + computeWindowNameSuffix(lhr);
|
|
106
106
|
const url = getAppsOrigin() + '/viewer/';
|
|
107
107
|
openTabAndSendData({lhr}, url, windowName);
|
|
@@ -111,7 +111,7 @@ export async function openViewerAndSendData(lhr) {
|
|
|
111
111
|
* Opens a new tab to the treemap app and sends the JSON results using URL.fragment
|
|
112
112
|
* @param {LH.Result} json
|
|
113
113
|
*/
|
|
114
|
-
|
|
114
|
+
function openTreemap(json) {
|
|
115
115
|
const treemapData = json.audits['script-treemap-data'].details;
|
|
116
116
|
if (!treemapData) {
|
|
117
117
|
throw new Error('no script treemap data found');
|
|
@@ -135,3 +135,9 @@ export function openTreemap(json) {
|
|
|
135
135
|
|
|
136
136
|
openTabWithUrlData(treemapOptions, url, windowName);
|
|
137
137
|
}
|
|
138
|
+
|
|
139
|
+
export {
|
|
140
|
+
openViewer,
|
|
141
|
+
openViewerAndSendData,
|
|
142
|
+
openTreemap,
|
|
143
|
+
};
|
|
@@ -183,15 +183,26 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
183
183
|
// Metrics.
|
|
184
184
|
const metricAudits = category.auditRefs.filter(audit => audit.group === 'metrics');
|
|
185
185
|
if (metricAudits.length) {
|
|
186
|
-
const
|
|
186
|
+
const [metricsGroupEl, metricsFooterEl] = this.renderAuditGroup(groups.metrics);
|
|
187
187
|
|
|
188
188
|
// Metric descriptions toggle.
|
|
189
|
-
const
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
189
|
+
const checkboxEl = this.dom.createElement('input', 'lh-metrics-toggle__input');
|
|
190
|
+
const checkboxId = `lh-metrics-toggle${Util.getUniqueSuffix()}`;
|
|
191
|
+
checkboxEl.setAttribute('aria-label', 'Toggle the display of metric descriptions');
|
|
192
|
+
checkboxEl.type = 'checkbox';
|
|
193
|
+
checkboxEl.id = checkboxId;
|
|
194
|
+
metricsGroupEl.prepend(checkboxEl);
|
|
195
|
+
const metricHeaderEl = this.dom.find('.lh-audit-group__header', metricsGroupEl);
|
|
196
|
+
const labelEl = this.dom.createChildOf(metricHeaderEl, 'label', 'lh-metrics-toggle__label');
|
|
197
|
+
labelEl.htmlFor = checkboxId;
|
|
198
|
+
const showEl = this.dom.createChildOf(labelEl, 'span', 'lh-metrics-toggle__labeltext--show');
|
|
199
|
+
const hideEl = this.dom.createChildOf(labelEl, 'span', 'lh-metrics-toggle__labeltext--hide');
|
|
200
|
+
showEl.textContent = Util.i18n.strings.expandView;
|
|
201
|
+
hideEl.textContent = Util.i18n.strings.collapseView;
|
|
202
|
+
|
|
203
|
+
const metricAudits = category.auditRefs.filter(audit => audit.group === 'metrics');
|
|
204
|
+
const metricsBoxesEl = this.dom.createElement('div', 'lh-metrics-container');
|
|
205
|
+
metricsGroupEl.insertBefore(metricsBoxesEl, metricsFooterEl);
|
|
195
206
|
metricAudits.forEach(item => {
|
|
196
207
|
metricsBoxesEl.appendChild(this._renderMetric(item));
|
|
197
208
|
});
|
|
@@ -208,8 +219,8 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
208
219
|
this.dom.safelySetHref(calculatorLink, this._getScoringCalculatorHref(category.auditRefs));
|
|
209
220
|
|
|
210
221
|
|
|
211
|
-
|
|
212
|
-
element.appendChild(
|
|
222
|
+
metricsGroupEl.classList.add('lh-audit-group--metrics');
|
|
223
|
+
element.appendChild(metricsGroupEl);
|
|
213
224
|
}
|
|
214
225
|
|
|
215
226
|
// Filmstrip
|
|
@@ -240,7 +251,7 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
240
251
|
const wastedMsValues = opportunityAudits.map(audit => this._getWastedMs(audit));
|
|
241
252
|
const maxWaste = Math.max(...wastedMsValues);
|
|
242
253
|
const scale = Math.max(Math.ceil(maxWaste / 1000) * 1000, minimumScale);
|
|
243
|
-
const groupEl = this.renderAuditGroup(groups['load-opportunities']);
|
|
254
|
+
const [groupEl, footerEl] = this.renderAuditGroup(groups['load-opportunities']);
|
|
244
255
|
const tmpl = this.dom.createComponent('opportunityHeader');
|
|
245
256
|
|
|
246
257
|
this.dom.find('.lh-load-opportunity__col--one', tmpl).textContent =
|
|
@@ -249,8 +260,9 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
249
260
|
strings.opportunitySavingsColumnLabel;
|
|
250
261
|
|
|
251
262
|
const headerEl = this.dom.find('.lh-load-opportunity__header', tmpl);
|
|
252
|
-
groupEl.
|
|
253
|
-
opportunityAudits.forEach(item =>
|
|
263
|
+
groupEl.insertBefore(headerEl, footerEl);
|
|
264
|
+
opportunityAudits.forEach(item =>
|
|
265
|
+
groupEl.insertBefore(this._renderOpportunity(item, scale), footerEl));
|
|
254
266
|
groupEl.classList.add('lh-audit-group--load-opportunities');
|
|
255
267
|
element.appendChild(groupEl);
|
|
256
268
|
}
|
|
@@ -266,8 +278,8 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
266
278
|
});
|
|
267
279
|
|
|
268
280
|
if (diagnosticAudits.length) {
|
|
269
|
-
const groupEl = this.renderAuditGroup(groups['diagnostics']);
|
|
270
|
-
diagnosticAudits.forEach(item => groupEl.
|
|
281
|
+
const [groupEl, footerEl] = this.renderAuditGroup(groups['diagnostics']);
|
|
282
|
+
diagnosticAudits.forEach(item => groupEl.insertBefore(this.renderAudit(item), footerEl));
|
|
271
283
|
groupEl.classList.add('lh-audit-group--diagnostics');
|
|
272
284
|
element.appendChild(groupEl);
|
|
273
285
|
}
|
|
@@ -294,16 +306,16 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
294
306
|
const table = this.detailsRenderer.render(audit.result.details);
|
|
295
307
|
if (table) {
|
|
296
308
|
table.id = id;
|
|
297
|
-
table.classList.add('lh-audit');
|
|
309
|
+
table.classList.add('lh-details', 'lh-details--budget', 'lh-audit');
|
|
298
310
|
budgetTableEls.push(table);
|
|
299
311
|
}
|
|
300
312
|
}
|
|
301
313
|
});
|
|
302
314
|
if (budgetTableEls.length > 0) {
|
|
303
|
-
const
|
|
304
|
-
budgetTableEls.forEach(table =>
|
|
305
|
-
|
|
306
|
-
element.appendChild(
|
|
315
|
+
const [groupEl, footerEl] = this.renderAuditGroup(groups.budgets);
|
|
316
|
+
budgetTableEls.forEach(table => groupEl.insertBefore(table, footerEl));
|
|
317
|
+
groupEl.classList.add('lh-audit-group--budgets');
|
|
318
|
+
element.appendChild(groupEl);
|
|
307
319
|
}
|
|
308
320
|
|
|
309
321
|
return element;
|
|
@@ -36,22 +36,43 @@ export class ReportRenderer {
|
|
|
36
36
|
constructor(dom) {
|
|
37
37
|
/** @type {DOM} */
|
|
38
38
|
this._dom = dom;
|
|
39
|
+
/** @type {LH.Renderer.Options} */
|
|
40
|
+
this._opts = {};
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
/**
|
|
42
44
|
* @param {LH.Result} lhr
|
|
43
|
-
* @param {
|
|
45
|
+
* @param {HTMLElement?} rootEl Report root element containing the report
|
|
46
|
+
* @param {LH.Renderer.Options=} opts
|
|
44
47
|
* @return {!Element}
|
|
45
48
|
*/
|
|
46
|
-
renderReport(lhr,
|
|
49
|
+
renderReport(lhr, rootEl, opts) {
|
|
50
|
+
// Allow legacy report rendering API
|
|
51
|
+
if (!this._dom.rootEl && rootEl) {
|
|
52
|
+
console.warn('Please adopt the new report API in renderer/api.js.');
|
|
53
|
+
const closestRoot = rootEl.closest('.lh-root');
|
|
54
|
+
if (closestRoot) {
|
|
55
|
+
this._dom.rootEl = /** @type {HTMLElement} */ (closestRoot);
|
|
56
|
+
} else {
|
|
57
|
+
rootEl.classList.add('lh-root', 'lh-vars');
|
|
58
|
+
this._dom.rootEl = rootEl;
|
|
59
|
+
}
|
|
60
|
+
} else if (this._dom.rootEl && rootEl) {
|
|
61
|
+
// Handle legacy flow-report case
|
|
62
|
+
this._dom.rootEl = rootEl;
|
|
63
|
+
}
|
|
64
|
+
if (opts) {
|
|
65
|
+
this._opts = opts;
|
|
66
|
+
}
|
|
67
|
+
|
|
47
68
|
this._dom.setLighthouseChannel(lhr.configSettings.channel || 'unknown');
|
|
48
69
|
|
|
49
70
|
const report = Util.prepareReportResult(lhr);
|
|
50
71
|
|
|
51
|
-
|
|
52
|
-
|
|
72
|
+
this._dom.rootEl.textContent = ''; // Remove previous report.
|
|
73
|
+
this._dom.rootEl.appendChild(this._renderReport(report));
|
|
53
74
|
|
|
54
|
-
return
|
|
75
|
+
return this._dom.rootEl;
|
|
55
76
|
}
|
|
56
77
|
|
|
57
78
|
/**
|
|
@@ -197,7 +218,7 @@ export class ReportRenderer {
|
|
|
197
218
|
gaugeWrapperEl.addEventListener('click', e => {
|
|
198
219
|
if (!gaugeWrapperEl.matches('[href^="#"]')) return;
|
|
199
220
|
const selector = gaugeWrapperEl.getAttribute('href');
|
|
200
|
-
const reportRoot =
|
|
221
|
+
const reportRoot = this._dom.rootEl;
|
|
201
222
|
if (!selector || !reportRoot) return;
|
|
202
223
|
const destEl = this._dom.find(selector, reportRoot);
|
|
203
224
|
e.preventDefault();
|
|
@@ -268,6 +289,7 @@ export class ReportRenderer {
|
|
|
268
289
|
}
|
|
269
290
|
|
|
270
291
|
const scoreScale = this._dom.createElement('div');
|
|
292
|
+
scoreScale.classList.add('lh-scorescale-wrap');
|
|
271
293
|
scoreScale.append(this._dom.createComponent('scorescale'));
|
|
272
294
|
if (scoreHeader) {
|
|
273
295
|
const scoresContainer = this._dom.find('.lh-scores-container', headerContainer);
|
|
@@ -287,7 +309,7 @@ export class ReportRenderer {
|
|
|
287
309
|
for (const category of Object.values(report.categories)) {
|
|
288
310
|
const renderer = specificCategoryRenderers[category.id] || categoryRenderer;
|
|
289
311
|
// .lh-category-wrapper is full-width and provides horizontal rules between categories.
|
|
290
|
-
// .lh-category within has the max-width: var(--report-width);
|
|
312
|
+
// .lh-category within has the max-width: var(--report-content-width);
|
|
291
313
|
const wrapper = renderer.dom.createChildOf(categories, 'div', 'lh-category-wrapper');
|
|
292
314
|
wrapper.appendChild(renderer.render(
|
|
293
315
|
category,
|
|
@@ -300,9 +322,11 @@ export class ReportRenderer {
|
|
|
300
322
|
|
|
301
323
|
const reportFragment = this._dom.createFragment();
|
|
302
324
|
reportFragment.append(this._dom.createComponent('styles'));
|
|
303
|
-
const topbarDocumentFragment = this._renderReportTopbar(report);
|
|
304
325
|
|
|
305
|
-
|
|
326
|
+
if (!this._opts.omitTopbar) {
|
|
327
|
+
reportFragment.appendChild(this._renderReportTopbar(report));
|
|
328
|
+
}
|
|
329
|
+
|
|
306
330
|
reportFragment.appendChild(reportContainer);
|
|
307
331
|
reportContainer.appendChild(headerContainer);
|
|
308
332
|
reportContainer.appendChild(reportSection);
|
|
@@ -310,7 +334,7 @@ export class ReportRenderer {
|
|
|
310
334
|
|
|
311
335
|
if (fullPageScreenshot) {
|
|
312
336
|
ElementScreenshotRenderer.installFullPageScreenshot(
|
|
313
|
-
|
|
337
|
+
this._dom.rootEl, fullPageScreenshot.screenshot);
|
|
314
338
|
}
|
|
315
339
|
|
|
316
340
|
return reportFragment;
|
|
@@ -40,16 +40,17 @@ function getTableRows(tableEl) {
|
|
|
40
40
|
export class ReportUIFeatures {
|
|
41
41
|
/**
|
|
42
42
|
* @param {DOM} dom
|
|
43
|
+
* @param {LH.Renderer.Options} opts
|
|
43
44
|
*/
|
|
44
|
-
constructor(dom) {
|
|
45
|
+
constructor(dom, opts = {}) {
|
|
45
46
|
/** @type {LH.Result} */
|
|
46
47
|
this.json; // eslint-disable-line no-unused-expressions
|
|
47
48
|
/** @type {DOM} */
|
|
48
49
|
this._dom = dom;
|
|
49
|
-
/** @type {Document} */
|
|
50
|
-
this._document = this._dom.document();
|
|
51
|
-
this._topbar = new TopbarFeatures(this, dom);
|
|
52
50
|
|
|
51
|
+
this._opts = opts;
|
|
52
|
+
|
|
53
|
+
this._topbar = opts.omitTopbar ? null : new TopbarFeatures(this, dom);
|
|
53
54
|
this.onMediaQueryChange = this.onMediaQueryChange.bind(this);
|
|
54
55
|
}
|
|
55
56
|
|
|
@@ -61,16 +62,19 @@ export class ReportUIFeatures {
|
|
|
61
62
|
initFeatures(lhr) {
|
|
62
63
|
this.json = lhr;
|
|
63
64
|
|
|
64
|
-
this._topbar
|
|
65
|
-
|
|
65
|
+
if (this._topbar) {
|
|
66
|
+
this._topbar.enable(lhr);
|
|
67
|
+
this._topbar.resetUIState();
|
|
68
|
+
}
|
|
66
69
|
this._setupMediaQueryListeners();
|
|
67
70
|
this._setupThirdPartyFilter();
|
|
68
|
-
this._setupElementScreenshotOverlay(this._dom.
|
|
71
|
+
this._setupElementScreenshotOverlay(this._dom.rootEl);
|
|
69
72
|
|
|
70
73
|
let turnOffTheLights = false;
|
|
71
74
|
// Do not query the system preferences for DevTools - DevTools should only apply dark theme
|
|
72
75
|
// if dark is selected in the settings panel.
|
|
73
|
-
|
|
76
|
+
const disableDarkMode = this._dom.isDevTools() || this._opts.disableAutoDarkModeAndFireworks;
|
|
77
|
+
if (!disableDarkMode && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
74
78
|
turnOffTheLights = true;
|
|
75
79
|
}
|
|
76
80
|
|
|
@@ -94,7 +98,7 @@ export class ReportUIFeatures {
|
|
|
94
98
|
const hasMetricError = lhr.categories.performance && lhr.categories.performance.auditRefs
|
|
95
99
|
.some(audit => Boolean(audit.group === 'metrics' && lhr.audits[audit.id].errorMessage));
|
|
96
100
|
if (hasMetricError) {
|
|
97
|
-
const toggleInputEl = this._dom.find('input.lh-metrics-toggle__input', this.
|
|
101
|
+
const toggleInputEl = this._dom.find('input.lh-metrics-toggle__input', this._dom.rootEl);
|
|
98
102
|
toggleInputEl.checked = true;
|
|
99
103
|
}
|
|
100
104
|
|
|
@@ -109,7 +113,7 @@ export class ReportUIFeatures {
|
|
|
109
113
|
}
|
|
110
114
|
|
|
111
115
|
// Fill in all i18n data.
|
|
112
|
-
for (const node of this._dom.findAll('[data-i18n]', this._dom.
|
|
116
|
+
for (const node of this._dom.findAll('[data-i18n]', this._dom.rootEl)) {
|
|
113
117
|
// These strings are guaranteed to (at least) have a default English string in Util.UIStrings,
|
|
114
118
|
// so this cannot be undefined as long as `report-ui-features.data-i18n` test passes.
|
|
115
119
|
const i18nKey = node.getAttribute('data-i18n');
|
|
@@ -119,18 +123,15 @@ export class ReportUIFeatures {
|
|
|
119
123
|
}
|
|
120
124
|
|
|
121
125
|
/**
|
|
122
|
-
* @param {{
|
|
126
|
+
* @param {{text: string, icon?: string, onClick: () => void}} opts
|
|
123
127
|
*/
|
|
124
128
|
addButton(opts) {
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const metricsEl = this._document.querySelector('.lh-audit-group--metrics');
|
|
129
|
-
const containerEl = opts.container || metricsEl;
|
|
130
|
-
if (!containerEl) return;
|
|
129
|
+
// Use qSA directly to as we don't want to throw (if this element is missing).
|
|
130
|
+
const metricsEl = this._dom.rootEl.querySelector('.lh-audit-group--metrics');
|
|
131
|
+
if (!metricsEl) return;
|
|
131
132
|
|
|
132
|
-
let buttonsEl =
|
|
133
|
-
if (!buttonsEl) buttonsEl = this._dom.createChildOf(
|
|
133
|
+
let buttonsEl = metricsEl.querySelector('.lh-buttons');
|
|
134
|
+
if (!buttonsEl) buttonsEl = this._dom.createChildOf(metricsEl, 'div', 'lh-buttons');
|
|
134
135
|
|
|
135
136
|
const classes = [
|
|
136
137
|
'lh-button',
|
|
@@ -150,8 +151,10 @@ export class ReportUIFeatures {
|
|
|
150
151
|
* @return {string}
|
|
151
152
|
*/
|
|
152
153
|
getReportHtml() {
|
|
153
|
-
this._topbar
|
|
154
|
-
|
|
154
|
+
if (this._topbar) {
|
|
155
|
+
this._topbar.resetUIState();
|
|
156
|
+
}
|
|
157
|
+
return `<!doctype html><body>${this._dom.rootEl.outerHTML}`;
|
|
155
158
|
}
|
|
156
159
|
|
|
157
160
|
/**
|
|
@@ -163,7 +166,7 @@ export class ReportUIFeatures {
|
|
|
163
166
|
}
|
|
164
167
|
|
|
165
168
|
_enableFireworks() {
|
|
166
|
-
const scoresContainer = this._dom.find('.lh-scores-container', this.
|
|
169
|
+
const scoresContainer = this._dom.find('.lh-scores-container', this._dom.rootEl);
|
|
167
170
|
scoresContainer.classList.add('lh-score100');
|
|
168
171
|
scoresContainer.addEventListener('click', _ => {
|
|
169
172
|
scoresContainer.classList.toggle('lh-fireworks-paused');
|
|
@@ -183,7 +186,9 @@ export class ReportUIFeatures {
|
|
|
183
186
|
* be in their closed state (not opened) and the templates should be unstamped.
|
|
184
187
|
*/
|
|
185
188
|
_resetUIState() {
|
|
186
|
-
this._topbar
|
|
189
|
+
if (this._topbar) {
|
|
190
|
+
this._topbar.resetUIState();
|
|
191
|
+
}
|
|
187
192
|
}
|
|
188
193
|
|
|
189
194
|
/**
|
|
@@ -191,8 +196,7 @@ export class ReportUIFeatures {
|
|
|
191
196
|
* @param {MediaQueryList|MediaQueryListEvent} mql
|
|
192
197
|
*/
|
|
193
198
|
onMediaQueryChange(mql) {
|
|
194
|
-
|
|
195
|
-
root.classList.toggle('lh-narrow', mql.matches);
|
|
199
|
+
this._dom.rootEl.classList.toggle('lh-narrow', mql.matches);
|
|
196
200
|
}
|
|
197
201
|
|
|
198
202
|
_setupThirdPartyFilter() {
|
|
@@ -209,7 +213,7 @@ export class ReportUIFeatures {
|
|
|
209
213
|
];
|
|
210
214
|
|
|
211
215
|
// Get all tables with a text url column.
|
|
212
|
-
const tables = Array.from(this.
|
|
216
|
+
const tables = Array.from(this._dom.rootEl.querySelectorAll('table.lh-table'));
|
|
213
217
|
const tablesWithUrls = tables
|
|
214
218
|
.filter(el =>
|
|
215
219
|
el.querySelector('td.lh-table-column--url, td.lh-table-column--source-location'))
|
|
@@ -256,10 +260,9 @@ export class ReportUIFeatures {
|
|
|
256
260
|
const allThirdParty = thirdPartyRows.length === rowEls.length;
|
|
257
261
|
const allFirstParty = !thirdPartyRows.length;
|
|
258
262
|
|
|
259
|
-
// If all or none of the rows are 3rd party,
|
|
263
|
+
// If all or none of the rows are 3rd party, hide the control.
|
|
260
264
|
if (allThirdParty || allFirstParty) {
|
|
261
|
-
|
|
262
|
-
filterInput.checked = allThirdParty;
|
|
265
|
+
this._dom.find('div.lh-3p-filter', filterTemplate).hidden = true;
|
|
263
266
|
}
|
|
264
267
|
|
|
265
268
|
// Add checkbox to the DOM.
|
|
@@ -276,9 +279,9 @@ export class ReportUIFeatures {
|
|
|
276
279
|
}
|
|
277
280
|
|
|
278
281
|
/**
|
|
279
|
-
* @param {Element}
|
|
282
|
+
* @param {Element} rootEl
|
|
280
283
|
*/
|
|
281
|
-
_setupElementScreenshotOverlay(
|
|
284
|
+
_setupElementScreenshotOverlay(rootEl) {
|
|
282
285
|
const fullPageScreenshot =
|
|
283
286
|
this.json.audits['full-page-screenshot'] &&
|
|
284
287
|
this.json.audits['full-page-screenshot'].details &&
|
|
@@ -288,8 +291,8 @@ export class ReportUIFeatures {
|
|
|
288
291
|
|
|
289
292
|
ElementScreenshotRenderer.installOverlayFeature({
|
|
290
293
|
dom: this._dom,
|
|
291
|
-
|
|
292
|
-
overlayContainerEl:
|
|
294
|
+
rootEl: rootEl,
|
|
295
|
+
overlayContainerEl: rootEl,
|
|
293
296
|
fullPageScreenshot,
|
|
294
297
|
});
|
|
295
298
|
}
|
|
@@ -33,17 +33,17 @@ export class SwapLocaleFeature {
|
|
|
33
33
|
throw new Error('missing icuMessagePaths');
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
this._dom.find('.lh-tools-locale', this._dom.
|
|
36
|
+
this._dom.find('.lh-tools-locale', this._dom.rootEl).classList.remove('lh-hidden');
|
|
37
37
|
|
|
38
38
|
const currentLocale = this._reportUIFeatures.json.configSettings.locale;
|
|
39
39
|
|
|
40
|
-
const containerEl = this._dom.find('.lh-tools-locale__selector-wrapper', this._dom.
|
|
40
|
+
const containerEl = this._dom.find('.lh-tools-locale__selector-wrapper', this._dom.rootEl);
|
|
41
41
|
containerEl.removeAttribute('aria-hidden');
|
|
42
42
|
const selectEl = this._dom.createChildOf(containerEl, 'select', 'lh-locale-selector');
|
|
43
43
|
selectEl.name = 'lh-locale-list';
|
|
44
44
|
selectEl.setAttribute('role', 'menuitem');
|
|
45
45
|
|
|
46
|
-
const toggleEl = this._dom.find('.lh-tool-locale__button', this._dom.
|
|
46
|
+
const toggleEl = this._dom.find('.lh-tool-locale__button', this._dom.rootEl);
|
|
47
47
|
toggleEl.addEventListener('click', () => {
|
|
48
48
|
toggleEl.classList.toggle('lh-active');
|
|
49
49
|
});
|