Skip to content
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

Ignore TTFB for loads where responseStart is zero #281

Merged
merged 2 commits into from
Nov 14, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 11 additions & 8 deletions src/onTTFB.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,20 +67,23 @@ export const onTTFB = (onReport: ReportCallback, opts?: ReportOpts) => {
const navEntry = getNavigationEntry();

if (navEntry) {
const responseStart = navEntry.responseStart;

// In some cases no value is reported by the browser (for
// privacy/security reasons), and in other cases (bugs) the value is
// negative or is larger than the current page time. Ignore these cases:
// https://github.com/GoogleChrome/web-vitals/issues/137
// https://github.com/GoogleChrome/web-vitals/issues/162
// https://github.com/GoogleChrome/web-vitals/issues/275
if (responseStart <= 0 || responseStart > performance.now()) return;

// The activationStart reference is used because TTFB should be
// relative to page activation rather than navigation start if the
// page was prerendered. But in cases where `activationStart` occurs
// after the first byte is received, this time should be clamped at 0.
metric.value = Math.max(navEntry.responseStart - getActivationStart(), 0);

// In some cases the value reported is negative or is larger
// than the current page time. Ignore these cases:
// https://github.com/GoogleChrome/web-vitals/issues/137
// https://github.com/GoogleChrome/web-vitals/issues/162
if (metric.value < 0 || metric.value > performance.now()) return;
metric.value = Math.max(responseStart - getActivationStart(), 0);

metric.entries = [navEntry];

report(true);

// Only report TTFB after bfcache restores if a `navigation` entry
Expand Down
27 changes: 26 additions & 1 deletion test/e2e/onTTFB-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import assert from 'assert';
import {beaconCountIs, clearBeacons, getBeacons} from '../utils/beacons.js';
import {domReadyState} from '../utils/domReadyState.js';
import {stubForwardBack} from '../utils/stubForwardBack.js';


Expand Down Expand Up @@ -175,15 +176,39 @@ describe('onTTFB()', async function() {

const ttfb2 = await getTTFBBeacon();

assert(ttfb2.value >= 0);
assert(ttfb2.id.match(/^v3-\d+-\d+$/));
assert.strictEqual(ttfb2.value, 0);
assert.strictEqual(ttfb2.name, 'TTFB');
assert.strictEqual(ttfb2.value, ttfb2.delta);
assert.strictEqual(ttfb2.rating, 'good');
assert.strictEqual(ttfb2.navigationType, 'back-forward-cache');
assert.strictEqual(ttfb2.entries.length, 0);
});

it('ignores navigations with invalid responseStart timestamps', async function() {
for (const rs of [-1, 0, 1e12]) {
await browser.url(`/test/ttfb?responseStart=${rs}`);

await domReadyState('complete');

// Wait a bit to ensure no beacons were sent.
await browser.pause(1000);

const loadBeacons = await getBeacons();
assert.strictEqual(loadBeacons.length, 0);

// Test back-forward navigations to ensure they're not sent either
// in these situations.
await stubForwardBack();

// Wait a bit to ensure no beacons were sent.
await browser.pause(1000);

const bfcacheBeacons = await getBeacons();
assert.strictEqual(bfcacheBeacons.length, 0);
}
});

describe('attribution', function() {
it('includes attribution data on the metric object', async function() {
await browser.url('/test/ttfb?attribution=1');
Expand Down
12 changes: 12 additions & 0 deletions test/views/ttfb.njk
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@

<p><a id="navigate-away" href="https://example.com">Navigate away</a></p>

<script>
// Set the blocking values based on query params if present.
const params = new URLSearchParams(location.search);

if (params.has('responseStart')) {
const navEntry = performance.getEntriesByType('navigation')[0];
Object.defineProperty(navEntry, 'responseStart', {
value: Number(params.get('responseStart')),
});
}
</script>

<script type="module">
import {onTTFB} from '{{ modulePath }}';

Expand Down