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

[Picture-In-Picture] Expose some APIs behind a runtime feature.

https://wicg.github.io/picture-in-picture/

The API is currently not working. It is mostly about implementing the shell: it
exposes document.pictureInPictureEnabled, document.exitPictureInPicture(), and
video.requestPictureInPicture(). It also adds the "picture-in-picture" feature
policy. Everything is gated by the "PictureInPictureAPI" runtime feature.

Intent to implement: https://groups.google.com/a/chromium.org/d/msg/blink-dev/U8Apo-WLBm4/03sO4ITYAQAJ

Change-Id: I64d8e17c6975017565c850afeeca19d94e9f9ceb
Bug: 806249
Reviewed-on: https://chromium-review.googlesource.com/867915
Commit-Queue: Mounir Lamouri (slow) <mlamouri@chromium.org>
Reviewed-by: default avatarMounir Lamouri (slow) <mlamouri@chromium.org>
Reviewed-by: default avatarMike West <mkwst@chromium.org>
Reviewed-by: default avatarIan Clelland <iclelland@chromium.org>
Reviewed-by: default avatarKentaro Hara <haraken@chromium.org>
Cr-Commit-Position: refs/heads/master@{#533310}
parent cad9c3d1
<!DOCTYPE html>
<body>
<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=/feature-policy/resources/featurepolicy.js></script>
<script src=/feature-policy/resources/picture-in-picture.js></script>
<script>
'use strict';
const relative_path = '/feature-policy/resources/feature-policy-picture-in-picture.html';
const base_src = '/feature-policy/resources/redirect-on-load.html#';
const same_origin_src = base_src + relative_path;
const cross_origin_src = base_src + 'https://{{domains[www]}}:{{ports[https][0]}}' +
relative_path;
const header = 'Feature-Policy allow="picture-in-picture"';
async_test(t => {
test_feature_availability(
'picture-in-picture', t, same_origin_src,
expect_feature_available_default, 'picture-in-picture');
}, header + ' allows same-origin navigation in an iframe.');
async_test(t => {
test_feature_availability(
'picture-in-picture', t, cross_origin_src,
expect_feature_unavailable_default, 'picture-in-picture');
}, header + ' disallows cross-origin navigation in an iframe.');
</script>
</body>
<!DOCTYPE html>
<body>
<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=/feature-policy/resources/featurepolicy.js></script>
<script src=/feature-policy/resources/picture-in-picture.js></script>
<script>
'use strict';
const same_origin_src = '/feature-policy/resources/feature-policy-picture-in-picture.html';
const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
same_origin_src;
const feature_name = 'Feature policy "picture-in-picture"';
const header = 'allow="picture-in-picture" attribute';
async_test(t => {
test_feature_availability(
'picture-in-picture', t, same_origin_src,
expect_feature_available_default, 'picture-in-picture');
}, feature_name + ' can be enabled in same-origin iframe using ' + header);
async_test(t => {
test_feature_availability(
'picture-in-picture', t, cross_origin_src,
expect_feature_available_default, 'picture-in-picture');
}, feature_name + ' can be enabled in cross-origin iframe using ' + header);
</script>
</body>
<!DOCTYPE html>
<body>
<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=/feature-policy/resources/featurepolicy.js></script>
<script src=/feature-policy/resources/picture-in-picture.js></script>
<script>
'use strict';
const same_origin_src = '/feature-policy/resources/feature-policy-picture-in-picture.html';
const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
same_origin_src;
const header = 'Feature-Policy header: picture-in-picture *';
async_test(t => {
isPictureInPictureAllowed().then(t.step_func_done((result) => {
assert_true(result);
}));
}, header + ' allows the top-level document.');
async_test(t => {
test_feature_availability('picture-in-picture', t, same_origin_src,
expect_feature_available_default);
}, header + ' allows same-origin iframes.');
async_test(t => {
test_feature_availability('picture-in-picture', t, cross_origin_src,
expect_feature_available_default);
}, header + ' allows cross-origin iframes.');
</script>
</body>
<!DOCTYPE html>
<body>
<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=/feature-policy/resources/featurepolicy.js></script>
<script src=/feature-policy/resources/picture-in-picture.js></script>
<script>
'use strict';
const same_origin_src = '/feature-policy/resources/feature-policy-picture-in-picture.html';
const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
same_origin_src;
const header = 'Default "picture-in-picture" feature policy ["self"]';
async_test(t => {
isPictureInPictureAllowed().then(t.step_func_done((result) => {
assert_true(result);
}));
}, header + ' allows the top-level document.');
async_test(t => {
test_feature_availability('picture-in-picture', t, same_origin_src,
expect_feature_available_default);
}, header + ' allows same-origin iframes.');
async_test(t => {
test_feature_availability('picture-in-picture', t, cross_origin_src,
expect_feature_unavailable_default,);
}, header + ' disallows cross-origin iframes.');
</script>
</body>
<!DOCTYPE html>
<body>
<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=/feature-policy/resources/featurepolicy.js></script>
<script src=/feature-policy/resources/picture-in-picture.js></script>
<script>
'use strict';
const same_origin_src = '/feature-policy/resources/feature-policy-picture-in-picture.html';
const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
same_origin_src;
const header = 'Feature-Policy header: picture-in-picture "none"';
async_test(t => {
isPictureInPictureAllowed().then(t.step_func_done((result) => {
assert_false(result);
}));
}, header + ' disallows the top-level document.');
async_test(t => {
test_feature_availability('picture-in-picture', t, same_origin_src,
expect_feature_unavailable_default);
}, header + ' disallows same-origin iframes.');
async_test(t => {
test_feature_availability('picture-in-picture', t, cross_origin_src,
expect_feature_unavailable_default,);
}, header + ' disallows cross-origin iframes.');
</script>
</body>
<script src=/feature-policy/resources/picture-in-picture.js></script>
<script>
'use strict';
window.addEventListener('load', () => {
isPictureInPictureAllowed().then(result => {
window.parent.postMessage({ enabled: result }, '*');
});
}, { once: true });
</script>
function isPictureInPictureAllowed() {
return new Promise((resolve, reject) => {
const video = document.createElement('video');
video.requestPictureInPicture()
.then(() => resolve(document.pictureInPictureEnabled))
.catch(e => {
if (e.name == 'NotAllowedError')
resolve(document.pictureInPictureEnabled);
else
resolve(false);
});
});
}
\ No newline at end of file
<!DOCTYPE html>
<title>Test request picture in picture requires a user gesture</title>
<script src='../../resources/testharness.js'></script>
<script src='../../resources/testharnessreport.js'></script>
<body></body>
<script>
promise_test(t => {
return promise_rejects(t, 'NotAllowedError',
document.createElement('video').requestPictureInPicture());
}, );
</script>
<!DOCTYPE html>
<title>Test request 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(t => {
return promise_rejects(t, 'NotSupportedError',
requestPictureInPictureWithTrustedClick(document.createElement('video')));
});
</script>
function callWithTrustedClick(callback) {
return new Promise(resolve => {
let button = document.createElement('button');
button.textContent = 'click to continue test';
button.style.display = 'block';
button.style.fontSize = '20px';
button.style.padding = '10px';
button.onclick = () => {
document.body.removeChild(button);
resolve(callback());
};
document.body.appendChild(button);
test_driver.click(button);
});
}
// Calls requestPictureInPicture() in a context that's 'allowed to request PiP'.
function requestPictureInPictureWithTrustedClick(videoElement) {
return callWithTrustedClick(
() => videoElement.requestPictureInPicture());
}
\ No newline at end of file
...@@ -1093,6 +1093,7 @@ html element video ...@@ -1093,6 +1093,7 @@ html element video
property preload property preload
property readyState property readyState
property remote property remote
property requestPictureInPicture
property seekable property seekable
property seeking property seeking
property setMediaKeys property setMediaKeys
......
...@@ -1576,6 +1576,7 @@ interface Document : Node ...@@ -1576,6 +1576,7 @@ interface Document : Node
getter onwebkitfullscreenerror getter onwebkitfullscreenerror
getter onwheel getter onwheel
getter origin getter origin
getter pictureInPictureEnabled
getter plugins getter plugins
getter pointerLockElement getter pointerLockElement
getter policy getter policy
...@@ -1631,6 +1632,7 @@ interface Document : Node ...@@ -1631,6 +1632,7 @@ interface Document : Node
method evaluate method evaluate
method execCommand method execCommand
method exitFullscreen method exitFullscreen
method exitPictureInPicture
method exitPointerLock method exitPointerLock
method getAnimations method getAnimations
method getElementById method getElementById
...@@ -3643,6 +3645,7 @@ interface HTMLVideoElement : HTMLMediaElement ...@@ -3643,6 +3645,7 @@ interface HTMLVideoElement : HTMLMediaElement
getter width getter width
method constructor method constructor
method getVideoPlaybackQuality method getVideoPlaybackQuality
method requestPictureInPicture
method webkitEnterFullScreen method webkitEnterFullScreen
method webkitEnterFullscreen method webkitEnterFullscreen
method webkitExitFullScreen method webkitExitFullScreen
......
...@@ -112,7 +112,6 @@ core_idl_files = ...@@ -112,7 +112,6 @@ core_idl_files =
"dom/DOMStringList.idl", "dom/DOMStringList.idl",
"dom/DOMStringMap.idl", "dom/DOMStringMap.idl",
"dom/DOMTokenList.idl", "dom/DOMTokenList.idl",
"dom/Document.idl",
"dom/DocumentFragment.idl", "dom/DocumentFragment.idl",
"dom/DocumentType.idl", "dom/DocumentType.idl",
"dom/Element.idl", "dom/Element.idl",
...@@ -458,6 +457,7 @@ core_idl_with_modules_dependency_files = ...@@ -458,6 +457,7 @@ core_idl_with_modules_dependency_files =
get_path_info([ get_path_info([
"clipboard/DataTransferItem.idl", "clipboard/DataTransferItem.idl",
"css/CSS.idl", "css/CSS.idl",
"dom/Document.idl",
"frame/Navigator.idl", "frame/Navigator.idl",
"frame/Screen.idl", "frame/Screen.idl",
"frame/Window.idl", "frame/Window.idl",
......
...@@ -135,6 +135,7 @@ target("jumbo_" + modules_target_type, "modules") { ...@@ -135,6 +135,7 @@ target("jumbo_" + modules_target_type, "modules") {
"//third_party/WebKit/Source/modules/payments", "//third_party/WebKit/Source/modules/payments",
"//third_party/WebKit/Source/modules/peerconnection", "//third_party/WebKit/Source/modules/peerconnection",
"//third_party/WebKit/Source/modules/permissions", "//third_party/WebKit/Source/modules/permissions",
"//third_party/WebKit/Source/modules/picture_in_picture",
"//third_party/WebKit/Source/modules/plugins", "//third_party/WebKit/Source/modules/plugins",
"//third_party/WebKit/Source/modules/presentation", "//third_party/WebKit/Source/modules/presentation",
"//third_party/WebKit/Source/modules/push_messaging", "//third_party/WebKit/Source/modules/push_messaging",
...@@ -287,6 +288,7 @@ jumbo_source_set("unit_tests") { ...@@ -287,6 +288,7 @@ jumbo_source_set("unit_tests") {
"payments/PaymentsValidatorsTest.cpp", "payments/PaymentsValidatorsTest.cpp",
"peerconnection/RTCDataChannelTest.cpp", "peerconnection/RTCDataChannelTest.cpp",
"peerconnection/RTCPeerConnectionTest.cpp", "peerconnection/RTCPeerConnectionTest.cpp",
"picture_in_picture/PictureInPictureTest.cpp",
"presentation/MockPresentationService.h", "presentation/MockPresentationService.h",
"presentation/MockWebPresentationClient.h", "presentation/MockWebPresentationClient.h",
"presentation/PresentationAvailabilityStateTest.cpp", "presentation/PresentationAvailabilityStateTest.cpp",
......
...@@ -700,6 +700,8 @@ modules_dependency_idl_files = ...@@ -700,6 +700,8 @@ modules_dependency_idl_files =
"payments/PaymentAppServiceWorkerRegistration.idl", "payments/PaymentAppServiceWorkerRegistration.idl",
"permissions/NavigatorPermissions.idl", "permissions/NavigatorPermissions.idl",
"permissions/WorkerNavigatorPermissions.idl", "permissions/WorkerNavigatorPermissions.idl",
"picture_in_picture/DocumentPictureInPicture.idl",
"picture_in_picture/HTMLVideoElementPictureInPicture.idl",
"plugins/NavigatorPlugins.idl", "plugins/NavigatorPlugins.idl",
"presentation/NavigatorPresentation.idl", "presentation/NavigatorPresentation.idl",
"push_messaging/ServiceWorkerGlobalScopePush.idl", "push_messaging/ServiceWorkerGlobalScopePush.idl",
......
# Copyright 2018 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//third_party/WebKit/Source/modules/modules.gni")
blink_modules_sources("picture_in_picture") {
sources = [
"DocumentPictureInPicture.cpp",
"DocumentPictureInPicture.h",
"HTMLVideoElementPictureInPicture.cpp",
"HTMLVideoElementPictureInPicture.h",
"PictureInPictureController.cpp",
"PictureInPictureController.h",
]
}
include_rules = [
"-modules",
"+modules/ModulesExport.h",
"+modules/picture_in_picture",
]
\ No newline at end of file
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "modules/picture_in_picture/DocumentPictureInPicture.h"
#include "modules/picture_in_picture/PictureInPictureController.h"
namespace blink {
// static
bool DocumentPictureInPicture::pictureInPictureEnabled(Document& document) {
return PictureInPictureController::Ensure(document).PictureInPictureEnabled();
}
// static
ScriptPromise DocumentPictureInPicture::exitPictureInPicture(
ScriptState* script_state,
const Document&) {
// TODO(crbug.com/806249): Call element.exitPictureInPicture().
return ScriptPromise::CastUndefined(script_state);
}
} // namespace blink
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef DocumentPictureInPicture_h
#define DocumentPictureInPicture_h
#include "platform/heap/Handle.h"
namespace blink {
class Document;
class ScriptPromise;
class ScriptState;
class DocumentPictureInPicture {
STATIC_ONLY(DocumentPictureInPicture);
public:
static bool pictureInPictureEnabled(Document&);
static ScriptPromise exitPictureInPicture(ScriptState*, const Document&);
};
} // namespace blink
#endif // DocumentPictureInPicture_h
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// https://wicg.github.io/picture-in-picture/#document-extensions
[
ImplementedAs=DocumentPictureInPicture,
RuntimeEnabled=PictureInPictureAPI
]
partial interface Document {
readonly attribute boolean pictureInPictureEnabled;
[CallWith=ScriptState] Promise<void> exitPictureInPicture();
};
\ No newline at end of file
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "modules/picture_in_picture/HTMLVideoElementPictureInPicture.h"
#include "core/dom/DOMException.h"
#include "core/html/media/HTMLVideoElement.h"
#include "modules/picture_in_picture/PictureInPictureController.h"
#include "platform/feature_policy/FeaturePolicy.h"
namespace blink {
using Status = PictureInPictureController::Status;
namespace {
const char kDetachedError[] =
"The element is no longer associated with a document.";
const char kFeaturePolicyBlocked[] =
"Access to the feature \"picture-in-picture\" is disallowed by feature "
"policy.";
const char kNotAvailable[] = "Picture-in-Picture is not available.";
const char kUserGestureRequired[] =
"Must be handling a user gesture to request picture in picture.";
} // namespace
// static
ScriptPromise HTMLVideoElementPictureInPicture::requestPictureInPicture(
ScriptState* script_state,
HTMLVideoElement& element) {
Document& document = element.GetDocument();
if (!document.GetFrame()) {
return ScriptPromise::RejectWithDOMException(
script_state, DOMException::Create(kInvalidStateError, kDetachedError));
}
switch (PictureInPictureController::Ensure(document).GetStatus()) {
case Status::kDisabledByFeaturePolicy:
return ScriptPromise::RejectWithDOMException(
script_state,
DOMException::Create(kSecurityError, kFeaturePolicyBlocked));
case Status::kDisabledBySystem:
return ScriptPromise::RejectWithDOMException(
script_state,
DOMException::Create(kNotSupportedError, kNotAvailable));
case Status::kEnabled:
break;
}
LocalFrame* frame = element.GetFrame();
if (!Frame::ConsumeTransientUserActivation(frame)) {
return ScriptPromise::RejectWithDOMException(
script_state,
DOMException::Create(kNotAllowedError, kUserGestureRequired));
}
// TODO(crbug.com/806249): Call element.enterPictureInPicture().
return ScriptPromise::RejectWithDOMException(
script_state,
DOMException::Create(kNotSupportedError, "Not implemented yet"));
}
} // namespace blink
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef HTMLVideoElementPictureInPicture_h
#define HTMLVideoElementPictureInPicture_h
#include "modules/ModulesExport.h"
#include "platform/heap/Handle.h"
namespace blink {
class HTMLVideoElement;
class ScriptPromise;
class ScriptState;
class MODULES_EXPORT HTMLVideoElementPictureInPicture {
STATIC_ONLY(HTMLVideoElementPictureInPicture);
public:
static ScriptPromise requestPictureInPicture(ScriptState*, HTMLVideoElement&);
};
} // namespace blink
#endif // HTMLVideoElementPictureInPicture_h
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// https://wicg.github.io/picture-in-picture/#htmlvideoelement-extensions
[
ImplementedAs=HTMLVideoElementPictureInPicture,
RuntimeEnabled=PictureInPictureAPI
]
partial interface HTMLVideoElement {
// TODO(crbug.com/806249): Promise should return a PiPWindow.
[CallWith=ScriptState] Promise<void> requestPictureInPicture();
// TODO(crbug.com/806249): Implement PiP video events.
//attribute EventHandler onenterpictureinpicture;
//attribute EventHandler onleavepictureinpicture;
};
\ No newline at end of file
# TEAM: media-dev@chromium.org
# COMPONENT: Blink>Media>PictureInPicture
\ No newline at end of file
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "modules/picture_in_picture/PictureInPictureController.h"
#include "core/dom/Document.h"
#include "platform/feature_policy/FeaturePolicy.h"
namespace blink {
PictureInPictureController::PictureInPictureController(Document& document)
: Supplement<Document>(document) {}
PictureInPictureController::~PictureInPictureController() = default;
// static
PictureInPictureController& PictureInPictureController::Ensure(
Document& document) {
PictureInPictureController* controller =
static_cast<PictureInPictureController*>(
Supplement<Document>::From(document, SupplementName()));
if (!controller) {
controller = new PictureInPictureController(document);
ProvideTo(document, SupplementName(), controller);
}
return *controller;
}
// static
const char* PictureInPictureController::SupplementName() {
return "PictureInPictureController";
}
bool PictureInPictureController::PictureInPictureEnabled() const {
return GetStatus() == Status::kEnabled;
}
PictureInPictureController::Status PictureInPictureController::GetStatus()
const {
DCHECK(GetSupplementable());
// If document is not allowed to use the policy-controlled feature named
// "picture-in-picture", return kDisabledByFeaturePolicy status.
LocalFrame* frame = GetSupplementable()->GetFrame();
if (IsSupportedInFeaturePolicy(
blink::FeaturePolicyFeature::kPictureInPicture) &&
!frame->IsFeatureEnabled(
blink::FeaturePolicyFeature::kPictureInPicture)) {
return Status::kDisabledByFeaturePolicy;
}
// TODO(crbug.com/806249): Handle status when disabled by attribute
// `picture_in_picture_enabled_` is set to false by the embedder when it
// or the system forbids the page from using Picture-in-Picture.
if (!picture_in_picture_enabled_)
return Status::kDisabledBySystem;
return Status::kEnabled;
}
void PictureInPictureController::SetPictureInPictureEnabledForTesting(
bool value) {
picture_in_picture_enabled_ = value;
}
} // namespace blink
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef PictureInPictureController_h
#define PictureInPictureController_h
#include "core/frame/LocalFrame.h"
#include "modules/ModulesExport.h"
namespace blink {
class MODULES_EXPORT PictureInPictureController
: public GarbageCollectedFinalized<PictureInPictureController>,
public Supplement<Document> {
USING_GARBAGE_COLLECTED_MIXIN(PictureInPictureController);
WTF_MAKE_NONCOPYABLE(PictureInPictureController);
public:
virtual ~PictureInPictureController();
static PictureInPictureController& Ensure(Document&);
static const char* SupplementName();
bool PictureInPictureEnabled() const;
void SetPictureInPictureEnabledForTesting(bool);
enum class Status {
kEnabled,
kDisabledByFeaturePolicy,
kDisabledBySystem,
};
Status GetStatus() const;
private:
explicit PictureInPictureController(Document&);
bool picture_in_picture_enabled_ = true;
};
} // namespace blink
#endif // PictureInPictureController_h
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "bindings/core/v8/V8BindingForCore.h"
#include "core/html/media/HTMLVideoElement.h"
#include "core/testing/PageTestBase.h"
#include "modules/picture_in_picture/HTMLVideoElementPictureInPicture.h"
#include "modules/picture_in_picture/PictureInPictureController.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace blink {
const char kNotSupportedString[] =
"NotSupportedError: Picture-in-Picture is not available.";
class PictureInPictureTest : public PageTestBase {
protected:
void SetUp() final { PageTestBase::SetUp(); }
ScriptState* GetScriptState() {
return ToScriptStateForMainWorld(GetDocument().GetFrame());
}
v8::Isolate* GetIsolate() { return GetScriptState()->GetIsolate(); }
v8::Local<v8::Context> GetContext() { return GetScriptState()->GetContext(); }
// Convenience methods for testing the returned promises.
ScriptValue GetRejectValue(ScriptPromise& promise) {
ScriptValue on_reject;
promise.Then(UnreachableFunction::Create(GetScriptState()),
TestFunction::Create(GetScriptState(), &on_reject));
v8::MicrotasksScope::PerformCheckpoint(GetIsolate());
return on_reject;
}
std::string GetRejectString(ScriptPromise& promise) {
ScriptValue on_reject = GetRejectValue(promise);
return ToCoreString(
on_reject.V8Value()->ToString(GetContext()).ToLocalChecked())
.Ascii()
.data();
}
private:
// A ScriptFunction that creates a test failure if it is ever called.
class UnreachableFunction : public ScriptFunction {
public:
static v8::Local<v8::Function> Create(ScriptState* script_state) {
UnreachableFunction* self = new UnreachableFunction(script_state);
return self->BindToV8Function();
}
ScriptValue Call(ScriptValue value) override {
ADD_FAILURE() << "Unexpected call to a null ScriptFunction.";
return value;
}
private:
UnreachableFunction(ScriptState* script_state)
: ScriptFunction(script_state) {}
};
// A ScriptFunction that saves its parameter; used by tests to assert on
// correct values being passed.
class TestFunction : public ScriptFunction {
public:
static v8::Local<v8::Function> Create(ScriptState* script_state,
ScriptValue* out_value) {
TestFunction* self = new TestFunction(script_state, out_value);
return self->BindToV8Function();
}
ScriptValue Call(ScriptValue value) override {
DCHECK(!value.IsEmpty());
*value_ = value;
return value;
}
private:
TestFunction(ScriptState* script_state, ScriptValue* out_value)
: ScriptFunction(script_state), value_(out_value) {}
ScriptValue* value_;
};
};
TEST_F(PictureInPictureTest,
RequestPictureInPictureRejectsWhenPictureInPictureEnabledIsFalse) {
Persistent<PictureInPictureController> controller =
PictureInPictureController::Ensure(GetDocument());
ScriptState::Scope scope(GetScriptState());
HTMLVideoElement& video =
static_cast<HTMLVideoElement&>(*HTMLVideoElement::Create(GetDocument()));
controller->SetPictureInPictureEnabledForTesting(false);
ScriptPromise promise =
HTMLVideoElementPictureInPicture::requestPictureInPicture(
GetScriptState(), video);
EXPECT_EQ(kNotSupportedString, GetRejectString(promise));
}
TEST_F(PictureInPictureTest,
PictureInPictureEnabledReturnsFalseWhenPictureInPictureEnabledIsFalse) {
Persistent<PictureInPictureController> controller =
PictureInPictureController::Ensure(GetDocument());
controller->SetPictureInPictureEnabledForTesting(false);
EXPECT_FALSE(controller->PictureInPictureEnabled());
}
} // namespace blink
...@@ -211,6 +211,9 @@ bool IsSupportedInFeaturePolicy(FeaturePolicyFeature feature) { ...@@ -211,6 +211,9 @@ bool IsSupportedInFeaturePolicy(FeaturePolicyFeature feature) {
case FeaturePolicyFeature::kAmbientLightSensor: case FeaturePolicyFeature::kAmbientLightSensor:
case FeaturePolicyFeature::kGyroscope: case FeaturePolicyFeature::kGyroscope:
case FeaturePolicyFeature::kMagnetometer: case FeaturePolicyFeature::kMagnetometer:
return true;
case FeaturePolicyFeature::kPictureInPicture:
return RuntimeEnabledFeatures::PictureInPictureAPIEnabled();
case FeaturePolicyFeature::kSyncXHR: case FeaturePolicyFeature::kSyncXHR:
return true; return true;
case FeaturePolicyFeature::kVibrate: case FeaturePolicyFeature::kVibrate:
...@@ -246,6 +249,10 @@ const FeatureNameMap& GetDefaultFeatureNameMap() { ...@@ -246,6 +249,10 @@ const FeatureNameMap& GetDefaultFeatureNameMap() {
default_feature_name_map.Set("gyroscope", FeaturePolicyFeature::kGyroscope); default_feature_name_map.Set("gyroscope", FeaturePolicyFeature::kGyroscope);
default_feature_name_map.Set("magnetometer", default_feature_name_map.Set("magnetometer",
FeaturePolicyFeature::kMagnetometer); FeaturePolicyFeature::kMagnetometer);
if (RuntimeEnabledFeatures::PictureInPictureAPIEnabled()) {
default_feature_name_map.Set("picture-in-picture",
FeaturePolicyFeature::kPictureInPicture);
}
if (RuntimeEnabledFeatures::FeaturePolicyExperimentalFeaturesEnabled()) { if (RuntimeEnabledFeatures::FeaturePolicyExperimentalFeaturesEnabled()) {
default_feature_name_map.Set("vibrate", FeaturePolicyFeature::kVibrate); default_feature_name_map.Set("vibrate", FeaturePolicyFeature::kVibrate);
default_feature_name_map.Set("cookie", default_feature_name_map.Set("cookie",
......
...@@ -827,6 +827,10 @@ ...@@ -827,6 +827,10 @@
name: "PictureInPicture", name: "PictureInPicture",
settable_from_internals: true, settable_from_internals: true,
}, },
{
name: "PictureInPictureAPI",
status: "test",
},
{ {
name: "PreciseMemoryInfo", name: "PreciseMemoryInfo",
}, },
......
...@@ -44,7 +44,7 @@ A step-to-step guide with examples. ...@@ -44,7 +44,7 @@ A step-to-step guide with examples.
##### Shipping features behind a flag ##### Shipping features behind a flag
There are currently two runtime-enabled flags: `FeaturePolicy` (status: There are currently two runtime-enabled flags: `FeaturePolicy` (status:
stable) and `FeaturePolicyExperiementalFeatures` (staus: experimental). stable) and `FeaturePolicyExperimentalFeatures` (status: experimental).
If the additional feature is unshipped, or if the correct behaviour with feature If the additional feature is unshipped, or if the correct behaviour with feature
policy is undetermined, consider shipping the feature behind a flag (i.e., policy is undetermined, consider shipping the feature behind a flag (i.e.,
`FeaturePolicyExperimentalFeatures`). `FeaturePolicyExperimentalFeatures`).
...@@ -55,7 +55,13 @@ policy is undetermined, consider shipping the feature behind a flag (i.e., ...@@ -55,7 +55,13 @@ policy is undetermined, consider shipping the feature behind a flag (i.e.,
enum with a brief decription about what the feature does in the comment, right enum with a brief decription about what the feature does in the comment, right
above `LAST_FEATURE` above `LAST_FEATURE`
2. Update `third_party/WebKit/Source/platform/feature_policy/FeaturePolicy.cpp`: 2. Append the new feature enum with a brief description as well in
`third_party/WebKit/common/feature_policy/feature_policy.mojom`
3. Update `third_party/WebKit/common/feature_policy/feature_policy_struct_traits.h`
to include the new feature
4. Update `third_party/WebKit/Source/platform/feature_policy/FeaturePolicy.cpp`:
Add your `("feature-name", FeatureEnumValue)` mapping to Add your `("feature-name", FeatureEnumValue)` mapping to
`GetDefaultFeatureNameMap()` (note: "feature-name" is the string web `GetDefaultFeatureNameMap()` (note: "feature-name" is the string web
developers will be using to define the policy in the HTTP header and iframe developers will be using to define the policy in the HTTP header and iframe
......
...@@ -279,7 +279,9 @@ const FeaturePolicy::FeatureList& FeaturePolicy::GetDefaultFeatureList() { ...@@ -279,7 +279,9 @@ const FeaturePolicy::FeatureList& FeaturePolicy::GetDefaultFeatureList() {
{FeaturePolicyFeature::kMagnetometer, {FeaturePolicyFeature::kMagnetometer,
FeaturePolicy::FeatureDefault::EnableForSelf}, FeaturePolicy::FeatureDefault::EnableForSelf},
{FeaturePolicyFeature::kUnsizedMedia, {FeaturePolicyFeature::kUnsizedMedia,
FeaturePolicy::FeatureDefault::EnableForAll}})); FeaturePolicy::FeatureDefault::EnableForAll},
{FeaturePolicyFeature::kPictureInPicture,
FeaturePolicy::FeatureDefault::EnableForSelf}}));
return default_feature_list; return default_feature_list;
} }
......
...@@ -84,6 +84,8 @@ enum FeaturePolicyFeature { ...@@ -84,6 +84,8 @@ enum FeaturePolicyFeature {
// Controls the layout size of intrinsically sized images and videos. When // Controls the layout size of intrinsically sized images and videos. When
// disabled, default size (300 x 150) is used to prevent relayout. // disabled, default size (300 x 150) is used to prevent relayout.
kUnsizedMedia, kUnsizedMedia,
// Controls access to Picture-in-Picture.
kPictureInPicture,
}; };
// This struct holds feature policy whitelist data that needs to be replicated // This struct holds feature policy whitelist data that needs to be replicated
......
...@@ -62,7 +62,9 @@ enum class FeaturePolicyFeature { ...@@ -62,7 +62,9 @@ enum class FeaturePolicyFeature {
// Controls the layout size of intrinsically sized images and videos. When // Controls the layout size of intrinsically sized images and videos. When
// disabled, default size (300 x 150) is used to prevent relayout. // disabled, default size (300 x 150) is used to prevent relayout.
kUnsizedMedia, kUnsizedMedia,
LAST_FEATURE = kUnsizedMedia // Controls access to Picture-in-Picture.
kPictureInPicture,
LAST_FEATURE = kPictureInPicture
}; };
} // namespace blink } // namespace blink
......
...@@ -65,6 +65,8 @@ STATIC_ASSERT_ENUM(::blink::FeaturePolicyFeature::kGyroscope, ...@@ -65,6 +65,8 @@ STATIC_ASSERT_ENUM(::blink::FeaturePolicyFeature::kGyroscope,
::blink::mojom::FeaturePolicyFeature::kGyroscope); ::blink::mojom::FeaturePolicyFeature::kGyroscope);
STATIC_ASSERT_ENUM(::blink::FeaturePolicyFeature::kMagnetometer, STATIC_ASSERT_ENUM(::blink::FeaturePolicyFeature::kMagnetometer,
::blink::mojom::FeaturePolicyFeature::kMagnetometer); ::blink::mojom::FeaturePolicyFeature::kMagnetometer);
STATIC_ASSERT_ENUM(::blink::FeaturePolicyFeature::kPictureInPicture,
::blink::mojom::FeaturePolicyFeature::kPictureInPicture);
// TODO(crbug.com/789818) - Merge these 2 WebSandboxFlags enums. // TODO(crbug.com/789818) - Merge these 2 WebSandboxFlags enums.
STATIC_ASSERT_ENUM(::blink::WebSandboxFlags::kNone, STATIC_ASSERT_ENUM(::blink::WebSandboxFlags::kNone,
......
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