-
Notifications
You must be signed in to change notification settings - Fork 396
/
onINP.ts
150 lines (134 loc) · 5.55 KB
/
onINP.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
/*
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {onBFCacheRestore} from './lib/bfcache.js';
import {bindReporter} from './lib/bindReporter.js';
import {initMetric} from './lib/initMetric.js';
import {
DEFAULT_DURATION_THRESHOLD,
processInteractionEntry,
estimateP98LongestInteraction,
resetInteractions,
} from './lib/interactions.js';
import {observe} from './lib/observe.js';
import {onHidden} from './lib/onHidden.js';
import {initInteractionCountPolyfill} from './lib/polyfills/interactionCountPolyfill.js';
import {whenActivated} from './lib/whenActivated.js';
import {whenIdle} from './lib/whenIdle.js';
import {INPMetric, MetricRatingThresholds, ReportOpts} from './types.js';
/** Thresholds for INP. See https://web.dev/articles/inp#what_is_a_good_inp_score */
export const INPThresholds: MetricRatingThresholds = [200, 500];
/**
* Calculates the [INP](https://web.dev/articles/inp) value for the current
* page and calls the `callback` function once the value is ready, along with
* the `event` performance entries reported for that interaction. The reported
* value is a `DOMHighResTimeStamp`.
*
* A custom `durationThreshold` configuration option can optionally be passed to
* control what `event-timing` entries are considered for INP reporting. The
* default threshold is `40`, which means INP scores of less than 40 are
* reported as 0. Note that this will not affect your 75th percentile INP value
* unless that value is also less than 40 (well below the recommended
* [good](https://web.dev/articles/inp#what_is_a_good_inp_score) threshold).
*
* If the `reportAllChanges` configuration option is set to `true`, the
* `callback` function will be called as soon as the value is initially
* determined as well as any time the value changes throughout the page
* lifespan.
*
* _**Important:** INP should be continually monitored for changes throughout
* the entire lifespan of a page—including if the user returns to the page after
* it's been hidden/backgrounded. However, since browsers often [will not fire
* additional callbacks once the user has backgrounded a
* page](https://developer.chrome.com/blog/page-lifecycle-api/#advice-hidden),
* `callback` is always called when the page's visibility state changes to
* hidden. As a result, the `callback` function might be called multiple times
* during the same page load._
*/
export const >
onReport: (metric: INPMetric) => void,
opts?: ReportOpts,
) => {
// Return if the browser doesn't support all APIs needed to measure INP.
if (
!(
'PerformanceEventTiming' in self &&
'interactionId' in PerformanceEventTiming.prototype
)
) {
return;
}
// Set defaults
opts = opts || {};
whenActivated(() => {
// TODO(philipwalton): remove once the polyfill is no longer needed.
initInteractionCountPolyfill();
let metric = initMetric('INP');
let report: ReturnType<typeof bindReporter>;
const handleEntries = (entries: INPMetric['entries']) => {
// Queue the `handleEntries()` callback in the next idle task.
// This is needed to increase the chances that all event entries that
// occurred between the user interaction and the next paint
// have been dispatched. Note: there is currently an experiment
// running in Chrome (EventTimingKeypressAndCompositionInteractionId)
// 123+ that if rolled out fully may make this no longer necessary.
whenIdle(() => {
entries.forEach(processInteractionEntry);
const inp = estimateP98LongestInteraction();
if (inp && inp.latency !== metric.value) {
metric.value = inp.latency;
metric.entries = inp.entries;
report();
}
});
};
const po = observe('event', handleEntries, {
// Event Timing entries have their durations rounded to the nearest 8ms,
// so a duration of 40ms would be any event that spans 2.5 or more frames
// at 60Hz. This threshold is chosen to strike a balance between usefulness
// and performance. Running this callback for any interaction that spans
// just one or two frames is likely not worth the insight that could be
// gained.
durationThreshold: opts!.durationThreshold ?? DEFAULT_DURATION_THRESHOLD,
});
report = bindReporter(
onReport,
metric,
INPThresholds,
opts!.reportAllChanges,
);
if (po) {
// Also observe entries of type `first-input`. This is useful in cases
// where the first interaction is less than the `durationThreshold`.
po.observe({type: 'first-input', buffered: true});
onHidden(() => {
handleEntries(po.takeRecords() as INPMetric['entries']);
report(true);
});
// Only report after a bfcache restore if the `PerformanceObserver`
// successfully registered.
onBFCacheRestore(() => {
resetInteractions();
metric = initMetric('INP');
report = bindReporter(
onReport,
metric,
INPThresholds,
opts!.reportAllChanges,
);
});
}
});
};