lighthouse 10.0.2-dev.20230305 → 10.0.2-dev.20230307
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/cli/test/smokehouse/config/exclusions.js +0 -2
- package/core/audits/audit.d.ts +6 -0
- package/core/audits/audit.js +14 -2
- package/core/audits/bootup-time.js +2 -3
- package/core/audits/byte-efficiency/byte-efficiency-audit.d.ts +2 -0
- package/core/audits/byte-efficiency/byte-efficiency-audit.js +3 -1
- package/core/audits/byte-efficiency/total-byte-weight.js +1 -1
- package/core/audits/byte-efficiency/uses-long-cache-ttl.js +2 -2
- package/core/audits/long-tasks.js +2 -1
- package/core/audits/mainthread-work-breakdown.js +2 -1
- package/core/audits/network-rtt.js +1 -1
- package/core/audits/network-server-latency.js +2 -1
- package/core/audits/performance-budget.js +1 -1
- package/core/audits/prioritize-lcp-image.js +1 -1
- package/core/audits/third-party-summary.js +4 -1
- package/core/audits/timing-budget.js +2 -1
- package/core/audits/uses-rel-preconnect.js +1 -1
- package/core/audits/uses-rel-preload.js +1 -1
- package/core/audits/work-during-interaction.js +1 -1
- package/core/lib/lighthouse-compatibility.js +17 -0
- package/core/runner.js +1 -0
- package/dist/report/bundle.esm.js +222 -42
- package/dist/report/flow.js +17 -17
- package/dist/report/standalone.js +16 -16
- package/flow-report/src/i18n/i18n.d.ts +6 -0
- package/package.json +3 -5
- package/report/assets/styles.css +86 -4
- package/report/renderer/components.js +1 -1
- package/report/renderer/details-renderer.d.ts +26 -6
- package/report/renderer/details-renderer.js +144 -21
- package/report/renderer/report-renderer.js +1 -0
- package/report/renderer/report-ui-features.js +18 -9
- package/report/renderer/report-utils.d.ts +13 -4
- package/report/renderer/report-utils.js +43 -11
- package/shared/localization/locales/en-US.json +9 -0
- package/shared/localization/locales/en-XL.json +9 -0
- package/types/lhr/audit-details.d.ts +18 -0
|
@@ -24,22 +24,26 @@
|
|
|
24
24
|
/** @typedef {LH.FormattedIcu<LH.Audit.Details.TableItem>} TableItem */
|
|
25
25
|
/** @typedef {LH.FormattedIcu<LH.Audit.Details.ItemValue>} TableItemValue */
|
|
26
26
|
/** @typedef {LH.FormattedIcu<LH.Audit.Details.TableColumnHeading>} TableColumnHeading */
|
|
27
|
+
/** @typedef {LH.FormattedIcu<LH.Audit.Details.Table | LH.Audit.Details.Opportunity>} TableLike */
|
|
27
28
|
|
|
28
29
|
import {Util} from '../../shared/util.js';
|
|
29
30
|
import {CriticalRequestChainRenderer} from './crc-details-renderer.js';
|
|
30
31
|
import {ElementScreenshotRenderer} from './element-screenshot-renderer.js';
|
|
31
32
|
import {Globals} from './report-globals.js';
|
|
33
|
+
import {ReportUtils} from './report-utils.js';
|
|
32
34
|
|
|
33
35
|
const URL_PREFIXES = ['http://', 'https://', 'data:'];
|
|
36
|
+
const SUMMABLE_VALUETYPES = ['bytes', 'numeric', 'ms', 'timespanMs'];
|
|
34
37
|
|
|
35
38
|
export class DetailsRenderer {
|
|
36
39
|
/**
|
|
37
40
|
* @param {DOM} dom
|
|
38
|
-
* @param {{fullPageScreenshot?: LH.Result.FullPageScreenshot}} [options]
|
|
41
|
+
* @param {{fullPageScreenshot?: LH.Result.FullPageScreenshot, entities?: LH.Result.Entities}} [options]
|
|
39
42
|
*/
|
|
40
43
|
constructor(dom, options = {}) {
|
|
41
44
|
this._dom = dom;
|
|
42
45
|
this._fullPageScreenshot = options.fullPageScreenshot;
|
|
46
|
+
this._entities = options.entities;
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
/**
|
|
@@ -376,7 +380,110 @@ export class DetailsRenderer {
|
|
|
376
380
|
}
|
|
377
381
|
|
|
378
382
|
/**
|
|
379
|
-
*
|
|
383
|
+
* Adorn a table row element with entity chips based on [data-entity] attribute.
|
|
384
|
+
* @param {HTMLTableRowElement} rowEl
|
|
385
|
+
*/
|
|
386
|
+
_adornEntityGroupRow(rowEl) {
|
|
387
|
+
const entityName = rowEl.dataset.entity;
|
|
388
|
+
if (!entityName) return;
|
|
389
|
+
const matchedEntity = this._entities?.find(e => e.name === entityName);
|
|
390
|
+
if (!matchedEntity) return;
|
|
391
|
+
|
|
392
|
+
const firstTdEl = this._dom.find('td', rowEl);
|
|
393
|
+
|
|
394
|
+
if (matchedEntity.category) {
|
|
395
|
+
const categoryChipEl = this._dom.createElement('span');
|
|
396
|
+
categoryChipEl.classList.add('lh-audit__adorn');
|
|
397
|
+
categoryChipEl.textContent = matchedEntity.category;
|
|
398
|
+
firstTdEl.append(' ', categoryChipEl);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (matchedEntity.isFirstParty) {
|
|
402
|
+
const firstPartyChipEl = this._dom.createElement('span');
|
|
403
|
+
firstPartyChipEl.classList.add('lh-audit__adorn', 'lh-audit__adorn1p');
|
|
404
|
+
firstPartyChipEl.textContent = Globals.strings.firstPartyChipLabel;
|
|
405
|
+
firstTdEl.append(' ', firstPartyChipEl);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (matchedEntity.homepage) {
|
|
409
|
+
const entityLinkEl = this._dom.createElement('a');
|
|
410
|
+
entityLinkEl.href = matchedEntity.homepage;
|
|
411
|
+
entityLinkEl.target = '_blank';
|
|
412
|
+
entityLinkEl.title = Globals.strings.openInANewTabTooltip;
|
|
413
|
+
entityLinkEl.classList.add('lh-report-icon--external');
|
|
414
|
+
firstTdEl.append(' ', entityLinkEl);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Renders an entity-grouped row.
|
|
420
|
+
* @param {TableItem} item
|
|
421
|
+
* @param {LH.Audit.Details.TableColumnHeading[]} headings
|
|
422
|
+
*/
|
|
423
|
+
_renderEntityGroupRow(item, headings) {
|
|
424
|
+
const entityColumnHeading = {...headings[0]};
|
|
425
|
+
// In subitem-situations (unused-javascript), ensure Entity name is not rendered as code, etc.
|
|
426
|
+
entityColumnHeading.valueType = 'text';
|
|
427
|
+
const groupedRowHeadings = [entityColumnHeading, ...headings.slice(1)];
|
|
428
|
+
const fragment = this._dom.createFragment();
|
|
429
|
+
fragment.append(this._renderTableRow(item, groupedRowHeadings));
|
|
430
|
+
this._dom.find('tr', fragment).classList.add('lh-row--group');
|
|
431
|
+
return fragment;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Returns an array of entity-grouped TableItems to use as the top-level rows in
|
|
436
|
+
* an grouped table. Each table item returned represents a unique entity, with every
|
|
437
|
+
* applicable key that can be grouped as a property. Optionally, supported columns are
|
|
438
|
+
* summed by entity, and sorted by specified keys.
|
|
439
|
+
* @param {TableLike} details
|
|
440
|
+
* @return {TableItem[]}
|
|
441
|
+
*/
|
|
442
|
+
_getEntityGroupItems(details) {
|
|
443
|
+
const {items, headings, sortedBy} = details;
|
|
444
|
+
// Exclude entity-grouped audits and results without entity classification.
|
|
445
|
+
// Eg. Third-party Summary comes entity-grouped.
|
|
446
|
+
if (!items.length || details.isEntityGrouped || !items.some(item => item.entity)) {
|
|
447
|
+
return [];
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const skippedColumns = new Set(details.skipSumming || []);
|
|
451
|
+
/** @type {string[]} */
|
|
452
|
+
const summableColumns = [];
|
|
453
|
+
for (const heading of headings) {
|
|
454
|
+
if (!heading.key || skippedColumns.has(heading.key)) continue;
|
|
455
|
+
if (SUMMABLE_VALUETYPES.includes(heading.valueType)) {
|
|
456
|
+
summableColumns.push(heading.key);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Grab the first column's key to group by entity
|
|
461
|
+
const firstColumnKey = headings[0].key;
|
|
462
|
+
if (!firstColumnKey) return [];
|
|
463
|
+
|
|
464
|
+
/** @type {Map<string | undefined, TableItem>} */
|
|
465
|
+
const byEntity = new Map();
|
|
466
|
+
for (const item of items) {
|
|
467
|
+
const entityName = typeof item.entity === 'string' ? item.entity : undefined;
|
|
468
|
+
const groupedItem = byEntity.get(entityName) || {
|
|
469
|
+
[firstColumnKey]: entityName || Globals.strings.unattributable,
|
|
470
|
+
entity: entityName,
|
|
471
|
+
};
|
|
472
|
+
for (const key of summableColumns) {
|
|
473
|
+
groupedItem[key] = Number(groupedItem[key] || 0) + Number(item[key] || 0);
|
|
474
|
+
}
|
|
475
|
+
byEntity.set(entityName, groupedItem);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const result = [...byEntity.values()];
|
|
479
|
+
if (sortedBy) {
|
|
480
|
+
result.sort(ReportUtils.getTableItemSortComparator(sortedBy));
|
|
481
|
+
}
|
|
482
|
+
return result;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* @param {TableLike} details
|
|
380
487
|
* @return {Element}
|
|
381
488
|
*/
|
|
382
489
|
_renderTable(details) {
|
|
@@ -394,29 +501,45 @@ export class DetailsRenderer {
|
|
|
394
501
|
this._dom.createChildOf(theadTrElem, 'th', classes).append(labelEl);
|
|
395
502
|
}
|
|
396
503
|
|
|
504
|
+
const entityItems = this._getEntityGroupItems(details);
|
|
397
505
|
const tbodyElem = this._dom.createChildOf(tableElem, 'tbody');
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
506
|
+
if (entityItems.length) {
|
|
507
|
+
for (const entityItem of entityItems) {
|
|
508
|
+
const entityName = typeof entityItem.entity === 'string' ? entityItem.entity : undefined;
|
|
509
|
+
const entityGroupFragment = this._renderEntityGroupRow(entityItem, details.headings);
|
|
510
|
+
// Render all the items that match the heading row
|
|
511
|
+
for (const item of details.items.filter((item) => item.entity === entityName)) {
|
|
512
|
+
entityGroupFragment.append(this._renderTableRowsFromItem(item, details.headings));
|
|
513
|
+
}
|
|
514
|
+
const rowEls = this._dom.findAll('tr', entityGroupFragment);
|
|
515
|
+
if (entityName && rowEls.length) {
|
|
516
|
+
rowEls.forEach(row => row.dataset.entity = entityName);
|
|
517
|
+
this._adornEntityGroupRow(rowEls[0]);
|
|
518
|
+
}
|
|
519
|
+
tbodyElem.append(entityGroupFragment);
|
|
409
520
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
521
|
+
} else {
|
|
522
|
+
let even = true;
|
|
523
|
+
for (const item of details.items) {
|
|
524
|
+
const rowsFragment = this._renderTableRowsFromItem(item, details.headings);
|
|
525
|
+
const rowEls = this._dom.findAll('tr', rowsFragment);
|
|
526
|
+
const firstRowEl = rowEls[0];
|
|
527
|
+
if (typeof item.entity === 'string') {
|
|
528
|
+
firstRowEl.dataset.entity = item.entity;
|
|
529
|
+
}
|
|
530
|
+
if (details.isEntityGrouped && item.entity) {
|
|
531
|
+
// If the audit is already grouped, consider first row as a heading row.
|
|
532
|
+
firstRowEl.classList.add('lh-row--group');
|
|
533
|
+
this._adornEntityGroupRow(firstRowEl);
|
|
534
|
+
} else {
|
|
535
|
+
for (const rowEl of rowEls) {
|
|
536
|
+
// For zebra styling (same shade for a row and its sub-rows).
|
|
537
|
+
rowEl.classList.add(even ? 'lh-row--even' : 'lh-row--odd');
|
|
538
|
+
}
|
|
416
539
|
}
|
|
540
|
+
even = !even;
|
|
541
|
+
tbodyElem.append(rowsFragment);
|
|
417
542
|
}
|
|
418
|
-
even = !even;
|
|
419
|
-
tbodyElem.append(rowsFragment);
|
|
420
543
|
}
|
|
421
544
|
|
|
422
545
|
return tableElem;
|
|
@@ -268,6 +268,7 @@ export class ReportRenderer {
|
|
|
268
268
|
|
|
269
269
|
const detailsRenderer = new DetailsRenderer(this._dom, {
|
|
270
270
|
fullPageScreenshot: report.fullPageScreenshot ?? undefined,
|
|
271
|
+
entities: report.entities,
|
|
271
272
|
});
|
|
272
273
|
|
|
273
274
|
const categoryRenderer = new CategoryRenderer(this._dom, detailsRenderer);
|
|
@@ -244,9 +244,11 @@ export class ReportUIFeatures {
|
|
|
244
244
|
|
|
245
245
|
tablesWithUrls.forEach((tableEl) => {
|
|
246
246
|
const rowEls = getTableRows(tableEl);
|
|
247
|
-
const
|
|
248
|
-
const thirdPartyRowEls = this._getThirdPartyRows(
|
|
249
|
-
|
|
247
|
+
const nonSubItemRows = rowEls.filter(rowEl => !rowEl.classList.contains('lh-sub-item-row'));
|
|
248
|
+
const thirdPartyRowEls = this._getThirdPartyRows(nonSubItemRows,
|
|
249
|
+
Util.getFinalDisplayedUrl(this.json));
|
|
250
|
+
// Entity-grouped tables don't have zebra lines.
|
|
251
|
+
const hasZebraStyle = rowEls.some(rowEl => rowEl.classList.contains('lh-row--even'));
|
|
250
252
|
|
|
251
253
|
// create input box
|
|
252
254
|
const filterTemplate = this._dom.createComponent('3pFilter');
|
|
@@ -255,16 +257,19 @@ export class ReportUIFeatures {
|
|
|
255
257
|
filterInput.addEventListener('change', e => {
|
|
256
258
|
const shouldHideThirdParty = e.target instanceof HTMLInputElement && !e.target.checked;
|
|
257
259
|
let even = true;
|
|
258
|
-
let rowEl =
|
|
260
|
+
let rowEl = nonSubItemRows[0];
|
|
259
261
|
while (rowEl) {
|
|
260
262
|
const shouldHide = shouldHideThirdParty && thirdPartyRowEls.includes(rowEl);
|
|
261
263
|
|
|
262
264
|
// Iterate subsequent associated sub item rows.
|
|
263
265
|
do {
|
|
264
266
|
rowEl.classList.toggle('lh-row--hidden', shouldHide);
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
267
|
+
|
|
268
|
+
if (hasZebraStyle) {
|
|
269
|
+
// Adjust for zebra styling.
|
|
270
|
+
rowEl.classList.toggle('lh-row--even', !shouldHide && even);
|
|
271
|
+
rowEl.classList.toggle('lh-row--odd', !shouldHide && !even);
|
|
272
|
+
}
|
|
268
273
|
|
|
269
274
|
rowEl = /** @type {HTMLElement} */ (rowEl.nextElementSibling);
|
|
270
275
|
} while (rowEl && rowEl.classList.contains('lh-sub-item-row'));
|
|
@@ -273,12 +278,16 @@ export class ReportUIFeatures {
|
|
|
273
278
|
}
|
|
274
279
|
});
|
|
275
280
|
|
|
281
|
+
// thirdPartyRowEls contains both heading and item rows.
|
|
282
|
+
// Filter out heading rows to get third party resource count.
|
|
283
|
+
const thirdPartyResourceCount = thirdPartyRowEls.filter(
|
|
284
|
+
rowEl => !rowEl.classList.contains('lh-row--group')).length;
|
|
276
285
|
this._dom.find('.lh-3p-filter-count', filterTemplate).textContent =
|
|
277
|
-
|
|
286
|
+
`${thirdPartyResourceCount}`;
|
|
278
287
|
this._dom.find('.lh-3p-ui-string', filterTemplate).textContent =
|
|
279
288
|
Globals.strings.thirdPartyResourcesLabel;
|
|
280
289
|
|
|
281
|
-
const allThirdParty = thirdPartyRowEls.length ===
|
|
290
|
+
const allThirdParty = thirdPartyRowEls.length === nonSubItemRows.length;
|
|
282
291
|
const allFirstParty = !thirdPartyRowEls.length;
|
|
283
292
|
|
|
284
293
|
// If all or none of the rows are 3rd party, hide the control.
|
|
@@ -13,15 +13,21 @@ export class ReportUtils {
|
|
|
13
13
|
* Given an audit's details, identify and return a URL locator function that
|
|
14
14
|
* can be called later with an `item` to extract the URL of it.
|
|
15
15
|
* @param {LH.FormattedIcu<LH.Audit.Details.TableColumnHeading[]>} headings
|
|
16
|
-
* @return {
|
|
16
|
+
* @return {((item: LH.FormattedIcu<LH.Audit.Details.TableItem>) => string|undefined)=}
|
|
17
17
|
*/
|
|
18
18
|
static getUrlLocatorFn(headings: LH.FormattedIcu<LH.Audit.Details.TableColumnHeading[]>): ((item: LH.FormattedIcu<LH.Audit.Details.TableItem>) => string | undefined) | undefined;
|
|
19
19
|
/**
|
|
20
20
|
* Mark TableItems/OpportunityItems with entity names.
|
|
21
|
-
* @param {LH.Result.Entities
|
|
22
|
-
* @param {
|
|
21
|
+
* @param {LH.Result.Entities} entities
|
|
22
|
+
* @param {LH.FormattedIcu<LH.Audit.Details.Opportunity|LH.Audit.Details.Table>} details
|
|
23
23
|
*/
|
|
24
|
-
static classifyEntities(entities: LH.Result.Entities
|
|
24
|
+
static classifyEntities(entities: LH.Result.Entities, details: LH.FormattedIcu<LH.Audit.Details.Opportunity | LH.Audit.Details.Table>): void;
|
|
25
|
+
/**
|
|
26
|
+
* Returns a comparator created from the supplied list of keys
|
|
27
|
+
* @param {Array<string>} sortedBy
|
|
28
|
+
* @return {((a: LH.Audit.Details.TableItem, b: LH.Audit.Details.TableItem) => number)}
|
|
29
|
+
*/
|
|
30
|
+
static getTableItemSortComparator(sortedBy: Array<string>): (a: LH.Audit.Details.TableItem, b: LH.Audit.Details.TableItem) => number;
|
|
25
31
|
/**
|
|
26
32
|
* @param {LH.Result['configSettings']} settings
|
|
27
33
|
* @return {!{deviceEmulation: string, screenEmulation?: string, networkThrottling: string, cpuThrottling: string, summary: string}}
|
|
@@ -125,5 +131,8 @@ export namespace UIStrings {
|
|
|
125
131
|
const collapseView: string;
|
|
126
132
|
const runtimeSlow4g: string;
|
|
127
133
|
const runtimeCustom: string;
|
|
134
|
+
const firstPartyChipLabel: string;
|
|
135
|
+
const openInANewTabTooltip: string;
|
|
136
|
+
const unattributable: string;
|
|
128
137
|
}
|
|
129
138
|
//# sourceMappingURL=report-utils.d.ts.map
|
|
@@ -27,7 +27,13 @@ class ReportUtils {
|
|
|
27
27
|
|
|
28
28
|
for (const audit of Object.values(clone.audits)) {
|
|
29
29
|
// Attach table/opportunity items with entity information.
|
|
30
|
-
|
|
30
|
+
if (audit.details) {
|
|
31
|
+
if (audit.details.type === 'opportunity' || audit.details.type === 'table') {
|
|
32
|
+
if (!audit.details.isEntityGrouped && clone.entities) {
|
|
33
|
+
ReportUtils.classifyEntities(clone.entities, audit.details);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
// For convenience, smoosh all AuditResults into their auditRef (which has just weight & group)
|
|
@@ -79,7 +85,7 @@ class ReportUtils {
|
|
|
79
85
|
* Given an audit's details, identify and return a URL locator function that
|
|
80
86
|
* can be called later with an `item` to extract the URL of it.
|
|
81
87
|
* @param {LH.FormattedIcu<LH.Audit.Details.TableColumnHeading[]>} headings
|
|
82
|
-
* @return {
|
|
88
|
+
* @return {((item: LH.FormattedIcu<LH.Audit.Details.TableItem>) => string|undefined)=}
|
|
83
89
|
*/
|
|
84
90
|
static getUrlLocatorFn(headings) {
|
|
85
91
|
// The most common type, valueType=url.
|
|
@@ -109,17 +115,12 @@ class ReportUtils {
|
|
|
109
115
|
|
|
110
116
|
/**
|
|
111
117
|
* Mark TableItems/OpportunityItems with entity names.
|
|
112
|
-
* @param {LH.Result.Entities
|
|
113
|
-
* @param {
|
|
118
|
+
* @param {LH.Result.Entities} entities
|
|
119
|
+
* @param {LH.FormattedIcu<LH.Audit.Details.Opportunity|LH.Audit.Details.Table>} details
|
|
114
120
|
*/
|
|
115
|
-
static classifyEntities(entities,
|
|
116
|
-
if (!entities) return;
|
|
117
|
-
if (audit.details?.type !== 'opportunity' && audit.details?.type !== 'table') {
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
+
static classifyEntities(entities, details) {
|
|
121
122
|
// If details.items are already marked with entity attribute during an audit, nothing to do here.
|
|
122
|
-
const {items, headings} =
|
|
123
|
+
const {items, headings} = details;
|
|
123
124
|
if (!items.length || items.some(item => item.entity)) return;
|
|
124
125
|
|
|
125
126
|
// Identify a URL-locator function that we could call against each item to get its URL.
|
|
@@ -142,6 +143,30 @@ class ReportUtils {
|
|
|
142
143
|
}
|
|
143
144
|
}
|
|
144
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Returns a comparator created from the supplied list of keys
|
|
148
|
+
* @param {Array<string>} sortedBy
|
|
149
|
+
* @return {((a: LH.Audit.Details.TableItem, b: LH.Audit.Details.TableItem) => number)}
|
|
150
|
+
*/
|
|
151
|
+
static getTableItemSortComparator(sortedBy) {
|
|
152
|
+
return (a, b) => {
|
|
153
|
+
for (const key of sortedBy) {
|
|
154
|
+
const aVal = a[key];
|
|
155
|
+
const bVal = b[key];
|
|
156
|
+
if (typeof aVal !== typeof bVal || !['number', 'string'].includes(typeof aVal)) {
|
|
157
|
+
console.warn(`Warning: Attempting to sort unsupported value type: ${key}.`);
|
|
158
|
+
}
|
|
159
|
+
if (typeof aVal === 'number' && typeof bVal === 'number' && aVal !== bVal) {
|
|
160
|
+
return bVal - aVal;
|
|
161
|
+
}
|
|
162
|
+
if (typeof aVal === 'string' && typeof bVal === 'string' && aVal !== bVal) {
|
|
163
|
+
return aVal.localeCompare(bVal);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return 0;
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
145
170
|
/**
|
|
146
171
|
* @param {LH.Result['configSettings']} settings
|
|
147
172
|
* @return {!{deviceEmulation: string, screenEmulation?: string, networkThrottling: string, cpuThrottling: string, summary: string}}
|
|
@@ -440,6 +465,13 @@ const UIStrings = {
|
|
|
440
465
|
runtimeSlow4g: 'Slow 4G throttling',
|
|
441
466
|
/** Label indicating that Lighthouse throttled the page using custom throttling settings. */
|
|
442
467
|
runtimeCustom: 'Custom throttling',
|
|
468
|
+
|
|
469
|
+
/** This label is for a decorative chip that is included in a table row. The label indicates that the entity/company name in the row belongs to the first-party (or "1st-party"). First-party label is used to identify resources that are directly controlled by the owner of the web page. */
|
|
470
|
+
firstPartyChipLabel: '1st party',
|
|
471
|
+
/** Descriptive explanation in a tooltip form for a link to be opened in a new tab of the browser. */
|
|
472
|
+
openInANewTabTooltip: 'Open in a new tab',
|
|
473
|
+
/** Generic category name for all resources that could not be attributed to a 1st or 3rd party entity. */
|
|
474
|
+
unattributable: 'Unattributable',
|
|
443
475
|
};
|
|
444
476
|
|
|
445
477
|
export {
|
|
@@ -3047,6 +3047,9 @@
|
|
|
3047
3047
|
"report/renderer/report-utils.js | expandView": {
|
|
3048
3048
|
"message": "Expand view"
|
|
3049
3049
|
},
|
|
3050
|
+
"report/renderer/report-utils.js | firstPartyChipLabel": {
|
|
3051
|
+
"message": "1st party"
|
|
3052
|
+
},
|
|
3050
3053
|
"report/renderer/report-utils.js | footerIssue": {
|
|
3051
3054
|
"message": "File an issue"
|
|
3052
3055
|
},
|
|
@@ -3065,6 +3068,9 @@
|
|
|
3065
3068
|
"report/renderer/report-utils.js | notApplicableAuditsGroupTitle": {
|
|
3066
3069
|
"message": "Not applicable"
|
|
3067
3070
|
},
|
|
3071
|
+
"report/renderer/report-utils.js | openInANewTabTooltip": {
|
|
3072
|
+
"message": "Open in a new tab"
|
|
3073
|
+
},
|
|
3068
3074
|
"report/renderer/report-utils.js | opportunityResourceColumnLabel": {
|
|
3069
3075
|
"message": "Opportunity"
|
|
3070
3076
|
},
|
|
@@ -3143,6 +3149,9 @@
|
|
|
3143
3149
|
"report/renderer/report-utils.js | toplevelWarningsMessage": {
|
|
3144
3150
|
"message": "There were issues affecting this run of Lighthouse:"
|
|
3145
3151
|
},
|
|
3152
|
+
"report/renderer/report-utils.js | unattributable": {
|
|
3153
|
+
"message": "Unattributable"
|
|
3154
|
+
},
|
|
3146
3155
|
"report/renderer/report-utils.js | varianceDisclaimer": {
|
|
3147
3156
|
"message": "Values are estimated and may vary. The [performance score is calculated](https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/) directly from these metrics."
|
|
3148
3157
|
},
|
|
@@ -3047,6 +3047,9 @@
|
|
|
3047
3047
|
"report/renderer/report-utils.js | expandView": {
|
|
3048
3048
|
"message": "Êx́p̂án̂d́ v̂íêẃ"
|
|
3049
3049
|
},
|
|
3050
|
+
"report/renderer/report-utils.js | firstPartyChipLabel": {
|
|
3051
|
+
"message": "1ŝt́ p̂ár̂t́ŷ"
|
|
3052
|
+
},
|
|
3050
3053
|
"report/renderer/report-utils.js | footerIssue": {
|
|
3051
3054
|
"message": "F̂íl̂é âń îśŝúê"
|
|
3052
3055
|
},
|
|
@@ -3065,6 +3068,9 @@
|
|
|
3065
3068
|
"report/renderer/report-utils.js | notApplicableAuditsGroupTitle": {
|
|
3066
3069
|
"message": "N̂ót̂ áp̂ṕl̂íĉáb̂ĺê"
|
|
3067
3070
|
},
|
|
3071
|
+
"report/renderer/report-utils.js | openInANewTabTooltip": {
|
|
3072
|
+
"message": "Ôṕêń îń â ńêẃ t̂áb̂"
|
|
3073
|
+
},
|
|
3068
3074
|
"report/renderer/report-utils.js | opportunityResourceColumnLabel": {
|
|
3069
3075
|
"message": "Ôṕp̂ór̂t́ûńît́ŷ"
|
|
3070
3076
|
},
|
|
@@ -3143,6 +3149,9 @@
|
|
|
3143
3149
|
"report/renderer/report-utils.js | toplevelWarningsMessage": {
|
|
3144
3150
|
"message": "T̂h́êŕê ẃêŕê íŝśûéŝ áf̂f́êćt̂ín̂ǵ t̂h́îś r̂ún̂ óf̂ Ĺîǵĥt́ĥóûśê:"
|
|
3145
3151
|
},
|
|
3152
|
+
"report/renderer/report-utils.js | unattributable": {
|
|
3153
|
+
"message": "Ûńât́t̂ŕîb́ût́âb́l̂é"
|
|
3154
|
+
},
|
|
3146
3155
|
"report/renderer/report-utils.js | varianceDisclaimer": {
|
|
3147
3156
|
"message": "V̂ál̂úêś âŕê éŝt́îḿât́êd́ âńd̂ ḿâý v̂ár̂ý. T̂h́ê [ṕêŕf̂ór̂ḿâńĉé ŝćôŕê íŝ ćâĺĉúl̂át̂éd̂](https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/) d́îŕêćt̂ĺŷ f́r̂óm̂ t́ĥéŝé m̂ét̂ŕîćŝ."
|
|
3148
3157
|
},
|
|
@@ -69,6 +69,15 @@ declare module Details {
|
|
|
69
69
|
headings: TableColumnHeading[];
|
|
70
70
|
items: OpportunityItem[];
|
|
71
71
|
debugData?: DebugData;
|
|
72
|
+
/**
|
|
73
|
+
* Columns to sort the items by, during grouping.
|
|
74
|
+
* If omitted, entity groups will be sorted by the audit ordering vs. the new totals.
|
|
75
|
+
*/
|
|
76
|
+
sortedBy?: Array<string>;
|
|
77
|
+
/** Will be true if the table is already grouped by entities. */
|
|
78
|
+
isEntityGrouped?: boolean;
|
|
79
|
+
/** Column keys to skip summing. If omitted, all column types supported are summed. */
|
|
80
|
+
skipSumming?: Array<string>;
|
|
72
81
|
}
|
|
73
82
|
|
|
74
83
|
interface Screenshot {
|
|
@@ -96,6 +105,15 @@ declare module Details {
|
|
|
96
105
|
wastedBytes?: number;
|
|
97
106
|
};
|
|
98
107
|
debugData?: DebugData;
|
|
108
|
+
/**
|
|
109
|
+
* Columns to sort the items by, during grouping.
|
|
110
|
+
* If omitted, entity groups will be sorted by the audit ordering vs. the new totals.
|
|
111
|
+
*/
|
|
112
|
+
sortedBy?: Array<string>;
|
|
113
|
+
/** Will be true if the table is already grouped by entities. */
|
|
114
|
+
isEntityGrouped?: boolean;
|
|
115
|
+
/** Column keys to skip summing. If omitted, all column types supported are summed. */
|
|
116
|
+
skipSumming?: Array<string>;
|
|
99
117
|
}
|
|
100
118
|
|
|
101
119
|
/** A table item for rows that are nested within a top-level TableItem (row). */
|