Bug 847763, add a way to prevent an iframe to take focus, (pref'ed off by default), r=jst
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Wed, 20 Nov 2013 00:21:16 +0200
changeset 171143 e864bbc290f5da79b0bc922e9b86627302a26b1e
parent 171142 34f95bb561db01238bf3addc088e2eacd5464846
child 171144 0879fec4cfc3b1a87f883f15c0c6dcf7915e1797
push id3224
push userlsblakk@mozilla.com
push dateTue, 04 Feb 2014 01:06:49 +0000
treeherdermozilla-beta@60c04d0987f1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjst
bugs847763
milestone28.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 847763, add a way to prevent an iframe to take focus, (pref'ed off by default), r=jst
content/base/public/nsContentUtils.h
content/base/public/nsIContent.h
content/base/src/FragmentOrElement.cpp
content/base/src/nsContentUtils.cpp
content/base/src/nsGkAtomList.h
content/events/src/nsEventStateManager.cpp
content/html/content/src/nsGenericHTMLElement.h
content/html/content/src/nsGenericHTMLFrameElement.cpp
content/html/content/src/nsGenericHTMLFrameElement.h
content/html/content/test/file_ignoreuserfocus.html
content/html/content/test/mochitest.ini
content/html/content/test/test_ignoreuserfocus.html
content/mathml/content/src/nsMathMLElement.cpp
content/mathml/content/src/nsMathMLElement.h
content/svg/content/src/SVGAElement.cpp
content/svg/content/src/SVGAElement.h
content/xul/content/src/nsXULElement.cpp
content/xul/content/src/nsXULElement.h
dom/base/nsFocusManager.cpp
--- a/content/base/public/nsContentUtils.h
+++ b/content/base/public/nsContentUtils.h
@@ -1882,16 +1882,25 @@ public:
    *
    * @param aElement element to test.
    *
    * @return Whether the subdocument is tabbable.
    */
   static bool IsSubDocumentTabbable(nsIContent* aContent);
 
   /**
+   * Returns if aNode ignores user focus.
+   *
+   * @param aNode node to test
+   *
+   * @return Whether the node ignores user focus.
+   */
+  static bool IsUserFocusIgnored(nsINode* aNode);
+
+  /**
    * Flushes the layout tree (recursively)
    *
    * @param aWindow the window the flush should start at
    *
    */
   static void FlushLayoutForTree(nsIDOMWindow* aWindow);
 
   /**
--- a/content/base/public/nsIContent.h
+++ b/content/base/public/nsIContent.h
@@ -29,18 +29,18 @@ struct IMEState;
 enum nsLinkState {
   eLinkState_Unvisited  = 1,
   eLinkState_Visited    = 2,
   eLinkState_NotLink    = 3 
 };
 
 // IID for the nsIContent interface
 #define NS_ICONTENT_IID \
-{ 0x976f4cd1, 0xbdfc, 0x4a1e, \
-  { 0x82, 0x46, 0x1c, 0x13, 0x9c, 0xd3, 0x73, 0x7f } }
+{ 0x34117ca3, 0x45d0, 0x479e, \
+  { 0x91, 0x30, 0x54, 0x49, 0xa9, 0x5f, 0x25, 0x99 } }
 
 /**
  * A node of content in a document's content model. This interface
  * is supported by all content objects.
  */
 class nsIContent : public nsINode {
 public:
   typedef mozilla::widget::IMEState IMEState;
@@ -552,22 +552,18 @@ public:
    *         In: default tabindex for element (-1 nonfocusable, == 0 focusable)
    *         Out: computed tabindex
    * @param  [optional] aTabIndex the computed tab index
    *         < 0 if not tabbable
    *         == 0 if in normal tab order
    *         > 0 can be tabbed to in the order specified by this value
    * @return whether the content is focusable via mouse, kbd or script.
    */
-  virtual bool IsFocusable(int32_t *aTabIndex = nullptr, bool aWithMouse = false)
-  {
-    if (aTabIndex) 
-      *aTabIndex = -1; // Default, not tabbable
-    return false;
-  }
+  bool IsFocusable(int32_t* aTabIndex = nullptr, bool aWithMouse = false);
+  virtual bool IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse);
 
   /**
    * The method focuses (or activates) element that accesskey is bound to. It is
    * called when accesskey is activated.
    *
    * @param aKeyCausesActivation - if true then element should be activated
    * @param aIsTrustedEvent - if true then event that is cause of accesskey
    *                          execution is trusted.
--- a/content/base/src/FragmentOrElement.cpp
+++ b/content/base/src/FragmentOrElement.cpp
@@ -833,16 +833,43 @@ nsIContent::AttrValueIs(int32_t aNameSpa
                         nsIAtom* aName,
                         nsIAtom* aValue,
                         nsCaseTreatment aCaseSensitive) const
 {
   return IsElement() &&
     AsElement()->AttrValueIs(aNameSpaceID, aName, aValue, aCaseSensitive);
 }
 
+bool
+nsIContent::IsFocusable(int32_t* aTabIndex, bool aWithMouse)
+{
+  bool focusable = IsFocusableInternal(aTabIndex, aWithMouse);
+  // Ensure that the return value and aTabIndex are consistent in the case
+  // we're in userfocusignored context.
+  if (focusable || (aTabIndex && *aTabIndex != -1)) {
+    if (nsContentUtils::IsUserFocusIgnored(this)) {
+      if (aTabIndex) {
+        *aTabIndex = -1;
+      }
+      return false;
+    }
+    return focusable;
+  }
+  return false;
+}
+
+bool
+nsIContent::IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse)
+{
+  if (aTabIndex) {
+    *aTabIndex = -1; // Default, not tabbable
+  }
+  return false;
+}
+
 const nsAttrValue*
 FragmentOrElement::DoGetClasses() const
 {
   NS_NOTREACHED("Shouldn't ever be called");
   return nullptr;
 }
 
 NS_IMETHODIMP
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -69,16 +69,17 @@
 #include "nsDOMMutationObserver.h"
 #include "nsDOMTouchEvent.h"
 #include "nsError.h"
 #include "nsEventDispatcher.h"
 #include "nsEventListenerManager.h"
 #include "nsEventStateManager.h"
 #include "nsFocusManager.h"
 #include "nsGenericHTMLElement.h"
+#include "nsGenericHTMLFrameElement.h"
 #include "nsGkAtoms.h"
 #include "nsHostObjectProtocolHandler.h"
 #include "nsHtml5Module.h"
 #include "nsHtml5StringParser.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
 #include "nsICategoryManager.h"
 #include "nsIChannelEventSink.h"
 #include "nsIChannelPolicy.h"
@@ -5893,16 +5894,40 @@ nsContentUtils::IsSubDocumentTabbable(ns
   contentViewer->GetPreviousViewer(getter_AddRefs(zombieViewer));
 
   // If there are 2 viewers for the current docshell, that
   // means the current document is a zombie document.
   // Only navigate into the subdocument if it's not a zombie.
   return !zombieViewer;
 }
 
+bool
+nsContentUtils::IsUserFocusIgnored(nsINode* aNode)
+{
+  if (!nsGenericHTMLFrameElement::BrowserFramesEnabled()) {
+    return false;
+  }
+
+  // Check if our mozbrowser iframe ancestors has ignoreuserfocus attribute.
+  while (aNode) {
+    nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(aNode);
+    if (browserFrame &&
+        aNode->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::ignoreuserfocus) &&
+        browserFrame->GetReallyIsBrowserOrApp()) {
+      return true;
+    }
+    nsPIDOMWindow* win = aNode->OwnerDoc()->GetWindow();
+    if (win) {
+      aNode = win->GetFrameElementInternal();
+    }
+  }
+
+  return false;
+}
+
 void
 nsContentUtils::FlushLayoutForTree(nsIDOMWindow* aWindow)
 {
     nsCOMPtr<nsPIDOMWindow> piWin = do_QueryInterface(aWindow);
     if (!piWin)
         return;
 
     // Note that because FlushPendingNotifications flushes parents, this
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -428,16 +428,17 @@ GK_ATOM(html, "html")
 GK_ATOM(httpEquiv, "http-equiv")
 GK_ATOM(i, "i")
 GK_ATOM(icon, "icon")
 GK_ATOM(id, "id")
 GK_ATOM(_if, "if")
 GK_ATOM(iframe, "iframe")
 GK_ATOM(ignorecase, "ignorecase")
 GK_ATOM(ignorekeys, "ignorekeys")
+GK_ATOM(ignoreuserfocus, "ignoreuserfocus")
 GK_ATOM(ilayer, "ilayer")
 GK_ATOM(image, "image")
 GK_ATOM(imageClickedPoint, "image-clicked-point")
 GK_ATOM(img, "img")
 GK_ATOM(implementation, "implementation")
 GK_ATOM(implements, "implements")
 GK_ATOM(import, "import")
 GK_ATOM(inactivetitlebarcolor, "inactivetitlebarcolor")
--- a/content/events/src/nsEventStateManager.cpp
+++ b/content/events/src/nsEventStateManager.cpp
@@ -3216,16 +3216,20 @@ nsEventStateManager::PostHandleEvent(nsP
             if (xulControl) {
               bool disabled;
               xulControl->GetDisabled(&disabled);
               suppressBlur = disabled;
             }
           }
         }
 
+        if (!suppressBlur) {
+          suppressBlur = nsContentUtils::IsUserFocusIgnored(activeContent);
+        }
+
         nsIFrame* currFrame = mCurrentTarget;
 
         // When a root content which isn't editable but has an editable HTML
         // <body> element is clicked, we should redirect the focus to the
         // the <body> element.  E.g., when an user click bottom of the editor
         // where is outside of the <body> element, the <body> should be focused
         // and the user can edit immediately after that.
         //
--- a/content/html/content/src/nsGenericHTMLElement.h
+++ b/content/html/content/src/nsGenericHTMLElement.h
@@ -553,17 +553,17 @@ public:
   {
     return SetAttr(aNameSpaceID, aName, nullptr, aValue, aNotify);
   }
   virtual nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                            nsIAtom* aPrefix, const nsAString& aValue,
                            bool aNotify) MOZ_OVERRIDE;
   virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                              bool aNotify) MOZ_OVERRIDE;
-  virtual bool IsFocusable(int32_t *aTabIndex = nullptr, bool aWithMouse = false) MOZ_OVERRIDE
+  virtual bool IsFocusableInternal(int32_t *aTabIndex, bool aWithMouse) MOZ_OVERRIDE
   {
     bool isFocusable = false;
     IsHTMLFocusable(aWithMouse, &isFocusable, aTabIndex);
     return isFocusable;
   }
   /**
    * Returns true if a subclass is not allowed to override the value returned
    * in aIsFocusable.
--- a/content/html/content/src/nsGenericHTMLFrameElement.cpp
+++ b/content/html/content/src/nsGenericHTMLFrameElement.cpp
@@ -318,28 +318,43 @@ nsGenericHTMLFrameElement::IsHTMLFocusab
 
   if (!*aIsFocusable && aTabIndex) {
     *aTabIndex = -1;
   }
 
   return false;
 }
 
+bool
+nsGenericHTMLFrameElement::BrowserFramesEnabled()
+{
+  static bool sMozBrowserFramesEnabled = false;
+  static bool sBoolVarCacheInitialized = false;
+
+  if (!sBoolVarCacheInitialized) {
+    sBoolVarCacheInitialized = true;
+    Preferences::AddBoolVarCache(&sMozBrowserFramesEnabled,
+                                 "dom.mozBrowserFramesEnabled");
+  }
+
+  return sMozBrowserFramesEnabled;
+}
+
 /**
  * Return true if this frame element really is a mozbrowser or mozapp.  (It
  * needs to have the right attributes, and its creator must have the right
  * permissions.)
  */
 /* [infallible] */ nsresult
 nsGenericHTMLFrameElement::GetReallyIsBrowserOrApp(bool *aOut)
 {
   *aOut = false;
 
   // Fail if browser frames are globally disabled.
-  if (!Preferences::GetBool("dom.mozBrowserFramesEnabled")) {
+  if (!nsGenericHTMLFrameElement::BrowserFramesEnabled()) {
     return NS_OK;
   }
 
   // Fail if this frame doesn't have the mozbrowser attribute.
   bool hasMozbrowser = false;
   GetMozbrowser(&hasMozbrowser);
   if (!hasMozbrowser) {
     return NS_OK;
--- a/content/html/content/src/nsGenericHTMLFrameElement.h
+++ b/content/html/content/src/nsGenericHTMLFrameElement.h
@@ -67,16 +67,17 @@ public:
 
   virtual int32_t TabIndexDefault() MOZ_OVERRIDE;
 
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_NO_UNLINK(nsGenericHTMLFrameElement,
                                                      nsGenericHTMLElement)
 
   void SwapFrameLoaders(nsXULElement& aOtherOwner, mozilla::ErrorResult& aError);
 
+  static bool BrowserFramesEnabled();
 protected:
   // This doesn't really ensure a frame loade in all cases, only when
   // it makes sense.
   void EnsureFrameLoader();
   nsresult LoadSrc();
   nsIDocument* GetContentDocument();
   nsresult GetContentDocument(nsIDOMDocument** aContentDocument);
   already_AddRefed<nsPIDOMWindow> GetContentWindow();
new file mode 100644
--- /dev/null
+++ b/content/html/content/test/file_ignoreuserfocus.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+  <body>
+    <map name=a>
+      <area shape=rect coords=0,0,100,100 href=#fail>
+    </map>
+    <img usemap=#a src=image.png>
+    <input><iframe></iframe>
+  </body>
+</html>
--- a/content/html/content/test/mochitest.ini
+++ b/content/html/content/test/mochitest.ini
@@ -149,16 +149,17 @@ support-files =
   form_submit_server.sjs
   image.png
   image-allow-credentials.png
   image-allow-credentials.png^headers^
   nnc_lockup.gif
   reflect.js
   wakelock.ogg
   wakelock.ogv
+  file_ignoreuserfocus.html
 
 [test_a_text.html]
 [test_anchor_href_cache_invalidation.html]
 [test_applet_attributes_reflection.html]
 [test_audio_wakelock.html]
 [test_base_attributes_reflection.html]
 [test_bug100533.html]
 [test_bug109445.html]
@@ -411,8 +412,9 @@ support-files =
 [test_srcdoc.html]
 [test_style_attributes_reflection.html]
 [test_track.html]
 [test_track_disabled.html]
 [test_ul_attributes_reflection.html]
 [test_undoManager.html]
 [test_video_wakelock.html]
 [test_input_files_not_nsIFile.html]
+[test_ignoreuserfocus.html]
new file mode 100644
--- /dev/null
+++ b/content/html/content/test/test_ignoreuserfocus.html
@@ -0,0 +1,146 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+    <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  </head>
+  <body>
+    <script type="application/javascript;version=1.7">
+      "use strict";
+
+      SimpleTest.waitForExplicitFinish();
+
+      // Copied from EventUtils.js, but we want *ToWindow methods.
+      function synthesizeMouseAtPoint(left, top, aEvent, aWindow) {
+        var utils = _getDOMWindowUtils(aWindow);
+        var defaultPrevented = false;
+        if (utils) {
+          var button = aEvent.button || 0;
+          var clickCount = aEvent.clickCount || 1;
+          var modifiers = _parseModifiers(aEvent);
+          var pressure = ("pressure" in aEvent) ? aEvent.pressure : 0;
+          var inputSource = ("inputSource" in aEvent) ? aEvent.inputSource : 0;
+
+          if (("type" in aEvent) && aEvent.type) {
+            defaultPrevented = utils.sendMouseEventToWindow(aEvent.type, left, top, button, clickCount, modifiers, false, pressure, inputSource);
+          }
+          else {
+            utils.sendMouseEventToWindow("mousedown", left, top, button, clickCount, modifiers, false, pressure, inputSource);
+            utils.sendMouseEventToWindow("mouseup", left, top, button, clickCount, modifiers, false, pressure, inputSource);
+          }
+        }
+      }
+
+      function runTest() {
+        var witness = document.createElement("input");
+        witness.setAttribute("type", "text");
+        var witness2 = document.createElement("input");
+        witness2.setAttribute("type", "text");
+
+        var iframe = document.createElement("iframe");
+        iframe.setAttribute("mozbrowser", "true");
+        iframe.setAttribute("ignoreuserfocus", "true");
+        iframe.setAttribute("height", "500px");
+        iframe.setAttribute("src", "file_ignoreuserfocus.html");
+
+        iframe.addEventListener('load', function (e) {
+          // Get privileged iframe because mozbrowser iframe is not same origin
+          // with the parent. We need to access its content through the wrapper.
+          var privilegedIframe = SpecialPowers.wrap(iframe);
+          privilegedIframe.contentWindow.addEventListener("MozAfterPaint", function afterPaint(e) {
+            privilegedIframe.contentWindow.removeEventListener("MozAfterPaint", afterPaint);
+            
+            privilegedIframe.contentWindow.addEventListener("focus",
+              function(e) {
+                ok(!iframe.hasAttribute("ignoreuserfocus"), "Shouldn't get a focus event in ignoreuserfocus iframe!");
+              },
+              true);
+            privilegedIframe.contentWindow.addEventListener("blur",
+              function(e) {
+                ok(!iframe.hasAttribute("ignoreuserfocus"), "Shouldn't get a blur event in ignoreuserfocus iframe!");
+              },
+              true);
+
+            // Sanity check
+            witness.focus();
+            is(document.activeElement, witness, "witness should have the focus");
+
+            iframe.focus();
+            isnot(document.activeElement, iframe, "[explicit iframe.focus()] iframe should not get the focus");
+
+            iframe.removeAttribute("ignoreuserfocus");
+            iframe.focus();
+            is(document.activeElement, iframe, "[explicit iframe.focus()] iframe should get the focus");
+
+            iframe.setAttribute("ignoreuserfocus", "true");
+
+            // Test the case when iframe contains <input> and .focus()
+            // is called and explicit focus using mouse
+            witness.focus();
+
+            var innerInput = privilegedIframe.contentDocument.getElementsByTagName("input")[0];
+            innerInput.focus();
+            isnot(document.activeElement, iframe, "[explicit innerInput.focus()] iframe should not have the focus");
+
+            var iframeWindow = SpecialPowers.unwrap(privilegedIframe.contentWindow);
+            witness.focus();
+            synthesizeMouseAtCenter(innerInput, {}, iframeWindow);
+            is(document.activeElement, witness, "[synthesize mouse click] witness should have the focus");
+
+            // Test the case when iframe contains <iframe> and .focus()
+            // is called and explicit focus using mouse
+            witness.focus();
+
+            var innerIframe = privilegedIframe.contentDocument.getElementsByTagName("iframe")[0];
+            innerIframe.focus();
+            isnot(document.activeElement, iframe, "[explicit innerIframe.focus()] iframe should not have the focus");
+
+            witness.focus();
+            synthesizeMouseAtCenter(innerIframe, {}, iframeWindow);
+            is(document.activeElement, witness, "[synthesize mouse click inner iframe] witness should have the focus");
+
+            // Test the case when iframe contains <area> and .focus()
+            // is called and explicit focus using mouse
+            witness.focus();
+
+            // Wait for paint to setup frame for area. Currently the area frame
+            // map is reset for each paint. If we are in the middle of a paint
+            // then the area will not be focusable.
+            privilegedIframe.contentWindow.addEventListener("MozAfterPaint", function afterPaintArea(e) {
+              privilegedIframe.contentWindow.removeEventListener("MozAfterPaint", afterPaintArea);
+              var innerArea = privilegedIframe.contentDocument.getElementsByTagName("area")[0];
+              innerArea.focus();
+              isnot(document.activeElement, iframe, "[explicit innerArea.focus()] iframe should not have the focus");
+
+              witness.focus();
+              synthesizeMouseAtCenter(innerArea, {}, iframeWindow);
+              is(document.activeElement, witness, "[synthesize mouse click] witness should have the focus");
+
+              // Test tabindex
+              witness.focus();
+              is(document.activeElement, witness, "witness should have the focus");
+              synthesizeKey("VK_TAB", {});
+              isnot(document.activeElement, iframe, "[synthesize tab key] iframe should not have the focus");
+              is(document.activeElement, witness2, "witness2 should have the focus");
+
+              SimpleTest.finish();
+            });
+          });
+        });
+
+        document.body.appendChild(witness);
+        document.body.appendChild(iframe);
+        document.body.appendChild(witness2);
+      }
+      addEventListener("load", function() {
+        SpecialPowers.addPermission("browser", true, document);
+        SpecialPowers.pushPrefEnv({
+          "set": [
+            ["dom.mozBrowserFramesEnabled", true]
+          ]
+        }, runTest);
+      });
+    </script>
+  </body>
+</html>
--- a/content/mathml/content/src/nsMathMLElement.cpp
+++ b/content/mathml/content/src/nsMathMLElement.cpp
@@ -810,17 +810,17 @@ nsMathMLElement::SetIncrementScriptLevel
   mIncrementScriptLevel = aIncrementScriptLevel;
 
   NS_ASSERTION(aNotify, "We always notify!");
 
   UpdateState(true);
 }
 
 bool
-nsMathMLElement::IsFocusable(int32_t *aTabIndex, bool aWithMouse)
+nsMathMLElement::IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse)
 {
   nsCOMPtr<nsIURI> uri;
   if (IsLink(getter_AddRefs(uri))) {
     if (aTabIndex) {
       *aTabIndex = ((sTabFocusModel & eTabFocus_linksMask) == 0 ? -1 : 0);
     }
     return true;
   }
--- a/content/mathml/content/src/nsMathMLElement.h
+++ b/content/mathml/content/src/nsMathMLElement.h
@@ -75,18 +75,17 @@ public:
 
   // Set during reflow as necessary. Does a style change notification,
   // aNotify must be true.
   void SetIncrementScriptLevel(bool aIncrementScriptLevel, bool aNotify);
   bool GetIncrementScriptLevel() const {
     return mIncrementScriptLevel;
   }
 
-  virtual bool IsFocusable(int32_t *aTabIndex = nullptr,
-                             bool aWithMouse = false) MOZ_OVERRIDE;
+  virtual bool IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) MOZ_OVERRIDE;
   virtual bool IsLink(nsIURI** aURI) const MOZ_OVERRIDE;
   virtual void GetLinkTarget(nsAString& aTarget) MOZ_OVERRIDE;
   virtual already_AddRefed<nsIURI> GetHrefURI() const MOZ_OVERRIDE;
   nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                    const nsAString& aValue, bool aNotify)
   {
     return SetAttr(aNameSpaceID, aName, nullptr, aValue, aNotify);
   }
--- a/content/svg/content/src/SVGAElement.cpp
+++ b/content/svg/content/src/SVGAElement.cpp
@@ -156,17 +156,17 @@ SVGAElement::IsAttributeMapped(const nsI
     sViewportsMap
   };
 
   return FindAttributeDependence(name, map) ||
     SVGAElementBase::IsAttributeMapped(name);
 }
 
 bool
-SVGAElement::IsFocusable(int32_t *aTabIndex, bool aWithMouse)
+SVGAElement::IsFocusableInternal(int32_t *aTabIndex, bool aWithMouse)
 {
   nsCOMPtr<nsIURI> uri;
   if (IsLink(getter_AddRefs(uri))) {
     if (aTabIndex) {
       *aTabIndex = ((sTabFocusModel & eTabFocus_linksMask) == 0 ? -1 : 0);
     }
     return true;
   }
--- a/content/svg/content/src/SVGAElement.h
+++ b/content/svg/content/src/SVGAElement.h
@@ -38,17 +38,17 @@ public:
 
   // nsIContent
   virtual nsresult BindToTree(nsIDocument *aDocument, nsIContent *aParent,
                               nsIContent *aBindingParent,
                               bool aCompileEventHandlers) MOZ_OVERRIDE;
   virtual void UnbindFromTree(bool aDeep = true,
                               bool aNullParent = true) MOZ_OVERRIDE;
   NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const MOZ_OVERRIDE;
-  virtual bool IsFocusable(int32_t *aTabIndex = nullptr, bool aWithMouse = false) MOZ_OVERRIDE;
+  virtual bool IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) MOZ_OVERRIDE;
   virtual bool IsLink(nsIURI** aURI) const MOZ_OVERRIDE;
   virtual void GetLinkTarget(nsAString& aTarget) MOZ_OVERRIDE;
   virtual already_AddRefed<nsIURI> GetHrefURI() const MOZ_OVERRIDE;
   virtual nsEventStates IntrinsicState() const MOZ_OVERRIDE;
   nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                    const nsAString& aValue, bool aNotify)
   {
     return SetAttr(aNameSpaceID, aName, nullptr, aValue, aNotify);
--- a/content/xul/content/src/nsXULElement.cpp
+++ b/content/xul/content/src/nsXULElement.cpp
@@ -506,17 +506,17 @@ nsXULElement::GetEventListenerManagerFor
 static bool IsNonList(nsINodeInfo* aNodeInfo)
 {
   return !aNodeInfo->Equals(nsGkAtoms::tree) &&
          !aNodeInfo->Equals(nsGkAtoms::listbox) &&
          !aNodeInfo->Equals(nsGkAtoms::richlistbox);
 }
 
 bool
-nsXULElement::IsFocusable(int32_t *aTabIndex, bool aWithMouse)
+nsXULElement::IsFocusableInternal(int32_t *aTabIndex, bool aWithMouse)
 {
   /* 
    * Returns true if an element may be focused, and false otherwise. The inout
    * argument aTabIndex will be set to the tab order index to be used; -1 for
    * elements that should not be part of the tab order and a greater value to
    * indicate its tab order.
    *
    * Confusingly, the supplied value for the aTabIndex argument may indicate
--- a/content/xul/content/src/nsXULElement.h
+++ b/content/xul/content/src/nsXULElement.h
@@ -391,17 +391,17 @@ public:
 #endif
 
     virtual void PerformAccesskey(bool aKeyCausesActivation,
                                   bool aIsTrustedEvent) MOZ_OVERRIDE;
     nsresult ClickWithInputSource(uint16_t aInputSource);
 
     virtual nsIContent *GetBindingParent() const MOZ_OVERRIDE;
     virtual bool IsNodeOfType(uint32_t aFlags) const MOZ_OVERRIDE;
-    virtual bool IsFocusable(int32_t *aTabIndex = nullptr, bool aWithMouse = false) MOZ_OVERRIDE;
+    virtual bool IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) MOZ_OVERRIDE;
 
     NS_IMETHOD WalkContentStyleRules(nsRuleWalker* aRuleWalker) MOZ_OVERRIDE;
     virtual nsChangeHint GetAttributeChangeHint(const nsIAtom* aAttribute,
                                                 int32_t aModType) const MOZ_OVERRIDE;
     NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const MOZ_OVERRIDE;
 
     // XUL element methods
     /**
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -1391,20 +1391,22 @@ nsFocusManager::IsNonFocusableRoot(nsICo
   NS_PRECONDITION(aContent, "aContent must not be NULL");
   NS_PRECONDITION(aContent->IsInDoc(), "aContent must be in a document");
 
   // If aContent is in designMode, the root element is not focusable.
   // NOTE: in designMode, most elements are not focusable, just the document is
   //       focusable.
   // Also, if aContent is not editable but it isn't in designMode, it's not
   // focusable.
+  // And in userfocusignored context nothing is focusable.
   nsIDocument* doc = aContent->GetCurrentDoc();
   NS_ASSERTION(doc, "aContent must have current document");
   return aContent == doc->GetRootElement() &&
-           (doc->HasFlag(NODE_IS_EDITABLE) || !aContent->IsEditable());
+           (doc->HasFlag(NODE_IS_EDITABLE) || !aContent->IsEditable() ||
+            nsContentUtils::IsUserFocusIgnored(aContent));
 }
 
 nsIContent*
 nsFocusManager::CheckIfFocusable(nsIContent* aContent, uint32_t aFlags)
 {
   if (!aContent)
     return nullptr;
 
@@ -1423,19 +1425,20 @@ nsFocusManager::CheckIfFocusable(nsICont
 
   // Make sure that our frames are up to date
   doc->FlushPendingNotifications(Flush_Layout);
 
   nsIPresShell *shell = doc->GetShell();
   if (!shell)
     return nullptr;
 
-  // the root content can always be focused
+  // the root content can always be focused,
+  // except in userfocusignored context.
   if (aContent == doc->GetRootElement())
-    return aContent;
+    return nsContentUtils::IsUserFocusIgnored(aContent) ? nullptr : aContent;
 
   // cannot focus content in print preview mode. Only the root can be focused.
   nsPresContext* presContext = shell->GetPresContext();
   if (presContext && presContext->Type() == nsPresContext::eContext_PrintPreview) {
     LOGCONTENT("Cannot focus %s while in print preview", aContent)
     return nullptr;
   }
 
@@ -1876,20 +1879,28 @@ nsFocusManager::SendFocusOrBlurEvent(uin
                                      bool aWindowRaised,
                                      bool aIsRefocus)
 {
   NS_ASSERTION(aType == NS_FOCUS_CONTENT || aType == NS_BLUR_CONTENT,
                "Wrong event type for SendFocusOrBlurEvent");
 
   nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(aTarget);
 
+  nsCOMPtr<nsINode> n = do_QueryInterface(aTarget);
+  if (!n) {
+    nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(aTarget);
+    n = win ? win->GetExtantDoc() : nullptr;
+  }
+  bool dontDispatchEvent = n && nsContentUtils::IsUserFocusIgnored(n);
+
   // for focus events, if this event was from a mouse or key and event
   // handling on the document is suppressed, queue the event and fire it
   // later. For blur events, a non-zero value would be set for aFocusMethod.
-  if (aFocusMethod && aDocument && aDocument->EventHandlingSuppressed()) {
+  if (aFocusMethod && !dontDispatchEvent &&
+      aDocument && aDocument->EventHandlingSuppressed()) {
     // aFlags is always 0 when aWindowRaised is true so this won't be called
     // on a window raise.
     NS_ASSERTION(!aWindowRaised, "aWindowRaised should not be set");
 
     for (uint32_t i = mDelayedBlurFocusEvents.Length(); i > 0; --i) {
       // if this event was already queued, remove it and append it to the end
       if (mDelayedBlurFocusEvents[i - 1].mType == aType &&
           mDelayedBlurFocusEvents[i - 1].mPresShell == aPresShell &&
@@ -1909,19 +1920,21 @@ nsFocusManager::SendFocusOrBlurEvent(uin
   if (accService) {
     if (aType == NS_FOCUS_CONTENT)
       accService->NotifyOfDOMFocus(aTarget);
     else
       accService->NotifyOfDOMBlur(aTarget);
   }
 #endif
 
-  nsContentUtils::AddScriptRunner(
-    new FocusBlurEvent(aTarget, aType, aPresShell->GetPresContext(),
-                       aWindowRaised, aIsRefocus));
+  if (!dontDispatchEvent) {
+    nsContentUtils::AddScriptRunner(
+      new FocusBlurEvent(aTarget, aType, aPresShell->GetPresContext(),
+                         aWindowRaised, aIsRefocus));
+  }
 }
 
 void
 nsFocusManager::ScrollIntoView(nsIPresShell* aPresShell,
                                nsIContent* aContent,
                                uint32_t aFlags)
 {
   // if the noscroll flag isn't set, scroll the newly focused element into view