Bug 125282 Webpage-JS can steal focus from URLbar / chrome r=enndeakin
authorMasayuki Nakano <masayuki@d-toybox.com>
Sat, 12 Dec 2009 14:17:40 +0900
changeset 35609 a8f46161c8918595239ea24a1d5e364d6f6948b6
parent 35608 ade8ba4157891141592955e17a9d6e48ba004537
child 35610 9a8561e57279ef0ba8a9e1156969c9774cb1442b
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersenndeakin
bugs125282
milestone1.9.3a1pre
Bug 125282 Webpage-JS can steal focus from URLbar / chrome r=enndeakin
browser/base/content/test/browser_drag.js
browser/base/content/test/browser_overflowScroll.js
dom/base/nsFocusManager.cpp
dom/base/nsFocusManager.h
dom/tests/Makefile.in
dom/tests/browser/Makefile.in
dom/tests/browser/browser_focus_steal_from_chrome.js
--- a/browser/base/content/test/browser_drag.js
+++ b/browser/base/content/test/browser_drag.js
@@ -15,9 +15,12 @@ function test()
 
   // set the valid attribute so dropping is allowed
   var proxyicon = document.getElementById("page-proxy-favicon")
   var oldstate = proxyicon.getAttribute("pageproxystate");
   proxyicon.setAttribute("pageproxystate", "valid");
   var dt = EventUtils.synthesizeDragStart(proxyicon, expected);
   is(dt, null, "drag on proxy icon");
   proxyicon.setAttribute("pageproxystate", oldstate);
+  // Now, the identity information panel is opened by the proxy icon click.
+  // We need to close it for next tests.
+  EventUtils.synthesizeKey("VK_ESCAPE", {}, window);
 }
--- a/browser/base/content/test/browser_overflowScroll.js
+++ b/browser/base/content/test/browser_overflowScroll.js
@@ -11,16 +11,23 @@ function isLeft(ele, msg)    is(left(ele
 function isRight(ele, msg)   is(right(ele), right(scrollbox), msg);
 function elementFromPoint(x) tabstrip._elementFromPoint(x);
 function nextLeftElement()   elementFromPoint(left(scrollbox) - 1);
 function nextRightElement()  elementFromPoint(right(scrollbox) + 1);
 
 function test() {
   waitForExplicitFinish();
 
+  // If the previous (or more) test finished with cleaning up the tabs,
+  // there may be some pending animations. That can cause a failure of
+  // this tests, so, we should test this in another stack.
+  setTimeout(doTest, 0);
+}
+
+function doTest() {
   tabstrip.smoothScroll = false;
 
   var tabMinWidth = gPrefService.getIntPref("browser.tabs.tabMinWidth");
   var tabCountForOverflow = Math.ceil(width(tabstrip) / tabMinWidth * 3);
   while (tabContainer.childNodes.length < tabCountForOverflow)
     gBrowser.addTab();
 
   tabstrip.addEventListener("overflow", runOverflowTests, false);
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -1070,17 +1070,34 @@ nsFocusManager::SetFocusInner(nsIContent
   // if the FLAG_NOSWITCHFRAME flag is used, only allow the focus to be
   // shifted away from the current element if the new shell to focus is
   // the same or an ancestor shell of the currently focused shell.
   PRBool allowFrameSwitch = !(aFlags & FLAG_NOSWITCHFRAME) ||
                             IsSameOrAncestor(newWindow, mFocusedWindow);
 
   // if the element is in the active window, frame switching is allowed and
   // the content is in a visible window, fire blur and focus events.
-  if (isElementInActiveWindow && allowFrameSwitch && IsWindowVisible(newWindow)) {
+  PRBool sendFocusEvent =
+    isElementInActiveWindow && allowFrameSwitch && IsWindowVisible(newWindow);
+
+  // When the following conditions are true:
+  //  * an element has focus
+  //  * isn't called by trusted event (i.e., called by untrusted event or by js)
+  //  * the focus is moved to another document's element
+  // we need to check the permission.
+  if (sendFocusEvent && mFocusedContent &&
+      mFocusedContent->GetOwnerDoc() != aNewContent->GetOwnerDoc()) {
+    // If the caller cannot access the current focused node, the caller should
+    // not be able to steal focus from it. E.g., When the current focused node
+    // is in chrome, any web contents should not be able to steal the focus.
+    nsCOMPtr<nsIDOMNode> domNode(do_QueryInterface(mFocusedContent));
+    sendFocusEvent = nsContentUtils::CanCallerAccess(domNode);
+  }
+
+  if (sendFocusEvent) {
     // return if blurring fails or the focus changes during the blur
     if (mFocusedWindow) {
       // if the focus is being moved to another element in the same document,
       // or to a descendant, pass the existing window to Blur so that the
       // current node in the existing window is cleared. If moving to a
       // window elsewhere, we want to maintain the current node in the
       // window but still blur it.
       PRBool currentIsSameOrAncestor = IsSameOrAncestor(mFocusedWindow, newWindow);
@@ -1104,20 +1121,20 @@ nsFocusManager::SetFocusInner(nsIContent
                 commonAncestor, !isElementInFocusedWindow))
         return;
     }
 
     Focus(newWindow, contentToFocus, aFlags, !isElementInFocusedWindow,
           aFocusChanged, PR_FALSE);
   }
   else {
-    // otherwise, for inactive windows, update the node in the window, and
-    // raise the window if desired.
+    // otherwise, for inactive windows and when the caller cannot steal the
+    // focus, update the node in the window, and  raise the window if desired.
     if (allowFrameSwitch)
-      AdjustWindowFocus(newWindow);
+      AdjustWindowFocus(newWindow, PR_TRUE);
 
     // set the focus node and method as needed
     PRUint32 focusMethod = aFocusChanged ? aFlags & FOCUSMETHOD_MASK :
                                            newWindow->GetFocusMethod();
     newWindow->SetFocusedNode(contentToFocus, focusMethod);
     if (aFocusChanged) {
       nsCOMPtr<nsIDocShell> docShell = newWindow->GetDocShell();
 
@@ -1196,17 +1213,18 @@ nsFocusManager::GetCommonAncestor(nsPIDO
     parent = child1;
   }
 
   nsCOMPtr<nsPIDOMWindow> window = do_GetInterface(parent);
   return window.forget();
 }
 
 void
-nsFocusManager::AdjustWindowFocus(nsPIDOMWindow* aWindow)
+nsFocusManager::AdjustWindowFocus(nsPIDOMWindow* aWindow,
+                                  PRBool aCheckPermission)
 {
   PRBool isVisible = IsWindowVisible(aWindow);
 
   nsCOMPtr<nsPIDOMWindow> window(aWindow);
   while (window) {
     // get the containing <iframe> or equivalent element so that it can be
     // focused below.
     nsCOMPtr<nsIContent> frameContent =
@@ -1222,16 +1240,22 @@ nsFocusManager::AdjustWindowFocus(nsPIDO
     window = do_GetInterface(parentDsti);
     if (window) {
       // if the parent window is visible but aWindow was not, then we have
       // likely moved up and out from a hidden tab to the browser window, or a
       // similar such arrangement. Stop adjusting the current nodes.
       if (IsWindowVisible(window) != isVisible)
         break;
 
+      // When aCheckPermission is true, we should check whether the caller can
+      // access the window or not.  If it cannot access, we should stop the
+      // adjusting.
+      if (aCheckPermission && !nsContentUtils::CanCallerAccess(window))
+        break;
+
       window->SetFocusedNode(frameContent);
     }
   }
 }
 
 PRBool
 nsFocusManager::IsWindowVisible(nsPIDOMWindow* aWindow)
 {
@@ -1523,17 +1547,17 @@ nsFocusManager::Focus(nsPIDOMWindow* aWi
   printf(" [Newdoc: %d FocusChanged: %d Raised: %d Flags: %d]\n",
          aIsNewDocument, aFocusChanged, aWindowRaised, aFlags);
 #endif
 
   // if this is a new document, update the parent chain of frames so that
   // focus can be traversed from the top level down to the newly focused
   // window.
   if (aIsNewDocument)
-    AdjustWindowFocus(aWindow);
+    AdjustWindowFocus(aWindow, PR_FALSE);
 
   // indicate that the window has taken focus.
   if (aWindow->TakeFocus(PR_TRUE, focusMethod))
     aIsNewDocument = PR_TRUE;
 
   mFocusedWindow = aWindow;
 
   // update the system focus.
--- a/dom/base/nsFocusManager.h
+++ b/dom/base/nsFocusManager.h
@@ -145,17 +145,17 @@ protected:
                                                     nsPIDOMWindow* aWindow2);
 
   /**
    * When aNewWindow is focused, adjust the ancestors of aNewWindow so that they
    * also have their corresponding frames focused. Thus, one can start at
    * the active top-level window and navigate down the currently focused
    * elements for each frame in the tree to get to aNewWindow.
    */
-  void AdjustWindowFocus(nsPIDOMWindow* aNewWindow);
+  void AdjustWindowFocus(nsPIDOMWindow* aNewWindow, PRBool aCheckPermission);
 
   /**
    * Returns true if aWindow is visible.
    */
   PRBool IsWindowVisible(nsPIDOMWindow* aWindow);
 
   /**
    * Checks and returns aContent if it may be focused, another content node if
--- a/dom/tests/Makefile.in
+++ b/dom/tests/Makefile.in
@@ -39,13 +39,15 @@ DEPTH		= ../..
 topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MODULE = test_dom
 
-DIRS		+= mochitest
+DIRS		+= mochitest \
+		browser \
+		$(NULL)
 XPCSHELL_TESTS = unit
 
 include $(topsrcdir)/config/rules.mk
 
new file mode 100644
--- /dev/null
+++ b/dom/tests/browser/Makefile.in
@@ -0,0 +1,51 @@
+#
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is
+# Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2007
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of the GNU General Public License Version 2 or later (the "GPL"),
+# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH		= ../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+relativesrcdir	= dom/tests/browser
+
+include $(DEPTH)/config/autoconf.mk
+include $(topsrcdir)/config/rules.mk
+
+_BROWSER_FILES = \
+		browser_focus_steal_from_chrome.js \
+
+libs::	$(_BROWSER_FILES)
+	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/dom/tests/browser/browser_focus_steal_from_chrome.js
@@ -0,0 +1,169 @@
+function test() {
+  waitForExplicitFinish();
+
+  let fm = Components.classes["@mozilla.org/focus-manager;1"]
+                     .getService(Components.interfaces.nsIFocusManager);
+
+  let tabs = [ gBrowser.mCurrentTab, gBrowser.addTab() ];
+  gBrowser.selectedTab = tabs[0];
+
+  let testingList = [
+    { uri: "data:text/html,<body onload=\"setTimeout(function () { document.getElementById('target').focus(); }, 10);\"><input id='target'></body>",
+      tagName: "INPUT", methodName: "focus" },
+    { uri: "data:text/html,<body onload=\"setTimeout(function () { document.getElementById('target').select(); }, 10);\"><input id='target'></body>",
+      tagName: "INPUT", methodName: "select" },
+    { uri: "data:text/html,<body onload=\"setTimeout(function () { document.getElementById('target').focus(); }, 10);\"><a href='about:blank' id='target'>anchor</a></body>",
+      tagName: "A", methodName: "focus" },
+    { uri: "data:text/html,<body onload=\"setTimeout(function () { document.getElementById('target').focus(); }, 10);\"><button id='target'>button</button></body>",
+      tagName: "BUTTON", methodName: "focus" },
+    { uri: "data:text/html,<body onload=\"setTimeout(function () { document.getElementById('target').focus(); }, 10);\"><select id='target'><option>item1</option></select></body>",
+      tagName: "SELECT", methodName: "focus" },
+    { uri: "data:text/html,<body onload=\"setTimeout(function () { document.getElementById('target').focus(); }, 10);\"><textarea id='target'>textarea</textarea></body>",
+      tagName: "TEXTAREA", methodName: "focus" },
+    { uri: "data:text/html,<body onload=\"setTimeout(function () { document.getElementById('target').select(); }, 10);\"><textarea id='target'>textarea</textarea></body>",
+      tagName: "TEXTAREA", methodName: "select" },
+    { uri: "data:text/html,<body onload=\"setTimeout(function () { document.getElementById('target').focus(); }, 10);\"><label id='target'><input></label></body>",
+      tagName: "INPUT", methodName: "focus of label element" },
+    { uri: "data:text/html,<body onload=\"setTimeout(function () { document.getElementById('target').focus(); }, 10);\"><fieldset><legend id='target'>legend</legend><input></fieldset></body>",
+      tagName: "INPUT", methodName: "focus of legend element" },
+    { uri: "data:text/html,<body onload=\"setTimeout(function () {" +
+           "  var element = document.getElementById('target');" +
+           "  var event = document.createEvent('MouseEvent');" +
+           "  event.initMouseEvent('mousedown', true, true, window," +
+           "    0, 0, 0, 0, 0, false, false, false, false, 0, element);" +
+           "  element.dispatchEvent(event); }, 10);\">" +
+           "<button id='target'>button</button></body>",
+      tagName: "BUTTON", methodName: "mousedown event on the button element" },
+    { uri: "data:text/html,<body onload=\"setTimeout(function () {" +
+           "  var element = document.getElementById('target');" +
+           "  var event = document.createEvent('MouseEvent');" +
+           "  event.initMouseEvent('click', true, true, window," +
+           "    1, 0, 0, 0, 0, false, false, false, false, 0, element);" +
+           "  element.dispatchEvent(event); }, 10);\">" +
+           "<label id='target'><input></label></body>",
+      tagName: "INPUT", methodName: "click event on the label element" },
+  ];
+
+  let testingIndex = -1;
+  let canRetry;
+  let callback;
+  let loadedCount;
+
+  function runNextTest() {
+    if (++testingIndex >= testingList.length) {
+      // cleaning-up...
+      let cleanTab = gBrowser.addTab();
+      gBrowser.selectedTab = cleanTab;
+      for (let i = 0; i < tabs.length; i++) {
+        gBrowser.removeTab(tabs[i]);
+      }
+      finish();
+      return;
+    }
+    callback = doTest1;
+    loadTestPage();
+  }
+
+  function loadTestPage() {
+    loadedCount = 0;
+    canRetry = 10;
+    // Set the focus to the contents.
+    tabs[0].linkedBrowser.focus();
+    for (let i = 0; i < tabs.length; i++) {
+      tabs[i].linkedBrowser.addEventListener("load", onLoad, true);
+      tabs[i].linkedBrowser.loadURI(testingList[testingIndex].uri);
+    }
+  }
+
+  function onLoad() {
+    if (++loadedCount == tabs.length) {
+      for (let i = 0; i < tabs.length; i++) {
+        tabs[i].linkedBrowser.removeEventListener("load", onLoad, true);
+      }
+      setTimeout(callback, 20);
+    }
+  }
+
+  function isPrepared() {
+    for (let i = 0; i < tabs.length; i++) {
+      if (tabs[i].linkedBrowser.contentDocument.activeElement.tagName !=
+            testingList[testingIndex].tagName) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  function doTest1() {
+    if (canRetry-- > 0 && !isPrepared()) {
+      setTimeout(callback, 10); // retry
+      return;
+    }
+
+    // The contents should be able to steal the focus from content.
+
+    // in foreground tab
+    let e = tabs[0].linkedBrowser.contentDocument.activeElement;
+    is(e.tagName, testingList[testingIndex].tagName,
+       "the foreground tab's " + testingList[testingIndex].tagName +
+       " element is not active by the " + testingList[testingIndex].methodName +
+       " (Test1: content can steal focus)");
+    is(fm.focusedElement, e,
+       "the " + testingList[testingIndex].tagName +
+       " element isn't focused by the " + testingList[testingIndex].methodName +
+       " (Test1: content can steal focus)");
+
+    // in background tab
+    e = tabs[1].linkedBrowser.contentDocument.activeElement;
+    is(e.tagName, testingList[testingIndex].tagName,
+       "the background tab's " + testingList[testingIndex].tagName +
+       " element is not active by the " + testingList[testingIndex].methodName +
+       " (Test1: content can steal focus)");
+    isnot(fm.focusedElement, e,
+          "the " + testingList[testingIndex].tagName +
+          " element is focused by the " + testingList[testingIndex].methodName +
+          " (Test1: content can steal focus)");
+
+    callback = doTest2;
+    loadTestPage();
+
+    // Set focus to chrome element before onload events of the loading contents.
+    BrowserSearch.searchBar.focus();
+  }
+
+
+  function doTest2() {
+    if (canRetry-- > 0 && !isPrepared()) {
+      setTimeout(callback, 10); // retry
+      return;
+    }
+
+    // The contents shouldn't be able to steal the focus from chrome.
+
+    // in foreground tab
+    let e = tabs[0].linkedBrowser.contentDocument.activeElement;
+    is(e.tagName, testingList[testingIndex].tagName,
+       "the foreground tab's " + testingList[testingIndex].tagName +
+       " element is not active by the " + testingList[testingIndex].methodName +
+       " (Test2: content can NOT steal focus)");
+    isnot(fm.focusedElement, e,
+          "the " + testingList[testingIndex].tagName +
+          " element is focused by the " + testingList[testingIndex].methodName +
+          " (Test2: content can NOT steal focus)");
+
+    // in background tab
+    e = tabs[1].linkedBrowser.contentDocument.activeElement;
+    is(e.tagName, testingList[testingIndex].tagName,
+       "the background tab's " + testingList[testingIndex].tagName +
+       " element is not active by the " + testingList[testingIndex].methodName +
+       " (Test2: content can NOT steal focus)");
+    isnot(fm.focusedElement, e,
+          "the " + testingList[testingIndex].tagName +
+          " element is focused by the " + testingList[testingIndex].methodName +
+          " (Test2: content can NOT steal focus)");
+
+    runNextTest();
+  }
+
+  runNextTest();
+}
\ No newline at end of file