Bug 690989 - Work around fullscreenchange events sometimes being dispatched before fullscreen transition completes on Linux. r=smaug
authorChris Pearce <cpearce@mozilla.com>
Tue, 09 Oct 2012 11:21:57 +1300
changeset 110263 e71ee7ecd8564ac10ccbd7cebc666da37819caec
parent 110255 0be7bfea4744e871588a4248c0fe15213fb5d5ff
child 110264 e56bbbca15e532a32d6949215d9ebf223c33beb7
push id93
push usernmatsakis@mozilla.com
push dateWed, 31 Oct 2012 21:26:57 +0000
reviewerssmaug
bugs690989
milestone19.0a1
Bug 690989 - Work around fullscreenchange events sometimes being dispatched before fullscreen transition completes on Linux. r=smaug
content/html/content/test/Makefile.in
content/html/content/test/file_fullscreen-api-keys.html
content/html/content/test/file_fullscreen-api.html
content/html/content/test/file_fullscreen-denied.html
content/html/content/test/file_fullscreen-esc-exit.html
content/html/content/test/file_fullscreen-plugins.html
content/html/content/test/file_fullscreen-rollback.html
content/html/content/test/file_fullscreen-utils.js
--- a/content/html/content/test/Makefile.in
+++ b/content/html/content/test/Makefile.in
@@ -238,16 +238,17 @@ MOCHITEST_FILES = \
 		test_bug660959-1.html \
 		test_bug660959-2.html \
 		test_bug660959-3.html \
 		test_checked.html \
 		test_bug677658.html \
 		test_bug677463.html \
 		test_bug682886.html \
 		test_bug717819.html \
+		file_fullscreen-utils.js \
 		file_fullscreen-api.html \
 		file_fullscreen-api-keys.html \
 		test_fullscreen-api.html \
 		file_fullscreen-plugins.html \
 		file_fullscreen-denied.html \
 		file_fullscreen-denied-inner.html \
 		file_fullscreen-hidden.html \
 		file_fullscreen-navigation.html \
--- a/content/html/content/test/file_fullscreen-api-keys.html
+++ b/content/html/content/test/file_fullscreen-api-keys.html
@@ -4,16 +4,17 @@
 https://bugzilla.mozilla.org/show_bug.cgi?id=545812
 
 Test that restricted key pressed drop documents out of DOM full-screen mode.
 
 -->
 <head>
   <title>Test for Bug 545812</title>
   <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="application/javascript" src="file_fullscreen-utils.js"></script>
   <style>
   body {
     background-color: black;
   }
   </style>
 </head>
 <body>
 
@@ -46,26 +47,25 @@ function keyHandler(event) {
 }
 
 function checkKeyEffect() {
   is(gKeySuppressed, !gKeyReceived, "Should not receive key events for " + gKeyName);
   is(document.mozFullScreen, false, "Should exit full-screen mode for " + gKeyName + " press");
   if (gKeyTestIndex < keyList.length) {
     setTimeout(testNextKey, 0);
   } else {
-    document.mozCancelFullScreen();
     opener.nextTest();
   }
 }
 
 function testTrustedKeyEvents() {
   document.body.focus();
   gKeyReceived = false;
+  addFullscreenChangeContinuation("exit", checkKeyEffect);
   synthesizeKey(gKeyName, {});
-  setTimeout(checkKeyEffect, 0);
 }
 
 function testScriptInitiatedKeyEvents() {
   // Script initiated untrusted key events should not cause full-screen exit.
   document.body.focus();
   gKeyReceived = false;
   var evt = document.createEvent("KeyEvents");
   evt.initKeyEvent("keydown", true, true, window,
@@ -87,27 +87,25 @@ function testScriptInitiatedKeyEvents() 
   
   ok(gKeyReceived, "dispatchEvent should dispatch events synchronously");
   ok(document.mozFullScreen,
      "Should remain in full-screen mode for script initiated key events for " + gKeyName);
 }
 
 function testNextKey() {
   if (!document.mozFullScreen) {
-    document.addEventListener("mozfullscreenchange", reallyTestNextKey, false);
+    addFullscreenChangeContinuation("enter", reallyTestNextKey);
     document.body.mozRequestFullScreen();
   }
   else {
     reallyTestNextKey();
   }
 }
 
 function reallyTestNextKey() {
-  document.removeEventListener("mozfullscreenchange", reallyTestNextKey, false);
-
   ok(document.mozFullScreen, "Must be in full-screen mode");
 
   gKeyName = keyList[gKeyTestIndex].code;
   gKeyCode = KeyEvent["DOM_" + gKeyName];
   gKeySuppressed = keyList[gKeyTestIndex].suppressed;
   gKeyTestIndex++;
 
   testScriptInitiatedKeyEvents();
--- a/content/html/content/test/file_fullscreen-api.html
+++ b/content/html/content/test/file_fullscreen-api.html
@@ -5,16 +5,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 
 Test DOM full-screen API.
 
 -->
 <head>
   <title>Test for Bug 545812</title>
   <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="file_fullscreen-utils.js"></script>
   <style>
   body {
     background-color: black;
   }
   </style>
 </head>
 <body>
 <script type="application/javascript">
@@ -38,207 +39,189 @@ function is(a, b, msg) {
 */
 var iframeContents = "data:text/html;charset=utf-8,<html><body onload%3D'parent.SimpleTest.waitForFocus(function(){document.body.mozRequestFullScreen();});'><iframe id%3D'inner-frame'><%2Fiframe><%2Fbody><%2Fhtml>";
 
 var iframe = null;
 var outOfDocElement = null;
 var inDocElement = null;
 var container = null;
 var button = null;
-var fullScreenChangeCount = 0;
-var fullscreendenied = false;
-var fullScreenErrorRun = false;
+
 
 function sendMouseClick(element) {
   synthesizeMouseAtCenter(element, {});
 }
 
 function setRequireTrustedContext(value) {
   opener.SpecialPowers.setBoolPref("full-screen-api.allow-trusted-requests-only", value);
 }
 
 function fullScreenElement() {
   return document.getElementById('full-screen-element');
 }
 
-function fullScreenChange(event) {
-  switch (fullScreenChangeCount) {
-    case 0: {
-      ok(document.mozFullScreen, "1. Should be in full-screen mode (first time)");
-      is(event.target, document, "2. Event target should be full-screen document #1");
-      is(document.mozFullScreenElement, fullScreenElement(), "3. Full-screen element should be div element.");
-      ok(document.mozFullScreenElement.mozMatchesSelector(":-moz-full-screen"), "4. FSE should match :-moz-full-screen");
-      var fse = fullScreenElement();
-      fse.parentNode.removeChild(fse);
-      is(document.mozFullScreenElement, null, "5. Full-screen element should be null after removing.");
-      ok(!document.mozFullScreen, "6. Should have left full-screen mode when we remove full-screen element");
-      document.body.appendChild(fse);
-      ok(!document.mozFullScreen, "7. Should not return to full-screen mode when re-adding former FSE");
-      is(document.mozFullScreenElement, null, "8. Full-screen element should still be null after re-adding former FSE.");
-      break;
-    }
-    case 1: {
-      ok(!document.mozFullScreen, "9. Should have left full-screen mode (first time)");
-      is(event.target, document, "10. Event target should be full-screen document #2");
-      is(document.mozFullScreenElement, null, "11. Full-screen element should be null.");
-      iframe = document.createElement("iframe");
-      iframe.mozAllowFullScreen = true;
-      document.body.appendChild(iframe);
-      iframe.src = iframeContents;
-      break;
-    }
-    case 2: {
-      ok(document.mozFullScreen, "12. Should be back in full-screen mode (second time)");
-      is(event.target, document, "13. Event target should be full-screen document #3");
-      is(document.mozFullScreenElement, iframe, "14. Full-screen element should be iframe element.");
-      is(iframe.contentDocument.mozFullScreenElement, iframe.contentDocument.body, "15. Full-screen element in subframe should be body");
-      
-      // The iframe's body is full-screen. Cancel full-screen in the subdocument to return
-      // the full-screen element to the previous full-screen element. This causes
-      // a fullscreenchange event.
-      document.mozCancelFullScreen();
-      break;
-    }
-    case 3: {
-      ok(!document.mozFullScreen, "16. Should have left full-screen when removing FSE ancestor.");
-      is(document.mozFullScreenElement, null, "17. Full-screen element should have rolled back.");
-      is(iframe.contentDocument.mozFullScreenElement, null, "18. Full-screen element in subframe should be null");
-      
-      fullScreenElement().mozRequestFullScreen();
-      break;
-    }
-    case 4: {
-      ok(document.mozFullScreen, "19. Should be back in full-screen mode (second time)");
-      is(event.target, document, "20. Event target should be full-screen document #3");
-      is(document.mozFullScreenElement, fullScreenElement(), "21. Full-screen element should be div.");
-      
-      // Transplant the FSE into subdoc. Should exit full-screen.
-      var _innerFrame = iframe.contentDocument.getElementById("inner-frame");
-      var fse = fullScreenElement();
-      _innerFrame.contentDocument.body.appendChild(fse);
-      ok(!document.mozFullScreen, "22. Should exit full-screen after transplanting FSE");
-      is(document.mozFullScreenElement, null, "23. Full-screen element transplanted, should be null.");
-      is(iframe.contentDocument.mozFullScreenElement, null, "24. Full-screen element in outer frame should be null.");
-      is(_innerFrame.contentDocument.mozFullScreenElement, null, "25. Full-screen element in inner frame should be null.");
-      ok(!iframe.contentDocument.mozFullScreen, "26. Outer frame should not acquire full-screen status.");
-      ok(!_innerFrame.contentDocument.mozFullScreen, "27. Inner frame should not acquire full-screen status.");
-      
-      document.body.appendChild(fse);
-      break;
-    }
-    case 5: {
-      ok(!document.mozFullScreen, "28. Should be back in non-full-screen mode (second time)");
-      is(event.target, document, "29. Event target should be full-screen document #4");
-      is(document.mozFullScreenElement, null, "30. Full-screen element should be null.");
-      document.body.removeChild(iframe);
-      iframe = null;
+function enter1(event) {
+  ok(document.mozFullScreen, "1. Should be in full-screen mode (first time)");
+  is(event.target, document, "2. Event target should be full-screen document #1");
+  is(document.mozFullScreenElement, fullScreenElement(), "3. Full-screen element should be div element.");
+  ok(document.mozFullScreenElement.mozMatchesSelector(":-moz-full-screen"), "4. FSE should match :-moz-full-screen");
+  var fse = fullScreenElement();
+  addFullscreenChangeContinuation("exit", exit1);
+  fse.parentNode.removeChild(fse);
+  is(document.mozFullScreenElement, null, "5. Full-screen element should be null after removing.");
+  ok(!document.mozFullScreen, "6. Should have left full-screen mode when we remove full-screen element");
+  document.body.appendChild(fse);
+  ok(!document.mozFullScreen, "7. Should not return to full-screen mode when re-adding former FSE");
+  is(document.mozFullScreenElement, null, "8. Full-screen element should still be null after re-adding former FSE.");
+}
 
-      // Do a request out of document. It should be denied.
-      // Continue test in the following mozfullscreenerror handler.
-      outOfDocElement = document.createElement("div");
-      var f =
-      function(e) {
-        document.removeEventListener("mozfullscreenerror", f, false);
-        ok(!document.mozFullScreen, "31. Requests for full-screen from not-in-doc elements should fail.");
-        fullScreenErrorRun = true;
+function exit1(event) {
+  ok(!document.mozFullScreen, "9. Should have left full-screen mode (first time)");
+  is(event.target, document, "10. Event target should be full-screen document #2");
+  is(document.mozFullScreenElement, null, "11. Full-screen element should be null.");
+  iframe = document.createElement("iframe");
+  iframe.mozAllowFullScreen = true;
+  addFullscreenChangeContinuation("enter", enter2);
+  document.body.appendChild(iframe);
+  iframe.src = iframeContents;
+}
 
-        container = document.createElement("div");
-        inDocElement = document.createElement("div");
-        container.appendChild(inDocElement);
-        fullScreenElement().appendChild(container);
-        
-        inDocElement.mozRequestFullScreen();
-      };
-      document.addEventListener("mozfullscreenerror", f, false);
-      outOfDocElement.mozRequestFullScreen();
-
-      break;
-    }
-    case 6: {
-      ok(document.mozFullScreen, "32. Should still be in full-screen mode (third time)");
-      is(event.target, document, "33. Event target should be full-screen document #5");
-      ok(fullScreenErrorRun, "34. Should have run fullscreenerror handler from previous case.");
-      is(document.mozFullScreenElement, inDocElement, "35. FSE should be inDocElement.");
-
-      var n = container;
-      do {
-        ok(n.mozMatchesSelector(":-moz-full-screen-ancestor"), "Ancestor " + n + " should match :-moz-full-screen-ancestor")
-        n = n.parentNode;
-      } while (n && n.mozMatchesSelector);
-        
-      // Remove full-screen ancestor element from document, verify it stops being reported as current FSE.
-      container.parentNode.removeChild(container);
-      ok(!document.mozFullScreen, "36. Should exit full-screen mode after removing full-screen element ancestor from document");
-      is(document.mozFullScreenElement, null, "37. Should not have a full-screen element again.");
-      break;
-    }
-    case 7: {
-      ok(!document.mozFullScreen, "38. Should be back in non-full-screen mode (third time)");
-      setRequireTrustedContext(true);
-      fullscreendenied = false;
-      fullScreenElement().mozRequestFullScreen();
+function enter2(event) {
+  ok(document.mozFullScreen, "12. Should be back in full-screen mode (second time)");
+  is(event.target, document, "13. Event target should be full-screen document #3");
+  is(document.mozFullScreenElement, iframe, "14. Full-screen element should be iframe element.");
+  is(iframe.contentDocument.mozFullScreenElement, iframe.contentDocument.body, "15. Full-screen element in subframe should be body");
+  
+  // The iframe's body is full-screen. Cancel full-screen in the subdocument to return
+  // the full-screen element to the previous full-screen element. This causes
+  // a fullscreenchange event.
+  addFullscreenChangeContinuation("exit", exit2);
+  document.mozCancelFullScreen();
+}
 
-      setTimeout(
-        function() {
-          ok(fullscreendenied, "Request for fullscreen should have been denied because calling context isn't trusted");
-          ok(!document.mozFullScreen, "Should still be in normal mode, because calling context isn't trusted.");
-          button = document.createElement("button");
-          button.onclick = function(){fullScreenElement().mozRequestFullScreen();}
-          fullScreenElement().appendChild(button);
-          sendMouseClick(button);
-        }, 0);
-      break;
-    }
-    case 8: {
-      ok(document.mozFullScreen, "Moved to full-screen after mouse click");
-      document.mozCancelFullScreen();
-      ok(document.mozFullScreen, "Should still be in full-screen mode, because calling context isn't trusted.");
-      setRequireTrustedContext(false);
-      document.mozCancelFullScreen();
-      ok(!document.mozFullScreen, "Should have left full-screen mode.");
-      break;
-    }
-    case 9: {
-      ok(!document.mozFullScreen, "Should have left full-screen mode (last time).");
+function exit2(event) {
+  ok(!document.mozFullScreen, "16. Should have left full-screen when canceling fullscreen in ancestor document.");
+  is(document.mozFullScreenElement, null, "17. Full-screen element should have rolled back.");
+  is(iframe.contentDocument.mozFullScreenElement, null, "18. Full-screen element in subframe should be null");
+  
+  addFullscreenChangeContinuation("enter", enter3);
+  fullScreenElement().mozRequestFullScreen();
+}
 
-      SpecialPowers.setBoolPref("full-screen-api.enabled", false);
-      is(document.mozFullScreenEnabled, false, "document.mozFullScreenEnabled should be false if full-screen-api.enabled is false");
-      fullscreendenied = false;
-      fullScreenElement().mozRequestFullScreen();
-      setTimeout(
-        function() {
-          ok(!document.mozFullScreen, "Should still be in normal mode, because pref is not enabled.");
-
-          SpecialPowers.setBoolPref("full-screen-api.enabled", true);
-          is(document.mozFullScreenEnabled, true, "document.mozFullScreenEnabled should be true if full-screen-api.enabled is true");
-
-          iframe = document.createElement("iframe");
-          fullScreenElement().appendChild(iframe);
-          iframe.src = iframeContents;
-          ok(!document.mozFullScreen, "Should still be in normal mode, because iframe did not have mozallowfullscreen attribute.");
-          fullScreenElement().removeChild(iframe);
-          iframe = null;
-
-          // Set timeout for calling finish(), so that any pending "mozfullscreenchange" events
-          // would have a chance to fire.
-          setTimeout(function(){opener.nextTest();}, 0);
-        }, 0);
-      break;
-    }
-    default: {
-      ok(false, "Should not receive any more fullscreenchange events!");
-    }
-  }
-  fullScreenChangeCount++;
+function enter3(event) {
+  ok(document.mozFullScreen, "19. Should be back in full-screen mode (second time)");
+  is(event.target, document, "20. Event target should be full-screen document #3");
+  is(document.mozFullScreenElement, fullScreenElement(), "21. Full-screen element should be div.");
+  
+  // Transplant the FSE into subdoc. Should exit full-screen.
+  addFullscreenChangeContinuation("exit", exit3);
+  var _innerFrame = iframe.contentDocument.getElementById("inner-frame");
+  var fse = fullScreenElement();
+  _innerFrame.contentDocument.body.appendChild(fse);
+  ok(!document.mozFullScreen, "22. Should exit full-screen after transplanting FSE");
+  is(document.mozFullScreenElement, null, "23. Full-screen element transplanted, should be null.");
+  is(iframe.contentDocument.mozFullScreenElement, null, "24. Full-screen element in outer frame should be null.");
+  is(_innerFrame.contentDocument.mozFullScreenElement, null, "25. Full-screen element in inner frame should be null.");
+  ok(!iframe.contentDocument.mozFullScreen, "26. Outer frame should not acquire full-screen status.");
+  ok(!_innerFrame.contentDocument.mozFullScreen, "27. Inner frame should not acquire full-screen status.");
+  
+  document.body.appendChild(fse);
 }
 
-document.addEventListener("mozfullscreenerror", function(){fullscreendenied=true;}, false);
-document.addEventListener("mozfullscreenchange", fullScreenChange, false);
+function exit3(event) {
+  ok(!document.mozFullScreen, "28. Should be back in non-full-screen mode (second time)");
+  is(event.target, document, "29. Event target should be full-screen document #4");
+  is(document.mozFullScreenElement, null, "30. Full-screen element should be null.");
+  document.body.removeChild(iframe);
+  iframe = null;
+
+  // Do a request out of document. It should be denied.
+  // Continue test in the following mozfullscreenerror handler.
+  outOfDocElement = document.createElement("div");
+  addFullscreenErrorContinuation(error1);    
+  outOfDocElement.mozRequestFullScreen();
+}
+
+function error1(event) {
+  ok(!document.mozFullScreen, "31. Requests for full-screen from not-in-doc elements should fail.");
+  container = document.createElement("div");
+  inDocElement = document.createElement("div");
+  container.appendChild(inDocElement);
+  fullScreenElement().appendChild(container);
+
+  addFullscreenChangeContinuation("enter", enter4);
+  inDocElement.mozRequestFullScreen();
+}
+
+function enter4(event) {
+  ok(document.mozFullScreen, "32. Should still be in full-screen mode (third time)");
+  is(event.target, document, "33. Event target should be full-screen document #5");
+  is(document.mozFullScreenElement, inDocElement, "35. FSE should be inDocElement.");
+
+  var n = container;
+  do {
+    ok(n.mozMatchesSelector(":-moz-full-screen-ancestor"), "Ancestor " + n + " should match :-moz-full-screen-ancestor")
+    n = n.parentNode;
+  } while (n && n.mozMatchesSelector);
+    
+  // Remove full-screen ancestor element from document, verify it stops being reported as current FSE.
+  addFullscreenChangeContinuation("exit", exit4);
+  container.parentNode.removeChild(container);
+  ok(!document.mozFullScreen, "36. Should exit full-screen mode after removing full-screen element ancestor from document");
+  is(document.mozFullScreenElement, null, "37. Should not have a full-screen element again.");
+}
+
+function exit4(event) {
+  ok(!document.mozFullScreen, "38. Should be back in non-full-screen mode (third time)");
+  setRequireTrustedContext(true);
+
+  addFullscreenErrorContinuation(error2);
+  fullScreenElement().mozRequestFullScreen();
+}
+
+function error2(event) {
+  ok(!document.mozFullScreen, "Should still be in normal mode, because calling context isn't trusted.");
+  button = document.createElement("button");
+  button.onclick = function(){fullScreenElement().mozRequestFullScreen();}
+  fullScreenElement().appendChild(button);
+  addFullscreenChangeContinuation("enter", enter5);
+  sendMouseClick(button);
+}
+
+function enter5(event) {
+  ok(document.mozFullScreen, "Moved to full-screen after mouse click");
+  addFullscreenChangeContinuation("exit", exit5);
+  document.mozCancelFullScreen();
+  ok(document.mozFullScreen, "Should still be in full-screen mode, because calling context isn't trusted.");
+  setRequireTrustedContext(false);
+  document.mozCancelFullScreen();
+  ok(!document.mozFullScreen, "Should have left full-screen mode.");
+}
+
+function exit5(event) {
+  ok(!document.mozFullScreen, "Should have left full-screen mode (last time).");
+
+  SpecialPowers.setBoolPref("full-screen-api.enabled", false);
+  is(document.mozFullScreenEnabled, false, "document.mozFullScreenEnabled should be false if full-screen-api.enabled is false");
+
+  addFullscreenErrorContinuation(error3);
+  fullScreenElement().mozRequestFullScreen();
+}
+
+function error3(event) {
+  ok(!document.mozFullScreen, "Should still be in normal mode, because pref is not enabled.");
+
+  SpecialPowers.setBoolPref("full-screen-api.enabled", true);
+  is(document.mozFullScreenEnabled, true, "document.mozFullScreenEnabled should be true if full-screen-api.enabled is true");
+
+  opener.nextTest();
+}
 
 function begin() {
+  addFullscreenChangeContinuation("enter", enter1);
   fullScreenElement().mozRequestFullScreen();
 }
 
 </script>
 </pre>
 <div id="full-screen-element"></div>
 </body>
 </html>
--- a/content/html/content/test/file_fullscreen-denied.html
+++ b/content/html/content/test/file_fullscreen-denied.html
@@ -4,16 +4,17 @@
 https://bugzilla.mozilla.org/show_bug.cgi?id=545812
 
 Test DOM full-screen API.
 
 -->
 <head>
   <title>Test for Bug 545812</title>
   <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="application/javascript" src="file_fullscreen-utils.js"></script>
   <style>
   body {
     background-color: black;
   }
   </style>
 </head>
 <body>
 
@@ -24,73 +25,65 @@ Test DOM full-screen API.
 function ok(condition, msg) {
   opener.ok(condition, "[denied] " + msg);
 }
 
 function is(a, b, msg) {
   opener.is(a, b, "[denied] " + msg);
 }
 
-var fullscreendenied = false;
-
-document.addEventListener("mozfullscreenerror", function(){fullscreendenied=true;}, false);
-
 var gotFullScreenChange = false;
 
 function begin() {
   document.addEventListener("mozfullscreenchange",
     function() {
       ok(false, "Should never receive a mozfullscreenchange event in the main window.");
       gotFullScreenChange = true;
     },
     false);
 
   // Request full-screen from a non trusted context (this script isn't a user
   // generated event!).
   SpecialPowers.setBoolPref("full-screen-api.allow-trusted-requests-only", true);
-  fullscreendenied = false;
-  document.body.mozRequestFullScreen();
-  setTimeout(
+  addFullscreenErrorContinuation(
     function() {
       ok(!document.mozFullScreen, "Should not grant request in non-trusted context");
-      ok(fullscreendenied, "Request in non-trusted event handler should be denied");
       // Test requesting full-screen mode in a long-running user-generated event handler.
       // The request in the key handler should not be granted.
       window.addEventListener("keypress", keyHandler, false);
       synthesizeKey("VK_A", {});
-    }, 0);
+    });
+  document.body.mozRequestFullScreen();
 }
 
 function keyHandler(event) {
   window.removeEventListener("keypress", keyHandler, false);
-  
+
   // Busy loop until 2s has passed. We should then be past the 1 second threshold, and so
   // our request for full-screen mode should be rejected.
   var end = (new Date()).getTime() + 2000;
   while ((new Date()).getTime() < end) {
     ; // Wait...
   }
-  fullscreendenied = false;
-  document.body.mozRequestFullScreen();
-  setTimeout(
+  addFullscreenErrorContinuation(
     function() {
-      ok(fullscreendenied, "Request in long running event handler should be denied");
       ok(!document.mozFullScreen, "Should not grant request in long-running event handler.");
-      
+
       // Disable the requirement for trusted contexts only, so the tests are easier
       // to write.
-      SpecialPowers.setBoolPref("full-screen-api.allow-trusted-requests-only", false);  
-      
+      SpecialPowers.setBoolPref("full-screen-api.allow-trusted-requests-only", false);
+
       // Create an iframe without a mozallowfullscreen attribute, whose contents requests
       // full-screen. The request should be denied, and we should not receive a fullscreenchange
       // event in this document.
       var iframe = document.createElement("iframe");
       iframe.src = "file_fullscreen-denied-inner.html";
       document.body.appendChild(iframe);
-    }, 0);
+    });
+  document.body.mozRequestFullScreen();
 }
 
 function finish() {
   ok(!gotFullScreenChange, "Should not ever grant a fullscreen request in this doc.");
   opener.nextTest();
 }
 
 </script>
--- a/content/html/content/test/file_fullscreen-esc-exit.html
+++ b/content/html/content/test/file_fullscreen-esc-exit.html
@@ -5,16 +5,18 @@ https://bugzilla.mozilla.org/show_bug.cg
 
 Verify that an ESC key press in a subdoc of a full-screen doc causes us to
 exit DOM full-screen mode.
 
 -->
 <head>
   <title>Test for Bug 700764</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="application/javascript" src="file_fullscreen-utils.js"></script>
   <style>
   body:-moz-full-screen, div:-moz-full-screen {
     background-color: red;
   }
   </style>
 </head>
 <body>
 
@@ -30,32 +32,30 @@ function is(a, b, msg) {
 
 function finish() {
   opener.nextTest();
 }
 
 function fullscreenchange1(event) {
   ok(document.mozFullScreen, "Should have entered full-screen mode");
   is(document.mozFullScreenElement, document.body, "FSE should be doc");
-  document.removeEventListener("mozfullscreenchange", fullscreenchange1, false);
-  document.addEventListener("mozfullscreenchange", fullscreenchange2, false);
+  addFullscreenChangeContinuation("exit", fullscreenchange2);
   ok(!document.getElementById("subdoc").contentWindow.escKeySent, "Should not yet have sent ESC key press.");
   document.getElementById("subdoc").contentWindow.startTest();
 }
 
 function fullscreenchange2(event) {
-  document.removeEventListener("mozfullscreenchange", fullscreenchange2, false);
   ok(document.getElementById("subdoc").contentWindow.escKeySent, "Should have sent ESC key press.");
   ok(!document.getElementById("subdoc").contentWindow.escKeyReceived, "ESC key press to exit should not be delivered.");
   ok(!document.mozFullScreen, "Should have left full-screen mode on ESC key press");
   finish();
 }
 
 function begin() {
-  document.addEventListener("mozfullscreenchange", fullscreenchange1, false);
+  addFullscreenChangeContinuation("enter", fullscreenchange1);
   document.body.mozRequestFullScreen();
 }
 
 </script>
 
 <!-- This subframe conducts the test. -->
 <iframe id="subdoc" src="file_fullscreen-esc-exit-inner.html"></iframe>
 
--- a/content/html/content/test/file_fullscreen-plugins.html
+++ b/content/html/content/test/file_fullscreen-plugins.html
@@ -10,16 +10,17 @@ Test plugins with DOM full-screen API:
 * Request for full-screen is not denied when the only plugin present is windowless.
 * Adding an existing (out-of-doc) windowed plugin to a full-screen document causes document to exit full-screen.
 * Create windowed plugin and adding it to full-screen document caused exit from full-screen.
 
 -->
 <head>
   <title>Test for Bug 545812</title>
   <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="application/javascript" src="file_fullscreen-utils.js"></script>
   <style>
   body:-moz-full-screen, div:-moz-full-screen {
     background-color: red;
   }
   </style>
 </head>
 <body>
 
@@ -75,39 +76,36 @@ function startTest() {
 
   // Focus the windowed plugin. On MacOS we should still enter full-screen mode,
   // on windows the pending request for full-screen should be denied.
   e("windowed-plugin").focus();
   
   if (isMacOs) {
     // Running on MacOS, all plugins are effectively windowless, request for full-screen should be granted.
     // Continue test in the (mac-specific) "mozfullscreenchange" handler.
-    document.addEventListener("mozfullscreenchange", macFullScreenChange, false);
-    return;
+    addFullscreenChangeContinuation("enter", macFullScreenChange1);
   } else {
     // Non-MacOS, request should be denied, carry on the test after receiving error event.
-    document.addEventListener("mozfullscreenerror", nonMacTest, false);
+    addFullscreenErrorContinuation(nonMacTest);
   }
 }
 
 function nonMacTest() {
-  document.removeEventListener("mozfullscreenerror", nonMacTest, false);
   ok(!document.mozFullScreen, "Request for full-screen with focused windowed plugin should be denied.");
 
   // Focus a regular html element, and re-request full-screen, request should be granted.
   e("windowless-plugin").focus();
-  document.addEventListener("mozfullscreenchange", nonMacTest2, false);
+  addFullscreenChangeContinuation("enter", nonMacTest2);
   document.body.mozRequestFullScreen();
 }
 
 function nonMacTest2() {
-  document.removeEventListener("mozfullscreenchange", nonMacTest2, false);
   ok(document.mozFullScreen, "Request for full-screen with non-plugin focused should be granted.");
   // Focus a windowed plugin, full-screen should be revoked.
-  document.addEventListener("mozfullscreenchange", nonMacTest3, false);
+  addFullscreenChangeContinuation("exit", nonMacTest3);
   e("windowed-plugin").focus();
 }
 
 function nonMacTest3() {
   ok(!document.mozFullScreen, "Full-screen should have been revoked when windowed-plugin was focused.");
   opener.nextTest();
 }
 
@@ -115,44 +113,35 @@ var fullScreenChangeCount = 0;
 
 function createWindowedPlugin() {
   var p = document.createElement("embed");
   p.setAttribute("type", "application/x-test");
   p.setAttribute("wmode", "window");
   return p;
 }
 
-function macFullScreenChange(event) {
-  switch (fullScreenChangeCount) {
-    case 0: {
-      ok(document.mozFullScreen, "Requests for full-screen with focused windowed plugins should be granted on MacOS");
-      
-      // Create a new windowed plugin, and add that to the document. Should *not* exit full-screen mode on MacOS.
-      windowedPlugin = createWindowedPlugin();
-      document.body.appendChild(windowedPlugin);
-      
-      // Focus windowed plugin. Should not exit full-screen mode on MacOS.
-      windowedPlugin.focus();
-      
-      setTimeout(
-        function() {
-          ok(document.mozFullScreen, "Adding & focusing a windowed plugin to document should not cause full-screen to exit on MacOS.");
-          document.mozCancelFullScreen();
-        }, 0);
-          
-      break;
-    }
-    case 1: {
-      ok(!document.mozFullScreen, "Should have left full-screen mode after calling document.mozCancelFullScreen().");
-      opener.nextTest();
-      break;
-    }
-    default: {
-      ok(false, "Should not receive any more fullscreenchange events!");
-    }
-  }
-  fullScreenChangeCount++;
+function macFullScreenChange1(event) {
+  ok(document.mozFullScreen, "Requests for full-screen with focused windowed plugins should be granted on MacOS");
+  
+  // Create a new windowed plugin, and add that to the document. Should *not* exit full-screen mode on MacOS.
+  windowedPlugin = createWindowedPlugin();
+  document.body.appendChild(windowedPlugin);
+  
+  // Focus windowed plugin. Should not exit full-screen mode on MacOS.
+  windowedPlugin.focus();
+  
+  setTimeout(
+    function() {
+      ok(document.mozFullScreen, "Adding & focusing a windowed plugin to document should not cause full-screen to exit on MacOS.");
+      addFullscreenChangeContinuation("exit", macFullScreenChange2);
+      document.mozCancelFullScreen();
+    }, 0);
+}
+
+function macFullScreenChange2(event) {
+  ok(!document.mozFullScreen, "Should have left full-screen mode after calling document.mozCancelFullScreen().");
+  opener.nextTest();
 }
 
 </script>
 </pre>
 </body>
 </html>
--- a/content/html/content/test/file_fullscreen-rollback.html
+++ b/content/html/content/test/file_fullscreen-rollback.html
@@ -14,16 +14,18 @@ Tests:
 * Request full-screen in subdoc.
 * Removing FSE should fully-exit full-screen.
 
 
 -->
 <head>
   <title>Test for Bug 700764</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="application/javascript" src="file_fullscreen-utils.js"></script>
 </head>
 <body>
 
 <div id="fse">
   <div id="fse-inner">
     <iframe id="subdoc" mozallowfullscreen src="data:text/html,<html><body bgcolor='black'></body></html>"></iframe>
   </div>
 </div>
@@ -31,101 +33,96 @@ Tests:
 <div id="non-fse"></div>
 
 <script type="application/javascript">
 
 /** Test for Bug 700764 **/
 
 function ok(condition, msg) {
   opener.ok(condition, "[rollback] " + msg);
+  if (!condition) {
+    opener.finish();
+  }
 }
 
 function is(a, b, msg) {
   opener.is(a, b, "[rollback] " + msg);
+  if (a != b) {
+    opener.finish();
+  }
 }
 
-function addListener(type, f) {
-  document.addEventListener("mozfullscreen" + type, f, false);
+function enterFullscreen(element, callback) {
+  addFullscreenChangeContinuation("enter", callback);
+  element.focus();
+  element.mozRequestFullScreen();
 }
 
-function removeListener(type, f) {
-  document.removeEventListener("mozfullscreen" + type, f, false);
+function revertFullscreen(doc, callback) {
+  ok(doc.mozFullScreenElement != null, "Should only exit fullscreen on a fullscreen doc");
+  addFullscreenChangeContinuation("exit", callback, doc);
+  doc.mozCancelFullScreen();
 }
 
 function e(id) {
   return document.getElementById(id);
 }
 
 function requestFullscreen(element) {
   element.focus();
   element.mozRequestFullScreen();
 }
 
 function begin() {
-  addListener("change", change1);
-  e("fse").mozRequestFullScreen();
+  enterFullscreen(e("fse"), change1);
 }
 
 function change1() {
-  removeListener("change", change1);
-  addListener("error", error1);
   is(document.mozFullScreenElement, e("fse"), "Body should be FSE");
-  
   // Request full-screen from element not descendent from current FSE.
+  addFullscreenErrorContinuation(error1);
   requestFullscreen(e("non-fse"));
 }
 
 function error1() {
-  removeListener("error", error1);
-  addListener("change", change2);
   is(document.mozFullScreenElement, e("fse"), "FSE should not change");
   var iframe = e("subdoc");
-  requestFullscreen(iframe.contentDocument.body);
+  enterFullscreen(iframe.contentDocument.body, change2);
 }
 
 function change2() {
-  removeListener("change", change2);
   var iframe = e("subdoc");
   is(document.mozFullScreenElement, iframe, "Subdoc container should be FSE.");
   is(iframe.contentDocument.mozFullScreenElement, iframe.contentDocument.body, "Subdoc body should be FSE in subdoc");
-  addListener("change", change3);
-  iframe.contentDocument.mozCancelFullScreen();  
+  revertFullscreen(document, change3);
 }
 
 function change3() {
-  removeListener("change", change3);
   is(document.mozFullScreenElement, e("fse"), "FSE should rollback to FSE.");
-  addListener("change", change4);
-  document.mozCancelFullScreen();
+  revertFullscreen(document, change4);
 }
 
 function change4() {
-  removeListener("change", change4);
   is(document.mozFullScreenElement, null, "Should have left full-screen entirely");
-  addListener("change", change5);
-  requestFullscreen(e("fse"));
+  enterFullscreen(e("fse"), change5);
 }
 
 function change5() {
-  removeListener("change", change5);
-  addListener("change", change6);
   is(document.mozFullScreenElement, e("fse"), "FSE should be e('fse')");
-  requestFullscreen(e("fse-inner"));
+  enterFullscreen(e("fse-inner"), change6);
 }
 
 function change6() {
-  removeListener("change", change6);
-  addListener("change", change7);
+  addFullscreenChangeContinuation("exit", change7);
   var element = e('fse-inner');
   is(document.mozFullScreenElement, element, "FSE should be e('fse-inner')");
   element.parentNode.removeChild(element);
 }
 
 function change7() {
-  removeListener("change", change7);
   is(document.mozFullScreenElement, null, "Should have fully exited full-screen mode when removed FSE from doc");
   opener.nextTest();
 }
 
 </script>
 </pre>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/content/html/content/test/file_fullscreen-utils.js
@@ -0,0 +1,104 @@
+// Remember the window size in non-fullscreen mode.
+var normalSize = new function() {
+  this.w = window.outerWidth;
+  this.h = window.outerHeight;
+}();
+
+// Returns true if the window occupies the entire screen.
+// Note this only returns true once the transition from normal to
+// fullscreen mode is complete.
+function inFullscreenMode() {
+  return window.outerWidth == window.screen.width &&
+         window.outerHeight == window.screen.height;
+}
+
+// Returns true if the window is in normal mode, i.e. non fullscreen mode.
+// Note this only returns true once the transition from fullscreen back to
+// normal mode is complete.
+function inNormalMode() {
+  return window.outerWidth == normalSize.w &&
+         window.outerHeight == normalSize.h;
+}
+
+function ok(condition, msg) {
+  opener.ok(condition, "[rollback] " + msg);
+  if (!condition) {
+    opener.finish();
+  }
+}
+
+// On Linux we sometimes receive fullscreenchange events before the window
+// has finished transitioning to fullscreen. This can cause problems and
+// test failures, so work around it on Linux until we can get a proper fix.
+const workAroundFullscreenTransition = navigator.userAgent.indexOf("Linux") != -1;
+
+// Adds a listener that will be called once a fullscreen transition
+// is complete. When type==='enter', callback is called when we've
+// received a fullscreenchange event, and the fullscreen transition is
+// complete. When type==='exit', callback is called when we've
+// received a fullscreenchange event and the window dimensions match
+// the window dimensions when the window opened (so don't resize the
+// window while running your test!). inDoc is the document which
+// the listeners are added on, if absent, the listeners are added to
+// the current document.
+function addFullscreenChangeContinuation(type, callback, inDoc) {
+  var doc = inDoc || document;
+  var listener = null;
+  if (type === "enter") {
+    // when entering fullscreen, ensure we don't call 'callback' until the
+    // enter transition is complete.
+    listener = function(event) {
+      doc.removeEventListener("mozfullscreenchange", listener, false);
+      if (!workAroundFullscreenTransition) {
+        callback(event);
+        return;
+      }
+      if (!inFullscreenMode()) {
+        opener.todo(false, "fullscreenchange before entering fullscreen complete! " +
+                    " window.fullScreen=" + window.fullScreen +
+                    " normal=(" + normalSize.w + "," + normalSize.h + ")" +
+                    " outerWidth=" + window.outerWidth + " width=" + window.screen.width +
+                    " outerHeight=" + window.outerHeight + " height=" + window.screen.height);
+        setTimeout(function(){listener(event);}, 100);
+        return;
+      }
+      setTimeout(function(){callback(event)}, 0);
+    };
+  } else if (type === "exit") {
+    listener = function(event) {
+      doc.removeEventListener("mozfullscreenchange", listener, false);
+      if (!workAroundFullscreenTransition) {
+        callback(event);
+        return;
+      }
+      if (!document.mozFullScreenElement && !inNormalMode()) {
+        opener.todo(false, "fullscreenchange before exiting fullscreen complete! " +
+                    " window.fullScreen=" + window.fullScreen +
+                    " normal=(" + normalSize.w + "," + normalSize.h + ")" +
+                    " outerWidth=" + window.outerWidth + " width=" + window.screen.width +
+                    " outerHeight=" + window.outerHeight + " height=" + window.screen.height);
+        // 'document' (*not* 'doc') has no fullscreen element, so we're trying
+        // to completely exit fullscreen mode. Wait until the transition
+        // to normal mode is complete before calling callback.
+        setTimeout(function(){listener(event);}, 100);
+        return;
+      }
+      opener.info("[rollback] Exited fullscreen");
+      setTimeout(function(){callback(event);}, 0);
+    };
+  } else {
+    throw "'type' must be either 'enter', or 'exit'.";
+  }
+  doc.addEventListener("mozfullscreenchange", listener, false);
+}
+
+// Calls |callback| when the next fullscreenerror is dispatched to inDoc||document.
+function addFullscreenErrorContinuation(callback, inDoc) {
+  var doc = inDoc || document;
+  var listener = function(event) {
+    doc.removeEventListener("mozfullscreenerror", listener, false);
+    setTimeout(function(){callback(event);}, 0);
+  };
+  doc.addEventListener("mozfullscreenerror", listener, false);
+}
+