Skip to content

Commit 1d76186

Browse files
committed
Bug 1858562 - Part 1: Implement Document Picture-in-Picture Spec. r=edgar,smaug,dom-core,webidl,firefox-style-system-reviewers,layout-reviewers,emilio
Differential Revision: https://phabricator.services.mozilla.com/D265290
1 parent 9525f21 commit 1d76186

File tree

64 files changed

+992
-215
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+992
-215
lines changed

docshell/base/BrowsingContext.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "mozilla/dom/ContentChild.h"
3131
#include "mozilla/dom/ContentParent.h"
3232
#include "mozilla/dom/Document.h"
33+
#include "mozilla/dom/DocumentPictureInPicture.h"
3334
#include "mozilla/dom/Element.h"
3435
#include "mozilla/dom/Geolocation.h"
3536
#include "mozilla/dom/HTMLEmbedElement.h"
@@ -2680,6 +2681,28 @@ void BrowsingContext::IncrementHistoryEntryCountForBrowsingContext() {
26802681
(void)SetHistoryEntryCount(GetHistoryEntryCount() + 1);
26812682
}
26822683

2684+
// https://wicg.github.io/document-picture-in-picture/#focusing-the-opener-window
2685+
static bool ConsumePiPWindowTransientActivation(nsPIDOMWindowOuter* outer) {
2686+
NS_ENSURE_TRUE(outer, false);
2687+
2688+
nsPIDOMWindowInner* inner = outer->GetCurrentInnerWindow();
2689+
NS_ENSURE_TRUE(inner, false);
2690+
2691+
DocumentPictureInPicture* dpip = inner->GetExtantDocumentPictureInPicture();
2692+
if (!dpip) {
2693+
return false;
2694+
}
2695+
nsGlobalWindowInner* pipWindow = dpip->GetWindow();
2696+
if (!pipWindow) {
2697+
return false;
2698+
}
2699+
2700+
WindowContext* wc = pipWindow->GetWindowContext();
2701+
NS_ENSURE_TRUE(wc, false);
2702+
2703+
return wc->ConsumeTransientUserGestureActivation();
2704+
}
2705+
26832706
std::tuple<bool, bool> BrowsingContext::CanFocusCheck(CallerType aCallerType) {
26842707
nsFocusManager* fm = nsFocusManager::GetFocusManager();
26852708
if (!fm) {
@@ -2703,6 +2726,13 @@ std::tuple<bool, bool> BrowsingContext::CanFocusCheck(CallerType aCallerType) {
27032726
PopupBlocker::openBlocked;
27042727
}
27052728

2729+
// Allow the opener to get system focus if the PIP window has transient
2730+
// activation
2731+
if (!canFocus && IsTopContent() &&
2732+
ConsumePiPWindowTransientActivation(GetDOMWindow())) {
2733+
canFocus = true;
2734+
}
2735+
27062736
bool isActive = false;
27072737
if (XRE_IsParentProcess()) {
27082738
CanonicalBrowsingContext* chromeTop = Canonical()->TopCrossChromeBoundary();

docshell/base/BrowsingContext.h

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,10 @@ struct EmbedderColorSchemes {
293293
FIELD(IPAddressSpace, nsILoadInfo::IPAddressSpace) \
294294
/* This is true if we should redirect to an error page when inserting * \
295295
* meta tags flagging adult content into our documents */ \
296-
FIELD(ParentalControlsEnabled, bool)
296+
FIELD(ParentalControlsEnabled, bool) \
297+
/* If true, this traversable is a Document Picture-in-Picture and \
298+
is subject to certain restrictions */ \
299+
FIELD(IsDocumentPiP, bool)
297300

298301
// BrowsingContext, in this context, is the cross process replicated
299302
// environment in which information about documents is stored. In
@@ -1485,6 +1488,10 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
14851488
return XRE_IsParentProcess();
14861489
}
14871490

1491+
bool CanSet(FieldIndex<IDX_IsDocumentPiP>, bool, ContentParent*) {
1492+
return IsTop();
1493+
}
1494+
14881495
// Overload `DidSet` to get notifications for a particular field being set.
14891496
//
14901497
// You can also overload the variant that gets the old value if you need it.
@@ -1506,6 +1513,14 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
15061513

15071514
void DidSet(FieldIndex<IDX_ForceOffline>, bool aOldValue);
15081515

1516+
void DidSet(FieldIndex<IDX_IsDocumentPiP>, bool aWasPiP) {
1517+
if (GetIsDocumentPiP() && !aWasPiP) {
1518+
SetDisplayMode(DisplayMode::Picture_in_picture, IgnoreErrors());
1519+
} else if (!GetIsDocumentPiP() && aWasPiP) {
1520+
MOZ_ASSERT_UNREACHABLE("BrowsingContext should never leave PiP mode");
1521+
}
1522+
}
1523+
15091524
// Allow if the process attemping to set field is the same as the owning
15101525
// process. Deprecated. New code that might use this should generally be moved
15111526
// to WindowContext or be settable only by the parent process.

docshell/base/WindowContext.cpp

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "mozilla/dom/BrowsingContext.h"
1313
#include "mozilla/dom/CloseWatcherManager.h"
1414
#include "mozilla/dom/Document.h"
15+
#include "mozilla/dom/DocumentPictureInPicture.h"
1516
#include "mozilla/dom/UserActivationIPCUtils.h"
1617
#include "mozilla/dom/WorkerCommon.h"
1718
#include "mozilla/PermissionDelegateIPCUtils.h"
@@ -614,6 +615,44 @@ bool WindowContext::HasValidTransientUserGestureActivation() {
614615
(TimeStamp::Now() - mLastActivationTimestamp) <= timeout;
615616
}
616617

618+
template <typename F>
619+
static void ConsumeUserGestureActivationBetweenPiP(BrowsingContext* aTop,
620+
F&& aCallback) {
621+
// https://wicg.github.io/document-picture-in-picture/#user-activation-propagation
622+
// Monkey patch to consume user activation
623+
if (aTop->GetIsDocumentPiP()) {
624+
// 4. If top is a PIP window, then extend navigables with the opener
625+
// window's inclusive decendant navigables
626+
RefPtr<BrowsingContext> opener = aTop->GetOpener();
627+
if (!opener) {
628+
return;
629+
}
630+
opener->GetBrowsingContext()->PreOrderWalk(aCallback);
631+
} else {
632+
// 5. Get top-level navigable's last opened PiP window
633+
nsPIDOMWindowOuter* outer = aTop->GetDOMWindow();
634+
NS_ENSURE_TRUE_VOID(outer);
635+
nsPIDOMWindowInner* inner = outer->GetCurrentInnerWindow();
636+
NS_ENSURE_TRUE_VOID(inner);
637+
DocumentPictureInPicture* dpip = inner->GetExtantDocumentPictureInPicture();
638+
if (!dpip) {
639+
return;
640+
}
641+
nsGlobalWindowInner* pip = dpip->GetWindow();
642+
if (!pip) {
643+
return;
644+
}
645+
646+
// 6. Extend navigables with the inclusive descendant navigables of the PIP
647+
// window.
648+
BrowsingContext* pipBC = pip->GetBrowsingContext();
649+
NS_ENSURE_TRUE_VOID(pipBC);
650+
WindowContext* pipWC = pipBC->GetCurrentWindowContext();
651+
NS_ENSURE_TRUE_VOID(pipWC);
652+
pipBC->PreOrderWalk(aCallback);
653+
}
654+
}
655+
617656
// https://html.spec.whatwg.org/#consume-user-activation
618657
bool WindowContext::ConsumeTransientUserGestureActivation() {
619658
MOZ_ASSERT(IsInProcess());
@@ -631,7 +670,7 @@ bool WindowContext::ConsumeTransientUserGestureActivation() {
631670

632671
// 3. Let navigables be the inclusive descendant navigables of top's active
633672
// document.
634-
top->PreOrderWalk([&](BrowsingContext* aBrowsingContext) {
673+
auto callback = [&](BrowsingContext* aBrowsingContext) {
635674
// 4. Let windows be the list of Window objects constructed by taking the
636675
// active window of each item in navigables.
637676
WindowContext* windowContext = aBrowsingContext->GetCurrentWindowContext();
@@ -650,7 +689,10 @@ bool WindowContext::ConsumeTransientUserGestureActivation() {
650689
(void)windowContext->SetUserActivationStateAndModifiers(
651690
stateAndModifiers.GetRawData());
652691
}
653-
});
692+
};
693+
top->PreOrderWalk(callback);
694+
695+
ConsumeUserGestureActivationBetweenPiP(top, callback);
654696

655697
return true;
656698
}

docshell/base/nsDocShell.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10929,6 +10929,19 @@ nsresult nsDocShell::DoURILoad(nsDocShellLoadState* aLoadState,
1092910929
aLoadState->PrincipalToInherit()) &&
1093010930
!shouldSkipSyncLoadForSHRestore();
1093110931

10932+
if (!isAboutBlankLoadOntoInitialAboutBlank) {
10933+
// https://wicg.github.io/document-picture-in-picture/#close-on-navigate
10934+
if (Document* doc = GetExtantDocument()) {
10935+
NS_DispatchToMainThread(NS_NewRunnableFunction(
10936+
"Close PIP window on navigate", [doc = RefPtr(doc)]() {
10937+
doc->CloseAnyAssociatedDocumentPiPWindows();
10938+
}));
10939+
}
10940+
if (GetBrowsingContext()->GetIsDocumentPiP()) {
10941+
return NS_OK;
10942+
}
10943+
}
10944+
1093210945
// FIXME We still have a ton of codepaths that don't pass through
1093310946
// DocumentLoadListener, so probably need to create session history info
1093410947
// in more places.

dom/base/Document.cpp

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@
162162
#include "mozilla/dom/DocumentFragment.h"
163163
#include "mozilla/dom/DocumentInlines.h"
164164
#include "mozilla/dom/DocumentL10n.h"
165+
#include "mozilla/dom/DocumentPictureInPicture.h"
165166
#include "mozilla/dom/DocumentTimeline.h"
166167
#include "mozilla/dom/DocumentType.h"
167168
#include "mozilla/dom/ElementBinding.h"
@@ -12269,6 +12270,35 @@ bool Document::CanSavePresentation(nsIRequest* aNewRequest,
1226912270
return ret;
1227012271
}
1227112272

12273+
// https://wicg.github.io/document-picture-in-picture/#close-any-associated-document-picture-in-picture-windows
12274+
void Document::CloseAnyAssociatedDocumentPiPWindows() {
12275+
BrowsingContext* bc = GetBrowsingContext();
12276+
if (!bc || !bc->IsTop()) {
12277+
return;
12278+
}
12279+
12280+
// 3. Close us if we're a PIP window
12281+
// Note that this method is called when the opener or pip document is
12282+
// destroyed, which might mean the PiP is already closing.
12283+
if (bc->GetIsDocumentPiP() && !bc->GetClosed()) {
12284+
if (IsUncommittedInitialDocument()) {
12285+
// Don't close us if we're just doing the initial about:blank load.
12286+
return;
12287+
}
12288+
return bc->Close(CallerType::System, IgnoreErrors());
12289+
}
12290+
12291+
// 4,5. Close a PIP window opened by us
12292+
if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
12293+
if (DocumentPictureInPicture* dpip =
12294+
inner->GetExtantDocumentPictureInPicture()) {
12295+
if (RefPtr<nsGlobalWindowInner> pipWindow = dpip->GetWindow()) {
12296+
pipWindow->Close();
12297+
}
12298+
}
12299+
}
12300+
}
12301+
1227212302
void Document::Destroy() {
1227312303
// The DocumentViewer wants to release the document now. So, tell our content
1227412304
// to drop any references to the document so that it can be destroyed.
@@ -12679,6 +12709,9 @@ void Document::OnPageHide(bool aPersisted, EventTarget* aDispatchStartTarget,
1267912709
mVisible = false;
1268012710
}
1268112711

12712+
// https://wicg.github.io/document-picture-in-picture/#close-on-destroy
12713+
CloseAnyAssociatedDocumentPiPWindows();
12714+
1268212715
PointerLockManager::Unlock("Document::OnPageHide", this);
1268312716

1268412717
if (!mIsBeingUsedAsImage) {
@@ -16450,6 +16483,12 @@ const char* Document::GetFullscreenError(CallerType aCallerType) {
1645016483
return "FullscreenDeniedDisabled";
1645116484
}
1645216485

16486+
BrowsingContext* bc = GetBrowsingContext();
16487+
// https://github.com/WICG/document-picture-in-picture/issues/133
16488+
if (!bc || bc->Top()->GetIsDocumentPiP()) {
16489+
return "FullscreenDeniedPiP";
16490+
}
16491+
1645316492
if (aCallerType == CallerType::System) {
1645416493
// Chrome code can always use the fullscreen API, provided it's not
1645516494
// explicitly disabled.
@@ -16466,8 +16505,7 @@ const char* Document::GetFullscreenError(CallerType aCallerType) {
1646616505

1646716506
// Ensure that all containing elements are <iframe> and have allowfullscreen
1646816507
// attribute set.
16469-
BrowsingContext* bc = GetBrowsingContext();
16470-
if (!bc || !bc->FullscreenAllowed()) {
16508+
if (!bc->FullscreenAllowed()) {
1647116509
return "FullscreenDeniedContainerNotAllowed";
1647216510
}
1647316511

@@ -18369,6 +18407,62 @@ BrowsingContext* Document::GetBrowsingContext() const {
1836918407
: nullptr;
1837018408
}
1837118409

18410+
static void PropagateUserGestureActivationBetweenPiP(
18411+
BrowsingContext* currentBC, UserActivation::Modifiers aModifiers) {
18412+
// https://wicg.github.io/document-picture-in-picture/#user-activation-propagation
18413+
// Monkey patch to activation notification
18414+
if (currentBC->Top()->GetIsDocumentPiP()) {
18415+
// 5. If we are in a PIP window, give transient activation to the opener
18416+
// window
18417+
// This means activation in a cross-origin subframe in the PIP window
18418+
// will cause the opener to get activation.
18419+
RefPtr<BrowsingContext> opener = currentBC->Top()->GetOpener();
18420+
if (!opener) {
18421+
return;
18422+
}
18423+
WindowContext* wc = opener->GetCurrentWindowContext();
18424+
NS_ENSURE_TRUE_VOID(wc);
18425+
wc->NotifyUserGestureActivation(aModifiers);
18426+
} else {
18427+
// 6. Get top-level navigable's last opened PiP window
18428+
// this means activation in a cross-origin subframe in the opener will
18429+
// cause the PIP window to get activation.
18430+
nsPIDOMWindowOuter* outer = currentBC->Top()->GetDOMWindow();
18431+
NS_ENSURE_TRUE_VOID(outer);
18432+
nsPIDOMWindowInner* inner = outer->GetCurrentInnerWindow();
18433+
NS_ENSURE_TRUE_VOID(inner);
18434+
DocumentPictureInPicture* dpip = inner->GetExtantDocumentPictureInPicture();
18435+
if (!dpip) {
18436+
return;
18437+
}
18438+
nsGlobalWindowInner* pip = dpip->GetWindow();
18439+
if (!pip) {
18440+
return;
18441+
}
18442+
18443+
// 7. Give transient activation to the pip window and it's same origin
18444+
// descendants
18445+
BrowsingContext* pipBC = pip->GetBrowsingContext();
18446+
NS_ENSURE_TRUE_VOID(pipBC);
18447+
WindowContext* pipWC = pipBC->GetCurrentWindowContext();
18448+
NS_ENSURE_TRUE_VOID(pipWC);
18449+
pipBC->PreOrderWalk([&](BrowsingContext* bc) {
18450+
WindowContext* wc = bc->GetCurrentWindowContext();
18451+
if (!wc) {
18452+
return;
18453+
}
18454+
18455+
// Check same-origin as current document
18456+
WindowGlobalChild* wgc = wc->GetWindowGlobalChild();
18457+
if (!wgc || !wgc->IsSameOriginWith(pipWC)) {
18458+
return;
18459+
}
18460+
18461+
wc->NotifyUserGestureActivation(aModifiers);
18462+
});
18463+
}
18464+
}
18465+
1837218466
void Document::NotifyUserGestureActivation(
1837318467
UserActivation::Modifiers
1837418468
aModifiers /* = UserActivation::Modifiers::None() */) {
@@ -18415,6 +18509,8 @@ void Document::NotifyUserGestureActivation(
1841518509
wc->NotifyUserGestureActivation(aModifiers);
1841618510
});
1841718511

18512+
PropagateUserGestureActivationBetweenPiP(currentBC, aModifiers);
18513+
1841818514
// If there has been a user activation, mark the current session history entry
1841918515
// as having been interacted with.
1842018516
SetSHEntryHasUserInteraction(true);

dom/base/Document.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2506,6 +2506,9 @@ class Document : public nsINode,
25062506
*/
25072507
virtual void Destroy();
25082508

2509+
// https://wicg.github.io/document-picture-in-picture/#close-on-destroy
2510+
void CloseAnyAssociatedDocumentPiPWindows();
2511+
25092512
/**
25102513
* Notify the document that its associated DocumentViewer is no longer
25112514
* the current viewer for the docshell. The document might still

dom/base/nsGlobalWindowInner.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@
119119
#include "mozilla/dom/DocGroup.h"
120120
#include "mozilla/dom/Document.h"
121121
#include "mozilla/dom/DocumentInlines.h"
122+
#include "mozilla/dom/DocumentPictureInPicture.h"
122123
#include "mozilla/dom/Element.h"
123124
#include "mozilla/dom/Event.h"
124125
#include "mozilla/dom/EventTarget.h"
@@ -1273,6 +1274,7 @@ void nsGlobalWindowInner::FreeInnerObjects() {
12731274

12741275
mConsole = nullptr;
12751276
mCookieStore = nullptr;
1277+
mDocumentPiP = nullptr;
12761278

12771279
mPaintWorklet = nullptr;
12781280

@@ -1468,6 +1470,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindowInner)
14681470
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCrypto)
14691471
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsole)
14701472
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCookieStore)
1473+
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentPiP)
14711474
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPaintWorklet)
14721475
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mExternal)
14731476
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIntlUtils)
@@ -7403,6 +7406,14 @@ already_AddRefed<CookieStore> nsGlobalWindowInner::CookieStore() {
74037406
return do_AddRef(mCookieStore);
74047407
}
74057408

7409+
DocumentPictureInPicture* nsGlobalWindowInner::DocumentPictureInPicture() {
7410+
if (!mDocumentPiP) {
7411+
mDocumentPiP = MakeRefPtr<class DocumentPictureInPicture>(this);
7412+
}
7413+
7414+
return mDocumentPiP;
7415+
}
7416+
74067417
bool nsGlobalWindowInner::IsSecureContext() const {
74077418
JS::Realm* realm = js::GetNonCCWObjectRealm(GetWrapperPreserveColor());
74087419
return JS::GetIsSecureContext(realm);

0 commit comments

Comments
 (0)