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
--- 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>
+