summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/close-watcher
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/close-watcher')
-rw-r--r--testing/web-platform/tests/close-watcher/META.yml4
-rw-r--r--testing/web-platform/tests/close-watcher/abortsignal.html123
-rw-r--r--testing/web-platform/tests/close-watcher/after-other-listeners.html26
-rw-r--r--testing/web-platform/tests/close-watcher/basic.html167
-rw-r--r--testing/web-platform/tests/close-watcher/frame-removal.html59
-rw-r--r--testing/web-platform/tests/close-watcher/resources/helpers.js21
-rw-r--r--testing/web-platform/tests/close-watcher/user-activation-multiple-plus-free.html32
-rw-r--r--testing/web-platform/tests/close-watcher/user-activation.html212
8 files changed, 644 insertions, 0 deletions
diff --git a/testing/web-platform/tests/close-watcher/META.yml b/testing/web-platform/tests/close-watcher/META.yml
new file mode 100644
index 0000000000..4534ab8abe
--- /dev/null
+++ b/testing/web-platform/tests/close-watcher/META.yml
@@ -0,0 +1,4 @@
+spec: https://wicg.github.io/close-watcher/
+suggested_reviewers:
+ - domenic
+ - natechapin
diff --git a/testing/web-platform/tests/close-watcher/abortsignal.html b/testing/web-platform/tests/close-watcher/abortsignal.html
new file mode 100644
index 0000000000..eb70ffed1b
--- /dev/null
+++ b/testing/web-platform/tests/close-watcher/abortsignal.html
@@ -0,0 +1,123 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+
+<div id='d' style='height: 100px; width: 100px'></div>
+<script>
+// *not* \uu001B; see https://w3c.github.io/webdriver/#keyboard-actions
+const ESC = '\uE00C';
+
+test(() => {
+ let watcher = new CloseWatcher({ signal: AbortSignal.abort() });
+ let oncancel_called = false;
+ let onclose_called = false;
+ watcher.oncancel = () => oncancel_called = true;
+ watcher.onclose = () => onclose_called = true;
+
+ watcher.close();
+
+ assert_false(oncancel_called);
+ assert_false(onclose_called);
+}, "already-aborted AbortSignal then close() fires no events");
+
+test(() => {
+ let controller = new AbortController();
+ let watcher = new CloseWatcher({ signal: controller.signal });
+ let oncancel_called = false;
+ let onclose_called = false;
+ watcher.oncancel = () => oncancel_called = true;
+ watcher.onclose = () => onclose_called = true;
+
+ controller.abort();
+ watcher.close();
+
+ assert_false(oncancel_called);
+ assert_false(onclose_called);
+}, "abortController.abort() then close() fires no events");
+
+test(() => {
+ let controller = new AbortController();
+ let watcher = new CloseWatcher({ signal: controller.signal });
+ let oncancel_call_count_ = 0;
+ let onclose_call_count_ = 0;
+ watcher.oncancel = () => oncancel_call_count_++;
+ watcher.onclose = () => onclose_call_count_++;
+
+ watcher.close();
+ controller.abort();
+
+ assert_equals(oncancel_call_count_, 0);
+ assert_equals(onclose_call_count_, 1);
+}, "close() then abortController.abort() fires only one close event");
+
+promise_test(async () => {
+ let watcher = new CloseWatcher({ signal: AbortSignal.abort() });
+ let oncancel_called = false;
+ let onclose_called = false;
+ watcher.oncancel = () => oncancel_called = true;
+ watcher.onclose = () => onclose_called = true;
+
+ await test_driver.send_keys(document.getElementById('d'), ESC);
+
+ assert_false(oncancel_called);
+ assert_false(onclose_called);
+}, "already-aborted AbortSignal then Esc key fires no events");
+
+promise_test(async t => {
+ let controller = new AbortController();
+ let watcher = new CloseWatcher({ signal: controller.signal });
+ let oncancel_called = false;
+ let onclose_called = false;
+ watcher.oncancel = () => oncancel_called = true;
+ watcher.onclose = () => onclose_called = true;
+
+ controller.abort();
+ await test_driver.send_keys(document.getElementById('d'), ESC);
+
+ assert_false(oncancel_called);
+ assert_false(onclose_called);
+}, "abortController.abort() then close via Esc key fires no events");
+
+promise_test(async t => {
+ let controller = new AbortController();
+ let watcher = new CloseWatcher({ signal: controller.signal });
+ let oncancel_call_count_ = 0;
+ let onclose_call_count_ = 0;
+ watcher.oncancel = () => oncancel_call_count_++;
+ watcher.onclose = () => onclose_call_count_++;
+
+ await test_driver.send_keys(document.getElementById('d'), ESC);
+ controller.abort();
+
+ assert_equals(oncancel_call_count_, 0);
+ assert_equals(onclose_call_count_, 1);
+}, "Esc key then abortController.abort() fires only one close event");
+
+test(t => {
+ let controller = new AbortController();
+ let watcher = new CloseWatcher({ signal: controller.signal });
+ controller.abort();
+ let watcher2 = new CloseWatcher();
+ t.add_cleanup(() => watcher2.destroy());
+}, "abortController.abort()ing a free CloseWatcher allows a new one to be created without a user activation");
+
+promise_test(async t => {
+ let controller = new AbortController();
+ let watcher = new CloseWatcher({ signal: controller.signal });
+ watcher.oncancel = () => { controller.abort(); }
+ watcher.onclose = t.unreached_func("onclose");
+ await test_driver.bless("give user activation so that cancel will fire", () => {
+ watcher.close();
+ });
+}, "abortController.abort() inside oncancel");
+
+test(t => {
+ let controller = new AbortController();
+ let watcher = new CloseWatcher({ signal: controller.signal });
+ watcher.onclose = () => { controller.abort(); }
+ watcher.close();
+}, "abortController.abort() inside onclose is benign");
+</script>
diff --git a/testing/web-platform/tests/close-watcher/after-other-listeners.html b/testing/web-platform/tests/close-watcher/after-other-listeners.html
new file mode 100644
index 0000000000..7dfe398012
--- /dev/null
+++ b/testing/web-platform/tests/close-watcher/after-other-listeners.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=author href="mailto:japhet@chromium.org">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1312594">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+
+<div id='d' style='height: 100px; width: 100px'></div>
+<script>
+// *not* \uu001B; see https://w3c.github.io/webdriver/#keyboard-actions
+const ESC = '\uE00C';
+
+promise_test(async t => {
+ let watcher = new CloseWatcher();
+ let onclose_called = false;
+ watcher.onclose = () => onclose_called = true;
+
+ window.onkeydown = e => e.preventDefault();
+
+ await test_driver.send_keys(document.getElementById('d'), ESC);
+ assert_false(onclose_called);
+}, "normal event listeners come before CloseWatcher");
+</script>
diff --git a/testing/web-platform/tests/close-watcher/basic.html b/testing/web-platform/tests/close-watcher/basic.html
new file mode 100644
index 0000000000..1c26c0ce15
--- /dev/null
+++ b/testing/web-platform/tests/close-watcher/basic.html
@@ -0,0 +1,167 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+
+<div id='d' style='height: 100px; width: 100px'></div>
+<script>
+// *not* \uu001B; see https://w3c.github.io/webdriver/#keyboard-actions
+const ESC = '\uE00C';
+
+test(() => {
+ let watcher = new CloseWatcher();
+ let oncancel_called = false;
+ let onclose_called = false;
+ watcher.oncancel = () => oncancel_called = true;
+ watcher.onclose = e => {
+ assert_equals(e.constructor, Event);
+ assert_false(e.cancelable);
+ assert_false(e.bubbles);
+ onclose_called = true;
+ }
+
+ watcher.close();
+
+ assert_false(oncancel_called);
+ assert_true(onclose_called);
+}, "close() with no user activation only fires close");
+
+test(() => {
+ let watcher = new CloseWatcher();
+ let oncancel_called = false;
+ let onclose_called = false;
+ watcher.oncancel = () => oncancel_called = true;
+ watcher.onclose = () => onclose_called = true;
+
+ watcher.destroy();
+ watcher.close();
+
+ assert_false(oncancel_called);
+ assert_false(onclose_called);
+}, "destroy() then close() fires no events");
+
+test(() => {
+ let watcher = new CloseWatcher();
+ let oncancel_call_count_ = 0;
+ let onclose_call_count_ = 0;
+ watcher.oncancel = () => oncancel_call_count_++;
+ watcher.onclose = () => onclose_call_count_++;
+
+ watcher.close();
+ watcher.destroy();
+ assert_equals(oncancel_call_count_, 0);
+ assert_equals(onclose_call_count_, 1);
+}, "close() then destroy() fires only one close event");
+
+promise_test(async t => {
+ let watcher = new CloseWatcher();
+ let oncancel_called = false;
+ let onclose_called = false;
+ watcher.oncancel = () => oncancel_called = true;
+ watcher.onclose = () => onclose_called = true;
+
+ await test_driver.send_keys(document.getElementById('d'), ESC);
+
+ assert_false(oncancel_called);
+ assert_true(onclose_called);
+}, "Esc key does not count as user activation, so it fires close but not cancel");
+
+promise_test(async t => {
+ let watcher = new CloseWatcher();
+ let oncancel_called = false;
+ let onclose_called = false;
+ watcher.oncancel = () => oncancel_called = true;
+ watcher.onclose = () => onclose_called = true;
+
+ watcher.destroy();
+ await test_driver.send_keys(document.getElementById('d'), ESC);
+
+ assert_false(oncancel_called);
+ assert_false(onclose_called);
+}, "destroy() then close via Esc key fires no events");
+
+promise_test(async t => {
+ let watcher = new CloseWatcher();
+ let oncancel_call_count_ = 0;
+ let onclose_call_count_ = 0;
+ watcher.oncancel = () => oncancel_call_count_++;
+ watcher.onclose = () => onclose_call_count_++;
+
+ await test_driver.send_keys(document.getElementById('d'), ESC);
+ watcher.destroy();
+
+ assert_equals(oncancel_call_count_, 0);
+ assert_equals(onclose_call_count_, 1);
+}, "Esc key then destroy() fires only one close event");
+
+test(t => {
+ let watcher = new CloseWatcher();
+ let oncancel_called = false;
+ let onclose_called = false;
+ watcher.oncancel = () => oncancel_called = true;
+ watcher.onclose = () => onclose_called = true;
+
+ t.add_cleanup(() => watcher.destroy());
+
+ let keydown = new KeyboardEvent('keydown', {'key': 'Escape', 'keyCode': 27});
+ window.dispatchEvent(keydown);
+ let keyup = new KeyboardEvent('keyup', {'key': 'Escape', 'keyCode': 27});
+ window.dispatchEvent(keyup);
+
+ assert_false(oncancel_called);
+ assert_false(onclose_called);
+
+ let keyup2 = document.createEvent("Event");
+ keyup2.initEvent("keyup", true);
+ window.dispatchEvent(keyup2);
+
+ assert_false(oncancel_called);
+ assert_false(onclose_called);
+}, "close via synthesized escape key should not work");
+
+promise_test(async t => {
+ let watcher = new CloseWatcher();
+ watcher.oncancel = () => { watcher.destroy(); }
+ watcher.onclose = t.unreached_func("onclose");
+ await test_driver.bless("give user activation so that cancel will fire", () => {
+ watcher.close();
+ });
+}, "destroy inside oncancel");
+
+test(t => {
+ let watcher = new CloseWatcher();
+ watcher.onclose = () => { watcher.destroy(); }
+ watcher.close();
+}, "destroy inside onclose is benign");
+
+promise_test(async t => {
+ let watcher = new CloseWatcher();
+ watcher.oncancel = () => { watcher.close(); }
+ await test_driver.bless("give user activation so that cancel will fire", () => {
+ watcher.close();
+ });
+}, "close inside oncancel should not trigger an infinite loop");
+
+test(t => {
+ let watcher = new CloseWatcher();
+ watcher.onclose = () => { watcher.close(); }
+ watcher.close();
+}, "close inside onclose should not trigger an infinite loop");
+
+promise_test(async () => {
+ let watcher = new CloseWatcher();
+ let oncancel_called = false;
+ let onclose_called = false;
+ watcher.addEventListener("cancel", () => oncancel_called = true);
+ watcher.addEventListener("close", () => onclose_called = true);
+
+ await test_driver.bless("give user activation so that cancel will fire", () => {
+ watcher.close();
+ });
+
+ assert_true(oncancel_called);
+ assert_true(onclose_called);
+}, "close with events added via addEventListener");
+</script>
diff --git a/testing/web-platform/tests/close-watcher/frame-removal.html b/testing/web-platform/tests/close-watcher/frame-removal.html
new file mode 100644
index 0000000000..b8bbac04f9
--- /dev/null
+++ b/testing/web-platform/tests/close-watcher/frame-removal.html
@@ -0,0 +1,59 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+promise_test(async (t) => {
+ const i = await setupIframe();
+ const watcher = new i.contentWindow.CloseWatcher();
+ watcher.oncancel = () => i.remove();
+ watcher.onclose = () => t.unreached_func("close event must not fire");
+
+ watcher.close();
+}, "detaching the iframe during the cancel event");
+
+promise_test(async (t) => {
+ const i = await setupIframe();
+ const watcher = new i.contentWindow.CloseWatcher();
+ watcher.onclose = () => i.remove();
+
+ watcher.close();
+}, "detaching the iframe during the close event");
+
+promise_test(async (t) => {
+ const i = await setupIframe();
+ const watcher = new i.contentWindow.CloseWatcher();
+ i.remove();
+
+ watcher.destroy();
+}, "detaching the iframe then calling destroy()");
+
+promise_test(async (t) => {
+ const i = await setupIframe();
+ const watcher = new i.contentWindow.CloseWatcher();
+ watcher.oncancel = () => t.unreached_func("cancel event must not fire");
+ watcher.onclose = () => t.unreached_func("close event must not fire");
+ i.remove();
+
+ watcher.close();
+}, "detaching the iframe then calling close()");
+
+promise_test(async (t) => {
+ const i = await setupIframe();
+ const iCloseWatcher = i.contentWindow.CloseWatcher;
+ const iDOMException = i.contentWindow.DOMException;
+ i.remove();
+
+ assert_throws_dom("InvalidStateError", iDOMException, () => new iCloseWatcher());
+}, "detaching the iframe then constructing a CloseWatcher");
+
+function setupIframe() {
+ return new Promise(resolve => {
+ const i = document.createElement("iframe");
+ i.onload = () => resolve(i);
+ i.src = "/common/blank.html";
+ document.body.append(i);
+ });
+}
+</script>
diff --git a/testing/web-platform/tests/close-watcher/resources/helpers.js b/testing/web-platform/tests/close-watcher/resources/helpers.js
new file mode 100644
index 0000000000..4e024a1908
--- /dev/null
+++ b/testing/web-platform/tests/close-watcher/resources/helpers.js
@@ -0,0 +1,21 @@
+// TODO(domenic): consider using these in all test files.
+
+window.createRecordingCloseWatcher = (t, events, name) => {
+ const watcher = new CloseWatcher();
+ t.add_cleanup(() => watcher.destroy());
+ watcher.oncancel = () => events.push(name + " cancel");
+ watcher.onclose = () => events.push(name + " close");
+
+ return watcher;
+};
+
+window.createBlessedRecordingCloseWatcher = (t, events, name) => {
+ return test_driver.bless("create " + name, () => createRecordingCloseWatcher(t, events, name));
+};
+
+window.sendCloseSignal = () => {
+ // *not* \uu001B; see https://w3c.github.io/webdriver/#keyboard-actions
+ const ESC = '\uE00C';
+
+ return test_driver.send_keys(document.getElementById("d"), ESC);
+};
diff --git a/testing/web-platform/tests/close-watcher/user-activation-multiple-plus-free.html b/testing/web-platform/tests/close-watcher/user-activation-multiple-plus-free.html
new file mode 100644
index 0000000000..8a86624913
--- /dev/null
+++ b/testing/web-platform/tests/close-watcher/user-activation-multiple-plus-free.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="resources/helpers.js"></script>
+
+<div id="d" style="height: 100px; width: 100px;"></div>
+<script>
+
+// This test needs to be separate from user-activation.html since, unlike those,
+// it relies on there not being any lingering user activation from previous
+// tests hanging around. That is, we need to be sure freeWatcher is created with
+// no user activation, to ensure that activationWatcher1 and activationWatcher2
+// get grouped as expected.
+promise_test(async t => {
+ const events = [];
+ createRecordingCloseWatcher(t, events, "freeWatcher");
+
+ await test_driver.bless("create two more CloseWatchers", () => {
+ createRecordingCloseWatcher(t, events, "activationWatcher1");
+ createRecordingCloseWatcher(t, events, "activationWatcher2");
+ });
+
+ await sendCloseSignal();
+ assert_array_equals(events, ["activationWatcher2 close", "activationWatcher1 close"]);
+
+ await sendCloseSignal();
+ assert_array_equals(events, ["activationWatcher2 close", "activationWatcher1 close", "freeWatcher close"]);
+}, "Multiple CloseWatchers created from a single user activation close together, but original free CloseWatcher closes separately");
+</script>
diff --git a/testing/web-platform/tests/close-watcher/user-activation.html b/testing/web-platform/tests/close-watcher/user-activation.html
new file mode 100644
index 0000000000..64f217533d
--- /dev/null
+++ b/testing/web-platform/tests/close-watcher/user-activation.html
@@ -0,0 +1,212 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="resources/helpers.js"></script>
+
+<div id="d" style="height: 100px; width: 100px;"></div>
+<script>
+promise_test(async t => {
+ const events = [];
+ const freeWatcher = createRecordingCloseWatcher(t, events, "freeWatcher");
+
+ await test_driver.bless("call close()", () => freeWatcher.close());
+
+ assert_array_equals(events, ["freeWatcher cancel", "freeWatcher close"]);
+}, "CloseWatchers created without user activation, but close()d via user activation, fires cancel");
+
+promise_test(async t => {
+ const events = [];
+ const freeWatcher = createRecordingCloseWatcher(t, events, "freeWatcher");
+ freeWatcher.addEventListener("cancel", e => e.preventDefault());
+
+ await test_driver.bless("call close()", () => freeWatcher.close());
+
+ assert_array_equals(events, ["freeWatcher cancel"]);
+}, "CloseWatchers created without user activation, but close()d via user activation, fires cancel, which can be preventDefault()ed");
+
+promise_test(async t => {
+ const events = [];
+ const freeWatcher = createRecordingCloseWatcher(t, events, "freeWatcher");
+
+ await test_driver.bless("grant user activation", () => sendCloseSignal());
+
+ assert_array_equals(events, ["freeWatcher cancel", "freeWatcher close"]);
+}, "CloseWatchers created without user activation, but closed via a close signal after user activation, fires cancel");
+
+promise_test(async t => {
+ const events = [];
+ const freeWatcher = createRecordingCloseWatcher(t, events, "freeWatcher");
+ freeWatcher.addEventListener("cancel", e => e.preventDefault());
+
+ await test_driver.bless("grant user activation", () => sendCloseSignal());
+
+ assert_array_equals(events, ["freeWatcher cancel"]);
+}, "CloseWatchers created without user activation, but closed via a close signal after user activation, fires cancel, which can be preventDefault()ed");
+
+promise_test(async t => {
+ const events = [];
+ createRecordingCloseWatcher(t, events, "freeWatcher");
+ createRecordingCloseWatcher(t, events, "watcher1");
+ createRecordingCloseWatcher(t, events, "watcher2");
+
+ await sendCloseSignal();
+ assert_array_equals(events, ["watcher2 close", "watcher1 close", "freeWatcher close"]);
+}, "Multiple CloseWatchers created without user activation close together (with no cancel)");
+
+promise_test(async t => {
+ const events = [];
+ createRecordingCloseWatcher(t, events, "freeWatcher");
+ await createBlessedRecordingCloseWatcher(t, events, "activationWatcher");
+
+ await sendCloseSignal();
+ assert_array_equals(events, ["activationWatcher close"]);
+
+ await sendCloseSignal();
+ assert_array_equals(events, ["activationWatcher close", "freeWatcher close"]);
+}, "Creating a CloseWatcher from user activation keeps it separate from the free CloseWatcher, but they don't fire cancel");
+
+promise_test(async t => {
+ const events = [];
+ const freeWatcher = createRecordingCloseWatcher(t, events, "freeWatcher");
+ const activationWatcher = await createBlessedRecordingCloseWatcher(t, events, "activationWatcher");
+
+ await test_driver.bless("call activationWatcher.close()", () => activationWatcher.close());
+ assert_array_equals(events, ["activationWatcher cancel", "activationWatcher close"]);
+
+ await test_driver.bless("call freeWatcher.close()", () => freeWatcher.close());
+ assert_array_equals(events, ["activationWatcher cancel", "activationWatcher close", "freeWatcher cancel", "freeWatcher close"]);
+}, "Creating a CloseWatcher from user activation, and close()ing CloseWatchers with user activation, fires cancel");
+
+promise_test(async t => {
+ const events = [];
+ const freeWatcher = createRecordingCloseWatcher(t, events, "freeWatcher");
+ const activationWatcher = await createBlessedRecordingCloseWatcher(t, events, "activationWatcher");
+
+ await test_driver.bless("grant user activation", () => sendCloseSignal());
+ assert_array_equals(events, ["activationWatcher cancel", "activationWatcher close"]);
+
+ await test_driver.bless("grant user activation", () => sendCloseSignal());
+ assert_array_equals(events, ["activationWatcher cancel", "activationWatcher close", "freeWatcher cancel", "freeWatcher close"]);
+}, "Creating a CloseWatcher from user activation, and closing CloseWatchers with a close signal after user activation, fires cancel");
+
+promise_test(async t => {
+ const events = [];
+ createRecordingCloseWatcher(t, events, "freeWatcher");
+ await createBlessedRecordingCloseWatcher(t, events, "activationWatcher1");
+ await createBlessedRecordingCloseWatcher(t, events, "activationWatcher2");
+
+ await sendCloseSignal();
+ assert_array_equals(events, ["activationWatcher2 close"]);
+
+ await sendCloseSignal();
+ assert_array_equals(events, ["activationWatcher2 close", "activationWatcher1 close"]);
+}, "Multiple CloseWatchers created with user activation close in reverse order");
+
+promise_test(async t => {
+ const events = [];
+ createRecordingCloseWatcher(t, events, "freeWatcher");
+ await createBlessedRecordingCloseWatcher(t, events, "activationWatcher1");
+ await createBlessedRecordingCloseWatcher(t, events, "activationWatcher2");
+ await createBlessedRecordingCloseWatcher(t, events, "activationWatcher3");
+ createRecordingCloseWatcher(t, events, "watcher4");
+
+ await sendCloseSignal();
+ assert_array_equals(events, ["watcher4 close", "activationWatcher3 close"]);
+
+ await sendCloseSignal();
+ assert_array_equals(events, ["watcher4 close", "activationWatcher3 close", "activationWatcher2 close"]);
+
+ await sendCloseSignal();
+ assert_array_equals(events, ["watcher4 close", "activationWatcher3 close", "activationWatcher2 close", "activationWatcher1 close"]);
+
+ await sendCloseSignal();
+ assert_array_equals(events, ["watcher4 close", "activationWatcher3 close", "activationWatcher2 close", "activationWatcher1 close", "freeWatcher close"]);
+}, "3 user activations let you have 3 + 1 = 4 ungrouped close watchers/0 cancel events");
+
+promise_test(async t => {
+ const events = [];
+ const freeWatcher = createRecordingCloseWatcher(t, events, "freeWatcher");
+ const activationWatcher1 = await createBlessedRecordingCloseWatcher(t, events, "activationWatcher1");
+ activationWatcher1.addEventListener("cancel", e => e.preventDefault());
+
+ await test_driver.bless("call activationWatcher1.close()", () => activationWatcher1.close());
+ assert_array_equals(events, ["activationWatcher1 cancel"]);
+
+ // This time we go straight to close, without a second cancel.
+ activationWatcher1.close();
+ assert_array_equals(events, ["activationWatcher1 cancel", "activationWatcher1 close"]);
+
+ freeWatcher.close();
+ assert_array_equals(events, ["activationWatcher1 cancel", "activationWatcher1 close", "freeWatcher close"]);
+}, "3 user activations let you have 2 close watchers with 1 cancel event, even if the first cancel event is prevented");
+
+promise_test(async t => {
+ const events = [];
+ const freeWatcher1 = createRecordingCloseWatcher(t, events, "freeWatcher1");
+
+ freeWatcher1.destroy();
+ assert_array_equals(events, []);
+
+ const freeWatcher2 = createRecordingCloseWatcher(t, events, "freeWatcher2");
+
+ await sendCloseSignal();
+ assert_array_equals(events, ["freeWatcher2 close"]);
+}, "destroy()ing the free CloseWatcher allows a new free one to be created without user activation, and it receives the close signal");
+
+promise_test(async t => {
+ const events = [];
+ const freeWatcher1 = createRecordingCloseWatcher(t, events, "freeWatcher1");
+
+ freeWatcher1.close();
+ assert_array_equals(events, ["freeWatcher1 close"]);
+
+ const freeWatcher2 = createRecordingCloseWatcher(t, events, "freeWatcher2");
+
+ await sendCloseSignal();
+ assert_array_equals(events, ["freeWatcher1 close", "freeWatcher2 close"]);
+}, "close()ing the free CloseWatcher allows a new free one to be created without user activation, and it receives the close signal");
+
+promise_test(async t => {
+ const events = [];
+ const freeWatcher1 = createRecordingCloseWatcher(t, events, "freeWatcher1");
+
+ await sendCloseSignal();
+ assert_array_equals(events, ["freeWatcher1 close"]);
+
+ const freeWatcher2 = createRecordingCloseWatcher(t, events, "freeWatcher2");
+
+ await sendCloseSignal();
+ assert_array_equals(events, ["freeWatcher1 close", "freeWatcher2 close"]);
+}, "closing the free CloseWatcher via a close signal allows a new free one to be created without user activation, and it receives a second close signal");
+
+promise_test(async t => {
+ const events = [];
+ const activationWatcher = await createBlessedRecordingCloseWatcher(t, events, "activationWatcher");
+ const freeWatcher = createRecordingCloseWatcher(t, events, "freeWatcher");
+
+ await sendCloseSignal();
+ assert_array_equals(events, ["freeWatcher close"]);
+
+ await sendCloseSignal();
+ assert_array_equals(events, ["freeWatcher close", "activationWatcher close"]);
+}, "The second watcher can be the free watcher, if the first is created with user activation");
+
+promise_test(async t => {
+ const events = [];
+ const activationWatcher1 = await createBlessedRecordingCloseWatcher(t, events, "activationWatcher1");
+ const activationWatcher2 = await createBlessedRecordingCloseWatcher(t, events, "activationWatcher2");
+ const freeWatcher = createRecordingCloseWatcher(t, events, "freeWatcher");
+
+ await sendCloseSignal();
+ assert_array_equals(events, ["freeWatcher close"]);
+
+ await sendCloseSignal();
+ assert_array_equals(events, ["freeWatcher close", "activationWatcher2 close"]);
+
+ await sendCloseSignal();
+ assert_array_equals(events, ["freeWatcher close", "activationWatcher2 close", "activationWatcher1 close"]);
+}, "The third watcher can be the free watcher, if the first two are created with user activation");
+</script>