Skip to content
Open
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
93 changes: 58 additions & 35 deletions core/audits/baseline.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,25 @@ class Baseline extends Audit {
return null;
}

/**
* @param {string} featureId
* @param {{high: Record<string, string>, low: Record<string, string>, limited: string[]}} featureData
* @return {string|null}
*/
static getLowDate(featureId, featureData) {
if (featureId in featureData.low) {
const lowData = /** @type {Record<string, string>} */ (featureData.low);
return lowData[featureId];
}
if (featureId in featureData.high) {
const highData = /** @type {Record<string, string>} */ (featureData.high);
const date = new Date(highData[featureId]);
date.setUTCMonth(date.getUTCMonth() - 30);
return date.toISOString().slice(0, 10);
}
return null;
}

/**
* @param {LH.Artifacts} artifacts
* @return {Promise<LH.Audit.Product>}
Expand All @@ -101,15 +120,14 @@ class Baseline extends Audit {
/** @type {Map<string, {featureId: string, source: LH.Audit.Details.SourceLocationValue | undefined}>} */
const featuresMap = new Map();

const dxEvents = /** @type {DXFeatureEvent[]} */ (
(trace.traceEvents || []).filter(e => e.cat === 'blink.webdx_feature_usage' &&
e.args?.feature)
);

for (const event of dxEvents) {
const key = `${event.args.feature}`;
for (const e of trace.traceEvents || []) {
if (e.cat !== 'blink.webdx_feature_usage' || !e.args?.feature) {
continue;
}
const event = /** @type {DXFeatureEvent} */ (e);

if (featuresMap.has(key)) continue;
const feature = /** @type {string} */ (event.args.feature);
if (featuresMap.has(feature)) continue;

/** @type {LH.Audit.Details.SourceLocationValue | undefined} */
let source;
Expand All @@ -121,8 +139,8 @@ class Baseline extends Audit {
source = Audit.makeSourceLocation(event.args.url, line, column);
}

featuresMap.set(key, {
featureId: event.args.feature,
featuresMap.set(feature, {
featureId: feature,
source,
});
}
Expand All @@ -142,6 +160,8 @@ class Baseline extends Audit {
if (!status) continue;
const {displayStatus, baselineTier} = status;

const lowDate = Baseline.getLowDate(featureId, Baseline.featureData) || '';

baselineStatus.push({
featureId: {
type: /** @type {const} */ ('link'),
Expand All @@ -154,11 +174,12 @@ class Baseline extends Audit {
displayString: displayStatus,
},
source: feature.source,
lowDate,
});
}

/** @type {LH.Audit.Details.Table['headings']} */
const headings = [
const webFeatureHeadings = [
{
key: 'featureId',
valueType: 'link',
Expand All @@ -176,46 +197,48 @@ class Baseline extends Audit {
},
];

/**
* Determines the sorting rank of a baseline status.
* @param {string} status The display status string.
* @return {number} The numerical rank (1 is the highest priority).
*/
const getStatusRank = (status) => {
if (status.startsWith('Limited')) {
return 1;
}
if (status.startsWith('Newly')) {
return 2;
}
if (status.startsWith('Widely')) {
return 3;
}
return 4;
/** @type {Record<string, number>} */
const TIER_RANKS = {
limited: 1,
low: 2,
high: 3,
};

const sortedStatuses = baselineStatus.sort((featureA, featureB) => {
const rankA = getStatusRank(featureA.displayStatus.displayString);
const rankB = getStatusRank(featureB.displayStatus.displayString);
const rankA = TIER_RANKS[featureA.displayStatus.status] || 4;
const rankB = TIER_RANKS[featureB.displayStatus.status] || 4;

if (rankA !== rankB) {
return rankA - rankB;
}

const hasSourceA = !!featureA.source;
const hasSourceB = !!featureB.source;
return featureB.lowDate.localeCompare(featureA.lowDate);
});

if (hasSourceA !== hasSourceB) {
return hasSourceA ? -1 : 1;
const hasLimited = baselineStatus.some(item => item.displayStatus.status === 'limited');

let displayValue;
if (!hasLimited && sortedStatuses.length > 0) {
const newestFeature = sortedStatuses[0];
const featureName = newestFeature.featureId.text;
const lowDate = newestFeature.lowDate;
if (lowDate) {
const year = lowDate.substring(0, 4);
displayValue = `Baseline ${year} based on ${featureName} (${lowDate})`;
}
}

return 0;
// Remove `lowDate` property from items before generating details table.
const tableItems = sortedStatuses.map(item => {
const {lowDate: _, ...rest} = item;
return rest;
});

const details = Audit.makeTableDetails(headings, sortedStatuses);
const details = Audit.makeTableDetails(webFeatureHeadings, tableItems);

return {
score: 1,
displayValue,
details,
};
}
Expand Down
52 changes: 52 additions & 0 deletions core/test/audits/baseline-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,58 @@ describe('Baseline Audit', () => {
});
});

it('should not set displayValue when a limited availability feature ' +
'is present (newest is newly available)', async () => {
const traceEvents = [
{args: {feature: 'accelerometer'}, cat: 'blink.webdx_feature_usage'},
{
args: {feature: 'abortsignal-any'}, // low (2024-03-19)
cat: 'blink.webdx_feature_usage',
},
{args: {feature: 'forced-colors'}, cat: 'blink.webdx_feature_usage'}, // high (2022-09-12)
];
const result = await Baseline.audit({Trace: {traceEvents}});
expect(result.displayValue).toBeUndefined();
});

it('should not set displayValue when a limited availability feature ' +
'is present (newest is widely available)', async () => {
const traceEvents = [
{args: {feature: 'accelerometer'}, cat: 'blink.webdx_feature_usage'},
{args: {feature: 'forced-colors'}, cat: 'blink.webdx_feature_usage'}, // high (2022-09-12)
];
const result = await Baseline.audit({Trace: {traceEvents}});
expect(result.displayValue).toBeUndefined();
});

it('should not set displayValue when a limited availability feature ' +
'is present (only limited)', async () => {
const traceEvents = [
{args: {feature: 'accelerometer'}, cat: 'blink.webdx_feature_usage'},
];
const result = await Baseline.audit({Trace: {traceEvents}});
expect(result.displayValue).toBeUndefined();
});

it('should set correct displayValue when no limited availability features ' +
'are present (newest is newly available)', async () => {
const traceEvents = [
{args: {feature: 'abortsignal-any'}, cat: 'blink.webdx_feature_usage'}, // low (2024-03-19)
{args: {feature: 'forced-colors'}, cat: 'blink.webdx_feature_usage'}, // high (2022-09-12)
];
const result = await Baseline.audit({Trace: {traceEvents}});
expect(result.displayValue).toEqual('Baseline 2024 based on abortsignal-any (2024-03-19)');
});

it('should set correct displayValue when no limited availability features ' +
'are present (newest is widely available)', async () => {
const traceEvents = [
{args: {feature: 'forced-colors'}, cat: 'blink.webdx_feature_usage'}, // high (2022-09-12)
];
const result = await Baseline.audit({Trace: {traceEvents}});
expect(result.displayValue).toEqual('Baseline 2020 based on forced-colors (2020-03-12)');
});

describe('getFeatureStatus', () => {
const fakeData = {
high: {
Expand Down
55 changes: 28 additions & 27 deletions core/test/results/sample_v2.json
Original file line number Diff line number Diff line change
Expand Up @@ -4335,6 +4335,7 @@
"description": "Lists web features used on the page and their Baseline status as of 2026-06-19. [Learn more about Baseline](https://webstatus.dev/).",
"score": 1,
"scoreDisplayMode": "informative",
"displayValue": "Baseline 2024 based on fetch-priority (2024-10-29)",
"details": {
"type": "table",
"headings": [
Expand Down Expand Up @@ -4370,20 +4371,25 @@
{
"featureId": {
"type": "link",
"text": "request-animation-frame",
"url": "https://webstatus.dev/features/request-animation-frame"
"text": "not",
"url": "https://webstatus.dev/features/not"
},
"displayStatus": {
"type": "baseline-status",
"status": "high",
"displayString": "Widely Available (2015-07-29)"
"displayString": "Widely Available (2021-01-21)"
}
},
{
"featureId": {
"type": "link",
"text": "slot",
"url": "https://webstatus.dev/features/slot"
},
"source": {
"type": "source-location",
"url": "http://localhost:10200/dobetterweb/dbw_tester.html",
"urlProvider": "network",
"line": 412,
"column": 2
"displayStatus": {
"type": "baseline-status",
"status": "high",
"displayString": "Widely Available (2020-01-15)"
}
},
{
Expand All @@ -4405,18 +4411,6 @@
"column": 23
}
},
{
"featureId": {
"type": "link",
"text": "template",
"url": "https://webstatus.dev/features/template"
},
"displayStatus": {
"type": "baseline-status",
"status": "high",
"displayString": "Widely Available (2015-11-12)"
}
},
{
"featureId": {
"type": "link",
Expand All @@ -4432,25 +4426,32 @@
{
"featureId": {
"type": "link",
"text": "slot",
"url": "https://webstatus.dev/features/slot"
"text": "template",
"url": "https://webstatus.dev/features/template"
},
"displayStatus": {
"type": "baseline-status",
"status": "high",
"displayString": "Widely Available (2020-01-15)"
"displayString": "Widely Available (2015-11-12)"
}
},
{
"featureId": {
"type": "link",
"text": "not",
"url": "https://webstatus.dev/features/not"
"text": "request-animation-frame",
"url": "https://webstatus.dev/features/request-animation-frame"
},
"displayStatus": {
"type": "baseline-status",
"status": "high",
"displayString": "Widely Available (2021-01-21)"
"displayString": "Widely Available (2015-07-29)"
},
"source": {
"type": "source-location",
"url": "http://localhost:10200/dobetterweb/dbw_tester.html",
"urlProvider": "network",
"line": 412,
"column": 2
}
}
]
Expand Down
Loading