summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/screen-orientation
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
commit0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch)
treea31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /testing/web-platform/tests/screen-orientation
parentInitial commit. (diff)
downloadfirefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz
firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/screen-orientation')
-rw-r--r--testing/web-platform/tests/screen-orientation/META.yml6
-rw-r--r--testing/web-platform/tests/screen-orientation/active-lock.html55
-rw-r--r--testing/web-platform/tests/screen-orientation/event-before-promise.html26
-rw-r--r--testing/web-platform/tests/screen-orientation/fullscreen-interactions.html33
-rw-r--r--testing/web-platform/tests/screen-orientation/hidden_document.html34
-rw-r--r--testing/web-platform/tests/screen-orientation/idlharness.window.js17
-rw-r--r--testing/web-platform/tests/screen-orientation/lock-bad-argument.html26
-rw-r--r--testing/web-platform/tests/screen-orientation/lock-basic.html76
-rw-r--r--testing/web-platform/tests/screen-orientation/lock-sandboxed-iframe.html59
-rw-r--r--testing/web-platform/tests/screen-orientation/lock-unlock-check.html36
-rw-r--r--testing/web-platform/tests/screen-orientation/nested-documents.html85
-rw-r--r--testing/web-platform/tests/screen-orientation/non-fully-active.html53
-rw-r--r--testing/web-platform/tests/screen-orientation/onchange-event-subframe.html83
-rw-r--r--testing/web-platform/tests/screen-orientation/onchange-event.html45
-rw-r--r--testing/web-platform/tests/screen-orientation/orientation-reading.html119
-rw-r--r--testing/web-platform/tests/screen-orientation/page-visibility-manual.html110
-rw-r--r--testing/web-platform/tests/screen-orientation/resources/empty.html1
-rw-r--r--testing/web-platform/tests/screen-orientation/resources/iframe-listen-orientation-change.html9
-rw-r--r--testing/web-platform/tests/screen-orientation/resources/orientation-utils.js52
-rw-r--r--testing/web-platform/tests/screen-orientation/resources/sandboxed-iframe-locking.html32
-rw-r--r--testing/web-platform/tests/screen-orientation/unlock.html56
21 files changed, 1013 insertions, 0 deletions
diff --git a/testing/web-platform/tests/screen-orientation/META.yml b/testing/web-platform/tests/screen-orientation/META.yml
new file mode 100644
index 0000000000..633c917b0e
--- /dev/null
+++ b/testing/web-platform/tests/screen-orientation/META.yml
@@ -0,0 +1,6 @@
+spec: https://w3c.github.io/screen-orientation/
+suggested_reviewers:
+ - marcoscaceres
+ - cdumez
+ - michaelwasserman
+ - makotokato
diff --git a/testing/web-platform/tests/screen-orientation/active-lock.html b/testing/web-platform/tests/screen-orientation/active-lock.html
new file mode 100644
index 0000000000..6dcbe4a8ff
--- /dev/null
+++ b/testing/web-platform/tests/screen-orientation/active-lock.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<meta viewport="width=device-width, initial-scale=1" />
+<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>
+<p id="#fragment"></p>
+<a href="#fragment">fragment</a>
+<script type="module">
+ import {
+ attachIframe,
+ getOppositeOrientation,
+ makeCleanup,
+ } from "./resources/orientation-utils.js";
+
+ promise_test(async (t) => {
+ t.add_cleanup(makeCleanup());
+ await test_driver.bless("request full screen");
+ await document.documentElement.requestFullscreen();
+ const orientation = getOppositeOrientation();
+ const p = screen.orientation.lock("landscape");
+ await test_driver.click(document.querySelector("a"));
+ await p;
+ }, "Performing a fragment navigation must not abort the screen orientation change");
+
+ promise_test(async (t) => {
+ t.add_cleanup(makeCleanup());
+ const iframe = await attachIframe();
+ iframe.contentDocument.body.innerHTML = `
+ <p id="#fragment"></p>
+ <a href="#fragment">fragment</a>
+ `;
+ await test_driver.bless("request full screen");
+ await document.documentElement.requestFullscreen();
+ const orientation = getOppositeOrientation();
+ const p = iframe.contentWindow.screen.orientation.lock(orientation);
+ await test_driver.click(iframe.contentDocument.querySelector("a"));
+ await p;
+ iframe.remove();
+ }, "Performing a fragment navigation within an iframe must not abort the lock promise");
+
+ promise_test(async (t) => {
+ t.add_cleanup(makeCleanup());
+ const iframe = await attachIframe();
+ await test_driver.bless("request full screen");
+ await document.documentElement.requestFullscreen();
+ const orientation = getOppositeOrientation();
+ const p = iframe.contentWindow.screen.orientation.lock(orientation);
+ const frameDOMException = iframe.contentWindow.DOMException;
+ iframe.contentWindow.location.href = "./resources/empty.html";
+ await promise_rejects_dom(t, "AbortError", frameDOMException, p);
+ iframe.remove();
+ }, "Unloading an iframe by navigating it must abort the lock promise");
+</script>
diff --git a/testing/web-platform/tests/screen-orientation/event-before-promise.html b/testing/web-platform/tests/screen-orientation/event-before-promise.html
new file mode 100644
index 0000000000..56be4379ac
--- /dev/null
+++ b/testing/web-platform/tests/screen-orientation/event-before-promise.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<meta viewport="width=device-width, initial-scale=1" />
+<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 type="module">
+ import {
+ getOppositeOrientation,
+ makeCleanup,
+ } from "./resources/orientation-utils.js";
+
+ promise_test(async (t) => {
+ t.add_cleanup(makeCleanup());
+ await test_driver.bless("request full screen");
+ await document.documentElement.requestFullscreen();
+ const result = await Promise.race([
+ new Promise((resolve) => {
+ screen.orientation.addEventListener("change", resolve);
+ }),
+ screen.orientation.lock(getOppositeOrientation())
+ ]);
+ assert_true(result instanceof Event, "Expected an instance of Event");
+ }, "The 'change' event must fire before the [[orientationPendingPromise]] is resolved.");
+</script>
diff --git a/testing/web-platform/tests/screen-orientation/fullscreen-interactions.html b/testing/web-platform/tests/screen-orientation/fullscreen-interactions.html
new file mode 100644
index 0000000000..cffacc1f4f
--- /dev/null
+++ b/testing/web-platform/tests/screen-orientation/fullscreen-interactions.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<meta viewport="width=device-width, initial-scale=1" />
+<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 type="module">
+ import {
+ getOppositeOrientation,
+ attachIframe,
+ } from "./resources/orientation-utils.js";
+
+ promise_test(async (t) => {
+ await test_driver.bless("request full screen");
+ await document.documentElement.requestFullscreen();
+ const lockPromise = screen.orientation.lock(getOppositeOrientation());
+ await document.exitFullscreen();
+ await promise_rejects_dom(t, "AbortError", lockPromise);
+ }, "Fully unlocking the screen orientation causes a pending lock to be aborted");
+
+ promise_test(async (t) => {
+ const iframe = await attachIframe();
+ await test_driver.bless("request full screen");
+ await document.documentElement.requestFullscreen();
+ const lockPromise = iframe.contentWindow.screen.orientation.lock(
+ getOppositeOrientation()
+ );
+ await document.exitFullscreen();
+ const frameDOMException = iframe.contentWindow.DOMException;
+ await promise_rejects_dom(t, "AbortError", frameDOMException, lockPromise);
+ }, "Fully unlocking the screen orientation causes a pending lock in a nested browsing context to be aborted");
+</script>
diff --git a/testing/web-platform/tests/screen-orientation/hidden_document.html b/testing/web-platform/tests/screen-orientation/hidden_document.html
new file mode 100644
index 0000000000..6c39d6e6e5
--- /dev/null
+++ b/testing/web-platform/tests/screen-orientation/hidden_document.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta name="timeout" content="long" />
+<title>
+ Prevent hidden documents from locking orientation
+</title>
+<link rel="help" href="https://github.com/w3c/screen-orientation/pull/232" />
+<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="/page-visibility/resources/window_state_context.js"></script>
+<script type="module">
+ import { makeCleanup, getOppositeOrientation } from "./resources/orientation-utils.js";
+
+ promise_test(async (t) => {
+ const { minimize, restore } = window_state_context(t);
+ t.add_cleanup(makeCleanup());
+
+ await minimize();
+
+ assert_equals(document.visibilityState, "hidden", "Document must be hidden");
+ const opposite = getOppositeOrientation();
+ await promise_rejects_dom(t, "SecurityError", screen.orientation.lock(opposite) );
+ }, "hidden documents must reject went trying to call lock or unlock");
+
+ promise_test(async (t) => {
+ const { minimize, restore } = window_state_context(t);
+
+ await minimize();
+
+ assert_equals(document.visibilityState, "hidden", "Document must be hidden");
+ assert_throws_dom("SecurityError", () => screen.orientation.unlock());
+ }, "hidden documents must reject went trying to call unlock");
+</script>
diff --git a/testing/web-platform/tests/screen-orientation/idlharness.window.js b/testing/web-platform/tests/screen-orientation/idlharness.window.js
new file mode 100644
index 0000000000..115f6ccb1e
--- /dev/null
+++ b/testing/web-platform/tests/screen-orientation/idlharness.window.js
@@ -0,0 +1,17 @@
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+
+'use strict';
+
+// https://w3c.github.io/screen-orientation/
+
+idl_test(
+ ['screen-orientation'],
+ ['dom', 'cssom-view', 'html'],
+ idl_array => {
+ idl_array.add_objects({
+ Screen: ['screen'],
+ ScreenOrientation: ['screen.orientation']
+ });
+ }
+);
diff --git a/testing/web-platform/tests/screen-orientation/lock-bad-argument.html b/testing/web-platform/tests/screen-orientation/lock-bad-argument.html
new file mode 100644
index 0000000000..f13ce2a8b9
--- /dev/null
+++ b/testing/web-platform/tests/screen-orientation/lock-bad-argument.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+promise_test(t => {
+ const invalid_lock_types = [
+ "invalid-orientation",
+ null,
+ undefined,
+ 123,
+ window,
+ "",
+ true,
+ ["portrait-primary", "landscape-primary"],
+ ];
+ const promisesToReject = invalid_lock_types.map(type =>
+ promise_rejects_js(t, TypeError, screen.orientation.lock(type))
+ );
+ return Promise.all(promisesToReject);
+}, "screen.orientation.lock() must throw given invalid input.");
+
+promise_test(t => {
+ return promise_rejects_js(t, TypeError, screen.orientation.lock());
+}, "screen.orientation.lock() must throw when the input is missing.");
+</script>
+
diff --git a/testing/web-platform/tests/screen-orientation/lock-basic.html b/testing/web-platform/tests/screen-orientation/lock-basic.html
new file mode 100644
index 0000000000..584747113c
--- /dev/null
+++ b/testing/web-platform/tests/screen-orientation/lock-basic.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<meta viewport="width=device-width, initial-scale=1" />
+<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 type="module">
+import { attachIframe, makeCleanup, getOppositeOrientation } from "./resources/orientation-utils.js";
+
+promise_test(async t => {
+ t.add_cleanup(makeCleanup());
+ await test_driver.bless("request full screen")
+ await document.documentElement.requestFullscreen();
+ const value = await screen.orientation.lock('any');
+ assert_equals(value, undefined);
+}, "Test that screen.orientation.lock returns a promise which will be fulfilled with a void value.");
+
+promise_test(async t => {
+ t.add_cleanup(makeCleanup());
+ await test_driver.bless("request full screen")
+ await document.documentElement.requestFullscreen();
+ const initialOrientation = screen.orientation.type;
+ const orientations = [
+ 'any',
+ 'natural',
+ 'portrait',
+ 'landscape',
+ 'portrait-secondary',
+ 'landscape-primary',
+ 'landscape-secondary',
+ 'portrait-primary',
+ ];
+ for (const orientation of orientations) {
+ try {
+ await screen.orientation.lock(orientation);
+ } catch(err) {
+ if (err.name === "NotSupportedError") {
+ continue;
+ }
+ assert_unreached("Unknown error: " + err);
+ }
+ const { type } = screen.orientation;
+ switch (orientation) {
+ case 'any':
+ break;
+ case 'natural':
+ assert_true(type.endsWith("primary"), `Expected primary orientation for "${orientation}", got "${type}"`);
+ break;
+ case 'portrait':
+ assert_true(type.startsWith("portrait"), `Expected portrait orientation for "${orientation}", got "${type}"`);
+ break;
+ case 'landscape':
+ assert_true(type.startsWith("landscape"), `Expected landscape orientation for "${orientation}", got "${type}"`);
+ break;
+ default:
+ assert_equals(type, orientation, "Expected orientation to change");
+ break;
+ }
+ await screen.orientation.lock(initialOrientation);
+ }
+}, "Test that screen.orientation.lock returns a pending promise.");
+
+promise_test(async t => {
+ t.add_cleanup(makeCleanup());
+ await test_driver.bless("request full screen")
+ await document.documentElement.requestFullscreen();
+ const initialType = screen.orientation.type;
+ const newType = getOppositeOrientation();
+ const p = screen.orientation.lock(newType);
+ assert_equals(screen.orientation.type, initialType, "Must not change orientation until next spin of event loop");
+ await p;
+ const finalType = screen.orientation.type;
+ assert_true(finalType.startsWith(newType), `Expected type to start with ${newType}, got "${finalType}"`);
+}, "Test that screen.orientation.lock() is actually async");
+</script>
diff --git a/testing/web-platform/tests/screen-orientation/lock-sandboxed-iframe.html b/testing/web-platform/tests/screen-orientation/lock-sandboxed-iframe.html
new file mode 100644
index 0000000000..ab954f04b4
--- /dev/null
+++ b/testing/web-platform/tests/screen-orientation/lock-sandboxed-iframe.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<meta viewport="width=device-width, initial-scale=1" />
+<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 type="module">
+ import {
+ attachIframe
+ } from "./resources/orientation-utils.js";
+
+ function wait_result() {
+ return new Promise((resolve) => {
+ function callback(evt) {
+ switch (evt.data.result) {
+ case "locked":
+ resolve(evt.data.orientation);
+ break;
+ case "errored":
+ resolve(evt.data.name);
+ break;
+ default:
+ assert_unreached(`Unexpected message: ${evt.data.result}`);
+ return;
+ }
+ window.removeEventListener("message", callback);
+ resolve(evt.data.msg);
+ }
+ window.addEventListener("message", callback);
+ });
+ }
+
+ promise_test(async (t) => {
+ const iframe = await attachIframe({
+ src: "resources/sandboxed-iframe-locking.html",
+ sandbox: "allow-scripts allow-same-origin",
+ });
+ const message = await wait_result();
+ assert_equals(
+ message,
+ "SecurityError",
+ "screen.lockOrientation() throws a SecurityError"
+ );
+ }, "Test without 'allow-orientation-lock' sandboxing directive");
+
+ promise_test(async (t) => {
+ const iframe = await attachIframe({
+ src: "resources/sandboxed-iframe-locking.html",
+ sandbox: "allow-scripts allow-same-origin allow-orientation-lock",
+ });
+ const message = await wait_result();
+ assert_equals(
+ message,
+ "portrait-primary",
+ "screen.orientation lock to portrait-primary"
+ );
+ }, "Test with 'allow-orientation-lock' sandboxing directive");
+</script>
diff --git a/testing/web-platform/tests/screen-orientation/lock-unlock-check.html b/testing/web-platform/tests/screen-orientation/lock-unlock-check.html
new file mode 100644
index 0000000000..b415f7b2a8
--- /dev/null
+++ b/testing/web-platform/tests/screen-orientation/lock-unlock-check.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<meta viewport="width=device-width, initial-scale=1" />
+<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 type="module">
+ import {
+ attachIframe,
+ getOppositeOrientation,
+ } from "./resources/orientation-utils.js";
+
+ promise_test(async (t) => {
+ await test_driver.bless("request full screen");
+ await document.documentElement.requestFullscreen();
+ screen.orientation.addEventListener(
+ "change",
+ async () => {
+ await screen.orientation.lock(getOppositeOrientation());
+ },
+ { once: true }
+ );
+ await screen.orientation.lock(getOppositeOrientation());
+ }, "Re-locking the screen orientation after a change event fires must not abort");
+
+ promise_test(async (t) => {
+ await test_driver.bless("request full screen");
+ await document.documentElement.requestFullscreen();
+ screen.orientation.onchange = async () => {
+ screen.orientation.onchange = null;
+ screen.orientation.unlock();
+ };
+ await screen.orientation.lock(getOppositeOrientation());
+ }, "Unlocking the screen orientation after a change event must not abort");
+</script>
diff --git a/testing/web-platform/tests/screen-orientation/nested-documents.html b/testing/web-platform/tests/screen-orientation/nested-documents.html
new file mode 100644
index 0000000000..efaed9cec8
--- /dev/null
+++ b/testing/web-platform/tests/screen-orientation/nested-documents.html
@@ -0,0 +1,85 @@
+<!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>
+<body>
+ <script type="module">
+ import {
+ attachIframe,
+ makeCleanup,
+ getOppositeOrientation,
+ } from "./resources/orientation-utils.js";
+
+ promise_test(async (t) => {
+ t.add_cleanup(makeCleanup());
+ const iframe = await attachIframe();
+ const iframeWin = iframe.contentWindow;
+
+ // Go full screen
+ await test_driver.bless("request full screen");
+ await iframe.contentDocument.documentElement.requestFullscreen();
+
+ // Lock the orientation from the iframe
+ const opposite = getOppositeOrientation();
+ const iframePromise = iframeWin.screen.orientation.lock(opposite);
+
+ // Calling lock() from top-level will cancel the iframe's promise
+ const topPromise = window.screen.orientation.lock(opposite);
+ await promise_rejects_dom(
+ t,
+ "AbortError",
+ iframeWin.DOMException,
+ iframePromise
+ );
+ await topPromise;
+ }, "Requesting orientation lock from one document cancels the lock request from another document");
+
+ promise_test(async (t) => {
+ t.add_cleanup(makeCleanup());
+ // Create 3 nested iframes
+ const src = "/screen-orientation/resources/empty.html";
+ const outerIframe = await attachIframe({ src: `${src}#1` });
+ const innerIframe = await attachIframe({
+ context: outerIframe.contentWindow,
+ src: `${src}#2`,
+ });
+
+ const iframes = [outerIframe, innerIframe];
+
+ // Go full screen
+ await test_driver.bless("request full screen");
+ await innerIframe.contentDocument.documentElement.requestFullscreen();
+ const opposite = getOppositeOrientation();
+
+ // Each iframe tries to lock the orientation
+ const requestToLock = iframes.map((iframe) => {
+ return {
+ promise: iframe.contentWindow.screen.orientation.lock(opposite),
+ context: iframe.contentWindow,
+ };
+ });
+
+ // But calling lock() from top-level will aborts all iframe's promises
+ const topPromise = window.screen.orientation.lock(opposite);
+
+ // Check that all promises are rejected with AbortError
+ const abortedPromises = [];
+ for (let i = 0; i < requestToLock.length; i++) {
+ const { promise, context } = requestToLock[i];
+ const p = promise_rejects_dom(
+ t,
+ "AbortError",
+ context.DOMException,
+ promise,
+ `Expected request to lock orientation from iframe ${i} to abort`
+ );
+ abortedPromises.push(p);
+ }
+ await Promise.all(abortedPromises);
+
+ // Finally, top-level promise resolves
+ await topPromise;
+ }, "The orientation lock from one document affects lock requests from other documents");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/screen-orientation/non-fully-active.html b/testing/web-platform/tests/screen-orientation/non-fully-active.html
new file mode 100644
index 0000000000..ee1acf07bd
--- /dev/null
+++ b/testing/web-platform/tests/screen-orientation/non-fully-active.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<meta viewport="width=device-width, initial-scale=1" />
+<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>
+<body>
+<script type="module">
+ import { attachIframe, getOppositeOrientation } from "./resources/orientation-utils.js";
+
+ promise_test(async (t) => {
+ const iframe = await attachIframe();
+ const { orientation } = iframe.contentWindow.screen;
+
+ const frameDOMException = iframe.contentWindow.DOMException;
+ iframe.remove();
+
+ await promise_rejects_dom(
+ t,
+ "InvalidStateError",
+ frameDOMException,
+ orientation.lock(getOppositeOrientation())
+ );
+ }, "Attempting to lock non-fully active documents results in a InvalidStateError");
+
+ promise_test(async (t) => {
+ const iframe = await attachIframe();
+ const { orientation } = iframe.contentWindow.screen;
+
+ const frameDOMException = iframe.contentWindow.DOMException;
+ iframe.remove();
+
+ assert_throws_dom("InvalidStateError", frameDOMException, () => { orientation.unlock() });
+ }, "Attempting to unlock non-fully active documents results in a InvalidStateError");
+
+ promise_test(async (t) => {
+ const iframe = await attachIframe();
+ const { orientation } = iframe.contentWindow.screen;
+
+ await test_driver.bless("request full screen", null, iframe.contentWindow);
+ await iframe.contentDocument.documentElement.requestFullscreen();
+
+ const p = orientation.lock(getOppositeOrientation());
+
+ const frameDOMException = iframe.contentWindow.DOMException;
+ iframe.remove();
+
+ await promise_rejects_dom(t, "AbortError", frameDOMException, p);
+ assert_throws_dom("InvalidStateError", frameDOMException, () => { orientation.unlock() });
+ }, "Making a document non-fully active while locking results in an AbortError");
+</script>
+</body>
diff --git a/testing/web-platform/tests/screen-orientation/onchange-event-subframe.html b/testing/web-platform/tests/screen-orientation/onchange-event-subframe.html
new file mode 100644
index 0000000000..1b4b8cd428
--- /dev/null
+++ b/testing/web-platform/tests/screen-orientation/onchange-event-subframe.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<meta viewport="width=device-width, initial-scale=1" />
+<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 type="module">
+ import {
+ attachIframe,
+ makeCleanup,
+ getOppositeOrientation,
+ } from "./resources/orientation-utils.js";
+
+ promise_test(async (t) => {
+ t.add_cleanup(makeCleanup());
+ await test_driver.bless("request fullscreen");
+ await document.documentElement.requestFullscreen();
+ let orientations = ["portrait", "landscape"];
+ if (screen.orientation.type.includes("portrait")) {
+ orientations = orientations.reverse();
+ }
+ const messageWatcher = new EventWatcher(t, window, "message");
+ const changeWatcher = new EventWatcher(t, screen.orientation, "change");
+ const iframe = await attachIframe({
+ src: "resources/iframe-listen-orientation-change.html",
+ sandbox: "allow-scripts allow-same-origin",
+ });
+ for (const orientation of orientations) {
+ const messagePromise = messageWatcher.wait_for("message");
+ const eventPromise = changeWatcher.wait_for("change");
+ await screen.orientation.lock(orientation);
+ const winner = await Promise.race([eventPromise, messagePromise]);
+ assert_true(winner instanceof Event, "change event must be fired first");
+ const message = await messagePromise;
+ assert_true(
+ message.data.startsWith(orientation),
+ "subframe receives orientation change event"
+ );
+ }
+ iframe.remove();
+ }, "Test subframes receive orientation change events");
+
+ promise_test(async (t) => {
+ t.add_cleanup(makeCleanup());
+ const iframe = await attachIframe();
+ const opposite = getOppositeOrientation();
+
+ const topEventPromise = new EventWatcher(
+ t,
+ screen.orientation,
+ "change"
+ ).wait_for("change");
+ const iframeEventPromise = new EventWatcher(
+ t,
+ iframe.contentWindow.screen.orientation,
+ "change"
+ ).wait_for("change");
+
+ // Lock from the iframe
+ await test_driver.bless("request fullscreen");
+ await document.documentElement.requestFullscreen();
+ const lockPromise = iframe.contentWindow.screen.orientation.lock(opposite);
+
+ const winningEvent = await Promise.race([
+ topEventPromise,
+ iframeEventPromise,
+ ]);
+ assert_true(
+ winningEvent instanceof window.Event,
+ "top-level change event must be fired first"
+ );
+
+ const iframeEvent = await iframeEventPromise;
+ assert_true(
+ iframeEvent instanceof iframe.contentWindow.Event,
+ "iframe event eventually fires"
+ );
+
+ await lockPromise;
+ iframe.remove();
+ }, "Check directly that events are fired in right order (from top to bottom)");
+</script>
diff --git a/testing/web-platform/tests/screen-orientation/onchange-event.html b/testing/web-platform/tests/screen-orientation/onchange-event.html
new file mode 100644
index 0000000000..e0d3afd7b4
--- /dev/null
+++ b/testing/web-platform/tests/screen-orientation/onchange-event.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<meta viewport="width=device-width, initial-scale=1" />
+<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 type="module">
+import { makeCleanup } from "./resources/orientation-utils.js";
+promise_test(async t => {
+ t.add_cleanup(makeCleanup());
+ await test_driver.bless("request full screen");
+ await document.documentElement.requestFullscreen();
+ const type = screen.orientation.type.startsWith("portrait") ? "portrait" : "landscape";
+ screen.orientation.onchange = t.unreached_func("change event should not be fired");
+ await screen.orientation.lock(type);
+ assert_true(screen.orientation.type.startsWith(type));
+}, "Test that orientationchange event is not fired when the orientation does not change.");
+
+promise_test(async t => {
+ t.add_cleanup(makeCleanup());
+ await test_driver.bless("request full screen");
+ await document.documentElement.requestFullscreen();
+ let orientations = [
+ 'portrait',
+ 'landscape',
+ ];
+ if (screen.orientation.type.startsWith('portrait')) {
+ orientations = orientations.reverse();
+ }
+ const orientationWatcher = new EventWatcher(t, screen.orientation, 'change');
+
+ for (const orientation of orientations) {
+ // change event is fired before resolving promise by lock.
+ let lockPromise = screen.orientation.lock(orientation);
+ const result = await Promise.race([
+ lockPromise,
+ orientationWatcher.wait_for('change'),
+ ]);
+ assert_true(result instanceof Event, "The event must be fired first.");
+ assert_true(screen.orientation.type.startsWith(orientation), "The orientation must match");
+ await lockPromise;
+ }
+}, "Test that orientationchange event is fired when the orientation changes.");
+</script>
diff --git a/testing/web-platform/tests/screen-orientation/orientation-reading.html b/testing/web-platform/tests/screen-orientation/orientation-reading.html
new file mode 100644
index 0000000000..90bbb8071d
--- /dev/null
+++ b/testing/web-platform/tests/screen-orientation/orientation-reading.html
@@ -0,0 +1,119 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<meta viewport="width=device-width, initial-scale=1" />
+<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 type="module">
+"use strict";
+import {
+ makeCleanup,
+ getOppositeOrientation,
+} from "./resources/orientation-utils.js";
+
+test(() => {
+ assert_true("type" in screen.orientation, ".type must be present");
+ assert_true("angle" in screen.orientation, ".angle must be present");
+}, "screen.orientation attributes are present");
+
+async function testExpectedOrientationAngles(expectedAngles) {
+ for (const [orientation, expectedAngle] of Object.entries(expectedAngles)) {
+ try {
+ if (screen.orientation.type !== orientation) {
+ await screen.orientation.lock(orientation);
+ }
+ assert_equals(
+ screen.orientation.angle,
+ expectedAngle,
+ `Orientation angle for '${orientation}' must be ${expectedAngle} degrees`
+ );
+ } catch (err) {
+ // implementation might not support locking to this orientation
+ }
+ }
+}
+
+promise_test(async (t) => {
+ t.add_cleanup(makeCleanup());
+ await test_driver.bless("request full screen");
+ await document.documentElement.requestFullscreen();
+
+ const expectedAnglesPortrait = {
+ "portrait-primary": 0,
+ "landscape-primary": 90,
+ "portrait-secondary": 180,
+ "landscape-secondary": 270,
+ };
+
+ await testExpectedOrientationAngles(expectedAnglesPortrait);
+}, "Test the orientations and associated angles when the natural orientation is 'portrait'");
+
+promise_test(async (t) => {
+ t.add_cleanup(makeCleanup());
+ await test_driver.bless("request full screen");
+ await document.documentElement.requestFullscreen();
+
+ const expectedAnglesLandscape = {
+ "landscape-primary": 0,
+ "portrait-primary": 90,
+ "landscape-secondary": 180,
+ "portrait-secondary": 270,
+ };
+
+ await testExpectedOrientationAngles(expectedAnglesLandscape);
+}, "Test the orientations and associated angles when the natural orientation is 'landscape'");
+
+test(() => {
+ const { angle, type } = screen.orientation;
+
+ assert_throws_js(
+ TypeError,
+ () => {
+ screen.orientation.type = "foo";
+ },
+ "throws when setting ScreenOrientation.type to a string in strict mode"
+ );
+ assert_throws_js(
+ TypeError,
+ () => {
+ screen.orientation.angle = 42;
+ },
+ "throws when setting ScreenOrientation.angle to a number in strict mode"
+ );
+
+ assert_equals(screen.orientation.type, type);
+ assert_equals(screen.orientation.angle, angle);
+}, "Test that ScreenOrientation properties are not writable");
+
+test(() => {
+ assert_equals(screen.orientation, screen.orientation);
+}, "Test that ScreenOrientation is always the same object");
+
+promise_test(async (t) => {
+ t.add_cleanup(makeCleanup());
+ await test_driver.bless("request full screen");
+ await document.documentElement.requestFullscreen();
+ const initialType = screen.orientation.type;
+ const initialAngle = screen.orientation.angle;
+ const orientationWatcher = new EventWatcher(t, screen.orientation, "change");
+ const newOrientationType = getOppositeOrientation();
+
+ // change event is fired before resolving promise by lock.
+ const event = await Promise.race([
+ orientationWatcher.wait_for("change"),
+ screen.orientation.lock(newOrientationType),
+ ]);
+ assert_true(event instanceof Event, "expected event");
+ assert_not_equals(
+ screen.orientation.type,
+ initialType,
+ ".type must change"
+ );
+ assert_not_equals(
+ screen.orientation.angle,
+ initialAngle,
+ ".angle must change"
+ );
+}, "Test that ScreenOrientation's attribute values change after 'change' event fires");
+</script>
diff --git a/testing/web-platform/tests/screen-orientation/page-visibility-manual.html b/testing/web-platform/tests/screen-orientation/page-visibility-manual.html
new file mode 100644
index 0000000000..cc63279217
--- /dev/null
+++ b/testing/web-platform/tests/screen-orientation/page-visibility-manual.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<html>
+<body>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<meta name='flags' content='interact'>
+<p>Switch the page to background, then switch back in a minute.</p>
+<iframe src='about:blank'></iframe>
+<script>
+
+var eventVisibleTest = async_test("Test that a change event is fired when the page is visible.");
+var noEventHiddenTest = async_test("Test that change event is not fired when the page is not visible.");
+var orientationUnchangeHiddenTest = async_test("Test that screen.orientation keeps returning the same orientation when the page is not visible.");
+var orientationUpdateVisibleTest = async_test("Test that screen.orientation is updated once the page is visible again.");
+var frameEventsTest = async_test("Test that the iframe got as many events as the main frame.");
+
+var orientationChangeContinuation = null;
+var orientationChangeEventListenerCalls = 0;
+var orientationChangeEventListenerCallsForFrame = 0;
+
+screen.orientation.addEventListener('change', function() {
+ orientationChangeEventListenerCalls++;
+ if (orientationChangeEventContinuation) {
+ setTimeout(orientationChangeEventContinuation);
+ orientationChangeEventContinuation = null;
+ }
+});
+
+window.frames[0].screen.orientation.addEventListener('change', function() {
+ orientationChangeEventListenerCallsForFrame++;
+});
+
+document.addEventListener("visibilitychange", function () {
+ if(document.hidden)
+ runNoEventHiddenTest();
+ else
+ runOrientationUpdateVisibleTest();
+});
+
+function runEventVisibleTest() {
+ eventVisibleTest.step(function() {
+ assert_false(document.hidden);
+ });
+
+ screen.orientation.lock("landscape-primary").then(function() {}, function() {});
+
+ orientationChangeEventContinuation = function() {
+ eventVisibleTest.step(function() {
+ assert_equals(orientationChangeEventListenerCalls, 1);
+ assert_equals(screen.orientation.type, "landscape-primary");
+ });
+ eventVisibleTest.done();
+
+ };
+}
+
+function runNoEventHiddenTest() {
+
+ noEventHiddenTest.step(function() {
+ assert_true(document.hidden);
+ });
+
+ screen.orientation.lock("portrait-primary").then(function() {}, function() {});
+
+ noEventHiddenTest.step(function() {
+ assert_equals(orientationChangeEventListenerCalls, 1);
+ });
+ noEventHiddenTest.done();
+
+ runOrientationUnchangeHiddenTest();
+}
+
+function runOrientationUnchangeHiddenTest() {
+ orientationUnchangeHiddenTest.step(function() {
+ assert_equals(screen.orientation.type, "landscape-primary");
+ });
+ orientationUnchangeHiddenTest.done();
+
+}
+
+function runOrientationUpdateVisibleTest() {
+
+ orientationChangeEventContinuation = function() {
+ orientationUpdateVisibleTest.step(function() {
+ assert_false(document.hidden);
+ // A change event should have been fired.
+ assert_equals(orientationChangeEventListenerCalls, 2);
+ // Should keep returning the start returning the orientation value.
+ assert_equals(screen.orientation.type, "portrait-primary");
+ });
+
+ orientationUpdateVisibleTest.done();
+
+ runFrameEventsTest();
+ };
+}
+
+function runFrameEventsTest() {
+ frameEventsTest.step(function() {
+ assert_equals(orientationChangeEventListenerCallsForFrame, orientationChangeEventListenerCalls);
+ });
+ frameEventsTest.done();
+}
+
+runEventVisibleTest();
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/screen-orientation/resources/empty.html b/testing/web-platform/tests/screen-orientation/resources/empty.html
new file mode 100644
index 0000000000..0e76edd65b
--- /dev/null
+++ b/testing/web-platform/tests/screen-orientation/resources/empty.html
@@ -0,0 +1 @@
+<!DOCTYPE html>
diff --git a/testing/web-platform/tests/screen-orientation/resources/iframe-listen-orientation-change.html b/testing/web-platform/tests/screen-orientation/resources/iframe-listen-orientation-change.html
new file mode 100644
index 0000000000..68a67f8818
--- /dev/null
+++ b/testing/web-platform/tests/screen-orientation/resources/iframe-listen-orientation-change.html
@@ -0,0 +1,9 @@
+<script>
+ try {
+ window.screen.orientation.addEventListener("change", () => {
+ parent.window.postMessage(screen.orientation.type, "*");
+ });
+ } catch (err) {
+ parent.window.postMessage(err.message, "*");
+ }
+</script>
diff --git a/testing/web-platform/tests/screen-orientation/resources/orientation-utils.js b/testing/web-platform/tests/screen-orientation/resources/orientation-utils.js
new file mode 100644
index 0000000000..95383750f1
--- /dev/null
+++ b/testing/web-platform/tests/screen-orientation/resources/orientation-utils.js
@@ -0,0 +1,52 @@
+/**
+ *
+ * @param {object} options
+ * @param {string} options.src - The iframe src
+ * @param {Window} options.context - The browsing context in which the iframe will be created
+ * @param {string} options.sandbox - The sandbox attribute for the iframe
+ * @returns
+ */
+export async function attachIframe(options = {}) {
+ const { src, context, sandbox, allowFullscreen } = {
+ ...{
+ src: "about:blank",
+ context: self,
+ allowFullscreen: true,
+ sandbox: null,
+ },
+ ...options,
+ };
+ const iframe = context.document.createElement("iframe");
+ if (sandbox !== null) iframe.sandbox = sandbox;
+ iframe.allowFullscreen = allowFullscreen;
+ await new Promise((resolve) => {
+ iframe.onload = resolve;
+ iframe.src = src;
+ context.document.body.appendChild(iframe);
+ });
+ return iframe;
+}
+
+export function getOppositeOrientation() {
+ return screen.orientation.type.startsWith("portrait")
+ ? "landscape"
+ : "portrait";
+}
+
+export function makeCleanup(
+ initialOrientation = screen.orientation?.type.split(/-/)[0]
+) {
+ return async () => {
+ if (initialOrientation) {
+ try {
+ await screen.orientation.lock(initialOrientation);
+ } catch {}
+ }
+ screen.orientation.unlock();
+ requestAnimationFrame(async () => {
+ try {
+ await document.exitFullscreen();
+ } catch {}
+ });
+ };
+}
diff --git a/testing/web-platform/tests/screen-orientation/resources/sandboxed-iframe-locking.html b/testing/web-platform/tests/screen-orientation/resources/sandboxed-iframe-locking.html
new file mode 100644
index 0000000000..436c67f5b5
--- /dev/null
+++ b/testing/web-platform/tests/screen-orientation/resources/sandboxed-iframe-locking.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script>
+test_driver.set_test_context(parent);
+
+// At first, run simple unlock test without lock.
+screen.orientation?.unlock();
+
+test_driver.bless("request full screen", async () => {
+ const data = {};
+ try {
+ await document.documentElement.requestFullscreen();
+ await screen.orientation.lock("portrait")
+ data.result = "locked";
+ data.orientation = screen.orientation.type;
+ } catch (error) {
+ data.result = "errored";
+ data.name = error.name;
+ }
+
+ screen.orientation.unlock();
+ try {
+ await document.exitFullscreen();
+ } catch (error) {
+ data.result = "errored";
+ data.name = error.name;
+ }
+
+ parent.window.postMessage(data, "*");
+});
+</script>
diff --git a/testing/web-platform/tests/screen-orientation/unlock.html b/testing/web-platform/tests/screen-orientation/unlock.html
new file mode 100644
index 0000000000..d5f32eb7be
--- /dev/null
+++ b/testing/web-platform/tests/screen-orientation/unlock.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<meta viewport="width=device-width, initial-scale=1" />
+<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 type="module">
+ import {
+ getOppositeOrientation,
+ makeCleanup,
+ attachIframe,
+ } from "./resources/orientation-utils.js";
+
+ test(() => {
+ screen.orientation.unlock();
+ }, "unlock() doesn't throw when there is no lock");
+
+ test(() => {
+ const value = screen.orientation.unlock();
+ assert_equals(value, undefined);
+ }, "unlock() returns a void value");
+
+ promise_test(async (t) => {
+ t.add_cleanup(makeCleanup());
+ await test_driver.bless("request full screen");
+ await document.documentElement.requestFullscreen();
+ screen.orientation.unlock();
+ }, "unlock() doesn't throw when there is no lock with fullscreen");
+
+ promise_test(async (t) => {
+ t.add_cleanup(makeCleanup());
+ await test_driver.bless("request full screen");
+ await document.documentElement.requestFullscreen();
+ const promise = screen.orientation.lock(getOppositeOrientation());
+ screen.orientation.unlock();
+ await promise_rejects_dom(t, "AbortError", promise);
+ }, "unlock() aborts a pending lock request");
+
+ promise_test(async (t) => {
+ t.add_cleanup(makeCleanup());
+ await test_driver.bless("request full screen");
+ await document.documentElement.requestFullscreen();
+ const iframe = await attachIframe();
+ const promise = iframe.contentWindow.screen.orientation.lock(
+ getOppositeOrientation()
+ );
+ screen.orientation.unlock();
+ await promise_rejects_dom(
+ t,
+ "AbortError",
+ iframe.contentWindow.DOMException,
+ promise
+ );
+ }, "unlock() aborts a pending lock request across documents");
+</script>