Socket
Socket
Sign inDemoInstall

webidl-conversions

Package Overview
Dependencies
0
Maintainers
2
Versions
16
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 3.0.1 to 4.0.0

290

lib/index.js
"use strict";
var conversions = {};
module.exports = conversions;
function sign(x) {
return x < 0 ? -1 : 1;
function type(V) {
if (V === null) {
return "Null";
}
switch (typeof V) {
case "undefined":
return "Undefined";
case "boolean":
return "Boolean";
case "number":
return "Number";
case "string":
return "String";
case "symbol":
return "Symbol";
case "object":
// Falls through
case "function":
// Falls through
default:
// Per ES spec, typeof returns an implemention-defined value that is not any of the existing ones for
// uncallable non-standard exotic objects. Yet Type() which the Web IDL spec depends on returns Object for
// such cases. So treat the default case as an object.
return "Object";
}
}

@@ -14,21 +34,59 @@

return Math.floor(x);
} else {
return Math.round(x);
}
return Math.round(x);
}
function createNumberConversion(bitLength, typeOpts) {
if (!typeOpts.unsigned) {
--bitLength;
function integerPart(n) {
const r = Math.floor(Math.abs(n));
if (n < 0) {
return -1 * r;
}
const lowerBound = typeOpts.unsigned ? 0 : -Math.pow(2, bitLength);
const upperBound = Math.pow(2, bitLength) - 1;
return r;
}
const moduloVal = typeOpts.moduloBitLength ? Math.pow(2, typeOpts.moduloBitLength) : Math.pow(2, bitLength);
const moduloBound = typeOpts.moduloBitLength ? Math.pow(2, typeOpts.moduloBitLength - 1) : Math.pow(2, bitLength - 1);
function sign(x) {
return x < 0 ? -1 : 1;
}
return function(V, opts) {
if (!opts) opts = {};
function modulo(x, y) {
// https://tc39.github.io/ecma262/#eqn-modulo
// Note that http://stackoverflow.com/a/4467559/3191 does NOT work for large modulos
const signMightNotMatch = x % y;
if (sign(y) !== sign(signMightNotMatch)) {
return signMightNotMatch + y;
}
return signMightNotMatch;
}
function censorNegativeZero(x) {
return x === 0 ? 0 : x;
}
function createIntegerConversion(bitLength, typeOpts) {
const isSigned = !typeOpts.unsigned;
let lowerBound;
let upperBound;
if (bitLength === 64) {
upperBound = Math.pow(2, 53) - 1;
lowerBound = !isSigned ? 0 : -Math.pow(2, 53) + 1;
} else if (!isSigned) {
lowerBound = 0;
upperBound = Math.pow(2, bitLength) - 1;
} else {
lowerBound = -Math.pow(2, bitLength - 1);
upperBound = Math.pow(2, bitLength - 1) - 1;
}
const twoToTheBitLength = Math.pow(2, bitLength);
const twoToOneLessThanTheBitLength = Math.pow(2, bitLength - 1);
return (V, opts) => {
if (opts === undefined) {
opts = {};
}
let x = +V;
x = censorNegativeZero(x); // Spec discussion ongoing: https://github.com/heycam/webidl/issues/306

@@ -40,5 +98,7 @@ if (opts.enforceRange) {

x = sign(x) * Math.floor(Math.abs(x));
x = integerPart(x);
if (x < lowerBound || x > upperBound) {
throw new TypeError("Argument is not in byte range");
throw new TypeError(
`Argument is outside the accepted range of ${lowerBound} to ${upperBound}, inclusive`);
}

@@ -49,7 +109,5 @@

if (!isNaN(x) && opts.clamp) {
if (!Number.isNaN(x) && opts.clamp) {
x = Math.min(Math.max(x, lowerBound), upperBound);
x = evenRound(x);
if (x < lowerBound) x = lowerBound;
if (x > upperBound) x = upperBound;
return x;

@@ -61,41 +119,44 @@ }

}
x = integerPart(x);
x = sign(x) * Math.floor(Math.abs(x));
x = x % moduloVal;
// Math.pow(2, 64) is not accurately representable in JavaScript, so try to avoid these per-spec operations if
// possible. Hopefully it's an optimization for the non-64-bitLength cases too.
if (x >= lowerBound && x <= upperBound) {
return x;
}
if (!typeOpts.unsigned && x >= moduloBound) {
return x - moduloVal;
} else if (typeOpts.unsigned) {
if (x < 0) {
x += moduloVal;
} else if (x === -0) { // don't return negative zero
return 0;
}
// These will not work great for bitLength of 64, but oh well. See the README for more details.
x = modulo(x, twoToTheBitLength);
if (isSigned && x >= twoToOneLessThanTheBitLength) {
return x - twoToTheBitLength;
}
return x;
}
};
}
conversions["void"] = function () {
exports.any = V => {
return V;
};
exports.void = function () {
return undefined;
};
conversions["boolean"] = function (val) {
exports.boolean = function (val) {
return !!val;
};
conversions["byte"] = createNumberConversion(8, { unsigned: false });
conversions["octet"] = createNumberConversion(8, { unsigned: true });
exports.byte = createIntegerConversion(8, { unsigned: false });
exports.octet = createIntegerConversion(8, { unsigned: true });
conversions["short"] = createNumberConversion(16, { unsigned: false });
conversions["unsigned short"] = createNumberConversion(16, { unsigned: true });
exports.short = createIntegerConversion(16, { unsigned: false });
exports["unsigned short"] = createIntegerConversion(16, { unsigned: true });
conversions["long"] = createNumberConversion(32, { unsigned: false });
conversions["unsigned long"] = createNumberConversion(32, { unsigned: true });
exports.long = createIntegerConversion(32, { unsigned: false });
exports["unsigned long"] = createIntegerConversion(32, { unsigned: true });
conversions["long long"] = createNumberConversion(32, { unsigned: false, moduloBitLength: 64 });
conversions["unsigned long long"] = createNumberConversion(32, { unsigned: true, moduloBitLength: 64 });
exports["long long"] = createIntegerConversion(64, { unsigned: false });
exports["unsigned long long"] = createIntegerConversion(64, { unsigned: true });
conversions["double"] = function (V) {
exports.double = V => {
const x = +V;

@@ -110,19 +171,51 @@

conversions["unrestricted double"] = function (V) {
exports["unrestricted double"] = V => {
const x = +V;
return x;
};
exports.float = V => {
const x = +V;
if (!Number.isFinite(x)) {
throw new TypeError("Argument is not a finite floating-point value");
}
if (Object.is(x, -0)) {
return x;
}
const array = new Float32Array(1);
array[0] = x;
const y = array[0];
if (!Number.isFinite(y)) {
throw new TypeError("Argument is not within the range of a single-precision floating-point value");
}
return y;
};
exports["unrestricted float"] = V => {
const x = +V;
if (isNaN(x)) {
throw new TypeError("Argument is NaN");
return x;
}
return x;
if (Object.is(x, -0)) {
return x;
}
const array = new Float32Array(1);
array[0] = x;
return array[0];
};
// not quite valid, but good enough for JS
conversions["float"] = conversions["double"];
conversions["unrestricted float"] = conversions["unrestricted double"];
exports.DOMString = function (V, opts) {
if (opts === undefined) {
opts = {};
}
conversions["DOMString"] = function (V, opts) {
if (!opts) opts = {};
if (opts.treatNullAsEmptyString && V === null) {

@@ -132,8 +225,12 @@ return "";

if (typeof V === "symbol") {
throw new TypeError("Argument is a symbol, which cannot be converted to a string");
}
return String(V);
};
conversions["ByteString"] = function (V, opts) {
exports.ByteString = V => {
const x = String(V);
let c = undefined;
let c;
for (let i = 0; (c = x.codePointAt(i)) !== undefined; ++i) {

@@ -148,3 +245,3 @@ if (c > 255) {

conversions["USVString"] = function (V) {
exports.USVString = V => {
const S = String(V);

@@ -159,15 +256,13 @@ const n = S.length;

U.push(String.fromCodePoint(0xFFFD));
} else if (i === n - 1) {
U.push(String.fromCodePoint(0xFFFD));
} else {
if (i === n - 1) {
const d = S.charCodeAt(i + 1);
if (0xDC00 <= d && d <= 0xDFFF) {
const a = c & 0x3FF;
const b = d & 0x3FF;
U.push(String.fromCodePoint((2 << 15) + ((2 << 9) * a) + b));
++i;
} else {
U.push(String.fromCodePoint(0xFFFD));
} else {
const d = S.charCodeAt(i + 1);
if (0xDC00 <= d && d <= 0xDFFF) {
const a = c & 0x3FF;
const b = d & 0x3FF;
U.push(String.fromCodePoint((2 << 15) + (2 << 9) * a + b));
++i;
} else {
U.push(String.fromCodePoint(0xFFFD));
}
}

@@ -177,19 +272,54 @@ }

return U.join('');
return U.join("");
};
conversions["Date"] = function (V, opts) {
if (!(V instanceof Date)) {
throw new TypeError("Argument is not a Date object");
exports.object = V => {
if (type(V) !== "Object") {
throw new TypeError("Argument is not an object");
}
if (isNaN(V)) {
return undefined;
return V;
};
// Not exported, but used in Function and VoidFunction.
// Neither Function nor VoidFunction is defined with [TreatNonObjectAsNull], so
// handling for that is omitted.
function convertCallbackFunction(V) {
if (typeof V !== "function") {
throw new TypeError("Argument is not a function");
}
return V;
}
[
Error,
ArrayBuffer, // The IsDetachedBuffer abstract operation is not exposed in JS
DataView, Int8Array, Int16Array, Int32Array, Uint8Array,
Uint16Array, Uint32Array, Uint8ClampedArray, Float32Array, Float64Array
].forEach(func => {
const name = func.name;
const article = /^[AEIOU]/.test(name) ? "an" : "a";
exports[name] = V => {
if (!(V instanceof func)) {
throw new TypeError(`Argument is not ${article} ${name} object`);
}
return V;
};
});
// Common definitions
exports.ArrayBufferView = V => {
if (!ArrayBuffer.isView(V)) {
throw new TypeError("Argument is not a view on an ArrayBuffer object");
}
return V;
};
conversions["RegExp"] = function (V, opts) {
if (!(V instanceof RegExp)) {
V = new RegExp(V);
exports.BufferSource = V => {
if (!(ArrayBuffer.isView(V) || V instanceof ArrayBuffer)) {
throw new TypeError("Argument is not an ArrayBuffer object or a view on one");
}

@@ -199,1 +329,7 @@

};
exports.DOMTimeStamp = exports["unsigned long long"];
exports.Function = convertCallbackFunction;
exports.VoidFunction = convertCallbackFunction;
{
"name": "webidl-conversions",
"version": "3.0.1",
"version": "4.0.0",
"description": "Implements the WebIDL algorithms for converting to and from JavaScript values",
"main": "lib/index.js",
"scripts": {
"lint": "eslint .",
"test": "mocha test/*.js"

@@ -21,4 +22,5 @@ },

"devDependencies": {
"eslint": "^3.15.0",
"mocha": "^1.21.4"
}
}

@@ -1,4 +0,4 @@

# WebIDL Type Conversions on JavaScript Values
# Web IDL Type Conversions on JavaScript Values
This package implements, in JavaScript, the algorithms to convert a given JavaScript value according to a given [WebIDL](http://heycam.github.io/webidl/) [type](http://heycam.github.io/webidl/#idl-types).
This package implements, in JavaScript, the algorithms to convert a given JavaScript value according to a given [Web IDL](http://heycam.github.io/webidl/) [type](http://heycam.github.io/webidl/#idl-types).

@@ -8,2 +8,3 @@ The goal is that you should be able to write code like

```js
"use strict";
const conversions = require("webidl-conversions");

@@ -18,3 +19,3 @@

and your function `doStuff` will behave the same as a WebIDL operation declared as
and your function `doStuff` will behave the same as a Web IDL operation declared as

@@ -27,12 +28,36 @@ ```webidl

This package's main module's default export is an object with a variety of methods, each corresponding to a different WebIDL type. Each method, when invoked on a JavaScript value, will give back the new JavaScript value that results after passing through the WebIDL conversion rules. (See below for more details on what that means.) Alternately, the method could throw an error, if the WebIDL algorithm is specified to do so: for example `conversions["float"](NaN)` [will throw a `TypeError`](http://heycam.github.io/webidl/#es-float).
This package's main module's default export is an object with a variety of methods, each corresponding to a different Web IDL type. Each method, when invoked on a JavaScript value, will give back the new JavaScript value that results after passing through the Web IDL conversion rules. (See below for more details on what that means.) Alternately, the method could throw an error, if the Web IDL algorithm is specified to do so: for example `conversions["float"](NaN)` [will throw a `TypeError`](http://heycam.github.io/webidl/#es-float).
## Status
## Conversions implemented
All of the numeric types are implemented (float being implemented as double) and some others are as well - check the source for all of them. This list will grow over time in service of the [HTML as Custom Elements](https://github.com/dglazkov/html-as-custom-elements) project, but in the meantime, pull requests welcome!
Conversions for all of the basic types from the Web IDL specification are implemented:
I'm not sure yet what the strategy will be for modifiers, e.g. [`[Clamp]`](http://heycam.github.io/webidl/#Clamp). Maybe something like `conversions["unsigned long"](x, { clamp: true })`? We'll see.
- [`any`](https://heycam.github.io/webidl/#es-any)
- [`void`](https://heycam.github.io/webidl/#es-void)
- [`boolean`](https://heycam.github.io/webidl/#es-boolean)
- [Integer types](https://heycam.github.io/webidl/#es-integer-types), which can additionally be provided the boolean options `{ clamp, enforceRange }` as a second parameter
- [`float`](https://heycam.github.io/webidl/#es-float), [`unrestricted float`](https://heycam.github.io/webidl/#es-unrestricted-float)
- [`double`](https://heycam.github.io/webidl/#es-double), [`unrestricted double`](https://heycam.github.io/webidl/#es-unrestricted-double)
- [`DOMString`](https://heycam.github.io/webidl/#es-DOMString), which can additionally be provided the boolean option `{ treatNullAsEmptyString }` as a second parameter
- [`ByteString`](https://heycam.github.io/webidl/#es-ByteString), [`USVString`](https://heycam.github.io/webidl/#es-USVString)
- [`object`](https://heycam.github.io/webidl/#es-object)
- [`Error`](https://heycam.github.io/webidl/#es-Error)
- [Buffer source types](https://heycam.github.io/webidl/#es-buffer-source-types)
We might also want to extend the API to give better error messages, e.g. "Argument 1 of HTMLMediaElement.fastSeek is not a finite floating-point value" instead of "Argument is not a finite floating-point value." This would require passing in more information to the conversion functions than we currently do.
Additionally, for convenience, the following derived type definitions are implemented:
- [`ArrayBufferView`](https://heycam.github.io/webidl/#ArrayBufferView)
- [`BufferSource`](https://heycam.github.io/webidl/#BufferSource)
- [`DOMTimeStamp`](https://heycam.github.io/webidl/#DOMTimeStamp)
- [`Function`](https://heycam.github.io/webidl/#Function)
- [`VoidFunction`](https://heycam.github.io/webidl/#VoidFunction) (although it will not censor the return type)
Derived types, such as nullable types, promise types, sequences, records, etc. are not handled by this library. You may wish to investigate the [webidl2js](https://github.com/jsdom/webidl2js) project.
### A note on the `long long` types
The `long long` and `unsigned long long` Web IDL types can hold values that cannot be stored in JavaScript numbers, so the conversion is imperfect. For example, converting the JavaScript number `18446744073709552000` to a Web IDL `long long` is supposed to produce the Web IDL value `-18446744073709551232`. Since we are representing our Web IDL values in JavaScript, we can't represent `-18446744073709551232`, so we instead the best we could do is `-18446744073709552000` as the output.
This library actually doesn't even get that far. Producing those results would require doing accurate modular arithmetic on 64-bit intermediate values, but JavaScript does not make this easy. We could pull in a big-integer library as a dependency, but in lieu of that, we for now have decided to just produce inaccurate results if you pass in numbers that are not strictly between `Number.MIN_SAFE_INTEGER` and `Number.MAX_SAFE_INTEGER`.
## Background

@@ -42,16 +67,16 @@

WebIDL, as part of its madness-inducing design, has its own type system. When people write algorithms in web platform specs, they usually operate on WebIDL values, i.e. instances of WebIDL types. For example, if they were specifying the algorithm for our `doStuff` operation above, they would treat `x` as a WebIDL value of [WebIDL type `boolean`](http://heycam.github.io/webidl/#idl-boolean). Crucially, they would _not_ treat `x` as a JavaScript variable whose value is either the JavaScript `true` or `false`. They're instead working in a different type system altogether, with its own rules.
Web IDL, as part of its madness-inducing design, has its own type system. When people write algorithms in web platform specs, they usually operate on Web IDL values, i.e. instances of Web IDL types. For example, if they were specifying the algorithm for our `doStuff` operation above, they would treat `x` as a Web IDL value of [Web IDL type `boolean`](http://heycam.github.io/webidl/#idl-boolean). Crucially, they would _not_ treat `x` as a JavaScript variable whose value is either the JavaScript `true` or `false`. They're instead working in a different type system altogether, with its own rules.
Separately from its type system, WebIDL defines a ["binding"](http://heycam.github.io/webidl/#ecmascript-binding) of the type system into JavaScript. This contains rules like: when you pass a JavaScript value to the JavaScript method that manifests a given WebIDL operation, how does that get converted into a WebIDL value? For example, a JavaScript `true` passed in the position of a WebIDL `boolean` argument becomes a WebIDL `true`. But, a JavaScript `true` passed in the position of a [WebIDL `unsigned long`](http://heycam.github.io/webidl/#idl-unsigned-long) becomes a WebIDL `1`. And so on.
Separately from its type system, Web IDL defines a ["binding"](http://heycam.github.io/webidl/#ecmascript-binding) of the type system into JavaScript. This contains rules like: when you pass a JavaScript value to the JavaScript method that manifests a given Web IDL operation, how does that get converted into a Web IDL value? For example, a JavaScript `true` passed in the position of a Web IDL `boolean` argument becomes a Web IDL `true`. But, a JavaScript `true` passed in the position of a [Web IDL `unsigned long`](http://heycam.github.io/webidl/#idl-unsigned-long) becomes a Web IDL `1`. And so on.
Finally, we have the actual implementation code. This is usually C++, although these days [some smart people are using Rust](https://github.com/servo/servo). The implementation, of course, has its own type system. So when they implement the WebIDL algorithms, they don't actually use WebIDL values, since those aren't "real" outside of specs. Instead, implementations apply the WebIDL binding rules in such a way as to convert incoming JavaScript values into C++ values. For example, if code in the browser called `doStuff(true, true)`, then the implementation code would eventually receive a C++ `bool` containing `true` and a C++ `uint32_t` containing `1`.
Finally, we have the actual implementation code. This is usually C++, although these days [some smart people are using Rust](https://github.com/servo/servo). The implementation, of course, has its own type system. So when they implement the Web IDL algorithms, they don't actually use Web IDL values, since those aren't "real" outside of specs. Instead, implementations apply the Web IDL binding rules in such a way as to convert incoming JavaScript values into C++ values. For example, if code in the browser called `doStuff(true, true)`, then the implementation code would eventually receive a C++ `bool` containing `true` and a C++ `uint32_t` containing `1`.
The upside of all this is that implementations can abstract all the conversion logic away, letting WebIDL handle it, and focus on implementing the relevant methods in C++ with values of the correct type already provided. That is payoff of WebIDL, in a nutshell.
The upside of all this is that implementations can abstract all the conversion logic away, letting Web IDL handle it, and focus on implementing the relevant methods in C++ with values of the correct type already provided. That is payoff of Web IDL, in a nutshell.
And getting to that payoff is the goal of _this_ project—but for JavaScript implementations, instead of C++ ones. That is, this library is designed to make it easier for JavaScript developers to write functions that behave like a given WebIDL operation. So conceptually, the conversion pipeline, which in its general form is JavaScript values ↦ WebIDL values ↦ implementation-language values, in this case becomes JavaScript values ↦ WebIDL values ↦ JavaScript values. And that intermediate step is where all the logic is performed: a JavaScript `true` becomes a WebIDL `1` in an unsigned long context, which then becomes a JavaScript `1`.
And getting to that payoff is the goal of _this_ project—but for JavaScript implementations, instead of C++ ones. That is, this library is designed to make it easier for JavaScript developers to write functions that behave like a given Web IDL operation. So conceptually, the conversion pipeline, which in its general form is JavaScript values ↦ Web IDL values ↦ implementation-language values, in this case becomes JavaScript values ↦ Web IDL values ↦ JavaScript values. And that intermediate step is where all the logic is performed: a JavaScript `true` becomes a Web IDL `1` in an unsigned long context, which then becomes a JavaScript `1`.
## Don't Use This
## Don't use this
Seriously, why would you ever use this? You really shouldn't. WebIDL is … not great, and you shouldn't be emulating its semantics. If you're looking for a generic argument-processing library, you should find one with better rules than those from WebIDL. In general, your JavaScript should not be trying to become more like WebIDL; if anything, we should fix WebIDL to make it more like JavaScript.
Seriously, why would you ever use this? You really shouldn't. Web IDL is … strange, and you shouldn't be emulating its semantics. If you're looking for a generic argument-processing library, you should find one with better rules than those from Web IDL. In general, your JavaScript should not be trying to become more like Web IDL; if anything, we should fix Web IDL to make it more like JavaScript.
The _only_ people who should use this are those trying to create faithful implementations (or polyfills) of web platform interfaces defined in WebIDL.
The _only_ people who should use this are those trying to create faithful implementations (or polyfills) of web platform interfaces defined in Web IDL. Its main consumer is the [jsdom](https://github.com/tmpvar/jsdom) project.
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc