summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--testing/web-platform/tests/js-self-profiling/META.yml1
-rw-r--r--testing/web-platform/tests/js-self-profiling/__dir__.headers1
-rw-r--r--testing/web-platform/tests/js-self-profiling/class-getter-names.https.html30
-rw-r--r--testing/web-platform/tests/js-self-profiling/class-names.https.html30
-rw-r--r--testing/web-platform/tests/js-self-profiling/class-setter-names.https.html30
-rw-r--r--testing/web-platform/tests/js-self-profiling/concurrent-profilers.https.html25
-rw-r--r--testing/web-platform/tests/js-self-profiling/cross-origin-script-cors.sub.html25
-rw-r--r--testing/web-platform/tests/js-self-profiling/cross-origin-script-no-cors.sub.html24
-rw-r--r--testing/web-platform/tests/js-self-profiling/external-script.https.html50
-rw-r--r--testing/web-platform/tests/js-self-profiling/function-anonymous-names.https.html24
-rw-r--r--testing/web-platform/tests/js-self-profiling/function-declaration-names.https.html24
-rw-r--r--testing/web-platform/tests/js-self-profiling/function-expression-names.https.html24
-rw-r--r--testing/web-platform/tests/js-self-profiling/idlharness.https.html25
-rw-r--r--testing/web-platform/tests/js-self-profiling/iframe-context-filtration.https.html36
-rw-r--r--testing/web-platform/tests/js-self-profiling/inline-script.html46
-rw-r--r--testing/web-platform/tests/js-self-profiling/max-buffer-size.window.js49
-rw-r--r--testing/web-platform/tests/js-self-profiling/resources/__dir__.headers1
-rw-r--r--testing/web-platform/tests/js-self-profiling/resources/child-frame.html8
-rw-r--r--testing/web-platform/tests/js-self-profiling/resources/external-script.js9
-rw-r--r--testing/web-platform/tests/js-self-profiling/resources/external-script.js.headers1
-rw-r--r--testing/web-platform/tests/js-self-profiling/resources/profile-utils.js149
-rw-r--r--testing/web-platform/tests/js-self-profiling/resources/profiling-script.js26
-rw-r--r--testing/web-platform/tests/js-self-profiling/resources/profiling-script.js.headers1
-rw-r--r--testing/web-platform/tests/js-self-profiling/tentative/README.md3
-rw-r--r--testing/web-platform/tests/js-self-profiling/tentative/marker-vm-state.https.html37
-rw-r--r--testing/web-platform/tests/js-self-profiling/tentative/marker-vm-state.https.html.headers3
-rw-r--r--testing/web-platform/tests/js-self-profiling/time-domain.window.js19
-rw-r--r--testing/web-platform/tests/js-self-profiling/without-document-policy/disabled.https.html16
-rw-r--r--testing/web-platform/tests/js/META.yml3
-rw-r--r--testing/web-platform/tests/js/behaviours/HostEnsureCanAddPrivateElement.window.js129
-rw-r--r--testing/web-platform/tests/js/behaviours/SetPrototypeOf-window.html33
-rw-r--r--testing/web-platform/tests/js/behaviours/frame.html10
-rw-r--r--testing/web-platform/tests/js/builtins/Array.DefineOwnProperty.html24
-rw-r--r--testing/web-platform/tests/js/builtins/Array.prototype.join-order.html86
-rw-r--r--testing/web-platform/tests/js/builtins/Math.max.html13
-rw-r--r--testing/web-platform/tests/js/builtins/Math.maxmin.js57
-rw-r--r--testing/web-platform/tests/js/builtins/Math.min.html13
-rw-r--r--testing/web-platform/tests/js/builtins/Object.prototype.freeze.html35
-rw-r--r--testing/web-platform/tests/js/builtins/Object.prototype.getOwnPropertyNames.html56
-rw-r--r--testing/web-platform/tests/js/builtins/Object.prototype.hasOwnProperty-order.html21
-rw-r--r--testing/web-platform/tests/js/builtins/Object.prototype.hasOwnProperty-prototype-chain.html35
-rw-r--r--testing/web-platform/tests/js/builtins/Object.prototype.preventExtensions.html35
-rw-r--r--testing/web-platform/tests/js/builtins/Object.prototype.seal.html35
-rw-r--r--testing/web-platform/tests/js/builtins/Promise-incumbent-global-subframe.sub.html12
-rw-r--r--testing/web-platform/tests/js/builtins/Promise-incumbent-global-subsubframe.sub.html13
-rw-r--r--testing/web-platform/tests/js/builtins/Promise-incumbent-global.sub.html20
-rw-r--r--testing/web-platform/tests/js/builtins/Promise-subclassing.html265
-rw-r--r--testing/web-platform/tests/js/builtins/WeakMap.prototype-properties.html104
-rw-r--r--testing/web-platform/tests/js/builtins/weakrefs/cleanup-prevented-with-reference.optional.any.js51
-rw-r--r--testing/web-platform/tests/js/builtins/weakrefs/cleanup-prevented-with-unregister.optional.any.js58
-rw-r--r--testing/web-platform/tests/js/builtins/weakrefs/finalizationregistry-cleanupCallback-gets-a-microtask.optional.any.js61
-rw-r--r--testing/web-platform/tests/js/builtins/weakrefs/finalizationregistry-cleanupCallback-throws-onerror-interaction.optional.window.js70
-rw-r--r--testing/web-platform/tests/js/builtins/weakrefs/gc-has-one-chance-to-call-cleanupCallback-queueMicrotask.optional.any.js109
-rw-r--r--testing/web-platform/tests/js/builtins/weakrefs/gc-has-one-chance-to-call-cleanupCallback-queueMicrotaskMutationObserver.optional.window.js115
-rw-r--r--testing/web-platform/tests/js/builtins/weakrefs/gc-has-one-chance-to-call-cleanupCallback.optional.any.js104
-rw-r--r--testing/web-platform/tests/js/builtins/weakrefs/holdings-multiple-values.optional.any.js66
-rw-r--r--testing/web-platform/tests/js/builtins/weakrefs/reentrancy.optional.any.js51
-rw-r--r--testing/web-platform/tests/js/builtins/weakrefs/resources/maybe-garbage-collect.js72
-rw-r--r--testing/web-platform/tests/js/builtins/weakrefs/return-undefined-with-gc.optional.any.js79
-rw-r--r--testing/web-platform/tests/js/builtins/weakrefs/unregister-cleaned-up-cell.optional.any.js73
60 files changed, 2625 insertions, 0 deletions
diff --git a/testing/web-platform/tests/js-self-profiling/META.yml b/testing/web-platform/tests/js-self-profiling/META.yml
new file mode 100644
index 0000000000..84e5177d5e
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/META.yml
@@ -0,0 +1 @@
+spec: https://wicg.github.io/js-self-profiling/
diff --git a/testing/web-platform/tests/js-self-profiling/__dir__.headers b/testing/web-platform/tests/js-self-profiling/__dir__.headers
new file mode 100644
index 0000000000..93537b44de
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/__dir__.headers
@@ -0,0 +1 @@
+Document-Policy: js-profiling
diff --git a/testing/web-platform/tests/js-self-profiling/class-getter-names.https.html b/testing/web-platform/tests/js-self-profiling/class-getter-names.https.html
new file mode 100644
index 0000000000..46e2ed2748
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/class-getter-names.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src="resources/profile-utils.js"></script>
+</head>
+
+<body>
+ <script>
+ // Getter methods should use `get ${label}` as the function/frame name. Source:
+ // https://www.ecma-international.org/ecma-262/#sec-method-definitions-runtime-semantics-propertydefinitionevaluation
+ promise_test(t => ProfileUtils.testFunction(sample => {
+ class SomeClass {
+ get someValue() {
+ sample();
+ }
+ }
+ let instance = new SomeClass();
+ instance.someValue;
+ }, {
+ name: 'get someValue',
+ }
+ ), 'class getter names are logged correctly');
+ </script>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/js-self-profiling/class-names.https.html b/testing/web-platform/tests/js-self-profiling/class-names.https.html
new file mode 100644
index 0000000000..9d201b03a1
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/class-names.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src="resources/profile-utils.js"></script>
+</head>
+
+<body>
+ <script>
+ // Methods should use their label as the function/frame name. Source:
+ // https://www.ecma-international.org/ecma-262/#sec-method-definitions-runtime-semantics-propertydefinitionevaluation
+ promise_test(async t => {
+ class SomeClass {
+ method(sample) {
+ sample();
+ }
+ }
+ let instance = new SomeClass();
+
+ await ProfileUtils.testFunction(instance.method.bind(instance), {
+ name: 'method',
+ });
+ }, 'class method names are logged correctly');
+ </script>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/js-self-profiling/class-setter-names.https.html b/testing/web-platform/tests/js-self-profiling/class-setter-names.https.html
new file mode 100644
index 0000000000..f03a372460
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/class-setter-names.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src="resources/profile-utils.js"></script>
+</head>
+
+<body>
+ <script>
+ // Setter methods should use `set ${label}` as the function/frame name. Source:
+ // https://www.ecma-international.org/ecma-262/#sec-method-definitions-runtime-semantics-propertydefinitionevaluation
+ promise_test(t => ProfileUtils.testFunction(sample => {
+ class SomeClass {
+ set someValue(_) {
+ sample();
+ }
+ }
+ let instance = new SomeClass();
+ instance.someValue = 5;
+ }, {
+ name: 'set someValue',
+ }
+ ), 'class setter names are logged correctly');
+ </script>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/js-self-profiling/concurrent-profilers.https.html b/testing/web-platform/tests/js-self-profiling/concurrent-profilers.https.html
new file mode 100644
index 0000000000..26f37ec8bd
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/concurrent-profilers.https.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script>
+ promise_test(async t => {
+ const profiler_a = new Profiler({
+ sampleInterval: 10,
+ maxBufferSize: Number.MAX_SAFE_INTEGER,
+ });
+
+ const profiler_b = new Profiler({
+ sampleInterval: 10,
+ maxBufferSize: Number.MAX_SAFE_INTEGER,
+ });
+
+ const trace_b = await profiler_b.stop();
+ const trace_a = await profiler_a.stop();
+ }, 'concurrent profilers should be supported');
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/js-self-profiling/cross-origin-script-cors.sub.html b/testing/web-platform/tests/js-self-profiling/cross-origin-script-cors.sub.html
new file mode 100644
index 0000000000..f2b2c8d14e
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/cross-origin-script-cors.sub.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src="resources/profile-utils.js"></script>
+
+ <script crossorigin src="https://{{hosts[alt][]}}:{{ports[https][0]}}/js-self-profiling/resources/profiling-script.js"></script>
+</head>
+<body>
+ <script>
+ promise_test(async t => {
+ const trace = await ProfilingScript.profileBuiltinsInNewTask();
+
+ // Ensure that signal from the external script was gathered.
+ assert_equals(trace.resources.length, 1);
+ assert_equals(trace.resources[0], 'https://{{hosts[alt][]}}:{{ports[https][0]}}/js-self-profiling/resources/profiling-script.js');
+ assert_greater_than(trace.frames.length, 0);
+ assert_greater_than(trace.stacks.length, 0);
+ assert_greater_than(trace.samples.length, 0);
+ }, 'cors cross-origin script execution is not observable');
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/js-self-profiling/cross-origin-script-no-cors.sub.html b/testing/web-platform/tests/js-self-profiling/cross-origin-script-no-cors.sub.html
new file mode 100644
index 0000000000..ea6fd3384e
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/cross-origin-script-no-cors.sub.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src="resources/profile-utils.js"></script>
+
+ <script src="https://{{hosts[alt][]}}:{{ports[https][0]}}/js-self-profiling/resources/profiling-script.js"></script>
+</head>
+<body>
+ <script>
+ promise_test(async t => {
+ const trace = await ProfilingScript.profileBuiltinsInNewTask();
+
+ // Ensure that no signal from the external script was gathered.
+ assert_equals(trace.resources.length, 0);
+ assert_equals(trace.frames.length, 0);
+ assert_equals(trace.stacks.length, 0);
+ assert_greater_than(trace.samples.length, 0);
+ }, 'no-cors cross-origin script execution is not observable');
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/js-self-profiling/external-script.https.html b/testing/web-platform/tests/js-self-profiling/external-script.https.html
new file mode 100644
index 0000000000..9f9438ba6d
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/external-script.https.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src="resources/profile-utils.js"></script>
+
+ <script id="external-script" src="resources/external-script.js"></script>
+</head>
+<body>
+ <script>
+ promise_test(async t => {
+ const trace = await ProfileUtils.profileFunction(function trampoline(sample) {
+ externalScriptFunction(sample);
+ });
+
+ const scriptUrl = document.querySelector('#external-script').src;
+ assert_true(ProfileUtils.containsResource(trace, scriptUrl),
+ 'external resource is included');
+
+ const expectedTrampolineFrame = {
+ name: 'trampoline',
+ resourceId: trace.resources.indexOf(
+ location.href,
+ ),
+ };
+ const expectedExternalFrame = {
+ name: 'externalScriptFunction',
+ resourceId: trace.resources.indexOf(scriptUrl),
+ line: EXTERNAL_SCRIPT_FUNCTION_LINE,
+ column: EXTERNAL_SCRIPT_FUNCTION_COLUMN,
+ };
+
+ assert_true(ProfileUtils.containsFrame(trace, expectedTrampolineFrame),
+ 'trampoline function included');
+
+ assert_true(ProfileUtils.containsFrame(trace, expectedExternalFrame),
+ 'external script function included');
+
+ assert_true(ProfileUtils.containsSubstack(trace, [
+ externalScriptFunction,
+ expectedTrampolineFrame,
+ ]),
+ 'stack exists with external script function');
+
+ }, 'external script function details');
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/js-self-profiling/function-anonymous-names.https.html b/testing/web-platform/tests/js-self-profiling/function-anonymous-names.https.html
new file mode 100644
index 0000000000..fd7fbecc50
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/function-anonymous-names.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src="resources/profile-utils.js"></script>
+</head>
+
+<body>
+ <script>
+ promise_test(async t => {
+ const f = function (sample) {
+ sample();
+ };
+ await ProfileUtils.testFunction(f, {
+ name: '',
+ });
+ }, 'anonymous function expression names are logged correctly');
+ </script>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/js-self-profiling/function-declaration-names.https.html b/testing/web-platform/tests/js-self-profiling/function-declaration-names.https.html
new file mode 100644
index 0000000000..9cb02cbfc0
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/function-declaration-names.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src="resources/profile-utils.js"></script>
+</head>
+
+<body>
+ <script>
+ promise_test(async t => {
+ function namedFunctionDeclaration(sample) {
+ sample();
+ };
+ await ProfileUtils.testFunction(namedFunctionDeclaration, {
+ name: 'namedFunctionDeclaration',
+ });
+ }, 'function declaration names are logged correctly');
+ </script>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/js-self-profiling/function-expression-names.https.html b/testing/web-platform/tests/js-self-profiling/function-expression-names.https.html
new file mode 100644
index 0000000000..402797402e
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/function-expression-names.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src="resources/profile-utils.js"></script>
+</head>
+
+<body>
+ <script>
+ promise_test(async t => {
+ const f = function namedFunctionExpression(sample) {
+ sample();
+ };
+ await ProfileUtils.testFunction(f, {
+ name: 'namedFunctionExpression',
+ });
+ }, 'function expression names are logged correctly');
+ </script>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/js-self-profiling/idlharness.https.html b/testing/web-platform/tests/js-self-profiling/idlharness.https.html
new file mode 100644
index 0000000000..3807a6e4fa
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/idlharness.https.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+<body>
+ <script>
+ 'use strict';
+
+ idl_test(
+ ['js-self-profiling'],
+ ['hr-time', 'dom'],
+ async idl_array => {
+ idl_array.add_objects({
+ Profiler: ['profiler'],
+ });
+
+ self.profiler = new Profiler({
+ sampleInterval: 1,
+ maxBufferSize: 1,
+ });
+ }
+ );
+ </script>
diff --git a/testing/web-platform/tests/js-self-profiling/iframe-context-filtration.https.html b/testing/web-platform/tests/js-self-profiling/iframe-context-filtration.https.html
new file mode 100644
index 0000000000..4d26264211
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/iframe-context-filtration.https.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src="resources/profile-utils.js"></script>
+</head>
+<body>
+ <iframe src="resources/child-frame.html"></iframe>
+
+ <script>
+ promise_test(_ => new Promise(res => window.addEventListener('load', res)),
+ 'wait for load event');
+
+ promise_test(async t => {
+ const profiler = new Profiler({
+ sampleInterval: 10,
+ maxBufferSize: Number.MAX_SAFE_INTEGER,
+ });
+
+ const iframe = frames[0];
+ await ProfileUtils.forceSampleFrame(iframe);
+
+ const trace = await profiler.stop();
+
+ assert_false(ProfileUtils.containsFrame(trace, { name: 'sampleFromMessage' }),
+ 'function from child context not included in trace');
+
+ const childUrl = iframe.src;
+ assert_false(ProfileUtils.containsResource(trace, childUrl),
+ 'child resources are not included');
+ }, 'functions from child frame are not included in profile created by parent frame');
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/js-self-profiling/inline-script.html b/testing/web-platform/tests/js-self-profiling/inline-script.html
new file mode 100644
index 0000000000..56a7b55c6b
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/inline-script.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src="resources/profile-utils.js"></script>
+</head>
+<body>
+ <script>
+ // Note: moving these function definitions will change the expected
+ // outcomes below.
+ function nestedInlineScriptFunction(sample) {
+ sample();
+ }
+
+ function inlineScriptFunction(sample) {
+ nestedInlineScriptFunction(sample);
+ }
+ </script>
+
+ <script>
+ promise_test(async t => {
+ const trace = await ProfileUtils.profileFunction(inlineScriptFunction);
+
+ assert_true(ProfileUtils.containsResource(trace, location.href),
+ 'inline script resource is included');
+
+ assert_true(ProfileUtils.containsSubstack(trace, [
+ {
+ name: 'nestedInlineScriptFunction',
+ resourceId: trace.resources.indexOf(location.href),
+ line: 13,
+ column: 40,
+ },
+ {
+ name: 'inlineScriptFunction',
+ resourceId: trace.resources.indexOf(location.href),
+ line: 17,
+ column: 34,
+ },
+ ]));
+ }, 'inline script function details');
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/js-self-profiling/max-buffer-size.window.js b/testing/web-platform/tests/js-self-profiling/max-buffer-size.window.js
new file mode 100644
index 0000000000..659d4cd14d
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/max-buffer-size.window.js
@@ -0,0 +1,49 @@
+// META: script=resources/profile-utils.js
+
+promise_test(async t => {
+ assert_throws_js(TypeError, () => {
+ new Profiler({ sampleInterval: 10 });
+ });
+}, 'max buffer size must be defined');
+
+promise_test(async t => {
+ const profiler = new Profiler({
+ sampleInterval: 10,
+ maxBufferSize: 2,
+ });
+
+ // Force 3 samples with a max buffer size of 2.
+ for (let i = 0; i < 3; i++) {
+ ProfileUtils.forceSample();
+ }
+
+ const trace = await profiler.stop();
+ assert_equals(trace.samples.length, 2);
+}, 'max buffer size is not exceeded');
+
+promise_test(async t => {
+ const largeBufferProfiler = new Profiler({ sampleInterval: 10, maxBufferSize: Number.MAX_SAFE_INTEGER });
+ const smallBufferProfiler = new Profiler({ sampleInterval: 10, maxBufferSize: 1 });
+
+ const watcher = new EventWatcher(t, smallBufferProfiler, ['samplebufferfull']);
+
+ largeBufferProfiler.addEventListener('samplebufferfull', () => {
+ assert_unreached('samplebufferfull invoked on wrong profiler');
+ largeBufferProfiler.stop();
+ smallBufferProfiler.stop();
+ });
+
+ smallBufferProfiler.addEventListener('samplebufferfull', () => {
+ largeBufferProfiler.stop();
+ smallBufferProfiler.stop();
+ });
+
+ // Force two samples to be taken, which should exceed
+ // |smallBufferProfiler|'s buffer size.
+ for (let i = 0; i < 2; i++) {
+ ProfileUtils.forceSample();
+ }
+
+ // Ensure that |smallBufferProfiler|'s buffer size is exceeded.
+ await watcher.wait_for('samplebufferfull');
+}, 'ensure samplebufferfull is fired on full profiler');
diff --git a/testing/web-platform/tests/js-self-profiling/resources/__dir__.headers b/testing/web-platform/tests/js-self-profiling/resources/__dir__.headers
new file mode 100644
index 0000000000..93537b44de
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/resources/__dir__.headers
@@ -0,0 +1 @@
+Document-Policy: js-profiling
diff --git a/testing/web-platform/tests/js-self-profiling/resources/child-frame.html b/testing/web-platform/tests/js-self-profiling/resources/child-frame.html
new file mode 100644
index 0000000000..7f8125eae7
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/resources/child-frame.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+ <script src="profile-utils.js"></script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/js-self-profiling/resources/external-script.js b/testing/web-platform/tests/js-self-profiling/resources/external-script.js
new file mode 100644
index 0000000000..6f6bd42b25
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/resources/external-script.js
@@ -0,0 +1,9 @@
+// NOTE: Modifying the location of functions in this file will cause
+// `external-script.html` to fail! Please update the following constants
+// accordingly.
+const EXTERNAL_SCRIPT_FUNCTION_LINE = 7;
+const EXTERNAL_SCRIPT_FUNCTION_COLUMN = 32;
+
+function externalScriptFunction(sample) {
+ sample();
+}
diff --git a/testing/web-platform/tests/js-self-profiling/resources/external-script.js.headers b/testing/web-platform/tests/js-self-profiling/resources/external-script.js.headers
new file mode 100644
index 0000000000..cb762eff80
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/resources/external-script.js.headers
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: *
diff --git a/testing/web-platform/tests/js-self-profiling/resources/profile-utils.js b/testing/web-platform/tests/js-self-profiling/resources/profile-utils.js
new file mode 100644
index 0000000000..2c32f34608
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/resources/profile-utils.js
@@ -0,0 +1,149 @@
+(function(global) {
+ const TEST_SAMPLE_INTERVAL = 10;
+ const ENSURE_SAMPLE_SPIN_WAIT_MS = 500;
+
+ function forceSample() {
+ // Spin for |TEST_SAMPLE_INTERVAL + 500|ms to ensure that a sample occurs
+ // before this function returns. As periodic sampling is enforced by a
+ // SHOULD clause, it is indeed testable.
+ //
+ // More reliable sampling will be handled in a future testdriver RFC
+ // (https://github.com/web-platform-tests/rfcs/pull/81).
+ for (const deadline = performance.now() + TEST_SAMPLE_INTERVAL +
+ ENSURE_SAMPLE_SPIN_WAIT_MS;
+ performance.now() < deadline;)
+ ;
+ }
+
+ // Creates a new profile that captures the execution of when the given
+ // function calls the `sample` function passed to it.
+ async function profileFunction(func) {
+ const profiler = new Profiler({
+ sampleInterval: TEST_SAMPLE_INTERVAL,
+ maxBufferSize: Number.MAX_SAFE_INTEGER,
+ });
+
+ func(() => forceSample());
+
+ const trace = await profiler.stop();
+
+ // Sanity check ensuring that we captured a sample.
+ assert_greater_than(trace.resources.length, 0);
+ assert_greater_than(trace.frames.length, 0);
+ assert_greater_than(trace.stacks.length, 0);
+ assert_greater_than(trace.samples.length, 0);
+
+ return trace;
+ }
+
+ async function testFunction(func, frame) {
+ const trace = await profileFunction(func);
+ assert_true(containsFrame(trace, frame), 'trace contains frame');
+ }
+
+ function substackMatches(trace, stackId, expectedStack) {
+ if (expectedStack.length === 0) {
+ return true;
+ }
+ if (stackId === undefined) {
+ return false;
+ }
+
+ const stackElem = trace.stacks[stackId];
+ const expectedFrame = expectedStack[0];
+
+ if (!frameMatches(trace.frames[stackElem.frameId], expectedFrame)) {
+ return false;
+ }
+ return substackMatches(trace, stackElem.parentId, expectedStack.slice(1));
+ }
+
+ // Returns true if the trace contains a frame matching the given specification.
+ // We define a "match" as follows: a frame A matches an expectation E if (and
+ // only if) for each field of E, A has the same value.
+ function containsFrame(trace, expectedFrame) {
+ return trace.frames.find(frame => {
+ return frameMatches(frame, expectedFrame);
+ }) !== undefined;
+ }
+
+ // Returns true if a trace contains a substack in one of its samples, ordered
+ // leaf to root.
+ function containsSubstack(trace, expectedStack) {
+ return trace.samples.find(sample => {
+ let stackId = sample.stackId;
+ while (stackId !== undefined) {
+ if (substackMatches(trace, stackId, expectedStack)) {
+ return true;
+ }
+ stackId = trace.stacks[stackId].parentId;
+ }
+ return false;
+ }) !== undefined;
+ }
+
+ function containsResource(trace, expectedResource) {
+ return trace.resources.includes(expectedResource);
+ }
+
+ // Returns true if a trace contains a sample matching the given specification.
+ // We define a "match" as follows: a sample A matches an expectation E if (and
+ // only if) for each field of E, A has the same value.
+ function containsSample(trace, expectedSample) {
+ return trace.samples.find(sample => {
+ return sampleMatches(sample, expectedSample);
+ }) !== undefined;
+ }
+
+ // Compares each set field of `expected` against the given frame `actual`.
+ function sampleMatches(actual, expected) {
+ return (expected.timestamp === undefined ||
+ expected.timestamp === actual.timestamp) &&
+ (expected.stackId === undefined ||
+ expected.stackId === actual.stackId) &&
+ (expected.marker === undefined || expected.marker === actual.marker);
+ }
+
+ // Compares each set field of `expected` against the given frame `actual`.
+ function frameMatches(actual, expected) {
+ return (expected.name === undefined || expected.name === actual.name) &&
+ (expected.resourceId === undefined || expected.resourceId === actual.resourceId) &&
+ (expected.line === undefined || expected.line === actual.line) &&
+ (expected.column === undefined || expected.column === actual.column);
+ }
+
+ function forceSampleFrame(frame) {
+ const channel = new MessageChannel();
+ const replyPromise = new Promise(res => {
+ channel.port1.onmessage = res;
+ });
+ frame.postMessage('', '*', [channel.port2]);
+ return replyPromise;
+ }
+
+ window.addEventListener('message', message => {
+ // Force sample in response to messages received.
+ (function sampleFromMessage() {
+ ProfileUtils.forceSample();
+ message.ports[0].postMessage('');
+ })();
+ });
+
+ global.ProfileUtils = {
+ // Capturing
+ profileFunction,
+ forceSample,
+
+ // Containment checks
+ containsFrame,
+ containsSubstack,
+ containsResource,
+ containsSample,
+
+ // Cross-frame sampling
+ forceSampleFrame,
+
+ // Assertions
+ testFunction,
+ };
+})(this);
diff --git a/testing/web-platform/tests/js-self-profiling/resources/profiling-script.js b/testing/web-platform/tests/js-self-profiling/resources/profiling-script.js
new file mode 100644
index 0000000000..9f09e05b34
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/resources/profiling-script.js
@@ -0,0 +1,26 @@
+(function(global) {
+ let counter = 0;
+
+ // Spins up a new profiler and performs some work in a new top-level task,
+ // calling some builtins. Returns a promise for the resulting trace.
+ const profileBuiltinsInNewTask = () => {
+ // Run profiling logic in a new task to eliminate the caller stack.
+ return new Promise(resolve => {
+ setTimeout(async () => {
+ const profiler = new Profiler({ sampleInterval: 10, maxBufferSize: 10000 });
+ for (const deadline = performance.now() + 500; performance.now() < deadline;) {
+ // Run a range of builtins to ensure they get included in the trace.
+ // Store this computation in a variable to prevent getting optimized out.
+ counter += Math.random();
+ counter += performance.now();
+ }
+ const trace = await profiler.stop();
+ resolve(trace);
+ });
+ });
+ }
+
+ global.ProfilingScript = {
+ profileBuiltinsInNewTask,
+ }
+})(window);
diff --git a/testing/web-platform/tests/js-self-profiling/resources/profiling-script.js.headers b/testing/web-platform/tests/js-self-profiling/resources/profiling-script.js.headers
new file mode 100644
index 0000000000..cb762eff80
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/resources/profiling-script.js.headers
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: *
diff --git a/testing/web-platform/tests/js-self-profiling/tentative/README.md b/testing/web-platform/tests/js-self-profiling/tentative/README.md
new file mode 100644
index 0000000000..a6b8d008f2
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/tentative/README.md
@@ -0,0 +1,3 @@
+Tests in this directory are for the proposed js-self-profiling API marker extension. This is not yet standardised and browsers should not be expected to pass these tests.
+
+See the explainer at https://github.com/WICG/js-self-profiling/blob/main/markers.md for more information about the API.
diff --git a/testing/web-platform/tests/js-self-profiling/tentative/marker-vm-state.https.html b/testing/web-platform/tests/js-self-profiling/tentative/marker-vm-state.https.html
new file mode 100644
index 0000000000..07dbe7f0ff
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/tentative/marker-vm-state.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src="../resources/profile-utils.js"></script>
+</head>
+
+<body>
+
+ <script>
+ // Note: moving these function definitions will change the expected
+ // outcomes below.
+ function scriptFunction(sample) {
+ sample();
+ }
+ </script>
+
+ <script>
+ promise_test(async t => {
+ // *.headers file should ensure we sesrve COOP and COEP headers.
+ assert_true(self.crossOriginIsolated,
+ "Cross origin isolation is required to surface markers");
+ const trace = await ProfileUtils.profileFunction(scriptFunction);
+
+ assert_true(ProfileUtils.containsSample(trace, {
+ stackId: 7,
+ marker: 'script'
+ }));
+ }, 'markers logged correctly');
+
+ </script>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/js-self-profiling/tentative/marker-vm-state.https.html.headers b/testing/web-platform/tests/js-self-profiling/tentative/marker-vm-state.https.html.headers
new file mode 100644
index 0000000000..228047a15a
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/tentative/marker-vm-state.https.html.headers
@@ -0,0 +1,3 @@
+Document-Policy: js-profiling
+Cross-Origin-Embedder-Policy: require-corp
+Cross-Origin-Opener-Policy: same-origin
diff --git a/testing/web-platform/tests/js-self-profiling/time-domain.window.js b/testing/web-platform/tests/js-self-profiling/time-domain.window.js
new file mode 100644
index 0000000000..5791a3de75
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/time-domain.window.js
@@ -0,0 +1,19 @@
+// META: script=resources/profile-utils.js
+
+promise_test(async () => {
+ const start = performance.now();
+
+ const profiler = new Profiler({
+ sampleInterval: 10,
+ maxBufferSize: Number.MAX_SAFE_INTEGER,
+ });
+ ProfileUtils.forceSample();
+ const trace = await profiler.stop();
+
+ const end = performance.now();
+
+ assert_greater_than(trace.samples.length, 0);
+ for (const sample of trace.samples) {
+ assert_between_inclusive(sample.timestamp, start, end);
+ }
+}, 'sample timestamps use the current high-resolution time');
diff --git a/testing/web-platform/tests/js-self-profiling/without-document-policy/disabled.https.html b/testing/web-platform/tests/js-self-profiling/without-document-policy/disabled.https.html
new file mode 100644
index 0000000000..bff9851263
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/without-document-policy/disabled.https.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script>
+ test(t => {
+ assert_throws_dom('NotAllowedError', () => {
+ new Profiler({ sampleInterval: 10, maxBufferSize: Number.MAX_SAFE_INTEGER });
+ });
+ }, 'profiling should throw without passing document policy');
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/js/META.yml b/testing/web-platform/tests/js/META.yml
new file mode 100644
index 0000000000..555eb7fe2f
--- /dev/null
+++ b/testing/web-platform/tests/js/META.yml
@@ -0,0 +1,3 @@
+spec: https://tc39.es/ecma262/
+suggested_reviewers:
+ - Ms2ger
diff --git a/testing/web-platform/tests/js/behaviours/HostEnsureCanAddPrivateElement.window.js b/testing/web-platform/tests/js/behaviours/HostEnsureCanAddPrivateElement.window.js
new file mode 100644
index 0000000000..03435fa37a
--- /dev/null
+++ b/testing/web-platform/tests/js/behaviours/HostEnsureCanAddPrivateElement.window.js
@@ -0,0 +1,129 @@
+// META: script=/common/get-host-info.sub.js
+
+// HTML PR https://github.com/whatwg/html/pull/8198 adds a definition for the
+// HostEnsureCanAddPrivateElement host hook which disallows private fields on
+// WindowProxy and Location objects.
+//
+// This test case ensure the hook works as designed.
+
+let host_info = get_host_info();
+
+const path = location.pathname.substring(0, location.pathname.lastIndexOf('/')) + '/frame.html';
+const path_setdomain = path + "?setdomain";
+
+class Base {
+ constructor(o) {
+ return o;
+ }
+}
+
+class Stamper extends Base {
+ #x = 10;
+ static hasX(o) { return #x in o; }
+};
+
+function test_iframe_window(a_src, b_src) {
+ const iframe = document.body.appendChild(document.createElement("iframe"));
+
+ var resolve, reject;
+ var promise = new Promise((res, rej) => {
+ resolve = res;
+ reject = rej
+ });
+
+ iframe.src = a_src;
+ iframe.onload = () => {
+ const windowA = iframe.contentWindow;
+ try {
+ assert_throws_js(TypeError, () => {
+ new Stamper(windowA);
+ }, "Can't Stamp (maybe cross-origin) exotic WindowProxy");
+ assert_equals(Stamper.hasX(windowA), false, "Didn't stamp on WindowProxy");
+ } catch (e) {
+ reject(e);
+ return;
+ }
+
+ iframe.src = b_src;
+ iframe.onload = () => {
+ const windowB = iframe.contentWindow;
+ try {
+ assert_equals(windowA == windowB, true, "Window is same")
+ assert_throws_js(TypeError, () => {
+ new Stamper(windowA);
+ }, "Can't Stamp (maybe cross-origin) exotics on WindowProxy");
+ assert_equals(Stamper.hasX(windowB), false, "Didn't stamp on WindowProxy");
+ } catch (e) {
+ reject(e);
+ return;
+ }
+ resolve();
+ }
+ };
+
+ return promise;
+}
+
+
+function test_iframe_location(a_src, b_src) {
+ const iframe = document.body.appendChild(document.createElement("iframe"));
+
+ var resolve, reject;
+ var promise = new Promise((res, rej) => {
+ resolve = res;
+ reject = rej
+ });
+
+ iframe.src = a_src;
+ iframe.onload = () => {
+ const locA = iframe.contentWindow.location;
+ try {
+ assert_throws_js(TypeError, () => {
+ new Stamper(locA);
+ }, "Can't Stamp (maybe cross-origin) exotic Location");
+ assert_equals(Stamper.hasX(locA), false, "Didn't stamp on Location");
+ } catch (e) {
+ reject(e);
+ return;
+ }
+
+ iframe.src = b_src;
+ iframe.onload = () => {
+ const locB = iframe.contentWindow.location
+ try {
+ assert_throws_js(TypeError, () => {
+ new Stamper(locB);
+ }, "Can't Stamp cross-origin exotic Location");
+ assert_equals(Stamper.hasX(locB), false, "Didn't stamp on Location");
+ } catch (e) {
+ reject(e);
+ return;
+ }
+ resolve();
+ }
+ };
+
+ return promise;
+}
+
+promise_test(() => test_iframe_window(host_info.HTTP_ORIGIN, host_info.HTTP_ORIGIN), "Same Origin: WindowProxy")
+promise_test(() => test_iframe_window(host_info.HTTP_ORIGIN, host_info.HTTP_ORIGIN_WITH_DIFFERENT_PORT), "Cross Origin (port): WindowProxy")
+promise_test(() => test_iframe_window(host_info.HTTP_ORIGIN, host_info.HTTP_REMOTE_ORIGIN), "Cross Origin (remote): WindowProxy")
+promise_test(() => test_iframe_window(path, path_setdomain), "Same Origin + document.domain WindowProxy")
+
+
+promise_test(() => test_iframe_location(host_info.HTTP_ORIGIN, host_info.HTTP_ORIGIN), "Same Origin: Location")
+promise_test(() => test_iframe_location(host_info.HTTP_ORIGIN, host_info.HTTP_ORIGIN_WITH_DIFFERENT_PORT), "Cross Origin (remote): Location")
+promise_test(() => test_iframe_location(host_info.HTTP_ORIGIN, host_info.HTTP_REMOTE_ORIGIN), "Cross Origin: Location")
+promise_test(() => test_iframe_location(path, path_setdomain), "Same Origin + document.domain: Location")
+
+// We can do this because promise_test promises to queue tests
+// https://web-platform-tests.org/writing-tests/testharness-api.html#promise-tests
+
+promise_test(async () => document.domain = document.domain, "Set document.domain");
+
+promise_test(() => test_iframe_location(path, path_setdomain), "(After document.domain set) Same Origin + document.domain: Location")
+promise_test(() => test_iframe_window(path, path_setdomain), "(After document.domain set) Same Origin + document.domain WindowProxy does carry private fields after navigation")
+
+promise_test(() => test_iframe_location(path_setdomain, path_setdomain), "(After document.domain set) Local navigation (setdomain) Location")
+promise_test(() => test_iframe_window(path_setdomain, path_setdomain), "(After document.domain set) Local navigation (setdomain) WindowProxy does carry private fields after navigation")
diff --git a/testing/web-platform/tests/js/behaviours/SetPrototypeOf-window.html b/testing/web-platform/tests/js/behaviours/SetPrototypeOf-window.html
new file mode 100644
index 0000000000..f104ca107c
--- /dev/null
+++ b/testing/web-platform/tests/js/behaviours/SetPrototypeOf-window.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for [[SetPrototypeOf]] with Windows</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ assert_throws_js(TypeError, function() {
+ Object.setPrototypeOf(window, window);
+ }, "Setting the prototype should throw");
+}, "Setting the prototype of a window to itself via setPrototypeOf");
+
+test(function() {
+ assert_throws_js(TypeError, function() {
+ window.__proto__ = window;
+ }, "Setting the prototype should throw");
+}, "Setting the prototype of a window to itself via __proto__");
+
+test(function() {
+ assert_throws_js(TypeError, function() {
+ Object.setPrototypeOf(window, Object.create(window));
+ }, "Setting the prototype should throw");
+}, "Setting the prototype of a window to something that has the window on " +
+ "its proto chain via setPrototypeOf");
+
+test(function() {
+ assert_throws_js(TypeError, function() {
+ window.__proto__ = Object.create(window);
+ }, "Setting the prototype should throw");
+}, "Setting the prototype of a window to something that has the window on " +
+ "its proto chain via __proto__");
+</script>
diff --git a/testing/web-platform/tests/js/behaviours/frame.html b/testing/web-platform/tests/js/behaviours/frame.html
new file mode 100644
index 0000000000..c06d1bf2b9
--- /dev/null
+++ b/testing/web-platform/tests/js/behaviours/frame.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<html>
+<head>
+<script>
+ if (location.search == "?setdomain") {
+ document.domain = document.domain;
+ }
+</script>
+</head>
+</html>
diff --git a/testing/web-platform/tests/js/builtins/Array.DefineOwnProperty.html b/testing/web-platform/tests/js/builtins/Array.DefineOwnProperty.html
new file mode 100644
index 0000000000..40ed00a4c7
--- /dev/null
+++ b/testing/web-platform/tests/js/builtins/Array.DefineOwnProperty.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<title>Array.[[DefineOwnProperty]]</title>
+<link rel=author href=mailto:Ms2ger@gmail.com title=Ms2ger>
+<link rel=help href=http://es5.github.com/#x15.4.5.1>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+test(function() {
+ var arr = new Array;
+ assert_equals(arr.length, 0);
+
+ var called = 0;
+ Object.defineProperty(arr, 0, { get: function() { ++called; return 7 } });
+ assert_equals(arr.length, 1);
+ assert_equals(called, 0);
+
+ assert_equals(arr[0], 7);
+ assert_equals(called, 1);
+
+ assert_equals(String(arr), "7");
+ assert_equals(called, 2);
+});
+</script>
diff --git a/testing/web-platform/tests/js/builtins/Array.prototype.join-order.html b/testing/web-platform/tests/js/builtins/Array.prototype.join-order.html
new file mode 100644
index 0000000000..ce091a5e54
--- /dev/null
+++ b/testing/web-platform/tests/js/builtins/Array.prototype.join-order.html
@@ -0,0 +1,86 @@
+<!doctype html>
+<title>Array.prototype.join</title>
+<link rel=author href=mailto:Ms2ger@gmail.com title=Ms2ger>
+<link rel=help href=http://es5.github.com/#x15.4.4.5>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+
+<div id=log></div>
+<script>
+var test_error = { name: "test" };
+
+// Step 1.
+test(function() {
+ assert_throws_js(TypeError, function() {
+ [].join.call(null, {
+ toString: function() { assert_unreached(); }
+ });
+ });
+ assert_throws_js(TypeError, function() {
+ [].join.call(undefined, {
+ toString: function() { assert_unreached(); }
+ });
+ });
+}, "Array.prototype.join must call ToObject before looking at the separator argument.")
+
+var generateTest = function(throwing_getter, getter_name) {
+ var throwing_object = {};
+ var interfaces = [Boolean, Number];
+
+ var objects = interfaces.map(function(p) { return p.prototype; });
+ objects.push(throwing_object);
+ objects.forEach(function(object) {
+ Object.defineProperty(object, "length", {
+ get: throwing_getter,
+ configurable: true
+ });
+ });
+
+ [throwing_object, true, false, 0, 1, Math.PI].forEach(function(that) {
+ test(function() {
+ assert_throws_exactly(test_error, function() {
+ [].join.call(that, ",");
+ });
+ }, "Array.prototype.join must forward the exception from the this " +
+ "object's length property with this=" + format_value(that) + " and " +
+ "getter " + getter_name + ".")
+ test(function() {
+ assert_throws_exactly(test_error, function() {
+ [].join.call(that, {
+ toString: function() { assert_unreached(); }
+ });
+ });
+ }, "Array.prototype.join must get the this object's length property " +
+ "before looking at the separator argument with this=" +
+ format_value(that) + " and getter " + getter_name + ".")
+ });
+ interfaces.forEach(function(iface) {
+ delete iface.length;
+ });
+}
+
+// Step 2.
+test(function() {
+ generateTest(function() { throw test_error; }, "function");
+}, "Step 2.");
+
+// Step 3.
+test(function() {
+ generateTest(function() {
+ return {
+ valueOf: function() { throw test_error; }
+ };
+ }, "valueOf");
+ generateTest(function() {
+ return {
+ toString: function() { throw test_error; }
+ };
+ }, "toString");
+ generateTest(function() {
+ return {
+ valueOf: function() { throw test_error; },
+ toString: function() { assert_unreached("toString should not be invoked if valueOf exists"); }
+ };
+ }, "valueOf and toString");
+}, "Step 3.");
+</script>
diff --git a/testing/web-platform/tests/js/builtins/Math.max.html b/testing/web-platform/tests/js/builtins/Math.max.html
new file mode 100644
index 0000000000..a4a6ae27c8
--- /dev/null
+++ b/testing/web-platform/tests/js/builtins/Math.max.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<title>Math.max</title>
+<link rel=author href=mailto:Ms2ger@gmail.com title=Ms2ger>
+<link rel=help href=http://es5.github.com/#x15.8.2>
+<link rel=help href=http://es5.github.com/#x15.8.2.11>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+
+<div id=log></div>
+<script src=Math.maxmin.js></script>
+<script>
+testMathMaxMin("max");
+</script>
diff --git a/testing/web-platform/tests/js/builtins/Math.maxmin.js b/testing/web-platform/tests/js/builtins/Math.maxmin.js
new file mode 100644
index 0000000000..bf7b2cd8cd
--- /dev/null
+++ b/testing/web-platform/tests/js/builtins/Math.maxmin.js
@@ -0,0 +1,57 @@
+function testMathMaxMin(aFun) {
+ var test_error = { name: "test" };
+ test(function() {
+ assert_throws_exactly(test_error, function() {
+ Math[aFun](NaN, {
+ valueOf: function() {
+ throw test_error;
+ }
+ });
+ });
+ }, "ToNumber should be called on all arguments: NaN.");
+ test(function() {
+ assert_throws_exactly(test_error, function() {
+ Math[aFun](-Infinity, {
+ valueOf: function() {
+ throw test_error;
+ }
+ });
+ });
+ }, "ToNumber should be called on all arguments: -Infinity.");
+ test(function() {
+ assert_throws_exactly(test_error, function() {
+ Math[aFun](Infinity, {
+ valueOf: function() {
+ throw test_error;
+ }
+ });
+ });
+ }, "ToNumber should be called on all arguments: Infinity.");
+ test(function() {
+ assert_throws_exactly(test_error, function() {
+ Math[aFun]({
+ valueOf: function() {
+ throw test_error;
+ }
+ },
+ {
+ valueOf: function() {
+ throw 7;
+ }
+ });
+ });
+ }, "ToNumber should be called left to right.");
+ test(function() {
+ assert_equals(Math[aFun]("1"), 1);
+ }, "Should return a number.");
+ test(function() {
+ var expected = {
+ "max": 0,
+ "min": -0
+ }
+ assert_equals(Math[aFun](0, -0), expected[aFun]);
+ assert_equals(Math[aFun](-0, 0), expected[aFun]);
+ assert_equals(Math[aFun](0, 0), 0);
+ assert_equals(Math[aFun](-0, -0), -0);
+ }, "Should handle negative zero correctly.");
+}
diff --git a/testing/web-platform/tests/js/builtins/Math.min.html b/testing/web-platform/tests/js/builtins/Math.min.html
new file mode 100644
index 0000000000..4ae71b9d76
--- /dev/null
+++ b/testing/web-platform/tests/js/builtins/Math.min.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<title>Math.min</title>
+<link rel=author href=mailto:Ms2ger@gmail.com title=Ms2ger>
+<link rel=help href=http://es5.github.com/#x15.8.2>
+<link rel=help href=http://es5.github.com/#x15.8.2.12>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+
+<div id=log></div>
+<script src=Math.maxmin.js></script>
+<script>
+testMathMaxMin("min");
+</script>
diff --git a/testing/web-platform/tests/js/builtins/Object.prototype.freeze.html b/testing/web-platform/tests/js/builtins/Object.prototype.freeze.html
new file mode 100644
index 0000000000..1e5ed418a9
--- /dev/null
+++ b/testing/web-platform/tests/js/builtins/Object.prototype.freeze.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Object.freeze</title>
+<link rel="author" title="Masaya Iseki" href="mailto:iseki.m.aa@gmail.com">
+<link rel="help" href="https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.freeze">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+test(function() {
+ [{}, []].forEach(function(that) {
+ assert_false(Object.isFrozen(that));
+ that.prop = 'exist';
+
+ Object.freeze(that);
+ assert_false(Object.isExtensible(that));
+ assert_true(Object.isSealed(that));
+ assert_true(Object.isFrozen(that));
+
+ that.extension = 'This property should not be added';
+ assert_equals(undefined, that.extension, 'Confirm to prevent adding property.');
+
+ that.prop = 'changed';
+ assert_equals('exist', that.prop,
+ 'Confirm to prevent changing a property value.');
+
+ delete that.prop;
+ assert_equals('exist', that.prop, 'Confirm to prevent deleting a property.');
+ });
+});
+</script>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/js/builtins/Object.prototype.getOwnPropertyNames.html b/testing/web-platform/tests/js/builtins/Object.prototype.getOwnPropertyNames.html
new file mode 100644
index 0000000000..582f41ba10
--- /dev/null
+++ b/testing/web-platform/tests/js/builtins/Object.prototype.getOwnPropertyNames.html
@@ -0,0 +1,56 @@
+<!doctype html>
+<title>Object.prototype.getOwnPropertyNames</title>
+<link rel=help href=http://es5.github.io/#x15.2.3.4>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+
+<div id=log></div>
+<script>
+test(function () {
+ var obj = {0: 'a', 1: 'b', 2: 'c'};
+ assert_array_equals(
+ Object.getOwnPropertyNames(obj).sort(),
+ ['0', '1', '2']
+ );
+}, "object");
+
+test(function () {
+ var arr = ['a', 'b', 'c'];
+ assert_array_equals(
+ Object.getOwnPropertyNames(arr).sort(),
+ ['0', '1', '2', 'length']
+ );
+}, "array-like");
+
+test(function () {
+ var obj = Object.create({}, {
+ getFoo: {
+ value: function() { return this.foo; },
+ enumerable: false
+ }
+ });
+ obj.foo = 1;
+ assert_array_equals(
+ Object.getOwnPropertyNames(obj).sort(),
+ ['foo', 'getFoo']
+ );
+}, "non-enumerable property");
+
+test(function() {
+ function ParentClass() {}
+ ParentClass.prototype.inheritedMethod = function() {};
+
+ function ChildClass() {
+ this.prop = 5;
+ this.method = function() {};
+ }
+ ChildClass.prototype = new ParentClass;
+ ChildClass.prototype.prototypeMethod = function() {};
+
+ var obj = new ChildClass;
+ assert_array_equals(
+ Object.getOwnPropertyNames(obj).sort(),
+ ['method', 'prop']
+ );
+}, 'items on the prototype chain are not listed');
+</script>
diff --git a/testing/web-platform/tests/js/builtins/Object.prototype.hasOwnProperty-order.html b/testing/web-platform/tests/js/builtins/Object.prototype.hasOwnProperty-order.html
new file mode 100644
index 0000000000..50325d9ea9
--- /dev/null
+++ b/testing/web-platform/tests/js/builtins/Object.prototype.hasOwnProperty-order.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<title>Object.prototype.hasOwnProperty</title>
+<link rel=author href=mailto:Ms2ger@gmail.com title=Ms2ger>
+<link rel=help href=http://es5.github.com/#x15.4.4.5>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+
+<div id=log></div>
+<script>
+var test_error = { name: "test" };
+
+test(function() {
+ [null, undefined, {}].forEach(function(that) {
+ test(function() {
+ assert_throws_exactly(test_error, function() {
+ ({}).hasOwnProperty.call(that, { toString: function() { throw test_error; } });
+ });
+ });
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/js/builtins/Object.prototype.hasOwnProperty-prototype-chain.html b/testing/web-platform/tests/js/builtins/Object.prototype.hasOwnProperty-prototype-chain.html
new file mode 100644
index 0000000000..7c02257fbc
--- /dev/null
+++ b/testing/web-platform/tests/js/builtins/Object.prototype.hasOwnProperty-prototype-chain.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Object.prototype.hasOwnProperty: Check prototype chain</title>
+<link rel="author" title="Masaya Iseki" href="mailto:iseki.m.aa@gmail.com">
+<link rel="help" href="https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.prototype.hasownproperty">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+test(function() {
+ [{}, []].forEach(function(that) {
+ that.prop = 'exists';
+ assert_true(that.hasOwnProperty('prop'));
+ assert_true('hasOwnProperty' in that);
+ assert_false(that.hasOwnProperty('hasOwnProperty'));
+ });
+});
+
+test(function() {
+ ['foo', 42].forEach(function(that) {
+ assert_false(that.hasOwnProperty('hasOwnProperty'));
+ });
+});
+
+test(function() {
+ [null, undefined].forEach(function(that) {
+ assert_throws_js(TypeError,
+ function() { that.hasOwnProperty('hasOwnProperty'); });
+ });
+});
+</script>
+
+</body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/js/builtins/Object.prototype.preventExtensions.html b/testing/web-platform/tests/js/builtins/Object.prototype.preventExtensions.html
new file mode 100644
index 0000000000..ceea7b3dd1
--- /dev/null
+++ b/testing/web-platform/tests/js/builtins/Object.prototype.preventExtensions.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Object.preventExtensions</title>
+<link rel="author" title="Masaya Iseki" href="mailto:iseki.m.aa@gmail.com">
+<link rel="help" href="https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.preventextensions">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+test(function() {
+ [{}, []].forEach(function(that){
+ assert_true(Object.isExtensible(that));
+ that.prop = 'exist';
+
+ Object.preventExtensions(that);
+ assert_false(Object.isExtensible(that));
+ assert_false(Object.isFrozen(that));
+ assert_false(Object.isSealed(that));
+
+ that.extension = 'This property should not be added';
+ assert_equals(undefined, that.extension, 'Confirm to prevent adding property.');
+
+ that.prop = 'changed';
+ assert_equals('changed', that.prop,
+ 'Confirm to be able to change a property value.');
+
+ delete that.prop;
+ assert_equals(undefined, that.prop, 'Confirm to be able to delete a property.');
+ });
+});
+</script>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/js/builtins/Object.prototype.seal.html b/testing/web-platform/tests/js/builtins/Object.prototype.seal.html
new file mode 100644
index 0000000000..ad84d8c218
--- /dev/null
+++ b/testing/web-platform/tests/js/builtins/Object.prototype.seal.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Object.seal</title>
+<link rel="author" title="Masaya Iseki" href="mailto:iseki.m.aa@gmail.com">
+<link rel="help" href="https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.seal">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+test(function() {
+ [{}, []].forEach(function(that) {
+ assert_false(Object.isSealed(that));
+ that.prop = 'exist';
+
+ Object.seal(that);
+ assert_false(Object.isExtensible(that));
+ assert_true(Object.isSealed(that));
+ assert_false(Object.isFrozen(that));
+
+ that.extension = 'This property should not be added';
+ assert_equals(undefined, that.extension, 'Confirm to prevent adding property.');
+
+ that.prop = 'changed';
+ assert_equals('changed', that.prop,
+ 'Confirm to be able to change a property value.');
+
+ delete that.prop;
+ assert_equals('changed', that.prop, 'Confirm to prevent deleting a property.');
+ });
+});
+</script>
+
+</body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/js/builtins/Promise-incumbent-global-subframe.sub.html b/testing/web-platform/tests/js/builtins/Promise-incumbent-global-subframe.sub.html
new file mode 100644
index 0000000000..dde0ac953e
--- /dev/null
+++ b/testing/web-platform/tests/js/builtins/Promise-incumbent-global-subframe.sub.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<iframe src="{{location[scheme]}}://{{domains[www2]}}:{{ports[http][0]}}{{location[path]}}/../Promise-incumbent-global-subsubframe.sub.html"></iframe>
+<script>
+ document.domain = "{{host}}";
+ onmessage = function(e) {
+ if (e.data == "start") {
+ frames[0].Promise.resolve().then(frames[0].postMessage.bind(frames[0], "start", "*"));
+ } else {
+ parent.postMessage(e.data, "*");
+ }
+ }
+</script>
diff --git a/testing/web-platform/tests/js/builtins/Promise-incumbent-global-subsubframe.sub.html b/testing/web-platform/tests/js/builtins/Promise-incumbent-global-subsubframe.sub.html
new file mode 100644
index 0000000000..9edd9d278a
--- /dev/null
+++ b/testing/web-platform/tests/js/builtins/Promise-incumbent-global-subsubframe.sub.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<script>
+ document.domain = "{{host}}";
+ onmessage = function (e) {
+ parent.postMessage(
+ {
+ actual: e.origin,
+ expected: "{{location[scheme]}}://{{domains[www1]}}:{{ports[http][0]}}",
+ reason: "Incumbent should have been the caller of then()"
+ },
+ "*");
+ }
+</script>
diff --git a/testing/web-platform/tests/js/builtins/Promise-incumbent-global.sub.html b/testing/web-platform/tests/js/builtins/Promise-incumbent-global.sub.html
new file mode 100644
index 0000000000..6ae0a9fe5e
--- /dev/null
+++ b/testing/web-platform/tests/js/builtins/Promise-incumbent-global.sub.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<iframe src="{{location[scheme]}}://{{domains[www1]}}:{{ports[http][0]}}{{location[path]}}/../Promise-incumbent-global-subframe.sub.html"></iframe>
+<script>
+
+var t = async_test("Check the incumbent global Promise callbacks are called with");
+
+onload = t.step_func(function() {
+ onmessage = t.step_func_done(function(e) {
+ var d = e.data;
+ assert_equals(d.actual, d.expected, d.reason);
+ });
+
+ frames[0].postMessage("start", "*");
+});
+
+</script>
diff --git a/testing/web-platform/tests/js/builtins/Promise-subclassing.html b/testing/web-platform/tests/js/builtins/Promise-subclassing.html
new file mode 100644
index 0000000000..2349c07b05
--- /dev/null
+++ b/testing/web-platform/tests/js/builtins/Promise-subclassing.html
@@ -0,0 +1,265 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+
+var theLog = [];
+var speciesGets = 0;
+var speciesCalls = 0;
+var constructorCalls = 0;
+var constructorGets = 0;
+var resolveCalls = 0;
+var rejectCalls = 0;
+var thenCalls = 0;
+var catchCalls = 0;
+var allCalls = 0;
+var raceCalls = 0;
+var nextCalls = 0;
+
+function takeLog() {
+ var oldLog = theLog;
+ theLog = [];
+ speciesGets = speciesCalls = constructorCalls = resolveCalls =
+ rejectCalls = thenCalls = catchCalls = allCalls = raceCalls =
+ nextCalls = constructorGets = 0;
+ return oldLog;
+}
+
+function clearLog() {
+ takeLog();
+}
+
+function log(str) {
+ theLog.push(str);
+}
+
+class LoggingPromise extends Promise {
+ constructor(func) {
+ super(func);
+ Object.defineProperty(this, "constructor",
+ {
+ get: function() {
+ ++constructorGets;
+ log(`Constructor get ${constructorGets}`);
+ return Object.getPrototypeOf(this).constructor;
+ }
+ });
+ ++constructorCalls;
+ log(`Constructor ${constructorCalls}`);
+ }
+
+ static get [Symbol.species]() {
+ ++speciesGets;
+ log(`Species get ${speciesGets}`);
+ return LoggingSpecies;
+ }
+
+ static resolve(val) {
+ ++resolveCalls;
+ log(`Resolve ${resolveCalls}`);
+ return super.resolve(val);
+ }
+
+ static reject(val) {
+ ++rejectCalls;
+ log(`Reject ${rejectCalls}`);
+ return super.reject(val);
+ }
+
+ then(resolve, reject) {
+ ++thenCalls;
+ log(`Then ${thenCalls}`);
+ return super.then(resolve, reject);
+ }
+
+ catch(handler) {
+ ++catchCalls;
+ log(`Catch ${catchCalls}`);
+ return super.catch(handler);
+ }
+
+ static all(val) {
+ ++allCalls;
+ log(`All ${allCalls}`);
+ return super.all(val);
+ }
+
+ static race(val) {
+ ++raceCalls;
+ log(`Race ${raceCalls}`);
+ return super.race(val);
+ }
+}
+
+class LoggingIterable {
+ constructor(array) {
+ this.iter = array[Symbol.iterator]();
+ }
+
+ get [Symbol.iterator]() { return () => this }
+
+ next() {
+ ++nextCalls;
+ log(`Next ${nextCalls}`);
+ return this.iter.next();
+ }
+}
+
+class LoggingSpecies extends LoggingPromise {
+ constructor(func) {
+ ++speciesCalls;
+ log(`Species call ${speciesCalls}`);
+ super(func)
+ }
+}
+
+class SpeciesLessPromise extends LoggingPromise {
+ static get [Symbol.species]() {
+ return undefined;
+ }
+}
+
+promise_test(function testBasicConstructor() {
+ var p = new LoggingPromise((resolve) => resolve(5));
+ var log = takeLog();
+ assert_array_equals(log, ["Constructor 1"]);
+ assert_true(p instanceof LoggingPromise);
+ return p.then(function(arg) {
+ assert_equals(arg, 5);
+ });
+}, "Basic constructor behavior");
+
+promise_test(function testPromiseRace() {
+ clearLog();
+ var p = LoggingPromise.race(new LoggingIterable([1, 2]));
+ var log = takeLog();
+ assert_array_equals(log, ["Race 1", "Constructor 1",
+ "Next 1", "Resolve 1", "Constructor 2",
+ "Then 1", "Constructor get 1", "Species get 1", "Species call 1", "Constructor 3",
+ "Next 2", "Resolve 2", "Constructor 4",
+ "Then 2", "Constructor get 2", "Species get 2", "Species call 2", "Constructor 5",
+ "Next 3"]);
+ assert_true(p instanceof LoggingPromise);
+ return p.then(function(arg) {
+ assert_in_array(arg, [1, 2]);
+ });
+}, "Promise.race behavior");
+
+promise_test(function testPromiseRaceNoSpecies() {
+ clearLog();
+ var p = SpeciesLessPromise.race(new LoggingIterable([1, 2]));
+ var log = takeLog();
+ assert_array_equals(log, ["Race 1", "Constructor 1",
+ "Next 1", "Resolve 1", "Constructor 2",
+ "Then 1", "Constructor get 1",
+ "Next 2", "Resolve 2", "Constructor 3",
+ "Then 2", "Constructor get 2",
+ "Next 3"]);
+ assert_true(p instanceof SpeciesLessPromise);
+ return p.then(function(arg) {
+ assert_in_array(arg, [1, 2]);
+ });
+}, "Promise.race without species behavior");
+
+promise_test(function testPromiseAll() {
+ clearLog();
+ var p = LoggingPromise.all(new LoggingIterable([1, 2]));
+ var log = takeLog();
+ assert_array_equals(log, ["All 1", "Constructor 1",
+ "Next 1", "Resolve 1", "Constructor 2",
+ "Then 1", "Constructor get 1", "Species get 1", "Species call 1", "Constructor 3",
+ "Next 2", "Resolve 2", "Constructor 4",
+ "Then 2", "Constructor get 2", "Species get 2", "Species call 2", "Constructor 5",
+ "Next 3"]);
+ assert_true(p instanceof LoggingPromise);
+ return p.then(function(arg) {
+ assert_array_equals(arg, [1, 2]);
+ });
+}, "Promise.all behavior");
+
+promise_test(function testPromiseResolve() {
+ clearLog();
+ var p = LoggingPromise.resolve(5);
+ var log = takeLog();
+ assert_array_equals(log, ["Resolve 1", "Constructor 1"]);
+ var q = LoggingPromise.resolve(p);
+ assert_equals(p, q,
+ "Promise.resolve with same constructor should preserve identity");
+ log = takeLog();
+ assert_array_equals(log, ["Resolve 1", "Constructor get 1"]);
+
+ var r = Promise.resolve(p);
+ log = takeLog();
+ assert_array_equals(log, ["Constructor get 1"]);
+ assert_not_equals(p, r,
+ "Promise.resolve with different constructor should " +
+ "create a new Promise instance (1)")
+ var s = Promise.resolve(6);
+ var u = LoggingPromise.resolve(s);
+ log = takeLog();
+ assert_array_equals(log, ["Resolve 1", "Constructor 1"]);
+ assert_not_equals(s, u,
+ "Promise.resolve with different constructor should " +
+ "create a new Promise instance (2)")
+
+ Object.defineProperty(s, "constructor", { value: LoggingPromise });
+ var v = LoggingPromise.resolve(s);
+ log = takeLog();
+ assert_array_equals(log, ["Resolve 1"]);
+ assert_equals(v, s, "Faking the .constructor should work");
+ assert_false(v instanceof LoggingPromise);
+
+ var results = Promise.all([p, q, r, s, u, v]);
+ return results.then(function(arg) {
+ assert_array_equals(arg, [5, 5, 5, 6, 6, 6]);
+ });
+}, "Promise.resolve behavior");
+
+promise_test(function testPromiseReject() {
+ clearLog();
+ var p = LoggingPromise.reject(5);
+ var log = takeLog();
+ assert_array_equals(log, ["Reject 1", "Constructor 1"]);
+
+ return p.catch(function(arg) {
+ assert_equals(arg, 5);
+ });
+}, "Promise.reject behavior");
+
+promise_test(function testPromiseThen() {
+ clearLog();
+ var p = LoggingPromise.resolve(5);
+ var log = takeLog();
+ assert_array_equals(log, ["Resolve 1", "Constructor 1"]);
+
+ var q = p.then((x) => x*x);
+ log = takeLog();
+ assert_array_equals(log, ["Then 1", "Constructor get 1", "Species get 1",
+ "Species call 1", "Constructor 1"]);
+ assert_true(q instanceof LoggingPromise);
+
+ return q.then(function(arg) {
+ assert_equals(arg, 25);
+ });
+}, "Promise.then behavior");
+
+promise_test(function testPromiseCatch() {
+ clearLog();
+ var p = LoggingPromise.reject(5);
+ var log = takeLog();
+ assert_array_equals(log, ["Reject 1", "Constructor 1"]);
+
+ var q = p.catch((x) => x*x);
+ log = takeLog();
+ assert_array_equals(log, ["Catch 1", "Then 1", "Constructor get 1",
+ "Species get 1", "Species call 1", "Constructor 1"]);
+ assert_true(q instanceof LoggingPromise);
+
+ return q.then(function(arg) {
+ assert_equals(arg, 25);
+ });
+}, "Promise.catch behavior");
+
+</script>
diff --git a/testing/web-platform/tests/js/builtins/WeakMap.prototype-properties.html b/testing/web-platform/tests/js/builtins/WeakMap.prototype-properties.html
new file mode 100644
index 0000000000..2c2bddfeb7
--- /dev/null
+++ b/testing/web-platform/tests/js/builtins/WeakMap.prototype-properties.html
@@ -0,0 +1,104 @@
+<!doctype html>
+<title>WeakMap.prototype</title>
+<link rel=author href=mailto:Ms2ger@gmail.com title=Ms2ger>
+<link rel=help href=https://people.mozilla.org/~jorendorff/es6-draft.html#sec-properties-of-the-weakmap-prototype-object>
+<link rel=help href=https://people.mozilla.org/~jorendorff/es6-draft.html#sec-functioninitialize>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+function assert_propdesc(obj, prop, Writable, Enumerable, Configurable) {
+ var propdesc = Object.getOwnPropertyDescriptor(obj, prop);
+ assert_equals(typeof propdesc, "object");
+ assert_equals(propdesc.writable, Writable, "[[Writable]]");
+ assert_equals(propdesc.enumerable, Enumerable, "[[Enumerable]]");
+ assert_equals(propdesc.configurable, Configurable, "[[Configurable]]");
+}
+
+function test_length(fun, expected) {
+ test(function() {
+ assert_propdesc(WeakMap.prototype[fun], "length", false, false, true);
+ assert_equals(WeakMap.prototype[fun].length, expected);
+ }, "WeakMap.prototype." + fun + ".length")
+}
+
+function test_thisval(fun, args) {
+ // Step 1-2
+ test(function() {
+ assert_throws_js(TypeError, function() {
+ WeakMap.prototype[fun].apply(null, args);
+ });
+ assert_throws_js(TypeError, function() {
+ WeakMap.prototype[fun].apply(undefined, args);
+ });
+ }, "WeakMap.prototype." + fun + ": ToObject on this")
+ // Step 3
+ test(function() {
+ assert_throws_js(TypeError, function() {
+ WeakMap.prototype[fun].apply({}, args);
+ });
+ }, "WeakMap.prototype." + fun + ": this has no [[WeakMapData]] internal property")
+}
+
+// In every case, the length property of a built-in Function object described
+// in this clause has the attributes { [[Writable]]: false, [[Enumerable]]:
+// false, [[Configurable]]: false }. Every other property described in this
+// clause has the attributes { [[Writable]]: true, [[Enumerable]]: false,
+// [[Configurable]]: true } unless otherwise specified.
+
+test(function() {
+ assert_equals(Object.getPrototypeOf(WeakMap.prototype), Object.prototype);
+}, "The value of the [[Prototype]] internal property of the WeakMap prototype object is the standard built-in Object prototype object (15.2.4).")
+
+// 23.3.3.1 WeakMap.prototype.constructor
+test(function() {
+ assert_equals(WeakMap.prototype.constructor, WeakMap);
+ assert_propdesc(WeakMap.prototype, "constructor", true, false, true);
+}, "The initial value of WeakMap.prototype.constructor is the built-in WeakMap constructor.")
+
+// 23.3.3.2 WeakMap.prototype.delete ( key )
+test(function() {
+ assert_propdesc(WeakMap.prototype, "delete", true, false, true);
+ test_length("delete", 1);
+ // Step 1-3
+ test_thisval("delete", [{}]);
+}, "WeakMap.prototype.delete")
+
+// 23.3.3.3 WeakMap.prototype.get ( key )
+test(function() {
+ assert_propdesc(WeakMap.prototype, "get", true, false, true);
+ test_length("get", 1);
+ // Step 1-3
+ test_thisval("get", [{}]);
+
+ // Step 8
+ test(function() {
+ var wm = new WeakMap();
+ var key = {};
+ var res = wm.get({}, {});
+ assert_equals(res, undefined);
+ }, "WeakMap.prototype.get: return undefined");
+}, "WeakMap.prototype.get")
+
+// 23.3.3.4 Map.prototype.has ( key )
+test(function() {
+ assert_propdesc(WeakMap.prototype, "has", true, false, true);
+ test_length("has", 1);
+ // Step 1-3
+ test_thisval("has", [{}]);
+}, "WeakMap.prototype.has")
+
+// 23.3.3.5 Map.prototype.set ( key , value )
+test(function() {
+ assert_propdesc(WeakMap.prototype, "set", true, false, true);
+ test_length("set", 2);
+ // Step 1-3
+ test_thisval("set", [{}, {}]);
+}, "WeakMap.prototype.set")
+
+// 23.3.3.6 Map.prototype.@@toStringTag
+test(function() {
+ assert_class_string(new WeakMap(), "WeakMap");
+ assert_class_string(WeakMap.prototype, "WeakMap");
+}, "WeakMap.prototype.@@toStringTag")
+</script>
diff --git a/testing/web-platform/tests/js/builtins/weakrefs/cleanup-prevented-with-reference.optional.any.js b/testing/web-platform/tests/js/builtins/weakrefs/cleanup-prevented-with-reference.optional.any.js
new file mode 100644
index 0000000000..da902ac4f1
--- /dev/null
+++ b/testing/web-platform/tests/js/builtins/weakrefs/cleanup-prevented-with-reference.optional.any.js
@@ -0,0 +1,51 @@
+// META: script=/common/gc.js
+// META: script=resources/maybe-garbage-collect.js
+// ├──> maybeGarbageCollectAndCleanupAsync
+// └──> resolveGarbageCollection
+/*---
+esid: sec-finalization-registry.prototype.cleanupSome
+info: |
+ FinalizationRegistry.prototype.cleanupSome ( [ callback ] )
+
+ 1. Let finalizationRegistry be the this value.
+ 2. If Type(finalizationRegistry) is not Object, throw a TypeError exception.
+ 3. If finalizationRegistry does not have a [[Cells]] internal slot, throw a TypeError exception.
+ 4. If callback is not undefined and IsCallable(callback) is false, throw a TypeError exception.
+ 5. Perform ? CleanupFinalizationRegistry(finalizationRegistry, callback).
+ 6. Return undefined.
+---*/
+
+let holdingsList = [];
+function cb(holding) {
+ holdingsList.push(holding);
+};
+let finalizationRegistry = new FinalizationRegistry(function() {});
+
+let referenced = {};
+
+function emptyCells() {
+ let target = {};
+ finalizationRegistry.register(target, 'target!');
+ finalizationRegistry.register(referenced, 'referenced');
+
+ let prom = maybeGarbageCollectAndCleanupAsync(target);
+ target = null;
+
+ return prom;
+}
+
+promise_test(() => {
+ return (async () => {
+ assert_implements(
+ typeof FinalizationRegistry.prototype.cleanupSome === 'function',
+ 'FinalizationRegistry.prototype.cleanupSome is not implemented.'
+ );
+
+ await emptyCells();
+ finalizationRegistry.cleanupSome(cb);
+
+ assert_equals(holdingsList.length, 1);
+ assert_equals(holdingsList[0], 'target!');
+ assert_equals(typeof referenced, 'object', 'referenced preserved');
+ })().catch(resolveGarbageCollection);
+}, 'cleanupCallback has only one optional chance to be called for a GC that cleans up a registered target.');
diff --git a/testing/web-platform/tests/js/builtins/weakrefs/cleanup-prevented-with-unregister.optional.any.js b/testing/web-platform/tests/js/builtins/weakrefs/cleanup-prevented-with-unregister.optional.any.js
new file mode 100644
index 0000000000..640f21e7f3
--- /dev/null
+++ b/testing/web-platform/tests/js/builtins/weakrefs/cleanup-prevented-with-unregister.optional.any.js
@@ -0,0 +1,58 @@
+// META: script=/common/gc.js
+// META: script=resources/maybe-garbage-collect.js
+// ├──> maybeGarbageCollectAndCleanupAsync
+// └──> resolveGarbageCollection
+/*---
+esid: sec-finalization-registry.prototype.cleanupSome
+info: |
+ FinalizationRegistry.prototype.cleanupSome ( [ callback ] )
+
+ 1. Let finalizationRegistry be the this value.
+ 2. If Type(finalizationRegistry) is not Object, throw a TypeError exception.
+ 3. If finalizationRegistry does not have a [[Cells]] internal slot, throw a TypeError exception.
+ 4. If callback is not undefined and IsCallable(callback) is false, throw a TypeError exception.
+ 5. Perform ? CleanupFinalizationRegistry(finalizationRegistry, callback).
+ 6. Return undefined.
+
+ FinalizationRegistry.prototype.unregister ( unregisterToken )
+
+ 1. Let removed be false.
+ 2. For each Record { [[Target]], [[Holdings]], [[UnregisterToken]] } cell that is an element of finalizationRegistry.[[Cells]], do
+ a. If SameValue(cell.[[UnregisterToken]], unregisterToken) is true, then
+ i. Remove cell from finalizationRegistry.[[Cells]].
+ ii. Set removed to true.
+ 3. Return removed.
+---*/
+let token = {};
+let finalizationRegistry = new FinalizationRegistry(function() {});
+
+function emptyCells() {
+ let target = {};
+ finalizationRegistry.register(target, 'target!', token);
+
+ let prom = maybeGarbageCollectAndCleanupAsync(target);
+ target = null;
+
+ return prom;
+}
+
+promise_test(() => {
+ return (async () => {
+ assert_implements(
+ typeof FinalizationRegistry.prototype.cleanupSome === 'function',
+ 'FinalizationRegistry.prototype.cleanupSome is not implemented.'
+ );
+
+ await emptyCells();
+ let called = 0;
+
+ let res = finalizationRegistry.unregister(token);
+ assert_equals(res, true, 'unregister target before iterating over it in cleanup');
+
+ finalizationRegistry.cleanupSome((holding) => {
+ called += 1;
+ });
+
+ assert_equals(called, 0, 'callback was not called');
+ })().catch(resolveGarbageCollection);
+}, 'Cleanup might be prevented with an unregister usage');
diff --git a/testing/web-platform/tests/js/builtins/weakrefs/finalizationregistry-cleanupCallback-gets-a-microtask.optional.any.js b/testing/web-platform/tests/js/builtins/weakrefs/finalizationregistry-cleanupCallback-gets-a-microtask.optional.any.js
new file mode 100644
index 0000000000..c3a84418f3
--- /dev/null
+++ b/testing/web-platform/tests/js/builtins/weakrefs/finalizationregistry-cleanupCallback-gets-a-microtask.optional.any.js
@@ -0,0 +1,61 @@
+// META: script=/common/gc.js
+// META: script=resources/maybe-garbage-collect.js
+// ├──> maybeGarbageCollectAsync
+// └──> resolveGarbageCollection
+/*---
+esid: sec-finalization-registry-target
+info: |
+ FinalizationRegistry ( cleanupCallback )
+
+ Execution
+ At any time, if a set of objects S is not live, an ECMAScript implementation may perform the
+ following steps atomically:
+
+ For each obj of S, do
+ For each WeakRef ref such that ref.[[WeakRefTarget]] is obj, do
+ Set ref.[[WeakRefTarget]] to empty.
+ For each FinalizationRegistry fg such that fg.[[Cells]] contains cell, such that
+ cell.[[WeakRefTarget]] is obj,
+ Set cell.[[WeakRefTarget]] to empty.
+ Optionally, perform ! HostCleanupFinalizationRegistry(fg).
+
+ HostCleanupFinalizationRegistry(finalizationRegistry)
+
+ HostCleanupFinalizationRegistry is an implementation-defined abstract operation that is expected
+ to call CleanupFinalizationRegistry(finalizationRegistry) at some point in the future, if
+ possible. The host's responsibility is to make this call at a time which does not interrupt
+ synchronous ECMAScript code execution.
+---*/
+
+let count = 1_000;
+let calls = 0;
+let registries = [];
+let callback = function() {
+ calls++;
+};
+for (let i = 0; i < count; i++) {
+ registries.push(
+ new FinalizationRegistry(callback)
+ );
+}
+setup({ allow_uncaught_exception: true });
+
+promise_test((test) => {
+ assert_implements(
+ typeof FinalizationRegistry.prototype.register === 'function',
+ 'FinalizationRegistry.prototype.register is not implemented.'
+ );
+ return (async () => {
+
+ {
+ let target = {};
+ for (let registry of registries) {
+ registry.register(target, 1);
+ }
+ target = null;
+ }
+
+ await maybeGarbageCollectAsync();
+ await test.step_wait(() => calls === count, `Expected ${count} registry cleanups.`);
+ })().catch(resolveGarbageCollection);
+}, 'HostCleanupFinalizationRegistry is an implementation-defined abstract operation that is expected to call CleanupFinalizationRegistry(finalizationRegistry) at some point in the future, if possible.');
diff --git a/testing/web-platform/tests/js/builtins/weakrefs/finalizationregistry-cleanupCallback-throws-onerror-interaction.optional.window.js b/testing/web-platform/tests/js/builtins/weakrefs/finalizationregistry-cleanupCallback-throws-onerror-interaction.optional.window.js
new file mode 100644
index 0000000000..71d6fbe174
--- /dev/null
+++ b/testing/web-platform/tests/js/builtins/weakrefs/finalizationregistry-cleanupCallback-throws-onerror-interaction.optional.window.js
@@ -0,0 +1,70 @@
+// META: script=/common/gc.js
+// META: script=resources/maybe-garbage-collect.js
+// ├──> maybeGarbageCollectAsync
+// └──> resolveGarbageCollection
+/*---
+esid: sec-finalization-registry-target
+info: |
+ FinalizationRegistry ( cleanupCallback )
+
+ CleanupFinalizationRegistry ( finalizationRegistry [ , callback ] )
+
+ The following steps are performed:
+
+ Assert: finalizationRegistry has [[Cells]] and [[CleanupCallback]] internal slots.
+ If callback is not present or undefined, set callback to finalizationRegistry.[[CleanupCallback]].
+ While finalizationRegistry.[[Cells]] contains a Record cell such that cell.[[WeakRefTarget]] is
+ empty, then an implementation may perform the following steps,
+ Choose any such cell.
+ Remove cell from finalizationRegistry.[[Cells]].
+ Perform ? Call(callback, undefined, « cell.[[HeldValue]] »).
+ Return NormalCompletion(undefined).
+
+ EDITOR'S NOTE
+ When called from HostCleanupFinalizationRegistry, if calling the callback throws an error, this will be caught within the RunJobs algorithm and reported to the host. HTML does not apply the RunJobs algorithm, but will also report the error, which may call window.onerror.
+---*/
+
+let error = new Error('FinalizationRegistryError');
+
+let finalizationRegistry = new FinalizationRegistry(function() {
+ throw error;
+});
+
+setup({ allow_uncaught_exception: true });
+
+promise_test((test) => {
+ assert_implements(
+ typeof FinalizationRegistry.prototype.register === 'function',
+ 'FinalizationRegistry.prototype.register is not implemented.'
+ );
+
+ return (async () => {
+
+ let resolve;
+ let reject;
+ let deferred = new Promise((resolverFn, rejecterFn) => {
+ resolve = resolverFn;
+ reject = rejecterFn;
+ });
+
+ window.onerror = test.step_func((message, source, lineno, colno, exception) => {
+ assert_equals(exception, error, 'window.onerror received the intended error object.');
+ resolve();
+ });
+
+ {
+ let target = {};
+ let heldValue = 1;
+ finalizationRegistry.register(target, heldValue);
+ target = null;
+ }
+
+ await maybeGarbageCollectAsync();
+
+ // Since the process of garbage collection is non-deterministic, we cannot know when
+ // (if ever) it will actually occur.
+ test.step_timeout(() => { reject(); }, 5000);
+
+ return deferred;
+ })().catch(resolveGarbageCollection);
+}, 'When called from HostCleanupFinalizationRegistry, if calling the callback throws an error, this will be caught within the RunJobs algorithm and reported to the host. HTML does not apply the RunJobs algorithm, but will also report the error, which may call window.onerror.');
diff --git a/testing/web-platform/tests/js/builtins/weakrefs/gc-has-one-chance-to-call-cleanupCallback-queueMicrotask.optional.any.js b/testing/web-platform/tests/js/builtins/weakrefs/gc-has-one-chance-to-call-cleanupCallback-queueMicrotask.optional.any.js
new file mode 100644
index 0000000000..456281c520
--- /dev/null
+++ b/testing/web-platform/tests/js/builtins/weakrefs/gc-has-one-chance-to-call-cleanupCallback-queueMicrotask.optional.any.js
@@ -0,0 +1,109 @@
+// META: script=/common/gc.js
+// META: script=resources/maybe-garbage-collect.js
+// ├──> maybeGarbageCollectAndCleanupAsync
+// ├──> maybeGarbageCollectAsync
+// └──> resolveGarbageCollection
+/*---
+esid: sec-finalization-registry-target
+info: |
+ FinalizationRegistry ( cleanupCallback )
+
+ FinalizationRegistry.prototype.cleanupSome ( [ callback ] )
+
+ ...
+ 4. If callback is not undefined and IsCallable(callback) is false, throw a TypeError exception.
+ 5. Perform ? CleanupFinalizationRegistry(finalizationRegistry, callback).
+ 6. Return undefined.
+
+ Execution
+
+ At any time, if an object obj is not live, an ECMAScript implementation may perform the following steps atomically:
+
+ 1. For each WeakRef ref such that ref.[[Target]] is obj,
+ a. Set ref.[[Target]] to empty.
+ 2. For each FinalizationRegistry finalizationRegistry such that finalizationRegistry.[[Cells]] contains cell, such that cell.[[Target]] is obj,
+ a. Set cell.[[Target]] to empty.
+ b. Optionally, perform ! HostCleanupFinalizationRegistry(finalizationRegistry).
+---*/
+
+
+let cleanupCallback = 0;
+let holdings = [];
+function cb(holding) {
+ holdings.push(holding);
+}
+
+let finalizationRegistry = new FinalizationRegistry(function() {
+ cleanupCallback += 1;
+});
+
+function emptyCells() {
+ let target = {};
+ finalizationRegistry.register(target, 'a');
+
+ let prom = maybeGarbageCollectAndCleanupAsync(target);
+ target = null;
+
+ return prom;
+}
+
+promise_test(() => {
+ return (async () => {
+ assert_implements(
+ typeof FinalizationRegistry.prototype.cleanupSome === 'function',
+ 'FinalizationRegistry.prototype.cleanupSome is not implemented.'
+ );
+
+ assert_implements(
+ typeof queueMicrotask === 'function',
+ 'queueMicrotask is not implemented.'
+ );
+
+ let ticks = 0;
+ await emptyCells();
+ await queueMicrotask(() => ticks++);
+
+ finalizationRegistry.cleanupSome(cb);
+
+ // cleanupSome will be invoked if there are empty cells left. If the
+ // cleanupCallback already ran, then cb won't be called.
+ let expectedCalled = cleanupCallback === 1 ? 0 : 1;
+ // This asserts the registered object was emptied in the previous GC.
+ assert_equals(holdings.length, expectedCalled, 'cleanupSome callback for the first time');
+
+ // At this point, we can't assert if cleanupCallback was called, because it's
+ // optional. Although, we can finally assert it's not gonna be called anymore
+ // for the other executions of the Garbage Collector.
+ // The chance of having it called only happens right after the
+ // cell.[[Target]] is set to empty.
+ assert_true(cleanupCallback >= 0, 'cleanupCallback might be 0');
+ assert_true(cleanupCallback <= 1, 'cleanupCallback might be 1');
+
+ // Restoring the cleanupCallback variable to 0 will help us asserting the
+ // finalizationRegistry callback is not called again.
+ cleanupCallback = 0;
+
+ await maybeGarbageCollectAsync();
+ await queueMicrotask(() => ticks++);
+
+ finalizationRegistry.cleanupSome(cb);
+
+ assert_equals(holdings.length, expectedCalled, 'cleanupSome callback is not called anymore, no empty cells');
+ assert_equals(cleanupCallback, 0, 'cleanupCallback is not called again #1');
+
+ await maybeGarbageCollectAsync();
+ await queueMicrotask(() => ticks++);
+
+ finalizationRegistry.cleanupSome(cb);
+
+ assert_equals(holdings.length, expectedCalled, 'cleanupSome callback is not called again #2');
+ assert_equals(cleanupCallback, 0, 'cleanupCallback is not called again #2');
+ assert_equals(ticks, 3, 'ticks is 3');
+
+ if (holdings.length) {
+ assert_array_equals(holdings, ['a']);
+ }
+
+ await maybeGarbageCollectAsync();
+ })().catch(resolveGarbageCollection);
+}, 'cleanupCallback has only one optional chance to be called for a GC that cleans up a registered target.');
diff --git a/testing/web-platform/tests/js/builtins/weakrefs/gc-has-one-chance-to-call-cleanupCallback-queueMicrotaskMutationObserver.optional.window.js b/testing/web-platform/tests/js/builtins/weakrefs/gc-has-one-chance-to-call-cleanupCallback-queueMicrotaskMutationObserver.optional.window.js
new file mode 100644
index 0000000000..7b29f00943
--- /dev/null
+++ b/testing/web-platform/tests/js/builtins/weakrefs/gc-has-one-chance-to-call-cleanupCallback-queueMicrotaskMutationObserver.optional.window.js
@@ -0,0 +1,115 @@
+// META: script=/common/gc.js
+// META: script=resources/maybe-garbage-collect.js
+// ├──> maybeGarbageCollectAndCleanupAsync
+// ├──> maybeGarbageCollectAsync
+// └──> resolveGarbageCollection
+/*---
+esid: sec-finalization-registry-target
+info: |
+ FinalizationRegistry ( cleanupCallback )
+
+ FinalizationRegistry.prototype.cleanupSome ( [ callback ] )
+
+ ...
+ 4. If callback is not undefined and IsCallable(callback) is false, throw a TypeError exception.
+ 5. Perform ? CleanupFinalizationRegistry(finalizationRegistry, callback).
+ 6. Return undefined.
+
+ Execution
+
+ At any time, if an object obj is not live, an ECMAScript implementation may perform the following steps atomically:
+
+ 1. For each WeakRef ref such that ref.[[Target]] is obj,
+ a. Set ref.[[Target]] to empty.
+ 2. For each FinalizationRegistry finalizationRegistry such that finalizationRegistry.[[Cells]] contains cell, such that cell.[[Target]] is obj,
+ a. Set cell.[[Target]] to empty.
+ b. Optionally, perform ! HostCleanupFinalizationRegistry(finalizationRegistry).
+---*/
+
+
+let cleanupCallback = 0;
+let holdings = [];
+function cb(holding) {
+ holdings.push(holding);
+}
+
+let finalizationRegistry = new FinalizationRegistry(function() {
+ cleanupCallback += 1;
+});
+
+function emptyCells() {
+ let target = {};
+ finalizationRegistry.register(target, 'a');
+
+ let prom = maybeGarbageCollectAndCleanupAsync(target);
+ target = null;
+
+ return prom;
+}
+
+function queueMicrotaskByMutationObserver(callback) {
+ const textNode = document.createTextNode('');
+ new MutationObserver(callback).observe(textNode, { characterData: true });
+ textNode.data = 1;
+}
+
+promise_test(() => {
+ return (async () => {
+ assert_implements(
+ typeof FinalizationRegistry.prototype.cleanupSome === 'function',
+ 'FinalizationRegistry.prototype.cleanupSome is not implemented.'
+ );
+
+ assert_implements(
+ typeof MutationObserver === 'function',
+ 'MutationObserver is not implemented.'
+ );
+
+ let ticks = 0;
+ await emptyCells();
+ await queueMicrotaskByMutationObserver(() => ticks++);
+
+ finalizationRegistry.cleanupSome(cb);
+
+ // cleanupSome will be invoked if there are empty cells left. If the
+ // cleanupCallback already ran, then cb won't be called.
+ let expectedCalled = cleanupCallback === 1 ? 0 : 1;
+ // This asserts the registered object was emptied in the previous GC.
+ assert_equals(holdings.length, expectedCalled, 'cleanupSome callback for the first time');
+
+ // At this point, we can't assert if cleanupCallback was called, because it's
+ // optional. Although, we can finally assert it's not gonna be called anymore
+ // for the other executions of the Garbage Collector.
+ // The chance of having it called only happens right after the
+ // cell.[[Target]] is set to empty.
+ assert_true(cleanupCallback >= 0, 'cleanupCallback might be 0');
+ assert_true(cleanupCallback <= 1, 'cleanupCallback might be 1');
+
+ // Restoring the cleanupCallback variable to 0 will help us asserting the
+ // finalizationRegistry callback is not called again.
+ cleanupCallback = 0;
+
+ await maybeGarbageCollectAsync();
+ await queueMicrotaskByMutationObserver(() => ticks++);
+
+ finalizationRegistry.cleanupSome(cb);
+
+ assert_equals(holdings.length, expectedCalled, 'cleanupSome callback is not called anymore, no empty cells');
+ assert_equals(cleanupCallback, 0, 'cleanupCallback is not called again #1');
+
+ await maybeGarbageCollectAsync();
+ await queueMicrotaskByMutationObserver(() => ticks++);
+
+ finalizationRegistry.cleanupSome(cb);
+
+ assert_equals(holdings.length, expectedCalled, 'cleanupSome callback is not called again #2');
+ assert_equals(cleanupCallback, 0, 'cleanupCallback is not called again #2');
+ assert_equals(ticks, 3, 'ticks is 3');
+
+ if (holdings.length) {
+ assert_array_equals(holdings, ['a']);
+ }
+
+ await maybeGarbageCollectAsync();
+ })().catch(resolveGarbageCollection);
+}, 'cleanupCallback has only one optional chance to be called for a GC that cleans up a registered target.');
diff --git a/testing/web-platform/tests/js/builtins/weakrefs/gc-has-one-chance-to-call-cleanupCallback.optional.any.js b/testing/web-platform/tests/js/builtins/weakrefs/gc-has-one-chance-to-call-cleanupCallback.optional.any.js
new file mode 100644
index 0000000000..92cd322869
--- /dev/null
+++ b/testing/web-platform/tests/js/builtins/weakrefs/gc-has-one-chance-to-call-cleanupCallback.optional.any.js
@@ -0,0 +1,104 @@
+// META: script=/common/gc.js
+// META: script=resources/maybe-garbage-collect.js
+// ├──> maybeGarbageCollectAndCleanupAsync
+// ├──> maybeGarbageCollectAsync
+// └──> resolveGarbageCollection
+/*---
+esid: sec-finalization-registry-target
+info: |
+ FinalizationRegistry ( cleanupCallback )
+
+ FinalizationRegistry.prototype.cleanupSome ( [ callback ] )
+
+ ...
+ 4. If callback is not undefined and IsCallable(callback) is false, throw a TypeError exception.
+ 5. Perform ? CleanupFinalizationRegistry(finalizationRegistry, callback).
+ 6. Return undefined.
+
+ Execution
+
+ At any time, if an object obj is not live, an ECMAScript implementation may perform the following steps atomically:
+
+ 1. For each WeakRef ref such that ref.[[Target]] is obj,
+ a. Set ref.[[Target]] to empty.
+ 2. For each FinalizationRegistry finalizationRegistry such that finalizationRegistry.[[Cells]] contains cell, such that cell.[[Target]] is obj,
+ a. Set cell.[[Target]] to empty.
+ b. Optionally, perform ! HostCleanupFinalizationRegistry(finalizationRegistry).
+---*/
+
+
+let cleanupCallback = 0;
+let holdings = [];
+function cb(holding) {
+ holdings.push(holding);
+}
+
+let finalizationRegistry = new FinalizationRegistry(function() {
+ cleanupCallback += 1;
+});
+
+function emptyCells() {
+ let target = {};
+ finalizationRegistry.register(target, 'a');
+
+ let prom = maybeGarbageCollectAndCleanupAsync(target);
+ target = null;
+
+ return prom;
+}
+
+promise_test(() => {
+ return (async () => {
+ assert_implements(
+ typeof FinalizationRegistry.prototype.cleanupSome === 'function',
+ 'FinalizationRegistry.prototype.cleanupSome is not implemented.'
+ );
+
+ let ticks = 0;
+ await emptyCells();
+ await ticks++;
+
+ finalizationRegistry.cleanupSome(cb);
+
+ // cleanupSome will be invoked if there are empty cells left. If the
+ // cleanupCallback already ran, then cb won't be called.
+ let expectedCalled = cleanupCallback === 1 ? 0 : 1;
+ // This asserts the registered object was emptied in the previous GC.
+ assert_equals(holdings.length, expectedCalled, 'cleanupSome callback for the first time');
+
+ // At this point, we can't assert if cleanupCallback was called, because it's
+ // optional. Although, we can finally assert it's not gonna be called anymore
+ // for the other executions of the Garbage Collector.
+ // The chance of having it called only happens right after the
+ // cell.[[Target]] is set to empty.
+ assert_true(cleanupCallback >= 0, 'cleanupCallback might be 0');
+ assert_true(cleanupCallback <= 1, 'cleanupCallback might be 1');
+
+ // Restoring the cleanupCallback variable to 0 will help us asserting the
+ // finalizationRegistry callback is not called again.
+ cleanupCallback = 0;
+
+ await maybeGarbageCollectAsync();
+ await ticks++;
+
+ finalizationRegistry.cleanupSome(cb);
+
+ assert_equals(holdings.length, expectedCalled, 'cleanupSome callback is not called anymore, no empty cells');
+ assert_equals(cleanupCallback, 0, 'cleanupCallback is not called again #1');
+
+ await maybeGarbageCollectAsync();
+ await ticks++;
+
+ finalizationRegistry.cleanupSome(cb);
+
+ assert_equals(holdings.length, expectedCalled, 'cleanupSome callback is not called again #2');
+ assert_equals(cleanupCallback, 0, 'cleanupCallback is not called again #2');
+ assert_equals(ticks, 3, 'ticks is 3');
+
+ if (holdings.length) {
+ assert_array_equals(holdings, ['a']);
+ }
+
+ await maybeGarbageCollectAsync();
+ })().catch(resolveGarbageCollection);
+}, 'cleanupCallback has only one optional chance to be called for a GC that cleans up a registered target.');
diff --git a/testing/web-platform/tests/js/builtins/weakrefs/holdings-multiple-values.optional.any.js b/testing/web-platform/tests/js/builtins/weakrefs/holdings-multiple-values.optional.any.js
new file mode 100644
index 0000000000..604174e467
--- /dev/null
+++ b/testing/web-platform/tests/js/builtins/weakrefs/holdings-multiple-values.optional.any.js
@@ -0,0 +1,66 @@
+// META: script=/common/gc.js
+// META: script=resources/maybe-garbage-collect.js
+// ├──> maybeGarbageCollectAndCleanupAsync
+// └──> resolveGarbageCollection
+/*---
+esid: sec-properties-of-the-finalization-registry-constructor
+info: |
+ FinalizationRegistry.prototype.cleanupSome ( [ callback ] )
+
+ 1. Let finalizationRegistry be the this value.
+ ...
+ 5. Perform ! CleanupFinalizationRegistry(finalizationRegistry, callback).
+ ...
+
+ CleanupFinalizationRegistry ( finalizationRegistry [ , callback ] )
+
+ ...
+ 3. While finalizationRegistry.[[Cells]] contains a Record cell such that cell.[[WeakRefTarget]] is ~empty~, then an implementation may perform the following steps,
+ a. Choose any such cell.
+ b. Remove cell from finalizationRegistry.[[Cells]].
+ c. Perform ? Call(callback, undefined, << cell.[[HeldValue]] >>).
+
+---*/
+
+function check(value, expectedName) {
+ let holdings = [];
+ let called = 0;
+ let finalizationRegistry = new FinalizationRegistry(function() {});
+
+ function callback(holding) {
+ called += 1;
+ holdings.push(holding);
+ }
+
+ // This is internal to avoid conflicts
+ function emptyCells(value) {
+ let target = {};
+ finalizationRegistry.register(target, value);
+
+ let prom = maybeGarbageCollectAndCleanupAsync(target);
+ target = null;
+
+ return prom;
+ }
+
+ return emptyCells(value).then(function() {
+ finalizationRegistry.cleanupSome(callback);
+ assert_equals(called, 1, expectedName);
+ assert_equals(holdings.length, 1, expectedName);
+ assert_equals(holdings[0], value, expectedName);
+ });
+}
+
+test(() =>
+ assert_implements(
+ typeof FinalizationRegistry.prototype.cleanupSome === 'function',
+ 'FinalizationRegistry.prototype.cleanupSome is not implemented.'
+), 'Requires FinalizationRegistry.prototype.cleanupSome');
+promise_test(() => check(undefined, 'undefined'), '`undefined` as registered holding value');
+promise_test(() => check(null, 'null'), '`null` as registered holding value');
+promise_test(() => check('', 'the empty string'), '`""` as registered holding value');
+promise_test(() => check({}, 'object'), '`{}` as registered holding value');
+promise_test(() => check(42, 'number'), '`42` as registered holding value');
+promise_test(() => check(true, 'true'), '`true` as registered holding value');
+promise_test(() => check(false, 'false'), '`false` as registered holding value');
+promise_test(() => check(Symbol(1), 'symbol'), '`Symbol(1)` as registered holding value');
diff --git a/testing/web-platform/tests/js/builtins/weakrefs/reentrancy.optional.any.js b/testing/web-platform/tests/js/builtins/weakrefs/reentrancy.optional.any.js
new file mode 100644
index 0000000000..fa7b7d55c4
--- /dev/null
+++ b/testing/web-platform/tests/js/builtins/weakrefs/reentrancy.optional.any.js
@@ -0,0 +1,51 @@
+// META: script=/common/gc.js
+// META: script=resources/maybe-garbage-collect.js
+// ├──> maybeGarbageCollectAndCleanupAsync
+// └──> resolveGarbageCollection
+/*---
+esid: sec-properties-of-the-finalization-registry-constructor
+---*/
+
+let called = 0;
+let endOfCall = 0;
+let finalizationRegistry = new FinalizationRegistry(function() {});
+
+function callback(holding) {
+ called += 1;
+
+ if (called === 1) {
+ // Atempt to re-enter the callback.
+ let nestedCallbackRan = false;
+ finalizationRegistry.cleanupSome(() => { nestedCallbackRan = true });
+ assert_equals(nestedCallbackRan, true);
+ }
+
+ endOfCall += 1;
+}
+
+function emptyCells() {
+ let o1 = {};
+ let o2 = {};
+ // Register more than one objects to test reentrancy.
+ finalizationRegistry.register(o1, 'holdings 1');
+ finalizationRegistry.register(o2, 'holdings 2');
+
+ let prom = maybeGarbageCollectAndCleanupAsync(o1);
+ o1 = null;
+
+ return prom;
+}
+
+promise_test(() => {
+ return (async () => {
+ assert_implements(
+ typeof FinalizationRegistry.prototype.cleanupSome === 'function',
+ 'FinalizationRegistry.prototype.cleanupSome is not implemented.'
+ );
+ await emptyCells();
+ finalizationRegistry.cleanupSome(callback);
+
+ assert_equals(called, 1, 'callback was called');
+ assert_equals(endOfCall, 1, 'callback finished');
+ })().catch(resolveGarbageCollection);
+}, 'cleanupCallback has only one optional chance to be called for a GC that cleans up a registered target.');
diff --git a/testing/web-platform/tests/js/builtins/weakrefs/resources/maybe-garbage-collect.js b/testing/web-platform/tests/js/builtins/weakrefs/resources/maybe-garbage-collect.js
new file mode 100644
index 0000000000..8bd4cac309
--- /dev/null
+++ b/testing/web-platform/tests/js/builtins/weakrefs/resources/maybe-garbage-collect.js
@@ -0,0 +1,72 @@
+/**
+ * maybeGarbageCollectAsync
+ *
+ * It might garbage collect, it might not. If it doesn't, that's ok.
+ */
+self.maybeGarbageCollectAsync = garbageCollect;
+
+/**
+ * maybeGarbageCollectKeptObjectsAsync
+ *
+ * Based on "asyncGCDeref" in https://github.com/tc39/test262/blob/master/harness/async-gc.js
+ *
+ * @return {Promise} Resolves to a trigger if ClearKeptObjects
+ * exists to provide one
+ */
+async function maybeGarbageCollectKeptObjectsAsync() {
+ let trigger;
+
+ if (typeof ClearKeptObjects === 'function') {
+ trigger = ClearKeptObjects();
+ }
+
+ await maybeGarbageCollectAsync();
+
+ return trigger;
+}
+
+/**
+ * maybeGarbageCollectAndCleanupAsync
+ *
+ * Based on "asyncGC" in https://github.com/tc39/test262/blob/master/harness/async-gc.js
+ *
+ * @return {undefined}
+ */
+async function maybeGarbageCollectAndCleanupAsync(...targets) {
+ let finalizationRegistry = new FinalizationRegistry(() => {});
+ let length = targets.length;
+
+ for (let target of targets) {
+ finalizationRegistry.register(target, 'target');
+ target = null;
+ }
+
+ targets = null;
+
+ await 'tick';
+ await maybeGarbageCollectKeptObjectsAsync();
+
+ let names = [];
+
+ finalizationRegistry.cleanupSome(name => names.push(name));
+
+ if (names.length !== length) {
+ throw maybeGarbageCollectAndCleanupAsync.NOT_COLLECTED;
+ }
+}
+
+maybeGarbageCollectAndCleanupAsync.NOT_COLLECTED = Symbol('Object was not collected');
+
+/**
+ * resolveGarbageCollection
+ *
+ * Based on "resolveAsyncGC" in https://github.com/tc39/test262/blob/master/harness/async-gc.js
+ *
+ * @param {Error} error An error object.
+ * @return {undefined}
+ */
+function resolveGarbageCollection(error) {
+ if (error && error !== maybeGarbageCollectAndCleanupAsync.NOT_COLLECTED) {
+ throw error;
+ }
+}
diff --git a/testing/web-platform/tests/js/builtins/weakrefs/return-undefined-with-gc.optional.any.js b/testing/web-platform/tests/js/builtins/weakrefs/return-undefined-with-gc.optional.any.js
new file mode 100644
index 0000000000..a5d23bf696
--- /dev/null
+++ b/testing/web-platform/tests/js/builtins/weakrefs/return-undefined-with-gc.optional.any.js
@@ -0,0 +1,79 @@
+// META: script=/common/gc.js
+// META: script=resources/maybe-garbage-collect.js
+// ├──> maybeGarbageCollectAndCleanupAsync
+// └──> resolveGarbageCollection
+/*---
+esid: sec-finalization-registry.prototype.cleanupSome
+info: |
+ FinalizationRegistry.prototype.cleanupSome ( [ callback ] )
+
+ 1. Let finalizationRegistry be the this value.
+ 2. If Type(finalizationRegistry) is not Object, throw a TypeError exception.
+ 3. If finalizationRegistry does not have a [[Cells]] internal slot, throw a TypeError exception.
+ 4. If callback is not undefined and IsCallable(callback) is false, throw a TypeError exception.
+ 5. Perform ? CleanupFinalizationRegistry(finalizationRegistry, callback).
+ 6. Return undefined.
+---*/
+
+let called;
+let fn = function() {
+ called += 1;
+ return 39;
+};
+let cb = function() {
+ called += 1;
+ return 42;
+};
+let finalizationRegistry = new FinalizationRegistry(fn);
+
+function emptyCells() {
+ let target = {};
+ finalizationRegistry.register(target);
+
+ let prom = maybeGarbageCollectAndCleanupAsync(target);
+ target = null;
+
+ return prom;
+}
+
+promise_test(() => {
+ return (async () => {
+ assert_implements(
+ typeof FinalizationRegistry.prototype.cleanupSome === 'function',
+ 'FinalizationRegistry.prototype.cleanupSome is not implemented.'
+ );
+
+ await emptyCells();
+ called = 0;
+ assert_equals(finalizationRegistry.cleanupSome(cb), undefined, 'regular callback');
+ assert_equals(called, 1);
+
+ await emptyCells();
+ called = 0;
+ assert_equals(finalizationRegistry.cleanupSome(fn), undefined, 'regular callback, same FG cleanup function');
+ assert_equals(called, 1);
+
+ await emptyCells();
+ called = 0;
+ assert_equals(finalizationRegistry.cleanupSome(), undefined, 'undefined (implicit) callback, defer to FB callback');
+ assert_equals(called, 1);
+
+ await emptyCells();
+ called = 0;
+ assert_equals(finalizationRegistry.cleanupSome(undefined), undefined, 'undefined (explicit) callback, defer to FB callback');
+ assert_equals(called, 1);
+
+ await emptyCells();
+ assert_equals(finalizationRegistry.cleanupSome(() => 1), undefined, 'arrow function');
+
+ await emptyCells();
+ assert_equals(finalizationRegistry.cleanupSome(async function() {}), undefined, 'async function');
+
+ await emptyCells();
+ assert_equals(finalizationRegistry.cleanupSome(function *() {}), undefined, 'generator');
+
+ await emptyCells();
+ assert_equals(finalizationRegistry.cleanupSome(async function *() {}), undefined, 'async generator');
+
+ })().catch(resolveGarbageCollection);
+}, 'Return undefined regardless the result of CleanupFinalizationRegistry');
diff --git a/testing/web-platform/tests/js/builtins/weakrefs/unregister-cleaned-up-cell.optional.any.js b/testing/web-platform/tests/js/builtins/weakrefs/unregister-cleaned-up-cell.optional.any.js
new file mode 100644
index 0000000000..920ea7619c
--- /dev/null
+++ b/testing/web-platform/tests/js/builtins/weakrefs/unregister-cleaned-up-cell.optional.any.js
@@ -0,0 +1,73 @@
+// META: script=/common/gc.js
+// META: script=resources/maybe-garbage-collect.js
+// ├──> maybeGarbageCollectAndCleanupAsync
+// └──> resolveGarbageCollection
+/*---
+esid: sec-finalization-registry.prototype.unregister
+info: |
+ FinalizationRegistry.prototype.cleanupSome ( [ callback ] )
+
+ 1. Let finalizationRegistry be the this value.
+ ...
+ 5. Perform ! CleanupFinalizationRegistry(finalizationRegistry, callback).
+ ...
+
+ CleanupFinalizationRegistry ( finalizationRegistry [ , callback ] )
+
+ ...
+ 3. While finalizationRegistry.[[Cells]] contains a Record cell such that cell.[[WeakRefTarget]] is ~empty~, then an implementation may perform the following steps,
+ a. Choose any such cell.
+ b. Remove cell from finalizationRegistry.[[Cells]].
+ c. Perform ? Call(callback, undefined, << cell.[[HeldValue]] >>).
+ ...
+
+ FinalizationRegistry.prototype.unregister ( unregisterToken )
+
+ 1. Let removed be false.
+ 2. For each Record { [[Target]], [[Holdings]], [[UnregisterToken]] } cell that is an element of finalizationRegistry.[[Cells]], do
+ a. If SameValue(cell.[[UnregisterToken]], unregisterToken) is true, then
+ i. Remove cell from finalizationRegistry.[[Cells]].
+ ii. Set removed to true.
+ 3. Return removed.
+
+---*/
+
+let value = 'target!';
+let token = {};
+let finalizationRegistry = new FinalizationRegistry(function() {});
+
+function emptyCells() {
+ let target = {};
+ finalizationRegistry.register(target, value, token);
+
+ let prom = maybeGarbageCollectAndCleanupAsync(target);
+ target = null;
+
+ return prom;
+}
+
+promise_test(() => {
+ return (async () => {
+ assert_implements(
+ typeof FinalizationRegistry.prototype.cleanupSome === 'function',
+ 'FinalizationRegistry.prototype.cleanupSome is not implemented.'
+ );
+
+ await emptyCells();
+ let called = 0;
+ let holdings = [];
+ finalizationRegistry.cleanupSome((holding) => {
+ called += 1;
+ holdings.push(holding);
+ });
+
+ assert_equals(called, 1);
+ assert_equals(holdings.length, 1);
+ assert_equals(holdings[0], value);
+
+ let res = finalizationRegistry.unregister(token);
+ assert_equals(res, false, 'unregister after iterating over it in cleanup');
+
+ })().catch(resolveGarbageCollection);
+}, 'Cannot unregister a cell that has been cleaned up');
+