Bug 545812 - Break out of DOM full-screen mode upon non-alpha-numeric key input. r=smaug
authorChris Pearce <chris@pearce.org.nz>
Mon, 05 Sep 2011 08:40:18 +1200
changeset 77022 d7cd9750988aeb73eda714b2682e60e6b13565e7
parent 77021 4c972377ec272ff426f33ac558269699d408807c
child 77023 710f3b41a5d7c2218ff7514ca775c80a72907111
push id387
push userbzbarsky@mozilla.com
push dateTue, 27 Sep 2011 17:43:12 +0000
treeherdermozilla-aurora@ec885a01be07 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs545812
milestone9.0a1
Bug 545812 - Break out of DOM full-screen mode upon non-alpha-numeric key input. r=smaug
content/base/public/nsContentUtils.h
content/base/public/nsIDocument.h
content/base/src/nsContentUtils.cpp
content/base/src/nsDocument.cpp
content/base/src/nsDocument.h
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/test_fullscreen-api.html
layout/base/nsPresShell.cpp
modules/libpref/src/init/all.js
--- a/content/base/public/nsContentUtils.h
+++ b/content/base/public/nsContentUtils.h
@@ -1708,16 +1708,23 @@ public:
   /**
    * Returns PR_TRUE if requests for full-screen are allowed in the current
    * context. Requests are only allowed if the user initiated them (like with
    * a mouse-click or key press), unless this check has been disabled by
    * setting the pref "full-screen-api.allow-trusted-requests-only" to false.
    */
   static PRBool IsRequestFullScreenAllowed();
 
+  /**
+   * Returns PR_TRUE if key input is restricted in DOM full-screen mode
+   * to non-alpha-numeric key codes only. This mirrors the
+   * "full-screen-api.key-input-restricted" pref.
+   */
+  static PRBool IsFullScreenKeyInputRestricted();
+
   static void GetShiftText(nsAString& text);
   static void GetControlText(nsAString& text);
   static void GetMetaText(nsAString& text);
   static void GetAltText(nsAString& text);
   static void GetModifierSeparatorText(nsAString& text);
 
   /**
    * Returns if aContent has a tabbable subdocument.
@@ -1874,16 +1881,17 @@ private:
   static PRUint32 sScriptBlockerCountWhereRunnersPrevented;
 
   static nsIInterfaceRequestor* sSameOriginChecker;
 
   static PRBool sIsHandlingKeyBoardEvent;
   static PRBool sAllowXULXBL_for_file;
   static PRBool sIsFullScreenApiEnabled;
   static PRBool sTrustedFullScreenOnly;
+  static PRBool sFullScreenKeyInputRestricted;
 
   static nsHtml5Parser* sHTMLFragmentParser;
   static nsIParser* sXMLFragmentParser;
   static nsIFragmentContentSink* sXMLFragmentSink;
 
   static nsString* sShiftText;
   static nsString* sControlText;
   static nsString* sMetaText;
--- a/content/base/public/nsIDocument.h
+++ b/content/base/public/nsIDocument.h
@@ -120,20 +120,19 @@ class Loader;
 } // namespace css
 
 namespace dom {
 class Link;
 class Element;
 } // namespace dom
 } // namespace mozilla
 
-
 #define NS_IDOCUMENT_IID      \
-{ 0x15d92ce2, 0x472a, 0x4ea7,  \
- { 0xaf, 0x29, 0x47, 0x7b, 0xac, 0x98, 0xa0, 0x43 } }
+{ 0x170d5a75, 0xff0b, 0x4599,  \
+ { 0x9b, 0x68, 0x18, 0xb7, 0x42, 0xe0, 0xf9, 0xf7 } }
 
 // Flag for AddStyleSheet().
 #define NS_STYLESHEET_FROM_CATALOG                (1 << 0)
 
 // Document states
 
 // RTL locale: specific to the XUL localedir attribute
 #define NS_DOCUMENT_STATE_RTL_LOCALE              NS_DEFINE_EVENT_STATE_MACRO(0)
@@ -756,16 +755,22 @@ public:
 
   /**
    * Requests that the document make aElement the full-screen element,
    * and move into full-screen mode.
    */
   virtual void RequestFullScreen(Element* aElement) = 0;
 
   /**
+   * Requests that the document, and all documents in its hierarchy exit
+   * from DOM full-screen mode.
+   */
+  virtual void CancelFullScreen() = 0;
+
+  /**
    * Updates the full-screen status on this document, setting the full-screen
    * mode to aIsFullScreen. This doesn't affect the window's full-screen mode,
    * this updates the document's internal state which determines whether the
    * document reports as being in full-screen mode.
    */
   virtual void UpdateFullScreenStatus(PRBool aIsFullScreen) = 0;
 
   /**
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -259,16 +259,17 @@ nsString* nsContentUtils::sShiftText = n
 nsString* nsContentUtils::sControlText = nsnull;
 nsString* nsContentUtils::sMetaText = nsnull;
 nsString* nsContentUtils::sAltText = nsnull;
 nsString* nsContentUtils::sModifierSeparator = nsnull;
 
 PRBool nsContentUtils::sInitialized = PR_FALSE;
 PRBool nsContentUtils::sIsFullScreenApiEnabled = PR_FALSE;
 PRBool nsContentUtils::sTrustedFullScreenOnly = PR_TRUE;
+PRBool nsContentUtils::sFullScreenKeyInputRestricted = PR_TRUE;
 
 nsHtml5Parser* nsContentUtils::sHTMLFragmentParser = nsnull;
 nsIParser* nsContentUtils::sXMLFragmentParser = nsnull;
 nsIFragmentContentSink* nsContentUtils::sXMLFragmentSink = nsnull;
 
 static PLDHashTable sEventListenerManagersHash;
 
 class EventListenerManagerMapEntry : public PLDHashEntryHdr
@@ -388,16 +389,19 @@ nsContentUtils::Init()
                                "dom.allow_XUL_XBL_for_file");
 
   Preferences::AddBoolVarCache(&sIsFullScreenApiEnabled,
                                "full-screen-api.enabled");
 
   Preferences::AddBoolVarCache(&sTrustedFullScreenOnly,
                                "full-screen-api.allow-trusted-requests-only");
 
+  Preferences::AddBoolVarCache(&sFullScreenKeyInputRestricted,
+                               "full-screen-api.key-input-restricted");
+
   sInitialized = PR_TRUE;
 
   return NS_OK;
 }
 
 void
 nsContentUtils::GetShiftText(nsAString& text)
 {
@@ -5711,8 +5715,14 @@ nsContentUtils::IsFullScreenApiEnabled()
 {
   return sIsFullScreenApiEnabled;
 }
 
 PRBool nsContentUtils::IsRequestFullScreenAllowed()
 {
   return !sTrustedFullScreenOnly || nsEventStateManager::IsHandlingUserInput();
 }
+
+PRBool
+nsContentUtils::IsFullScreenKeyInputRestricted()
+{
+  return sFullScreenKeyInputRestricted;
+}
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -8566,32 +8566,41 @@ ResetFullScreenElementInDocTree(nsIDocum
 {
   nsIDocument* root = GetRootDocument(aDoc);
   if (root) {
     ResetFullScreenElement(root, nsnull);
   }
 }
 
 NS_IMETHODIMP
-nsDocument::MozCancelFullScreen()
+nsDocument::MozCancelFullScreen()
+{
+  if (!nsContentUtils::IsRequestFullScreenAllowed()) {
+    return NS_OK;
+  }
+  CancelFullScreen();
+  return NS_OK;
+}
+
+void
+nsDocument::CancelFullScreen()
 {
   if (!nsContentUtils::IsFullScreenApiEnabled() ||
-      !nsContentUtils::IsRequestFullScreenAllowed() ||
       !IsFullScreenDoc() ||
       !GetWindow()) {
-    return NS_OK;
+    return;
   }
 
   // Disable full-screen mode in all documents in this hierarchy.
   UpdateFullScreenStatusInDocTree(this, PR_FALSE);
 
   // Move the window out of full-screen mode.
   GetWindow()->SetFullScreen(PR_FALSE);
 
-  return NS_OK;
+  return;
 }
 
 PRBool
 nsDocument::IsFullScreenDoc()
 {
   return nsContentUtils::IsFullScreenApiEnabled() && mIsFullScreen;
 }
 
--- a/content/base/src/nsDocument.h
+++ b/content/base/src/nsDocument.h
@@ -939,16 +939,17 @@ public:
   virtual nsDOMNavigationTiming* GetNavigationTiming() const;
   virtual nsresult SetNavigationTiming(nsDOMNavigationTiming* aTiming);
 
   virtual Element* FindImageMap(const nsAString& aNormalizedMapName);
 
   virtual void ResetFullScreenElement();
   virtual Element* GetFullScreenElement();
   virtual void RequestFullScreen(Element* aElement);
+  virtual void CancelFullScreen();
   virtual void UpdateFullScreenStatus(PRBool aIsFullScreen);
   virtual PRBool IsFullScreenDoc();
 
 protected:
   friend class nsNodeUtils;
 
   /**
    * Check that aId is not empty and log a message to the console
--- a/content/html/content/test/Makefile.in
+++ b/content/html/content/test/Makefile.in
@@ -273,13 +273,14 @@ include $(topsrcdir)/config/rules.mk
 		test_bug674558.html \
 		test_bug583533.html \
 		test_restore_from_parser_fragment.html \
 		test_bug617528.html \
 		test_checked.html \
 		test_bug677658.html \
 		test_bug677463.html \
 		file_fullscreen-api.html \
+		file_fullscreen-api-keys.html \
 		test_fullscreen-api.html \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/content/html/content/test/file_fullscreen-api-keys.html
@@ -0,0 +1,250 @@
+ <!DOCTYPE HTML>
+<html>
+<!--
+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>
+</head>
+<body onload="document.body.mozRequestFullScreen();">
+
+<script type="application/javascript">
+
+/** Test for Bug 545812 **/
+
+// List of key codes, and whether they're restricted in full-screen mode.
+var keyList = [
+  // Allowed: DOM_VK_CANCEL to DOM_VK_CAPS_LOCK, inclusive
+  { code: "VK_CANCEL",        allowed: true},
+  { code: "VK_HELP",          allowed: true},
+  { code: "VK_BACK_SPACE",    allowed: true},
+  { code: "VK_TAB",           allowed: true},
+  { code: "VK_CLEAR",         allowed: true},
+  { code: "VK_RETURN",        allowed: true},
+  { code: "VK_ENTER",         allowed: true},
+  { code: "VK_SHIFT",         allowed: true},
+  { code: "VK_CONTROL",       allowed: true},
+  { code: "VK_ALT",           allowed: true},
+  { code: "VK_PAUSE",         allowed: true},
+  { code: "VK_CAPS_LOCK",     allowed: true},
+
+  { code: "VK_KANA",          allowed: false},
+  { code: "VK_HANGUL",        allowed: false},
+  { code: "VK_JUNJA",         allowed: false},
+  { code: "VK_FINAL",         allowed: false},
+  { code: "VK_HANJA",         allowed: false},
+  { code: "VK_KANJI",         allowed: false},
+  { code: "VK_ESCAPE",        allowed: false},
+  { code: "VK_CONVERT",       allowed: false},
+  { code: "VK_NONCONVERT",    allowed: false},
+  { code: "VK_ACCEPT",        allowed: false},
+  { code: "VK_MODECHANGE",    allowed: false},
+
+  // Allowed: DOM_VK_SPACE to DOM_VK_DELETE, inclusive
+  { code: "VK_SPACE",         allowed: true},
+  { code: "VK_PAGE_UP",       allowed: true},
+  { code: "VK_PAGE_DOWN",     allowed: true},
+  { code: "VK_END",           allowed: true},
+  { code: "VK_HOME",          allowed: true},
+  { code: "VK_LEFT",          allowed: true},
+  { code: "VK_UP",            allowed: true},
+  { code: "VK_RIGHT",         allowed: true},
+  { code: "VK_DOWN",          allowed: true},
+  { code: "VK_SELECT",        allowed: true},
+  { code: "VK_PRINT",         allowed: true},
+  { code: "VK_EXECUTE",       allowed: true},
+  { code: "VK_PRINTSCREEN",   allowed: true},
+  { code: "VK_INSERT",        allowed: true},
+  { code: "VK_DELETE",        allowed: true},
+
+  { code: "VK_0",             allowed: false},
+  { code: "VK_1",             allowed: false},
+  { code: "VK_2",             allowed: false},
+  { code: "VK_3",             allowed: false},
+  { code: "VK_4",             allowed: false},
+  { code: "VK_5",             allowed: false},
+  { code: "VK_6",             allowed: false},
+  { code: "VK_7",             allowed: false},
+  { code: "VK_8",             allowed: false},
+  { code: "VK_9",             allowed: false},
+
+  // Allowed: DOM_VK_SPACE to DOM_VK_DELETE, inclusive
+  { code: "VK_SEMICOLON",     allowed: true},
+  { code: "VK_EQUALS",        allowed: true},
+
+  { code: "VK_A",             allowed: false},
+  { code: "VK_B",             allowed: false},
+  { code: "VK_C",             allowed: false},
+  { code: "VK_D",             allowed: false},
+  { code: "VK_E",             allowed: false},
+  { code: "VK_F",             allowed: false},
+  { code: "VK_G",             allowed: false},
+  { code: "VK_H",             allowed: false},
+  { code: "VK_I",             allowed: false},
+  { code: "VK_J",             allowed: false},
+  { code: "VK_K",             allowed: false},
+  { code: "VK_L",             allowed: false},
+  { code: "VK_M",             allowed: false},
+  { code: "VK_N",             allowed: false},
+  { code: "VK_O",             allowed: false},
+  { code: "VK_P",             allowed: false},
+  { code: "VK_Q",             allowed: false},
+  { code: "VK_R",             allowed: false},
+  { code: "VK_S",             allowed: false},
+  { code: "VK_T",             allowed: false},
+  { code: "VK_U",             allowed: false},
+  { code: "VK_V",             allowed: false},
+  { code: "VK_W",             allowed: false},
+  { code: "VK_X",             allowed: false},
+  { code: "VK_Y",             allowed: false},
+  { code: "VK_Z",             allowed: false},
+  { code: "VK_CONTEXT_MENU",  allowed: false},
+  { code: "VK_SLEEP",         allowed: false},
+  { code: "VK_NUMPAD0",       allowed: false},
+  { code: "VK_NUMPAD1",       allowed: false},
+  { code: "VK_NUMPAD2",       allowed: false},
+  { code: "VK_NUMPAD3",       allowed: false},
+  { code: "VK_NUMPAD4",       allowed: false},
+  { code: "VK_NUMPAD5",       allowed: false},
+  { code: "VK_NUMPAD6",       allowed: false},
+  { code: "VK_NUMPAD7",       allowed: false},
+  { code: "VK_NUMPAD8",       allowed: false},
+  { code: "VK_NUMPAD9",       allowed: false},
+
+  // Allowed: DOM_VK_MULTIPLY to DOM_VK_META, inclusive
+  { code: "VK_MULTIPLY",      allowed: true},
+  { code: "VK_ADD",           allowed: true},
+  { code: "VK_SEPARATOR",     allowed: true},
+  { code: "VK_SUBTRACT",      allowed: true},
+  { code: "VK_DECIMAL",       allowed: true},
+  { code: "VK_DIVIDE",        allowed: true},
+  { code: "VK_F1",            allowed: true},
+  { code: "VK_F2",            allowed: true},
+  { code: "VK_F3",            allowed: true},
+  { code: "VK_F4",            allowed: true},
+  { code: "VK_F5",            allowed: true},
+  { code: "VK_F6",            allowed: true},
+  { code: "VK_F7",            allowed: true},
+  { code: "VK_F8",            allowed: true},
+  { code: "VK_F9",            allowed: true},
+  { code: "VK_F10",           allowed: true},
+  { code: "VK_F11",           allowed: true},
+  { code: "VK_F12",           allowed: true},
+  { code: "VK_F13",           allowed: true},
+  { code: "VK_F14",           allowed: true},
+  { code: "VK_F15",           allowed: true},
+  { code: "VK_F16",           allowed: true},
+  { code: "VK_F17",           allowed: true},
+  { code: "VK_F18",           allowed: true},
+  { code: "VK_F19",           allowed: true},
+  { code: "VK_F20",           allowed: true},
+  { code: "VK_F21",           allowed: true},
+  { code: "VK_F22",           allowed: true},
+  { code: "VK_F23",           allowed: true},
+  { code: "VK_F24",           allowed: true},
+  { code: "VK_NUM_LOCK",      allowed: true},
+  { code: "VK_SCROLL_LOCK",   allowed: true},
+  { code: "VK_COMMA",         allowed: true},
+  { code: "VK_PERIOD",        allowed: true},
+  { code: "VK_SLASH",         allowed: true},
+  { code: "VK_BACK_QUOTE",    allowed: true},
+  { code: "VK_OPEN_BRACKET",  allowed: true},
+  { code: "VK_BACK_SLASH",    allowed: true},
+  { code: "VK_CLOSE_BRACKET", allowed: true},
+  { code: "VK_QUOTE",         allowed: true},
+  { code: "VK_META",          allowed: true},
+];
+
+function ok(condition, msg) {
+  opener.ok(condition, msg);
+}
+
+function is(a, b, msg) {
+  opener.is(a, b, msg);
+}
+
+var gKeyTestIndex = 0;
+var gKeyName;
+var gKeyCode;
+var gKeyAllowed;
+var gKeyReceived = false;
+
+function keyHandler(event) {
+  event.preventDefault()
+  gKeyReceived = true;
+}
+
+function checkKeyEffect() {
+  is(document.mozFullScreen, gKeyAllowed,
+     (gKeyAllowed ? ("Should remain in full-screen mode for allowed key press " + gKeyName)
+                  : ("Should drop out of full-screen mode for restricted key press " + gKeyName)));
+
+  if (gKeyTestIndex < keyList.length) {
+    setTimeout(testNextKey, 0);
+  } else {
+    opener.keysTestFinished();
+  }
+}
+
+function testTrustedKeyEvents() {
+  document.body.focus();
+  gKeyReceived = false;
+  synthesizeKey(gKeyName, {});
+  setTimeout(checkKeyEffect, 0);
+}
+
+function testScriptInitiatedKeyEvents() {
+  // Script initiated untrusted key events should not be blocked.
+  document.body.focus();
+  gKeyReceived = false;
+  var evt = document.createEvent("KeyEvents");
+  evt.initKeyEvent("keydown", true, true, window,
+                   false, false, false, false,
+                   gKeyCode, 0);
+  document.body.dispatchEvent(evt);
+
+  evt = document.createEvent("KeyEvents");
+  evt.initKeyEvent("keypress", true, true, window,
+                   false, false, false, false,
+                   gKeyCode, 0);
+  document.body.dispatchEvent(evt);
+
+  evt = document.createEvent("KeyEvents");
+  evt.initKeyEvent("keyup", true, true, window,
+                   false, false, false, false,
+                   gKeyCode, 0);
+  document.body.dispatchEvent(evt);
+  
+  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.body.mozRequestFullScreen();
+  }
+  ok(document.mozFullScreen, "Must be in full-screen mode");
+
+  gKeyName = keyList[gKeyTestIndex].code;
+  gKeyCode = KeyEvent["DOM_" + gKeyName];
+  gKeyAllowed = keyList[gKeyTestIndex].allowed;
+  gKeyTestIndex++;
+
+  testScriptInitiatedKeyEvents();
+  testTrustedKeyEvents();
+}
+
+window.addEventListener("keydown", keyHandler, true);
+window.addEventListener("keyup", keyHandler, true);
+window.addEventListener("keypress", keyHandler, true);
+setTimeout(testNextKey, 0);
+
+</script>
+</pre>
+</body>
+</html>
--- a/content/html/content/test/file_fullscreen-api.html
+++ b/content/html/content/test/file_fullscreen-api.html
@@ -138,17 +138,17 @@ function fullScreenChange(event) {
       document.mozCancelFullScreen();
       ok(!document.mozFullScreen, "Should have left full-screen mode.");
       break;
     }
     case 7: {
       ok(!document.mozFullScreen, "Should have left full-screen mode (last time).");      
       // Set timeout for calling finish(), so that any pending "mozfullscreenchange" events
       // would have a chance to fire.
-      setTimeout(function(){opener.finish();}, 0);
+      setTimeout(function(){opener.apiTestFinished();}, 0);
       break;
     }
     default: {
       ok(false, "Should not receive any more fullscreenchange events!");
     }
   }
   fullScreenChangeCount++;
 }
--- a/content/html/content/test/test_fullscreen-api.html
+++ b/content/html/content/test/test_fullscreen-api.html
@@ -21,57 +21,61 @@ var testWindow = null;
 /*
 <html>
   <body onload='document.body.mozRequestFullScreen();'>
   </body>
 </html>
 */
 var requestFullScreenContents = "data:text/html;charset=utf-8,<html>%0D%0A  <body onload%3D'document.body.mozRequestFullScreen()%3B'>%0D%0A  <%2Fbody>%0D%0A<%2Fhtml>";
 
+var prevTrusted = false;
+var prevEnabled = false;
 
 function run() {
   document.addEventListener("mozfullscreenchange",
     function(){ok(false, "Should never receive a mozfullscreenchange event in the main window.");},
     false);
 
   // Ensure the full-screen api is enabled, and will be disabled on test exit.
-  var prevEnabled = SpecialPowers.getBoolPref("full-screen-api.enabled");
+  prevEnabled = SpecialPowers.getBoolPref("full-screen-api.enabled");
   SpecialPowers.setBoolPref("full-screen-api.enabled", true);
 
-  var prevTrusted = SpecialPowers.getBoolPref("full-screen-api.allow-trusted-requests-only");
+  prevTrusted = SpecialPowers.getBoolPref("full-screen-api.allow-trusted-requests-only");
 
   // Request full-screen from a non trusted context (this script isn't a user
   // generated event!). We should not receive a "mozfullscreenchange" event.
   SpecialPowers.setBoolPref("full-screen-api.allow-trusted-requests-only", true);
   document.body.mozRequestFullScreen();
 
   // Disable the requirement for trusted contexts only, so the tests are easier
   // to write.
   SpecialPowers.setBoolPref("full-screen-api.allow-trusted-requests-only", false);
-  
-  window.addEventListener("unload", function() {
-    SpecialPowers.setBoolPref("full-screen-api.enabled", prevEnabled);
-    SpecialPowers.setBoolPref("full-screen-api.allow-trusted-requests-only", prevTrusted);	
-  }, false);
-  
+
   // Load an iframe whose contents requests full-screen. This request should
   // fail, and we should never receive a "mozfullscreenchange" event, because the
   // iframe doesn't have a mozallowfullscreen attribute.
   var iframe = document.createElement("iframe");
   iframe.src = requestFullScreenContents;
   document.body.appendChild(iframe);
  
   // Run the tests which go full-screen in a new window, as mochitests normally
   // run in an iframe, which by default will not have the mozallowfullscreen
   // attribute set, so full-screen won't work.
   testWindow = window.open("file_fullscreen-api.html", "", "width=500,height=500");
 }
 
-function finish() {
+function apiTestFinished() {
   testWindow.close();
+  testWindow = window.open("file_fullscreen-api-keys.html", "", "width=500,height=500");
+}
+
+function keysTestFinished() {
+  testWindow.close();
+  SpecialPowers.setBoolPref("full-screen-api.enabled", prevEnabled);
+  SpecialPowers.setBoolPref("full-screen-api.allow-trusted-requests-only", prevTrusted);	
   SimpleTest.finish();
 }
 
 addLoadEvent(run);
 SimpleTest.waitForExplicitFinish();
 
 </script>
 </pre>
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -6983,16 +6983,53 @@ static PRBool CanHandleContextMenuEvent(
          }
       }
     }
   }
 #endif
   return PR_TRUE;
 }
 
+static PRBool
+IsFullScreenAndRestrictedKeyEvent(nsIContent* aTarget, const nsEvent* aEvent)
+{
+  NS_ABORT_IF_FALSE(aEvent, "Must have an event to check.");
+
+  // Bail out if the event is not a key event, or the target's document is
+  // not in DOM full screen mode, or full-screen key input is not restricted.
+  nsIDocument *doc;
+  if (!aTarget ||
+      (aEvent->message != NS_KEY_DOWN &&
+      aEvent->message != NS_KEY_UP &&
+      aEvent->message != NS_KEY_PRESS) ||
+      !(doc = aTarget->GetOwnerDoc()) ||
+      !doc->IsFullScreenDoc() ||
+      !nsContentUtils::IsFullScreenKeyInputRestricted()) {
+    return PR_FALSE;
+  }
+
+  // Key input is restricted. Determine if the key event has a restricted
+  // key code. Non-restricted codes are:
+  //   DOM_VK_CANCEL to DOM_VK_CAPS_LOCK, inclusive
+  //   DOM_VK_SPACE to DOM_VK_DELETE, inclusive
+  //   DOM_VK_SEMICOLON to DOM_VK_EQUALS, inclusive
+  //   DOM_VK_MULTIPLY to DOM_VK_META, inclusive
+  int key = static_cast<const nsKeyEvent*>(aEvent)->keyCode;
+  if ((key >= NS_VK_CANCEL && key <= NS_VK_CAPS_LOCK) ||
+      (key >= NS_VK_SPACE && key <= NS_VK_DELETE) ||
+      (key >= NS_VK_SEMICOLON && key <= NS_VK_EQUALS) ||
+      (key >= NS_VK_MULTIPLY && key <= NS_VK_META)) {
+    return PR_FALSE;
+  }
+
+  // Otherwise, fullscreen is enabled, key input is restricted, and the key
+  // code is not an allowed key code.
+  return PR_TRUE;
+}
+
 nsresult
 PresShell::HandleEventInternal(nsEvent* aEvent, nsIView *aView,
                                nsEventStatus* aStatus)
 {
   NS_TIME_FUNCTION_MIN(1.0);
 
 #ifdef ACCESSIBILITY
   if (aEvent->eventStructType == NS_ACCESSIBLE_EVENT)
@@ -7024,21 +7061,31 @@ PresShell::HandleEventInternal(nsEvent* 
   nsresult rv = NS_OK;
 
   if (!NS_EVENT_NEEDS_FRAME(aEvent) || GetCurrentEventFrame()) {
     PRBool isHandlingUserInput = PR_FALSE;
 
     // XXX How about IME events and input events for plugins?
     if (NS_IS_TRUSTED_EVENT(aEvent)) {
       switch (aEvent->message) {
-      case NS_MOUSE_BUTTON_DOWN:
-      case NS_MOUSE_BUTTON_UP:
       case NS_KEY_PRESS:
       case NS_KEY_DOWN:
       case NS_KEY_UP:
+        if (IsFullScreenAndRestrictedKeyEvent(mCurrentEventContent, aEvent) &&
+            aEvent->message == NS_KEY_DOWN) {
+          // We're in DOM full-screen mode, and a key with a restricted key
+          // code has been pressed. Exit full-screen mode.
+          NS_DispatchToCurrentThread(
+            NS_NewRunnableMethod(mCurrentEventContent->GetOwnerDoc(),
+                                 &nsIDocument::CancelFullScreen));
+        }
+        // Else not full-screen mode or key code is unrestricted, fall
+        // through to normal handling.
+      case NS_MOUSE_BUTTON_DOWN:
+      case NS_MOUSE_BUTTON_UP:
         isHandlingUserInput = PR_TRUE;
         break;
       case NS_DRAGDROP_DROP:
         nsCOMPtr<nsIDragSession> session = nsContentUtils::GetDragSession();
         if (session) {
           PRBool onlyChromeDrop = PR_FALSE;
           session->GetOnlyChromeDrop(&onlyChromeDrop);
           if (onlyChromeDrop) {
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -3345,11 +3345,12 @@ pref("notification.feature.enabled", fal
 pref("alerts.slideIncrement", 1);
 pref("alerts.slideIncrementTime", 10);
 pref("alerts.totalOpenTime", 4000);
 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.key-input-restricted", true);
  
 //3D Transforms
 pref("layout.3d-transforms.enabled", false);