Source code
Revision control
Copy as Markdown
Other Tools
<html>
<head>
<title>Test for IME state controling and focus moving for iframes</title>
<link rel="stylesheet" type="text/css"
<style type="text/css">
iframe {
border: none;
height: 100px;
}
</style>
</head>
<body onunload="onUnload();">
<p id="display">
<!-- Use input[readonly] because it isn't affected by the partial focus
movement on Mac -->
<input id="prev" readonly><br>
<iframe id="iframe_not_editable"
src="data:text/html,<html><body><input id='editor'></body></html>"></iframe><br>
<!-- Testing IME state and focus movement, the anchor elements cannot get focus -->
<iframe id="iframe_html"
src="data:text/html,<html id='editor' contenteditable='true'><body><a href='about:blank'>about:blank;</a></body></html>"></iframe><br>
<iframe id="iframe_designMode"
src="data:text/html,<body id='editor' onload='document.designMode="on";'><a href='about:blank'>about:blank;</a></body>"></iframe><br>
<iframe id="iframe_body"
src="data:text/html,<body id='editor' contenteditable='true'><a href='about:blank'>about:blank;</a></body>"></iframe><br>
<iframe id="iframe_p"
src="data:text/html,<body><p id='editor' contenteditable='true'><a href='about:blank'>about:blank;</a></p></body>"></iframe><br>
<input id="next" readonly><br>
</p>
<script class="testbody" type="application/javascript">
window.opener.SimpleTest.waitForFocus(runTests, window);
function ok(aCondition, aMessage) {
window.opener.SimpleTest.ok(aCondition, aMessage);
}
function is(aLeft, aRight, aMessage) {
window.opener.SimpleTest.is(aLeft, aRight, aMessage);
}
function onUnload() {
window.opener.onFinish();
}
var gFocusObservingElement = null;
var gBlurObservingElement = null;
var canTabMoveFocusToRootElement =
!SpecialPowers.getBoolPref("dom.disable_tab_focus_to_root_element");
function onFocus(aEvent) {
if (aEvent.target != gFocusObservingElement) {
return;
}
ok(gFocusObservingElement.willFocus,
"focus event is fired on unexpected element");
gFocusObservingElement.willFocus = false;
}
function onBlur(aEvent) {
if (aEvent.target != gBlurObservingElement) {
return;
}
ok(gBlurObservingElement.willBlur,
"blur event is fired on unexpected element");
gBlurObservingElement.willBlur = false;
}
function observeFocusBlur(aNextFocusedNode, aWillFireFocusEvent,
aNextBlurredNode, aWillFireBlurEvent) {
if (gFocusObservingElement) {
if (gFocusObservingElement.willFocus) {
ok(false, "focus event was never fired on " + gFocusObservingElement);
}
gFocusObservingElement.removeEventListener("focus", onFocus, true);
gFocusObservingElement.willFocus = NaN;
gFocusObservingElement = null;
}
if (gBlurObservingElement) {
if (gBlurObservingElement.willBlur) {
ok(false, "blur event was never fired on " + gBlurObservingElement);
}
gBlurObservingElement.removeEventListener("blur", onBlur, true);
gBlurObservingElement.willBlur = NaN;
gBlurObservingElement = null;
}
if (aNextFocusedNode) {
gFocusObservingElement = aNextFocusedNode;
gFocusObservingElement.willFocus = aWillFireFocusEvent;
gFocusObservingElement.addEventListener("focus", onFocus, true);
}
if (aNextBlurredNode) {
gBlurObservingElement = aNextBlurredNode;
gBlurObservingElement.willBlur = aWillFireBlurEvent;
gBlurObservingElement.addEventListener("blur", onBlur, true);
}
}
function runTests() {
var utils = window.windowUtils;
var fm = Services.focus;
var iframe, editor, root;
var prev = document.getElementById("prev");
var next = document.getElementById("next");
var html = document.documentElement;
function resetFocusToInput(aDescription) {
observeFocusBlur(null, false, null, false);
prev.focus();
is(fm.focusedElement, prev,
"input#prev[readonly] element didn't get focus: " + aDescription);
is(utils.IMEStatus, utils.IME_STATUS_DISABLED,
"IME enabled on input#prev[readonly]: " + aDescription);
}
function resetFocusToParentHTML(aDescription) {
observeFocusBlur(null, false, null, false);
html.focus();
is(fm.focusedElement, html,
"Parent html element didn't get focus: " + aDescription);
is(utils.IMEStatus, utils.IME_STATUS_DISABLED,
"IME enabled on parent html element: " + aDescription);
}
function testTabKey(aForward,
aNextFocusedNode, aWillFireFocusEvent,
aNextBlurredNode, aWillFireBlurEvent,
aIMEShouldBeEnabled, aTestingCaseDescription) {
observeFocusBlur(aNextFocusedNode, aWillFireFocusEvent,
aNextBlurredNode, aWillFireBlurEvent);
synthesizeKey("VK_TAB", { shiftKey: !aForward });
var description = "Tab key test: ";
if (!aForward) {
description = "Shift-" + description;
}
description += aTestingCaseDescription + ": ";
is(fm.focusedElement, aNextFocusedNode,
description + "didn't move focus as expected");
is(utils.IMEStatus,
aIMEShouldBeEnabled ?
utils.IME_STATUS_ENABLED : utils.IME_STATUS_DISABLED,
description + "didn't set IME state as expected");
}
function testMouseClick(aNextFocusedNode, aWillFireFocusEvent,
aWillAllNodeLostFocus,
aNextBlurredNode, aWillFireBlurEvent,
aIMEShouldBeEnabled, aTestingCaseDescription) {
observeFocusBlur(aNextFocusedNode, aWillFireFocusEvent,
aNextBlurredNode, aWillFireBlurEvent);
// We're relying on layout inside the iframe being up to date, so make it so
iframe.contentDocument.documentElement.getBoundingClientRect();
synthesizeMouse(iframe, 10, 80, { });
var description = "Click test: " + aTestingCaseDescription + ": ";
is(fm.focusedElement, !aWillAllNodeLostFocus ? aNextFocusedNode : null,
description + "didn't move focus as expected");
is(utils.IMEStatus,
aIMEShouldBeEnabled ?
utils.IME_STATUS_ENABLED : utils.IME_STATUS_DISABLED,
description + "didn't set IME state as expected");
}
function testOnEditorFlagChange(aDescription, aIsInDesignMode) {
const kReadonly = Ci.nsIEditor.eEditorReadonlyMask;
var description = "testOnEditorFlagChange: " + aDescription;
resetFocusToParentHTML(description);
var htmlEditor = iframe.contentWindow.docShell.editor;
var e = aIsInDesignMode ? root : editor;
e.focus();
is(fm.focusedElement, e,
description + ": focus() of editor didn't move focus as expected");
is(utils.IMEStatus, utils.IME_STATUS_ENABLED,
description + ": IME isn't enabled when the editor gets focus");
var flags = htmlEditor.flags;
htmlEditor.flags |= kReadonly;
is(fm.focusedElement, e,
description + ": when editor becomes readonly, focus moved unexpectedly");
is(utils.IMEStatus, utils.IME_STATUS_DISABLED,
description + ": when editor becomes readonly, IME is still enabled");
htmlEditor.flags = flags;
is(fm.focusedElement, e,
description + ": when editor becomes read-write, focus moved unexpectedly");
is(utils.IMEStatus, utils.IME_STATUS_ENABLED,
description + ": when editor becomes read-write, IME is still disabled");
}
// hide all iframes
document.getElementById("iframe_not_editable").style.display = "none";
document.getElementById("iframe_html").style.display = "none";
document.getElementById("iframe_designMode").style.display = "none";
document.getElementById("iframe_body").style.display = "none";
document.getElementById("iframe_p").style.display = "none";
// non editable HTML element and input element can get focus.
iframe = document.getElementById("iframe_not_editable");
iframe.style.display = "inline";
editor = iframe.contentDocument.getElementById("editor");
root = iframe.contentDocument.documentElement;
resetFocusToInput("initializing for iframe_not_editable");
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");
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";
// HTML element (of course, it's root) must enables IME.
iframe = document.getElementById("iframe_html");
iframe.style.display = "inline";
editor = iframe.contentDocument.getElementById("editor");
root = iframe.contentDocument.documentElement;
resetFocusToInput("initializing for iframe_html");
testTabKey(true, editor, true, prev, true,
true, "input#prev[readonly] -> html[contentediable=true]");
testTabKey(true, next, true, editor, true,
false, "html[contentediable=true] -> input#next[readonly]");
testTabKey(false, editor, true, next, true,
true, "input#next[readonly] -> html[contentediable=true]");
testTabKey(false, prev, true, editor, true,
false, "html[contenteditable=true] -> input[readonly]");
prev.style.display = "none";
resetFocusToParentHTML("testing iframe_html");
testTabKey(true, editor, true, html, false,
true, "html of parent -> html[contentediable=true]");
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]");
testMouseClick(editor, true, false, prev, true, true, "iframe_html");
testOnEditorFlagChange("html[contentediable=true]", false);
iframe.style.display = "none";
// designMode should behave like <html contenteditable="true"></html>
// but focus/blur events shouldn't be fired on its root element because
// any elements shouldn't be focused state in designMode.
iframe = document.getElementById("iframe_designMode");
iframe.style.display = "inline";
iframe.contentDocument.designMode = "on";
editor = iframe.contentDocument.getElementById("editor");
root = iframe.contentDocument.documentElement;
resetFocusToInput("initializing for iframe_designMode");
testTabKey(true, root, false, prev, true,
true, "input#prev[readonly] -> html in designMode");
testTabKey(true, next, true, root, false,
false, "html in designMode -> input#next[readonly]");
testTabKey(false, root, false, next, true,
true, "input#next[readonly] -> html in designMode");
testTabKey(false, prev, true, root, false,
false, "html in designMode -> input#prev[readonly]");
prev.style.display = "none";
resetFocusToParentHTML("testing iframe_designMode");
testTabKey(true, root, false, html, false,
true, "html of parent -> html in designMode");
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");
testMouseClick(editor, false, true, prev, true, true, "iframe_designMode");
testOnEditorFlagChange("html in designMode", true);
iframe.style.display = "none";
// When there is no HTML element but the BODY element is editable,
// the body element should get focus and enables IME.
iframe = document.getElementById("iframe_body");
iframe.style.display = "inline";
editor = iframe.contentDocument.getElementById("editor");
root = iframe.contentDocument.documentElement;
resetFocusToInput("initializing for iframe_body");
testTabKey(true, editor, true, prev, true,
true, "input#prev[readonly] -> body[contentediable=true]");
testTabKey(true, next, true, editor, true,
false, "body[contentediable=true] -> input#next[readonly]");
testTabKey(false, editor, true, next, true,
true, "input#next[readonly] -> body[contentediable=true]");
testTabKey(false, prev, true, editor, true,
false, "body[contenteditable=true] -> input#prev[readonly]");
prev.style.display = "none";
resetFocusToParentHTML("testing iframe_body");
testTabKey(true, editor, true, html, false,
true, "html of parent -> body[contentediable=true]");
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]");
testMouseClick(editor, true, false, prev, true, true, "iframe_body");
testOnEditorFlagChange("body[contentediable=true]", false);
iframe.style.display = "none";
// When HTML/BODY elements are not editable, focus shouldn't be moved to
// the editable content directly.
iframe = document.getElementById("iframe_p");
iframe.style.display = "inline";
editor = iframe.contentDocument.getElementById("editor");
root = iframe.contentDocument.documentElement;
resetFocusToInput("initializing for iframe_p");
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]");
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");
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])");
testMouseClick(root, false, true, prev, true, false, "iframe_p");
testOnEditorFlagChange("p[contenteditable=true]", false);
iframe.style.display = "none";
window.close();
}
</script>
</body>
</html>