Skip to content

Commit

Permalink
core: refactor audits to use async syntax for improved readability
Browse files Browse the repository at this point in the history
  • Loading branch information
alexnj committed Nov 16, 2022
1 parent 3ee2de5 commit 75149ae
Show file tree
Hide file tree
Showing 5 changed files with 259 additions and 266 deletions.
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

0 comments on commit 75149ae

Please sign in to comment.