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 ...@@ -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-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-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 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/controls/picture-in-picture-button.html [ Failure ]
crbug.com/806249 media/picture-in-picture/picture-in-picture-enabled.html [ Skip ] crbug.com/806249 media/picture-in-picture/controls/picture-in-picture-video-with-audio-only-button.html [ Failure ]
crbug.com/811977 media/picture-in-picture/picture-in-picture-interstitial.html [ Skip ] crbug.com/806249 media/picture-in-picture/picture-in-picture-enabled.html [ Failure ]
crbug.com/806249 virtual/video-surface-layer/media/picture-in-picture/controls/picture-in-picture-button.html [ Skip ] crbug.com/811977 media/picture-in-picture/picture-in-picture-interstitial.html [ Failure ]
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 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-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-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/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-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-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-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/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 # Sheriff 2018-02-26
crbug.com/816475 [ Win7 Linux ] external/wpt/webrtc/RTCDTMFSender-ontonechange.https.html [ Failure Pass ] crbug.com/816475 [ Win7 Linux ] external/wpt/webrtc/RTCDTMFSender-ontonechange.https.html [ Failure Pass ]
......
...@@ -4,35 +4,25 @@ ...@@ -4,35 +4,25 @@
<script src="../../../resources/testharnessreport.js"></script> <script src="../../../resources/testharnessreport.js"></script>
<script src="../../media-file.js"></script> <script src="../../media-file.js"></script>
<script src="../../media-controls.js"></script> <script src="../../media-controls.js"></script>
<script src="../utils.js"></script>
<body> <body>
<video controls></video>
<script> <script>
async_test(t => { async_test(t => {
var video = document.createElement('video'); enablePictureInPictureForTest(t);
video.setAttribute('controls', '');
video.src = '../../content/test.ogv';
document.body.appendChild(video);
video.onloadedmetadata = t.step_func_done(function() { var video = document.querySelector("video");
// Should have a picture in picture button. video.src = "../../content/test.ogv";
var button = pictureInPictureButton(video);
assert_false(
("display" in button.style) && (button.style.display == "none"),
"button should exist");
// Check its position is to the right of the timeline. video.onloadedmetadata = t.step_func(function() {
var buttonBoundingRect = button.getBoundingClientRect(); assert_true(isPictureInPictureButtonEnabled(video), "button should exist");
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");
// Button should be to the left of the fullscreen button. clickPictureInPictureButton(video, t.step_func(() => {
var fullscreenPosition = assert_false(isPictureInPictureButtonEnabled(video), "button should not exist");
mediaControlsButtonCoordinates(video, "fullscreen-button"); }));
assert_less_than(x, fullscreenPosition[0], });
"button should be to left of fullscreen button");
}); video.onenterpictureinpicture = t.step_func_done();
}); });
</script> </script>
</body> </body>
\ No newline at end of file
...@@ -6,22 +6,21 @@ ...@@ -6,22 +6,21 @@
<script src="../../media-controls.js"></script> <script src="../../media-controls.js"></script>
<script src="../utils.js"></script> <script src="../utils.js"></script>
<body> <body>
<video controls></video>
<script> <script>
async_test(t => { async_test(t => {
enablePictureInPictureForTest(t); enablePictureInPictureForTest(t);
var video = document.createElement('video'); var video = document.querySelector("video");
video.setAttribute('controls', ''); video.src = '../../content/test.wav';
video.src = '../../content/test.wav';
document.body.appendChild(video);
video.onloadeddata = t.step_func_done(function() { video.onloadedmetadata = t.step_func(function() {
assertPictureInPictureButtonNotVisible(video); assert_false(isPictureInPictureButtonEnabled(video), "button should not be visible.");
}); });
video.onloadedmetadata = t.step_func(function() { video.onloadeddata = t.step_func_done(function() {
assertPictureInPictureButtonNotVisible(video); assert_false(isPictureInPictureButtonEnabled(video), "button should not be visible.");
}); });
}); });
</script> </script>
</body> </body>
\ No newline at end of file
...@@ -6,38 +6,24 @@ ...@@ -6,38 +6,24 @@
<script src="../media-controls.js"></script> <script src="../media-controls.js"></script>
<script src="utils.js"></script> <script src="utils.js"></script>
<body> <body>
<video controls></video>
<script> <script>
async_test(t => { async_test(t => {
enablePictureInPictureForTest(t); enablePictureInPictureForTest(t);
var video = document.createElement('video'); var video = document.querySelector("video");
video.setAttribute('controls', ''); video.src = "../content/test.ogv";
video.src = '../content/test.ogv';
document.body.appendChild(video);
video.play(); video.onloadedmetadata = t.step_func(function() {
assert_true(isPictureInPictureButtonEnabled(video), "button should exist");
video.onloadedmetadata = t.step_func(function() { checkPictureInPictureInterstitialDoesNotExist(video);
// Should have a picture in picture button.
var button = pictureInPictureButton(video);
assert_false(
("display" in button.style) && (button.style.display == "none"),
"button should exist");
// Should not have a picture in picture interstitial while the video clickPictureInPictureButton(video, t.step_func_done(() => {
// is not playing. assert_true(isPictureInPictureInterstitialVisible(video),
checkPictureInPictureInterstitialDoesNotExist(video); "Interstitial should be visible when button is clicked");
}));
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');
}));
video.play();
button.click();
});
}); });
</script> </script>
</body> </body>
function assertPictureInPictureButtonVisible(videoElement) function pictureInPictureOverflowItem(video) {
{ return overflowItem(video, '-internal-media-controls-picture-in-picture-button');
assert_true(isVisible(pictureInPictureButton(videoElement)),
"Picture in picture button should be visible.");
} }
function assertPictureInPictureButtonNotVisible(videoElement) function isPictureInPictureButtonEnabled(video) {
{ var button = pictureInPictureOverflowItem(video);
assert_false(isVisible(pictureInPictureButton(videoElement)), return !button.disabled && button.style.display != "none";
"Picture in picture button should not be visible.");
} }
function pictureInPictureButton(videoElement) function clickPictureInPictureButton(video, callback) {
{ openOverflowAndClickButton(video, pictureInPictureOverflowItem(video), callback);
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 assertPictureInPictureInterstitialVisible(videoElement) function checkPictureInPictureInterstitialDoesNotExist(video) {
{ var controlID = '-internal-picture-in-picture-icon';
assert_true(isVisible(pictureInPictureInterstitial(videoElement)),
"Picture in picture interstitial should be visible."); var interstitial = getElementByPseudoId(internals.shadowRoot(video), controlID);
if (interstitial)
throw 'Should not have a picture in picture interstitial';
} }
function assertPictureInPictureInterstitialNotVisible(videoElement) function isPictureInPictureInterstitialVisible(video) {
{ return isVisible(pictureInPictureInterstitial(video));
assert_false(isVisible(pictureInPictureInterstitial(videoElement)),
"Picture in picture interstitial should not be visible.");
} }
function pictureInPictureInterstitial(videoElement) function pictureInPictureInterstitial(video)
{ {
var elementId = '-internal-picture-in-picture-interstitial'; var elementId = '-internal-media-interstitial';
var interstitial = mediaControlsElement( var interstitial = mediaControlsElement(
window.internals.shadowRoot(videoElement).firstChild, window.internals.shadowRoot(video).firstChild,
elementId); elementId);
if (!interstitial) if (!interstitial)
throw 'Failed to find picture in picture interstitial.'; throw 'Failed to find picture in picture interstitial.';
...@@ -54,13 +44,4 @@ function enablePictureInPictureForTest(t) ...@@ -54,13 +44,4 @@ function enablePictureInPictureForTest(t)
internals.runtimeFlags.pictureInPictureEnabled = internals.runtimeFlags.pictureInPictureEnabled =
pictureInPictureEnabledValue; pictureInPictureEnabledValue;
}); });
}
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 ...@@ -44,6 +44,9 @@ class CORE_EXPORT PictureInPictureController
// controller is allowed to request Picture-in-Picture. // controller is allowed to request Picture-in-Picture.
virtual Status IsElementAllowed(const HTMLVideoElement&) const = 0; 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; void Trace(blink::Visitor*) override;
protected: protected:
......
...@@ -558,6 +558,9 @@ void HTMLVideoElement::PictureInPictureStarted() { ...@@ -558,6 +558,9 @@ void HTMLVideoElement::PictureInPictureStarted() {
void HTMLVideoElement::PictureInPictureStopped() { void HTMLVideoElement::PictureInPictureStopped() {
if (picture_in_picture_interstitial_) if (picture_in_picture_interstitial_)
picture_in_picture_interstitial_->Hide(); picture_in_picture_interstitial_->Hide();
PictureInPictureController::From(GetDocument())
.OnExitedPictureInPicture(nullptr);
} }
bool HTMLVideoElement::IsInPictureInPictureMode() { bool HTMLVideoElement::IsInPictureInPictureMode() {
......
...@@ -8,6 +8,7 @@ include_rules = [ ...@@ -8,6 +8,7 @@ include_rules = [
"+third_party/blink/renderer/modules/device_orientation", "+third_party/blink/renderer/modules/device_orientation",
"+third_party/blink/renderer/modules/media_controls", "+third_party/blink/renderer/modules/media_controls",
"+third_party/blink/renderer/modules/modules_export.h", "+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/remoteplayback",
"+third_party/blink/renderer/modules/screen_orientation", "+third_party/blink/renderer/modules/screen_orientation",
] ]
...@@ -8,8 +8,10 @@ ...@@ -8,8 +8,10 @@
#include "third_party/blink/renderer/core/dom/events/event.h" #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_element.h"
#include "third_party/blink/renderer/core/html/media/html_media_source.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/core/input_type_names.h"
#include "third_party/blink/renderer/modules/media_controls/media_controls_impl.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 { namespace blink {
...@@ -44,8 +46,15 @@ const char* MediaControlPictureInPictureButtonElement::GetNameForHistograms() ...@@ -44,8 +46,15 @@ const char* MediaControlPictureInPictureButtonElement::GetNameForHistograms()
void MediaControlPictureInPictureButtonElement::DefaultEventHandler( void MediaControlPictureInPictureButtonElement::DefaultEventHandler(
Event* event) { Event* event) {
if (event->type() == EventTypeNames::click) if (event->type() == EventTypeNames::click) {
MediaElement().enterPictureInPicture(base::DoNothing()); PictureInPictureControllerImpl& controller =
PictureInPictureControllerImpl::From(MediaElement().GetDocument());
DCHECK(MediaElement().IsHTMLVideoElement());
// TODO(crbug.com/840516): Toggle PiP instead.
controller.EnterPictureInPicture(&ToHTMLVideoElement(MediaElement()),
nullptr);
}
MediaControlInputElement::DefaultEventHandler(event); MediaControlInputElement::DefaultEventHandler(event);
} }
......
...@@ -92,8 +92,8 @@ void PictureInPictureControllerImpl::OnEnteredPictureInPicture( ...@@ -92,8 +92,8 @@ void PictureInPictureControllerImpl::OnEnteredPictureInPicture(
ScriptPromiseResolver* resolver, ScriptPromiseResolver* resolver,
const WebSize& picture_in_picture_window_size) { const WebSize& picture_in_picture_window_size) {
if (IsElementAllowed(*element) == Status::kDisabledByAttribute) { if (IsElementAllowed(*element) == Status::kDisabledByAttribute) {
resolver->Reject(DOMException::Create(kInvalidStateError, "")); if (resolver)
resolver = nullptr; resolver->Reject(DOMException::Create(kInvalidStateError, ""));
// TODO(crbug.com/806249): Test that WMPI sends the message. // TODO(crbug.com/806249): Test that WMPI sends the message.
element->exitPictureInPicture(base::DoNothing()); element->exitPictureInPicture(base::DoNothing());
return; return;
...@@ -111,7 +111,8 @@ void PictureInPictureControllerImpl::OnEnteredPictureInPicture( ...@@ -111,7 +111,8 @@ void PictureInPictureControllerImpl::OnEnteredPictureInPicture(
picture_in_picture_window_ = new PictureInPictureWindow( picture_in_picture_window_ = new PictureInPictureWindow(
GetSupplementable(), picture_in_picture_window_size); GetSupplementable(), picture_in_picture_window_size);
resolver->Resolve(picture_in_picture_window_); if (resolver)
resolver->Resolve(picture_in_picture_window_);
} }
void PictureInPictureControllerImpl::ExitPictureInPicture( void PictureInPictureControllerImpl::ExitPictureInPicture(
...@@ -124,13 +125,21 @@ void PictureInPictureControllerImpl::ExitPictureInPicture( ...@@ -124,13 +125,21 @@ void PictureInPictureControllerImpl::ExitPictureInPicture(
void PictureInPictureControllerImpl::OnExitedPictureInPicture( void PictureInPictureControllerImpl::OnExitedPictureInPicture(
ScriptPromiseResolver* resolver) { ScriptPromiseResolver* resolver) {
DCHECK(GetSupplementable());
// Bail out if document is not active.
if (!GetSupplementable()->IsActive())
return;
if (picture_in_picture_window_) if (picture_in_picture_window_)
picture_in_picture_window_->OnClose(); picture_in_picture_window_->OnClose();
HTMLVideoElement* element = picture_in_picture_element_; if (picture_in_picture_element_) {
picture_in_picture_element_ = nullptr; HTMLVideoElement* element = picture_in_picture_element_;
element->DispatchEvent( picture_in_picture_element_ = nullptr;
Event::CreateBubble(EventTypeNames::leavepictureinpicture)); element->DispatchEvent(
Event::CreateBubble(EventTypeNames::leavepictureinpicture));
}
if (resolver) if (resolver)
resolver->Resolve(); resolver->Resolve();
......
...@@ -58,9 +58,8 @@ class PictureInPictureControllerImpl : public PictureInPictureController { ...@@ -58,9 +58,8 @@ class PictureInPictureControllerImpl : public PictureInPictureController {
// Exit Picture-in-Picture for a video element and resolve promise if any. // Exit Picture-in-Picture for a video element and resolve promise if any.
void ExitPictureInPicture(HTMLVideoElement*, ScriptPromiseResolver*); void ExitPictureInPicture(HTMLVideoElement*, ScriptPromiseResolver*);
// Meant to be called internally when an element has exited successfully // Implementation of PictureInPictureController.
// Picture-in-Picture. void OnExitedPictureInPicture(ScriptPromiseResolver*) override;
void OnExitedPictureInPicture(ScriptPromiseResolver*);
// Returns element currently in Picture-in-Picture if any. Null otherwise. // Returns element currently in Picture-in-Picture if any. Null otherwise.
Element* PictureInPictureElement(TreeScope&) const; 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