Bug 1444580: Devirtualize the fullscreen stuff. r=smaug draft
authorEmilio Cobos Álvarez <emilio@crisal.io>
Sat, 10 Mar 2018 06:15:28 +0100
changeset 765763 4a0e57ce351909454ba7e8bc648a9668faf392ef
parent 765762 dab669fbf32c490bc910f3ccaf235903eadb1eca
child 765764 38de88908ceabf878f8cdc4c972b1bdb95b3ea19
push id102160
push userbmo:emilio@crisal.io
push dateSat, 10 Mar 2018 06:32:21 +0000
reviewerssmaug
bugs1444580
milestone60.0a1
Bug 1444580: Devirtualize the fullscreen stuff. r=smaug MozReview-Commit-ID: CgPENqExkQh
dom/base/nsDocument.cpp
dom/base/nsDocument.h
dom/base/nsIDocument.h
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -1490,16 +1490,17 @@ nsIDocument::nsIDocument()
     mStyleSetFilled(false),
     mSSApplicableStateNotificationPending(false),
     mMayHaveTitleElement(false),
     mDOMLoadingSet(false),
     mDOMInteractiveSet(false),
     mDOMCompleteSet(false),
     mAutoFocusFired(false),
     mIsScopedStyleEnabled(eScopedStyle_Unknown),
+    mPendingFullscreenRequests(0),
     mCompatMode(eCompatibility_FullStandards),
     mReadyState(ReadyState::READYSTATE_UNINITIALIZED),
     mStyleBackendType(StyleBackendType::None),
 #ifdef MOZILLA_INTERNAL_API
     mVisibilityState(dom::VisibilityState::Hidden),
 #else
     mDummy(0),
 #endif
@@ -1546,17 +1547,16 @@ nsDocument::nsDocument(const char* aCont
   : nsIDocument()
   , mHasWarnedAboutBoxObjects(false)
   , mDelayFrameLoaderInitialization(false)
   , mSynchronousDOMContentLoaded(false)
   , mParserAborted(false)
   , mCurrentOrientationAngle(0)
   , mCurrentOrientationType(OrientationType::Portrait_primary)
   , mReportedUseCounters(false)
-  , mPendingFullscreenRequests(0)
   , mXMLDeclarationBits(0)
   , mBoxObjectTable(nullptr)
   , mOnloadBlockCount(0)
   , mAsyncOnloadBlockCount(0)
   , mPreloadPictureDepth(0)
   , mScrolledToRefAlready(0)
   , mChangeScrollPosWhenScrollingToRef(0)
   , mValidWidth(false)
@@ -10508,24 +10508,24 @@ FullscreenRoots::IsEmpty()
 {
   return !sInstance;
 }
 
 } // end namespace mozilla.
 using mozilla::FullscreenRoots;
 
 nsIDocument*
-nsDocument::GetFullscreenRoot()
+nsIDocument::GetFullscreenRoot()
 {
   nsCOMPtr<nsIDocument> root = do_QueryReferent(mFullscreenRoot);
   return root;
 }
 
 void
-nsDocument::SetFullscreenRoot(nsIDocument* aRoot)
+nsIDocument::SetFullscreenRoot(nsIDocument* aRoot)
 {
   mFullscreenRoot = do_GetWeakReference(aRoot);
 }
 
 void
 nsIDocument::ExitFullscreen()
 {
   RestorePreviousFullScreenState();
@@ -10595,33 +10595,33 @@ static uint32_t
 CountFullscreenSubDocuments(nsIDocument* aDoc)
 {
   uint32_t count = 0;
   aDoc->EnumerateSubDocuments(CountFullscreenSubDocuments, &count);
   return count;
 }
 
 bool
-nsDocument::IsFullscreenLeaf()
+nsIDocument::IsFullscreenLeaf()
 {
   // A fullscreen leaf document is fullscreen, and has no fullscreen
   // subdocuments.
   if (!FullScreenStackTop()) {
     return false;
   }
   return CountFullscreenSubDocuments(this) == 0;
 }
 
 static bool
 ResetFullScreen(nsIDocument* aDocument, void* aData)
 {
   if (aDocument->FullScreenStackTop()) {
     NS_ASSERTION(CountFullscreenSubDocuments(aDocument) <= 1,
         "Should have at most 1 fullscreen subdocument.");
-    static_cast<nsDocument*>(aDocument)->CleanupFullscreenState();
+    aDocument->CleanupFullscreenState();
     NS_ASSERTION(!aDocument->FullScreenStackTop(),
                  "Should reset full-screen");
     auto changed = reinterpret_cast<nsCOMArray<nsIDocument>*>(aData);
     changed->AppendElement(aDocument);
     aDocument->EnumerateSubDocuments(ResetFullScreen, aData);
   }
   return true;
 }
@@ -10736,56 +10736,55 @@ GetFullscreenLeaf(nsIDocument* aDoc)
   if (!root->FullScreenStackTop()) {
     return nullptr;
   }
   GetFullscreenLeaf(root, &leaf);
   return leaf;
 }
 
 void
-nsDocument::RestorePreviousFullScreenState()
+nsIDocument::RestorePreviousFullScreenState()
 {
   NS_ASSERTION(!FullScreenStackTop() || !FullscreenRoots::IsEmpty(),
     "Should have at least 1 fullscreen root when fullscreen!");
 
   if (!FullScreenStackTop() || !GetWindow() || FullscreenRoots::IsEmpty()) {
     return;
   }
 
   nsCOMPtr<nsIDocument> fullScreenDoc = GetFullscreenLeaf(this);
-  AutoTArray<nsDocument*, 8> exitDocs;
+  AutoTArray<nsIDocument*, 8> exitDocs;
 
   nsIDocument* doc = fullScreenDoc;
   // Collect all subdocuments.
   for (; doc != this; doc = doc->GetParentDocument()) {
-    exitDocs.AppendElement(static_cast<nsDocument*>(doc));
+    exitDocs.AppendElement(doc);
   }
   MOZ_ASSERT(doc == this, "Must have reached this doc");
   // Collect all ancestor documents which we are going to change.
   for (; doc; doc = doc->GetParentDocument()) {
-    nsDocument* theDoc = static_cast<nsDocument*>(doc);
-    MOZ_ASSERT(!theDoc->mFullScreenStack.IsEmpty(),
+    MOZ_ASSERT(!doc->mFullScreenStack.IsEmpty(),
                "Ancestor of fullscreen document must also be in fullscreen");
     if (doc != this) {
-      Element* top = theDoc->FullScreenStackTop();
+      Element* top = doc->FullScreenStackTop();
       if (top->IsHTMLElement(nsGkAtoms::iframe)) {
         if (static_cast<HTMLIFrameElement*>(top)->FullscreenFlag()) {
           // If this is an iframe, and it explicitly requested
           // fullscreen, don't rollback it automatically.
           break;
         }
       }
     }
-    exitDocs.AppendElement(theDoc);
-    if (theDoc->mFullScreenStack.Length() > 1) {
+    exitDocs.AppendElement(doc);
+    if (doc->mFullScreenStack.Length() > 1) {
       break;
     }
   }
 
-  nsDocument* lastDoc = exitDocs.LastElement();
+  nsIDocument* lastDoc = exitDocs.LastElement();
   if (!lastDoc->GetParentDocument() &&
       lastDoc->mFullScreenStack.Length() == 1) {
     // If we are fully exiting fullscreen, don't touch anything here,
     // just wait for the window to get out from fullscreen first.
     AskWindowToExitFullscreen(this);
     return;
   }
 
@@ -10802,17 +10801,17 @@ nsDocument::RestorePreviousFullScreenSta
   if (lastDoc->mFullScreenStack.Length() > 1) {
     lastDoc->FullScreenStackPop();
     newFullscreenDoc = lastDoc;
   } else {
     lastDoc->CleanupFullscreenState();
     newFullscreenDoc = lastDoc->GetParentDocument();
   }
   // Dispatch the fullscreenchange event to all document listed.
-  for (nsDocument* d : exitDocs) {
+  for (nsIDocument* d : exitDocs) {
     DispatchFullScreenChange(d);
   }
 
   MOZ_ASSERT(newFullscreenDoc, "If we were going to exit from fullscreen on "
              "all documents in this doctree, we should've asked the window to "
              "exit first instead of reaching here.");
   if (fullScreenDoc != newFullscreenDoc &&
       !nsContentUtils::HaveEqualPrincipals(fullScreenDoc, newFullscreenDoc)) {
@@ -10840,17 +10839,17 @@ public:
     mRequest->GetDocument()->RequestFullScreen(Move(mRequest));
     return NS_OK;
   }
 
   UniquePtr<FullscreenRequest> mRequest;
 };
 
 void
-nsDocument::AsyncRequestFullScreen(UniquePtr<FullscreenRequest>&& aRequest)
+nsIDocument::AsyncRequestFullScreen(UniquePtr<FullscreenRequest>&& aRequest)
 {
   if (!aRequest->GetElement()) {
     MOZ_ASSERT_UNREACHABLE(
       "Must pass non-null element to nsDocument::AsyncRequestFullScreen");
     return;
   }
 
   // Request full-screen asynchronously.
@@ -10889,17 +10888,17 @@ ClearFullscreenStateOnElement(Element* a
   EventStateManager::SetFullScreenState(aElement, false);
   // Reset iframe fullscreen flag.
   if (aElement->IsHTMLElement(nsGkAtoms::iframe)) {
     static_cast<HTMLIFrameElement*>(aElement)->SetFullscreenFlag(false);
   }
 }
 
 void
-nsDocument::CleanupFullscreenState()
+nsIDocument::CleanupFullscreenState()
 {
   // Iterate the fullscreen stack and clear the fullscreen states.
   // Since we also need to clear the fullscreen-ancestor state, and
   // currently fullscreen elements can only be placed in hierarchy
   // order in the stack, reversely iterating the stack could be more
   // efficient. NOTE that fullscreen-ancestor state would be removed
   // in bug 1199529, and the elements may not in hierarchy order
   // after bug 1195213.
@@ -10909,32 +10908,32 @@ nsDocument::CleanupFullscreenState()
     }
   }
   mFullScreenStack.Clear();
   mFullscreenRoot = nullptr;
   UpdateViewportScrollbarOverrideForFullscreen(this);
 }
 
 bool
-nsDocument::FullScreenStackPush(Element* aElement)
+nsIDocument::FullScreenStackPush(Element* aElement)
 {
   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(FullScreenStackTop() == aElement, "Should match");
   UpdateViewportScrollbarOverrideForFullscreen(this);
   return true;
 }
 
 void
-nsDocument::FullScreenStackPop()
+nsIDocument::FullScreenStackPop()
 {
   if (mFullScreenStack.IsEmpty()) {
     return;
   }
 
   ClearFullscreenStateOnElement(FullScreenStackTop());
 
   // Remove top element. Note the remaining top element in the stack
@@ -10958,31 +10957,31 @@ nsDocument::FullScreenStackPop()
       break;
     }
   }
 
   UpdateViewportScrollbarOverrideForFullscreen(this);
 }
 
 Element*
-nsDocument::FullScreenStackTop()
+nsIDocument::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->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*>
+nsIDocument::GetFullscreenStack() const
 {
   nsTArray<Element*> elements;
   for (const nsWeakPtr& ptr : mFullScreenStack) {
     if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) {
       MOZ_ASSERT(elem->State().HasState(NS_EVENT_STATE_FULL_SCREEN));
       elements.AppendElement(elem);
     }
   }
@@ -11023,31 +11022,31 @@ IsInActiveTab(nsIDocument* aDoc)
   fm->GetActiveWindow(getter_AddRefs(activeWindow));
   if (!activeWindow) {
     return false;
   }
 
   return activeWindow == rootWin;
 }
 
-nsresult nsDocument::RemoteFrameFullscreenChanged(nsIDOMElement* aFrameElement)
+nsresult nsIDocument::RemoteFrameFullscreenChanged(nsIDOMElement* aFrameElement)
 {
   // Ensure the frame element is the fullscreen element in this document.
   // If the frame element is already the fullscreen element in this document,
   // this has no effect.
   nsCOMPtr<nsIContent> content(do_QueryInterface(aFrameElement));
   auto request = MakeUnique<FullscreenRequest>(content->AsElement());
   request->mIsCallerChrome = false;
   request->mShouldNotifyNewOrigin = false;
   RequestFullScreen(Move(request));
 
   return NS_OK;
 }
 
-nsresult nsDocument::RemoteFrameFullscreenReverted()
+nsresult nsIDocument::RemoteFrameFullscreenReverted()
 {
   RestorePreviousFullScreenState();
   return NS_OK;
 }
 
 /* static */ bool
 nsIDocument::IsUnprefixedFullscreenEnabled(JSContext* aCx, JSObject* aObject)
 {
@@ -11086,18 +11085,18 @@ GetFullscreenError(nsIDocument* aDoc, bo
   nsCOMPtr<nsIDocShell> docShell(aDoc->GetDocShell());
   if (!docShell || !docShell->GetFullscreenAllowed()) {
     return "FullscreenDeniedContainerNotAllowed";
   }
   return nullptr;
 }
 
 bool
-nsDocument::FullscreenElementReadyCheck(Element* aElement,
-                                        bool aWasCallerChrome)
+nsIDocument::FullscreenElementReadyCheck(Element* aElement,
+                                         bool aWasCallerChrome)
 {
   NS_ASSERTION(aElement,
     "Must pass non-null element to nsDocument::RequestFullScreen");
   if (!aElement || aElement == FullScreenStackTop()) {
     return false;
   }
   if (!aElement->IsInComposedDoc()) {
     DispatchFullscreenError("FullscreenDeniedNotInDocument");
@@ -11305,17 +11304,17 @@ ShouldApplyFullscreenDirectly(nsIDocumen
     // if it is already in fullscreen. If we do not apply the state but
     // instead add it to the queue and wait for the window as normal,
     // we would get stuck.
     return true;
   }
 }
 
 void
-nsDocument::RequestFullScreen(UniquePtr<FullscreenRequest>&& aRequest)
+nsIDocument::RequestFullScreen(UniquePtr<FullscreenRequest>&& aRequest)
 {
   nsCOMPtr<nsPIDOMWindowOuter> rootWin = GetRootWindow(this);
   if (!rootWin) {
     return;
   }
 
   if (ShouldApplyFullscreenDirectly(this, rootWin)) {
     ApplyFullscreen(*aRequest);
@@ -11373,17 +11372,17 @@ ClearPendingFullscreenRequests(nsIDocume
   PendingFullscreenRequestList::Iterator iter(
     aDoc, PendingFullscreenRequestList::eInclusiveDescendants);
   while (!iter.AtEnd()) {
     iter.DeleteAndNext();
   }
 }
 
 bool
-nsDocument::ApplyFullscreen(const FullscreenRequest& aRequest)
+nsIDocument::ApplyFullscreen(const FullscreenRequest& aRequest)
 {
   Element* elem = aRequest.GetElement();
   if (!FullscreenElementReadyCheck(elem, aRequest.mIsCallerChrome)) {
     return false;
   }
 
   // Stash a reference to any existing fullscreen doc, we'll use this later
   // to detect if the origin which is fullscreen has changed.
@@ -11474,17 +11473,17 @@ nsDocument::ApplyFullscreen(const Fullsc
   // document, as required by the spec.
   for (uint32_t i = 0; i < changed.Length(); ++i) {
     DispatchFullScreenChange(changed[changed.Length() - i - 1]);
   }
   return true;
 }
 
 bool
-nsDocument::FullscreenEnabled(CallerType aCallerType)
+nsIDocument::FullscreenEnabled(CallerType aCallerType)
 {
   return !GetFullscreenError(this, aCallerType == CallerType::System);
 }
 
 uint16_t
 nsDocument::CurrentOrientationAngle() const
 {
   return mCurrentOrientationAngle;
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -102,21 +102,21 @@ class Performance;
 
 struct FullscreenRequest : public LinkedListElement<FullscreenRequest>
 {
   explicit FullscreenRequest(Element* aElement);
   FullscreenRequest(const FullscreenRequest&) = delete;
   ~FullscreenRequest();
 
   Element* GetElement() const { return mElement; }
-  nsDocument* GetDocument() const { return mDocument; }
+  nsIDocument* GetDocument() const { return mDocument; }
 
 private:
   RefPtr<Element> mElement;
-  RefPtr<nsDocument> mDocument;
+  RefPtr<nsIDocument> mDocument;
 
 public:
   // This value should be true if the fullscreen request is
   // originated from chrome code.
   bool mIsCallerChrome = false;
   // This value denotes whether we should trigger a NewOrigin event if
   // requesting fullscreen in its document causes the origin which is
   // fullscreen to change. We may want *not* to trigger that event if
@@ -664,28 +664,16 @@ public:
   // Notifies any responsive content added by AddResponsiveContent upon media
   // features values changing.
   virtual void NotifyMediaFeatureValuesChanged() override;
 
   virtual nsresult GetStateObject(nsIVariant** aResult) override;
 
   virtual Element* FindImageMap(const nsAString& aNormalizedMapName) override;
 
-  virtual nsTArray<Element*> GetFullscreenStack() const override;
-  virtual void AsyncRequestFullScreen(
-    mozilla::UniquePtr<FullscreenRequest>&& aRequest) override;
-  virtual void RestorePreviousFullScreenState() override;
-  virtual bool IsFullscreenLeaf() override;
-  virtual nsresult
-    RemoteFrameFullscreenChanged(nsIDOMElement* aFrameElement) override;
-
-  virtual nsresult RemoteFrameFullscreenReverted() override;
-  virtual nsIDocument* GetFullscreenRoot() override;
-  virtual void SetFullscreenRoot(nsIDocument* aRoot) override;
-
   // Returns the size of the mBlockedTrackingNodes array. (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).
   //
   // A script can subsequently use GetBlockedTrackingNodes()
   // to get a list of references to these nodes.
@@ -702,44 +690,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;
 
-  // 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);
-
-  // Removes all elements from the full-screen stack, removing full-scren
-  // styles from the top element in the stack.
-  void CleanupFullscreenState();
-
-  // Pushes aElement onto the full-screen stack, and removes full-screen styles
-  // from the former full-screen stack top, and its ancestors, and applies the
-  // styles to aElement. aElement becomes the new "full-screen element".
-  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() override;
-
-  // DOM-exposed fullscreen API
-  bool FullscreenEnabled(mozilla::dom::CallerType aCallerType) override;
-
   void RequestPointerLock(Element* aElement,
                           mozilla::dom::CallerType aCallerType) override;
   bool SetPointerLock(Element* aElement, int aCursorStyle);
   static void UnlockPointer(nsIDocument* aDoc = nullptr);
 
   void SetCurrentOrientation(mozilla::dom::OrientationType aType,
                              uint16_t aAngle) override;
   uint16_t CurrentOrientationAngle() const override;
@@ -815,37 +775,23 @@ protected:
   void VerifyRootContentState();
 #endif
 
   explicit nsDocument(const char* aContentType);
   virtual ~nsDocument();
 
   void EnsureOnloadBlocker();
 
-  // Apply the fullscreen state to the document, and trigger related
-  // events. It returns false if the fullscreen element ready check
-  // fails and nothing gets changed.
-  bool ApplyFullscreen(const FullscreenRequest& aRequest);
-
   // Array of owning references to all children
   nsAttrAndChildArray mChildren;
 
   // Tracker for animations that are waiting to start.
   // nullptr until GetOrCreatePendingAnimationTracker is called.
   RefPtr<mozilla::PendingAnimationTracker> mPendingAnimationTracker;
 
-  // Stack of full-screen elements. When we request full-screen we push the
-  // full-screen element onto this stack, and when we cancel full-screen we
-  // pop one off this stack, restoring the previous full-screen state
-  nsTArray<nsWeakPtr> mFullScreenStack;
-
-  // The root of the doc tree in which this document is in. This is only
-  // non-null when this document is in fullscreen mode.
-  nsWeakPtr mFullscreenRoot;
-
 public:
   RefPtr<mozilla::EventListenerManager> mListenerManager;
   RefPtr<mozilla::dom::ScriptLoader> mScriptLoader;
 
   nsClassHashtable<nsStringHashKey, nsRadioGroupStruct> mRadioGroups;
 
   bool mHasWarnedAboutBoxObjects:1;
 
@@ -869,18 +815,16 @@ public:
   // Whether we have reported use counters for this document with Telemetry yet.
   // Normally this is only done at document destruction time, but for image
   // documents (SVG documents) that are not guaranteed to be destroyed, we
   // report use counters when the image cache no longer has any imgRequestProxys
   // pointing to them.  We track whether we ever reported use counters so
   // that we only report them once for the document.
   bool mReportedUseCounters:1;
 
-  uint8_t mPendingFullscreenRequests;
-
   uint8_t mXMLDeclarationBits;
 
   nsRefPtrHashtable<nsPtrHashKey<nsIContent>, mozilla::dom::BoxObject>* mBoxObjectTable;
 
   // A document "without a browsing context" that owns the content of
   // HTMLTemplateElement.
   nsCOMPtr<nsIDocument> mTemplateContentsOwner;
 
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -1606,77 +1606,97 @@ public:
   virtual void AddToIdTable(Element* aElement, nsAtom* aId) = 0;
   virtual void RemoveFromIdTable(Element* aElement, nsAtom* aId) = 0;
   virtual void AddToNameTable(Element* aElement, nsAtom* aName) = 0;
   virtual void RemoveFromNameTable(Element* aElement, nsAtom* aName) = 0;
 
   /**
    * Returns all elements in the fullscreen stack in the insertion order.
    */
-  virtual nsTArray<Element*> GetFullscreenStack() const = 0;
+  nsTArray<Element*> GetFullscreenStack() const;
 
   /**
    * Asynchronously requests that the document make aElement the fullscreen
    * element, and move into fullscreen mode. The current fullscreen element
    * (if any) is pushed onto the fullscreen element stack, and it can be
    * returned to fullscreen status by calling RestorePreviousFullScreenState().
    *
    * Note that requesting fullscreen in a document also makes the element which
    * contains this document in this document's parent document fullscreen. i.e.
    * the <iframe> or <browser> that contains this document is also mode
    * fullscreen. This happens recursively in all ancestor documents.
    */
-  virtual void AsyncRequestFullScreen(
-    mozilla::UniquePtr<FullscreenRequest>&& aRequest) = 0;
+  void AsyncRequestFullScreen(mozilla::UniquePtr<FullscreenRequest>&&);
+
+  // 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);
+
+  // Removes all elements from the full-screen stack, removing full-scren
+  // styles from the top element in the stack.
+  void CleanupFullscreenState();
+
+  // Pushes aElement onto the full-screen stack, and removes full-screen styles
+  // from the former full-screen stack top, and its ancestors, and applies the
+  // styles to aElement. aElement becomes the new "full-screen element".
+  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();
 
   /**
    * Called when a frame in a child process has entered fullscreen or when a
    * fullscreen frame in a child process changes to another origin.
    * aFrameElement is the frame element which contains the child-process
    * fullscreen document.
    */
-  virtual nsresult
-    RemoteFrameFullscreenChanged(nsIDOMElement* aFrameElement) = 0;
+  nsresult RemoteFrameFullscreenChanged(nsIDOMElement* aFrameElement);
 
   /**
    * Called when a frame in a remote child document has rolled back fullscreen
    * so that all its fullscreen element stacks are empty; we must continue the
    * rollback in this parent process' doc tree branch which is fullscreen.
    * Note that only one branch of the document tree can have its documents in
    * fullscreen state at one time. We're in inconsistent state if a
    * fullscreen document has a parent and that parent isn't fullscreen. We
    * preserve this property across process boundaries.
    */
-   virtual nsresult RemoteFrameFullscreenReverted() = 0;
+   nsresult RemoteFrameFullscreenReverted();
 
   /**
    * Restores the previous full-screen element to full-screen status. If there
    * is no former full-screen element, this exits full-screen, moving the
    * top-level browser window out of full-screen mode.
    */
-  virtual void RestorePreviousFullScreenState() = 0;
+  void RestorePreviousFullScreenState();
 
   /**
    * Returns true if this document is a fullscreen leaf document, i.e. it
    * is in fullscreen mode and has no fullscreen children.
    */
-  virtual bool IsFullscreenLeaf() = 0;
+  bool IsFullscreenLeaf();
 
   /**
    * Returns the document which is at the root of this document's branch
    * in the in-process document tree. Returns nullptr if the document isn't
    * fullscreen.
    */
-  virtual nsIDocument* GetFullscreenRoot() = 0;
+  nsIDocument* GetFullscreenRoot();
 
   /**
    * Sets the fullscreen root to aRoot. This stores a weak reference to aRoot
    * in this document.
    */
-  virtual void SetFullscreenRoot(nsIDocument* aRoot) = 0;
+  void SetFullscreenRoot(nsIDocument* aRoot);
 
   /**
    * Synchronously cleans up the fullscreen state on the given document.
    *
    * Calling this without performing fullscreen transition could lead
    * to undesired effect (the transition happens after document state
    * flips), hence it should only be called either by nsGlobalWindow
    * when we have performed the transition, or when it is necessary to
@@ -2997,18 +3017,18 @@ public:
   {
     return IsSyntheticDocument();
   }
   Element* GetCurrentScript();
   void ReleaseCapture() const;
   void MozSetImageElement(const nsAString& aImageElementId, Element* aElement);
   nsIURI* GetDocumentURIObject() const;
   // Not const because all the full-screen goop is not const
-  virtual bool FullscreenEnabled(mozilla::dom::CallerType aCallerType) = 0;
-  virtual Element* FullScreenStackTop() = 0;
+  bool FullscreenEnabled(mozilla::dom::CallerType aCallerType);
+  Element* FullScreenStackTop();
   bool Fullscreen()
   {
     return !!GetFullscreenElement();
   }
   void ExitFullscreen();
   void ExitPointerLock()
   {
     UnlockPointer(this);
@@ -3339,16 +3359,21 @@ protected:
 
   // Recomputes the visibility state but doesn't set the new value.
   mozilla::dom::VisibilityState ComputeVisibilityState() const;
 
   // Since we wouldn't automatically play media from non-visited page, we need
   // to notify window when the page was first visited.
   void MaybeActiveMediaComponents();
 
+  // Apply the fullscreen state to the document, and trigger related
+  // events. It returns false if the fullscreen element ready check
+  // fails and nothing gets changed.
+  bool ApplyFullscreen(const FullscreenRequest& aRequest);
+
   bool GetUseCounter(mozilla::UseCounter aUseCounter)
   {
     return mUseCounters[aUseCounter];
   }
 
   void SetChildDocumentUseCounter(mozilla::UseCounter aUseCounter)
   {
     if (!mChildDocumentUseCounters[aUseCounter]) {
@@ -3776,16 +3801,18 @@ protected:
   bool mDOMInteractiveSet: 1;
   bool mDOMCompleteSet: 1;
   bool mAutoFocusFired: 1;
 
   // Whether <style scoped> support is enabled in this document.
   enum { eScopedStyle_Unknown, eScopedStyle_Disabled, eScopedStyle_Enabled };
   unsigned int mIsScopedStyleEnabled : 2;
 
+  uint8_t mPendingFullscreenRequests;
+
   // Compatibility mode
   nsCompatibility mCompatMode;
 
   // Our readyState
   ReadyState mReadyState;
 
   // Whether this document has (or will have, once we have a pres shell) a
   // Gecko- or Servo-backed style system.
@@ -4011,16 +4038,25 @@ protected:
   // that, unlike mScriptGlobalObject, is never unset once set. This
   // is a weak reference to avoid leaks due to circular references.
   nsWeakPtr mScopeObject;
 
   // Array of intersection observers
   nsTHashtable<nsPtrHashKey<mozilla::dom::DOMIntersectionObserver>>
     mIntersectionObservers;
 
+  // Stack of full-screen elements. When we request full-screen we push the
+  // full-screen element onto this stack, and when we cancel full-screen we
+  // pop one off this stack, restoring the previous full-screen state
+  nsTArray<nsWeakPtr> mFullScreenStack;
+
+  // The root of the doc tree in which this document is in. This is only
+  // non-null when this document is in fullscreen mode.
+  nsWeakPtr mFullscreenRoot;
+
   nsTArray<RefPtr<mozilla::StyleSheet>> mOnDemandBuiltInUASheets;
   nsTArray<RefPtr<mozilla::StyleSheet>> mAdditionalSheets[AdditionalSheetTypeCount];
 
   // Member to store out last-selected stylesheet set.
   nsString mLastStyleSheetSet;
   RefPtr<nsDOMStyleSheetSetList> mStyleSheetSetList;
 
   // We lazily calculate declaration blocks for SVG elements with mapped