Bug 1708324 - [Fission] Full screen button for YouTube video embedded on Reddit doesn't work r=spohl
authorHaik Aftandilian <haftandilian@mozilla.com>
Tue, 18 May 2021 04:54:25 +0000
changeset 579867 8c381a3500a284dae1ca91120ecb74f1dd7c1254
parent 579866 d70a37d3e1b975fe0275267e668c77e8972bf579
child 579868 99e716181ac9a7b325741188d1aad6f57d71ef88
push id143252
push userhaftandilian@mozilla.com
push dateTue, 18 May 2021 04:56:53 +0000
treeherderautoland@8c381a3500a2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersspohl
bugs1708324
milestone90.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1708324 - [Fission] Full screen button for YouTube video embedded on Reddit doesn't work r=spohl During fullscreen transitions on Mac, ignore occlusion events caused by the widget DOM fullscreen transition effect which uses a temporary window. Add a test that attempts to enter fullscreen from a non-focused window. This test is to ensure the fix (and future fixes) do not regress the focus requirement for fullscreen. Differential Revision: https://phabricator.services.mozilla.com/D115046
dom/html/test/file_fullscreen-focus-inner.html
dom/html/test/file_fullscreen-focus.html
dom/html/test/mochitest.ini
dom/html/test/test_fullscreen-api.html
widget/cocoa/nsCocoaWindow.h
widget/cocoa/nsCocoaWindow.mm
new file mode 100644
--- /dev/null
+++ b/dom/html/test/file_fullscreen-focus-inner.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Focus test - child window</title>
+  <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+</head>
+<body>
+
+<script type="application/javascript">
+
+function enterFullscreen() {
+  addFullscreenErrorContinuation(() => { opener.enteredFullscreen(false); });
+
+  addFullscreenChangeContinuation("enter", () => {
+    opener.enteredFullscreen(true);
+  });
+
+  document.body.requestFullscreen();
+}
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/html/test/file_fullscreen-focus.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+
+Test that a fullscreen request fails if the window is not focused.
+
+Open window1, open window2, focus window2, and then attempt to fullscreen
+window1 while it is not focused. The fullscreen attempt should be rejected
+because the window is not focused.
+
+-->
+<head>
+  <title>Test fullscreen request is blocked when window is not focused</title>
+  <script src="/tests/SimpleTest/EventUtils.js"></script>
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+</head>
+<body>
+
+<script type="application/javascript">
+
+function ok(condition, msg) {
+  opener.ok(condition, "[focus] " + msg);
+}
+
+var window1, window2;
+
+function openWindow() {
+  var w = window.open("file_fullscreen-focus-inner.html", "",
+    "width=500,height=500");
+  return w;
+}
+
+function begin() {
+  window1 = openWindow();
+  window1.focus();
+
+  SimpleTest.waitForFocus(function(){
+    window2 = openWindow();
+    window2.focus();
+
+    SimpleTest.waitForFocus(function(){
+      // Now that window2 is focused, attempt to fullscreen window1.
+      // This should fail.
+      window1.enterFullscreen("one");
+    }, window2);
+
+  }, window1);
+}
+
+async function enteredFullscreen(enteredSuccessfully) {
+  ok(!enteredSuccessfully, "window1 did not enter fullscreen");
+
+  if (enteredSuccessfully) {
+    await window1.document.exitFullscreen()
+  }
+
+  window1.close();
+  window2.close();
+  opener.nextTest();
+}
+
+</script>
+</pre>
+<div id="full-screen-element"></div>
+</body>
+</html>
--- a/dom/html/test/mochitest.ini
+++ b/dom/html/test/mochitest.ini
@@ -446,16 +446,18 @@ support-files =
   file_fullscreen-backdrop.html
   file_fullscreen-denied-inner.html
   file_fullscreen-denied.html
   file_fullscreen-esc-exit-inner.html
   file_fullscreen-esc-exit.html
   file_fullscreen-event-order.html
   file_fullscreen-featurePolicy.html
   file_fullscreen-featurePolicy-inner.html
+  file_fullscreen-focus.html
+  file_fullscreen-focus-inner.html
   file_fullscreen-hidden.html
   file_fullscreen-lenient-setters.html
   file_fullscreen-multiple-inner.html
   file_fullscreen-multiple.html
   file_fullscreen-navigation.html
   file_fullscreen-nested.html
   file_fullscreen-prefixed.html
   file_fullscreen-rollback.html
--- a/dom/html/test/test_fullscreen-api.html
+++ b/dom/html/test/test_fullscreen-api.html
@@ -31,16 +31,17 @@ var gTestWindows = [
   { test: "file_fullscreen-multiple.html",
     prefs: [["full-screen-api.exit-on.windowRaise", false],
             ["full-screen-api.exit-on.windowOpen", false]] },
   { test: "file_fullscreen-rollback.html" },
   { test: "file_fullscreen-esc-exit.html" },
   { test: "file_fullscreen-denied.html" },
   { test: "file_fullscreen-api.html" },
   { test: "file_fullscreen-hidden.html" },
+  { test: "file_fullscreen-focus.html" },
   { test: "file_fullscreen-svg-element.html" },
   { test: "file_fullscreen-navigation.html" },
   { test: "file_fullscreen-scrollbar.html" },
   { test: "file_fullscreen-selector.html" },
   { test: "file_fullscreen-shadowdom.html" },
   { test: "file_fullscreen-top-layer.html" },
   { test: "file_fullscreen-backdrop.html" },
   { test: "file_fullscreen-nested.html" },
--- a/widget/cocoa/nsCocoaWindow.h
+++ b/widget/cocoa/nsCocoaWindow.h
@@ -260,16 +260,17 @@ class nsCocoaWindow final : public nsBas
   virtual void SuppressAnimation(bool aSuppress) override;
   virtual void HideWindowChrome(bool aShouldHide) override;
 
   void WillEnterFullScreen(bool aFullScreen);
   void EnteredFullScreen(bool aFullScreen, bool aNativeMode = true);
   virtual bool PrepareForFullscreenTransition(nsISupports** aData) override;
   virtual void PerformFullscreenTransition(FullscreenTransitionStage aStage, uint16_t aDuration,
                                            nsISupports* aData, nsIRunnable* aCallback) override;
+  virtual void CleanupFullscreenTransition() override;
   nsresult MakeFullScreen(bool aFullScreen, nsIScreen* aTargetScreen = nullptr) final;
   nsresult MakeFullScreenWithNativeTransition(bool aFullScreen,
                                               nsIScreen* aTargetScreen = nullptr) final;
   NSAnimation* FullscreenTransitionAnimation() const { return mFullscreenTransitionAnimation; }
   void ReleaseFullscreenTransitionAnimation() {
     MOZ_ASSERT(mFullscreenTransitionAnimation, "Should only be called when there is animation");
     [mFullscreenTransitionAnimation release];
     mFullscreenTransitionAnimation = nil;
@@ -404,16 +405,22 @@ class nsCocoaWindow final : public nsBas
   WindowAnimationType mAnimationType;
 
   bool mWindowMadeHere;  // true if we created the window, false for embedding
   bool mSheetNeedsShow;  // if this is a sheet, are we waiting to be shown?
                          // this is used for sibling sheet contention only
   bool mInFullScreenMode;
   bool mInFullScreenTransition;  // true from the request to enter/exit fullscreen
                                  // (MakeFullScreen() call) to EnteredFullScreen()
+
+  // Ignore occlusion events caused by displaying the temporary fullscreen
+  // window during the fullscreen transition animation because only focused
+  // contexts are permitted to enter DOM fullscreen.
+  int mIgnoreOcclusionCount;
+
   bool mModal;
   bool mFakeModal;
 
   // Whether we are currently using native fullscreen. It could be false because
   // we are in the DOM fullscreen where we do not use the native fullscreen.
   bool mInNativeFullScreenMode;
 
   bool mIsAnimationSuppressed;
--- a/widget/cocoa/nsCocoaWindow.mm
+++ b/widget/cocoa/nsCocoaWindow.mm
@@ -131,16 +131,17 @@ nsCocoaWindow::nsCocoaWindow()
       mFullscreenTransitionAnimation(nil),
       mShadowStyle(StyleWindowShadow::Default),
       mBackingScaleFactor(0.0),
       mAnimationType(nsIWidget::eGenericWindowAnimation),
       mWindowMadeHere(false),
       mSheetNeedsShow(false),
       mInFullScreenMode(false),
       mInFullScreenTransition(false),
+      mIgnoreOcclusionCount(0),
       mModal(false),
       mFakeModal(false),
       mInNativeFullScreenMode(false),
       mIsAnimationSuppressed(false),
       mInReportMoveEvent(false),
       mInResize(false),
       mWindowTransformIsIdentity(true),
       mAlwaysOnTop(false),
@@ -1511,16 +1512,27 @@ NS_IMPL_ISUPPORTS0(FullscreenTransitionD
 static bool AlwaysUsesNativeFullScreen() {
   return Preferences::GetBool("full-screen-api.macos-native-full-screen", false);
 }
 
 /* virtual */ bool nsCocoaWindow::PrepareForFullscreenTransition(nsISupports** aData) {
   if (AlwaysUsesNativeFullScreen()) {
     return false;
   }
+
+  // Our fullscreen transition creates a new window occluding this window.
+  // That triggers an occlusion event which can cause DOM fullscreen requests
+  // to fail due to the context not being focused at the time the focus check
+  // is performed in the child process. Until the transition is cleaned up in
+  // CleanupFullscreenTransition(), ignore occlusion events for this window.
+  // If this method is changed to return false, the transition will not be
+  // performed and mIgnoreOcclusionCount should not be incremented.
+  MOZ_ASSERT(mIgnoreOcclusionCount >= 0);
+  mIgnoreOcclusionCount++;
+
   nsCOMPtr<nsIScreen> widgetScreen = GetWidgetScreen();
   NSScreen* cocoaScreen = ScreenHelperCocoa::CocoaScreenForScreen(widgetScreen);
 
   NSWindow* win = [[NSWindow alloc] initWithContentRect:[cocoaScreen frame]
                                               styleMask:NSWindowStyleMaskBorderless
                                                 backing:NSBackingStoreBuffered
                                                   defer:YES];
   [win setBackgroundColor:[NSColor blackColor]];
@@ -1530,16 +1542,21 @@ static bool AlwaysUsesNativeFullScreen()
   [win makeKeyAndOrderFront:nil];
 
   auto data = new FullscreenTransitionData(win);
   *aData = data;
   NS_ADDREF(data);
   return true;
 }
 
+/* virtual */ void nsCocoaWindow::CleanupFullscreenTransition() {
+  MOZ_ASSERT(mIgnoreOcclusionCount > 0);
+  mIgnoreOcclusionCount--;
+}
+
 /* virtual */ void nsCocoaWindow::PerformFullscreenTransition(FullscreenTransitionStage aStage,
                                                               uint16_t aDuration,
                                                               nsISupports* aData,
                                                               nsIRunnable* aCallback) {
   auto data = static_cast<FullscreenTransitionData*>(aData);
   FullscreenTransitionDelegate* delegate = [[FullscreenTransitionDelegate alloc] init];
   delegate->mWindow = this;
   // Storing already_AddRefed directly could cause static checking fail.
@@ -2043,16 +2060,21 @@ void nsCocoaWindow::DispatchOcclusionEve
 
   bool newOcclusionState = !([mWindow occlusionState] & NSWindowOcclusionStateVisible);
 
   // Don't dispatch if the new occlustion state is the same as the current state.
   if (mIsFullyOccluded == newOcclusionState) {
     return;
   }
 
+  MOZ_ASSERT(mIgnoreOcclusionCount >= 0);
+  if (newOcclusionState && mIgnoreOcclusionCount > 0) {
+    return;
+  }
+
   mIsFullyOccluded = newOcclusionState;
   if (mWidgetListener) {
     mWidgetListener->OcclusionStateChanged(mIsFullyOccluded);
   }
 }
 
 void nsCocoaWindow::ReportSizeEvent() {
   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;