Skip to content

Commit ac811b6

Browse files
committed
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: nodejs#44902 Reviewed-By: James M Snell <[email protected]>
1 parent 9083e49 commit ac811b6

File tree

5 files changed

+236
-0
lines changed

5 files changed

+236
-0
lines changed

lib/internal/perf/observe.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ const {
6767
const { inspect } = require('util');
6868

6969
const { now } = require('internal/perf/utils');
70+
const { convertToInt } = require('internal/webidl');
7071

7172
const kDispatch = Symbol('kDispatch');
7273
const kMaybeBuffer = Symbol('kMaybeBuffer');
@@ -431,6 +432,8 @@ function bufferResourceTiming(entry) {
431432

432433
// https://w3c.github.io/resource-timing/#dom-performance-setresourcetimingbuffersize
433434
function setResourceTimingBufferSize(maxSize) {
435+
// unsigned long
436+
maxSize = convertToInt('maxSize', maxSize, 32);
434437
// If the maxSize parameter is less than resource timing buffer current
435438
// size, no PerformanceResourceTiming objects are to be removed from the
436439
// performance entry buffer.

lib/internal/webidl.js

Lines changed: 163 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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',
Lines changed: 58 additions & 0 deletions
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

Lines changed: 11 additions & 0 deletions
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)