From 2704ae17f226edccccf08e28bc2ebd205e96f88b Mon Sep 17 00:00:00 2001 From: Barry Pollard Date: Tue, 15 Nov 2022 22:13:44 +0000 Subject: [PATCH 1/5] Support 'discarded' as a navigationType --- README.md | 9 ++++++--- src/lib/initMetric.ts | 2 ++ src/types/base.ts | 11 ++++++++++- test/e2e/onCLS-test.js | 21 +++++++++++++++++++++ test/e2e/onFCP-test.js | 17 +++++++++++++++++ test/e2e/onFID-test.js | 22 ++++++++++++++++++++++ test/e2e/onINP-test.js | 24 ++++++++++++++++++++++++ test/e2e/onLCP-test.js | 24 ++++++++++++++++++++++++ test/e2e/onTTFB-test.js | 18 ++++++++++++++++++ test/views/layout.njk | 20 ++++++++++++++++++++ 10 files changed, 164 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 71431874..e0137631 100644 --- a/README.md +++ b/README.md @@ -697,10 +697,13 @@ interface Metric { * The type of navigation * * Navigation Timing API (or `undefined` if the browser doesn't - * support that API). For pages that are restored from the bfcache, this - * value will be 'back-forward-cache'. + * support that API). + * For pages that are restored from the bfcache, this value will + * be 'back-forward-cache'. + * For pages that are restored after being discarded, this value will + * be 'discarded'. */ - navigationType: 'navigate' | 'reload' | 'back-forward' | 'back-forward-cache' | 'prerender'; + navigationType: 'navigate' | 'reload' | 'back-forward' | 'back-forward-cache' | 'prerender' | 'discarded'; } ``` diff --git a/src/lib/initMetric.ts b/src/lib/initMetric.ts index 63a9a665..1f6a7dbf 100644 --- a/src/lib/initMetric.ts +++ b/src/lib/initMetric.ts @@ -30,6 +30,8 @@ export const initMetric = (name: Metric['name'], value?: number): Metric => { } else if (navEntry) { if (document.prerendering || getActivationStart() > 0) { navigationType = 'prerender'; + } else if (document.wasDiscarded) { + navigationType = 'discarded'; } else { navigationType = navEntry.type.replace(/_/g, '-') as Metric['navigationType']; diff --git a/src/types/base.ts b/src/types/base.ts index a5145f5b..c430b8cf 100644 --- a/src/types/base.ts +++ b/src/types/base.ts @@ -65,7 +65,7 @@ export interface Metric { * support that API). For pages that are restored from the bfcache, this * value will be 'back-forward-cache'. */ - navigationType: 'navigate' | 'reload' | 'back-forward' | 'back-forward-cache' | 'prerender'; + navigationType: 'navigate' | 'reload' | 'back-forward' | 'back-forward-cache' | 'prerender' | 'discarded'; } /** @@ -105,3 +105,12 @@ export interface ReportOpts { * loading. This is equivalent to the corresponding `readyState` value. */ export type LoadState = 'loading' | 'dom-interactive' | 'dom-content-loaded' | 'complete'; + +/** + * Extend the document with the new wasDiscarded boolean which is not supported + * in typescript yet + * https://github.com/WICG/page-lifecycle/blob/main/README.md + */ +declare global { + interface Document { wasDiscarded?: boolean; } +} diff --git a/test/e2e/onCLS-test.js b/test/e2e/onCLS-test.js index 3a116e26..d2ade3d7 100644 --- a/test/e2e/onCLS-test.js +++ b/test/e2e/onCLS-test.js @@ -632,6 +632,27 @@ describe('onCLS()', async function() { assert.strictEqual(cls.navigationType, 'back-forward-cache'); }); + it('reports discarded as nav type for wasDiscarded', async function() { + if (!browserSupportsCLS) this.skip(); + + await browser.url('/test/cls?wasDiscarded=1'); + + // Wait until all images are loaded and rendered, then change to hidden. + await imagesPainted(); + await stubVisibilityChange('hidden'); + + await beaconCountIs(1); + const [cls] = await getBeacons(); + + assert(cls.value >= 0); + assert(cls.id.match(/^v3-\d+-\d+$/)); + assert.strictEqual(cls.name, 'CLS'); + assert.strictEqual(cls.value, cls.delta); + assert.strictEqual(cls.rating, 'good'); + assert.strictEqual(cls.entries.length, 2); + assert.strictEqual(cls.navigationType, 'discarded'); + }); + describe('attribution', function() { it('includes attribution data on the metric object', async function() { if (!browserSupportsCLS) this.skip(); diff --git a/test/e2e/onFCP-test.js b/test/e2e/onFCP-test.js index 01adf266..e612daf9 100644 --- a/test/e2e/onFCP-test.js +++ b/test/e2e/onFCP-test.js @@ -235,6 +235,23 @@ describe('onFCP()', async function() { assert.strictEqual(fcp2.navigationType, 'back-forward-cache'); }); + it('reports discarded as nav type for wasDiscarded', async function() { + if (!browserSupportsFCP) this.skip(); + + await browser.url('/test/fcp?wasDiscarded=1'); + + await beaconCountIs(1); + + const [fcp] = await getBeacons(); + assert(fcp.value >= 0); + assert(fcp.id.match(/^v3-\d+-\d+$/)); + assert.strictEqual(fcp.name, 'FCP'); + assert.strictEqual(fcp.value, fcp.delta); + assert.strictEqual(fcp.rating, 'good'); + assert.strictEqual(fcp.entries.length, 1); + assert.strictEqual(fcp.navigationType, 'discarded'); + }); + describe('attribution', function() { it('includes attribution data on the metric object', async function() { if (!browserSupportsFCP) this.skip(); diff --git a/test/e2e/onFID-test.js b/test/e2e/onFID-test.js index 97751742..54cb282b 100644 --- a/test/e2e/onFID-test.js +++ b/test/e2e/onFID-test.js @@ -194,6 +194,28 @@ describe('onFID()', async function() { assert.match(fid2.entries[0].name, /(mouse|pointer)down/); }); + it('reports discarded as nav type for wasDiscarded', async function() { + if (!browserSupportsFID) this.skip(); + + await browser.url('/test/fid?wasDiscarded=1'); + + // Click on the

. + const h1 = await $('h1'); + await h1.click(); + + await beaconCountIs(1); + + const [fid] = await getBeacons(); + assert(fid.value >= 0); + assert(fid.id.match(/^v3-\d+-\d+$/)); + assert.strictEqual(fid.name, 'FID'); + assert.strictEqual(fid.value, fid.delta); + assert.strictEqual(fid.rating, 'good'); + assert.strictEqual(fid.navigationType, 'discarded'); + assert.match(fid.entries[0].name, /(mouse|pointer)down/); + }); + + describe('attribution', function() { it('includes attribution data on the metric object', async function() { if (!browserSupportsFID) this.skip(); diff --git a/test/e2e/onINP-test.js b/test/e2e/onINP-test.js index d40bb804..660e3916 100644 --- a/test/e2e/onINP-test.js +++ b/test/e2e/onINP-test.js @@ -316,6 +316,30 @@ describe('onINP()', async function() { assert.strictEqual(beacons.length, 0); }); + it('reports discarded as nav type for wasDiscarded', async function() { + if (!browserSupportsINP) this.skip(); + + await browser.url('/test/inp?click=100&wasDiscarded=1'); + + const h1 = await $('h1'); + await h1.click(); + + await stubVisibilityChange('hidden'); + + await beaconCountIs(1); + + const [inp] = await getBeacons(); + assert(inp.value >= 0); + assert(inp.id.match(/^v3-\d+-\d+$/)); + assert.strictEqual(inp.name, 'INP'); + assert.strictEqual(inp.value, inp.delta); + assert.strictEqual(inp.rating, 'good'); + assert(containsEntry(inp.entries, 'click', 'h1')); + assert(interactionIDsMatch(inp.entries)); + assert(inp.entries[0].interactionId > 0); + assert.strictEqual(inp.navigationType, 'discarded'); + }); + describe('attribution', function() { it('includes attribution data on the metric object', async function() { if (!browserSupportsINP) this.skip(); diff --git a/test/e2e/onLCP-test.js b/test/e2e/onLCP-test.js index 4cc7e8e7..6d9c305a 100644 --- a/test/e2e/onLCP-test.js +++ b/test/e2e/onLCP-test.js @@ -373,6 +373,30 @@ describe('onLCP()', async function() { assert.strictEqual(lcp2.navigationType, 'back-forward-cache'); }); + it('reports discarded as nav type for wasDiscarded', async function() { + if (!browserSupportsLCP) this.skip(); + + await browser.url('/test/lcp?wasDiscarded=1'); + + // Wait until all images are loaded and fully rendered. + await imagesPainted(); + + // Load a new page to trigger the hidden state. + await browser.url('about:blank'); + + await beaconCountIs(1); + + const [lcp] = await getBeacons(); + + assert(lcp.value > 0); // Greater than the image load delay. + assert(lcp.id.match(/^v3-\d+-\d+$/)); + assert.strictEqual(lcp.name, 'LCP'); + assert.strictEqual(lcp.value, lcp.delta); + assert.strictEqual(lcp.rating, 'good'); + assert.strictEqual(lcp.entries.length, 1); + assert.strictEqual(lcp.navigationType, 'discarded'); + }); + describe('attribution', function() { it('includes attribution data on the metric object', async function() { if (!browserSupportsLCP) this.skip(); diff --git a/test/e2e/onTTFB-test.js b/test/e2e/onTTFB-test.js index 1505acc6..c8a3f4f7 100644 --- a/test/e2e/onTTFB-test.js +++ b/test/e2e/onTTFB-test.js @@ -209,6 +209,24 @@ describe('onTTFB()', async function() { } }); + it('reports discarded as nav type for wasDiscarded', async function() { + await browser.url('/test/ttfb?wasDiscarded=1'); + + const ttfb = await getTTFBBeacon(); + + assert(ttfb.value >= 0); + assert(ttfb.value >= ttfb.entries[0].requestStart); + assert(ttfb.value <= ttfb.entries[0].loadEventEnd); + assert(ttfb.id.match(/^v3-\d+-\d+$/)); + assert.strictEqual(ttfb.name, 'TTFB'); + assert.strictEqual(ttfb.value, ttfb.delta); + assert.strictEqual(ttfb.rating, 'good'); + assert.strictEqual(ttfb.navigationType, 'discarded'); + assert.strictEqual(ttfb.entries.length, 1); + + assertValidEntry(ttfb.entries[0]); + }); + describe('attribution', function() { it('includes attribution data on the metric object', async function() { await browser.url('/test/ttfb?attribution=1'); diff --git a/test/views/layout.njk b/test/views/layout.njk index 2128b92a..c3a3a9b4 100644 --- a/test/views/layout.njk +++ b/test/views/layout.njk @@ -100,6 +100,22 @@ }); } + /** + * @return {Promise} + */ + self.__stubWasDiscarded = () => { + return new Promise((resolve) => { + const navEntry = performance.getEntriesByType('navigation')[0]; + // Only stub if the page isn't actually discarded. + if (!document.wasDiscarded) { + Object.defineProperty(document, 'wasDiscarded', { + value: true, + configurable: true, + }); + } + }); + } + // Uncomment to stub running in a browser that doesn't support performance APIs // (e.g. some version of Opera support this). // delete self.performance; @@ -131,6 +147,10 @@ if (params.has('prerender')) { self.__stubPrerender(); } + + if (params.has('wasDiscarded')) { + self.__stubWasDiscarded(); + } }()); {% if polyfill %} From 656ff0038a4161e42ff5c6aa13c35e3db3150f0e Mon Sep 17 00:00:00 2001 From: Barry Pollard Date: Tue, 15 Nov 2022 22:16:48 +0000 Subject: [PATCH 2/5] discard -> restore --- README.md | 4 ++-- test/e2e/onCLS-test.js | 4 ++-- test/e2e/onFCP-test.js | 4 ++-- test/e2e/onFID-test.js | 4 ++-- test/e2e/onINP-test.js | 4 ++-- test/e2e/onLCP-test.js | 4 ++-- test/e2e/onTTFB-test.js | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index e0137631..63728f6a 100644 --- a/README.md +++ b/README.md @@ -701,9 +701,9 @@ interface Metric { * For pages that are restored from the bfcache, this value will * be 'back-forward-cache'. * For pages that are restored after being discarded, this value will - * be 'discarded'. + * be 'restore'. */ - navigationType: 'navigate' | 'reload' | 'back-forward' | 'back-forward-cache' | 'prerender' | 'discarded'; + navigationType: 'navigate' | 'reload' | 'back-forward' | 'back-forward-cache' | 'prerender' | 'restore'; } ``` diff --git a/test/e2e/onCLS-test.js b/test/e2e/onCLS-test.js index d2ade3d7..4552852f 100644 --- a/test/e2e/onCLS-test.js +++ b/test/e2e/onCLS-test.js @@ -632,7 +632,7 @@ describe('onCLS()', async function() { assert.strictEqual(cls.navigationType, 'back-forward-cache'); }); - it('reports discarded as nav type for wasDiscarded', async function() { + it('reports restore as nav type for wasDiscarded', async function() { if (!browserSupportsCLS) this.skip(); await browser.url('/test/cls?wasDiscarded=1'); @@ -650,7 +650,7 @@ describe('onCLS()', async function() { assert.strictEqual(cls.value, cls.delta); assert.strictEqual(cls.rating, 'good'); assert.strictEqual(cls.entries.length, 2); - assert.strictEqual(cls.navigationType, 'discarded'); + assert.strictEqual(cls.navigationType, 'restore'); }); describe('attribution', function() { diff --git a/test/e2e/onFCP-test.js b/test/e2e/onFCP-test.js index e612daf9..aa065951 100644 --- a/test/e2e/onFCP-test.js +++ b/test/e2e/onFCP-test.js @@ -235,7 +235,7 @@ describe('onFCP()', async function() { assert.strictEqual(fcp2.navigationType, 'back-forward-cache'); }); - it('reports discarded as nav type for wasDiscarded', async function() { + it('reports restore as nav type for wasDiscarded', async function() { if (!browserSupportsFCP) this.skip(); await browser.url('/test/fcp?wasDiscarded=1'); @@ -249,7 +249,7 @@ describe('onFCP()', async function() { assert.strictEqual(fcp.value, fcp.delta); assert.strictEqual(fcp.rating, 'good'); assert.strictEqual(fcp.entries.length, 1); - assert.strictEqual(fcp.navigationType, 'discarded'); + assert.strictEqual(fcp.navigationType, 'restore'); }); describe('attribution', function() { diff --git a/test/e2e/onFID-test.js b/test/e2e/onFID-test.js index 54cb282b..f22fa5ce 100644 --- a/test/e2e/onFID-test.js +++ b/test/e2e/onFID-test.js @@ -194,7 +194,7 @@ describe('onFID()', async function() { assert.match(fid2.entries[0].name, /(mouse|pointer)down/); }); - it('reports discarded as nav type for wasDiscarded', async function() { + it('reports restore as nav type for wasDiscarded', async function() { if (!browserSupportsFID) this.skip(); await browser.url('/test/fid?wasDiscarded=1'); @@ -211,7 +211,7 @@ describe('onFID()', async function() { assert.strictEqual(fid.name, 'FID'); assert.strictEqual(fid.value, fid.delta); assert.strictEqual(fid.rating, 'good'); - assert.strictEqual(fid.navigationType, 'discarded'); + assert.strictEqual(fid.navigationType, 'restore'); assert.match(fid.entries[0].name, /(mouse|pointer)down/); }); diff --git a/test/e2e/onINP-test.js b/test/e2e/onINP-test.js index 660e3916..e26aee49 100644 --- a/test/e2e/onINP-test.js +++ b/test/e2e/onINP-test.js @@ -316,7 +316,7 @@ describe('onINP()', async function() { assert.strictEqual(beacons.length, 0); }); - it('reports discarded as nav type for wasDiscarded', async function() { + it('reports restore as nav type for wasDiscarded', async function() { if (!browserSupportsINP) this.skip(); await browser.url('/test/inp?click=100&wasDiscarded=1'); @@ -337,7 +337,7 @@ describe('onINP()', async function() { assert(containsEntry(inp.entries, 'click', 'h1')); assert(interactionIDsMatch(inp.entries)); assert(inp.entries[0].interactionId > 0); - assert.strictEqual(inp.navigationType, 'discarded'); + assert.strictEqual(inp.navigationType, 'restore'); }); describe('attribution', function() { diff --git a/test/e2e/onLCP-test.js b/test/e2e/onLCP-test.js index 6d9c305a..5c73faf7 100644 --- a/test/e2e/onLCP-test.js +++ b/test/e2e/onLCP-test.js @@ -373,7 +373,7 @@ describe('onLCP()', async function() { assert.strictEqual(lcp2.navigationType, 'back-forward-cache'); }); - it('reports discarded as nav type for wasDiscarded', async function() { + it('reports restore as nav type for wasDiscarded', async function() { if (!browserSupportsLCP) this.skip(); await browser.url('/test/lcp?wasDiscarded=1'); @@ -394,7 +394,7 @@ describe('onLCP()', async function() { assert.strictEqual(lcp.value, lcp.delta); assert.strictEqual(lcp.rating, 'good'); assert.strictEqual(lcp.entries.length, 1); - assert.strictEqual(lcp.navigationType, 'discarded'); + assert.strictEqual(lcp.navigationType, 'restore'); }); describe('attribution', function() { diff --git a/test/e2e/onTTFB-test.js b/test/e2e/onTTFB-test.js index c8a3f4f7..17df7880 100644 --- a/test/e2e/onTTFB-test.js +++ b/test/e2e/onTTFB-test.js @@ -209,7 +209,7 @@ describe('onTTFB()', async function() { } }); - it('reports discarded as nav type for wasDiscarded', async function() { + it('reports restore as nav type for wasDiscarded', async function() { await browser.url('/test/ttfb?wasDiscarded=1'); const ttfb = await getTTFBBeacon(); @@ -221,7 +221,7 @@ describe('onTTFB()', async function() { assert.strictEqual(ttfb.name, 'TTFB'); assert.strictEqual(ttfb.value, ttfb.delta); assert.strictEqual(ttfb.rating, 'good'); - assert.strictEqual(ttfb.navigationType, 'discarded'); + assert.strictEqual(ttfb.navigationType, 'restore'); assert.strictEqual(ttfb.entries.length, 1); assertValidEntry(ttfb.entries[0]); From b894478dd5f0b058e8afbc0632da37cfc4b16d63 Mon Sep 17 00:00:00 2001 From: Barry Pollard Date: Tue, 15 Nov 2022 22:18:34 +0000 Subject: [PATCH 3/5] Missed 2 --- src/lib/initMetric.ts | 2 +- src/types/base.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/initMetric.ts b/src/lib/initMetric.ts index 1f6a7dbf..57453bd6 100644 --- a/src/lib/initMetric.ts +++ b/src/lib/initMetric.ts @@ -31,7 +31,7 @@ export const initMetric = (name: Metric['name'], value?: number): Metric => { if (document.prerendering || getActivationStart() > 0) { navigationType = 'prerender'; } else if (document.wasDiscarded) { - navigationType = 'discarded'; + navigationType = 'restore'; } else { navigationType = navEntry.type.replace(/_/g, '-') as Metric['navigationType']; diff --git a/src/types/base.ts b/src/types/base.ts index c430b8cf..811e628e 100644 --- a/src/types/base.ts +++ b/src/types/base.ts @@ -65,7 +65,7 @@ export interface Metric { * support that API). For pages that are restored from the bfcache, this * value will be 'back-forward-cache'. */ - navigationType: 'navigate' | 'reload' | 'back-forward' | 'back-forward-cache' | 'prerender' | 'discarded'; + navigationType: 'navigate' | 'reload' | 'back-forward' | 'back-forward-cache' | 'prerender' | 'restore'; } /** From 43a116b73f6bebcb409b336564eba93a9a4525a2 Mon Sep 17 00:00:00 2001 From: Barry Pollard Date: Tue, 15 Nov 2022 22:57:18 +0000 Subject: [PATCH 4/5] Review feedback --- src/types.ts | 6 ++++-- src/types/base.ts | 9 --------- test/views/layout.njk | 1 - 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/types.ts b/src/types.ts index 26beaba2..22446712 100644 --- a/src/types.ts +++ b/src/types.ts @@ -58,9 +58,11 @@ interface PerformanceEntryMap { // Update built-in types to be more accurate. declare global { - // https://wicg.github.io/nav-speculation/prerendering.html#document-prerendering interface Document { - prerendering?: boolean + // https://wicg.github.io/nav-speculation/prerendering.html#document-prerendering + prerendering?: boolean; + // https://github.com/WICG/page-lifecycle/blob/main/README.md + wasDiscarded?: boolean; } interface Performance { diff --git a/src/types/base.ts b/src/types/base.ts index 811e628e..1d2d0a6a 100644 --- a/src/types/base.ts +++ b/src/types/base.ts @@ -105,12 +105,3 @@ export interface ReportOpts { * loading. This is equivalent to the corresponding `readyState` value. */ export type LoadState = 'loading' | 'dom-interactive' | 'dom-content-loaded' | 'complete'; - -/** - * Extend the document with the new wasDiscarded boolean which is not supported - * in typescript yet - * https://github.com/WICG/page-lifecycle/blob/main/README.md - */ -declare global { - interface Document { wasDiscarded?: boolean; } -} diff --git a/test/views/layout.njk b/test/views/layout.njk index c3a3a9b4..9136e6f2 100644 --- a/test/views/layout.njk +++ b/test/views/layout.njk @@ -105,7 +105,6 @@ */ self.__stubWasDiscarded = () => { return new Promise((resolve) => { - const navEntry = performance.getEntriesByType('navigation')[0]; // Only stub if the page isn't actually discarded. if (!document.wasDiscarded) { Object.defineProperty(document, 'wasDiscarded', { From 70e4d78aa17d187314cfaea8ddae189a3d8ed5ca Mon Sep 17 00:00:00 2001 From: Barry Pollard Date: Tue, 15 Nov 2022 23:02:23 +0000 Subject: [PATCH 5/5] Update src/types.ts Co-authored-by: Philip Walton --- src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index 22446712..e75751a7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -61,7 +61,7 @@ declare global { interface Document { // https://wicg.github.io/nav-speculation/prerendering.html#document-prerendering prerendering?: boolean; - // https://github.com/WICG/page-lifecycle/blob/main/README.md + // https://wicg.github.io/page-lifecycle/#sec-api wasDiscarded?: boolean; }