Bug 1160014 part 1 - Implement common part of fullscreen transition. r=roc,smaug,dao
☠☠ backed out by 7ab0df390753 ☠ ☠
authorXidorn Quan <quanxunzhen@gmail.com>
Sat, 11 Jul 2015 10:08:59 +1000
changeset 278019 b0b7f4f09ed2caa64e163cb4d3945c31bbf866dc
parent 278018 675ea719b91c3847f5aaa353c801389a9cfb57f7
child 278020 fca26897d5349472296d9c35207df6e0ce9521cf
push id3422
push usercykesiopka.bmo@gmail.com
push dateSun, 12 Jul 2015 16:26:48 +0000
reviewersroc, smaug, dao
bugs1160014
milestone42.0a1
Bug 1160014 part 1 - Implement common part of fullscreen transition. r=roc,smaug,dao This patch implements the code which is shared by all platforms for fullscreen transition. It adds two prefs for the duration of fullscreen transition. They can also be used to completely suppress the transition. In addition, this patch uses the newly added prefs to suppress the transition in all tests which use the DOM fullscreen.
browser/base/content/browser-fullScreen.js
browser/base/content/tab-content.js
browser/base/content/test/general/browser_domFullscreen_fullscreenMode.js
browser/base/content/test/general/test_contextmenu.html
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
dom/html/test/test_fullscreen-api.html
dom/tests/mochitest/chrome/test_MozDomFullscreen_event.xul
dom/tests/mochitest/pointerlock/test_pointerlock-api.html
modules/libpref/init/all.js
widget/cocoa/nsCocoaWindow.h
widget/cocoa/nsCocoaWindow.mm
widget/nsBaseWidget.cpp
widget/nsBaseWidget.h
widget/nsIWidget.h
--- a/browser/base/content/browser-fullScreen.js
+++ b/browser/base/content/browser-fullScreen.js
@@ -3,16 +3,17 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 var FullScreen = {
   _MESSAGES: [
     "DOMFullscreen:Request",
     "DOMFullscreen:NewOrigin",
     "DOMFullscreen:Exit",
+    "DOMFullscreen:Painted",
   ],
 
   init: function() {
     // called when we go into full screen, even if initiated by a web page script
     window.addEventListener("fullscreen", this, true);
     window.addEventListener("MozDOMFullscreen:Entered", this,
                             /* useCapture */ true,
                             /* wantsUntrusted */ false);
@@ -161,16 +162,20 @@ var FullScreen = {
       case "DOMFullscreen:NewOrigin": {
         this.showWarning(aMessage.data.originNoSuffix);
         break;
       }
       case "DOMFullscreen:Exit": {
         this._windowUtils.remoteFrameFullscreenReverted();
         break;
       }
+      case "DOMFullscreen:Painted": {
+        Services.obs.notifyObservers(window, "fullscreen-painted", "");
+        break;
+      }
     }
   },
 
   enterDomFullscreen : function(aBrowser) {
     if (!document.mozFullScreen)
       return false;
 
     // If we've received a fullscreen notification, we have to ensure that the
--- a/browser/base/content/tab-content.js
+++ b/browser/base/content/tab-content.js
@@ -619,18 +619,20 @@ if (Services.appinfo.processType == Serv
 let DOMFullscreenHandler = {
   _fullscreenDoc: null,
 
   init: function() {
     addMessageListener("DOMFullscreen:Entered", this);
     addMessageListener("DOMFullscreen:Approved", this);
     addMessageListener("DOMFullscreen:CleanUp", this);
     addEventListener("MozDOMFullscreen:Request", this);
+    addEventListener("MozDOMFullscreen:Entered", this);
     addEventListener("MozDOMFullscreen:NewOrigin", this);
     addEventListener("MozDOMFullscreen:Exit", this);
+    addEventListener("MozDOMFullscreen:Exited", this);
   },
 
   get _windowUtils() {
     return content.QueryInterface(Ci.nsIInterfaceRequestor)
                   .getInterface(Ci.nsIDOMWindowUtils);
   },
 
   receiveMessage: function(aMessage) {
@@ -673,12 +675,22 @@ let DOMFullscreenHandler = {
           originNoSuffix: this._fullscreenDoc.nodePrincipal.originNoSuffix,
         });
         break;
       }
       case "MozDOMFullscreen:Exit": {
         sendAsyncMessage("DOMFullscreen:Exit");
         break;
       }
+      case "MozDOMFullscreen:Entered":
+      case "MozDOMFullscreen:Exited": {
+        addEventListener("MozAfterPaint", this);
+        break;
+      }
+      case "MozAfterPaint": {
+        removeEventListener("MozAfterPaint", this);
+        sendAsyncMessage("DOMFullscreen:Painted");
+        break;
+      }
     }
   }
 };
 DOMFullscreenHandler.init();
--- a/browser/base/content/test/general/browser_domFullscreen_fullscreenMode.js
+++ b/browser/base/content/test/general/browser_domFullscreen_fullscreenMode.js
@@ -116,16 +116,20 @@ let gTests = [
     affectsFullscreenMode: true,
     exitFunc: function () {
       executeSoon(() => EventUtils.synthesizeKey("VK_F11", {}));
     }
   }
 ];
 
 add_task(function* () {
+  yield pushPrefs(
+    ["full-screen-api.transition-duration.enter", "0 0"],
+    ["full-screen-api.transition-duration.leave", "0 0"]);
+
   let tab = gBrowser.addTab("about:robots");
   let browser = tab.linkedBrowser;
   gBrowser.selectedTab = tab;
   yield waitForDocLoadComplete();
 
   registerCleanupFunction(() => {
     if (browser.contentWindow.fullScreen) {
       BrowserFullScreen();
--- a/browser/base/content/test/general/test_contextmenu.html
+++ b/browser/base/content/test/general/test_contextmenu.html
@@ -502,16 +502,18 @@ function runTest(testNum) {
         var full_screen_element = subwindow.document.getElementById("test-dom-full-screen");
         var openDomFullScreen = function() {
             subwindow.removeEventListener("mozfullscreenchange", openDomFullScreen, false);
             openContextMenuFor(dom_full_screen, true); // Invoke context menu for next test.
         }
         subwindow.addEventListener("mozfullscreenchange", openDomFullScreen, false);
         SpecialPowers.setBoolPref("full-screen-api.approval-required", false);
         SpecialPowers.setBoolPref("full-screen-api.allow-trusted-requests-only", false);
+        SpecialPowers.setCharPref("full-screen-api.transition-duration.enter", "0 0");
+        SpecialPowers.setCharPref("full-screen-api.transition-duration.leave", "0 0");
         full_screen_element.mozRequestFullScreen();
     },
 
     function () {
         // Context menu for DOM Fullscreen mode (NOTE: this is *NOT* on an img)
         checkContextMenu(["context-navigation", null,
                               ["context-back",            false,
                                "context-forward",         false,
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -6025,29 +6025,227 @@ FinishDOMFullscreenChange(nsIDocument* a
     // Force exit from DOM full-screen mode. This is so that if we're in
     // DOM full-screen mode and the user exits full-screen mode with
     // the browser full-screen mode toggle keyboard-shortcut, we'll detect
     // that and leave DOM API full-screen mode too.
     nsIDocument::ExitFullscreen(aDoc, /* async */ false);
   }
 }
 
+struct FullscreenTransitionDuration
+{
+  // The unit of the durations is millisecond
+  uint16_t mFadeIn = 0;
+  uint16_t mFadeOut = 0;
+  bool IsSuppressed() const
+  {
+    return mFadeIn == 0 && mFadeOut == 0;
+  }
+};
+
+static void
+GetFullscreenTransitionDuration(bool aEnterFullscreen,
+                                FullscreenTransitionDuration* aDuration)
+{
+  const char* pref = aEnterFullscreen ?
+    "full-screen-api.transition-duration.enter" :
+    "full-screen-api.transition-duration.leave";
+  nsAdoptingCString prefValue = Preferences::GetCString(pref);
+  if (!prefValue.IsEmpty()) {
+    sscanf(prefValue.get(), "%hu%hu",
+           &aDuration->mFadeIn, &aDuration->mFadeOut);
+  }
+}
+
+class FullscreenTransitionTask : public nsRunnable
+{
+public:
+  FullscreenTransitionTask(const FullscreenTransitionDuration& aDuration,
+                           nsGlobalWindow* aWindow, bool aFullscreen,
+                           nsIWidget* aWidget, nsIScreen* aScreen,
+                           nsISupports* aTransitionData)
+    : mWindow(aWindow)
+    , mWidget(aWidget)
+    , mScreen(aScreen)
+    , mTransitionData(aTransitionData)
+    , mDuration(aDuration)
+    , mStage(eBeforeToggle)
+    , mFullscreen(aFullscreen)
+  {}
+
+  NS_IMETHOD Run() override;
+
+private:
+  enum Stage {
+    // BeforeToggle stage happens before we enter or leave fullscreen
+    // state. In this stage, the task triggers the pre-toggle fullscreen
+    // transition on the widget.
+    eBeforeToggle,
+    // ToggleFullscreen stage actually executes the fullscreen toggle,
+    // and wait for the next paint on the content to continue.
+    eToggleFullscreen,
+    // AfterToggle stage happens after we toggle the fullscreen state.
+    // In this stage, the task triggers the post-toggle fullscreen
+    // transition on the widget.
+    eAfterToggle
+  };
+
+  class Observer final : public nsIObserver
+  {
+  public:
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIOBSERVER
+
+    explicit Observer(FullscreenTransitionTask* aTask)
+      : mTask(aTask) { }
+
+  private:
+    ~Observer() {}
+
+    nsRefPtr<FullscreenTransitionTask> mTask;
+  };
+
+  static const uint32_t kNextPaintTimeout = 1000; // ms
+  static const char* const kPaintedTopic;
+
+  nsRefPtr<nsGlobalWindow> mWindow;
+  nsCOMPtr<nsIWidget> mWidget;
+  nsCOMPtr<nsIScreen> mScreen;
+  nsCOMPtr<nsITimer> mTimer;
+  nsCOMPtr<nsISupports> mTransitionData;
+
+  FullscreenTransitionDuration mDuration;
+  Stage mStage;
+  bool mFullscreen;
+};
+
+const char* const
+FullscreenTransitionTask::kPaintedTopic = "fullscreen-painted";
+
+NS_IMETHODIMP
+FullscreenTransitionTask::Run()
+{
+  Stage stage = mStage;
+  mStage = Stage(mStage + 1);
+  if (stage == eBeforeToggle) {
+    mWidget->PerformFullscreenTransition(nsIWidget::eBeforeFullscreenToggle,
+                                         mDuration.mFadeIn, mTransitionData,
+                                         this);
+  } else if (stage == eToggleFullscreen) {
+    if (MOZ_UNLIKELY(mWindow->mFullScreen != mFullscreen)) {
+      // This could happen in theory if several fullscreen requests in
+      // different direction happen continuously in a short time. We
+      // need to ensure the fullscreen state matches our target here,
+      // otherwise the widget would change the window state as if we
+      // toggle for Fullscreen Mode instead of Fullscreen API.
+      NS_WARNING("The fullscreen state of the window does not match");
+      mWindow->mFullScreen = mFullscreen;
+    }
+    // Toggle the fullscreen state on the widget
+    mWidget->MakeFullScreen(mFullscreen, mScreen);
+    // Set observer for the next content paint.
+    nsCOMPtr<nsIObserver> observer = new Observer(this);
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    obs->AddObserver(observer, kPaintedTopic, false);
+    // There are several edge cases where we may never get the paint
+    // notification, including:
+    // 1. the window/tab is closed before the next paint;
+    // 2. the user has switched to another tab before we get here.
+    // Completely fixing those cases seems to be tricky, and since they
+    // should rarely happen, it probably isn't worth to fix. Hence we
+    // simply add a timeout here to ensure we never hang forever.
+    mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+    mTimer->Init(observer, kNextPaintTimeout, nsITimer::TYPE_ONE_SHOT);
+  } else if (stage == eAfterToggle) {
+    mWidget->PerformFullscreenTransition(nsIWidget::eAfterFullscreenToggle,
+                                         mDuration.mFadeOut, mTransitionData,
+                                         this);
+  }
+  return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(FullscreenTransitionTask::Observer, nsIObserver)
+
+NS_IMETHODIMP
+FullscreenTransitionTask::Observer::Observe(nsISupports* aSubject,
+                                            const char* aTopic,
+                                            const char16_t* aData)
+{
+  bool shouldContinue = false;
+  if (strcmp(aTopic, FullscreenTransitionTask::kPaintedTopic) == 0) {
+    nsCOMPtr<nsPIDOMWindow> win(do_QueryInterface(aSubject));
+    nsCOMPtr<nsIWidget> widget = win ?
+      static_cast<nsGlobalWindow*>(win.get())->GetMainWidget() : nullptr;
+    if (widget == mTask->mWidget) {
+      // The paint notification arrives first. Cancel the timer.
+      mTask->mTimer->Cancel();
+      shouldContinue = true;
+    }
+  } else {
+#ifdef DEBUG
+    MOZ_ASSERT(strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC) == 0,
+               "Should only get fullscreen-painted or timer-callback");
+    nsCOMPtr<nsITimer> timer(do_QueryInterface(aSubject));
+    MOZ_ASSERT(timer && timer == mTask->mTimer,
+               "Should only trigger this with the timer the task created");
+#endif
+    shouldContinue = true;
+  }
+  if (shouldContinue) {
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    obs->RemoveObserver(this, kPaintedTopic);
+    mTask->mTimer = nullptr;
+    mTask->Run();
+  }
+  return NS_OK;
+}
+
+static bool
+MakeWidgetFullscreen(nsGlobalWindow* aWindow, gfx::VRHMDInfo* aHMD,
+                     nsPIDOMWindow::FullscreenReason aReason, bool aFullscreen)
+{
+  nsCOMPtr<nsIWidget> widget = aWindow->GetMainWidget();
+  if (!widget) {
+    return false;
+  }
+
+  FullscreenTransitionDuration duration;
+  bool performTransition = false;
+  nsCOMPtr<nsISupports> transitionData;
+  if (aReason == nsPIDOMWindow::eForFullscreenAPI) {
+    GetFullscreenTransitionDuration(aFullscreen, &duration);
+    if (!duration.IsSuppressed()) {
+      performTransition = widget->
+        PrepareForFullscreenTransition(getter_AddRefs(transitionData));
+    }
+  }
+  nsCOMPtr<nsIScreen> screen = aHMD ? aHMD->GetScreen() : nullptr;
+  if (!performTransition) {
+    widget->MakeFullScreen(aFullscreen, screen);
+  } else {
+    nsCOMPtr<nsIRunnable> task =
+      new FullscreenTransitionTask(duration, aWindow, aFullscreen,
+                                   widget, screen, transitionData);
+    task->Run();
+  }
+  return true;
+}
+
 nsresult
 nsGlobalWindow::SetFullscreenInternal(FullscreenReason aReason,
                                       bool aFullScreen,
                                       gfx::VRHMDInfo* aHMD)
 {
   MOZ_ASSERT(IsOuterWindow());
 
   NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);
 
   // Only chrome can change our fullscreen mode. Otherwise, the state
   // can only be changed for DOM fullscreen.
-  if (aFullScreen == FullScreen() ||
-      (aReason == eForFullscreenMode && !nsContentUtils::IsCallerChrome())) {
+  if (aReason == eForFullscreenMode && !nsContentUtils::IsCallerChrome()) {
     return NS_OK;
   }
 
   // SetFullScreen needs to be called on the root window, so get that
   // via the DocShell tree, and if we are not already the root,
   // call SetFullScreen on that window instead.
   nsCOMPtr<nsIDocShellTreeItem> rootItem;
   mDocShell->GetRootTreeItem(getter_AddRefs(rootItem));
@@ -6093,26 +6291,17 @@ nsGlobalWindow::SetFullscreenInternal(Fu
   // gone full screen, the state trap above works.
   mFullScreen = aFullScreen;
 
   // Sometimes we don't want the top-level widget to actually go fullscreen,
   // for example in the B2G desktop client, we don't want the emulated screen
   // dimensions to appear to increase when entering fullscreen mode; we just
   // want the content to fill the entire client area of the emulator window.
   if (!Preferences::GetBool("full-screen-api.ignore-widgets", false)) {
-    nsCOMPtr<nsIWidget> widget = GetMainWidget();
-    if (widget) {
-      nsCOMPtr<nsIScreen> screen;
-      if (aHMD) {
-        screen = aHMD->GetScreen();
-      }
-      if (aReason == eForFullscreenAPI) {
-        widget->PrepareForDOMFullscreenTransition();
-      }
-      widget->MakeFullScreen(aFullScreen, screen);
+    if (MakeWidgetFullscreen(this, aHMD, aReason, aFullScreen)) {
       // The rest of code for switching fullscreen is in nsGlobalWindow::
       // FinishFullscreenChange() which will be called after sizemodechange
       // event is dispatched.
       return NS_OK;
     }
   }
 
   FinishFullscreenChange(aFullScreen);
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -479,16 +479,19 @@ public:
 
   // Outer windows only.
   virtual bool DispatchCustomEvent(const nsAString& aEventName) override;
   bool DispatchResizeEvent(const mozilla::CSSIntSize& aSize);
 
   // Inner windows only.
   virtual void RefreshCompartmentPrincipal() override;
 
+  // For accessing protected field mFullScreen
+  friend class FullscreenTransitionTask;
+
   // Outer windows only.
   virtual nsresult SetFullscreenInternal(
     FullscreenReason aReason, bool aIsFullscreen,
     mozilla::gfx::VRHMDInfo *aHMD = nullptr) override final;
   virtual void FinishFullscreenChange(bool aIsFullscreen) override final;
   bool FullScreen() const;
 
   // Inner windows only.
--- a/dom/html/test/test_fullscreen-api.html
+++ b/dom/html/test/test_fullscreen-api.html
@@ -81,16 +81,20 @@ try {
 } catch (e) {
 }
 is(window.fullScreen, false, "Shouldn't be able to set window fullscreen from content");
 // Ensure the full-screen api is enabled, and will be disabled on test exit.
 // Disable the requirement for trusted contexts only, so the tests are easier
 // to write
 addLoadEvent(function() {
   SpecialPowers.pushPrefEnv({
-    "set":[["full-screen-api.enabled", true],
-            ["full-screen-api.allow-trusted-requests-only", false]]}, nextTest);
+      "set": [
+        ["full-screen-api.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"]
+      ]}, nextTest);
 });
 SimpleTest.waitForExplicitFinish();
 </script>
 </pre>
 </body>
 </html>
--- a/dom/tests/mochitest/chrome/test_MozDomFullscreen_event.xul
+++ b/dom/tests/mochitest/chrome/test_MozDomFullscreen_event.xul
@@ -27,18 +27,22 @@ function make_uri(url) {
 // Ensure "fullscreen" permissions are not present on the test URI.
 var pm = Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager);
 var uri = make_uri("http://mochi.test:8888");
 var principal = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
                   .getService(Ci.nsIScriptSecurityManager)
                   .getNoAppCodebasePrincipal(uri);
 pm.removeFromPrincipal(principal, "fullscreen");
 
-SpecialPowers.pushPrefEnv({"set": [['full-screen-api.enabled', true],
-                                   ['full-screen-api.allow-trusted-requests-only', false]]}, setup);
+SpecialPowers.pushPrefEnv({"set": [
+  ['full-screen-api.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']
+]}, setup);
 
 function setup() {
    newwindow = window.open("MozDomFullscreen_chrome.xul", "_blank","chrome,resizable=yes,width=400,height=400");
 }
 
 function done()
 {
   newwindow.close();
--- a/dom/tests/mochitest/pointerlock/test_pointerlock-api.html
+++ b/dom/tests/mochitest/pointerlock/test_pointerlock-api.html
@@ -23,26 +23,27 @@ https://bugzilla.mozilla.org/show_bug.cg
          * which doesn't work when run in the mochitests' iframe, since the
          * mochitests' iframe doesn't have an allowfullscreen attribute.  To get
          * around this, all tests are run in a child window, which can go fullscreen.
          * This method is borrowed from dom/html/test/test_fullscreen-api.html.
          **/
 
         SimpleTest.waitForExplicitFinish();
 
-        // Ensure the full-screen api is enabled, and will be disabled on test exit.
-        SpecialPowers.setBoolPref("full-screen-api.enabled", true);
-
-        // Disable the requirement for trusted contexts only, so the tests are easier to write.
-        SpecialPowers.setBoolPref("full-screen-api.allow-trusted-requests-only", false);
-
         // Grant "fullscreen" permission on the test domain. This means fullscreen will be
         // automatically approved, so pointer lock in the tests will be too.
         SpecialPowers.setFullscreenAllowed(document);
 
+        SpecialPowers.pushPrefEnv({"set": [
+          ["full-screen-api.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"]
+        ]}, nextTest);
+
         // Run the tests which go full-screen in new window, as Mochitests
         // normally run in an iframe, which by default will not have the
         // allowfullscreen attribute set, so full-screen won't work.
         var gTestFiles = [
           "file_approval.html",
           "file_screenClientXYConst.html",
           "file_childIframe.html",
           "file_doubleLock.html",
@@ -67,18 +68,16 @@ https://bugzilla.mozilla.org/show_bug.cg
           { file: "file_screenClientXYConst.html",
             platform: "MacIntel" }
         ];
 
         var gTestWindow = null;
         var gTestIndex = 0;
 
         function finish() {
-          SpecialPowers.clearUserPref("full-screen-api.enabled");
-          SpecialPowers.clearUserPref("full-screen-api.allow-trusted-requests-only");
           SpecialPowers.removeFullscreenAllowed(document)
           SimpleTest.finish();
         }
 
         function nextTest() {
           if (gTestWindow) {
             gTestWindow.close();
           }
@@ -102,14 +101,12 @@ https://bugzilla.mozilla.org/show_bug.cg
               gTestWindow = window.open(file, "", "width=500,height=500");
             } else {
               nextTest();
             }
           } else {
             finish();
           }
         }
-
-        addLoadEvent(nextTest);
       </script>
     </pre>
   </body>
 </html>
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4337,16 +4337,19 @@ pref("dom.webnotifications.enabled", tru
 
 // Alert animation effect, name is disableSlidingEffect for backwards-compat.
 pref("alerts.disableSlidingEffect", false);
 
 // DOM full-screen API.
 pref("full-screen-api.enabled", false);
 pref("full-screen-api.allow-trusted-requests-only", true);
 pref("full-screen-api.pointer-lock.enabled", true);
+// transition duration of fade-to-black and fade-from-black, unit: ms
+pref("full-screen-api.transition-duration.enter", "400 400");
+pref("full-screen-api.transition-duration.leave", "400 400");
 
 // DOM idle observers API
 pref("dom.idle-observers-api.enabled", true);
 
 // Time limit, in milliseconds, for EventStateManager::IsHandlingUserInput().
 // Used to detect long running handlers of user-generated events.
 pref("dom.event.handling-user-input-time-limit", 1000);
 
--- a/widget/cocoa/nsCocoaWindow.h
+++ b/widget/cocoa/nsCocoaWindow.h
@@ -280,17 +280,17 @@ public:
     NS_IMETHOD              ConstrainPosition(bool aAllowSlop,
                                               int32_t *aX, int32_t *aY) override;
     virtual void            SetSizeConstraints(const SizeConstraints& aConstraints) override;
     NS_IMETHOD              Move(double aX, double aY) override;
     NS_IMETHOD              PlaceBehind(nsTopLevelWidgetZPlacement aPlacement,
                                         nsIWidget *aWidget, bool aActivate) override;
     NS_IMETHOD              SetSizeMode(int32_t aMode) override;
     NS_IMETHOD              HideWindowChrome(bool aShouldHide) override;
-    virtual void            PrepareForDOMFullscreenTransition() override;
+    virtual bool PrepareForFullscreenTransition(nsISupports** aData) override;
     void EnteredFullScreen(bool aFullScreen, bool aNativeMode = true);
     inline bool ShouldToggleNativeFullscreen(bool aFullScreen);
     NS_IMETHOD              MakeFullScreen(bool aFullScreen, nsIScreen* aTargetScreen = nullptr) override;
     NS_IMETHOD              Resize(double aWidth, double aHeight, bool aRepaint) override;
     NS_IMETHOD              Resize(double aX, double aY, double aWidth, double aHeight, bool aRepaint) override;
     NS_IMETHOD              GetClientBounds(nsIntRect &aRect) override;
     NS_IMETHOD              GetScreenBounds(nsIntRect &aRect) override;
     void                    ReportMoveEvent();
--- a/widget/cocoa/nsCocoaWindow.mm
+++ b/widget/cocoa/nsCocoaWindow.mm
@@ -1261,19 +1261,21 @@ NS_IMETHODIMP nsCocoaWindow::HideWindowC
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
-void nsCocoaWindow::PrepareForDOMFullscreenTransition()
+/* virtual */ bool
+nsCocoaWindow::PrepareForFullscreenTransition(nsISupports** aData)
 {
   mInDOMFullscreenTransition = true;
+  return false;
 }
 
 void nsCocoaWindow::EnteredFullScreen(bool aFullScreen, bool aNativeMode)
 {
   mInFullScreenTransition = false;
   mInFullScreenMode = aFullScreen;
   if (aNativeMode || mInNativeFullScreenMode) {
     mInNativeFullScreenMode = aFullScreen;
--- a/widget/nsBaseWidget.cpp
+++ b/widget/nsBaseWidget.cpp
@@ -765,16 +765,26 @@ NS_IMETHODIMP nsBaseWidget::SetWindowSha
 // Hide window borders/decorations for this widget
 //
 //-------------------------------------------------------------------------
 NS_IMETHODIMP nsBaseWidget::HideWindowChrome(bool aShouldHide)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
+/* virtual */ void
+nsBaseWidget::PerformFullscreenTransition(FullscreenTransitionStage aStage,
+                                          uint16_t aDuration,
+                                          nsISupports* aData,
+                                          nsIRunnable* aCallback)
+{
+  MOZ_ASSERT_UNREACHABLE(
+    "Should never call PerformFullscreenTransition on nsBaseWidget");
+}
+
 //-------------------------------------------------------------------------
 //
 // Put the window into full-screen mode
 //
 //-------------------------------------------------------------------------
 NS_IMETHODIMP nsBaseWidget::MakeFullScreen(bool aFullScreen, nsIScreen* aScreen)
 {
   HideWindowChrome(aFullScreen);
--- a/widget/nsBaseWidget.h
+++ b/widget/nsBaseWidget.h
@@ -138,17 +138,21 @@ public:
   virtual void            SetTransparencyMode(nsTransparencyMode aMode) override;
   virtual nsTransparencyMode GetTransparencyMode() override;
   virtual void            GetWindowClipRegion(nsTArray<nsIntRect>* aRects) override;
   NS_IMETHOD              SetWindowShadowStyle(int32_t aStyle) override;
   virtual void            SetShowsToolbarButton(bool aShow) override {}
   virtual void            SetShowsFullScreenButton(bool aShow) override {}
   virtual void            SetWindowAnimationType(WindowAnimationType aType) override {}
   NS_IMETHOD              HideWindowChrome(bool aShouldHide) override;
-  virtual void            PrepareForDOMFullscreenTransition() override {}
+  virtual bool PrepareForFullscreenTransition(nsISupports** aData) override { return false; }
+  virtual void PerformFullscreenTransition(FullscreenTransitionStage aStage,
+                                           uint16_t aDuration,
+                                           nsISupports* aData,
+                                           nsIRunnable* aCallback) override;
   NS_IMETHOD              MakeFullScreen(bool aFullScreen, nsIScreen* aScreen = nullptr) override;
   virtual LayerManager*   GetLayerManager(PLayerTransactionChild* aShadowManager = nullptr,
                                           LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE,
                                           LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT,
                                           bool* aAllowRetaining = nullptr) override;
 
   CompositorVsyncDispatcher* GetCompositorVsyncDispatcher() override;
   void            CreateCompositorVsyncDispatcher();
--- a/widget/nsIWidget.h
+++ b/widget/nsIWidget.h
@@ -32,16 +32,17 @@
 // forward declarations
 class   nsIRollupListener;
 class   imgIContainer;
 class   nsIContent;
 class   ViewWrapper;
 class   nsIWidgetListener;
 class   nsIntRegion;
 class   nsIScreen;
+class   nsIRunnable;
 
 namespace mozilla {
 class CompositorVsyncDispatcher;
 class WritingMode;
 namespace dom {
 class TabChild;
 }
 namespace plugins {
@@ -114,18 +115,18 @@ typedef void* nsNativeWidget;
 #if defined(MOZ_WIDGET_GTK)
 // set/get nsPluginNativeWindowGtk, e10s specific
 #define NS_NATIVE_PLUGIN_OBJECT_PTR    104
 #endif
 // See RegisterPluginWindowForRemoteUpdates
 #define NS_NATIVE_PLUGIN_ID            105
 
 #define NS_IWIDGET_IID \
-{ 0x53376F57, 0xF081, 0x4949, \
-  { 0xB5, 0x5E, 0x87, 0xEF, 0x6A, 0xE9, 0xE3, 0x5A } };
+{ 0x22b4504e, 0xddba, 0x4211, \
+  { 0xa1, 0x49, 0x6e, 0x11, 0x73, 0xc4, 0x11, 0x45 } };
 
 /*
  * Window shadow styles
  * Also used for the -moz-window-shadow CSS property
  */
 
 #define NS_STYLE_WINDOW_SHADOW_NONE             0
 #define NS_STYLE_WINDOW_SHADOW_DEFAULT          1
@@ -1580,28 +1581,43 @@ class nsIWidget : public nsISupports {
     virtual void SetUseBrightTitlebarForeground(bool aBrightForeground) {}
 
     /** 
      * Hide window chrome (borders, buttons) for this widget.
      *
      */
     NS_IMETHOD HideWindowChrome(bool aShouldHide) = 0;
 
+    enum FullscreenTransitionStage
+    {
+      eBeforeFullscreenToggle,
+      eAfterFullscreenToggle
+    };
+
     /**
-     * Ask the widget to start the transition for entering or exiting
-     * DOM Fullscreen.
-     *
-     * XXX This method is currently not actually implemented by any
-     * widget. The only function of this method is to notify cocoa
-     * window that it should not use the native fullscreen mode. This
-     * method is reserved for bug 1160014 where a transition will be
-     * added for DOM fullscreen. Hence, this function is likely to
-     * be further changed then.
+     * Prepares for fullscreen transition and returns whether the widget
+     * supports fullscreen transition. If this method returns false,
+     * PerformFullscreenTransition() must never be called. Otherwise,
+     * caller should call that method twice with "before" and "after"
+     * stages respectively in order. In the latter case, this method may
+     * return some data via aData pointer. Caller must pass that data to
+     * PerformFullscreenTransition() if any, and caller is responsible
+     * for releasing that data.
      */
-    virtual void PrepareForDOMFullscreenTransition() = 0;
+    virtual bool PrepareForFullscreenTransition(nsISupports** aData) = 0;
+
+    /**
+     * Performs fullscreen transition. This method returns immediately,
+     * and will post aCallback to the main thread when the transition
+     * finishes.
+     */
+    virtual void PerformFullscreenTransition(FullscreenTransitionStage aStage,
+                                             uint16_t aDuration,
+                                             nsISupports* aData,
+                                             nsIRunnable* aCallback) = 0;
 
     /**
      * Put the toplevel window into or out of fullscreen mode.
      * If aTargetScreen is given, attempt to go fullscreen on that screen,
      * if possible.  (If not, it behaves as if aTargetScreen is null.)
      * If !aFullScreen, aTargetScreen is ignored.
      * aTargetScreen support is currently only implemented on Windows.
      */