Skip to content

core: refactor audits to use async syntax #14542

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 87 additions & 88 deletions core/audits/byte-efficiency/uses-long-cache-ttl.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,103 +194,102 @@ class CacheHeaders extends Audit {
* @param {LH.Audit.Context} context
* @return {Promise<LH.Audit.Product>}
*/
static audit(artifacts, context) {
static async audit(artifacts, context) {
const devtoolsLogs = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
return NetworkRecords.request(devtoolsLogs, context).then(records => {
const results = [];
let totalWastedBytes = 0;

for (const record of records) {
if (!CacheHeaders.isCacheableAsset(record)) continue;

/** @type {Map<string, string>} */
const headers = new Map();
for (const header of record.responseHeaders || []) {
if (headers.has(header.name.toLowerCase())) {
const previousHeaderValue = headers.get(header.name.toLowerCase());
headers.set(header.name.toLowerCase(),
`${previousHeaderValue}, ${header.value}`);
} else {
headers.set(header.name.toLowerCase(), header.value);
}
}

const cacheControl = parseCacheControl(headers.get('cache-control'));
if (this.shouldSkipRecord(headers, cacheControl)) {
continue;
const records = await NetworkRecords.request(devtoolsLogs, context);
const results = [];
let totalWastedBytes = 0;

for (const record of records) {
if (!CacheHeaders.isCacheableAsset(record)) continue;

/** @type {Map<string, string>} */
const headers = new Map();
for (const header of record.responseHeaders || []) {
if (headers.has(header.name.toLowerCase())) {
const previousHeaderValue = headers.get(header.name.toLowerCase());
headers.set(header.name.toLowerCase(),
`${previousHeaderValue}, ${header.value}`);
} else {
headers.set(header.name.toLowerCase(), header.value);
}
}

// Ignore if cacheLifetimeInSeconds is a nonpositive number.
let cacheLifetimeInSeconds = CacheHeaders.computeCacheLifetimeInSeconds(
headers, cacheControl);
if (cacheLifetimeInSeconds !== null &&
(!Number.isFinite(cacheLifetimeInSeconds) || cacheLifetimeInSeconds <= 0)) {
continue;
}
cacheLifetimeInSeconds = cacheLifetimeInSeconds || 0;

// Ignore assets whose cache lifetime is already high enough
const cacheHitProbability = CacheHeaders.getCacheHitProbability(cacheLifetimeInSeconds);
if (cacheHitProbability > IGNORE_THRESHOLD_IN_PERCENT) continue;

const url = UrlUtils.elideDataURI(record.url);
const totalBytes = record.transferSize || 0;
const wastedBytes = (1 - cacheHitProbability) * totalBytes;

totalWastedBytes += wastedBytes;

// Include cacheControl info (if it exists) per url as a diagnostic.
/** @type {LH.Audit.Details.DebugData|undefined} */
let debugData;
if (cacheControl) {
debugData = {
type: 'debugdata',
...cacheControl,
};
}
const cacheControl = parseCacheControl(headers.get('cache-control'));
if (this.shouldSkipRecord(headers, cacheControl)) {
continue;
}

results.push({
url,
debugData,
cacheLifetimeMs: cacheLifetimeInSeconds * 1000,
cacheHitProbability,
totalBytes,
wastedBytes,
});
// Ignore if cacheLifetimeInSeconds is a nonpositive number.
let cacheLifetimeInSeconds = CacheHeaders.computeCacheLifetimeInSeconds(
headers, cacheControl);
if (cacheLifetimeInSeconds !== null &&
(!Number.isFinite(cacheLifetimeInSeconds) || cacheLifetimeInSeconds <= 0)) {
continue;
}
cacheLifetimeInSeconds = cacheLifetimeInSeconds || 0;

// Ignore assets whose cache lifetime is already high enough
const cacheHitProbability = CacheHeaders.getCacheHitProbability(cacheLifetimeInSeconds);
if (cacheHitProbability > IGNORE_THRESHOLD_IN_PERCENT) continue;

const url = UrlUtils.elideDataURI(record.url);
const totalBytes = record.transferSize || 0;
const wastedBytes = (1 - cacheHitProbability) * totalBytes;

totalWastedBytes += wastedBytes;

// Include cacheControl info (if it exists) per url as a diagnostic.
/** @type {LH.Audit.Details.DebugData|undefined} */
let debugData;
if (cacheControl) {
debugData = {
type: 'debugdata',
...cacheControl,
};
}

results.sort((a, b) => {
return a.cacheLifetimeMs - b.cacheLifetimeMs ||
b.totalBytes - a.totalBytes ||
a.url.localeCompare(b.url);
results.push({
url,
debugData,
cacheLifetimeMs: cacheLifetimeInSeconds * 1000,
cacheHitProbability,
totalBytes,
wastedBytes,
});
}

const score = Audit.computeLogNormalScore(
{p10: context.options.p10, median: context.options.median},
totalWastedBytes
);

/** @type {LH.Audit.Details.Table['headings']} */
const headings = [
{key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)},
// TODO(i18n): pre-compute localized duration
{key: 'cacheLifetimeMs', valueType: 'ms', label: str_(i18n.UIStrings.columnCacheTTL),
displayUnit: 'duration'},
{key: 'totalBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnTransferSize),
displayUnit: 'kb', granularity: 1},
];

const summary = {wastedBytes: totalWastedBytes};
const details = Audit.makeTableDetails(headings, results, summary);

return {
score,
numericValue: totalWastedBytes,
numericUnit: 'byte',
displayValue: str_(UIStrings.displayValue, {itemCount: results.length}),
details,
};
results.sort((a, b) => {
return a.cacheLifetimeMs - b.cacheLifetimeMs ||
b.totalBytes - a.totalBytes ||
a.url.localeCompare(b.url);
});

const score = Audit.computeLogNormalScore(
{p10: context.options.p10, median: context.options.median},
totalWastedBytes
);

/** @type {LH.Audit.Details.Table['headings']} */
const headings = [
{key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)},
// TODO(i18n): pre-compute localized duration
{key: 'cacheLifetimeMs', valueType: 'ms', label: str_(i18n.UIStrings.columnCacheTTL),
displayUnit: 'duration'},
{key: 'totalBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnTransferSize),
displayUnit: 'kb', granularity: 1},
];

const summary = {wastedBytes: totalWastedBytes};
const details = Audit.makeTableDetails(headings, results, summary);

return {
score,
numericValue: totalWastedBytes,
numericUnit: 'byte',
displayValue: str_(UIStrings.displayValue, {itemCount: results.length}),
details,
};
}
}

Expand Down
83 changes: 41 additions & 42 deletions core/audits/critical-request-chains.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,52 +166,51 @@ class CriticalRequestChains extends Audit {
* @param {LH.Audit.Context} context
* @return {Promise<LH.Audit.Product>}
*/
static audit(artifacts, context) {
static async audit(artifacts, context) {
const trace = artifacts.traces[Audit.DEFAULT_PASS];
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
const URL = artifacts.URL;
return ComputedChains.request({devtoolsLog, trace, URL}, context).then(chains => {
let chainCount = 0;
/**
* @param {LH.Audit.Details.SimpleCriticalRequestNode} node
* @param {number} depth
*/
function walk(node, depth) {
const childIds = Object.keys(node);

childIds.forEach(id => {
const child = node[id];
if (child.children) {
walk(child.children, depth + 1);
} else {
// if the node doesn't have a children field, then it is a leaf, so +1
chainCount++;
}
}, '');
}
// Convert
const flattenedChains = CriticalRequestChains.flattenRequests(chains);

// Account for initial navigation
const initialNavKey = Object.keys(flattenedChains)[0];
const initialNavChildren = initialNavKey && flattenedChains[initialNavKey].children;
if (initialNavChildren && Object.keys(initialNavChildren).length > 0) {
walk(initialNavChildren, 0);
}
const chains = await ComputedChains.request({devtoolsLog, trace, URL}, context);
let chainCount = 0;
/**
* @param {LH.Audit.Details.SimpleCriticalRequestNode} node
* @param {number} depth
*/
function walk(node, depth) {
const childIds = Object.keys(node);

const longestChain = CriticalRequestChains._getLongestChain(flattenedChains);

return {
score: Number(chainCount === 0),
notApplicable: chainCount === 0,
displayValue: chainCount ? str_(UIStrings.displayValue, {itemCount: chainCount}) : '',
details: {
type: 'criticalrequestchain',
chains: flattenedChains,
longestChain,
},
};
});
childIds.forEach(id => {
const child = node[id];
if (child.children) {
walk(child.children, depth + 1);
} else {
// if the node doesn't have a children field, then it is a leaf, so +1
chainCount++;
}
}, '');
}
// Convert
const flattenedChains = CriticalRequestChains.flattenRequests(chains);

// Account for initial navigation
const initialNavKey = Object.keys(flattenedChains)[0];
const initialNavChildren = initialNavKey && flattenedChains[initialNavKey].children;
if (initialNavChildren && Object.keys(initialNavChildren).length > 0) {
walk(initialNavChildren, 0);
}

const longestChain = CriticalRequestChains._getLongestChain(flattenedChains);

return {
score: Number(chainCount === 0),
notApplicable: chainCount === 0,
displayValue: chainCount ? str_(UIStrings.displayValue, {itemCount: chainCount}) : '',
details: {
type: 'criticalrequestchain',
chains: flattenedChains,
longestChain,
},
};
}
}

Expand Down
77 changes: 38 additions & 39 deletions core/audits/is-on-https.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,53 +70,52 @@ class HTTPS extends Audit {
* @param {LH.Audit.Context} context
* @return {Promise<LH.Audit.Product>}
*/
static audit(artifacts, context) {
static async audit(artifacts, context) {
const devtoolsLogs = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
return NetworkRecords.request(devtoolsLogs, context).then(networkRecords => {
const insecureURLs = networkRecords
.filter(record => !NetworkRequest.isSecureRequest(record))
.map(record => UrlUtils.elideDataURI(record.url));
const networkRecords = await NetworkRecords.request(devtoolsLogs, context);
const insecureURLs = networkRecords
.filter(record => !NetworkRequest.isSecureRequest(record))
.map(record => UrlUtils.elideDataURI(record.url));

/** @type {Array<{url: string, resolution?: LH.IcuMessage|string}>} */
const items = Array.from(new Set(insecureURLs)).map(url => ({url, resolution: undefined}));
/** @type {Array<{url: string, resolution?: LH.IcuMessage|string}>} */
const items = Array.from(new Set(insecureURLs)).map(url => ({url, resolution: undefined}));

/** @type {LH.Audit.Details.Table['headings']} */
const headings = [
{key: 'url', valueType: 'url', label: str_(UIStrings.columnInsecureURL)},
{key: 'resolution', valueType: 'text', label: str_(UIStrings.columnResolution)},
];
/** @type {LH.Audit.Details.Table['headings']} */
const headings = [
{key: 'url', valueType: 'url', label: str_(UIStrings.columnInsecureURL)},
{key: 'resolution', valueType: 'text', label: str_(UIStrings.columnResolution)},
];

for (const details of artifacts.InspectorIssues.mixedContentIssue) {
let item = items.find(item => item.url === details.insecureURL);
if (!item) {
item = {url: details.insecureURL};
items.push(item);
}
item.resolution = resolutionToString[details.resolutionStatus] ?
str_(resolutionToString[details.resolutionStatus]) :
details.resolutionStatus;
for (const details of artifacts.InspectorIssues.mixedContentIssue) {
let item = items.find(item => item.url === details.insecureURL);
if (!item) {
item = {url: details.insecureURL};
items.push(item);
}
item.resolution = resolutionToString[details.resolutionStatus] ?
str_(resolutionToString[details.resolutionStatus]) :
details.resolutionStatus;
}

// If a resolution wasn't assigned from an InspectorIssue, then the item
// is not blocked by the browser but we've determined it is insecure anyhow.
// For example, if the URL is localhost, all `http` requests are valid
// (localhost is a secure context), but we still identify `http` requests
// as an "Allowed" insecure URL.
for (const item of items) {
if (!item.resolution) item.resolution = str_(UIStrings.allowed);
}
// If a resolution wasn't assigned from an InspectorIssue, then the item
// is not blocked by the browser but we've determined it is insecure anyhow.
// For example, if the URL is localhost, all `http` requests are valid
// (localhost is a secure context), but we still identify `http` requests
// as an "Allowed" insecure URL.
for (const item of items) {
if (!item.resolution) item.resolution = str_(UIStrings.allowed);
}

let displayValue;
if (items.length > 0) {
displayValue = str_(UIStrings.displayValue, {itemCount: items.length});
}
let displayValue;
if (items.length > 0) {
displayValue = str_(UIStrings.displayValue, {itemCount: items.length});
}

return {
score: Number(items.length === 0),
displayValue,
details: Audit.makeTableDetails(headings, items),
};
});
return {
score: Number(items.length === 0),
displayValue,
details: Audit.makeTableDetails(headings, items),
};
}
}

Expand Down
Loading