Bug 277178 - Move focus to a fragment identifier (#fragment) if it's focusable; r?smaug draft
authorTakeshi Kurosawa <taken.spc@gmail.com>
Tue, 23 Aug 2016 11:06:32 +0900
changeset 404208 267d7f6dee53c4911fce49258fed63283cde0fcf
parent 403581 f97a056ae6235de7855fd8aaa04fb1c8d183bd06
child 529134 75991217397be79c211997681c6761a98312d7a5
push id27157
push userbmo:taken.spc@gmail.com
push dateTue, 23 Aug 2016 05:13:08 +0000
reviewerssmaug
bugs277178
milestone51.0a1
Bug 277178 - Move focus to a fragment identifier (#fragment) if it's focusable; r?smaug This change improves HTML Standard compliance and browser compatibility. https://html.spec.whatwg.org/multipage/browsers.html#scroll-to-the-fragment-identifier MozReview-Commit-ID: BgOq9dPwdGF
layout/base/nsPresShell.cpp
layout/base/tests/mochitest.ini
layout/base/tests/test_bug277178.html
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -3057,20 +3057,16 @@ PresShell::GoToAnchor(const nsAString& a
           }
         }
       }
     }
   }
 
   esm->SetContentState(content, NS_EVENT_STATE_URLTARGET);
 
-#ifdef ACCESSIBILITY
-  nsIContent *anchorTarget = content;
-#endif
-
   nsIScrollableFrame* rootScroll = GetRootScrollFrameAsScrollable();
   if (rootScroll && rootScroll->DidHistoryRestore()) {
     // Scroll position restored from history trumps scrolling to anchor.
     aScroll = false;
     rootScroll->ClearDidHistoryRestore();
   }
 
   if (content) {
@@ -3091,42 +3087,53 @@ PresShell::GoToAnchor(const nsAString& a
     // Should we select the target? This action is controlled by a
     // preference: the default is to not select.
     bool selectAnchor = Preferences::GetBool("layout.selectanchor");
 
     // Even if select anchor pref is false, we must still move the
     // caret there. That way tabbing will start from the new
     // location
     RefPtr<nsIDOMRange> jumpToRange = new nsRange(mDocument);
-    while (content && content->GetFirstChild()) {
-      content = content->GetFirstChild();
-    }
-    nsCOMPtr<nsIDOMNode> node(do_QueryInterface(content));
+    nsIContent* selectionTarget = content;
+    while (selectionTarget && selectionTarget->GetFirstChild()) {
+      selectionTarget = selectionTarget->GetFirstChild();
+    }
+    nsCOMPtr<nsIDOMNode> node(do_QueryInterface(selectionTarget));
     NS_ASSERTION(node, "No nsIDOMNode for descendant of anchor");
     jumpToRange->SelectNodeContents(node);
     // Select the anchor
     nsISelection* sel = mSelection->GetSelection(SelectionType::eNormal);
     if (sel) {
       sel->RemoveAllRanges();
       sel->AddRange(jumpToRange);
       if (!selectAnchor) {
         // Use a caret (collapsed selection) at the start of the anchor
         sel->CollapseToStart();
       }
     }
     // Selection is at anchor.
-    // Now focus the document itself if focus is on an element within it.
-    nsPIDOMWindowOuter *win = mDocument->GetWindow();
-
+
+    // https://html.spec.whatwg.org/#scroll-to-the-fragment-identifier
+    // 3. Run the focusing steps for target, with the Document's viewport
+    // as the fallback target.
+    nsCOMPtr<nsIDOMElement> element(do_QueryInterface(content));
     nsIFocusManager* fm = nsFocusManager::GetFocusManager();
-    if (fm && win) {
-      nsCOMPtr<mozIDOMWindowProxy> focusedWindow;
-      fm->GetFocusedWindow(getter_AddRefs(focusedWindow));
-      if (SameCOMIdentity(win, focusedWindow)) {
-        fm->ClearFocus(focusedWindow);
+    if (fm && element) {
+      bool isFocusable = false;
+      fm->ElementIsFocusable(element, 0, &isFocusable);
+      if (isFocusable) {
+        fm->SetFocus(element, 0);
+      } else {
+        nsCOMPtr<mozIDOMWindowProxy> focusedWindow;
+        fm->GetFocusedWindow(getter_AddRefs(focusedWindow));
+
+        nsPIDOMWindowOuter* win = mDocument->GetWindow();
+        if (SameCOMIdentity(win, focusedWindow)) {
+          fm->ClearFocus(focusedWindow);
+        }
       }
     }
 
     // If the target is an animation element, activate the animation
     if (content->IsNodeOfType(nsINode::eANIMATION)) {
       SVGContentUtils::ActivateByHyperlink(content.get());
     }
   } else {
@@ -3142,20 +3149,20 @@ PresShell::GoToAnchor(const nsAString& a
       if (aScroll && sf) {
         // Scroll to the top of the page
         sf->ScrollTo(nsPoint(0, 0), nsIScrollableFrame::INSTANT);
       }
     }
   }
 
 #ifdef ACCESSIBILITY
-  if (anchorTarget) {
+  if (content) {
     nsAccessibilityService* accService = AccService();
     if (accService)
-      accService->NotifyOfAnchorJumpTo(anchorTarget);
+      accService->NotifyOfAnchorJumpTo(content);
   }
 #endif
 
   return rv;
 }
 
 nsresult
 PresShell::ScrollToAnchor()
--- a/layout/base/tests/mochitest.ini
+++ b/layout/base/tests/mochitest.ini
@@ -54,16 +54,17 @@ support-files =
   multi-range-user-select-ref.html
   multi-range-script-select.html
   multi-range-script-select-ref.html
   transformed_scrolling_repaints_3_window.html
 
 [test_preserve3d_sorting_hit_testing.html]
 [test_preserve3d_sorting_hit_testing2.html]
 [test_after_paint_pref.html]
+[test_bug277178.html]
 [test_bug370436.html]
 skip-if = buildapp == 'b2g'
 [test_bug993936.html]
 [test_border_radius_hit_testing.html]
 [test_bug66619.html]
 [test_bug93077-1.html]
 [test_bug93077-2.html]
 [test_bug93077-3.html]
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/test_bug277178.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=277178
+-->
+<head>
+<meta charset="utf-8">
+<title>Test for Bug 277178</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body onload="test()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=277178">Mozilla Bug 277178</a>
+<p id="display"></p>
+<div id="content">
+<label>#anchor01<input id="anchor01"></label>
+<div id="anchor02" tabindex="0">#anchor02</div>
+<div id="anchor03">#anchor03</div>
+</div>
+<pre id="test">
+<script>
+
+/** Test for Bug 277178 **/
+
+SimpleTest.waitForExplicitFinish();
+function test() {
+  SimpleTest.waitForFocus(function() {
+    var targets = [
+      {
+        fragment: "#anchor01",
+        activeElement: $("anchor01")
+      },
+      {
+        fragment: "#anchor02",
+        activeElement: $("anchor02")
+      },
+      {
+        fragment: "#anchor03",
+        activeElement: document.body
+      }
+    ];
+
+    for (var target of targets) {
+      location.hash = target.fragment;
+      is(document.activeElement, target.activeElement,
+        "The focus should be moved to " + target.activeElement);
+    }
+
+    SimpleTest.finish();
+  });
+}
+</script>
+</pre>
+</body>
+</html>
+