Skip to content

Commit

Permalink
Bug 1483828 - [Part 1] Disallow <tab> to move focus to root element r…
Browse files Browse the repository at this point in the history
…=smaug

Brings us on-par with Chrome and Safari

Differential Revision: https://phabricator.services.mozilla.com/D198436
  • Loading branch information
sefeng211 committed Mar 4, 2024
1 parent 4b63bf2 commit 8f46bf2
Show file tree
Hide file tree
Showing 9 changed files with 133 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,10 @@
synthesizeKey("KEY_Tab", { shiftKey: true });
},
state: { current: 0, active: null },
activeElement: listEl.ownerDocument.documentElement,
activeElement:
!SpecialPowers.getBoolPref("dom.disable_tab_focus_to_root_element.enabled")
? listEl.ownerDocument.documentElement
: defaultFocus,
}];

for (const test of tests) {
Expand Down
11 changes: 9 additions & 2 deletions dom/base/nsFocusManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4522,7 +4522,7 @@ nsresult nsFocusManager::GetNextTabbableContent(
if (aCurrentTabIndex == (aForward ? 0 : 1)) {
// if going backwards, the canvas should be focused once the beginning
// has been reached, so get the root element.
if (!aForward) {
if (!aForward && !StaticPrefs::dom_disable_tab_focus_to_root_element()) {
nsCOMPtr<nsPIDOMWindowOuter> window = GetCurrentWindow(aRootContent);
NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);

Expand Down Expand Up @@ -4576,7 +4576,7 @@ bool nsFocusManager::TryToMoveFocusToSubDocument(
NS_ASSERTION(doc, "content not in document");
Document* subdoc = doc->GetSubDocumentFor(aCurrentContent);
if (subdoc && !subdoc->EventHandlingSuppressed()) {
if (aForward) {
if (aForward && !StaticPrefs::dom_disable_tab_focus_to_root_element()) {
// When tabbing forward into a frame, return the root
// frame so that the canvas becomes focused.
if (nsCOMPtr<nsPIDOMWindowOuter> subframe = subdoc->GetWindow()) {
Expand All @@ -4597,6 +4597,13 @@ bool nsFocusManager::TryToMoveFocusToSubDocument(
if (*aResultContent) {
return true;
}
if (rootElement->IsEditable() &&
StaticPrefs::dom_disable_tab_focus_to_root_element()) {
// Only move to the root element with a valid reason
*aResultContent = rootElement;
NS_ADDREF(*aResultContent);
return true;
}
}
}
}
Expand Down
24 changes: 16 additions & 8 deletions dom/base/test/file_focus_shadow_dom.html
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,16 @@
opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (3)");
synthesizeKey("KEY_Tab");
opener.is(lastFocusTarget, shadowDate, "Should have focused date element with a calendar button in shadow DOM. (3)");
synthesizeKey("KEY_Tab");
opener.is(shadowIframe.contentDocument.activeElement,
shadowIframe.contentDocument.documentElement,
"Should have focused document element in shadow iframe. (3)");

let canTabMoveFocusToRootElement =
!SpecialPowers.getBoolPref("dom.disable_tab_focus_to_root_element");
if (canTabMoveFocusToRootElement) {
synthesizeKey("KEY_Tab");
opener.is(shadowIframe.contentDocument.activeElement,
shadowIframe.contentDocument.documentElement,
"Should have focused document element in shadow iframe. (3)");
}

synthesizeKey("KEY_Tab");
opener.is(shadowIframe.contentDocument.activeElement,
shadowIframe.contentDocument.body.firstChild,
Expand All @@ -99,10 +105,12 @@
opener.is(shadowIframe.contentDocument.activeElement,
shadowIframe.contentDocument.body.firstChild,
"Should have focused input element in shadow iframe. (4)");
synthesizeKey("KEY_Tab", {shiftKey: true});
opener.is(shadowIframe.contentDocument.activeElement,
shadowIframe.contentDocument.documentElement,
"Should have focused document element in shadow iframe. (4)");
if (canTabMoveFocusToRootElement) {
synthesizeKey("KEY_Tab", {shiftKey: true});
opener.is(shadowIframe.contentDocument.activeElement,
shadowIframe.contentDocument.documentElement,
"Should have focused document element in shadow iframe. (4)");
}
synthesizeKey("KEY_Tab", {shiftKey: true});
opener.is(lastFocusTarget, shadowDate, "Should have focused date element with a calendar button in shadow DOM. (4)");
synthesizeKey("KEY_Tab", {shiftKey: true});
Expand Down
9 changes: 6 additions & 3 deletions dom/events/test/test_bug226361.xhtml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=226361

</p>
<div id="content" style="display: none">

</div>
<pre id="test">
<script type="application/javascript">
Expand All @@ -43,8 +43,11 @@ function tab_to(id) {
}

function tab_iframe() {
doc = document;
tab_to('iframe');
let canTabMoveFocusToRootElement = !SpecialPowers.getBoolPref("dom.disable_tab_focus_to_root_element");
if (canTabMoveFocusToRootElement) {
doc = document;
tab_to('iframe');
}

// inside iframe
doc = document.getElementById('iframe').contentDocument
Expand Down
10 changes: 6 additions & 4 deletions dom/html/test/test_bug556645.html
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,12 @@
const pbutton = document.getElementById("pbutton");
pbutton.focus();

synthesizeKey("KEY_Tab");
is(document.activeElement, aObjectOrEmbed, `${desc}: focus in parent after tab`);
is(childDoc.activeElement, childDoc.documentElement, `${desc}: focus in child after tab`);

let canTabMoveFocusToRootElement = !SpecialPowers.getBoolPref("dom.disable_tab_focus_to_root_element");
if (canTabMoveFocusToRootElement) {
synthesizeKey("KEY_Tab");
is(document.activeElement, aObjectOrEmbed, `${desc}: focus in parent after tab`);
is(childDoc.activeElement, childDoc.documentElement, `${desc}: focus in child after tab`);
}
synthesizeKey("KEY_Tab");
is(document.activeElement, aObjectOrEmbed, `${desc}: focus in parent after tab 2`);
is(childDoc.activeElement, button, `${desc}: focus in child after tab 2`);
Expand Down
2 changes: 2 additions & 0 deletions dom/ipc/BrowserChild.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2530,6 +2530,8 @@ mozilla::ipc::IPCResult BrowserChild::RecvNavigateByKey(
aForward
? (aForDocumentNavigation
? static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_FIRSTDOC)
: StaticPrefs::dom_disable_tab_focus_to_root_element()
? static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_FIRST)
: static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_ROOT))
: (aForDocumentNavigation
? static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_LASTDOC)
Expand Down
12 changes: 10 additions & 2 deletions dom/tests/mochitest/general/test_focus_scrollchildframe.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Tests for for-of loops</title>
<title>Test for using TAB to move focus and scroll into view</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
Expand All @@ -11,10 +11,18 @@
<div id="content" style="display: none"></div>
<script>
function doTest() {
var canTabMoveFocusToRootElement =
!SpecialPowers.getBoolPref("dom.disable_tab_focus_to_root_element");
document.getElementById("button").focus();
const iframe = document.querySelector("iframe");
is(window.scrollY, 0, "Scrolled position initially 0");
synthesizeKey("KEY_Tab");
ok(window.scrollY > 200, "Scrolled child frame into view");
is(document.activeElement, iframe, "Focus moved to the iframe");
if (canTabMoveFocusToRootElement) {
ok(window.scrollY > 200, "Scrolled child frame into view");
} else {
is(window.scrollY, 0, "Scrolled position remained the same");
}
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
Expand Down
6 changes: 6 additions & 0 deletions modules/libpref/init/StaticPrefList.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4817,6 +4817,12 @@
value: true
mirror: always

# Whether allowing using <tab> to move focus to root elements
- name: dom.disable_tab_focus_to_root_element
type: bool
value: @IS_NIGHTLY_BUILD@
mirror: always

#---------------------------------------------------------------------------
# Prefs starting with "editor"
#---------------------------------------------------------------------------
Expand Down
100 changes: 74 additions & 26 deletions widget/tests/window_imestate_iframes.html
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@

var gFocusObservingElement = null;
var gBlurObservingElement = null;
var canTabMoveFocusToRootElement =
!SpecialPowers.getBoolPref("dom.disable_tab_focus_to_root_element");

function onFocus(aEvent) {
if (aEvent.target != gFocusObservingElement) {
Expand Down Expand Up @@ -201,18 +203,30 @@
root = iframe.contentDocument.documentElement;
resetFocusToInput("initializing for iframe_not_editable");

testTabKey(true, root, false, prev, true,
false, "input#prev[readonly] -> html");
testTabKey(true, editor, true, root, false,
true, "html -> input in the subdoc");
if (canTabMoveFocusToRootElement) {
testTabKey(true, root, false, prev, true,
false, "input#prev[readonly] -> html");
testTabKey(true, editor, true, root, false,
true, "html -> input in the subdoc");
} else {
testTabKey(true, editor, true, prev, true,
true, "input#prev[readonly] -> input in the subdoc");
}
testTabKey(true, next, true, editor, true,
false, "input in the subdoc -> input#next[readonly]");
testTabKey(false, editor, true, next, true,
true, "input#next[readonly] -> input in the subdoc");
testTabKey(false, root, false, editor, true,
false, "input in the subdoc -> html");
testTabKey(false, prev, true, root, false,
false, "html -> input#next[readonly]");
if (canTabMoveFocusToRootElement) {
testTabKey(false, root, false, editor, true,
false, "input in the subdoc -> html");
testTabKey(false, prev, true, root, false,
false, "html -> input#next[readonly]");
} else {
testTabKey(false, prev, true, editor, true,
false, "input in the subdoc -> input#prev[readonly]");
testTabKey(false, next, true, prev, true,
false, "input#prev[readonly] -> input#next[readonly]");
}

iframe.style.display = "none";

Expand All @@ -236,8 +250,13 @@
resetFocusToParentHTML("testing iframe_html");
testTabKey(true, editor, true, html, false,
true, "html of parent -> html[contentediable=true]");
testTabKey(false, html, false, editor, true,
false, "html[contenteditable=true] -> html of parent");
if (canTabMoveFocusToRootElement) {
testTabKey(false, html, false, editor, true,
false, "html[contenteditable=true] -> html of parent");
} else {
testTabKey(false, next, true, editor, true,
false, "html[contenteditable=true] -> input#next[readonly]");
}
prev.style.display = "inline";
resetFocusToInput("after parent html <-> html[contenteditable=true]");

Expand Down Expand Up @@ -270,8 +289,13 @@
resetFocusToParentHTML("testing iframe_designMode");
testTabKey(true, root, false, html, false,
true, "html of parent -> html in designMode");
testTabKey(false, html, false, root, false,
false, "html in designMode -> html of parent");
if (canTabMoveFocusToRootElement) {
testTabKey(false, html, false, root, false,
false, "html[contenteditable=true] -> html of parent");
} else {
testTabKey(false, next, true, root, false,
false, "html in designMode -> html of parent");
}
prev.style.display = "inline";
resetFocusToInput("after parent html <-> html in designMode");

Expand Down Expand Up @@ -302,8 +326,13 @@
resetFocusToParentHTML("testing iframe_body");
testTabKey(true, editor, true, html, false,
true, "html of parent -> body[contentediable=true]");
testTabKey(false, html, false, editor, true,
false, "body[contenteditable=true] -> html of parent");
if (canTabMoveFocusToRootElement) {
testTabKey(false, html, false, editor, true,
false, "body[contenteditable=true] -> html of parent");
} else {
testTabKey(false, next, true, editor, true,
false, "body[contenteditable=true] -> input#next[readonly]");
}
prev.style.display = "inline";
resetFocusToInput("after parent html <-> body[contenteditable=true]");

Expand All @@ -321,25 +350,44 @@
root = iframe.contentDocument.documentElement;
resetFocusToInput("initializing for iframe_p");

testTabKey(true, root, false, prev, true,
false, "input#prev[readonly] -> html (has p[contenteditable=true])");
testTabKey(true, editor, true, root, false,
true, "html (has p[contenteditable=true]) -> p[contentediable=true]");
if (canTabMoveFocusToRootElement) {
testTabKey(true, root, false, prev, true,
false, "input#prev[readonly] -> html (has p[contenteditable=true])");
testTabKey(true, editor, true, root, false,
true, "html (has p[contenteditable=true]) -> p[contentediable=true]");
} else {
testTabKey(true, editor, true, prev, true,
true, "input#prev[readonly] -> p[contenteditable=true]");
}
testTabKey(true, next, true, editor, true,
false, "p[contentediable=true] -> input#next[readonly]");
testTabKey(false, editor, true, next, true,
true, "input#next[readonly] -> p[contentediable=true]");
testTabKey(false, root, false, editor, true,
false, "p[contenteditable=true] -> html (has p[contenteditable=true])");
testTabKey(false, prev, true, root, false,
false, "html (has p[contenteditable=true]) -> input#prev[readonly]");
if (canTabMoveFocusToRootElement) {
testTabKey(false, root, false, editor, true,
false, "p[contenteditable=true] -> html (has p[contenteditable=true])");
testTabKey(false, prev, true, root, false,
false, "html (has p[contenteditable=true]) -> input#prev[readonly]");
} else {
testTabKey(false, prev, true, editor, true,
false, "p[contenteditable=true] -> html (has p[contenteditable=true])");
testTabKey(false, next, true, prev, true,
false, "html (has p[contenteditable=true]) -> input#next[readonly]");
}
prev.style.display = "none";

resetFocusToParentHTML("testing iframe_p");
testTabKey(true, root, false, html, false,
false, "html of parent -> html (has p[contentediable=true])");
testTabKey(false, html, false, root, false,
false, "html (has p[contentediable=true]) -> html of parent");
if (canTabMoveFocusToRootElement) {
testTabKey(true, root, false, html, false,
false, "html of parent -> html (has p[contentediable=true])");
testTabKey(false, html, false, root, false,
false, "html (has p[contentediable=true]) -> html of parent");
} else {
testTabKey(true, editor, true, html, false,
true, "html of parent -> p[contentediable=true]");
testTabKey(false, next, true, editor, true,
false, "p[contentediable=true] -> input#next[readonly]");
}
prev.style.display = "inline";
resetFocusToInput("after parent html <-> html (has p[contentediable=true])");

Expand Down

0 comments on commit 8f46bf2

Please sign in to comment.