Bug 1430305 - Implement ShadowRoot.fullscreenElement , r=mrbkap
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Tue, 13 Feb 2018 18:57:32 +0200
changeset 754890 0babcbf5ff1e263916684965996092fc7600364e
parent 754889 af441cafff7086ffdd0ea8c4700f738fdf3261b9
child 754891 c47ba61ce442211227ccf909eee6101841e5aa94
push id99032
push userbmo:rail@mozilla.com
push dateWed, 14 Feb 2018 14:28:48 +0000
reviewersmrbkap
bugs1430305
milestone60.0a1
Bug 1430305 - Implement ShadowRoot.fullscreenElement , r=mrbkap
dom/base/DocumentOrShadowRoot.cpp
dom/base/DocumentOrShadowRoot.h
dom/base/nsDocument.cpp
dom/base/nsDocument.h
dom/base/nsIDocument.h
dom/html/test/file_fullscreen-shadowdom.html
dom/html/test/mochitest.ini
dom/html/test/test_fullscreen-api.html
dom/webidl/Document.webidl
dom/webidl/DocumentOrShadowRoot.webidl
--- a/dom/base/DocumentOrShadowRoot.cpp
+++ b/dom/base/DocumentOrShadowRoot.cpp
@@ -141,10 +141,30 @@ DocumentOrShadowRoot::GetPointerLockElem
   }
 
   nsIContent* retargetedPointerLockedElement = Retarget(pointerLockedElement);
   return
     retargetedPointerLockedElement && retargetedPointerLockedElement->IsElement() ?
       retargetedPointerLockedElement->AsElement() : nullptr;
 }
 
+Element*
+DocumentOrShadowRoot::GetFullscreenElement()
+{
+  if (!AsNode().IsInComposedDoc()) {
+    return nullptr;
+  }
+
+  Element* element = AsNode().OwnerDoc()->FullScreenStackTop();
+  NS_ASSERTION(!element ||
+               element->State().HasState(NS_EVENT_STATE_FULL_SCREEN),
+    "Fullscreen element should have fullscreen styles applied");
+
+  nsIContent* retargeted = Retarget(element);
+  if (retargeted && retargeted->IsElement()) {
+    return retargeted->AsElement();
+  }
+
+  return nullptr;
+}
+
 }
 }
--- a/dom/base/DocumentOrShadowRoot.h
+++ b/dom/base/DocumentOrShadowRoot.h
@@ -109,16 +109,17 @@ public:
                          mozilla::ErrorResult&);
 
   already_AddRefed<nsContentList>
   GetElementsByClassName(const nsAString& aClasses);
 
   ~DocumentOrShadowRoot() = default;
 
   Element* GetPointerLockElement();
+  Element* GetFullscreenElement();
 protected:
   nsIContent* Retarget(nsIContent* aContent) const;
 
   /**
    * If focused element's subtree root is this document or shadow root, return
    * focused element, otherwise, get the shadow host recursively until the
    * shadow host's subtree root is this document or shadow root.
    */
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -8812,17 +8812,17 @@ nsDocument::OnPageHide(bool aPersisted,
   mVisible = false;
 
   UpdateVisibilityState();
 
   EnumerateExternalResources(NotifyPageHide, &aPersisted);
   EnumerateActivityObservers(NotifyActivityChanged, nullptr);
 
   ClearPendingFullscreenRequests(this);
-  if (GetFullscreenElement()) {
+  if (FullScreenStackTop()) {
     // If this document was fullscreen, we should exit fullscreen in this
     // doctree branch. This ensures that if the user navigates while in
     // fullscreen mode we don't leave its still visible ancestor documents
     // in fullscreen mode. So exit fullscreen in the document's fullscreen
     // root document, as this will exit fullscreen in all the root's
     // descendant documents. Note that documents are removed from the
     // doctree by the time OnPageHide() is called, so we must store a
     // reference to the root (in nsDocument::mFullscreenRoot) since we can't
@@ -10614,17 +10614,17 @@ nsIDocument::AsyncExitFullscreen(nsIDocu
   } else {
     NS_DispatchToCurrentThread(exit.forget());
   }
 }
 
 static bool
 CountFullscreenSubDocuments(nsIDocument* aDoc, void* aData)
 {
-  if (aDoc->GetFullscreenElement()) {
+  if (aDoc->FullScreenStackTop()) {
     uint32_t* count = static_cast<uint32_t*>(aData);
     (*count)++;
   }
   return true;
 }
 
 static uint32_t
 CountFullscreenSubDocuments(nsIDocument* aDoc)
@@ -10634,30 +10634,30 @@ CountFullscreenSubDocuments(nsIDocument*
   return count;
 }
 
 bool
 nsDocument::IsFullscreenLeaf()
 {
   // A fullscreen leaf document is fullscreen, and has no fullscreen
   // subdocuments.
-  if (!GetFullscreenElement()) {
+  if (!FullScreenStackTop()) {
     return false;
   }
   return CountFullscreenSubDocuments(this) == 0;
 }
 
 static bool
 ResetFullScreen(nsIDocument* aDocument, void* aData)
 {
-  if (aDocument->GetFullscreenElement()) {
+  if (aDocument->FullScreenStackTop()) {
     NS_ASSERTION(CountFullscreenSubDocuments(aDocument) <= 1,
         "Should have at most 1 fullscreen subdocument.");
     static_cast<nsDocument*>(aDocument)->CleanupFullscreenState();
-    NS_ASSERTION(!aDocument->GetFullscreenElement(),
+    NS_ASSERTION(!aDocument->FullScreenStackTop(),
                  "Should reset full-screen");
     auto changed = reinterpret_cast<nsCOMArray<nsIDocument>*>(aData);
     changed->AppendElement(aDocument);
     aDocument->EnumerateSubDocuments(ResetFullScreen, aData);
   }
   return true;
 }
 
@@ -10698,17 +10698,17 @@ private:
 nsIDocument::ExitFullscreenInDocTree(nsIDocument* aMaybeNotARootDoc)
 {
   MOZ_ASSERT(aMaybeNotARootDoc);
 
   // Unlock the pointer
   UnlockPointer();
 
   nsCOMPtr<nsIDocument> root = aMaybeNotARootDoc->GetFullscreenRoot();
-  if (!root || !root->GetFullscreenElement()) {
+  if (!root || !root->FullScreenStackTop()) {
     // If a document was detached before exiting from fullscreen, it is
     // possible that the root had left fullscreen state. In this case,
     // we would not get anything from the ResetFullScreen() call. Root's
     // not being a fullscreen doc also means the widget should have
     // exited fullscreen state. It means even if we do not return here,
     // we would actually do nothing below except crashing ourselves via
     // dispatching the "MozDOMFullscreen:Exited" event to an nonexistent
     // document.
@@ -10727,34 +10727,34 @@ nsIDocument::ExitFullscreenInDocTree(nsI
 
   // Dispatch "fullscreenchange" events. Note this loop is in reverse
   // order so that the events for the leaf document arrives before the root
   // document, as required by the spec.
   for (uint32_t i = 0; i < changed.Length(); ++i) {
     DispatchFullScreenChange(changed[changed.Length() - i - 1]);
   }
 
-  NS_ASSERTION(!root->GetFullscreenElement(),
+  NS_ASSERTION(!root->FullScreenStackTop(),
     "Fullscreen root should no longer be a fullscreen doc...");
 
   // Move the top-level window out of fullscreen mode.
   FullscreenRoots::Remove(root);
 
   nsContentUtils::AddScriptRunner(
     new ExitFullscreenScriptRunnable(Move(changed)));
 }
 
 bool
 GetFullscreenLeaf(nsIDocument* aDoc, void* aData)
 {
   if (aDoc->IsFullscreenLeaf()) {
     nsIDocument** result = static_cast<nsIDocument**>(aData);
     *result = aDoc;
     return false;
-  } else if (aDoc->GetFullscreenElement()) {
+  } else if (aDoc->FullScreenStackTop()) {
     aDoc->EnumerateSubDocuments(GetFullscreenLeaf, aData);
   }
   return true;
 }
 
 static nsIDocument*
 GetFullscreenLeaf(nsIDocument* aDoc)
 {
@@ -10763,30 +10763,30 @@ GetFullscreenLeaf(nsIDocument* aDoc)
   if (leaf) {
     return leaf;
   }
   // Otherwise we could be either in a non-fullscreen doc tree, or we're
   // below the fullscreen doc. Start the search from the root.
   nsIDocument* root = nsContentUtils::GetRootDocument(aDoc);
   // Check that the root is actually fullscreen so we don't waste time walking
   // around its descendants.
-  if (!root->GetFullscreenElement()) {
+  if (!root->FullScreenStackTop()) {
     return nullptr;
   }
   GetFullscreenLeaf(root, &leaf);
   return leaf;
 }
 
 void
 nsDocument::RestorePreviousFullScreenState()
 {
-  NS_ASSERTION(!GetFullscreenElement() || !FullscreenRoots::IsEmpty(),
+  NS_ASSERTION(!FullScreenStackTop() || !FullscreenRoots::IsEmpty(),
     "Should have at least 1 fullscreen root when fullscreen!");
 
-  if (!GetFullscreenElement() || !GetWindow() || FullscreenRoots::IsEmpty()) {
+  if (!FullScreenStackTop() || !GetWindow() || FullscreenRoots::IsEmpty()) {
     return;
   }
 
   nsCOMPtr<nsIDocument> fullScreenDoc = GetFullscreenLeaf(this);
   AutoTArray<nsDocument*, 8> exitDocs;
 
   nsIDocument* doc = fullScreenDoc;
   // Collect all subdocuments.
@@ -10955,17 +10955,17 @@ nsDocument::FullScreenStackPush(Element*
 {
   NS_ASSERTION(aElement, "Must pass non-null to FullScreenStackPush()");
   Element* top = FullScreenStackTop();
   if (top == aElement || !aElement) {
     return false;
   }
   EventStateManager::SetFullScreenState(aElement, true);
   mFullScreenStack.AppendElement(do_GetWeakReference(aElement));
-  NS_ASSERTION(GetFullscreenElement() == aElement, "Should match");
+  NS_ASSERTION(FullScreenStackTop() == aElement, "Should match");
   UpdateViewportScrollbarOverrideForFullscreen(this);
   return true;
 }
 
 void
 nsDocument::FullScreenStackPop()
 {
   if (mFullScreenStack.IsEmpty()) {
@@ -11003,17 +11003,17 @@ Element*
 nsDocument::FullScreenStackTop()
 {
   if (mFullScreenStack.IsEmpty()) {
     return nullptr;
   }
   uint32_t last = mFullScreenStack.Length() - 1;
   nsCOMPtr<Element> element(do_QueryReferent(mFullScreenStack[last]));
   NS_ASSERTION(element, "Should have full-screen element!");
-  NS_ASSERTION(element->IsInUncomposedDoc(), "Full-screen element should be in doc");
+  NS_ASSERTION(element->IsInComposedDoc(), "Full-screen element should be in doc");
   NS_ASSERTION(element->OwnerDoc() == this, "Full-screen element should be in this doc");
   return element;
 }
 
 /* virtual */ nsTArray<Element*>
 nsDocument::GetFullscreenStack() const
 {
   nsTArray<Element*> elements;
@@ -11081,17 +11081,17 @@ nsresult nsDocument::RemoteFrameFullscre
 
 nsresult nsDocument::RemoteFrameFullscreenReverted()
 {
   RestorePreviousFullScreenState();
   return NS_OK;
 }
 
 /* static */ bool
-nsDocument::IsUnprefixedFullscreenEnabled(JSContext* aCx, JSObject* aObject)
+nsIDocument::IsUnprefixedFullscreenEnabled(JSContext* aCx, JSObject* aObject)
 {
   MOZ_ASSERT(NS_IsMainThread());
   return nsContentUtils::IsSystemCaller(aCx) ||
          nsContentUtils::IsUnprefixedFullscreenApiEnabled();
 }
 
 static bool
 HasFullScreenSubDocument(nsIDocument* aDoc)
@@ -11128,20 +11128,20 @@ GetFullscreenError(nsIDocument* aDoc, bo
 }
 
 bool
 nsDocument::FullscreenElementReadyCheck(Element* aElement,
                                         bool aWasCallerChrome)
 {
   NS_ASSERTION(aElement,
     "Must pass non-null element to nsDocument::RequestFullScreen");
-  if (!aElement || aElement == GetFullscreenElement()) {
+  if (!aElement || aElement == FullScreenStackTop()) {
     return false;
   }
-  if (!aElement->IsInUncomposedDoc()) {
+  if (!aElement->IsInComposedDoc()) {
     DispatchFullscreenError("FullscreenDeniedNotInDocument");
     return false;
   }
   if (aElement->OwnerDoc() != this) {
     DispatchFullscreenError("FullscreenDeniedMovedDocument");
     return false;
   }
   if (!GetWindow()) {
@@ -11155,18 +11155,21 @@ nsDocument::FullscreenElementReadyCheck(
   if (!IsVisible()) {
     DispatchFullscreenError("FullscreenDeniedHidden");
     return false;
   }
   if (HasFullScreenSubDocument(this)) {
     DispatchFullscreenError("FullscreenDeniedSubDocFullScreen");
     return false;
   }
-  if (GetFullscreenElement() &&
-      !nsContentUtils::ContentIsDescendantOf(aElement, GetFullscreenElement())) {
+  //XXXsmaug Note, we don't follow the latest fullscreen spec here.
+  //         This whole check could be probably removed.
+  if (FullScreenStackTop() &&
+      !nsContentUtils::ContentIsHostIncludingDescendantOf(aElement,
+                                                          FullScreenStackTop())) {
     // If this document is full-screen, only grant full-screen requests from
     // a descendant of the current full-screen element.
     DispatchFullscreenError("FullscreenDeniedNotDescendant");
     return false;
   }
   if (!nsContentUtils::IsChromeDoc(this) && !IsInActiveTab(this)) {
     DispatchFullscreenError("FullscreenDeniedNotFocusedTab");
     return false;
@@ -11507,26 +11510,16 @@ nsDocument::ApplyFullscreen(const Fullsc
   // order so that the events for the root document arrives before the leaf
   // document, as required by the spec.
   for (uint32_t i = 0; i < changed.Length(); ++i) {
     DispatchFullScreenChange(changed[changed.Length() - i - 1]);
   }
   return true;
 }
 
-Element*
-nsDocument::GetFullscreenElement()
-{
-  Element* element = FullScreenStackTop();
-  NS_ASSERTION(!element ||
-               element->State().HasState(NS_EVENT_STATE_FULL_SCREEN),
-    "Fullscreen element should have fullscreen styles applied");
-  return element;
-}
-
 bool
 nsDocument::FullscreenEnabled(CallerType aCallerType)
 {
   return !GetFullscreenError(this, aCallerType == CallerType::System);
 }
 
 uint16_t
 nsDocument::CurrentOrientationAngle() const
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -878,18 +878,16 @@ public:
   // Returns strong references to mBlockedTrackingNodes. (nsIDocument.h)
   //
   // This array contains nodes that have been blocked to prevent
   // user tracking. They most likely have had their nsIChannel
   // canceled by the URL classifier (Safebrowsing).
   //
   already_AddRefed<nsSimpleContentList> BlockedTrackingNodes() const;
 
-  static bool IsUnprefixedFullscreenEnabled(JSContext* aCx, JSObject* aObject);
-
   // Do the "fullscreen element ready check" from the fullscreen spec.
   // It returns true if the given element is allowed to go into fullscreen.
   bool FullscreenElementReadyCheck(Element* aElement, bool aWasCallerChrome);
 
   // This is called asynchronously by nsIDocument::AsyncRequestFullScreen()
   // to move this document into full-screen mode if allowed.
   void RequestFullScreen(mozilla::UniquePtr<FullscreenRequest>&& aRequest);
 
@@ -903,21 +901,20 @@ public:
   bool FullScreenStackPush(Element* aElement);
 
   // Remove the top element from the full-screen stack. Removes the full-screen
   // styles from the former top element, and applies them to the new top
   // element, if there is one.
   void FullScreenStackPop();
 
   // Returns the top element from the full-screen stack.
-  Element* FullScreenStackTop();
+  Element* FullScreenStackTop() override;
 
   // DOM-exposed fullscreen API
   bool FullscreenEnabled(mozilla::dom::CallerType aCallerType) override;
-  Element* GetFullscreenElement() override;
 
   virtual bool AllowPaymentRequest() const override;
   virtual void SetAllowPaymentRequest(bool aIsAllowPaymentRequest) override;
 
   void RequestPointerLock(Element* aElement,
                           mozilla::dom::CallerType aCallerType) override;
   bool SetPointerLock(Element* aElement, int aCursorStyle);
   static void UnlockPointer(nsIDocument* aDoc = nullptr);
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -2921,26 +2921,29 @@ public:
   }
   Element* GetCurrentScript();
   void ReleaseCapture() const;
   virtual void MozSetImageElement(const nsAString& aImageElementId,
                                   Element* aElement) = 0;
   nsIURI* GetDocumentURIObject() const;
   // Not const because all the full-screen goop is not const
   virtual bool FullscreenEnabled(mozilla::dom::CallerType aCallerType) = 0;
-  virtual Element* GetFullscreenElement() = 0;
+  virtual Element* FullScreenStackTop() = 0;
   bool Fullscreen()
   {
     return !!GetFullscreenElement();
   }
   void ExitFullscreen();
   void ExitPointerLock()
   {
     UnlockPointer(this);
   }
+
+  static bool IsUnprefixedFullscreenEnabled(JSContext* aCx, JSObject* aObject);
+
 #ifdef MOZILLA_INTERNAL_API
   bool Hidden() const
   {
     return mVisibilityState != mozilla::dom::VisibilityState::Visible;
   }
   mozilla::dom::VisibilityState VisibilityState() const
   {
     return mVisibilityState;
new file mode 100644
--- /dev/null
+++ b/dom/html/test/file_fullscreen-shadowdom.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+  <!--
+  https://bugzilla.mozilla.org/show_bug.cgi?id=1430305
+  Bug 1430305 - Implement ShadowRoot.fullscreenElement
+  -->
+  <head>
+    <title>Bug 1430305</title>
+    <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js">
+    </script>
+    <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js">
+    </script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  </head>
+  <body>
+    <a target="_blank"
+       href="https://bugzilla.mozilla.org/show_bug.cgi?id=1430305">
+      Mozilla Bug 1430305</a>
+
+    <div id="host"></div>
+
+    <pre id="test">
+      <script type="application/javascript">
+
+        function begin() {
+          var host = document.getElementById("host");
+          var shadowRoot = host.attachShadow({mode: "open"});
+          shadowRoot.innerHTML = "<div>test</div>";
+          var elem = shadowRoot.firstChild;
+          var gotFullscreenEvent = false;
+
+          document.addEventListener("fullscreenchange", function (e) {
+            if (document.fullscreenElement === host) {
+              is(shadowRoot.fullscreenElement, elem,
+                 "Expected element entered fullsceen");
+              gotFullscreenEvent = true;
+              document.exitFullscreen();
+            } else {
+              opener.ok(gotFullscreenEvent, "Entered fullscreen as expected");
+              is(shadowRoot.fullscreenElement, null,
+                 "Shouldn't have fullscreenElement anymore.");
+              is(document.fullscreenElement, null,
+                 "Shouldn't have fullscreenElement anymore.");
+              opener.nextTest();
+            }
+          });
+          elem.requestFullscreen();
+        }
+      </script>
+    </pre>
+  </body>
+</html>
--- a/dom/html/test/mochitest.ini
+++ b/dom/html/test/mochitest.ini
@@ -454,16 +454,17 @@ support-files =
   file_fullscreen-multiple.html
   file_fullscreen-navigation.html
   file_fullscreen-nested.html
   file_fullscreen-prefixed.html
   file_fullscreen-plugins.html
   file_fullscreen-rollback.html
   file_fullscreen-scrollbar.html
   file_fullscreen-selector.html
+  file_fullscreen-shadowdom.html
   file_fullscreen-svg-element.html
   file_fullscreen-table.html
   file_fullscreen-top-layer.html
   file_fullscreen-unprefix-disabled-inner.html
   file_fullscreen-unprefix-disabled.html
   file_fullscreen-utils.js
 [test_fullscreen-api-race.html]
 tags = fullscreen
--- a/dom/html/test/test_fullscreen-api.html
+++ b/dom/html/test/test_fullscreen-api.html
@@ -33,16 +33,17 @@ var gTestWindows = [
   "file_fullscreen-denied.html",
   "file_fullscreen-api.html",
   "file_fullscreen-plugins.html",
   "file_fullscreen-hidden.html",
   "file_fullscreen-svg-element.html",
   "file_fullscreen-navigation.html",
   "file_fullscreen-scrollbar.html",
   "file_fullscreen-selector.html",
+  "file_fullscreen-shadowdom.html",
   "file_fullscreen-top-layer.html",
   "file_fullscreen-backdrop.html",
   "file_fullscreen-nested.html",
   "file_fullscreen-prefixed.html",
   "file_fullscreen-unprefix-disabled.html",
   "file_fullscreen-lenient-setters.html",
   "file_fullscreen-table.html",
 ];
@@ -141,16 +142,17 @@ is(window.fullScreen, false, "Shouldn't 
 // to write
 addLoadEvent(function() {
   SpecialPowers.pushPrefEnv({
       "set": [
         ["full-screen-api.enabled", true],
         ["full-screen-api.unprefix.enabled", true],
         ["full-screen-api.allow-trusted-requests-only", false],
         ["full-screen-api.transition-duration.enter", "0 0"],
-        ["full-screen-api.transition-duration.leave", "0 0"]
+        ["full-screen-api.transition-duration.leave", "0 0"],
+        ["dom.webcomponents.shadowdom.enabled", true]
       ]}, nextTest);
 });
 SimpleTest.waitForExplicitFinish();
 </script>
 </pre>
 </body>
 </html>
--- a/dom/webidl/Document.webidl
+++ b/dom/webidl/Document.webidl
@@ -237,20 +237,16 @@ partial interface Document {
   [LenientSetter, Func="nsDocument::IsUnprefixedFullscreenEnabled"]
   readonly attribute boolean fullscreen;
   [BinaryName="fullscreen"]
   readonly attribute boolean mozFullScreen;
   [LenientSetter, Func="nsDocument::IsUnprefixedFullscreenEnabled", NeedsCallerType]
   readonly attribute boolean fullscreenEnabled;
   [BinaryName="fullscreenEnabled", NeedsCallerType]
   readonly attribute boolean mozFullScreenEnabled;
-  [LenientSetter, Func="nsDocument::IsUnprefixedFullscreenEnabled"]
-  readonly attribute Element? fullscreenElement;
-  [BinaryName="fullscreenElement"]
-  readonly attribute Element? mozFullScreenElement;
 
   [Func="nsDocument::IsUnprefixedFullscreenEnabled"]
   void exitFullscreen();
   [BinaryName="exitFullscreen"]
   void mozCancelFullScreen();
 
   // Events handlers
   [Func="nsDocument::IsUnprefixedFullscreenEnabled"]
--- a/dom/webidl/DocumentOrShadowRoot.webidl
+++ b/dom/webidl/DocumentOrShadowRoot.webidl
@@ -18,11 +18,13 @@ interface DocumentOrShadowRoot {
   // sequence<Element> elementsFromPoint (float x, float y);
   // Not implemented yet: bug 1430307.
   // CaretPosition? caretPositionFromPoint (float x, float y);
 
   readonly attribute Element? activeElement;
   readonly attribute StyleSheetList styleSheets;
 
   readonly attribute Element? pointerLockElement;
-  // Not implemented yet: bug 1430305.
-  // readonly attribute Element? fullscreenElement;
+  [LenientSetter, Func="nsIDocument::IsUnprefixedFullscreenEnabled"]
+  readonly attribute Element? fullscreenElement;
+  [BinaryName="fullscreenElement"]
+  readonly attribute Element? mozFullScreenElement;
 };