Commit 43b2d267 authored by François Beaufort's avatar François Beaufort Committed by Commit Bot

[Picture-in-Picture] Fire enter/leave events for native video controls.

This makes sure enterpictureinpicture and leavepictureinpicture events
are fired when user enters and leaves Picture-in-Picture from native
video controls.

Bug: 806249
Change-Id: I8d8717ce6fa97c3882913e73d8599cede6ec92dc
Reviewed-on: https://chromium-review.googlesource.com/1029951
Commit-Queue: François Beaufort <beaufort.francois@gmail.com>
Reviewed-by: default avatarKentaro Hara <haraken@chromium.org>
Reviewed-by: default avatarMounir Lamouri <mlamouri@chromium.org>
Cr-Commit-Position: refs/heads/master@{#557978}
parent 217626a0
......@@ -4542,21 +4542,21 @@ crbug.com/806249 external/wpt/feature-policy/picture-in-picture-allowed-by-featu
crbug.com/806249 external/wpt/feature-policy/picture-in-picture-allowed-by-feature-policy-attribute.https.sub.html [ Skip ]
crbug.com/806249 external/wpt/feature-policy/picture-in-picture-allowed-by-feature-policy.https.sub.html [ Skip ]
crbug.com/806249 external/wpt/feature-policy/picture-in-picture-default-feature-policy.https.sub.html [ Skip ]
crbug.com/806249 media/picture-in-picture/controls/picture-in-picture-button.html [ Skip ]
crbug.com/806249 media/picture-in-picture/picture-in-picture-enabled.html [ Skip ]
crbug.com/811977 media/picture-in-picture/picture-in-picture-interstitial.html [ Skip ]
crbug.com/806249 virtual/video-surface-layer/media/picture-in-picture/controls/picture-in-picture-button.html [ Skip ]
crbug.com/806249 virtual/video-surface-layer/media/picture-in-picture/picture-in-picture-enabled.html [ Skip ]
crbug.com/811977 virtual/video-surface-layer/media/picture-in-picture/picture-in-picture-interstitial.html [ Skip ]
crbug.com/806249 media/picture-in-picture/controls/picture-in-picture-button.html [ Failure ]
crbug.com/806249 media/picture-in-picture/controls/picture-in-picture-video-with-audio-only-button.html [ Failure ]
crbug.com/806249 media/picture-in-picture/picture-in-picture-enabled.html [ Failure ]
crbug.com/811977 media/picture-in-picture/picture-in-picture-interstitial.html [ Failure ]
crbug.com/806249 virtual/picture-in-picture/external/wpt/feature-policy/autoplay-allowed-by-feature-policy-attribute-redirect-on-load.https.sub.html [ Skip ]
crbug.com/806249 virtual/picture-in-picture/external/wpt/feature-policy/autoplay-default-feature-policy.https.sub.html [ Skip ]
crbug.com/806249 virtual/picture-in-picture/external/wpt/feature-policy/autoplay-disabled-by-feature-policy.https.sub.html [ Skip ]
crbug.com/811977 virtual/picture-in-picture/media/picture-in-picture/picture-in-picture-interstitial.html [ Skip ]
crbug.com/806249 virtual/picture-in-picture/media/picture-in-picture/controls/picture-in-picture-button.html [ Skip ]
crbug.com/806249 virtual/unified-autoplay/external/wpt/feature-policy/picture-in-picture-allowed-by-feature-policy-attribute-redirect-on-load.https.sub.html [ Skip ]
crbug.com/806249 virtual/unified-autoplay/external/wpt/feature-policy/picture-in-picture-allowed-by-feature-policy-attribute.https.sub.html [ Skip ]
crbug.com/806249 virtual/unified-autoplay/external/wpt/feature-policy/picture-in-picture-allowed-by-feature-policy.https.sub.html [ Skip ]
crbug.com/806249 virtual/unified-autoplay/external/wpt/feature-policy/picture-in-picture-default-feature-policy.https.sub.html [ Skip ]
crbug.com/806249 virtual/video-surface-layer/media/picture-in-picture/picture-in-picture-enabled.html [ Failure ]
crbug.com/811977 virtual/video-surface-layer/media/picture-in-picture/picture-in-picture-interstitial.html [ Failure ]
crbug.com/806249 virtual/video-surface-layer/media/picture-in-picture/controls/picture-in-picture-button.html [ Failure ]
crbug.com/806249 virtual/video-surface-layer/media/picture-in-picture/controls/picture-in-picture-video-with-audio-only-button.html [ Failure ]
# Sheriff 2018-02-26
crbug.com/816475 [ Win7 Linux ] external/wpt/webrtc/RTCDTMFSender-ontonechange.https.html [ Failure Pass ]
......
......@@ -4,35 +4,25 @@
<script src="../../../resources/testharnessreport.js"></script>
<script src="../../media-file.js"></script>
<script src="../../media-controls.js"></script>
<script src="../utils.js"></script>
<body>
<video controls></video>
<script>
async_test(t => {
var video = document.createElement('video');
video.setAttribute('controls', '');
video.src = '../../content/test.ogv';
document.body.appendChild(video);
enablePictureInPictureForTest(t);
video.onloadedmetadata = t.step_func_done(function() {
// Should have a picture in picture button.
var button = pictureInPictureButton(video);
assert_false(
("display" in button.style) && (button.style.display == "none"),
"button should exist");
var video = document.querySelector("video");
video.src = "../../content/test.ogv";
// Check its position is to the right of the timeline.
var buttonBoundingRect = button.getBoundingClientRect();
var x = buttonBoundingRect.left + buttonBoundingRect.width / 2;
var timelinePosition =
mediaControlsButtonCoordinates(video, "timeline");
assert_greater_than(x, timelinePosition[0],
"button should be to right of timeline");
video.onloadedmetadata = t.step_func(function() {
assert_true(isPictureInPictureButtonEnabled(video), "button should exist");
// Button should be to the left of the fullscreen button.
var fullscreenPosition =
mediaControlsButtonCoordinates(video, "fullscreen-button");
assert_less_than(x, fullscreenPosition[0],
"button should be to left of fullscreen button");
clickPictureInPictureButton(video, t.step_func(() => {
assert_false(isPictureInPictureButtonEnabled(video), "button should not exist");
}));
});
video.onenterpictureinpicture = t.step_func_done();
});
</script>
</body>
\ No newline at end of file
......@@ -6,21 +6,20 @@
<script src="../../media-controls.js"></script>
<script src="../utils.js"></script>
<body>
<video controls></video>
<script>
async_test(t => {
enablePictureInPictureForTest(t);
var video = document.createElement('video');
video.setAttribute('controls', '');
var video = document.querySelector("video");
video.src = '../../content/test.wav';
document.body.appendChild(video);
video.onloadeddata = t.step_func_done(function() {
assertPictureInPictureButtonNotVisible(video);
video.onloadedmetadata = t.step_func(function() {
assert_false(isPictureInPictureButtonEnabled(video), "button should not be visible.");
});
video.onloadedmetadata = t.step_func(function() {
assertPictureInPictureButtonNotVisible(video);
video.onloadeddata = t.step_func_done(function() {
assert_false(isPictureInPictureButtonEnabled(video), "button should not be visible.");
});
});
</script>
......
......@@ -6,37 +6,23 @@
<script src="../media-controls.js"></script>
<script src="utils.js"></script>
<body>
<video controls></video>
<script>
async_test(t => {
enablePictureInPictureForTest(t);
var video = document.createElement('video');
video.setAttribute('controls', '');
video.src = '../content/test.ogv';
document.body.appendChild(video);
video.play();
var video = document.querySelector("video");
video.src = "../content/test.ogv";
video.onloadedmetadata = t.step_func(function() {
// Should have a picture in picture button.
var button = pictureInPictureButton(video);
assert_false(
("display" in button.style) && (button.style.display == "none"),
"button should exist");
assert_true(isPictureInPictureButtonEnabled(video), "button should exist");
// Should not have a picture in picture interstitial while the video
// is not playing.
checkPictureInPictureInterstitialDoesNotExist(video);
button.onclick = setTimeout(t.step_func_done(function() {
// Interstitial should appear when picture in picture button is clicked.
var interstitial = pictureInPictureInterstitial(video);
assert_equals(interstitial.style.display, '',
'interstitial should be visible when the video is not playing');
clickPictureInPictureButton(video, t.step_func_done(() => {
assert_true(isPictureInPictureInterstitialVisible(video),
"Interstitial should be visible when button is clicked");
}));
video.play();
button.click();
});
});
</script>
......
function assertPictureInPictureButtonVisible(videoElement)
{
assert_true(isVisible(pictureInPictureButton(videoElement)),
"Picture in picture button should be visible.");
function pictureInPictureOverflowItem(video) {
return overflowItem(video, '-internal-media-controls-picture-in-picture-button');
}
function assertPictureInPictureButtonNotVisible(videoElement)
{
assert_false(isVisible(pictureInPictureButton(videoElement)),
"Picture in picture button should not be visible.");
function isPictureInPictureButtonEnabled(video) {
var button = pictureInPictureOverflowItem(video);
return !button.disabled && button.style.display != "none";
}
function pictureInPictureButton(videoElement)
{
var elementId = '-internal-media-controls-picture-in-picture-button';
var button = mediaControlsElement(
window.internals.shadowRoot(videoElement).firstChild,
elementId);
if (!button)
throw 'Failed to find picture in picture button.';
return button;
function clickPictureInPictureButton(video, callback) {
openOverflowAndClickButton(video, pictureInPictureOverflowItem(video), callback);
}
function assertPictureInPictureInterstitialVisible(videoElement)
{
assert_true(isVisible(pictureInPictureInterstitial(videoElement)),
"Picture in picture interstitial should be visible.");
function checkPictureInPictureInterstitialDoesNotExist(video) {
var controlID = '-internal-picture-in-picture-icon';
var interstitial = getElementByPseudoId(internals.shadowRoot(video), controlID);
if (interstitial)
throw 'Should not have a picture in picture interstitial';
}
function assertPictureInPictureInterstitialNotVisible(videoElement)
{
assert_false(isVisible(pictureInPictureInterstitial(videoElement)),
"Picture in picture interstitial should not be visible.");
function isPictureInPictureInterstitialVisible(video) {
return isVisible(pictureInPictureInterstitial(video));
}
function pictureInPictureInterstitial(videoElement)
function pictureInPictureInterstitial(video)
{
var elementId = '-internal-picture-in-picture-interstitial';
var elementId = '-internal-media-interstitial';
var interstitial = mediaControlsElement(
window.internals.shadowRoot(videoElement).firstChild,
window.internals.shadowRoot(video).firstChild,
elementId);
if (!interstitial)
throw 'Failed to find picture in picture interstitial.';
......@@ -55,12 +45,3 @@ function enablePictureInPictureForTest(t)
pictureInPictureEnabledValue;
});
}
\ No newline at end of file
function click(button)
{
const pos = offset(button);
const rect = button.getBoundingClientRect();
singleTapAtCoordinates(
Math.ceil(pos.left + rect.width / 2),
Math.ceil(pos.top + rect.height / 2));
}
\ No newline at end of file
......@@ -44,6 +44,9 @@ class CORE_EXPORT PictureInPictureController
// controller is allowed to request Picture-in-Picture.
virtual Status IsElementAllowed(const HTMLVideoElement&) const = 0;
// Should be called when an element has exited Picture-in-Picture.
virtual void OnExitedPictureInPicture(ScriptPromiseResolver*) = 0;
void Trace(blink::Visitor*) override;
protected:
......
......@@ -558,6 +558,9 @@ void HTMLVideoElement::PictureInPictureStarted() {
void HTMLVideoElement::PictureInPictureStopped() {
if (picture_in_picture_interstitial_)
picture_in_picture_interstitial_->Hide();
PictureInPictureController::From(GetDocument())
.OnExitedPictureInPicture(nullptr);
}
bool HTMLVideoElement::IsInPictureInPictureMode() {
......
......@@ -8,6 +8,7 @@ include_rules = [
"+third_party/blink/renderer/modules/device_orientation",
"+third_party/blink/renderer/modules/media_controls",
"+third_party/blink/renderer/modules/modules_export.h",
"+third_party/blink/renderer/modules/picture_in_picture",
"+third_party/blink/renderer/modules/remoteplayback",
"+third_party/blink/renderer/modules/screen_orientation",
]
......@@ -8,8 +8,10 @@
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/core/html/media/html_media_element.h"
#include "third_party/blink/renderer/core/html/media/html_media_source.h"
#include "third_party/blink/renderer/core/html/media/html_video_element.h"
#include "third_party/blink/renderer/core/input_type_names.h"
#include "third_party/blink/renderer/modules/media_controls/media_controls_impl.h"
#include "third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.h"
namespace blink {
......@@ -44,8 +46,15 @@ const char* MediaControlPictureInPictureButtonElement::GetNameForHistograms()
void MediaControlPictureInPictureButtonElement::DefaultEventHandler(
Event* event) {
if (event->type() == EventTypeNames::click)
MediaElement().enterPictureInPicture(base::DoNothing());
if (event->type() == EventTypeNames::click) {
PictureInPictureControllerImpl& controller =
PictureInPictureControllerImpl::From(MediaElement().GetDocument());
DCHECK(MediaElement().IsHTMLVideoElement());
// TODO(crbug.com/840516): Toggle PiP instead.
controller.EnterPictureInPicture(&ToHTMLVideoElement(MediaElement()),
nullptr);
}
MediaControlInputElement::DefaultEventHandler(event);
}
......
......@@ -92,8 +92,8 @@ void PictureInPictureControllerImpl::OnEnteredPictureInPicture(
ScriptPromiseResolver* resolver,
const WebSize& picture_in_picture_window_size) {
if (IsElementAllowed(*element) == Status::kDisabledByAttribute) {
if (resolver)
resolver->Reject(DOMException::Create(kInvalidStateError, ""));
resolver = nullptr;
// TODO(crbug.com/806249): Test that WMPI sends the message.
element->exitPictureInPicture(base::DoNothing());
return;
......@@ -111,6 +111,7 @@ void PictureInPictureControllerImpl::OnEnteredPictureInPicture(
picture_in_picture_window_ = new PictureInPictureWindow(
GetSupplementable(), picture_in_picture_window_size);
if (resolver)
resolver->Resolve(picture_in_picture_window_);
}
......@@ -124,13 +125,21 @@ void PictureInPictureControllerImpl::ExitPictureInPicture(
void PictureInPictureControllerImpl::OnExitedPictureInPicture(
ScriptPromiseResolver* resolver) {
DCHECK(GetSupplementable());
// Bail out if document is not active.
if (!GetSupplementable()->IsActive())
return;
if (picture_in_picture_window_)
picture_in_picture_window_->OnClose();
if (picture_in_picture_element_) {
HTMLVideoElement* element = picture_in_picture_element_;
picture_in_picture_element_ = nullptr;
element->DispatchEvent(
Event::CreateBubble(EventTypeNames::leavepictureinpicture));
}
if (resolver)
resolver->Resolve();
......
......@@ -58,9 +58,8 @@ class PictureInPictureControllerImpl : public PictureInPictureController {
// Exit Picture-in-Picture for a video element and resolve promise if any.
void ExitPictureInPicture(HTMLVideoElement*, ScriptPromiseResolver*);
// Meant to be called internally when an element has exited successfully
// Picture-in-Picture.
void OnExitedPictureInPicture(ScriptPromiseResolver*);
// Implementation of PictureInPictureController.
void OnExitedPictureInPicture(ScriptPromiseResolver*) override;
// Returns element currently in Picture-in-Picture if any. Null otherwise.
Element* PictureInPictureElement(TreeScope&) const;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment