perf_hooks: add missing resource timing attributes · nodejs/node@09b333b · GitHub
Skip to content

Commit 09b333b

Browse files
greenheadHQaduh95
authored andcommitted
perf_hooks: add missing resource timing attributes
Add the finalResponseHeadersStart, firstInterimResponseStart, renderBlockingStatus, contentType and contentEncoding getters to PerformanceResourceTiming and update the WPT status accordingly. Signed-off-by: greenhead <shren0812@gmail.com> PR-URL: #65017 Refs: #51589 Reviewed-By: Daeyeon Jeong <daeyeon.dev@gmail.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
1 parent 8e9151c commit 09b333b

6 files changed

Lines changed: 320 additions & 14 deletions

File tree

‎doc/api/perf_hooks.md‎

Lines changed: 79 additions & 0 deletions

‎lib/internal/perf/resource_timing.js‎

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const { enqueue, bufferResourceTiming } = require('internal/perf/observe');
1717
const { validateThisInternalField } = require('internal/validators');
1818
const { kEnumerableProperty } = require('internal/util');
1919

20+
const kBodyInfo = Symbol('kBodyInfo');
2021
const kCacheMode = Symbol('kCacheMode');
2122
const kRequestedUrl = Symbol('kRequestedUrl');
2223
const kTimingInfo = Symbol('kTimingInfo');
@@ -110,11 +111,22 @@ class PerformanceResourceTiming extends PerformanceEntry {
110111
return this[kTimingInfo].finalNetworkRequestStartTime;
111112
}
112113

113-
get responseStart() {
114+
get finalResponseHeadersStart() {
114115
validateThisInternalField(this, kTimingInfo, 'PerformanceResourceTiming');
115116
return this[kTimingInfo].finalNetworkResponseStartTime;
116117
}
117118

119+
get firstInterimResponseStart() {
120+
validateThisInternalField(this, kTimingInfo, 'PerformanceResourceTiming');
121+
return this[kTimingInfo].firstInterimNetworkResponseStartTime ?? 0;
122+
}
123+
124+
get responseStart() {
125+
validateThisInternalField(this, kTimingInfo, 'PerformanceResourceTiming');
126+
return this[kTimingInfo].firstInterimNetworkResponseStartTime ||
127+
this[kTimingInfo].finalNetworkResponseStartTime;
128+
}
129+
118130
get responseEnd() {
119131
validateThisInternalField(this, kTimingInfo, 'PerformanceResourceTiming');
120132
return this[kTimingInfo].endTime;
@@ -148,6 +160,22 @@ class PerformanceResourceTiming extends PerformanceEntry {
148160
return this[kResponseStatus];
149161
}
150162

163+
get renderBlockingStatus() {
164+
validateThisInternalField(this, kTimingInfo, 'PerformanceResourceTiming');
165+
return this[kTimingInfo].renderBlocking === true ?
166+
'blocking' : 'non-blocking';
167+
}
168+
169+
get contentType() {
170+
validateThisInternalField(this, kTimingInfo, 'PerformanceResourceTiming');
171+
return this[kBodyInfo]?.contentType ?? '';
172+
}
173+
174+
get contentEncoding() {
175+
validateThisInternalField(this, kTimingInfo, 'PerformanceResourceTiming');
176+
return this[kBodyInfo]?.contentEncoding ?? '';
177+
}
178+
151179
toJSON() {
152180
validateThisInternalField(this, kInitiatorType, 'PerformanceResourceTiming');
153181
return {
@@ -167,13 +195,18 @@ class PerformanceResourceTiming extends PerformanceEntry {
167195
connectEnd: this.connectEnd,
168196
secureConnectionStart: this.secureConnectionStart,
169197
requestStart: this.requestStart,
198+
finalResponseHeadersStart: this.finalResponseHeadersStart,
199+
firstInterimResponseStart: this.firstInterimResponseStart,
170200
responseStart: this.responseStart,
171201
responseEnd: this.responseEnd,
172202
transferSize: this.transferSize,
173203
encodedBodySize: this.encodedBodySize,
174204
decodedBodySize: this.decodedBodySize,
175205
deliveryType: this.deliveryType,
176206
responseStatus: this.responseStatus,
207+
renderBlockingStatus: this.renderBlockingStatus,
208+
contentType: this.contentType,
209+
contentEncoding: this.contentEncoding,
177210
};
178211
}
179212
}
@@ -191,13 +224,18 @@ ObjectDefineProperties(PerformanceResourceTiming.prototype, {
191224
connectEnd: kEnumerableProperty,
192225
secureConnectionStart: kEnumerableProperty,
193226
requestStart: kEnumerableProperty,
227+
finalResponseHeadersStart: kEnumerableProperty,
228+
firstInterimResponseStart: kEnumerableProperty,
194229
responseStart: kEnumerableProperty,
195230
responseEnd: kEnumerableProperty,
196231
transferSize: kEnumerableProperty,
197232
encodedBodySize: kEnumerableProperty,
198233
decodedBodySize: kEnumerableProperty,
199234
deliveryType: kEnumerableProperty,
200235
responseStatus: kEnumerableProperty,
236+
renderBlockingStatus: kEnumerableProperty,
237+
contentType: kEnumerableProperty,
238+
contentEncoding: kEnumerableProperty,
201239
toJSON: kEnumerableProperty,
202240
[SymbolToStringTag]: {
203241
__proto__: null,
@@ -224,6 +262,7 @@ function createPerformanceResourceTiming(
224262
// The spec doesn't say to validate it in the class construction.
225263
resourceTiming[kTimingInfo] = timingInfo;
226264
resourceTiming[kCacheMode] = cacheMode;
265+
resourceTiming[kBodyInfo] = bodyInfo;
227266
resourceTiming[kDeliveryType] = deliveryType;
228267
resourceTiming[kResponseStatus] = responseStatus;
229268

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
'use strict';
2+
3+
require('../common');
4+
const assert = require('assert');
5+
const {
6+
PerformanceResourceTiming,
7+
performance,
8+
} = require('perf_hooks');
9+
10+
// Covers the IDL attributes finalResponseHeadersStart,
11+
// firstInterimResponseStart, renderBlockingStatus, contentType and contentEncoding,
12+
// and the spec requirement that responseStart prefers a non-zero
13+
// firstInterimResponseStart over finalResponseHeadersStart.
14+
15+
function createTimingInfo(overrides = {}) {
16+
return {
17+
startTime: 0,
18+
redirectStartTime: 0,
19+
redirectEndTime: 0,
20+
postRedirectStartTime: 0,
21+
finalServiceWorkerStartTime: 0,
22+
finalNetworkRequestStartTime: 0,
23+
finalNetworkResponseStartTime: 0,
24+
endTime: 0,
25+
encodedBodySize: 0,
26+
decodedBodySize: 0,
27+
finalConnectionTimingInfo: null,
28+
...overrides,
29+
};
30+
}
31+
32+
function markResourceTiming(timingInfo, bodyInfo) {
33+
return performance.markResourceTiming(
34+
timingInfo,
35+
'http://localhost:8080',
36+
'fetch',
37+
{},
38+
'',
39+
bodyInfo,
40+
200,
41+
'',
42+
);
43+
}
44+
45+
// Default values when no optional timing or body metadata is present.
46+
{
47+
const resource = markResourceTiming(createTimingInfo(), {});
48+
49+
assert.strictEqual(resource.finalResponseHeadersStart, 0);
50+
assert.strictEqual(resource.firstInterimResponseStart, 0);
51+
assert.strictEqual(resource.responseStart, 0);
52+
assert.strictEqual(resource.renderBlockingStatus, 'non-blocking');
53+
assert.strictEqual(resource.contentType, '');
54+
assert.strictEqual(resource.contentEncoding, '');
55+
}
56+
57+
// responseStart falls back to the final response when no interim response timing was recorded.
58+
{
59+
const resource = markResourceTiming(createTimingInfo({
60+
finalNetworkResponseStartTime: 123,
61+
firstInterimNetworkResponseStartTime: 0,
62+
}), {});
63+
64+
assert.strictEqual(resource.finalResponseHeadersStart, 123);
65+
assert.strictEqual(resource.firstInterimResponseStart, 0);
66+
assert.strictEqual(resource.responseStart, 123);
67+
}
68+
69+
// Values reflected from timing info and body info.
70+
{
71+
const timingInfo = createTimingInfo({
72+
finalNetworkResponseStartTime: 123,
73+
firstInterimNetworkResponseStartTime: 45,
74+
renderBlocking: true,
75+
});
76+
const bodyInfo = {
77+
contentType: 'text/html',
78+
contentEncoding: 'gzip',
79+
};
80+
const resource = markResourceTiming(timingInfo, bodyInfo);
81+
82+
assert.strictEqual(resource.finalResponseHeadersStart, 123);
83+
assert.strictEqual(resource.firstInterimResponseStart, 45);
84+
assert.strictEqual(resource.responseStart, 45);
85+
assert.strictEqual(resource.renderBlockingStatus, 'blocking');
86+
assert.strictEqual(resource.contentType, 'text/html');
87+
assert.strictEqual(resource.contentEncoding, 'gzip');
88+
89+
const json = resource.toJSON();
90+
assert.strictEqual(json.finalResponseHeadersStart, 123);
91+
assert.strictEqual(json.firstInterimResponseStart, 45);
92+
assert.strictEqual(json.responseStart, 45);
93+
assert.strictEqual(json.renderBlockingStatus, 'blocking');
94+
assert.strictEqual(json.contentType, 'text/html');
95+
assert.strictEqual(json.contentEncoding, 'gzip');
96+
}
97+
98+
// The attributes are enumerable getters on the prototype and perform a
99+
// brand check like the other PerformanceResourceTiming attributes.
100+
for (const name of [
101+
'finalResponseHeadersStart',
102+
'firstInterimResponseStart',
103+
'renderBlockingStatus',
104+
'contentType',
105+
'contentEncoding',
106+
]) {
107+
const desc = Object.getOwnPropertyDescriptor(
108+
PerformanceResourceTiming.prototype, name);
109+
assert.strictEqual(desc.enumerable, true, name);
110+
assert.strictEqual(typeof desc.get, 'function', name);
111+
assert.throws(() => desc.get.call({}), {
112+
code: 'ERR_INVALID_THIS',
113+
}, name);
114+
}
115+
116+
performance.clearResourceTimings();
Lines changed: 67 additions & 0 deletions

0 commit comments

Comments
 (0)