diff options
Diffstat (limited to 'testing/web-platform/tests/webidl/ecmascript-binding')
41 files changed, 2455 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/allow-resizable.html b/testing/web-platform/tests/webidl/ecmascript-binding/allow-resizable.html new file mode 100644 index 0000000000..54daa57bce --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/allow-resizable.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/sab.js"></script> +<script type="module"> +test(t => { + // Fixed-length ABs should not throw + const ab = new ArrayBuffer(16); + new Response(new Uint8Array(ab)); + + const rab = new ArrayBuffer(16, { maxByteLength: 1024 }); + // Response doesn't have [AllowResizable] or [AllowShared] + assert_throws_js(TypeError, () => { + new Response(new Uint8Array(rab)); + }); +}, "APIs without [AllowResizable] throw when passed resizable ArrayBuffers"); + +test(t => { + const enc = new TextEncoder(); + + // Fixed-length SABs should not throw + const sab = createBuffer('SharedArrayBuffer', 16); + enc.encodeInto("foobar", new Uint8Array(sab)); + + const gsab = createBuffer('SharedArrayBuffer', 16, { maxByteLength: 1024 }); + // TextEncoder.encodeInto doesn't have [AllowResizable] but has [AllowShared] + assert_throws_js(TypeError, () => { + enc.encodeInto("foobar", new Uint8Array(gsab)); + }); +}, "APIs with [AllowShared] but without [AllowResizable] throw when passed growable SharedArrayBuffers"); +</script> diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/attributes-accessors-unique-function-objects.html b/testing/web-platform/tests/webidl/ecmascript-binding/attributes-accessors-unique-function-objects.html new file mode 100644 index 0000000000..167f55bcef --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/attributes-accessors-unique-function-objects.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>All attributes accessors are unique function objects</title> +<link rel="help" href="https://webidl.spec.whatwg.org/#idl-interface-mixins"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +"use strict"; + +test(() => { + const seenPrototypes = new WeakSet([Function.prototype]); + const seenFunctions = new WeakMap(); + + for (const windowKey of Object.getOwnPropertyNames(window)) { + const windowValue = window[windowKey]; + if (typeof windowValue !== "function") continue; + + const {prototype} = windowValue; + if (!prototype || seenPrototypes.has(prototype)) continue; + seenPrototypes.add(prototype); + + for (const key of Object.getOwnPropertyNames(prototype)) { + const assert_not_seen = (fn, field) => { + const fnInfo = `${windowKey}.${key}.${field}`; + assert_equals(seenFunctions.get(fn), undefined, fnInfo); + seenFunctions.set(fn, fnInfo); + }; + + const desc = Object.getOwnPropertyDescriptor(prototype, key); + if (desc.get) assert_not_seen(desc.get, "[[Get]]"); + if (desc.set) assert_not_seen(desc.set, "[[Set]]"); + } + } +}, "For attributes, each copy of the accessor property has distinct built-in function objects for its getters and setters."); +</script> diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/builtin-function-properties.any.js b/testing/web-platform/tests/webidl/ecmascript-binding/builtin-function-properties.any.js new file mode 100644 index 0000000000..885bb441ea --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/builtin-function-properties.any.js @@ -0,0 +1,23 @@ +"use strict"; + +test(() => { + const ownPropKeys = Reflect.ownKeys(Blob).slice(0, 3); + assert_array_equals(ownPropKeys, ["length", "name", "prototype"]); +}, 'Constructor property enumeration order of "length", "name", and "prototype"'); + +test(() => { + assert_own_property(Blob.prototype, "slice"); + + const ownPropKeys = Reflect.ownKeys(Blob.prototype.slice).slice(0, 2); + assert_array_equals(ownPropKeys, ["length", "name"]); +}, 'Method property enumeration order of "length" and "name"'); + +test(() => { + assert_own_property(Blob.prototype, "size"); + + const desc = Reflect.getOwnPropertyDescriptor(Blob.prototype, "size"); + assert_equals(typeof desc.get, "function"); + + const ownPropKeys = Reflect.ownKeys(desc.get).slice(0, 2); + assert_array_equals(ownPropKeys, ["length", "name"]); +}, 'Getter property enumeration order of "length" and "name"'); diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/class-string-interface.any.js b/testing/web-platform/tests/webidl/ecmascript-binding/class-string-interface.any.js new file mode 100644 index 0000000000..ee792d5368 --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/class-string-interface.any.js @@ -0,0 +1,62 @@ +"use strict"; + +test(() => { + assert_own_property(Blob.prototype, Symbol.toStringTag); + + const propDesc = Object.getOwnPropertyDescriptor(Blob.prototype, Symbol.toStringTag); + assert_equals(propDesc.value, "Blob", "value"); + assert_equals(propDesc.configurable, true, "configurable"); + assert_equals(propDesc.enumerable, false, "enumerable"); + assert_equals(propDesc.writable, false, "writable"); +}, "@@toStringTag exists on the prototype with the appropriate descriptor"); + +test(() => { + assert_not_own_property(new Blob(), Symbol.toStringTag); +}, "@@toStringTag must not exist on the instance"); + +test(() => { + assert_equals(Object.prototype.toString.call(Blob.prototype), "[object Blob]"); +}, "Object.prototype.toString applied to the prototype"); + +test(() => { + assert_equals(Object.prototype.toString.call(new Blob()), "[object Blob]"); +}, "Object.prototype.toString applied to an instance"); + +test(t => { + assert_own_property(Blob.prototype, Symbol.toStringTag, "Precondition for this test: @@toStringTag on the prototype"); + + t.add_cleanup(() => { + Object.defineProperty(Blob.prototype, Symbol.toStringTag, { value: "Blob" }); + }); + + Object.defineProperty(Blob.prototype, Symbol.toStringTag, { value: "NotABlob" }); + assert_equals(Object.prototype.toString.call(Blob.prototype), "[object NotABlob]", "prototype"); + assert_equals(Object.prototype.toString.call(new Blob()), "[object NotABlob]", "instance"); +}, "Object.prototype.toString applied after modifying the prototype's @@toStringTag"); + +test(t => { + const instance = new Blob(); + assert_not_own_property(instance, Symbol.toStringTag, "Precondition for this test: no @@toStringTag on the instance"); + + Object.defineProperty(instance, Symbol.toStringTag, { value: "NotABlob" }); + assert_equals(Object.prototype.toString.call(instance), "[object NotABlob]"); +}, "Object.prototype.toString applied to the instance after modifying the instance's @@toStringTag"); + +// Chrome had a bug (https://bugs.chromium.org/p/chromium/issues/detail?id=793406) where if there +// was no @@toStringTag in the prototype, it would fall back to a magic class string. This tests +// that the bug is fixed. + +test(() => { + const instance = new Blob(); + Object.setPrototypeOf(instance, null); + + assert_equals(Object.prototype.toString.call(instance), "[object Object]"); +}, "Object.prototype.toString applied to a null-prototype instance"); + +// This test must be last. +test(() => { + delete Blob.prototype[Symbol.toStringTag]; + + assert_equals(Object.prototype.toString.call(Blob.prototype), "[object Object]", "prototype"); + assert_equals(Object.prototype.toString.call(new Blob()), "[object Object]", "instance"); +}, "Object.prototype.toString applied after deleting @@toStringTag"); diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/class-string-iterator-prototype-object.any.js b/testing/web-platform/tests/webidl/ecmascript-binding/class-string-iterator-prototype-object.any.js new file mode 100644 index 0000000000..f878eb7b35 --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/class-string-iterator-prototype-object.any.js @@ -0,0 +1,58 @@ +"use strict"; + +const iteratorProto = Object.getPrototypeOf((new URLSearchParams()).entries()); + +test(() => { + assert_own_property(iteratorProto, Symbol.toStringTag); + + const propDesc = Object.getOwnPropertyDescriptor(iteratorProto, Symbol.toStringTag); + assert_equals(propDesc.value, "URLSearchParams Iterator", "value"); + assert_equals(propDesc.configurable, true, "configurable"); + assert_equals(propDesc.enumerable, false, "enumerable"); + assert_equals(propDesc.writable, false, "writable"); +}, "@@toStringTag exists with the appropriate descriptor"); + +test(() => { + assert_equals(Object.prototype.toString.call(iteratorProto), "[object URLSearchParams Iterator]"); +}, "Object.prototype.toString"); + +test(t => { + assert_own_property(iteratorProto, Symbol.toStringTag, "Precondition for this test: @@toStringTag exists"); + + t.add_cleanup(() => { + Object.defineProperty(iteratorProto, Symbol.toStringTag, { value: "URLSearchParams Iterator" }); + }); + + Object.defineProperty(iteratorProto, Symbol.toStringTag, { value: "Not URLSearchParams Iterator" }); + assert_equals(Object.prototype.toString.call(iteratorProto), "[object Not URLSearchParams Iterator]"); +}, "Object.prototype.toString applied after modifying @@toStringTag"); + +// Chrome had a bug (https://bugs.chromium.org/p/chromium/issues/detail?id=793406) where if there +// was no @@toStringTag, it would fall back to a magic class string. This tests that the bug is +// fixed. + +test(() => { + const iterator = (new URLSearchParams()).keys(); + assert_equals(Object.prototype.toString.call(iterator), "[object URLSearchParams Iterator]"); + + Object.setPrototypeOf(iterator, null); + assert_equals(Object.prototype.toString.call(iterator), "[object Object]"); +}, "Object.prototype.toString applied to a null-prototype instance"); + +test(t => { + const proto = Object.getPrototypeOf(iteratorProto); + t.add_cleanup(() => { + Object.setPrototypeOf(iteratorProto, proto); + }); + + Object.setPrototypeOf(iteratorProto, null); + + assert_equals(Object.prototype.toString.call(iteratorProto), "[object URLSearchParams Iterator]"); +}, "Object.prototype.toString applied after nulling the prototype"); + +// This test must be last. +test(() => { + delete iteratorProto[Symbol.toStringTag]; + + assert_equals(Object.prototype.toString.call(iteratorProto), "[object Object]", "prototype"); +}, "Object.prototype.toString applied after deleting @@toStringTag"); diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/class-string-named-properties-object.window.js b/testing/web-platform/tests/webidl/ecmascript-binding/class-string-named-properties-object.window.js new file mode 100644 index 0000000000..a427a2f814 --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/class-string-named-properties-object.window.js @@ -0,0 +1,23 @@ +"use strict"; + +const namedPropertiesObject = Object.getPrototypeOf(Window.prototype); + +test(() => { + assert_own_property(namedPropertiesObject, Symbol.toStringTag); + + const propDesc = Object.getOwnPropertyDescriptor(namedPropertiesObject, Symbol.toStringTag); + assert_equals(propDesc.value, "WindowProperties", "value"); + assert_equals(propDesc.configurable, true, "configurable"); + assert_equals(propDesc.enumerable, false, "enumerable"); + assert_equals(propDesc.writable, false, "writable"); +}, "@@toStringTag exists with the appropriate descriptor"); + +test(() => { + assert_equals(Object.prototype.toString.call(namedPropertiesObject), "[object WindowProperties]"); +}, "Object.prototype.toString"); + +// Chrome had a bug (https://bugs.chromium.org/p/chromium/issues/detail?id=793406) where if there +// was no @@toStringTag, it would fall back to a magic class string. Tests for this are present in +// the sibling class-string*.any.js tests. However, the named properties object always fails calls +// to [[DefineOwnProperty]] or [[SetPrototypeOf]] per the Web IDL spec, so there is no way to +// trigger the buggy behavior for it. diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/constructors.html b/testing/web-platform/tests/webidl/ecmascript-binding/constructors.html new file mode 100644 index 0000000000..61993a6200 --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/constructors.html @@ -0,0 +1,132 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Realm for constructed objects</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id=log></div> +<script> +function object_realm(dp) { + // Note that browsers use the URL of the relevant global object's associated + // Document. + // https://github.com/w3c/DOM-Parsing/issues/46 + var url = DOMParser.prototype.parseFromString.call(dp, "x", "text/html").documentURI; + var file = url.slice(url.lastIndexOf("/") + 1); + switch (file) { + case "constructors.html": + return "parent window"; + case "constructors-support.html": + return "child window"; + default: + return "???"; + } +} + +async_test(function() { + var iframe = document.createElement("iframe"); + iframe.onload = this.step_func_done(function() { + var child = iframe.contentWindow; + test(function() { + var dp = new DOMParser(); + assert_equals(Object.getPrototypeOf(dp), DOMParser.prototype); + assert_equals(object_realm(dp), "parent window"); + }, "Normal constructor in parent window"); + + test(function() { + var dp = new child.DOMParser(); + assert_equals(Object.getPrototypeOf(dp), child.DOMParser.prototype); + assert_equals(object_realm(dp), "child window"); + }, "Normal constructor in child window"); + + test(function() { + var dp = Reflect.construct(child.DOMParser, [], DOMParser); + assert_equals(Object.getPrototypeOf(dp), DOMParser.prototype); + assert_equals(object_realm(dp), "child window"); + }, "Constructor in child window with normal NewTarget from parent window"); + + test(function() { + var dp = Reflect.construct(DOMParser, [], child.DOMParser); + assert_equals(Object.getPrototypeOf(dp), child.DOMParser.prototype); + assert_equals(object_realm(dp), "parent window"); + }, "Constructor in parent window with normal NewTarget from child window"); + + test(function() { + class DOMParserSubclass extends DOMParser {} + var dp = new DOMParserSubclass(); + assert_equals(Object.getPrototypeOf(dp), DOMParserSubclass.prototype); + assert_equals(object_realm(dp), "parent window"); + }, "Subclass constructor in parent window"); + + test(function() { + var dp = new child.DOMParserSubclass(); + assert_equals(Object.getPrototypeOf(dp), child.DOMParserSubclass.prototype); + assert_equals(object_realm(dp), "child window"); + }, "Subclass constructor in child window"); + + test(function() { + class ForeignDOMParserSubclass extends child.DOMParser {} + var dp = new ForeignDOMParserSubclass(); + assert_equals(Object.getPrototypeOf(dp), ForeignDOMParserSubclass.prototype); + assert_equals(object_realm(dp), "child window"); + }, "Subclass constructor in parent window with parent class in child window"); + + test(function() { + var dp = new child.ForeignDOMParserSubclass(); + assert_equals(Object.getPrototypeOf(dp), child.ForeignDOMParserSubclass.prototype); + assert_equals(object_realm(dp), "parent window"); + }, "Subclass constructor in child window with parent class in parent window"); + + test(function() { + var badNewTarget = function() {}; + badNewTarget.prototype = 7; + + var dp = Reflect.construct(child.DOMParser, [], badNewTarget); + assert_equals(Object.getPrototypeOf(dp), DOMParser.prototype); + assert_equals(object_realm(dp), "child window"); + }, "Constructor in child window with bad NewTarget from parent window"); + + test(function() { + var dp = Reflect.construct(DOMParser, [], child.badNewTarget); + assert_equals(Object.getPrototypeOf(dp), child.DOMParser.prototype); + assert_equals(object_realm(dp), "parent window"); + }, "Constructor in parent window with bad NewTarget from child window"); + + test(function() { + var badNewTarget = Function.prototype.bind.call(new child.Function()); + badNewTarget.prototype = 7; + + var dp = Reflect.construct(DOMParser, [], badNewTarget); + assert_equals(Object.getPrototypeOf(dp), child.DOMParser.prototype); + assert_equals(object_realm(dp), "parent window"); + }, "Constructor in parent window with bad NewTarget from parent window that's a bound child window function"); + + test(function() { + var badNewTarget = child.Function.prototype.bind.call(new Function()); + badNewTarget.prototype = 7; + + var dp = Reflect.construct(child.DOMParser, [], badNewTarget); + assert_equals(Object.getPrototypeOf(dp), DOMParser.prototype); + assert_equals(object_realm(dp), "child window"); + }, "Constructor in child window with bad NewTarget from child window that's a bound parent window function"); + + test(function() { + var badNewTarget = new Proxy(new child.Function(), {}); + badNewTarget.prototype = 7; + + var dp = Reflect.construct(DOMParser, [], badNewTarget); + assert_equals(Object.getPrototypeOf(dp), child.DOMParser.prototype); + assert_equals(object_realm(dp), "parent window"); + }, "Constructor in parent window with bad NewTarget from parent window that's a proxy for a child window function"); + + test(function() { + var badNewTarget = new child.Proxy(new Function(), {}); + badNewTarget.prototype = 7; + + var dp = Reflect.construct(child.DOMParser, [], badNewTarget); + assert_equals(Object.getPrototypeOf(dp), DOMParser.prototype); + assert_equals(object_realm(dp), "child window"); + }, "Constructor in child window with bad NewTarget from child window that's a proxy for a parent window function"); + }); + iframe.src = "support/constructors-support.html"; + document.body.appendChild(iframe); +}); +</script> diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/default-iterator-object.html b/testing/web-platform/tests/webidl/ecmascript-binding/default-iterator-object.html new file mode 100644 index 0000000000..c7e9188521 --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/default-iterator-object.html @@ -0,0 +1,27 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Default iterator objects</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +test(() => { + const iterator1 = new URLSearchParams()[Symbol.iterator](); + const iterator2 = new URLSearchParams().keys(); + const iterator3 = new URLSearchParams().values(); + const iterator4 = new URLSearchParams().entries(); + assert_equals(Object.getPrototypeOf(iterator1), Object.getPrototypeOf(iterator2)); + assert_equals(Object.getPrototypeOf(iterator1), Object.getPrototypeOf(iterator3)); + assert_equals(Object.getPrototypeOf(iterator1), Object.getPrototypeOf(iterator4)); +}, "Default iterator objects for an interface have the same prototype"); + +test(() => { + const iterator = new URLSearchParams().entries(); + assert_equals(Object.prototype.toString.call(iterator), "[object URLSearchParams Iterator]"); +}, "Object.prototype.toString returns correct value"); + +test(() => { + const iterator = new URLSearchParams().entries(); + assert_equals(iterator[Symbol.toStringTag], "URLSearchParams Iterator"); + assert_equals(Object.getOwnPropertyDescriptor(iterator, Symbol.toStringTag), undefined); +}, "@@toStringTag has correct value from prototype"); +</script> diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/default-toJSON-cross-realm.html b/testing/web-platform/tests/webidl/ecmascript-binding/default-toJSON-cross-realm.html new file mode 100644 index 0000000000..79c3097f33 --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/default-toJSON-cross-realm.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Cross-realm [Default] toJSON() creates result object in its realm</title> +<link rel="help" href="https://webidl.spec.whatwg.org/#default-tojson-steps"> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="support/create-realm.js"></script> + +<body> +<script> +promise_test(async t => { + const other = await createRealm(t); + const json = other.DOMRectReadOnly.prototype.toJSON.call(new DOMRectReadOnly()); + + assert_equals(Object.getPrototypeOf(json), other.Object.prototype); +}, "Cross-realm [Default] toJSON() creates result object in its realm"); + +promise_test(async t => { + const other = await createRealm(t); + const json = other.DOMQuad.prototype.toJSON.call(new DOMQuad()); + + assert_equals(Object.getPrototypeOf(json.p1), DOMPoint.prototype); +}, "Cross-realm [Default] toJSON() converts its interface attributes to ECMAScript values of correct realm"); +</script> +</body> diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/DOMException-constants.any.js b/testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/DOMException-constants.any.js new file mode 100644 index 0000000000..bb846a494e --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/DOMException-constants.any.js @@ -0,0 +1,51 @@ +'use strict'; + +test(function() { + // https://www.w3.org/Bugs/Public/show_bug.cgi?id=27732 + var constants = [ + "INDEX_SIZE_ERR", + "DOMSTRING_SIZE_ERR", + "HIERARCHY_REQUEST_ERR", + "WRONG_DOCUMENT_ERR", + "INVALID_CHARACTER_ERR", + "NO_DATA_ALLOWED_ERR", + "NO_MODIFICATION_ALLOWED_ERR", + "NOT_FOUND_ERR", + "NOT_SUPPORTED_ERR", + "INUSE_ATTRIBUTE_ERR", + "INVALID_STATE_ERR", + "SYNTAX_ERR", + "INVALID_MODIFICATION_ERR", + "NAMESPACE_ERR", + "INVALID_ACCESS_ERR", + "VALIDATION_ERR", + "TYPE_MISMATCH_ERR", + "SECURITY_ERR", + "NETWORK_ERR", + "ABORT_ERR", + "URL_MISMATCH_ERR", + "QUOTA_EXCEEDED_ERR", + "TIMEOUT_ERR", + "INVALID_NODE_TYPE_ERR", + "DATA_CLONE_ERR" + ] + var objects = [ + [DOMException, "DOMException constructor object"], + [DOMException.prototype, "DOMException prototype object"] + ] + constants.forEach(function(name, i) { + objects.forEach(function(o) { + var object = o[0], description = o[1]; + test(function() { + assert_equals(object[name], i + 1, name) + assert_own_property(object, name) + var pd = Object.getOwnPropertyDescriptor(object, name) + assert_false("get" in pd, "get") + assert_false("set" in pd, "set") + assert_false(pd.writable, "writable") + assert_true(pd.enumerable, "enumerable") + assert_false(pd.configurable, "configurable") + }, "Constant " + name + " on " + description) + }) + }) +}) diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/DOMException-constructor-and-prototype.any.js b/testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/DOMException-constructor-and-prototype.any.js new file mode 100644 index 0000000000..a015470cad --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/DOMException-constructor-and-prototype.any.js @@ -0,0 +1,32 @@ +test(function() { + assert_own_property(self, "DOMException", "property of global"); + + var desc = Object.getOwnPropertyDescriptor(self, "DOMException"); + assert_false("get" in desc, "get"); + assert_false("set" in desc, "set"); + assert_true(desc.writable, "writable"); + assert_false(desc.enumerable, "enumerable"); + assert_true(desc.configurable, "configurable"); +}, "existence and property descriptor of DOMException"); + +test(function() { + assert_own_property(self.DOMException, "prototype", "prototype property"); + + var desc = Object.getOwnPropertyDescriptor(self.DOMException, "prototype"); + assert_false("get" in desc, "get"); + assert_false("set" in desc, "set"); + assert_false(desc.writable, "writable"); + assert_false(desc.enumerable, "enumerable"); + assert_false(desc.configurable, "configurable"); +}, "existence and property descriptor of DOMException.prototype"); + +test(function() { + assert_own_property(self.DOMException.prototype, "constructor", "property of prototype"); + var desc = Object.getOwnPropertyDescriptor(self.DOMException.prototype, "constructor"); + assert_false("get" in desc, "get"); + assert_false("set" in desc, "set"); + assert_true(desc.writable, "writable"); + assert_false(desc.enumerable, "enumerable"); + assert_true(desc.configurable, "configurable"); + assert_equals(self.DOMException.prototype.constructor, self.DOMException, "equality with actual constructor"); +}, "existence and property descriptor of DOMException.prototype.constructor"); diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/DOMException-constructor-behavior.any.js b/testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/DOMException-constructor-behavior.any.js new file mode 100644 index 0000000000..e9917af228 --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/DOMException-constructor-behavior.any.js @@ -0,0 +1,140 @@ +'use strict'; + +test(function() { + var ex = new DOMException(); + assert_equals(ex.name, "Error", + "Not passing a name should end up with 'Error' as the name"); + assert_equals(ex.message, "", + "Not passing a message should end up with empty string as the message"); +}, 'new DOMException()'); + +test(function() { + var ex = new DOMException(); + assert_false(ex.hasOwnProperty("name"), + "The name property should be inherited"); + assert_false(ex.hasOwnProperty("message"), + "The message property should be inherited"); +}, 'new DOMException(): inherited-ness'); + +test(function() { + var ex = new DOMException(null); + assert_equals(ex.name, "Error", + "Not passing a name should end up with 'Error' as the name"); + assert_equals(ex.message, "null", + "Passing null as message should end up with stringified 'null' as the message"); +}, 'new DOMException(null)'); + +test(function() { + var ex = new DOMException(undefined); + assert_equals(ex.name, "Error", + "Not passing a name should end up with 'Error' as the name"); + assert_equals(ex.message, "", + "Not passing a message should end up with empty string as the message"); +}, 'new DOMException(undefined)'); + +test(function() { + var ex = new DOMException(undefined); + assert_false(ex.hasOwnProperty("name"), + "The name property should be inherited"); + assert_false(ex.hasOwnProperty("message"), + "The message property should be inherited"); +}, 'new DOMException(undefined): inherited-ness'); + +test(function() { + var ex = new DOMException("foo"); + assert_equals(ex.name, "Error", + "Not passing a name should still end up with 'Error' as the name"); + assert_equals(ex.message, "foo", "Should be using passed-in message"); +}, 'new DOMException("foo")'); + +test(function() { + var ex = new DOMException("foo"); + assert_false(ex.hasOwnProperty("name"), + "The name property should be inherited"); + assert_false(ex.hasOwnProperty("message"), + "The message property should be inherited"); +}, 'new DOMException("foo"): inherited-ness'); + +test(function() { + var ex = new DOMException("bar", undefined); + assert_equals(ex.name, "Error", + "Passing undefined for name should end up with 'Error' as the name"); + assert_equals(ex.message, "bar", "Should still be using passed-in message"); +}, 'new DOMException("bar", undefined)'); + +test(function() { + var ex = new DOMException("bar", "NotSupportedError"); + assert_equals(ex.name, "NotSupportedError", "Should be using the passed-in name"); + assert_equals(ex.message, "bar", "Should still be using passed-in message"); + assert_equals(ex.code, DOMException.NOT_SUPPORTED_ERR, + "Should have the right exception code"); +}, 'new DOMException("bar", "NotSupportedError")'); + +test(function() { + var ex = new DOMException("bar", "NotSupportedError"); + assert_false(ex.hasOwnProperty("name"), + "The name property should be inherited"); + assert_false(ex.hasOwnProperty("message"), + "The message property should be inherited"); +}, 'new DOMException("bar", "NotSupportedError"): inherited-ness'); + +test(function() { + var ex = new DOMException("bar", "foo"); + assert_equals(ex.name, "foo", "Should be using the passed-in name"); + assert_equals(ex.message, "bar", "Should still be using passed-in message"); + assert_equals(ex.code, 0, + "Should have 0 for code for a name not in the exception names table"); +}, 'new DOMException("bar", "foo")'); + +[ + {name: "IndexSizeError", code: 1}, + {name: "HierarchyRequestError", code: 3}, + {name: "WrongDocumentError", code: 4}, + {name: "InvalidCharacterError", code: 5}, + {name: "NoModificationAllowedError", code: 7}, + {name: "NotFoundError", code: 8}, + {name: "NotSupportedError", code: 9}, + {name: "InUseAttributeError", code: 10}, + {name: "InvalidStateError", code: 11}, + {name: "SyntaxError", code: 12}, + {name: "InvalidModificationError", code: 13}, + {name: "NamespaceError", code: 14}, + {name: "InvalidAccessError", code: 15}, + {name: "TypeMismatchError", code: 17}, + {name: "SecurityError", code: 18}, + {name: "NetworkError", code: 19}, + {name: "AbortError", code: 20}, + {name: "URLMismatchError", code: 21}, + {name: "QuotaExceededError", code: 22}, + {name: "TimeoutError", code: 23}, + {name: "InvalidNodeTypeError", code: 24}, + {name: "DataCloneError", code: 25}, + + // These were removed from the error names table. + // See https://github.com/heycam/webidl/pull/946. + {name: "DOMStringSizeError", code: 0}, + {name: "NoDataAllowedError", code: 0}, + {name: "ValidationError", code: 0}, + + // The error names which don't have legacy code values. + {name: "EncodingError", code: 0}, + {name: "NotReadableError", code: 0}, + {name: "UnknownError", code: 0}, + {name: "ConstraintError", code: 0}, + {name: "DataError", code: 0}, + {name: "TransactionInactiveError", code: 0}, + {name: "ReadOnlyError", code: 0}, + {name: "VersionError", code: 0}, + {name: "OperationError", code: 0}, + {name: "NotAllowedError", code: 0} +].forEach(function(test_case) { + test(function() { + var ex = new DOMException("msg", test_case.name); + assert_equals(ex.name, test_case.name, + "Should be using the passed-in name"); + assert_equals(ex.message, "msg", + "Should be using the passed-in message"); + assert_equals(ex.code, test_case.code, + "Should have matching legacy code from error names table"); + },'new DOMexception("msg", "' + test_case.name + '")'); +}); diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/DOMException-custom-bindings.any.js b/testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/DOMException-custom-bindings.any.js new file mode 100644 index 0000000000..cd4e5b6341 --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/DOMException-custom-bindings.any.js @@ -0,0 +1,120 @@ +"use strict"; + +test(() => { + assert_throws_js(TypeError, () => DOMException()); +}, "Cannot construct without new"); + +test(() => { + assert_equals(Object.getPrototypeOf(DOMException.prototype), Error.prototype); +}, "inherits from Error: prototype-side"); + +test(() => { + assert_equals(Object.getPrototypeOf(DOMException), Function.prototype); +}, "does not inherit from Error: class-side"); + +test(() => { + const e = new DOMException("message", "name"); + assert_false(e.hasOwnProperty("message"), "property is not own"); + + const propDesc = Object.getOwnPropertyDescriptor(DOMException.prototype, "message"); + assert_equals(typeof propDesc.get, "function", "property descriptor is a getter"); + assert_equals(propDesc.set, undefined, "property descriptor is not a setter"); + assert_true(propDesc.enumerable, "property descriptor enumerable"); + assert_true(propDesc.configurable, "property descriptor configurable"); +}, "message property descriptor"); + +test(() => { + const getter = Object.getOwnPropertyDescriptor(DOMException.prototype, "message").get; + + assert_throws_js(TypeError, () => getter.apply({})); +}, "message getter performs brand checks (i.e. is not [LegacyLenientThis])"); + +test(() => { + const e = new DOMException("message", "name"); + assert_false(e.hasOwnProperty("name"), "property is not own"); + + const propDesc = Object.getOwnPropertyDescriptor(DOMException.prototype, "name"); + assert_equals(typeof propDesc.get, "function", "property descriptor is a getter"); + assert_equals(propDesc.set, undefined, "property descriptor is not a setter"); + assert_true(propDesc.enumerable, "property descriptor enumerable"); + assert_true(propDesc.configurable, "property descriptor configurable"); +}, "name property descriptor"); + +test(() => { + const getter = Object.getOwnPropertyDescriptor(DOMException.prototype, "name").get; + + assert_throws_js(TypeError, () => getter.apply({})); +}, "name getter performs brand checks (i.e. is not [LegacyLenientThis])"); + +test(() => { + const e = new DOMException("message", "name"); + assert_false(e.hasOwnProperty("code"), "property is not own"); + + const propDesc = Object.getOwnPropertyDescriptor(DOMException.prototype, "code"); + assert_equals(typeof propDesc.get, "function", "property descriptor is a getter"); + assert_equals(propDesc.set, undefined, "property descriptor is not a setter"); + assert_true(propDesc.enumerable, "property descriptor enumerable"); + assert_true(propDesc.configurable, "property descriptor configurable"); +}, "code property descriptor"); + +test(() => { + const getter = Object.getOwnPropertyDescriptor(DOMException.prototype, "code").get; + + assert_throws_js(TypeError, () => getter.apply({})); +}, "code getter performs brand checks (i.e. is not [LegacyLenientThis])"); + +test(() => { + const e = new DOMException("message", "InvalidCharacterError"); + assert_equals(e.code, 5, "Initially the code is set to 5"); + + Object.defineProperty(e, "name", { + value: "WrongDocumentError" + }); + + assert_equals(e.code, 5, "The code is still set to 5"); +}, "code property is not affected by shadowing the name property"); + +test(() => { + const e = new DOMException("message", "name"); + assert_equals(Object.prototype.toString.call(e), "[object DOMException]"); +}, "Object.prototype.toString behavior is like other interfaces"); + +test(() => { + const e = new DOMException("message", "name"); + assert_false(e.hasOwnProperty("toString"), "toString must not exist on the instance"); + assert_false(DOMException.prototype.hasOwnProperty("toString"), "toString must not exist on DOMException.prototype"); + assert_equals(typeof e.toString, "function", "toString must still exist (via Error.prototype)"); +}, "Inherits its toString() from Error.prototype"); + +test(() => { + const e = new DOMException("message", "name"); + assert_equals(e.toString(), "name: message", + "The default Error.prototype.toString() behavior must work on supplied name and message"); + + Object.defineProperty(e, "name", { value: "new name" }); + Object.defineProperty(e, "message", { value: "new message" }); + assert_equals(e.toString(), "new name: new message", + "The default Error.prototype.toString() behavior must work on shadowed names and messages"); +}, "toString() behavior from Error.prototype applies as expected"); + +test(() => { + assert_throws_js(TypeError, () => DOMException.prototype.toString()); +}, "DOMException.prototype.toString() applied to DOMException.prototype throws because of name/message brand checks"); + +test(() => { + let stackOnNormalErrors; + try { + throw new Error("normal error"); + } catch (e) { + stackOnNormalErrors = e.stack; + } + + let stackOnDOMException; + try { + throw new DOMException("message", "name"); + } catch (e) { + stackOnDOMException = e.stack; + } + + assert_equals(typeof stackOnDOMException, typeof stackOnNormalErrors, "The typeof values must match"); +}, "If the implementation has a stack property on normal errors, it also does on DOMExceptions"); diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/exceptions.html b/testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/exceptions.html new file mode 100644 index 0000000000..d26c662669 --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/exceptions.html @@ -0,0 +1,78 @@ +<!doctype html> +<meta charset=utf-8> +<title>DOMException-throwing tests</title> +<link rel=author title="Aryeh Gregor" href=ayg@aryeh.name> +<div id=log></div> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script> +/** + * This file just picks one case where browsers are supposed to throw an + * exception, and tests the heck out of whether it meets the spec. In the + * future, all these checks should be in assert_throws_dom(), but we don't want + * every browser failing every assert_throws_dom() check until they fix every + * single bug in their exception-throwing. + * + * We don't go out of our way to test everything that's already tested by + * interfaces.html, like whether all constants are present on the object, but + * some things are duplicated. + */ +setup({explicit_done: true}); + +function testException(exception, global, desc) { + test(function() { + assert_equals(global.Object.getPrototypeOf(exception), + global.DOMException.prototype); + }, desc + "Object.getPrototypeOf(exception) === DOMException.prototype"); + + + test(function() { + assert_false(exception.hasOwnProperty("name")); + }, desc + "exception.hasOwnProperty(\"name\")"); + test(function() { + assert_false(exception.hasOwnProperty("message")); + }, desc + "exception.hasOwnProperty(\"message\")"); + + test(function() { + assert_equals(exception.name, "HierarchyRequestError"); + }, desc + "exception.name === \"HierarchyRequestError\""); + + test(function() { + assert_equals(exception.code, global.DOMException.HIERARCHY_REQUEST_ERR); + }, desc + "exception.code === DOMException.HIERARCHY_REQUEST_ERR"); + + test(function() { + assert_equals(global.Object.prototype.toString.call(exception), + "[object DOMException]"); + }, desc + "Object.prototype.toString.call(exception) === \"[object DOMException]\""); +} + + +// Test in current window +var exception = null; +try { + // This should throw a HierarchyRequestError in every browser since the + // Stone Age, so we're really only testing exception-throwing details. + document.documentElement.appendChild(document); +} catch(e) { + exception = e; +} +testException(exception, window, ""); + +// Test in iframe +var iframe = document.createElement("iframe"); +iframe.src = "about:blank"; +iframe.onload = function() { + var exception = null; + try { + iframe.contentDocument.documentElement.appendChild(iframe.contentDocument); + } catch(e) { + exception = e; + } + testException(exception, iframe.contentWindow, "In iframe: "); + + document.body.removeChild(iframe); + done(); +}; +document.body.appendChild(iframe); +</script> diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/global-immutable-prototype.any.js b/testing/web-platform/tests/webidl/ecmascript-binding/global-immutable-prototype.any.js new file mode 100644 index 0000000000..6291c3ae93 --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/global-immutable-prototype.any.js @@ -0,0 +1,25 @@ +// META: global=window,worker +// META: title=Immutability of the global prototype chain + +const objects = []; +setup(() => { + for (let object = self; object; object = Object.getPrototypeOf(object)) { + objects.push(object); + } +}); + +test(() => { + for (const object of objects) { + assert_throws_js(TypeError, () => { + Object.setPrototypeOf(object, {}); + }); + } +}, "Setting to a different prototype"); + +test(() => { + for (const object of objects) { + const expected = Object.getPrototypeOf(object); + Object.setPrototypeOf(object, expected); + assert_equals(Object.getPrototypeOf(object), expected); + } +}, "Setting to the same prototype"); diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/global-object-implicit-this-value-cross-realm.html b/testing/web-platform/tests/webidl/ecmascript-binding/global-object-implicit-this-value-cross-realm.html new file mode 100644 index 0000000000..b9939b801c --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/global-object-implicit-this-value-cross-realm.html @@ -0,0 +1,97 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Cross-realm getter / setter / operation doesn't use lexical global object if |this| value is incompatible object / null / undefined</title> +<link rel="help" href="https://webidl.spec.whatwg.org/#dfn-attribute-getter"> +<link rel="help" href="https://webidl.spec.whatwg.org/#dfn-attribute-setter"> +<link rel="help" href="https://webidl.spec.whatwg.org/#dfn-create-operation-function"> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="support/create-realm.js"></script> + +<body> +<script> +promise_test(async t => { + const other = await createRealm(t); + const notWindow = Object.create(Object.getPrototypeOf(other)); + + assert_throws_js(other.TypeError, () => { Object.create(other).window; }); + assert_throws_js(other.TypeError, () => { Object.getOwnPropertyDescriptor(other, "history").get.call(notWindow); }); + assert_throws_js(other.TypeError, () => { Reflect.get(other, "screen", notWindow); }); + assert_throws_js(other.TypeError, () => { new Proxy(other, {}).onclick; }); +}, "Cross-realm global object's getter throws when called on incompatible object"); + +promise_test(async t => { + const other = await createRealm(t); + const notWindow = Object.create(Object.getPrototypeOf(other)); + + assert_throws_js(other.TypeError, () => { Object.create(other).name = "dummy"; }); + assert_throws_js(other.TypeError, () => { Object.getOwnPropertyDescriptor(other, "status").set.call(notWindow, other.status); }); + // parent is [Replaceable] + assert_throws_js(other.TypeError, () => { Reflect.set(other, "parent", window, notWindow); }); + assert_throws_js(other.TypeError, () => { new Proxy(other, {}).location = location; }); +}, "Cross-realm global object's setter throws when called on incompatible object"); + +promise_test(async t => { + const other = await createRealm(t); + const notWindow = Object.create(Object.getPrototypeOf(other)); + + assert_throws_js(other.TypeError, () => { Object.create(other).focus(); }); + assert_throws_js(other.TypeError, () => { other.clearInterval.call(notWindow, 0); }); + assert_throws_js(other.TypeError, () => { Reflect.apply(other.blur, notWindow, []); }); + assert_throws_js(other.TypeError, () => { new Proxy(other, {}).removeEventListener("foo", () => {}); }); +}, "Cross-realm global object's operation throws when called on incompatible object"); + +promise_test(async t => { + const other = await createRealm(t); + const otherNameGetter = Object.getOwnPropertyDescriptor(other, "name").get; + + assert_equals(Reflect.get(other, "self", null), other); + assert_equals(Reflect.get(other, "document", undefined), other.document); + assert_equals(otherNameGetter.call(null), "dummy"); + // An engine might have different code path for calling a function from outer scope to implement step 1.b.iii of https://tc39.es/ecma262/#sec-evaluatecall + assert_equals((() => otherNameGetter())(), "dummy"); +}, "Cross-realm global object's getter called on null / undefined"); + +promise_test(async t => { + const other = await createRealm(t); + const otherLocationSetter = Object.getOwnPropertyDescriptor(other, "location").set; + const otherHref = other.location.href; + const newSelf = {}; + + // self is [Replaceable] + assert_true(Reflect.set(other, "self", newSelf, null)); + assert_true(Reflect.set(other, "name", "newName", undefined)); + + otherLocationSetter.call(null, `${otherHref}#foo`); + assert_equals(other.location.hash, "#foo"); + // An engine might have different code path for calling a function from outer scope to implement step 1.b.iii of https://tc39.es/ecma262/#sec-evaluatecall + (() => { otherLocationSetter(`${otherHref}#bar`); })(); + assert_equals(other.location.hash, "#bar"); + + // Check these after calling "location" setter make sure no navigation has occurred + assert_equals(other.self, newSelf); + assert_equals(other.name, "newName"); +}, "Cross-realm global object's setter called on null / undefined"); + +promise_test(async t => { + const other = await createRealm(t); + const otherFocus = other.focus; + const otherDispatchEvent = other.dispatchEvent; + + assert_equals(document.activeElement, document.body); + // An engine might have different code path for calling a function from outer scope to implement step 1.b.iii of https://tc39.es/ecma262/#sec-evaluatecall + (() => { otherFocus(); })(); + assert_equals(document.activeElement.contentWindow, other); + + let caughtEvent; + other.addEventListener.call(null, "foo", event => { caughtEvent = event; }); + const dispatchedEvent = new other.Event("foo"); + assert_true(otherDispatchEvent(dispatchedEvent)); + assert_equals(caughtEvent, dispatchedEvent); + + const messagePromise = new EventWatcher(t, other, "message").wait_for("message"); + other.postMessage.call(null, "foo"); + assert_equals((await messagePromise).data, "foo"); +}, "Cross-realm global object's operation called on null / undefined"); +</script> diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/global-object-implicit-this-value.any.js b/testing/web-platform/tests/webidl/ecmascript-binding/global-object-implicit-this-value.any.js new file mode 100644 index 0000000000..4c159c6751 --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/global-object-implicit-this-value.any.js @@ -0,0 +1,85 @@ +// META: global=window,worker + +// https://webidl.spec.whatwg.org/#dfn-attribute-getter (step 1.1.2.1) +// https://webidl.spec.whatwg.org/#dfn-attribute-setter (step 4.5.1) +// https://webidl.spec.whatwg.org/#dfn-create-operation-function (step 2.1.2.1) + +const notGlobalObject = Object.create(Object.getPrototypeOf(globalThis)); + +test(() => { + assert_throws_js(TypeError, () => { Object.create(globalThis).self; }); + assert_throws_js(TypeError, () => { getGlobalPropertyDescriptor("location").get.call(notGlobalObject); }); + assert_throws_js(TypeError, () => { Reflect.get(globalThis, "navigator", notGlobalObject); }); + assert_throws_js(TypeError, () => { new Proxy(globalThis, {}).onerror; }); +}, "Global object's getter throws when called on incompatible object"); + +test(() => { + assert_throws_js(TypeError, () => { Object.create(globalThis).origin = origin; }); + assert_throws_js(TypeError, () => { getGlobalPropertyDescriptor("onerror").set.call(notGlobalObject, onerror); }); + assert_throws_js(TypeError, () => { Reflect.set(globalThis, "onoffline", onoffline, notGlobalObject); }); + assert_throws_js(TypeError, () => { new Proxy(globalThis, {}).ononline = ononline; }); +}, "Global object's setter throws when called on incompatible object"); + +test(() => { + assert_throws_js(TypeError, () => { Object.create(globalThis).setInterval(() => {}, 100); }); + assert_throws_js(TypeError, () => { clearTimeout.call(notGlobalObject, () => {}); }); + assert_throws_js(TypeError, () => { Reflect.apply(btoa, notGlobalObject, [""]); }); + assert_throws_js(TypeError, () => { new Proxy(globalThis, {}).removeEventListener("foo", () => {}); }); +}, "Global object's operation throws when called on incompatible object"); + +if (typeof document !== "undefined") { + test(() => { + assert_throws_js(TypeError, () => { Object.getOwnPropertyDescriptor(window, "document").get.call(document.all); }); + }, "Global object's getter throws when called on incompatible object (document.all)"); + + test(() => { + assert_throws_js(TypeError, () => { Object.getOwnPropertyDescriptor(window, "name").set.call(document.all); }); + }, "Global object's setter throws when called on incompatible object (document.all)"); + + test(() => { + assert_throws_js(TypeError, () => { focus.call(document.all); }); + }, "Global object's operation throws when called on incompatible object (document.all)"); +} + +// An engine might have different code path for calling a function from outer scope to implement step 1.b.iii of https://tc39.es/ecma262/#sec-evaluatecall +const locationGetter = getGlobalPropertyDescriptor("location").get; +test(() => { + assert_equals(getGlobalPropertyDescriptor("self").get.call(null), self); + assert_equals((() => locationGetter())(), location); + assert_equals(Reflect.get(globalThis, "origin", null), origin); + assert_equals(Reflect.get(globalThis, "onoffline", undefined), onoffline); +}, "Global object's getter works when called on null / undefined"); + +test(() => { + const fn = () => {}; + + // origin is [Replaceable] + getGlobalPropertyDescriptor("origin").set.call(null, "foo"); + assert_equals(origin, "foo"); + getGlobalPropertyDescriptor("onerror").set.call(undefined, fn); + assert_equals(onerror, fn); + assert_true(Reflect.set(globalThis, "onoffline", fn, null)); + assert_equals(onoffline, fn); + + const ononlineSetter = getGlobalPropertyDescriptor("ononline").set; + (() => { ononlineSetter(fn); })(); + assert_equals(ononline, fn); +}, "Global object's setter works when called on null / undefined"); + +// An engine might have different code path for calling a function from outer scope to implement step 1.b.iii of https://tc39.es/ecma262/#sec-evaluatecall +const __addEventListener = addEventListener; +test(() => { + assert_equals(atob.call(null, ""), ""); + assert_equals(typeof (0, setInterval)(() => {}, 100), "number"); + + (() => { __addEventListener("foo", event => { event.preventDefault(); }); })(); + const __dispatchEvent = dispatchEvent; + (() => { assert_false(__dispatchEvent(new Event("foo", { cancelable: true }))); })(); +}, "Global object's operation works when called on null / undefined"); + +function getGlobalPropertyDescriptor(key) { + for (let obj = globalThis; obj; obj = Object.getPrototypeOf(obj)) { + const desc = Object.getOwnPropertyDescriptor(obj, key); + if (desc) return desc; + } +} diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/has-instance.html b/testing/web-platform/tests/webidl/ecmascript-binding/has-instance.html new file mode 100644 index 0000000000..caf0be4729 --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/has-instance.html @@ -0,0 +1,26 @@ +<!doctype html> +<meta charset="utf-8"> +<title>instanceof behavior</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<iframe></iframe> + +<script> +test(function() { + var obj = Object.create(Element.prototype); + assert_true(obj instanceof Element); + assert_true(obj instanceof Node); + assert_false(obj instanceof Attr); +}, "Manually-constructed prototype chains are correctly handled by instanceof"); + +test(() => { + // This tests that the historical override of [[HasInstance]] was removed: + // https://github.com/heycam/webidl/pull/356 + assert_false(document.body instanceof frames[0].Element); +}, "instanceof must return false across different globals, for platform objects"); + +test(() => { + assert_false(EventTarget.hasOwnProperty(Symbol.hasInstance)); +}, "platform objects do not have Symbol.hasInstance installed"); +</script> diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/interface-object-set-receiver.html b/testing/web-platform/tests/webidl/ecmascript-binding/interface-object-set-receiver.html new file mode 100644 index 0000000000..ca75a96bba --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/interface-object-set-receiver.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>window.Interface is defined on [[Set]] receiver</title> +<link rel="help" href="https://webidl.spec.whatwg.org/#define-the-global-property-references"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +"use strict"; +const testValue = Object.freeze(function() {}); + +test(() => { + window.Window = testValue; + assert_false(window.propertyIsEnumerable("Window")); + + window.History = testValue; + assert_false(window.propertyIsEnumerable("History")); + + window.HTMLDivElement = testValue; + assert_false(window.propertyIsEnumerable("HTMLDivElement")) +}, "Direct [[Set]] preserves [[Enumerable]]: false property attribute"); + +test(() => { + const heir = Object.create(window); + + heir.Location = testValue; + assert_equals(heir.Location, testValue); + assert_not_equals(window.Location, testValue); + + heir.HTMLDocument = testValue; + assert_equals(heir.HTMLDocument, testValue); + assert_not_equals(window.HTMLDocument, testValue); + + heir.HTMLPreElement = testValue; + assert_equals(heir.HTMLPreElement, testValue); + assert_not_equals(window.HTMLPreElement, testValue); +}, "Prototype chain [[Set]] creates property on receiver"); +</script> diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/interface-object.html b/testing/web-platform/tests/webidl/ecmascript-binding/interface-object.html new file mode 100644 index 0000000000..132c61ddae --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/interface-object.html @@ -0,0 +1,28 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Interface objects</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +test(function () { + assert_equals(typeof window.Blob, "function") + delete window.Blob; + assert_equals(window.Blob, undefined); +}, "An interface object deleted after it has been accessed is undefined"); + +test(function () { + delete window.File; + assert_equals(window.File, undefined); +}, "An interface object deleted before it has been defined is undefined"); + +test(function () { + delete window.ImageData; + assert_equals(Object.getOwnPropertyDescriptor(window, "ImageData"), undefined); + delete window.ImageData; + assert_equals(Object.getOwnPropertyDescriptor(window, "ImageData"), undefined); +}, "Interface objects deleted multiple times stay deleted"); + +test(function () { + assert_equals(window["abc\udc88"], undefined); +}, "Fancy property names don't break the resolve hook on Window"); +</script> diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/interface-prototype-constructor-set-receiver.html b/testing/web-platform/tests/webidl/ecmascript-binding/interface-prototype-constructor-set-receiver.html new file mode 100644 index 0000000000..64a2da8eb2 --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/interface-prototype-constructor-set-receiver.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Interface.prototype.constructor is defined on [[Set]] receiver</title> +<link rel="help" href="https://webidl.spec.whatwg.org/#interface-prototype-object"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +"use strict"; +const testValue = Object.freeze(function() {}); + +test(() => { + Location.prototype.constructor = testValue; + assert_false(Location.prototype.propertyIsEnumerable("constructor")); + + HTMLDocument.prototype.constructor = testValue; + assert_false(HTMLDocument.prototype.propertyIsEnumerable("constructor")); + + HTMLDivElement.prototype.constructor = testValue; + assert_false(HTMLDivElement.prototype.propertyIsEnumerable("constructor")); +}, "Direct [[Set]] preserves [[Enumerable]]: false property attribute"); + +test(() => { + window.constructor = testValue; + assert_equals(window.constructor, testValue); + assert_equals(Window.prototype.constructor, Window); + + navigator.constructor = testValue; + assert_equals(navigator.constructor, testValue); + assert_equals(Navigator.prototype.constructor, Navigator); + + const span = document.createElement("span"); + span.constructor = testValue; + assert_equals(span.constructor, testValue); + assert_equals(HTMLSpanElement.prototype.constructor, HTMLSpanElement); +}, "Prototype chain [[Set]] creates property on receiver"); +</script> diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/interface-prototype-object.html b/testing/web-platform/tests/webidl/ecmascript-binding/interface-prototype-object.html new file mode 100644 index 0000000000..299bcf926d --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/interface-prototype-object.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Interface prototype objects</title> +<link rel="help" href="https://webidl.spec.whatwg.org/#interface-prototype-object"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +test(function() { + // https://webidl.spec.whatwg.org/#create-an-interface-prototype-object + assert_own_property(Element.prototype, Symbol.unscopables, "Element.prototype has @@unscopables."); + let unscopables = Element.prototype[Symbol.unscopables]; + assert_equals(typeof unscopables, "object", "@@unscopables is an Object."); + assert_equals(Object.getPrototypeOf(unscopables), null, "@@unscopables's prototype is null."); +}, "[Unscopable] extended attribute makes @@unscopables object on the prototype object, whose prototype is null."); +</script> diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/invalid-this-value-cross-realm.html b/testing/web-platform/tests/webidl/ecmascript-binding/invalid-this-value-cross-realm.html new file mode 100644 index 0000000000..0535115ac6 --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/invalid-this-value-cross-realm.html @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Cross-realm getter / setter / operation doesn't use lexical global object to throw an error for incompatible |this| value</title> +<link rel="help" href="https://webidl.spec.whatwg.org/#dfn-attribute-getter"> +<link rel="help" href="https://webidl.spec.whatwg.org/#dfn-attribute-setter"> +<link rel="help" href="https://webidl.spec.whatwg.org/#dfn-create-operation-function"> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="support/create-realm.js"></script> + +<body> +<script> +promise_test(async t => { + const other = await createRealm(t); + const notElement = Object.create(other.HTMLElement.prototype); + + assert_throws_js(other.TypeError, () => { Object.create(other.document).head; }); + assert_throws_js(other.TypeError, () => { Object.getOwnPropertyDescriptor(other.HTMLElement.prototype, "title").get.call(notElement); }); + assert_throws_js(other.TypeError, () => { Reflect.get(other.document.createElement("div"), "hidden", notElement); }); + assert_throws_js(other.TypeError, () => { new Proxy(other.text, {}).nodeType; }); +}, "Cross-realm getter throws when called on incompatible object"); + +promise_test(async t => { + const other = await createRealm(t); + const notElement = Object.create(other.HTMLElement.prototype); + const notText = Object.create(other.Text.prototype); + + assert_throws_js(other.TypeError, () => { Object.create(other.element).innerHTML = ""; }); + assert_throws_js(other.TypeError, () => { Object.getOwnPropertyDescriptor(other.HTMLElement.prototype, "onclick").set.call(notElement, null); }); + assert_throws_js(other.TypeError, () => { Reflect.set(new other.Text("foo"), "data", "foo", notText); }); + assert_throws_js(other.TypeError, () => { new Proxy(other.document, {}).title = ""; }); +}, "Cross-realm setter throws when called on incompatible object"); + +promise_test(async t => { + const other = await createRealm(t); + const notDocument = Object.create(other.HTMLDocument.prototype); + const notText = Object.create(other.Text.prototype); + + assert_throws_js(other.TypeError, () => { Object.create(other.element).click(); }); + assert_throws_js(other.TypeError, () => { other.document.querySelector.call(notDocument, "*"); }); + assert_throws_js(other.TypeError, () => { Reflect.apply(other.text.remove, notText, []); }); + assert_throws_js(other.TypeError, () => { new Proxy(other.document.createElement("a"), {}).addEventListener("foo", () => {}); }); +}, "Cross-realm operation throws when called on incompatible object"); +</script> diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/iterator-invalidation-foreach.html b/testing/web-platform/tests/webidl/ecmascript-binding/iterator-invalidation-foreach.html new file mode 100644 index 0000000000..9d2e3b9cb2 --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/iterator-invalidation-foreach.html @@ -0,0 +1,40 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Behavior of iterators when modified within foreach</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://webidl.spec.whatwg.org/#es-forEach"> +<link rel="author" title="Manish Goregaokar" href="mailto:manishsmail@gmail.com"> +<script> +test(function() { + let params = new URLSearchParams("a=1&b=2&c=3"); + let arr = []; + params.forEach((p) => { + arr.push(p); + params.delete("b"); + }) + assert_array_equals(arr, ["1", "3"]); +}, "forEach will not iterate over elements removed during iteration"); +test(function() { + let params = new URLSearchParams("a=1&b=2&c=3&d=4"); + let arr = []; + params.forEach((p) => { + arr.push(p); + if (p == "2") { + params.delete("a"); + } + }) + assert_array_equals(arr, ["1", "2", "4"]); +}, "Removing elements already iterated over during forEach will cause iterator to skip an element"); +test(function() { + let params = new URLSearchParams("a=1&b=2&c=3&d=4"); + let arr = []; + params.forEach((p) => { + arr.push(p); + if (p == "2") { + params.append("e", "5"); + } + }) + assert_array_equals(arr, ["1", "2", "3", "4", "5"]); +}, "Elements added during iteration with forEach will be reached"); +</script> diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/iterator-prototype-object.html b/testing/web-platform/tests/webidl/ecmascript-binding/iterator-prototype-object.html new file mode 100644 index 0000000000..7859c1e46a --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/iterator-prototype-object.html @@ -0,0 +1,47 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Iterator prototype objects</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +test(() => { + const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]())); + const iteratorProto = Object.getPrototypeOf(new URLSearchParams().entries()); + assert_equals(Object.getPrototypeOf(iteratorProto), esIteratorPrototype); +}, "Has %IteratorPrototype% as prototype"); + +test(() => { + const iteratorProto = Object.getPrototypeOf(new URLSearchParams().entries()); + const desc = Object.getOwnPropertyDescriptor(iteratorProto, "next"); + assert_equals(typeof desc.value, "function"); + assert_equals(desc.writable, true); + assert_equals(desc.enumerable, true); + assert_equals(desc.configurable, true); +}, "next() exists and is writable, enumerable, and configurable"); + +test(() => { + const usp = new URLSearchParams(); + const iteratorProto = Object.getPrototypeOf(usp.entries()); + + assert_throws_js(TypeError, () => { + iteratorProto.next(); + }); + assert_throws_js(TypeError, () => { + iteratorProto.next.call(undefined); + }); + assert_throws_js(TypeError, () => { + iteratorProto.next.call(42); + }); + assert_throws_js(TypeError, () => { + iteratorProto.next.call(new Headers().entries()); + }); +}, "next() throws TypeError when called on ineligible receiver"); + +// class string behavior tested in a dedicated file + +test(() => { + const iteratorProto1 = Object.getPrototypeOf(new URLSearchParams().entries()); + const iteratorProto2 = Object.getPrototypeOf(new Headers().entries()); + assert_not_equals(iteratorProto1, iteratorProto2); +}, "Is specific to an interface"); +</script> diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/legacy-callback-interface-object.html b/testing/web-platform/tests/webidl/ecmascript-binding/legacy-callback-interface-object.html new file mode 100644 index 0000000000..627d29507f --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/legacy-callback-interface-object.html @@ -0,0 +1,69 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Legacy callback interface objects</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me"> +<link rel="help" href="https://webidl.spec.whatwg.org/#legacy-callback-interface-object"> + +<script> +test(() => { + assert_equals(typeof NodeFilter, "function"); +}, "Must be a function according to typeof"); + +test(() => { + assert_true(Object.getPrototypeOf(NodeFilter) === Function.prototype); +}, "Must have the correct [[Prototype]]"); + +test(() => { + const propDesc = Object.getOwnPropertyDescriptor(window, "NodeFilter"); + assert_true(propDesc.writable, "writable"); + assert_false(propDesc.enumerable, "enumerable"); + assert_true(propDesc.configurable, "configurable"); +}, "Must have the correct property descriptor"); + +test(() => { + assert_throws_js(TypeError, () => NodeFilter(), "call"); + assert_throws_js(TypeError, () => new NodeFilter(), "construct"); +}, "Must throw a TypeError when called or constructed") + +test(() => { + assert_false(NodeFilter.hasOwnProperty("prototype")); +}, "Must not have a .prototype property"); + +test(() => { + assert_true(NodeFilter.hasOwnProperty("name")); + assert_equals(NodeFilter.name, "NodeFilter"); + + const propDesc = Object.getOwnPropertyDescriptor(NodeFilter, "name"); + assert_false(propDesc.writable, "writable"); + assert_false(propDesc.enumerable, "enumerable"); + assert_true(propDesc.configurable, "configurable"); +}, "Must have an own name property equal to the interface name and with the correct descriptors"); + +test(() => { + assert_true(NodeFilter.hasOwnProperty("length")); + assert_equals(NodeFilter.length, 0); + + const propDesc = Object.getOwnPropertyDescriptor(NodeFilter, "length"); + assert_false(propDesc.writable, "writable"); + assert_false(propDesc.enumerable, "enumerable"); + assert_true(propDesc.configurable, "configurable"); +}, "Must have an own length property with value zero and the correct descriptors"); + +test(() => { + // The JS spec (OrdinaryHasInstance) bails out early for non-objects + // Historically we overrode [[HasInstance]] to throw anyway, but this was removed: + // https://github.com/heycam/webidl/pull/356 + assert_false(5 instanceof NodeFilter, "5"); + + // OrdinaryHasInstance throws a TypeError if the right-hand-side doesn't have a .prototype object, + // which is the case for callback interfaces. + assert_throws_js(TypeError, () => { + (function () { }) instanceof NodeFilter; + }); + assert_throws_js(TypeError, () => { + ({ }) instanceof NodeFilter; + }); +}, "instanceof must throw but only when we don't bail out early"); +</script> diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/legacy-factor-function-subclass.window.js b/testing/web-platform/tests/webidl/ecmascript-binding/legacy-factor-function-subclass.window.js new file mode 100644 index 0000000000..1fd64f41bb --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/legacy-factor-function-subclass.window.js @@ -0,0 +1,13 @@ +"use strict"; + +test(() => { + class CustomImage extends Image {} + var instance = new CustomImage(); + + assert_equals( + Object.getPrototypeOf(instance), CustomImage.prototype, + "Object.getPrototypeOf(instance) === CustomImage.prototype"); + + assert_true(instance instanceof CustomImage, "instance instanceof CustomImage"); + assert_true(instance instanceof HTMLImageElement, "instance instanceof HTMLImageElement"); +}, "[LegacyFactoryFunction] can be subclassed and correctly handles NewTarget"); diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/DefineOwnProperty.html b/testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/DefineOwnProperty.html new file mode 100644 index 0000000000..ad32f6f89f --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/DefineOwnProperty.html @@ -0,0 +1,89 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Legacy platform objects [[DefineOwnProperty]] method</title> +<link rel="help" href="https://webidl.spec.whatwg.org/#legacy-platform-object-defineownproperty"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="./helper.js"></script> +<script> + +test(function() { + let span = document.createElement("span"); + span.className = "foo"; + // DOMTokenList supports an indexed property getter but not a setter. + let domTokenList = span.classList; + // Confirm the test settings. + assert_equals(domTokenList.length, 1); + assert_prop_desc_equals(domTokenList, "0", + {value: "foo", writable: false, enumerable: true, + configurable: true}); + assert_prop_desc_equals(domTokenList, "1", undefined); + // Actual test + assert_throws_js(TypeError, () => + Object.defineProperty(domTokenList, "0", {value: true, writable: true})); + assert_throws_js(TypeError, () => + Object.defineProperty(domTokenList, "1", {value: true, writable: true})); + assert_throws_js(TypeError, () => + Object.defineProperty(domTokenList, "0", {get: () => {}})); + assert_throws_js(TypeError, () => + Object.defineProperty(domTokenList, "0", {set: () => {}})); + assert_throws_js(TypeError, () => + Object.defineProperty(domTokenList, "1", {get: () => {}})); + assert_throws_js(TypeError, () => + Object.defineProperty(domTokenList, "1", {set: () => {}})); + assert_equals(domTokenList[0], "foo"); + assert_equals(domTokenList[1], undefined); + domTokenList[0] = "bar"; + domTokenList[1] = "bar"; + assert_equals(domTokenList[0], "foo"); + assert_equals(domTokenList[1], undefined); + assert_throws_js(TypeError, () => { + "use strict"; + domTokenList[0] = "bar"; + }); + assert_throws_js(TypeError, () => { + "use strict"; + domTokenList[1] = "bar"; + }); + // Nothing must change after all. + assert_equals(domTokenList.length, 1); + assert_prop_desc_equals(domTokenList, "0", + {value: "foo", writable: false, enumerable: true, + configurable: true}); + assert_prop_desc_equals(domTokenList, "1", undefined); +}, "Test [[DefineOwnProperty]] with no indexed property setter support."); + +test(function() { + // HTMLSelectElement supports an indexed property setter. + let select = document.createElement("select"); + let option0 = document.createElement("option"); + let option1 = document.createElement("option"); + // Confirm the test settings. + assert_equals(select.length, 0); + assert_prop_desc_equals(select, "0", undefined); + // Try to define an accessor property with non supported property index. + assert_throws_js(TypeError, () => + Object.defineProperty(select, "0", {get: () => {}})); + assert_throws_js(TypeError, () => + Object.defineProperty(select, "0", {set: () => {}})); + assert_prop_desc_equals(select, "0", undefined); + // writable, enumerable, configurable will be ignored. + Object.defineProperty(select, "0", {value: option0, writable: false, + enumerable: false, configurable: false}); + assert_prop_desc_equals(select, "0", + {value: option0, writable: true, enumerable: true, + configurable: true}); + select[1] = option1; + assert_prop_desc_equals(select, "1", + {value: option1, writable: true, enumerable: true, + configurable: true}); + // Try to define an accessor property with a supported property index. + assert_throws_js(TypeError, () => + Object.defineProperty(select, "0", {get: () => {}})); + assert_throws_js(TypeError, () => + Object.defineProperty(select, "0", {set: () => {}})); + assert_prop_desc_equals(select, "0", + {value: option0, writable: true, enumerable: true, + configurable: true}); +}, "Test [[DefineOwnProperty]] with indexed property setter support."); +</script> diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/GetOwnProperty.html b/testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/GetOwnProperty.html new file mode 100644 index 0000000000..be3bcc61f0 --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/GetOwnProperty.html @@ -0,0 +1,84 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Legacy platform objects [[GetOwnProperty]] method</title> +<link rel="help" href="https://webidl.spec.whatwg.org/#legacy-platform-object-getownproperty"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="./helper.js"></script> +<script> + +test(function() { + // DOMTokenList has an indexed property getter, no indexed property setter + // and no named property handlers. + let div = document.createElement("div"); + div.classList.add("baz", "quux"); + const domTokenList = div.classList; + assert_prop_desc_equals( + domTokenList, "1", + {value: "quux", writable: false, enumerable: true, configurable: true}, + "[[GetOwnProperty]] for indexed properties returns the right descriptor"); + assert_prop_desc_equals( + domTokenList, "42", undefined, + "[[GetOwnProperty]] with invalid index returns undefined"); + assert_array_equals(Object.keys(domTokenList), ["0", "1"]); + assert_array_equals(Object.values(domTokenList), ["baz", "quux"]); + + // getElementsByTagName() returns an HTMLCollection. + // HTMLCollection has indexed and named property getters, no setters. Its IDL + // interface declaration has [LegacyUnenumerableNamedProperties] so its named + // properties are not enumerable. + let span1 = document.createElement("span"); + span1.id = "foo"; + let span2 = document.createElement("span"); + span2.id = "bar"; + document.head.appendChild(span1); + document.head.appendChild(span2); + const elementList = document.getElementsByTagName("span"); + assert_prop_desc_equals( + elementList, "foo", + {value: span1, writable: false, enumerable: false, configurable: true}, + "[[GetOwnProperty]] for named properties returns the right descriptor"); + assert_prop_desc_equals( + elementList, "1", + {value: span2, writable: false, enumerable: true, configurable: true}, + "[[GetOwnProperty]] for indexed properties returns the right descriptor"); + assert_prop_desc_equals( + elementList, "unknown", undefined, + "[[GetOwnProperty]] with invalid property name returns undefined"); + assert_array_equals(Object.keys(elementList), ["0", "1"]); + assert_array_equals(Object.values(elementList), [span1, span2]); +}, "[[GetOwnProperty]] with getters and no setters"); + +test(function() { + // DOMStringMap supports named property getters and setters, but not indexed + // properties. + let span = document.createElement("span"); + span.dataset.foo = "bar"; + assert_prop_desc_equals( + span.dataset, "foo", + {value: "bar", writable: true, enumerable: true, configurable: true}, + "[[GetOwnProperty]] for named properties returns the right descriptor"); + assert_prop_desc_equals( + span.dataset, "unknown", undefined, + "[[GetOwnProperty]] with invalid property name returns undefined"); + assert_array_equals(Object.keys(span.dataset), ["foo"]); + assert_array_equals(Object.values(span.dataset), ["bar"]); +}, "[[GetOwnProperty]] with named property getters and setters"); + +test(function() { + // HTMLSelectElement has indexed property getters and setters, but no support + // for named properties. + let selectElement = document.createElement("select"); + assert_prop_desc_equals( + selectElement, "0", undefined, + "[[GetOwnProperty]] with invalid property index returns undefined"); + let optionElement = document.createElement("option"); + selectElement.appendChild(optionElement); + assert_prop_desc_equals( + selectElement, "0", + {value: optionElement, writable: true, enumerable: true, configurable: true}, + "[[GetOwnProperty]] for indexed properties returns the right descriptor"); + assert_array_equals(Object.keys(selectElement), ["0"]); + assert_array_equals(Object.values(selectElement), [optionElement]); +}, "[[GetOwnProperty]] with indexed property getters and setters"); +</script> diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/OwnPropertyKeys.html b/testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/OwnPropertyKeys.html new file mode 100644 index 0000000000..d33980517b --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/OwnPropertyKeys.html @@ -0,0 +1,65 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Legacy platform objects [[OwnPropertyKeys]] method</title> +<link rel="help" href="https://webidl.spec.whatwg.org/#legacy-platform-object-ownpropertykeys"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + function custom_assert_array_equals(actual, expected, msg) { + function replacer(k, v) { + if (typeof v === "symbol") { + return v.toString(); + } + return v; + } + assert_array_equals(actual, expected, " actual " + JSON.stringify(actual, replacer) + " expected " + JSON.stringify(expected, replacer)); + } + + test(function() { + var element = document.createElement("div"); + element.appendChild(document.createElement("div")); + element.appendChild(document.createElement("div")); + element.appendChild(document.createElement("div")); + custom_assert_array_equals(Reflect.ownKeys(element.childNodes), ["0", "1", "2"]); + }, "must enumerate property indices in ascending numerical order"); + + test(function() { + var element = document.createElement("div"); + element.setAttribute("data-foo", "foo content"); + element.setAttribute("data-bar", "bar content"); + element.setAttribute("data-baz", "baz content"); + custom_assert_array_equals(Reflect.ownKeys(element.dataset), ["foo", "bar", "baz"]); + }, "must enumerate property names in list order"); + + + test(function() { + var element = document.createElement("div"); + element.setAttribute("id", "foo"); + element.setAttribute("class", "bar"); + custom_assert_array_equals(Reflect.ownKeys(element.attributes), ["0", "1", "id", "class"]); + }, "must first enumerate property indices in ascending numerical order, then named properties in list order"); + + + test(function() { + var element = document.createElement("div"); + element.attributes.foo = "some value"; + element.attributes.bar = "and another"; + element.setAttribute("id", "foo"); + element.setAttribute("class", "bar"); + custom_assert_array_equals(Reflect.ownKeys(element.attributes), ["0", "1", "id", "class", "foo", "bar"]); + }, "must enumerate own properties after indexed and named properties even when they're added first"); + + test(function() { + var symb1 = Symbol(); + var symb2 = Symbol(); + var element = document.createElement("div"); + element.attributes.foo = "some value"; + element.attributes[symb1] = "symb1"; + element.attributes[symb2] = "symb2"; + element.attributes.bar = "and another"; + element.setAttribute("id", "foo"); + element.setAttribute("class", "bar"); + custom_assert_array_equals(Reflect.ownKeys(element.attributes), + ["0", "1", "id", "class", "foo", "bar", symb1, symb2]); + }, "must enumerate symbols after strings, regardless of which ones got added first"); +</script> diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/Set.html b/testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/Set.html new file mode 100644 index 0000000000..3134c81859 --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/Set.html @@ -0,0 +1,79 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Legacy platform objects [[Set]] method</title> +<link rel="help" href="https://webidl.spec.whatwg.org/#legacy-platform-object-set"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + test(function() { + var element = document.createElement("div"); + element.appendChild(document.createElement("div")); + element.childNodes["5"] = "foo"; + assert_equals(element.childNodes["5"], undefined); + }, "must not set the property value when assigning to a numeric property on an object which implements an indexed property getter but not a setter when not in strict mode"); + + test(function() { + "use strict"; + var element = document.createElement("div"); + element.appendChild(document.createElement("div")); + assert_throws_js(TypeError, function() { element.childNodes["5"] = "foo"; }); + }, "must throw when assigning to a numeric property on an object which implements a indexed property getter but not a setter in strict mode"); + + test(function() { + var element = document.createElement("div"); + element.attributes.foo = "foo"; + assert_equals(element.attributes.foo, "foo"); + }, "must allow assigning to a named property on an object which implements a named property getter but not a setter when not in strict mode"); + + test(function() { + "use strict"; + var element = document.createElement("div"); + element.attributes.foo = "foo"; + assert_equals(element.attributes.foo, "foo"); + }, "must allow assigning to a named property on an object which implements a named property getter but not a setter in strict mode"); + + var symbol = Symbol(); + + test(function() { + var element = document.createElement("div"); + element.appendChild(document.createElement("div")); + element.childNodes[symbol] = "foo"; + assert_equals(element.childNodes[symbol], "foo"); + }, "must allow assigning to a symbol property on an object which implements an indexed property getter but not a setter when not in strict mode"); + + test(function() { + "use strict"; + var element = document.createElement("div"); + element.appendChild(document.createElement("div")); + element.childNodes[symbol] = "foo"; + assert_equals(element.childNodes[symbol], "foo"); + }, "must allow assigning to a symbol property on an object which implements an indexed property getter but not a setter in strict mode"); + + test(function() { + var element = document.createElement("div"); + element.attributes[symbol] = "foo"; + assert_equals(element.attributes[symbol], "foo"); + }, "must allow assigning to a symbol property on an object which implements indexed and named property getters but no setters when not in strict mode"); + + test(function() { + "use strict"; + var element = document.createElement("div"); + element.attributes[symbol] = "foo"; + assert_equals(element.attributes[symbol], "foo"); + }, "must allow assigning to a symbol property on an object which implements indexed and named property getters but no setters in strict mode"); + + test(function() { + sessionStorage.clear(); + this.add_cleanup(function() { sessionStorage.clear(); }); + sessionStorage[symbol] = "foo"; + assert_equals(sessionStorage[symbol], "foo"); + }, "must allow assigning to a symbol property on an object which implements indexed and named property getters and setters when not in strict mode"); + + test(function() { + "use strict"; + sessionStorage.clear(); + this.add_cleanup(function() { sessionStorage.clear(); }); + sessionStorage[symbol] = "foo"; + assert_equals(sessionStorage[symbol], "foo"); + }, "must allow assigning to a symbol property on an object which implements indexed and named property getters and setters in strict mode"); +</script> diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/helper.js b/testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/helper.js new file mode 100644 index 0000000000..01c1d00694 --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/helper.js @@ -0,0 +1,22 @@ +function assert_prop_desc_equals(object, property_key, expected) { + let actual = Object.getOwnPropertyDescriptor(object, property_key); + if (expected === undefined) { + assert_equals( + actual, undefined, + "(assert_prop_desc_equals: no property descriptor expected)"); + return; + } + for (p in actual) { + assert_true( + expected.hasOwnProperty(p), + "(assert_prop_desc_equals: property '" + p + "' is not expected)"); + assert_equals( + actual[p], expected[p], + "(assert_prop_desc_equals: property '" + p + "')"); + } + for (p in expected) { + assert_true( + actual.hasOwnProperty(p), + "(assert_prop_desc_equals: expected property '" + p + "' missing)"); + } +} diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/no-regexp-special-casing.any.js b/testing/web-platform/tests/webidl/ecmascript-binding/no-regexp-special-casing.any.js new file mode 100644 index 0000000000..4446dbf69c --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/no-regexp-special-casing.any.js @@ -0,0 +1,47 @@ +"use strict"; +// RegExps used to be special-cased in Web IDL, but that was removed in +// https://github.com/heycam/webidl/commit/bbb2bde. These tests check that implementations no longer +// do any such special-casing. + +test(() => { + const regExp = new RegExp(); + regExp.message = "some message"; + + const errorEvent = new ErrorEvent("type", regExp); + + assert_equals(errorEvent.message, "some message"); +}, "Conversion to a dictionary works"); + +test(() => { + const messageChannel = new MessageChannel(); + const regExp = new RegExp(); + regExp[Symbol.iterator] = function* () { + yield messageChannel.port1; + }; + + const messageEvent = new MessageEvent("type", { ports: regExp }); + + assert_array_equals(messageEvent.ports, [messageChannel.port1]); +}, "Conversion to a sequence works"); + +promise_test(async () => { + const regExp = new RegExp(); + + const response = new Response(regExp); + + assert_equals(await response.text(), "/(?:)/"); +}, "Can convert a RegExp to a USVString"); + +test(() => { + let functionCalled = false; + + const regExp = new RegExp(); + regExp.handleEvent = () => { + functionCalled = true; + }; + + self.addEventListener("testevent", regExp); + self.dispatchEvent(new Event("testevent")); + + assert_true(functionCalled); +}, "Can be used as an object implementing a callback interface"); diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/observable-array-no-leak-of-internals.window.js b/testing/web-platform/tests/webidl/ecmascript-binding/observable-array-no-leak-of-internals.window.js new file mode 100644 index 0000000000..f93464005d --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/observable-array-no-leak-of-internals.window.js @@ -0,0 +1,18 @@ +"use strict"; + +test(() => { + const observableArray = document.adoptedStyleSheets; + + let leaked_target = null; + let leaked_handler = null; + + let target_leaker = (target) => { leaked_target = target; return null; }; + Object.defineProperty(Object.prototype, "getPrototypeOf", {get: function() { + leaked_handler = this; + return target_leaker; + }}) + Object.getPrototypeOf(observableArray); + + assert_equals(leaked_target, null, "The proxy target leaked."); + assert_equals(leaked_handler, null, "The proxy handler leaked."); +}, "ObservableArray's internals won't leak"); diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/observable-array-ownkeys.window.js b/testing/web-platform/tests/webidl/ecmascript-binding/observable-array-ownkeys.window.js new file mode 100644 index 0000000000..29b537c475 --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/observable-array-ownkeys.window.js @@ -0,0 +1,34 @@ +"use strict"; + +test(() => { + const observableArray = document.adoptedStyleSheets; + assert_array_equals( + Object.getOwnPropertyNames(observableArray), + ["length"], + "Initially only \"length\"."); + + observableArray["zzz"] = true; + observableArray["aaa"] = true; + assert_array_equals( + Object.getOwnPropertyNames(observableArray), + ["length", "zzz", "aaa"], + "Own properties whose key is a string have been added."); + + observableArray[0] = new CSSStyleSheet(); + observableArray[1] = new CSSStyleSheet(); + assert_array_equals( + Object.getOwnPropertyNames(observableArray), + ["0", "1", "length", "zzz", "aaa"], + "Own properties whose key is an array index have been added."); + + observableArray[Symbol.toStringTag] = "string_tag"; + observableArray[Symbol.toPrimitive] = "primitive"; + assert_array_equals( + Object.getOwnPropertyNames(observableArray), + ["0", "1", "length", "zzz", "aaa"], + "Own properties whose key is a symbol have been added (non-symbol)."); + assert_array_equals( + Object.getOwnPropertySymbols(observableArray), + [Symbol.toStringTag, Symbol.toPrimitive], + "Own properties whose key is a symbol have been added (symbol)."); +}, "ObservableArray's ownKeys trap"); diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/put-forwards.html b/testing/web-platform/tests/webidl/ecmascript-binding/put-forwards.html new file mode 100644 index 0000000000..7d99d65aa2 --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/put-forwards.html @@ -0,0 +1,148 @@ +<!doctype html> +<meta charset="utf-8"> +<title>[PutForwards] behavior</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="author" title="Jens Widell" href="mailto:jl@opera.com"> +<link rel="help" href="https://webidl.spec.whatwg.org/#PutForwards"> + +<script> +test(() => { + var getter_called = false; + var element = document.createElement("div"); + var element_style = element.style; + var descriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "style"); + + Object.defineProperty(element, "style", { + get: function () { + getter_called = true; + return element_style; + }, + set: descriptor.set + }); + + element.style = "color: green"; + + assert_true(getter_called, "Overridden getter should be called"); + assert_equals(element_style.color, "green", "Put forwarding still works"); +}, "Overriding getter of [PutForwards] attribute"); + +test(() => { + var setter_called = false; + var element = document.createElement("div"); + var element_style = element.style; + var descriptor = Object.getOwnPropertyDescriptor(CSSStyleDeclaration.prototype, "cssText"); + + Object.defineProperty(element_style, "cssText", { + get: descriptor.get, + set: function (v) { + setter_called = true; + descriptor.set.call(this, v); + } + }); + + element.style = "color: green"; + + assert_true(setter_called, "Overridden setter should be called"); + assert_equals(element_style.color, "green", "Put forwarding still works"); +}, "Overriding setter of [PutForwards] target attribute"); + +test(() => { + var element = document.createElement("div"); + var element_style = element.style; + var fake_style = { cssText: "original" }; + var descriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "style"); + + Object.defineProperty(element, "style", { + get: function () { + return fake_style; + }, + set: descriptor.set + }); + + element.style = "color: green"; + + assert_equals(element_style.cssText, "", "Original value intact"); + assert_equals(fake_style.cssText, "color: green", "Fake style object updated"); +}, "Overriding target of [PutForwards] attribute"); + +test(() => { + var element = document.createElement("div"); + var descriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "style"); + + Object.defineProperty(element, "style", { + get: function () { + throw new SyntaxError(); + }, + set: descriptor.set + }); + + assert_throws_js(SyntaxError, () => { + element.style = "color: green"; + }); +}, "Exception propagation from getter of [PutForwards] attribute"); + +test(() => { + var element = document.createElement("div"); + var element_style = element.style; + var descriptor = Object.getOwnPropertyDescriptor(CSSStyleDeclaration.prototype, "cssText"); + + Object.defineProperty(element_style, "cssText", { + get: descriptor.get, + set: function (v) { + throw new SyntaxError(); + } + }); + + assert_throws_js(SyntaxError, () => { + element.style = "color: green"; + }); +}, "Exception propagation from setter of [PutForwards] target attribute"); + +test(() => { + var element = document.createElement("div"); + var descriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "style"); + + Object.defineProperty(element, "style", { + get: function () { + return null; + }, + set: descriptor.set + }); + + assert_throws_js(TypeError, () => { + element.style = "color: green"; + }); +}, "TypeError when getter of [PutForwards] attribute returns non-object"); + + +test(() => { + var element = document.createElement("div"); + + var element_style = element.style; + Object.defineProperty(element.style, "cssText", { + value: null, + writable: false, + }); + + element.style = "color: green"; + assert_equals(element.style, element_style); + assert_equals(element.style.cssText, null); +}, "Does not throw when setter of [PutForwards] attribute returns false"); + +function test_token_list(elementName, attribute, target, value) { + test(() => { + var element=document.createElement(elementName); + assert_true(element[attribute] instanceof DOMTokenList,"The attribute is a DOMTokenList"); + element[attribute]=value; + assert_equals(element.getAttribute(target),value,"Setting the attribute is reflected in the target"); + element[attribute]=""; + assert_equals(element.getAttribute(target),"","Clearing the attribute is reflected in the target"); + },"Setting "+elementName+"."+attribute+" to "+value+" is reflected in "+target) +} + +test_token_list("a","relList","rel","noreferrer"); +test_token_list("area","relList","rel","noreferrer"); +test_token_list("form","relList","rel","noreferrer"); +test_token_list("link","relList","rel","stylesheet"); +</script> diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/sequence-conversion.html b/testing/web-platform/tests/webidl/ecmascript-binding/sequence-conversion.html new file mode 100644 index 0000000000..40764e9f57 --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/sequence-conversion.html @@ -0,0 +1,157 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Sequence conversion</title> +<link rel="help" href="https://webidl.spec.whatwg.org/#es-sequence"> +<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me"> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<canvas></canvas> + +<script> +"use strict"; + +const canvas = document.querySelector("canvas"); +const ctx = canvas.getContext("2d"); + +test(() => { + ctx.setLineDash([1, 2]); + assert_array_equals(ctx.getLineDash(), [1, 2]); +}, "An array"); + +test(() => { + function* generatorFunc() { + yield 4; + yield 5; + } + const generator = generatorFunc(); + + ctx.setLineDash(generator); + assert_array_equals(ctx.getLineDash(), [4, 5]); +}, "A generator"); + +test(() => { + function* generatorFunc() { + yield 6; + yield 7; + } + + let callCount = 0; + const array = [1, 2]; + Object.defineProperty(array, Symbol.iterator, { + get() { + ++callCount; + return generatorFunc; + } + }); + + ctx.setLineDash(array); + assert_array_equals(ctx.getLineDash(), [6, 7]); + assert_equals(callCount, 1, "@@iterator must only have been gotten once"); +}, "An array with an overridden Symbol.iterator"); + +test(t => { + function* generatorFunc() { + yield ["foo", "bar"]; + yield ["baz", "quux"]; + } + + let callCount = 0; + const obj = {}; + Object.defineProperty(obj, Symbol.iterator, { + get() { + ++callCount; + return generatorFunc; + } + }); + + const searchParams = new URLSearchParams(obj); + assert_equals(searchParams.get("foo"), "bar"); + assert_equals(searchParams.get("baz"), "quux"); + assert_equals(callCount, 1, "@@iterator must only have been gotten once"); +}, "An object with an overriden Symbol.iterator"); + +test(t => { + const originalIterator = Object.getOwnPropertyDescriptor(Array.prototype, Symbol.iterator); + t.add_cleanup(() => { + Object.defineProperty(Array.prototype, Symbol.iterator, originalIterator); + }); + + function* generatorFunc() { + yield 11; + yield 12; + } + + let callCount = 0; + const array = [1, 2]; + Object.defineProperty(Array.prototype, Symbol.iterator, { + get() { + ++callCount; + return generatorFunc; + } + }); + + ctx.setLineDash(array); + assert_array_equals(ctx.getLineDash(), [11, 12]); + assert_equals(callCount, 1, "@@iterator must only have been gotten once"); +}, "An array with an overridden Symbol.iterator on the prototype"); + +test(t => { + const arrayIteratorPrototype = Object.getPrototypeOf(Array.prototype[Symbol.iterator]()); + const nextBefore = arrayIteratorPrototype.next; + t.add_cleanup(() => { + arrayIteratorPrototype.next = nextBefore; + }); + + let callCount = 0; + arrayIteratorPrototype.next = () => { + switch (callCount) { + case 0: { + ++callCount; + return { done: false, value: 8 }; + } + case 1: { + ++callCount; + return { done: false, value: 9 }; + } + case 2: { + ++callCount; + return { done: true, value: 10 }; // value should be ignored this time + } + default: { + assert_unreached("next() should be called three times exactly"); + } + } + }; + + const array = [1, 2]; + ctx.setLineDash(array); + assert_array_equals(ctx.getLineDash(), [8, 9]); + assert_equals(callCount, 3, "next() must have been called thrice"); +}, "An array with an overridden %ArrayIterator%.prototype.next"); + +test(t => { + t.add_cleanup(() => { + delete Array.prototype[1]; + }); + + Object.defineProperty(Array.prototype, "1", { + configurable: true, + enumerable: true, + get() { + return 14; + } + }); + + const array = [13, , 15, 16]; + ctx.setLineDash(array); + assert_array_equals(ctx.getLineDash(), [13, 14, 15, 16]); +}, "A holey array with fallback to an accessor on the prototype"); + +test(t => { + // Should fail rather than falling back to record + assert_throws_js(TypeError, function() { new URLSearchParams(["key", "value"]); }); +}, "A string array in sequence<sequence> or record"); + +</script> diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/support/constructors-support.html b/testing/web-platform/tests/webidl/ecmascript-binding/support/constructors-support.html new file mode 100644 index 0000000000..3b2616170b --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/support/constructors-support.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<script> +window.badNewTarget = function() {}; +badNewTarget.prototype = 8; + +window.DOMParserSubclass = class extends DOMParser {} +window.ForeignDOMParserSubclass = class extends parent.DOMParser {} +</script> diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/support/create-realm.js b/testing/web-platform/tests/webidl/ecmascript-binding/support/create-realm.js new file mode 100644 index 0000000000..45ded884fc --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/support/create-realm.js @@ -0,0 +1,12 @@ +"use strict"; + +function createRealm(t) { + return new Promise(resolve => { + const iframe = document.createElement("iframe"); + t.add_cleanup(() => { iframe.remove(); }); + iframe.onload = () => { resolve(iframe.contentWindow); }; + iframe.name = "dummy"; + iframe.src = "support/dummy-iframe.html"; + document.body.append(iframe); + }); +} diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/support/dummy-iframe.html b/testing/web-platform/tests/webidl/ecmascript-binding/support/dummy-iframe.html new file mode 100644 index 0000000000..3f773ae6f8 --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/support/dummy-iframe.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<body>foo +<button id="element"></button> +<script> + window.text = document.body.firstChild; +</script> diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/window-named-properties-object.html b/testing/web-platform/tests/webidl/ecmascript-binding/window-named-properties-object.html new file mode 100644 index 0000000000..cc49768906 --- /dev/null +++ b/testing/web-platform/tests/webidl/ecmascript-binding/window-named-properties-object.html @@ -0,0 +1,284 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Internal methods of Window's named properties object</title> +<link rel="help" href="https://webidl.spec.whatwg.org/#named-properties-object"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +function sloppyModeSet(base, key, value) { base[key] = value; } +</script> +<script> +"use strict"; + +const supportedNonIndex = "supported non-index property name"; +const supportedIndex = "supported indexed property name"; +const unsupportedNonIndex = "unsupported non-index property name"; +const unsupportedIndex = "unsupported indexed property name"; +const existingSymbol = "existing symbol property name"; +const nonExistingSymbol = "non-existing symbol property name"; + +test(t => { + const { w, wp } = createWindowProperties(t); + + Object.setPrototypeOf(wp, w.EventTarget.prototype); // Setting current [[Prototype]] value shouldn't throw + + assert_throws_js(TypeError, () => { Object.setPrototypeOf(wp, {}); }); + assert_throws_js(w.TypeError, () => { wp.__proto__ = null; }); + assert_false(Reflect.setPrototypeOf(wp, w.Object.prototype)); + + assert_equals(Object.getPrototypeOf(wp), w.EventTarget.prototype); +}, "[[SetPrototypeOf]] and [[GetPrototypeOf]]"); + +test(t => { + const { wp } = createWindowProperties(t); + + assert_throws_js(TypeError, () => { Object.preventExtensions(wp); }); + assert_false(Reflect.preventExtensions(wp)); + + assert_true(Object.isExtensible(wp)); +}, "[[PreventExtensions]] and [[IsExtensible]]"); + +test(t => { + const { w, wp } = createWindowProperties(t); + + const elA = appendElementWithId(w, "a"); + const el0 = appendIframeWithName(w, 0); + + assert_prop_desc(Object.getOwnPropertyDescriptor(wp, "a"), elA, supportedNonIndex); + assert_prop_desc(Reflect.getOwnPropertyDescriptor(wp, 0), el0, supportedIndex); + assert_equals(Reflect.getOwnPropertyDescriptor(wp, "b"), undefined, unsupportedNonIndex); + assert_equals(Object.getOwnPropertyDescriptor(wp, 1), undefined, unsupportedIndex); +}, "[[GetOwnProperty]]"); + +test(t => { + const { w, wp } = createWindowProperties(t); + + appendIframeWithName(w, "hasOwnProperty"); + appendFormWithName(w, "addEventListener"); + appendElementWithId(w, "a"); + appendIframeWithName(w, 0); + + w.Object.prototype.a = {}; + w.EventTarget.prototype[0] = {}; + + // These are shadowed by properties higher in [[Prototype]] chain. See https://webidl.spec.whatwg.org/#dfn-named-property-visibility + assert_equals(Object.getOwnPropertyDescriptor(wp, "hasOwnProperty"), undefined, supportedNonIndex); + assert_equals(Reflect.getOwnPropertyDescriptor(wp, "addEventListener"), undefined, supportedNonIndex); + assert_equals(Object.getOwnPropertyDescriptor(wp, "a"), undefined, supportedNonIndex); + assert_equals(Reflect.getOwnPropertyDescriptor(wp, 0), undefined, supportedIndex); +}, "[[GetOwnProperty]] (named property visibility algorithm)"); + +test(t => { + const { w, wp } = createWindowProperties(t); + + appendElementWithId(w, "a"); + appendFormWithName(w, 0); + + assert_define_own_property_fails(wp, "a", {}, supportedNonIndex); + assert_define_own_property_fails(wp, 0, {}, supportedIndex); + assert_define_own_property_fails(wp, "b", {}, unsupportedNonIndex); + assert_define_own_property_fails(wp, 1, {}, unsupportedIndex); + assert_define_own_property_fails(wp, Symbol.toStringTag, {}, existingSymbol); + assert_define_own_property_fails(wp, Symbol(), {}, nonExistingSymbol); +}, "[[DefineOwnProperty]]"); + +test(t => { + const { w, wp } = createWindowProperties(t); + + appendFormWithName(w, "a"); + appendElementWithId(w, 0); + + assert_true("a" in wp, supportedNonIndex); + assert_true(Reflect.has(wp, "a"), supportedNonIndex); + assert_true(0 in wp, supportedIndex); + assert_true(Reflect.has(wp, 0), supportedIndex); + + assert_false("b" in wp, unsupportedNonIndex); + assert_false(Reflect.has(wp, 1), unsupportedIndex); +}, "[[HasProperty]]"); + +test(t => { + const { w, wp } = createWindowProperties(t); + const elA = appendFormWithName(w, "a"); + const el0 = appendIframeWithName(w, 0); + + assert_equals(wp.a, elA, supportedNonIndex); + assert_equals(wp[0], el0, supportedIndex); + assert_equals(wp[Symbol.toStringTag], "WindowProperties", existingSymbol); + + assert_equals(wp.b, undefined, unsupportedNonIndex); + assert_equals(wp[1], undefined, unsupportedIndex); + assert_equals(wp[Symbol.iterator], undefined, nonExistingSymbol); +}, "[[Get]]"); + +test(t => { + const { w, wp } = createWindowProperties(t); + + appendIframeWithName(w, "isPrototypeOf"); + appendFormWithName(w, "dispatchEvent"); + appendElementWithId(w, "a"); + appendElementWithId(w, 0); + + w.EventTarget.prototype.a = 10; + w.Object.prototype[0] = 20; + + // These are shadowed by properties higher in [[Prototype]] chain. See https://webidl.spec.whatwg.org/#dfn-named-property-visibility + assert_equals(wp.isPrototypeOf, w.Object.prototype.isPrototypeOf, supportedNonIndex); + assert_equals(wp.dispatchEvent, w.EventTarget.prototype.dispatchEvent, supportedNonIndex); + assert_equals(wp.a, 10, supportedNonIndex); + assert_equals(wp[0], 20, supportedIndex); +}, "[[Get]] (named property visibility algorithm)"); + +test(t => { + const { w, wp } = createWindowProperties(t); + const elA = appendIframeWithName(w, "a"); + const el0 = appendFormWithName(w, 0); + + assert_set_fails(wp, "a", supportedNonIndex); + assert_set_fails(wp, "b", unsupportedNonIndex); + assert_set_fails(wp, 0, supportedIndex); + assert_set_fails(wp, 1, unsupportedIndex); + assert_set_fails(wp, Symbol.toStringTag, existingSymbol); + assert_set_fails(wp, Symbol(), nonExistingSymbol); + + assert_equals(wp.a, elA, supportedNonIndex); + assert_equals(wp[0], el0, supportedIndex); + assert_equals(wp.b, undefined, unsupportedNonIndex); + assert_equals(wp[1], undefined, unsupportedIndex); +}, "[[Set]] (direct)"); + +test(t => { + const { w, wp } = createWindowProperties(t); + const receiver = Object.create(wp); + + appendIframeWithName(w, "a"); + appendElementWithId(w, 0); + + let setterThisValue; + Object.defineProperty(w.Object.prototype, 1, { set() { setterThisValue = this; } }); + Object.defineProperty(w.EventTarget.prototype, "b", { writable: false }); + + receiver.a = 10; + assert_throws_js(TypeError, () => { receiver.b = {}; }, unsupportedNonIndex); + receiver[0] = 20; + receiver[1] = {}; + + assert_equals(receiver.a, 10, supportedNonIndex); + assert_equals(receiver[0], 20, supportedIndex); + assert_false(receiver.hasOwnProperty("b"), unsupportedNonIndex); + assert_false(receiver.hasOwnProperty(1), unsupportedIndex); + assert_equals(setterThisValue, receiver, "setter |this| value is receiver"); +}, "[[Set]] (prototype chain)"); + +test(t => { + const { w, wp } = createWindowProperties(t); + const receiver = {}; + + appendFormWithName(w, "a"); + appendIframeWithName(w, 0); + + let setterThisValue; + Object.defineProperty(w.Object.prototype, "b", { set() { setterThisValue = this; } }); + Object.defineProperty(w.EventTarget.prototype, 1, { writable: false }); + + assert_true(Reflect.set(wp, "a", 10, receiver), supportedNonIndex); + assert_true(Reflect.set(wp, 0, 20, receiver), supportedIndex); + assert_true(Reflect.set(wp, "b", {}, receiver), unsupportedNonIndex); + assert_false(Reflect.set(wp, 1, {}, receiver), unsupportedIndex); + + assert_equals(receiver.a, 10, supportedNonIndex); + assert_equals(receiver[0], 20, supportedIndex); + assert_false(receiver.hasOwnProperty("b"), unsupportedNonIndex); + assert_equals(setterThisValue, receiver, "setter |this| value is receiver"); + assert_false(receiver.hasOwnProperty(1), unsupportedIndex); +}, "[[Set]] (Reflect.set)"); + +test(t => { + const { w, wp } = createWindowProperties(t); + const elA = appendFormWithName(w, "a"); + const el0 = appendElementWithId(w, 0); + + assert_delete_fails(wp, "a", supportedNonIndex); + assert_delete_fails(wp, 0, supportedIndex); + assert_delete_fails(wp, "b", unsupportedNonIndex); + assert_delete_fails(wp, 1, unsupportedIndex); + assert_delete_fails(wp, Symbol.toStringTag, existingSymbol); + assert_delete_fails(wp, Symbol("foo"), nonExistingSymbol); + + assert_equals(wp.a, elA, supportedNonIndex); + assert_equals(wp[0], el0, supportedIndex); + assert_equals(wp[Symbol.toStringTag], "WindowProperties", existingSymbol); +}, "[[Delete]]"); + +test(t => { + const { w, wp } = createWindowProperties(t); + + appendIframeWithName(w, "a"); + appendElementWithId(w, 0); + appendFormWithName(w, "b"); + + const forInKeys = []; + for (const key in wp) + forInKeys.push(key); + + assert_array_equals(forInKeys, Object.keys(w.EventTarget.prototype)); + assert_array_equals(Object.getOwnPropertyNames(wp), []); + assert_array_equals(Reflect.ownKeys(wp), [Symbol.toStringTag]); +}, "[[OwnPropertyKeys]]"); + +function createWindowProperties(t) { + const iframe = document.createElement("iframe"); + document.body.append(iframe); + t.add_cleanup(() => { iframe.remove(); }); + + const w = iframe.contentWindow; + const wp = Object.getPrototypeOf(w.Window.prototype); + return { w, wp }; +} + +function appendIframeWithName(w, name) { + const el = w.document.createElement("iframe"); + el.name = name; + w.document.body.append(el); + return el.contentWindow; +} + +function appendFormWithName(w, name) { + const el = w.document.createElement("form"); + el.name = name; + w.document.body.append(el); + return el; +} + +function appendElementWithId(w, id) { + const el = w.document.createElement("div"); + el.id = id; + w.document.body.append(el); + return el; +} + +function assert_prop_desc(desc, value, testInfo) { + assert_equals(typeof desc, "object", `${testInfo} typeof desc`); + assert_equals(desc.value, value, `${testInfo} [[Value]]`); + assert_true(desc.writable, `${testInfo} [[Writable]]`); + assert_false(desc.enumerable, `${testInfo} [[Enumerable]]`); + assert_true(desc.configurable, `${testInfo} [[Configurable]]`); +} + +function assert_define_own_property_fails(object, key, desc, testInfo) { + assert_throws_js(TypeError, () => { Object.defineProperty(object, key, desc); }, testInfo); + assert_false(Reflect.defineProperty(object, key, desc), testInfo); +} + +function assert_set_fails(object, key, value, testInfo) { + sloppyModeSet(object, key, value); + assert_throws_js(TypeError, () => { object[key] = value; }, testInfo); + assert_false(Reflect.set(object, key, value), testInfo); +} + +function assert_delete_fails(object, key, testInfo) { + assert_throws_js(TypeError, () => { delete object[key]; }, testInfo); + assert_false(Reflect.deleteProperty(object, key), testInfo); +} +</script> |