Skip to content

Commit 7cdf745

Browse files
authored
perf_hooks: convert maxSize to IDL value in setResourceTimingBufferSize
ECMAScript values of WebIDL interface parameters should be converted to IDL representatives before the actual implementation, as defined in step 11.5 of the WebIDL Overload resolution algorithm. Refs: https://webidl.spec.whatwg.org/#dfn-create-operation-function Refs: https://webidl.spec.whatwg.org/#es-overloads PR-URL: #44902 Reviewed-By: James M Snell <[email protected]>
1 parent 15f1051 commit 7cdf745

File tree

5 files changed

+236
-0
lines changed

5 files changed

+236
-0
lines changed

lib/internal/perf/performance.js

+3
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const timerify = require('internal/perf/timerify');
4444
const { customInspectSymbol: kInspect, kEnumerableProperty, kEmptyObject } = require('internal/util');
4545
const { inspect } = require('util');
4646
const { validateInternalField } = require('internal/validators');
47+
const { convertToInt } = require('internal/webidl');
4748

4849
const {
4950
getTimeOriginTimestamp
@@ -144,6 +145,8 @@ class Performance extends EventTarget {
144145
if (arguments.length === 0) {
145146
throw new ERR_MISSING_ARGS('maxSize');
146147
}
148+
// unsigned long
149+
maxSize = convertToInt('maxSize', maxSize, 32);
147150
return setResourceTimingBufferSize(maxSize);
148151
}
149152

lib/internal/webidl.js

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
'use strict';
2+
3+
const {
4+
MathAbs,
5+
MathMax,
6+
MathMin,
7+
MathPow,
8+
MathSign,
9+
MathTrunc,
10+
NumberIsNaN,
11+
NumberMAX_SAFE_INTEGER,
12+
NumberMIN_SAFE_INTEGER,
13+
} = primordials;
14+
15+
const {
16+
codes: {
17+
ERR_INVALID_ARG_VALUE,
18+
},
19+
} = require('internal/errors');
20+
const { kEmptyObject } = require('internal/util');
21+
22+
// https://webidl.spec.whatwg.org/#abstract-opdef-integerpart
23+
const integerPart = MathTrunc;
24+
25+
/* eslint-disable node-core/non-ascii-character */
26+
// Round x to the nearest integer, choosing the even integer if it lies halfway
27+
// between two, and choosing +0 rather than -0.
28+
// This is different from Math.round, which rounds to the next integer in the
29+
// direction of +∞ when the fraction portion is exactly 0.5.
30+
/* eslint-enable node-core/non-ascii-character */
31+
function evenRound(x) {
32+
// Convert -0 to +0.
33+
const i = integerPart(x) + 0;
34+
const reminder = MathAbs(x % 1);
35+
const sign = MathSign(i);
36+
if (reminder === 0.5) {
37+
return i % 2 === 0 ? i : i + sign;
38+
}
39+
const r = reminder < 0.5 ? i : i + sign;
40+
// Convert -0 to +0.
41+
if (r === 0) {
42+
return 0;
43+
}
44+
return r;
45+
}
46+
47+
function pow2(exponent) {
48+
// << operates on 32 bit signed integers.
49+
if (exponent < 31) {
50+
return 1 << exponent;
51+
}
52+
if (exponent === 31) {
53+
return 0x8000_0000;
54+
}
55+
if (exponent === 32) {
56+
return 0x1_0000_0000;
57+
}
58+
return MathPow(2, exponent);
59+
}
60+
61+
// https://tc39.es/ecma262/#eqn-modulo
62+
// The notation “x modulo y” computes a value k of the same sign as y.
63+
function modulo(x, y) {
64+
const r = x % y;
65+
// Convert -0 to +0.
66+
if (r === 0) {
67+
return 0;
68+
}
69+
return r;
70+
}
71+
72+
// https://webidl.spec.whatwg.org/#abstract-opdef-converttoint
73+
function convertToInt(name, value, bitLength, options = kEmptyObject) {
74+
const { signed = false, enforceRange = false, clamp = false } = options;
75+
76+
let upperBound;
77+
let lowerBound;
78+
// 1. If bitLength is 64, then:
79+
if (bitLength === 64) {
80+
// 1.1. Let upperBound be 2^53 − 1.
81+
upperBound = NumberMAX_SAFE_INTEGER;
82+
// 1.2. If signedness is "unsigned", then let lowerBound be 0.
83+
// 1.3. Otherwise let lowerBound be −2^53 + 1.
84+
lowerBound = !signed ? 0 : NumberMIN_SAFE_INTEGER;
85+
} else if (!signed) {
86+
// 2. Otherwise, if signedness is "unsigned", then:
87+
// 2.1. Let lowerBound be 0.
88+
// 2.2. Let upperBound be 2^bitLength − 1.
89+
lowerBound = 0;
90+
upperBound = pow2(bitLength) - 1;
91+
} else {
92+
// 3. Otherwise:
93+
// 3.1. Let lowerBound be -2^(bitLength − 1).
94+
// 3.2. Let upperBound be 2^(bitLength − 1) − 1.
95+
lowerBound = -pow2(bitLength - 1);
96+
upperBound = pow2(bitLength - 1) - 1;
97+
}
98+
99+
// 4. Let x be ? ToNumber(V).
100+
let x = +value;
101+
// 5. If x is −0, then set x to +0.
102+
if (x === 0) {
103+
x = 0;
104+
}
105+
106+
// 6. If the conversion is to an IDL type associated with the [EnforceRange]
107+
// extended attribute, then:
108+
if (enforceRange) {
109+
// 6.1. If x is NaN, +∞, or −∞, then throw a TypeError.
110+
if (NumberIsNaN(x) || x === Infinity || x === -Infinity) {
111+
throw new ERR_INVALID_ARG_VALUE(name, x);
112+
}
113+
// 6.2. Set x to IntegerPart(x).
114+
x = integerPart(x);
115+
116+
// 6.3. If x < lowerBound or x > upperBound, then throw a TypeError.
117+
if (x < lowerBound || x > upperBound) {
118+
throw new ERR_INVALID_ARG_VALUE(name, x);
119+
}
120+
121+
// 6.4. Return x.
122+
return x;
123+
}
124+
125+
// 7. If x is not NaN and the conversion is to an IDL type associated with
126+
// the [Clamp] extended attribute, then:
127+
if (clamp && !NumberIsNaN(x)) {
128+
// 7.1. Set x to min(max(x, lowerBound), upperBound).
129+
x = MathMin(MathMax(x, lowerBound), upperBound);
130+
131+
// 7.2. Round x to the nearest integer, choosing the even integer if it
132+
// lies halfway between two, and choosing +0 rather than −0.
133+
x = evenRound(x);
134+
135+
// 7.3. Return x.
136+
return x;
137+
}
138+
139+
// 8. If x is NaN, +0, +∞, or −∞, then return +0.
140+
if (NumberIsNaN(x) || x === 0 || x === Infinity || x === -Infinity) {
141+
return 0;
142+
}
143+
144+
// 9. Set x to IntegerPart(x).
145+
x = integerPart(x);
146+
147+
// 10. Set x to x modulo 2^bitLength.
148+
x = modulo(x, pow2(bitLength));
149+
150+
// 11. If signedness is "signed" and x ≥ 2^(bitLength − 1), then return x −
151+
// 2^bitLength.
152+
if (signed && x >= pow2(bitLength - 1)) {
153+
return x - pow2(bitLength);
154+
}
155+
156+
// 12. Otherwise, return x.
157+
return x;
158+
}
159+
160+
module.exports = {
161+
convertToInt,
162+
evenRound,
163+
};

test/parallel/test-bootstrap-modules.js

+1
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ const expectedModules = new Set([
149149
'NativeModule internal/validators',
150150
'NativeModule internal/vm/module',
151151
'NativeModule internal/wasm_web_api',
152+
'NativeModule internal/webidl',
152153
'NativeModule internal/webstreams/adapters',
153154
'NativeModule internal/webstreams/compression',
154155
'NativeModule internal/webstreams/encoding',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Flags: --expose-internals
2+
'use strict';
3+
4+
require('../common');
5+
const assert = require('assert');
6+
const { convertToInt, evenRound } = require('internal/webidl');
7+
8+
assert.strictEqual(evenRound(-0.5), 0);
9+
assert.strictEqual(evenRound(0.5), 0);
10+
assert.strictEqual(evenRound(-1.5), -2);
11+
assert.strictEqual(evenRound(1.5), 2);
12+
assert.strictEqual(evenRound(3.4), 3);
13+
assert.strictEqual(evenRound(4.6), 5);
14+
assert.strictEqual(evenRound(5), 5);
15+
assert.strictEqual(evenRound(6), 6);
16+
17+
// https://webidl.spec.whatwg.org/#abstract-opdef-converttoint
18+
assert.strictEqual(convertToInt('x', 0, 64), 0);
19+
assert.strictEqual(convertToInt('x', 1, 64), 1);
20+
assert.strictEqual(convertToInt('x', -0.5, 64), 0);
21+
assert.strictEqual(convertToInt('x', -0.5, 64, { signed: true }), 0);
22+
assert.strictEqual(convertToInt('x', -1.5, 64, { signed: true }), -1);
23+
24+
// EnforceRange
25+
const OutOfRangeValues = [ NaN, Infinity, -Infinity, 2 ** 53, -(2 ** 53) ];
26+
for (const value of OutOfRangeValues) {
27+
assert.throws(() => convertToInt('x', value, 64, { enforceRange: true }), {
28+
name: 'TypeError',
29+
code: 'ERR_INVALID_ARG_VALUE',
30+
});
31+
}
32+
33+
// Out of range: clamp
34+
assert.strictEqual(convertToInt('x', NaN, 64, { clamp: true }), 0);
35+
assert.strictEqual(convertToInt('x', Infinity, 64, { clamp: true }), Number.MAX_SAFE_INTEGER);
36+
assert.strictEqual(convertToInt('x', -Infinity, 64, { clamp: true }), 0);
37+
assert.strictEqual(convertToInt('x', -Infinity, 64, { signed: true, clamp: true }), Number.MIN_SAFE_INTEGER);
38+
assert.strictEqual(convertToInt('x', 0x1_0000_0000, 32, { clamp: true }), 0xFFFF_FFFF);
39+
assert.strictEqual(convertToInt('x', 0xFFFF_FFFF, 32, { clamp: true }), 0xFFFF_FFFF);
40+
assert.strictEqual(convertToInt('x', 0x8000_0000, 32, { clamp: true, signed: true }), 0x7FFF_FFFF);
41+
assert.strictEqual(convertToInt('x', 0xFFFF_FFFF, 32, { clamp: true, signed: true }), 0x7FFF_FFFF);
42+
assert.strictEqual(convertToInt('x', 0.5, 64, { clamp: true }), 0);
43+
assert.strictEqual(convertToInt('x', 1.5, 64, { clamp: true }), 2);
44+
assert.strictEqual(convertToInt('x', -0.5, 64, { clamp: true }), 0);
45+
assert.strictEqual(convertToInt('x', -0.5, 64, { signed: true, clamp: true }), 0);
46+
assert.strictEqual(convertToInt('x', -1.5, 64, { signed: true, clamp: true }), -2);
47+
48+
// Out of range, step 8.
49+
assert.strictEqual(convertToInt('x', NaN, 64), 0);
50+
assert.strictEqual(convertToInt('x', Infinity, 64), 0);
51+
assert.strictEqual(convertToInt('x', -Infinity, 64), 0);
52+
assert.strictEqual(convertToInt('x', 0x1_0000_0000, 32), 0);
53+
assert.strictEqual(convertToInt('x', 0x1_0000_0001, 32), 1);
54+
assert.strictEqual(convertToInt('x', 0xFFFF_FFFF, 32), 0xFFFF_FFFF);
55+
56+
// Out of range, step 11.
57+
assert.strictEqual(convertToInt('x', 0x8000_0000, 32, { signed: true }), -0x8000_0000);
58+
assert.strictEqual(convertToInt('x', 0xFFF_FFFF, 32, { signed: true }), 0xFFF_FFFF);

test/parallel/test-performance-resourcetimingbuffersize.js

+11
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,17 @@ const initiatorType = '';
3030
const cacheMode = '';
3131

3232
async function main() {
33+
// Invalid buffer size values are converted to 0.
34+
const invalidValues = [ null, undefined, true, false, -1, 0.5, Infinity, NaN, '', 'foo', {}, [], () => {} ];
35+
for (const value of invalidValues) {
36+
performance.setResourceTimingBufferSize(value);
37+
performance.markResourceTiming(timingInfo, requestedUrl, initiatorType, globalThis, cacheMode);
38+
assert.strictEqual(performance.getEntriesByType('resource').length, 0);
39+
performance.clearResourceTimings();
40+
}
41+
// Wait for the buffer full event to be cleared.
42+
await waitBufferFullEvent();
43+
3344
performance.setResourceTimingBufferSize(1);
3445
performance.markResourceTiming(timingInfo, requestedUrl, initiatorType, globalThis, cacheMode);
3546
// Trigger a resourcetimingbufferfull event.

0 commit comments

Comments
 (0)