lighthouse
Version:
Automated auditing, performance metrics, and best practices for the web.
212 lines (187 loc) • 7.44 kB
JavaScript
/**
* @license
* Copyright 2023 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {Util} from '../../shared/util.js';
const SCREENSHOT_PREFIX = 'data:image/jpeg;base64,';
/** @typedef {import('../../types/lhr/audit-details').default.ItemValueType} ItemValueType */
/**
* Upgrades an lhr object in-place to account for changes in the data structure over major versions.
* @param {LH.Result} lhr
*/
function upgradeLhrForCompatibility(lhr) {
// If LHR is older (≤3.0.3), it has no locale setting. Set default.
if (!lhr.configSettings.locale) {
lhr.configSettings.locale = 'en';
}
if (!lhr.configSettings.formFactor) {
// @ts-expect-error fallback handling for emulatedFormFactor
lhr.configSettings.formFactor = lhr.configSettings.emulatedFormFactor;
}
lhr.finalDisplayedUrl = Util.getFinalDisplayedUrl(lhr);
lhr.mainDocumentUrl = Util.getMainDocumentUrl(lhr);
for (const audit of Object.values(lhr.audits)) {
// Turn 'not-applicable' (LHR <4.0) and 'not_applicable' (older proto versions)
// into 'notApplicable' (LHR ≥4.0).
// @ts-expect-error tsc rightly flags that these values shouldn't occur.
// eslint-disable-next-line max-len
if (audit.scoreDisplayMode === 'not_applicable' || audit.scoreDisplayMode === 'not-applicable') {
audit.scoreDisplayMode = 'notApplicable';
}
if (audit.scoreDisplayMode === 'informative') {
audit.score = 1;
}
if (audit.details) {
// Turn `auditDetails.type` of undefined (LHR <4.2) and 'diagnostic' (LHR <5.0)
// into 'debugdata' (LHR ≥5.0).
// @ts-expect-error tsc rightly flags that these values shouldn't occur.
if (audit.details.type === undefined || audit.details.type === 'diagnostic') {
// @ts-expect-error details is of type never.
audit.details.type = 'debugdata';
}
// Add the jpg data URL prefix to filmstrip screenshots without them (LHR <5.0).
if (audit.details.type === 'filmstrip') {
for (const screenshot of audit.details.items) {
if (!screenshot.data.startsWith(SCREENSHOT_PREFIX)) {
screenshot.data = SCREENSHOT_PREFIX + screenshot.data;
}
}
}
// Circa 10.0, table items were refactored.
if (audit.details.type === 'table') {
for (const heading of audit.details.headings) {
/** @type {{itemType: ItemValueType|undefined, text: string|undefined}} */
// @ts-expect-error
const {itemType, text} = heading;
if (itemType !== undefined) {
heading.valueType = itemType;
// @ts-expect-error
delete heading.itemType;
}
if (text !== undefined) {
heading.label = text;
// @ts-expect-error
delete heading.text;
}
// @ts-expect-error
const subItemsItemType = heading.subItemsHeading?.itemType;
if (heading.subItemsHeading && subItemsItemType !== undefined) {
heading.subItemsHeading.valueType = subItemsItemType;
// @ts-expect-error
delete heading.subItemsHeading.itemType;
}
}
}
// In 10.0, third-party-summary deprecated entity: LinkValue and switched to entity name string
if (audit.id === 'third-party-summary') {
if (audit.details.type === 'opportunity' || audit.details.type === 'table') {
const {headings, items} = audit.details;
if (headings[0].valueType === 'link') {
// Apply upgrade only if we are dealing with an older version (valueType=link marker).
headings[0].valueType = 'text';
for (const item of items) {
if (typeof item.entity === 'object' && item.entity.type === 'link') {
item.entity = item.entity.text;
}
}
audit.details.isEntityGrouped = true;
}
}
}
// TODO: convert printf-style displayValue.
// Added: #5099, v3
// Removed: #6767, v4
}
}
// This backcompat converts old LHRs to use the "hidden" and "diagnostics" groups.
// <9.0.0 LHRs used "no group" to identify audits that should be hidden in performance instead of the "hidden" group.
// <9.0.0 LHRs used "load-opportunities" for opportunity audits that are now grouped under "diagnostics".
// >=9.0.0 && <12.0.0 LHRs use "no group" to identify opportunities and diagnostics whose groups are assigned by details type.
// >=12.0.0 LHRs use the "hidden" group to identify hidden audits and "diagnostics" to identify diagnostics. "no group" is meaningless.
const [majorVersion] = lhr.lighthouseVersion.split('.').map(Number);
const perfCategory = lhr.categories['performance'];
if (perfCategory) {
if (majorVersion < 9) {
if (!lhr.categoryGroups) lhr.categoryGroups = {};
lhr.categoryGroups['hidden'] = {title: ''};
for (const auditRef of perfCategory.auditRefs) {
if (!auditRef.group) {
auditRef.group = 'hidden';
} else if (auditRef.group === 'load-opportunities') {
auditRef.group = 'diagnostics';
}
}
} else if (majorVersion < 12) {
for (const auditRef of perfCategory.auditRefs) {
if (!auditRef.group) {
auditRef.group = 'diagnostics';
}
}
}
}
if (majorVersion < 12 && perfCategory) {
/** @type {Map<string, string[]>} */
const metricRelevanceMap = new Map();
for (const auditRef of perfCategory.auditRefs) {
/** @type {string[]|undefined} */
// @ts-expect-error Removed in v12
const relevantAudits = auditRef.relevantAudits;
if (!relevantAudits || !auditRef.acronym) continue;
for (const auditId of relevantAudits) {
const acronyms = metricRelevanceMap.get(auditId) || [];
acronyms.push(auditRef.acronym);
metricRelevanceMap.set(auditId, acronyms);
}
}
for (const [auditId, acronyms] of metricRelevanceMap) {
if (!acronyms.length) continue;
const audit = lhr.audits[auditId];
if (!audit) continue;
// Old versions can still define metric savings, let's not mess with it.
if (audit.metricSavings) continue;
audit.metricSavings = {};
for (const acronym of acronyms) {
audit.metricSavings[acronym] = 0;
}
}
}
// Add some minimal stuff so older reports still work.
if (!lhr.environment) {
lhr.environment = {
benchmarkIndex: 0,
networkUserAgent: lhr.userAgent,
hostUserAgent: lhr.userAgent,
};
}
if (!lhr.configSettings.screenEmulation) {
lhr.configSettings.screenEmulation = {
width: -1,
height: -1,
deviceScaleFactor: -1,
mobile: /mobile/i.test(lhr.environment.hostUserAgent),
disabled: false,
};
}
if (!lhr.i18n) {
// @ts-expect-error
lhr.i18n = {};
}
// In 10.0, full-page-screenshot became a top-level property on the LHR.
if (lhr.audits['full-page-screenshot']) {
const details = /** @type {LH.Result.FullPageScreenshot=} */ (
lhr.audits['full-page-screenshot'].details);
if (details) {
lhr.fullPageScreenshot = {
screenshot: details.screenshot,
nodes: details.nodes,
};
} else {
lhr.fullPageScreenshot = null;
}
delete lhr.audits['full-page-screenshot'];
}
}
export {
upgradeLhrForCompatibility,
};