summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/picture-in-picture
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/picture-in-picture
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/picture-in-picture')
-rw-r--r--testing/web-platform/tests/picture-in-picture/META.yml4
-rw-r--r--testing/web-platform/tests/picture-in-picture/WEB_FEATURES.yml3
-rw-r--r--testing/web-platform/tests/picture-in-picture/css-selector.html34
-rw-r--r--testing/web-platform/tests/picture-in-picture/disable-picture-in-picture.html74
-rw-r--r--testing/web-platform/tests/picture-in-picture/enter-picture-in-picture.html30
-rw-r--r--testing/web-platform/tests/picture-in-picture/exit-picture-in-picture.html21
-rw-r--r--testing/web-platform/tests/picture-in-picture/idlharness.window.js28
-rw-r--r--testing/web-platform/tests/picture-in-picture/leave-picture-in-picture.html56
-rw-r--r--testing/web-platform/tests/picture-in-picture/mediastream.html24
-rw-r--r--testing/web-platform/tests/picture-in-picture/picture-in-picture-element.html24
-rw-r--r--testing/web-platform/tests/picture-in-picture/picture-in-picture-window.html96
-rw-r--r--testing/web-platform/tests/picture-in-picture/removed-from-document.html24
-rw-r--r--testing/web-platform/tests/picture-in-picture/request-picture-in-picture-twice.html29
-rw-r--r--testing/web-platform/tests/picture-in-picture/request-picture-in-picture.html37
-rw-r--r--testing/web-platform/tests/picture-in-picture/resources/picture-in-picture-helpers.js15
-rw-r--r--testing/web-platform/tests/picture-in-picture/shadow-dom.html88
16 files changed, 587 insertions, 0 deletions
diff --git a/testing/web-platform/tests/picture-in-picture/META.yml b/testing/web-platform/tests/picture-in-picture/META.yml
new file mode 100644
index 0000000000..8df9be5e5b
--- /dev/null
+++ b/testing/web-platform/tests/picture-in-picture/META.yml
@@ -0,0 +1,4 @@
+spec: https://w3c.github.io/picture-in-picture/
+suggested_reviewers:
+ - beaufortfrancois
+ - mounirlamouri
diff --git a/testing/web-platform/tests/picture-in-picture/WEB_FEATURES.yml b/testing/web-platform/tests/picture-in-picture/WEB_FEATURES.yml
new file mode 100644
index 0000000000..e895260b8b
--- /dev/null
+++ b/testing/web-platform/tests/picture-in-picture/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: picture-in-picture
+ files: "**"
diff --git a/testing/web-platform/tests/picture-in-picture/css-selector.html b/testing/web-platform/tests/picture-in-picture/css-selector.html
new file mode 100644
index 0000000000..bf64a1421f
--- /dev/null
+++ b/testing/web-platform/tests/picture-in-picture/css-selector.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>Test CSS selector :picture-in-picture</title>
+<script src="/common/media.js"></script>
+<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/picture-in-picture-helpers.js"></script>
+<style>
+ video {
+ color: rgb(0, 0, 255);
+ }
+ :picture-in-picture {
+ color: rgb(0, 255, 0);
+ }
+ /* illegal selector list */
+ video, :picture-in-picture(*) {
+ color: rgb(255, 0, 0);
+ }
+</style>
+<body></body>
+<script>
+promise_test(async t => {
+ const video = await loadVideo();
+ document.body.appendChild(video);
+ assert_equals(getComputedStyle(video).color, 'rgb(0, 0, 255)');
+
+ await requestPictureInPictureWithTrustedClick(video);
+ assert_equals(getComputedStyle(video).color, 'rgb(0, 255, 0)');
+
+ await document.exitPictureInPicture();
+ assert_equals(getComputedStyle(video).color, 'rgb(0, 0, 255)');
+}, 'Entering and leaving Picture-in-Picture toggles CSS selector');
+</script>
diff --git a/testing/web-platform/tests/picture-in-picture/disable-picture-in-picture.html b/testing/web-platform/tests/picture-in-picture/disable-picture-in-picture.html
new file mode 100644
index 0000000000..a6b757477a
--- /dev/null
+++ b/testing/web-platform/tests/picture-in-picture/disable-picture-in-picture.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<title>Test disable Picture-in-Picture</title>
+<script src="/common/media.js"></script>
+<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/picture-in-picture-helpers.js"></script>
+<body></body>
+<script>
+test(t => {
+ const video = document.createElement('video');
+ assert_false(video.disablePictureInPicture); // default value
+
+ video.setAttribute('disablepictureinpicture', 'foo');
+ assert_true(video.disablePictureInPicture);
+
+ video.removeAttribute('disablepictureinpicture');
+ assert_false(video.disablePictureInPicture);
+
+ video.disablePictureInPicture = true;
+ assert_equals(video.getAttribute('disablepictureinpicture'), '');
+
+ video.disablePictureInPicture = false;
+ assert_equals(video.getAttribute('disablepictureinpicture'), null);
+}, 'Test disablePictureInPicture IDL attribute');
+
+promise_test(async t => {
+ const video = await loadVideo();
+ video.disablePictureInPicture = true;
+ return promise_rejects_dom(t, 'InvalidStateError',
+ requestPictureInPictureWithTrustedClick(video));
+}, 'Request Picture-in-Picture rejects if disablePictureInPicture is true');
+
+promise_test(async t => {
+ const video = await loadVideo();
+ await test_driver.bless('request Picture-in-Picture');
+ const promise = video.requestPictureInPicture();
+ video.disablePictureInPicture = true;
+ await promise_rejects_dom(t, 'InvalidStateError', promise);
+ assert_equals(document.pictureInPictureElement, null);
+}, 'Request Picture-in-Picture rejects if disablePictureInPicture becomes ' +
+ 'true before promise resolves.');
+
+promise_test(async t => {
+ const video = await loadVideo();
+ return requestPictureInPictureWithTrustedClick(video)
+ .then(() => {
+ video.disablePictureInPicture = true;
+ video.addEventListener('leavepictureinpicture', t.step_func(() => {
+ assert_equals(document.pictureInPictureElement, null);
+ }));
+ });
+}, 'pictureInPictureElement is unset if disablePictureInPicture becomes true');
+
+promise_test(async t => {
+ const video = await loadVideo();
+ return requestPictureInPictureWithTrustedClick(video)
+ .then(() => {
+ video.disablePictureInPicture = false;
+ assert_equals(document.pictureInPictureElement, video);
+ });
+}, 'pictureInPictureElement is unchanged if disablePictureInPicture becomes false');
+
+promise_test(async t => {
+ const video = await loadVideo();
+ return requestPictureInPictureWithTrustedClick(video)
+ .then(() => {
+ document.createElement('video').disablePictureInPicture = true;
+ assert_equals(document.pictureInPictureElement, video);
+ });
+}, 'pictureInPictureElement is unchanged if disablePictureInPicture becomes ' +
+ 'true for another video');
+</script>
diff --git a/testing/web-platform/tests/picture-in-picture/enter-picture-in-picture.html b/testing/web-platform/tests/picture-in-picture/enter-picture-in-picture.html
new file mode 100644
index 0000000000..a9d7b5c048
--- /dev/null
+++ b/testing/web-platform/tests/picture-in-picture/enter-picture-in-picture.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<title>Test enterpictureinpicture event</title>
+<script src="/common/media.js"></script>
+<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/picture-in-picture-helpers.js"></script>
+<body></body>
+<script>
+promise_test(async t => {
+ let pictureInPictureWindow;
+ const video = await loadVideo();
+
+ video.addEventListener('enterpictureinpicture', t.step_func_done(event => {
+ pictureInPictureWindow = event.pictureInPictureWindow;
+
+ assert_equals(event.target, video);
+ assert_equals(event.bubbles, true);
+ assert_equals(event.cancelable, false);
+ assert_equals(event.composed, false);
+ assert_equals(document.pictureInPictureElement, video);
+ }));
+
+ return requestPictureInPictureWithTrustedClick(video)
+ .then(pipWindow => {
+ assert_equals(pipWindow, pictureInPictureWindow);
+ })
+});
+</script>
diff --git a/testing/web-platform/tests/picture-in-picture/exit-picture-in-picture.html b/testing/web-platform/tests/picture-in-picture/exit-picture-in-picture.html
new file mode 100644
index 0000000000..520293b5aa
--- /dev/null
+++ b/testing/web-platform/tests/picture-in-picture/exit-picture-in-picture.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<title>Test exit Picture-in-Picture</title>
+<script src="/common/media.js"></script>
+<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/picture-in-picture-helpers.js"></script>
+<body></body>
+<script>
+promise_test(async t => {
+ const video = await loadVideo();
+ return requestPictureInPictureWithTrustedClick(video)
+ .then(() => document.exitPictureInPicture());
+}, 'Exit Picture-in-Picture resolves when there is a Picture-in-Picture video');
+
+promise_test(t => {
+ return promise_rejects_dom(t, 'InvalidStateError',
+ document.exitPictureInPicture());
+}, 'Exit Picture-in-Picture rejects when there is no Picture-in-Picture video');
+</script>
diff --git a/testing/web-platform/tests/picture-in-picture/idlharness.window.js b/testing/web-platform/tests/picture-in-picture/idlharness.window.js
new file mode 100644
index 0000000000..8977588478
--- /dev/null
+++ b/testing/web-platform/tests/picture-in-picture/idlharness.window.js
@@ -0,0 +1,28 @@
+// META: script=/common/media.js
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+// META: script=resources/picture-in-picture-helpers.js
+// META: timeout=long
+
+'use strict';
+
+// https://wicg.github.io/picture-in-picture/
+
+idl_test(
+ ['picture-in-picture'],
+ ['html', 'dom'],
+ async idl_array => {
+ idl_array.add_objects({
+ Document: ['document'],
+ DocumentOrShadowRoot: ['document'],
+ HTMLVideoElement: ['video'],
+ PictureInPictureWindow: ['pipw'],
+ PictureInPictureEvent: ['new PictureInPictureEvent("type", { pictureInPictureWindow: pipw })'],
+ });
+
+ self.video = await loadVideo();
+ self.pipw = await requestPictureInPictureWithTrustedClick(video);
+ }
+);
diff --git a/testing/web-platform/tests/picture-in-picture/leave-picture-in-picture.html b/testing/web-platform/tests/picture-in-picture/leave-picture-in-picture.html
new file mode 100644
index 0000000000..a0fbcb23e5
--- /dev/null
+++ b/testing/web-platform/tests/picture-in-picture/leave-picture-in-picture.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<title>Test leavepictureinpicture event</title>
+<meta name="timeout" content="long">
+<script src="/common/media.js"></script>
+<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/picture-in-picture-helpers.js"></script>
+<body></body>
+<script>
+promise_test(async t => {
+ let pictureInPictureWindow;
+ const video = await loadVideo();
+
+ video.addEventListener('enterpictureinpicture', t.step_func_done(event => {
+ pictureInPictureWindow = event.pictureInPictureWindow;
+ }));
+
+ video.addEventListener('leavepictureinpicture', t.step_func_done(event => {
+ assert_equals(pictureInPictureWindow, event.pictureInPictureWindow);
+
+ assert_equals(event.target, video);
+ assert_equals(event.bubbles, true);
+ assert_equals(event.cancelable, false);
+ assert_equals(event.composed, false);
+ assert_equals(document.pictureInPictureElement, null);
+ }));
+
+ return requestPictureInPictureWithTrustedClick(video)
+ .then(() => document.exitPictureInPicture());
+}, 'leavepictureinpicture event is fired if document.exitPictureInPicture');
+
+promise_test(async t => {
+ let pictureInPictureWindow;
+ const video = await loadVideo();
+
+ video.addEventListener('enterpictureinpicture', t.step_func_done(event => {
+ pictureInPictureWindow = event.pictureInPictureWindow;
+ }));
+
+ video.addEventListener('leavepictureinpicture', t.step_func_done(event => {
+ assert_equals(pictureInPictureWindow, event.pictureInPictureWindow);
+ assert_equals(event.target, video);
+ assert_equals(event.bubbles, true);
+ assert_equals(event.cancelable, false);
+ assert_equals(event.composed, false);
+ assert_equals(document.pictureInPictureElement, null);
+ }));
+
+ return requestPictureInPictureWithTrustedClick(video)
+ .then(() => {
+ video.disablePictureInPicture = true;
+ });
+}, 'leavepictureinpicture event is fired if video.disablePictureInPicture is set to true');
+</script>
diff --git a/testing/web-platform/tests/picture-in-picture/mediastream.html b/testing/web-platform/tests/picture-in-picture/mediastream.html
new file mode 100644
index 0000000000..116a0f7dd4
--- /dev/null
+++ b/testing/web-platform/tests/picture-in-picture/mediastream.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<title>Test mediastream video in Picture-in-Picture</title>
+<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/picture-in-picture-helpers.js"></script>
+<body></body>
+<script>
+promise_test(async t => {
+ const canvas = document.createElement('canvas');
+ const video = document.createElement('video');
+ canvas.getContext('2d').fillRect(0, 0, canvas.width, canvas.height);
+ video.muted = true;
+ video.srcObject = canvas.captureStream(60 /* fps */);
+ await video.play();
+
+ return requestPictureInPictureWithTrustedClick(video)
+ .then(pipWindow => {
+ assert_not_equals(pipWindow.width, 0);
+ assert_not_equals(pipWindow.height, 0);
+ });
+}, 'request Picture-in-Picture resolves on user click with Picture-in-Picture window');
+</script>
diff --git a/testing/web-platform/tests/picture-in-picture/picture-in-picture-element.html b/testing/web-platform/tests/picture-in-picture/picture-in-picture-element.html
new file mode 100644
index 0000000000..2763eca476
--- /dev/null
+++ b/testing/web-platform/tests/picture-in-picture/picture-in-picture-element.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<title>Test Picture-in-Picture element</title>
+<script src="/common/media.js"></script>
+<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/picture-in-picture-helpers.js"></script>
+<body></body>
+<script>
+promise_test(async t => {
+ assert_equals(document.pictureInPictureElement, null);
+ const video = await loadVideo();
+
+ return requestPictureInPictureWithTrustedClick(video)
+ .then(() => {
+ assert_equals(document.pictureInPictureElement, video);
+ return document.exitPictureInPicture();
+ })
+ .then(() => {
+ assert_equals(document.pictureInPictureElement, null);
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/picture-in-picture/picture-in-picture-window.html b/testing/web-platform/tests/picture-in-picture/picture-in-picture-window.html
new file mode 100644
index 0000000000..ed1ad8e2cc
--- /dev/null
+++ b/testing/web-platform/tests/picture-in-picture/picture-in-picture-window.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<title>Test Picture-in-Picture window</title>
+<meta name="timeout" content="long">
+<script src="/common/media.js"></script>
+<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/picture-in-picture-helpers.js"></script>
+<body></body>
+<script>
+promise_test(async t => {
+ const video = await loadVideo();
+ return requestPictureInPictureWithTrustedClick(video)
+ .then(pipWindow => {
+ assert_not_equals(pipWindow.width, 0);
+ assert_not_equals(pipWindow.height, 0);
+ const videoAspectRatio = video.videoWidth / video.videoHeight;
+ const pipWindowAspectRatio = pipWindow.width / pipWindow.height;
+ assert_approx_equals(videoAspectRatio, pipWindowAspectRatio, 0.01);
+ });
+}, 'Picture-in-Picture window dimensions are set after entering Picture-in-Picture');
+
+promise_test(async t => {
+ const video1 = await loadVideo();
+ const video2 = await loadVideo();
+ return requestPictureInPictureWithTrustedClick(video1)
+ .then(pipWindow1 => {
+ return requestPictureInPictureWithTrustedClick(video2)
+ .then(pipWindow2 => {
+ assert_equals(pipWindow1.width, 0);
+ assert_equals(pipWindow1.height, 0);
+ assert_not_equals(pipWindow2.width, 0);
+ assert_not_equals(pipWindow2.height, 0);
+ });
+ });
+}, 'Picture-in-Picture window dimensions are set to 0 after entering ' +
+ 'Picture-in-Picture for another video');
+
+promise_test(async t => {
+ const video = await loadVideo();
+
+ video.addEventListener('leavepictureinpicture', t.step_func_done(event => {
+ assert_unreached('leavepictureinpicture event should not fire.')
+ }));
+
+ let enterCounts = 0;
+ video.addEventListener('enterpictureinpicture', event => {
+ enterCounts++;
+ });
+
+ return requestPictureInPictureWithTrustedClick(video)
+ .then(pipWindow1 => {
+ pipWindow1.onresize = function foo() {};
+ return requestPictureInPictureWithTrustedClick(video)
+ .then(pipWindow2 => {
+ assert_equals(pipWindow1, pipWindow2);
+ assert_equals(pipWindow1.width, pipWindow2.width);
+ assert_equals(pipWindow1.height, pipWindow2.height);
+ assert_equals(pipWindow1.onresize, pipWindow2.onresize);
+ assert_equals(enterCounts, 1);
+ });
+ });
+}, 'Picture-in-Picture window is unchanged after entering ' +
+ 'Picture-in-Picture for video already in Picture-in-Picture');
+
+promise_test(async t => {
+ const video = await loadVideo();
+
+ return requestPictureInPictureWithTrustedClick(video)
+ .then(pipWindow => {
+ return document.exitPictureInPicture()
+ .then(() => {
+ assert_equals(pipWindow.width, 0);
+ assert_equals(pipWindow.height, 0);
+ });
+ });
+}, 'Picture-in-Picture window dimensions are set to 0 after exiting Picture-in-Picture');
+
+promise_test(async t => {
+ const video = await loadVideo();
+ let thePipWindow;
+
+ video.addEventListener('leavepictureinpicture', t.step_func_done(event => {
+ assert_equals(thePipWindow.width, 0);
+ assert_equals(thePipWindow.height, 0);
+ }));
+
+ return requestPictureInPictureWithTrustedClick(video)
+ .then(pipWindow => {
+ thePipWindow = pipWindow;
+ video.disablePictureInPicture = true;
+ });
+}, 'Picture-in-Picture window dimensions are set to 0 if ' +
+ 'disablePictureInPicture becomes true');
+</script>
diff --git a/testing/web-platform/tests/picture-in-picture/removed-from-document.html b/testing/web-platform/tests/picture-in-picture/removed-from-document.html
new file mode 100644
index 0000000000..2c363a0eec
--- /dev/null
+++ b/testing/web-platform/tests/picture-in-picture/removed-from-document.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<title>Test Picture-in-Picture when removed from document</title>
+<script src="/common/media.js"></script>
+<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/picture-in-picture-helpers.js"></script>
+<body></body>
+<script>
+promise_test(async t => {
+ const video = await loadVideo();
+ document.body.appendChild(video);
+ video.muted = true;
+ await video.play();
+ await requestPictureInPictureWithTrustedClick(video);
+
+ assert_false(video.paused);
+ document.body.offsetLeft;
+ document.body.removeChild(video);
+ await new Promise(resolve => step_timeout(resolve, 1000));
+ assert_false(video.paused);
+}, 'Picture-in-Picture video does not pause when removed from document');
+</script>
diff --git a/testing/web-platform/tests/picture-in-picture/request-picture-in-picture-twice.html b/testing/web-platform/tests/picture-in-picture/request-picture-in-picture-twice.html
new file mode 100644
index 0000000000..c1369ee8c3
--- /dev/null
+++ b/testing/web-platform/tests/picture-in-picture/request-picture-in-picture-twice.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<title>Test request Picture-in-Picture on two videos</title>
+<meta name="timeout" content="long">
+<script src="/common/media.js"></script>
+<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/picture-in-picture-helpers.js"></script>
+<body></body>
+<script>
+promise_test(async t => {
+ const video1 = await loadVideo();
+ const video2 = await loadVideo();
+ await test_driver.bless('request Picture-in-Picture');
+ const promise = video1.requestPictureInPicture();
+ await promise_rejects_dom(t, 'NotAllowedError', video2.requestPictureInPicture());
+ return promise;
+}, 'request Picture-in-Picture consumes user gesture');
+
+promise_test(async t => {
+ const video1 = await loadVideo();
+ const video2 = await loadVideo();
+ await test_driver.bless('request Picture-in-Picture');
+ await video1.requestPictureInPicture();
+ assert_equals(document.pictureInPictureElement, video1);
+ return video2.requestPictureInPicture();
+}, 'request Picture-in-Picture does not require user gesture if document.pictureInPictureElement is set');
+</script>
diff --git a/testing/web-platform/tests/picture-in-picture/request-picture-in-picture.html b/testing/web-platform/tests/picture-in-picture/request-picture-in-picture.html
new file mode 100644
index 0000000000..d8383ecbe9
--- /dev/null
+++ b/testing/web-platform/tests/picture-in-picture/request-picture-in-picture.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>Test request Picture-in-Picture</title>
+<script src="/common/media.js"></script>
+<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/picture-in-picture-helpers.js"></script>
+<body></body>
+<script>
+promise_test(async t => {
+ const video = await loadVideo();
+ return promise_rejects_dom(t, 'NotAllowedError', video.requestPictureInPicture());
+}, 'request Picture-in-Picture requires a user gesture');
+
+promise_test(t => {
+ const video = document.createElement('video');
+ return promise_rejects_dom(t, 'InvalidStateError',
+ requestPictureInPictureWithTrustedClick(video));
+}, 'request Picture-in-Picture requires loaded metadata for the video element');
+
+promise_test(async t => {
+ const video = document.createElement('video');
+ await new Promise(resolve => {
+ video.src = getAudioURI('/media/sound_5');
+ video.onloadeddata = resolve;
+ }).then(() => {
+ return promise_rejects_dom(t, 'InvalidStateError',
+ requestPictureInPictureWithTrustedClick(video));
+ })
+}, 'request Picture-in-Picture requires video track for the video element');
+
+promise_test(async t => {
+ const video = await loadVideo();
+ return requestPictureInPictureWithTrustedClick(video);
+}, 'request Picture-in-Picture resolves on user click');
+</script>
diff --git a/testing/web-platform/tests/picture-in-picture/resources/picture-in-picture-helpers.js b/testing/web-platform/tests/picture-in-picture/resources/picture-in-picture-helpers.js
new file mode 100644
index 0000000000..7561944a18
--- /dev/null
+++ b/testing/web-platform/tests/picture-in-picture/resources/picture-in-picture-helpers.js
@@ -0,0 +1,15 @@
+function loadVideo(activeDocument, sourceUrl) {
+ return new Promise((resolve, reject) => {
+ const document = activeDocument || window.document;
+ const video = document.createElement('video');
+ video.src = sourceUrl || getVideoURI('/media/movie_5');
+ video.onloadedmetadata = () => { resolve(video); };
+ video.onerror = error => { reject(error); };
+ });
+}
+
+// Calls requestPictureInPicture() in a context that's 'allowed to request PiP'.
+async function requestPictureInPictureWithTrustedClick(videoElement) {
+ await test_driver.bless('request Picture-in-Picture');
+ return videoElement.requestPictureInPicture();
+}
diff --git a/testing/web-platform/tests/picture-in-picture/shadow-dom.html b/testing/web-platform/tests/picture-in-picture/shadow-dom.html
new file mode 100644
index 0000000000..adcd659762
--- /dev/null
+++ b/testing/web-platform/tests/picture-in-picture/shadow-dom.html
@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<title>Test for Picture-In-Picture and Shadow DOM</title>
+<script src="/common/media.js"></script>
+<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/picture-in-picture-helpers.js"></script>
+<script src="../shadow-dom/resources/shadow-dom.js"></script>
+<style>
+ #host2 { color: rgb(0, 0, 254); }
+ #host2:picture-in-picture { color: rgb(0, 0, 255); }
+</style>
+<body>
+<div id='host'>
+ <template data-mode='open' id='root'>
+ <slot></slot>
+ </template>
+ <div id='host2'>
+ <template data-mode='open' id='root2'>
+ <style>
+ #host3 { color: rgb(0, 0, 127); }
+ #host3:picture-in-picture { color: rgb(0, 0, 128); }
+ </style>
+ <div id='host3'>
+ <template data-mode='open' id='root3'>
+ <style>
+ video { color: rgb(0, 254, 0); }
+ video:picture-in-picture { color: rgb(0, 255, 0); }
+ </style>
+ <video id='video'></video>
+ <div id='host4'>
+ <template data-mode='open' id='root4'>
+ <div></div>
+ </template>
+ </div>
+ </template>
+ </div>
+ <div id='host5'>
+ <template data-mode='open' id='root5'>
+ <div></div>
+ </template>
+ </div>
+ </template>
+ </div>
+</div>
+</body>
+<script>
+promise_test(async t => {
+ const ids = createTestTree(host);
+ document.body.appendChild(ids.host);
+
+ assert_equals(document.pictureInPictureElement, null);
+ assert_equals(ids.root.pictureInPictureElement, null);
+ assert_equals(ids.root2.pictureInPictureElement, null);
+ assert_equals(ids.root3.pictureInPictureElement, null);
+ assert_equals(ids.root4.pictureInPictureElement, null);
+ assert_equals(ids.root5.pictureInPictureElement, null);
+
+ assert_equals(getComputedStyle(ids.video).color, 'rgb(0, 254, 0)');
+ assert_equals(getComputedStyle(ids.host3).color, 'rgb(0, 0, 127)');
+ assert_equals(getComputedStyle(ids.host2).color, 'rgb(0, 0, 254)');
+
+ await new Promise(resolve => {
+ ids.video.src = getVideoURI('/media/movie_5');
+ ids.video.onloadeddata = resolve;
+ })
+ .then(() => requestPictureInPictureWithTrustedClick(ids.video))
+ .then(() => {
+ assert_equals(document.pictureInPictureElement, ids.host2);
+ assert_equals(ids.root.pictureInPictureElement, null);
+ assert_equals(ids.root2.pictureInPictureElement, ids.host3);
+ assert_equals(ids.root3.pictureInPictureElement, ids.video);
+ assert_equals(ids.root4.pictureInPictureElement, null);
+ assert_equals(ids.root5.pictureInPictureElement, null);
+
+ assert_equals(getComputedStyle(ids.video).color, 'rgb(0, 255, 0)');
+ assert_equals(getComputedStyle(ids.host3).color, 'rgb(0, 0, 127)');
+ assert_equals(getComputedStyle(ids.host2).color, 'rgb(0, 0, 254)');
+ })
+ .then(() => document.exitPictureInPicture())
+ .then(() => {
+ assert_equals(getComputedStyle(ids.video).color, 'rgb(0, 254, 0)');
+ assert_equals(getComputedStyle(ids.host3).color, 'rgb(0, 0, 127)');
+ assert_equals(getComputedStyle(ids.host2).color, 'rgb(0, 0, 254)');
+ });
+});
+</script>