Bug 1302341 - Part 1: SVGAElement focus checks tabIndex first and to be 0 by default; r?heycam draft
authordmu@mozilla.com <dmu@mozilla.com>
Wed, 28 Sep 2016 02:21:50 +0000
changeset 479954 24cb0355279d837799e3ce0a8eac5ec8b990df77
parent 479651 af8a2573d0f1e9cc6f2ba0ab67d7a702a197f177
child 479955 8e580e42c4cbd42b34f091b5ba7c66b5b11d3346
push id44406
push userbmo:dmu@mozilla.com
push dateTue, 07 Feb 2017 15:40:49 +0000
reviewersheycam
bugs1302341
milestone54.0a1
Bug 1302341 - Part 1: SVGAElement focus checks tabIndex first and to be 0 by default; r?heycam MozReview-Commit-ID: BjT03C3aYUK
dom/svg/SVGAElement.cpp
dom/svg/SVGAElement.h
dom/svg/test/test_tabindex.html
--- a/dom/svg/SVGAElement.cpp
+++ b/dom/svg/SVGAElement.cpp
@@ -182,34 +182,87 @@ SVGAElement::IsAttributeMapped(const nsI
     sTextContentElementsMap,
     sViewportsMap
   };
 
   return FindAttributeDependence(name, map) ||
     SVGAElementBase::IsAttributeMapped(name);
 }
 
-bool
-SVGAElement::IsFocusableInternal(int32_t *aTabIndex, bool aWithMouse)
+int32_t
+SVGAElement::TabIndexDefault()
+{
+  return 0;
+}
+
+static bool
+IsNodeInEditableRegion(nsINode* aNode)
 {
-  nsCOMPtr<nsIURI> uri;
-  if (IsLink(getter_AddRefs(uri))) {
-    if (aTabIndex) {
-      *aTabIndex = ((sTabFocusModel & eTabFocus_linksMask) == 0 ? -1 : 0);
+  while (aNode) {
+    if (aNode->IsEditable()) {
+      return true;
     }
-    return true;
+    aNode = aNode->GetParent();
   }
-  if (nsSVGElement::IsFocusableInternal(aTabIndex, aWithMouse)) {
+  return false;
+}
+
+bool
+SVGAElement::IsSVGFocusable(bool* aIsFocusable, int32_t* aTabIndex)
+{
+  if (nsSVGElement::IsSVGFocusable(aIsFocusable, aTabIndex)) {
     return true;
   }
 
-  if (aTabIndex) {
+  // cannot focus links if there is no link handler
+  nsIDocument* doc = GetComposedDoc();
+  if (doc) {
+    nsIPresShell* presShell = doc->GetShell();
+    if (presShell) {
+      nsPresContext* presContext = presShell->GetPresContext();
+      if (presContext && !presContext->GetLinkHandler()) {
+        *aIsFocusable = false;
+        return false;
+      }
+    }
+  }
+
+  // Links that are in an editable region should never be focusable, even if
+  // they are in a contenteditable="false" region.
+  if (IsNodeInEditableRegion(this)) {
+    if (aTabIndex) {
+      *aTabIndex = -1;
+    }
+
+    *aIsFocusable = false;
+
+    return true;
+  }
+
+  if (!HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex)) {
+    // check whether we're actually a link
+    if (!Link::HasURI()) {
+      // Not tabbable or focusable without href (bug 17605), unless
+      // forced to be via presence of nonnegative tabindex attribute
+      if (aTabIndex) {
+        *aTabIndex = -1;
+      }
+
+      *aIsFocusable = false;
+
+      return false;
+    }
+  }
+
+  if (aTabIndex && (sTabFocusModel & eTabFocus_linksMask) == 0) {
     *aTabIndex = -1;
   }
 
+  *aIsFocusable = true;
+
   return false;
 }
 
 bool
 SVGAElement::IsLink(nsIURI** aURI) const
 {
   // To be a clickable XLink for styling and interaction purposes, we require:
   //
--- a/dom/svg/SVGAElement.h
+++ b/dom/svg/SVGAElement.h
@@ -45,17 +45,18 @@ public:
 
   // nsIContent
   virtual nsresult BindToTree(nsIDocument *aDocument, nsIContent *aParent,
                               nsIContent *aBindingParent,
                               bool aCompileEventHandlers) override;
   virtual void UnbindFromTree(bool aDeep = true,
                               bool aNullParent = true) override;
   NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const override;
-  virtual bool IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) override;
+  virtual int32_t TabIndexDefault() override;
+  virtual bool IsSVGFocusable(bool* aIsFocusable, int32_t* aTabIndex) override;
   virtual bool IsLink(nsIURI** aURI) const override;
   virtual void GetLinkTarget(nsAString& aTarget) override;
   virtual already_AddRefed<nsIURI> GetHrefURI() const override;
   virtual EventStates IntrinsicState() const override;
   nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                    const nsAString& aValue, bool aNotify)
   {
     return SetAttr(aNameSpaceID, aName, nullptr, aValue, aNotify);
--- a/dom/svg/test/test_tabindex.html
+++ b/dom/svg/test/test_tabindex.html
@@ -11,54 +11,89 @@
 <svg xmlns="http://www.w3.org/2000/svg" overflow="visible">
   <foreignObject id="f" x="0" y="0" width="200" height="60" tabindex="0">
     <body xmlns="http://www.w3.org/1999/xhtml">
       <p>Here is a paragraph that requires word wrap</p>
     </body>
   </foreignObject>
   <rect id="r" x="0" y="70" width="100" height="100" fill="yellow" tabindex="1"/>
   <text id="t" x="0" y="200" tabindex="2">
-        This is SVG text
+    This is SVG text
   </text>
+  <a xlink:href="#" id="l1" tabindex="3">
+    <circle cx="10" cy="230" r="10"/>
+  </a>
+  <a id="l2" tabindex="4">
+    <circle cx="10" cy="260" r="10"/>
+  </a>
 </svg>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 SimpleTest.waitForExplicitFinish();
 
 function main()
 {
   var f = document.getElementById('f');
   var r = document.getElementById('r');
   var t = document.getElementById('t');
+  var l1 = document.getElementById('l1');
+  var l2 = document.getElementById('l2');
+  const isMac = ("nsILocalFileMac" in SpecialPowers.Ci);
 
   try {
     // Step 1: Checking by assigning tabIndex
     is(f.tabIndex, 0, "foreignObject initial tabIndex");
     f.tabIndex = 1;
     is(f.tabIndex, 1, "foreignObject tabIndex is set to 1");
 
     is(r.tabIndex, 1, "rect initial tabIndex");
     r.tabIndex = 2;
     is(r.tabIndex, 2, "rect tabIndex is set to 2");
 
     is(t.tabIndex, 2, "text initial tabIndex");
     t.tabIndex = 3;
     is(t.tabIndex, 3, "text is set to 3");
 
+    is(l1.tabIndex, 3, "link initial tabIndex");
+    l1.tabIndex = 4;
+    is(l1.tabIndex, 4, "link is set to 4");
+
+    is(l2.tabIndex, 4, "non-link initial tabIndex");
+    l2.tabIndex = 5;
+    is(l2.tabIndex, 5, "non-link is set to 4");
+
     // Step 2: Checking by triggering TAB event
     is(document.activeElement.tabIndex, -1, "In the beginning, the active element tabindex is -1");
 
     synthesizeKey("VK_TAB", {});
     is(document.activeElement.tabIndex, 1, "The active element tabindex is 1");
 
     synthesizeKey("VK_TAB", {});
     is(document.activeElement.tabIndex, 2, "The active element tabindex is 2");
 
     synthesizeKey("VK_TAB", {});
     is(document.activeElement.tabIndex, 3, "The active element tabindex is 3");
+
+    synthesizeKey("VK_TAB", {});
+    // On Mac, SVG link elements should not be focused.
+    if (isMac) {
+      is(document.activeElement.tabIndex, 3, "The active element tabindex is 3");
+    } else {
+      is(document.activeElement.tabIndex, 4, "The active element tabindex is 4");
+    }
+
+    synthesizeKey("VK_TAB", {});
+    // On Mac, SVG link elements should not be focused.
+    if (isMac) {
+      // This test has to be run with other tests, otherwise,
+      // document.activeElement.tabIndex will be -1 on Mac.
+      is(document.activeElement.tabIndex, 3, "The active element tabindex is 3");
+    } else {
+      is(document.activeElement.tabIndex, 5, "The active element tabindex is 5");
+    }
   } catch(e) {
     ok(false, "Got unexpected exception" + e);
   }
 
   SimpleTest.finish();
 }
 
 window.addEventListener("load", main);