diff options
Diffstat (limited to 'dom/tests/mochitest/chrome/window_focus.xhtml')
-rw-r--r-- | dom/tests/mochitest/chrome/window_focus.xhtml | 1699 |
1 files changed, 1699 insertions, 0 deletions
diff --git a/dom/tests/mochitest/chrome/window_focus.xhtml b/dom/tests/mochitest/chrome/window_focus.xhtml new file mode 100644 index 0000000000..a2a1b5a2c3 --- /dev/null +++ b/dom/tests/mochitest/chrome/window_focus.xhtml @@ -0,0 +1,1699 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- + This test checks focus in various ways +--> +<window id="outer-document" title="Focus Test" width="600" height="550" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"/> + + <script type="application/javascript"><![CDATA[ + +const { BrowserTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/BrowserTestUtils.sys.mjs" +); +const { ContentTask } = ChromeUtils.import( + "resource://testing-common/ContentTask.jsm" +); + +var fm = Cc["@mozilla.org/focus-manager;1"]. + getService(Ci.nsIFocusManager); + +const kChildDocumentRootIndex = 13; +const kBeforeTabboxIndex = 34; +const kTabbableSteps = 38; +const kFocusSteps = 26; +const kNoFocusSteps = 7; +const kOverflowElementIndex = 27; + +var gTestStarted = false; +var gPartialTabbing = false; +var gMoveToFocusFrame = false; +var gLastFocus = null; +var gLastFocusWindow = window; +var gLastFocusMethod = -1; +var gEvents = ""; +var gExpectedEvents = ""; +var gEventMatched = true; +var gShowOutput = false; +var gChildWindow = null; + +var gOldExpectedWindow = null; +var gNewExpectedWindow = null; + +function is(l, r, n) { window.arguments[0].SimpleTest.is(l,r,n); } +function ok(v, n) { window.arguments[0].SimpleTest.ok(v,n); } + +function initEvents(target) +{ + target.addEventListener("focus", eventOccured, true); + target.addEventListener("blur", eventOccured, true); + getTopWindow(target).addEventListener("activate", eventOccured, true); + getTopWindow(target).addEventListener("deactivate", eventOccured, true); +} + +function eventOccured(event) +{ + // iframes should never receive focus or blur events directly + if (Element.isInstance(event.target) && event.target.localName == "iframe") + ok(false, "iframe " + event.type + "occured"); + + var id; + if (gOldExpectedWindow && event.type == "blur") { + if (Window.isInstance(event.target)) + id = "frame-" + gOldExpectedWindow.document.documentElement.id + "-window"; + else if (Document.isInstance(event.target)) + id = "frame-" + gOldExpectedWindow.document.documentElement.id + "-document"; + else + id = event.originalTarget.id; + } + else if (gNewExpectedWindow && event.type == "focus") { + if (Window.isInstance(event.target)) + id = "frame-" + gNewExpectedWindow.document.documentElement.id + "-window"; + else if (Document.isInstance(event.target)) + id = "frame-" + gNewExpectedWindow.document.documentElement.id + "-document"; + else + id = event.originalTarget.id; + } + else if (event.type == "activate" || event.type == "deactivate") + id = event.target.document.documentElement.id + "-window"; + else if (Window.isInstance(event.target)) + id = (event.target == window) ? "outer-window" : "child-window"; + else if (Document.isInstance(event.target)) + id = (event.target == document) ? "outer-document" : "child-document"; + else + id = event.originalTarget.id; + + if (gEvents) + gEvents += " "; + gEvents += event.type + ": " + id; +} + +function expectFocusShift(callback, expectedWindow, expectedElement, focusChanged, testid) +{ + if (expectedWindow == null) + expectedWindow = expectedElement ? + expectedElement.ownerGlobal : + gLastFocusWindow; + + var expectedEvents = ""; + if (focusChanged) { + var id; + if (getTopWindow(gLastFocusWindow) != getTopWindow(expectedWindow)) { + id = getTopWindow(gLastFocusWindow).document.documentElement.id; + expectedEvents += "deactivate: " + id + "-window"; + } + + if (gLastFocus && gLastFocus.id != "t" + kChildDocumentRootIndex && + (!gOldExpectedWindow || gOldExpectedWindow.document.documentElement != gLastFocus)) { + if (expectedEvents) + expectedEvents += " "; + if (!gOldExpectedWindow) + expectedEvents += "commandupdate: cu "; + expectedEvents += "blur: " + gLastFocus.id; + } + + if (gLastFocusWindow && gLastFocusWindow != expectedWindow) { + if (!gMoveToFocusFrame) { + if (gOldExpectedWindow) + id = "frame-" + gOldExpectedWindow.document.documentElement.id; + else + id = (gLastFocusWindow == window) ? "outer" : "child"; + if (expectedEvents) + expectedEvents += " "; + expectedEvents += "blur: " + id + "-document " + + "blur: " + id + "-window"; + } + } + + if (getTopWindow(gLastFocusWindow) != getTopWindow(expectedWindow)) { + id = getTopWindow(expectedWindow).document.documentElement.id; + if (expectedEvents) + expectedEvents += " "; + expectedEvents += "activate: " + id + "-window"; + } + + if (expectedWindow && gLastFocusWindow != expectedWindow) { + if (gNewExpectedWindow) + id = "frame-" + gNewExpectedWindow.document.documentElement.id; + else + id = (expectedWindow == window) ? "outer" : "child"; + if (expectedEvents) + expectedEvents += " "; + expectedEvents += "focus: " + id + "-document " + + "focus: " + id + "-window"; + } + + // for this test which fires a mouse event on a label, the document will + // be focused first and then the label code will focus the related + // control. This doesn't result in different focus events, but a command + // update will occur for the document and then a second command update will + // occur when the control is focused. However, this will only happen on + // platforms or controls where mouse clicks cause trigger focus. + if (testid == "mouse on html label with content inside" && + mouseWillTriggerFocus(expectedElement)) { + expectedEvents += " commandupdate: cu"; + } + + if (expectedElement && + (!gNewExpectedWindow || gNewExpectedWindow.document.documentElement != expectedElement)) { + if (!gNewExpectedWindow) { + if (expectedEvents) + expectedEvents += " "; + expectedEvents += "commandupdate: cu"; + } + if (expectedElement.id != "t" + kChildDocumentRootIndex) { + if (expectedEvents) + expectedEvents += " "; + expectedEvents += "focus: " + expectedElement.id; + } + } + else if (expectedWindow && gLastFocusWindow != expectedWindow && + !expectedElement) { + if (expectedEvents) + expectedEvents += " "; + expectedEvents += "commandupdate: cu"; + } + } + + gLastFocus = expectedElement; + gLastFocusWindow = expectedWindow; + + callback(); + + compareEvents(expectedEvents, expectedWindow, expectedElement, testid); +} + +function compareEvents(expectedEvents, expectedWindow, expectedElement, testid) +{ + if (!gShowOutput) { + gEvents = ""; + return; + } + + is(gEvents, expectedEvents, testid + " events"); + gEvents = ""; + + var doc; + if (expectedWindow == window) + doc = "outer-document"; + else if (expectedWindow == gChildWindow) + doc = "inner-document"; + else if (gNewExpectedWindow) + doc = gNewExpectedWindow.document.body ? gNewExpectedWindow.document.body.id : + gNewExpectedWindow.document.documentElement.id; + else + doc = "other-document"; + + var focusedElement = fm.focusedElement; + is(focusedElement ? focusedElement.id : "none", + expectedElement ? expectedElement.id : "none", testid + " focusedElement"); + is(fm.focusedWindow, expectedWindow, testid + " focusedWindow"); + var focusedWindow = {}; + is(fm.getFocusedElementForWindow(expectedWindow, false, focusedWindow), + expectedElement, testid + " getFocusedElementForWindow"); + is(focusedWindow.value, expectedWindow, testid + " getFocusedElementForWindow frame"); + is(expectedWindow.document.hasFocus(), true, testid + " hasFocus"); + is(expectedWindow.document.activeElement ? expectedWindow.document.activeElement.id : "none", + expectedElement ? expectedElement.id : doc, testid + " activeElement"); + var cdwindow = getTopWindow(expectedWindow); + if (cdwindow.document.commandDispatcher) { + is(cdwindow.document.commandDispatcher.focusedWindow, expectedWindow, testid + " commandDispatcher focusedWindow"); + is(cdwindow.document.commandDispatcher.focusedElement, focusedElement, testid + " commandDispatcher focusedElement"); + } + + if (gLastFocusMethod != -1) { + is(fm.getLastFocusMethod(null), gLastFocusMethod, testid + " lastFocusMethod null"); + is(fm.getLastFocusMethod(expectedWindow), gLastFocusMethod, testid + " lastFocusMethod window"); + } + + // the parent should have the iframe focused + if (doc == "inner-document") { + is(document.hasFocus(), true, testid + " hasFocus"); + is(fm.getFocusedElementForWindow(window, false, focusedWindow), + $("childframe"), testid + " getFocusedElementForWindow for parent"); + is(focusedWindow.value, window, testid + " getFocusedElementForWindow for parent frame"); + is(fm.getFocusedElementForWindow(window, true, focusedWindow), + expectedElement, testid + " getFocusedElementForWindow deep for parent"); + is(focusedWindow.value, gChildWindow, testid + " getFocusedElementForWindow deep for parent frame"); + is(document.activeElement.id, "childframe", testid + " activeElement for parent"); + } + + // compare the selection for the child window. Skip mouse tests as the caret + // is adjusted by the selection code for mouse clicks, and not the focus code. + if (expectedWindow == window) { + var selection = window.getSelection(); + ok(selection.focusNode == null && selection.focusOffset == 0 && + selection.anchorNode == null && selection.anchorOffset == 0, testid + " selection"); + } + else if ((expectedWindow == gChildWindow) && !testid.indexOf("mouse") == -1) { + checkSelection(expectedElement, testid); + } +} + +function checkSelection(node, testid) +{ + var selection = gChildWindow.getSelection(); + + var range = gChildWindow.document.createRange(); + range.selectNodeContents(node); + if (!node.firstChild || node.localName == "input" || + node.localName == "select" || node.localName == "button") { + range.setStartBefore(node); + range.setEndBefore(node); + } + + if (node.firstChild) + range.setEnd(range.startContainer, range.startOffset); + + is(selection.focusNode, range.startContainer, testid + " selection focusNode"); + is(selection.focusOffset, range.startOffset, testid + " selection focusOffset"); + is(selection.anchorNode, range.endContainer, testid + " selection anchorNode"); + is(selection.anchorOffset, range.endOffset, testid + " selection anchorOffset"); +} + +function getTopWindow(win) +{ + return win.browsingContext.topChromeWindow; +} + +function mouseWillTriggerFocus(element) +{ + if (!element) { + return false; + } + + if (SpecialPowers.getIntPref("accessibility.mouse_focuses_formcontrol") == 1) { + // This is a chrome document, so we'll only trigger focus with the mouse if the value of the pref is 1. + return true; + } + + if (element.namespaceURI == "http://www.w3.org/1999/xhtml") { + // links are special. They can be focused but show no focus ring + if (element.localName == "a" || element.localName == "div" || + element.localName == "select" || + element.localName == "input" && (element.type == "text" || + element.type == "password")) { + return true; + } + } else if (element.localName == "richlistbox") { + return true; + } + + return false; +} + +function mouseOnElement(element, expectedElement, focusChanged, testid) +{ + var expectedWindow = (element.ownerGlobal == gChildWindow) ? gChildWindow : window; + // on Mac, form elements are not focused when clicking, except for lists and inputs. + var noFocusOnMouse = !mouseWillTriggerFocus(element) + + if (noFocusOnMouse) { + // no focus so the last focus method will be 0 + gLastFocusMethod = 0; + expectFocusShift(() => synthesizeMouse(element, 4, 4, { }, element.ownerGlobal), + expectedWindow, null, true, testid); + gLastFocusMethod = fm.FLAG_BYMOUSE; + } + else { + expectFocusShift(() => synthesizeMouse(element, 4, 4, { }, element.ownerGlobal), + element.ownerGlobal, + expectedElement, focusChanged, testid); + } +} + +function done() +{ + var opener = window.arguments[0]; + window.close(); + window.arguments[0].SimpleTest.finish(); +} + +var pressTab = () => synthesizeKey("KEY_Tab"); + +function setFocusTo(id, fwindow) +{ + gLastFocus = getById(id); + gLastFocusWindow = fwindow; + if (gLastFocus) + gLastFocus.focus(); + else + fm.clearFocus(fwindow); + gEvents = ""; +} + +function getById(id) +{ + if (gNewExpectedWindow) + return gNewExpectedWindow.document.getElementById(id); + var element = $(id); + if (!element) + element = $("childframe").contentDocument.getElementById(id); + return element; +} + +function startTest() +{ + if (gTestStarted) + return; + gTestStarted = true; + + gChildWindow = $("childframe").contentWindow; + gShowOutput = true; + + // synthesize a mousemove over the image to ensure that the imagemap data is + // created. Otherwise, the special imagemap frames might not exist, and + // won't be focusable. + synthesizeMouse(getById("image"), 4, 4, { type: "mousemove" }, gChildWindow); + + initEvents(window); + + is(fm.activeWindow, window, "activeWindow"); + is(gChildWindow.document.hasFocus(), false, " child document hasFocus"); + + // test to see if the Mac Full Keyboard Access setting is set. If t3 is + // focused after tab is pressed, then it is set to inputs and lists only. + // Otherwise, all elements are in the tab order. + pressTab(); + + if (fm.focusedElement.id == "t3") + gPartialTabbing = true; + else + is(fm.focusedElement.id, "t1", "initial tab key"); + + is(fm.getLastFocusMethod(null), fm.FLAG_BYKEY, "last focus method null start"); + is(fm.getLastFocusMethod(window), fm.FLAG_BYKEY, "last focus method window start"); + + fm.clearFocus(window); + gEvents = ""; + + gLastFocusMethod = fm.FLAG_BYKEY; + if (gPartialTabbing) { + var partialTabList = ["t3", "t5", "t9", "t10", "t11", "t12", "t13", "t14", "t15", + "t16", "t19", "t20", "t21", "t22", "t26", "t27", "t28", "t29", "t30"]; + for (var idx = 0; idx < partialTabList.length; idx++) { + expectFocusShift(pressTab, null, getById(partialTabList[idx]), true, "partial tab key " + partialTabList[idx]); + } + setFocusTo("last", window); + expectFocusShift(pressTab, null, getById(partialTabList[0]), true, "partial tab key wrap to start"); + expectFocusShift(() => synthesizeKey("KEY_Tab", {shiftKey: true}), + null, getById("last"), true, "partial shift tab key wrap to end"); + for (var idx = partialTabList.length - 1; idx >= 0; idx--) { + expectFocusShift(() => synthesizeKey("KEY_Tab", {shiftKey: true}), + null, getById(partialTabList[idx]), true, "partial tab key " + partialTabList[idx]); + } + } + else { + // TAB key + for (var idx = 1; idx <= kTabbableSteps; idx++) { + expectFocusShift(pressTab, null, getById("t" + idx), true, "tab key t" + idx); + } + + // wrapping around at end with TAB key + setFocusTo("last", window); + expectFocusShift(pressTab, null, getById("t1"), true, "tab key wrap to start"); + expectFocusShift(() => synthesizeKey("KEY_Tab", {shiftKey: true}), + null, getById("last"), true, "shift tab key wrap to end"); + + // Shift+TAB key + setFocusTo("o5", window); + for (idx = kTabbableSteps; idx > 0; idx--) { + expectFocusShift(() => synthesizeKey("KEY_Tab", {shiftKey: true}), + null, getById("t" + idx), true, "shift tab key t" + idx); + } + } + + var t19 = getById("t19"); + is(t19.selectionStart, 0, "input focused from tab key selectionStart"); + is(t19.selectionEnd, 5, "input focused from tab key selectionEnd"); + t19.setSelectionRange(0, 0); + + gLastFocusMethod = 0; + var selectFired = false; + function selectListener() { selectFired = true; } + t19.addEventListener("select", selectListener, false); + expectFocusShift(() => t19.select(), + null, getById("t" + 19), true, "input.select()"); + t19.removeEventListener("select", selectListener, false); + ok(!selectFired, "select event does not fire asynchronously for input"); + + // mouse clicking + gLastFocusMethod = fm.FLAG_BYMOUSE; + for (idx = kTabbableSteps; idx >= 1; idx--) { + // skip the document root and the overflow element + if (idx == kChildDocumentRootIndex || idx == kOverflowElementIndex) + continue; + if ((navigator.platform.indexOf("Mac") == 0) && (idx == kBeforeTabboxIndex + 1)) + continue; + + var element = getById("t" + idx); + // skip area elements, as getBoundingClientRect doesn't return their actual coordinates + if (element.localName == "area") + continue; + + mouseOnElement(element, element, true, "mouse on element t" + idx); + var expectedWindow = (element.ownerGlobal == gChildWindow) ? gChildWindow : window; + if (element.localName == "richlistbox" && expectedWindow == window && + navigator.platform.indexOf("Mac") == 0) { + // after focusing a listbox on Mac, clear the focus before continuing. + setFocusTo(null, window); + } + } + + ok(t19.selectionStart == t19.selectionEnd, "input focused from mouse selection"); + + // mouse clicking on elements that are not tabbable + for (idx = 1; idx <= kFocusSteps; idx++) { + var element = getById("o" + (idx % 2 ? idx : idx - 1)); + + mouseOnElement(element, element, idx % 2, + "mouse on non-tabbable element o" + idx); + } + + // mouse clicking on elements that are not tabbable and have user-focus: none + // or are not focusable for other reasons (for instance, being disabled) + // These elements will clear the focus when clicked. + for (idx = 1; idx <= kNoFocusSteps; idx++) { + var element = getById("n" + idx); + gLastFocusMethod = idx % 2 ? 0 : fm.FLAG_BYMOUSE; + + mouseOnElement(element, idx % 2 ? null: element, true, "mouse on unfocusable element n" + idx); + } + + if (idx == kOverflowElementIndex) { + gLastFocusMethod = fm.FLAG_BYMOUSE; + var element = getById("t" + idx); + expectFocusShift(() => synthesizeMouse(element, 4, 4, { }, element.ownerGlobal), + window, null, true, "mouse on scrollable element"); + } + + // focus() method + gLastFocusMethod = fm.FLAG_BYJS; + for (idx = kTabbableSteps; idx >= 1; idx--) { + if ((navigator.platform.indexOf("Mac") == 0) && (idx == kBeforeTabboxIndex + 1)) + continue; + expectFocusShift(() => getById("t" + idx).focus(), + null, getById("t" + idx), true, "focus method on element t" + idx); + } + + $("t1").focus(); + ok(gEvents === "", "focusing element that is already focused"); + + $("t2").blur(); + $("t7").blur(); + ok(gEvents === "", "blurring element that is not focused"); + is(document.activeElement, $("t1"), "old element still focused after blur() on another element"); + + // focus() method on elements that are not tabbable + for (idx = 1; idx <= kFocusSteps; idx++) { + var expected = getById("o" + (idx % 2 ? idx : idx - 1)); + expectFocusShift(() => getById("o" + idx).focus(), + expected.ownerGlobal, + expected, idx % 2, "focus method on non-tabbable element o" + idx); + } + + // focus() method on elements that are not tabbable and have user-focus: none + // or are not focusable for other reasons (for instance, being disabled) + for (idx = 1; idx <= kNoFocusSteps; idx++) { + var expected = getById("o" + (idx % 2 ? idx : idx - 1)); + expectFocusShift(() => getById("o" + idx).focus(), + expected.ownerGlobal, + expected, idx % 2, "focus method on unfocusable element n" + idx); + } + + // the focus() method on the legend element should focus the legend if it is + // focusable, or the first element after the legend if it is not focusable. + if (!gPartialTabbing) { + gLastFocusMethod = fm.FLAG_BYJS; + var legend = getById("legend"); + expectFocusShift(() => legend.focus(), + null, getById("t28"), true, "focus method on unfocusable legend"); + legend.tabIndex = "0"; + expectFocusShift(() => legend.focus(), + null, getById("legend"), true, "focus method on focusable legend"); + legend.tabIndex = "-1"; + } + + var accessKeyDetails = (navigator.platform.includes("Mac")) ? + { ctrlKey : true } : { altKey : true }; + + // test accesskeys + var keys = ["t26", "t19", "t22", "t29", "t15", "t17", "n6", + "t4", "o1", "o9", "n4"]; + for (var k = 0; k < keys.length; k++) { + var key = String.fromCharCode(65 + k); + + // accesskeys D and G are for labels so get redirected + gLastFocusMethod = (key == "D" || key == "G") ? fm.FLAG_BYMOVEFOCUS : fm.FLAG_BYKEY; + + // on Windows and Linux, the shift key must be pressed for content area access keys + // and on Mac, the alt key must be pressed for content area access keys + var isContent = (getById(keys[k]).ownerGlobal == gChildWindow); + if (!navigator.platform.includes("Mac")) { + accessKeyDetails.shiftKey = isContent; + } else { + accessKeyDetails.altKey = isContent; + } + + expectFocusShift(() => synthesizeKey(key, accessKeyDetails), + null, getById(keys[k]), true, "accesskey " + key); + } + + // clicking on the labels + gLastFocusMethod = fm.FLAG_BYMOVEFOCUS | fm.FLAG_BYMOUSE; + mouseOnElement(getById("ad"), getById("t29"), true, "mouse on html label with content inside"); + mouseOnElement(getById("ag"), getById("n6"), true, "mouse on html label with for attribute"); + gLastFocusMethod = fm.FLAG_BYJS; + expectFocusShift(() => synthesizeMouse(getById("aj"), 2, 2, { }), + null, getById("o9"), true, "mouse on xul label with content inside"); + expectFocusShift(() => synthesizeMouse(getById("ak"), 2, 2, { }), + null, getById("n4"), true, "mouse on xul label with control attribute"); + + // test accesskeys that shouldn't work + k = "o".charCodeAt(0); + gLastFocusMethod = fm.FLAG_BYJS; + while (k++ < "v".charCodeAt(0)) { + var key = String.fromCharCode(k); + expectFocusShift(() => synthesizeKey(key, accessKeyDetails), + window, getById("n4"), false, "non accesskey " + key); + } + gLastFocusMethod = -1; + + // should focus the for element when using the focus method on a label as well + expectFocusShift(() => getById("ad").focus(), + null, getById("t29"), true, "mouse on html label using focus method"); + + // make sure that the text is selected when clicking a label associated with an input + getById("ag").htmlFor = "t19"; + expectFocusShift(() => synthesizeMouse(getById("ag"), 2, 2, { }, gChildWindow), + null, getById("t19"), true, "mouse on html label with for attribute changed"); + is(t19.selectionStart, 0, "input focused from label, selectionStart"); + is(t19.selectionEnd, 5, "input focused from label, selectionEnd"); + + // switch to another panel in a tabbox and ensure that tabbing moves between + // elements on the new panel. + $("tabbox").selectedIndex = 1; + expectFocusShift(() => getById("t" + kBeforeTabboxIndex).focus(), + null, getById("t" + kBeforeTabboxIndex), true, "focus method on element before tabbox"); + + if (!gPartialTabbing) { + expectFocusShift(pressTab, null, getById("tab2"), true, "focus method on tab"); + expectFocusShift(pressTab, null, getById("htab1"), true, "tab key switch tabpanel 1"); + expectFocusShift(pressTab, null, getById("htab2"), true, "tab key switch tabpanel 2"); + expectFocusShift(pressTab, null, getById("t" + (kBeforeTabboxIndex + 4)), true, "tab key switch tabpanel 3"); + } + $("tabbox").selectedIndex = 0; + + // ---- the following checks when the focus changes during a blur or focus event ---- + + var o5 = $("o5"); + var o9 = $("o9"); + var t3 = $("t3"); + var t17 = getById("t17"); + var t19 = getById("t19"); + var shiftFocusParentDocument = () => o9.focus(); + var shiftFocusChildDocument = () => t17.focus(); + + var trapBlur = function (element, eventListener, blurFunction) + { + element.focus(); + gEvents = ""; + element.addEventListener("blur", eventListener, false); + blurFunction(); + element.removeEventListener("blur", eventListener, false); + } + + var functions = [ + element => element.focus(), + element => synthesizeMouse(element, 4, 4, { }, element.ownerGlobal) + ]; + + // first, check cases where the focus is adjusted during the blur event. Iterate twice, + // once with the focus method and then focusing by mouse clicking + for (var l = 0; l < 2; l++) { + var adjustFocus = functions[l]; + var mod = (l == 1) ? " with mouse" : ""; + + // an attempt is made to switch the focus from one element (o5) to another + // element (t3) within the same document, yet the focus is shifted to a + // third element (o9) in the same document during the blur event for the + // first element. + trapBlur(o5, shiftFocusParentDocument, () => adjustFocus(t3)); + compareEvents("commandupdate: cu blur: o5 commandupdate: cu focus: o9", + window, o9, "change focus to sibling during element blur, attempted sibling" + mod); + + // similar, but the third element (t17) is in a child document + trapBlur(o9, shiftFocusChildDocument, () => adjustFocus(t3)); + compareEvents("commandupdate: cu blur: o9 blur: outer-document blur: outer-window " + + "focus: child-document focus: child-window commandupdate: cu focus: t17", + gChildWindow, t17, "change focus to child document during element blur, attempted sibling" + mod); + + // similar, but an attempt to switch focus within the same document, but the + // third element (t17) is in a parent document + trapBlur(t17, shiftFocusParentDocument, () => adjustFocus(t19)); + compareEvents("commandupdate: cu blur: t17 blur: child-document blur: child-window " + + "focus: outer-document focus: outer-window commandupdate: cu focus: o9", + window, o9, "change focus to parent document during element blur, attempted sibling" + mod); + + // similar, but blur is called instead of switching focus + trapBlur(t3, shiftFocusParentDocument, () => t3.blur()); + compareEvents("commandupdate: cu blur: t3 commandupdate: cu focus: o9", + window, o9, "change focus to same document during clear focus" + mod); + + // check when an element in the same document is focused during the + // element's blur event, but an attempt was made to focus an element in the + // child document. In this case, the focus in the parent document should be + // what was set during the blur event, but the actual focus should still + // move to the child document. + trapBlur(t3, shiftFocusParentDocument, () => adjustFocus(t17)); + compareEvents("commandupdate: cu blur: t3 commandupdate: cu focus: o9 " + + "blur: outer-document blur: outer-window " + + "focus: child-document focus: child-window commandupdate: cu focus: t17", + gChildWindow, t17, "change focus to sibling during element blur, attempted child" + mod); + is(fm.getFocusedElementForWindow(window, false, {}), $("childframe"), + "change focus to sibling during element blur, attempted child, focused in parent" + mod); + + // similar, but with a parent + trapBlur(t19, shiftFocusChildDocument, () => adjustFocus(t3)); + compareEvents("commandupdate: cu blur: t19 commandupdate: cu focus: t17 " + + "blur: child-document blur: child-window " + + "focus: outer-document focus: outer-window commandupdate: cu focus: t3", + window, t3, "change focus to sibling during element blur, attempted parent" + mod); + is(fm.getFocusedElementForWindow(gChildWindow, false, {}), t17, + "change focus to sibling during element blur, attempted child, focused in child" + mod); + + // similar, with a child, but the blur event focuses a child element also + trapBlur(t3, shiftFocusChildDocument, () => adjustFocus(t19)); + compareEvents("commandupdate: cu blur: t3 blur: outer-document blur: outer-window " + + "focus: child-document focus: child-window commandupdate: cu focus: t17", + gChildWindow, t17, "change focus to child during element blur, attempted child" + mod); + + // similar, with a parent, where the blur event focuses a parent element also + trapBlur(t17, shiftFocusParentDocument, () => adjustFocus(t3)); + compareEvents("commandupdate: cu blur: t17 blur: child-document blur: child-window " + + "focus: outer-document focus: outer-window commandupdate: cu focus: o9", + window, o9, "change focus to parent during element blur, attempted parent" + mod); + } + + var trapFocus = function (element, eventListener) + { + element.addEventListener("focus", eventListener, false); + element.focus(); + element.removeEventListener("focus", eventListener, false); + } + + fm.clearFocus(window); + gEvents = ""; + + // next, check cases where the focus is adjusted during the focus event + + // switch focus to an element in the same document + trapFocus(o5, shiftFocusParentDocument); + compareEvents("commandupdate: cu focus: o5 commandupdate: cu blur: o5 commandupdate: cu focus: o9", + window, o9, "change focus to sibling during element focus"); + + // similar, but the new element (t17) is in a child document + trapFocus(o5, shiftFocusChildDocument); + compareEvents("commandupdate: cu blur: o9 " + + "commandupdate: cu focus: o5 commandupdate: cu blur: o5 " + + "blur: outer-document blur: outer-window " + + "focus: child-document focus: child-window commandupdate: cu focus: t17", + gChildWindow, t17, "change focus to child document during element focus"); + + // similar, but the new element (o9) is in a parent document. + trapFocus(t19, shiftFocusParentDocument); + compareEvents("commandupdate: cu blur: t17 " + + "commandupdate: cu focus: t19 commandupdate: cu blur: t19 " + + "blur: child-document blur: child-window " + + "focus: outer-document focus: outer-window commandupdate: cu focus: o9", + window, o9, "change focus to parent document during element focus"); + + // clear the focus during the focus event + trapFocus(t3, () => fm.clearFocus(window)); + compareEvents("commandupdate: cu blur: o9 commandupdate: cu focus: t3 commandupdate: cu blur: t3", + window, null, "clear focus during focus event"); + + if (!gPartialTabbing) + doCommandDispatcherTests(); + + testMoveFocus(); + + doRemoveTests(); + + // tests various focus manager apis for null checks + var exh = false; + try { + fm.clearFocus(null); + } + catch (ex) { exh = true; } + is(exh, true, "clearFocus with null window causes exception"); + + var exh = false; + try { + fm.getFocusedElementForWindow(null, false, focusedWindow); + } + catch (ex) { exh = true; } + is(exh, true, "getFocusedElementForWindow with null window causes exception"); + + // just make sure that this doesn't crash + fm.moveCaretToFocus(null); + + // ---- tests for the FLAG_NOSWITCHFRAME flag + getById("o5").focus(); + gLastFocusMethod = fm.FLAG_BYJS; + gEvents = ""; + // focus is being shifted in a child, so the focus should not change + expectFocusShift(() => fm.setFocus(getById("t20"), fm.FLAG_NOSWITCHFRAME), + window, getById("o5"), false, "no switch frame focus to child"); + setFocusTo("t20", gChildWindow); + + gLastFocusMethod = 0; + + // here, however, focus is being shifted in a parent, which will have to blur + // the child, so the focus will always change + expectFocusShift(() => fm.setFocus(getById("o5"), fm.FLAG_NOSWITCHFRAME), + window, getById("o5"), true, "no switch frame focus to parent"); + + expectFocusShift(() => fm.setFocus(getById("t1"), fm.FLAG_NOSWITCHFRAME), + window, getById("t1"), true, "no switch frame focus to same window"); + + // ---- tests for focus and scrolling into view ---- + var inscroll = getById("inscroll"); + inscroll.tabIndex = 0; + is(inscroll.parentNode.scrollTop, 0, "scroll position before focus"); + inscroll.focus(); + ok(inscroll.parentNode.scrollTop > 5, "scroll position after focus"); + inscroll.parentNode.scrollTop = 0; + fm.setFocus(inscroll, fm.FLAG_NOSCROLL); + is(inscroll.parentNode.scrollTop, 0, "scroll position after noscroll focus"); + + getById("t9").focus(); + getById("inpopup1").focus(); + is(fm.focusedElement, getById("t9"), "focus in closed popup"); + + // ---- tests to check if tabbing out of a input works + + setFocusTo("t1", window); + + var input1 = document.createElement("input"); + $("innerbox").appendChild(input1); + + var input2 = document.createElement("input"); + $("innerbox").appendChild(input2); + + gLastFocusMethod = fm.FLAG_BYJS; + expectFocusShift(() => input2.focus(), + null, input2, true, "focus on input"); + gLastFocusMethod = fm.FLAG_BYKEY; + expectFocusShift(() => synthesizeKey("KEY_Tab", {shiftKey: true}), + null, input1, true, "shift+tab on input"); + + input1.tabIndex = 2; + input2.tabIndex = 2; + gLastFocusMethod = fm.FLAG_BYJS; + expectFocusShift(() => input2.focus(), + null, input2, true, "focus on input with tabindex set"); + gLastFocusMethod = fm.FLAG_BYKEY; + expectFocusShift(() => synthesizeKey("KEY_Tab", {shiftKey: true}), + null, input1, true, "shift+tab on input with tabindex set"); + + // ---- test to check that refocusing an element during a blur event doesn't succeed + + var t1 = getById("t1"); + t1.addEventListener("blur", () => t1.focus(), true); + t1.focus(); + var t3 = getById("t3"); + synthesizeMouse(t3, 2, 2, { }); + is(fm.focusedElement, t3, "focus during blur"); + + setFocusTo("t9", window); + gLastFocusMethod = -1; + window.openDialog("focus_window2.xhtml", "_blank", "chrome", otherWindowFocused); +} + +function doCommandDispatcherTests() +{ + var t19 = getById("t19"); + t19.focus(); + gLastFocusWindow = gChildWindow; + gLastFocus = t19; + gEvents = ""; + + expectFocusShift(() => document.commandDispatcher.focusedElement = getById("o9"), + null, getById("o9"), true, "command dispatcher set focusedElement"); + expectFocusShift(() => document.commandDispatcher.advanceFocus(), + null, getById("o13"), true, "command dispatcher advanceFocus"); + expectFocusShift(() => document.commandDispatcher.rewindFocus(), + null, getById("o9"), true, "command dispatcher rewindFocus"); + expectFocusShift(() => document.commandDispatcher.focusedElement = null, + null, null, true, "command dispatcher set focusedElement to null"); + expectFocusShift(() => document.commandDispatcher.focusedWindow = gChildWindow, + null, getById("t19"), true, "command dispatcher set focusedElement to null"); + expectFocusShift(() => document.commandDispatcher.focusedElement = null, + gChildWindow, null, true, "command dispatcher set focusedElement to null in child"); + expectFocusShift(() => document.commandDispatcher.advanceFocusIntoSubtree(getById("t19")), + null, getById("t20"), true, "command dispatcher advanceFocusIntoSubtree child"); + expectFocusShift(() => document.commandDispatcher.advanceFocusIntoSubtree(null), + null, getById("t21"), true, "command dispatcher advanceFocusIntoSubtree null child"); + expectFocusShift(() => document.commandDispatcher.advanceFocusIntoSubtree(getById("o9").parentNode), + null, getById("o9"), true, "command dispatcher advanceFocusIntoSubtree parent"); +} + +function doRemoveTests() +{ + // next, some tests which remove elements + var t19 = getById("t19"); + t19.focus(); + t19.remove(); + + is(fm.focusedElement, null, "removed element focusedElement"); + is(fm.focusedWindow, gChildWindow, "removed element focusedWindow"); + is(gChildWindow.document.hasFocus(), true, "removed element hasFocus"); + is(gChildWindow.document.activeElement, getById("inner-document"), "removed element activeElement"); + + getById("t15").focus(); + var abs = getById("abs"); + abs.remove(); + + is(fm.focusedElement, null, "removed ancestor focusedElement"); + is(fm.focusedWindow, gChildWindow, "removed ancestor focusedWindow"); + is(gChildWindow.document.hasFocus(), true, "removed ancestor hasFocus"); + is(gChildWindow.document.activeElement, getById("inner-document"), "removed ancestor activeElement"); +} + +// tests for the FocusManager moveFocus method +function testMoveFocus() +{ + setFocusTo("t6", window); + + // moving focus while an element is already focused + var newFocus; + gLastFocusMethod = fm.FLAG_BYMOVEFOCUS; + var expectedFirst = getById(gPartialTabbing ? "t3" : "t1"); + expectFocusShift(() => newFocus = fm.moveFocus(null, null, fm.MOVEFOCUS_FIRST, 0), + window, expectedFirst, true, "moveFocus to first null window null content"); + is(newFocus, fm.focusedElement, "moveFocus to first null window null content return value"); + + expectFocusShift(() => newFocus = fm.moveFocus(null, null, fm.MOVEFOCUS_LAST, 0), + window, getById("last"), true, "moveFocus to last null window null content"); + is(newFocus, fm.focusedElement, "moveFocus to last null window null content return value"); + + gLastFocusMethod = 0; + newFocus = fm.moveFocus(null, null, fm.MOVEFOCUS_ROOT, 0); + is(newFocus, null, "moveFocus to root null window null content return value"); + is(fm.focusedWindow, window, "moveFocus to root null window null content focusedWindow"); + is(fm.focusedElement, null, "moveFocus to root null window null content focusedElement"); + + // moving focus while no element is focused + fm.clearFocus(window); + gEvents = ""; + gLastFocus = null; + gLastFocusMethod = fm.FLAG_BYMOVEFOCUS; + expectFocusShift(() => newFocus = fm.moveFocus(null, null, fm.MOVEFOCUS_FIRST, 0), + window, expectedFirst, true, "moveFocus to first null window null content no focus"); + is(newFocus, fm.focusedElement, "moveFocus to first null window null content no focus return value"); + fm.clearFocus(window); + gEvents = ""; + gLastFocus = null; + expectFocusShift(() => newFocus = fm.moveFocus(null, null, fm.MOVEFOCUS_LAST, 0), + window, getById("last"), true, "moveFocus to last null window null content no focus"); + is(newFocus, fm.focusedElement, "moveFocus to last null window null content no focus return value"); + fm.clearFocus(window); + gEvents = ""; + gLastFocusMethod = 0; + newFocus = fm.moveFocus(null, null, fm.MOVEFOCUS_ROOT, 0); + is(newFocus, null, "moveFocus to root null window null content no focus return value"); + is(fm.focusedWindow, window, "moveFocus to root null window null content no focus focusedWindow"); + is(fm.focusedElement, null, "moveFocus to root null window null content no focus focusedElement"); + + // moving focus from a specified element + setFocusTo("t6", window); + gLastFocusMethod = fm.FLAG_BYMOVEFOCUS; + expectFocusShift(() => newFocus = fm.moveFocus(null, getById("specialroot"), fm.MOVEFOCUS_FIRST, 0), + window, getById("t3"), true, "moveFocus to first null window with content"); +// XXXndeakin P3 this doesn't work +// expectFocusShift(() => newFocus = fm.moveFocus(null, getById("specialroot"), fm.MOVEFOCUS_LAST, 0), +// window, getById("o3"), true, "moveFocus to last null window with content"); + + // move focus to first in child window + expectFocusShift(() => newFocus = fm.moveFocus(gChildWindow, null, fm.MOVEFOCUS_FIRST, 0), + gChildWindow, getById("t" + (kChildDocumentRootIndex + 1)), true, + "moveFocus to first child window null content"); + is(newFocus, getById("t" + (kChildDocumentRootIndex + 1)), + "moveFocus to first child window null content return value"); + + // move focus to last in child window + setFocusTo("t6", window); + var expectedLast = getById(gPartialTabbing ? "t30" : "t" + (kBeforeTabboxIndex - 1)); + expectFocusShift(() => newFocus = fm.moveFocus(gChildWindow, null, fm.MOVEFOCUS_LAST, 0), + gChildWindow, expectedLast, true, + "moveFocus to last child window null content"); + is(newFocus, getById(expectedLast), + "moveFocus to last child window null content return value"); + + // move focus to root in child window + setFocusTo("t6", window); + var childroot = getById("t" + kChildDocumentRootIndex); + gLastFocusMethod = 0; + newFocus = fm.moveFocus(gChildWindow, null, fm.MOVEFOCUS_ROOT, 0), + is(newFocus, childroot, "moveFocus to root child window null content return value"); + is(fm.focusedWindow, gChildWindow, "moveFocus to root child window null content focusedWindow"); + is(fm.focusedElement, childroot, "moveFocus to root child window null content focusedElement"); + + // MOVEFOCUS_CARET tests + getById("t20").focus(); + gEvents = ""; + + var selection = gChildWindow.getSelection(); + selection.removeAllRanges(); + + newFocus = fm.moveFocus(gChildWindow, null, fm.MOVEFOCUS_CARET, 0); + is(newFocus, null, "move caret when at document root"); + is(fm.focusedElement, null, "move caret when at document root"); + + var node = getById("t16").firstChild; + var range = gChildWindow.document.createRange(); + range.setStart(node, 3); + range.setEnd(node, 3); + selection.addRange(range); + + newFocus = fm.moveFocus(gChildWindow, null, fm.MOVEFOCUS_CARET, 0); + is(newFocus, null, "move caret to non-link return value"); + is(fm.focusedElement, null, "move caret to non-link"); + + var t25 = getById("t25"); + var node = t25.firstChild; + range.setStart(node, 1); + range.setEnd(node, 1); + newFocus = fm.moveFocus(gChildWindow, null, fm.MOVEFOCUS_CARET, 0); + + is(newFocus, t25, "move caret to link return value"); + is(fm.focusedElement, t25, "move caret to link focusedElement"); + + // enable caret browsing temporarily to test caret movement + var prefs = Cc["@mozilla.org/preferences-service;1"]. + getService(Ci.nsIPrefBranch); + prefs.setBoolPref("accessibility.browsewithcaret", true); + + synthesizeKey("KEY_ArrowLeft", {}, gChildWindow); + synthesizeKey("KEY_ArrowLeft", {}, gChildWindow); + is(fm.focusedElement, null, "move caret away from link"); + + synthesizeKey("KEY_ArrowLeft", {}, gChildWindow); + is(fm.focusedElement, getById("t24"), "move caret away onto link"); + + prefs.setBoolPref("accessibility.browsewithcaret", false); + + // cases where focus in on a content node with no frame + + if (!gPartialTabbing) { + getById("t24").blur(); + gEvents = ""; + gLastFocus = null; + gLastFocusWindow = gChildWindow; + gLastFocusMethod = fm.FLAG_BYKEY; + + selection.selectAllChildren(getById("hiddenspan")); + expectFocusShift(() => synthesizeKey("KEY_Tab"), + gChildWindow, getById("t26"), true, "tab with selection on hidden content"); + + setFocusTo($("o15"), window); + $("o15").hidden = true; + document.documentElement.getBoundingClientRect(); // flush after hiding + expectFocusShift(() => synthesizeKey("KEY_Tab"), + window, $("o17"), true, "tab with focus on hidden content"); + + $("o17").hidden = true; + document.documentElement.getBoundingClientRect(); + expectFocusShift(() => synthesizeKey("KEY_Tab", {shiftKey: true}), + window, $("o13"), true, "shift+tab with focus on hidden content"); + } + + // cases with selection in an <input> + + var t19 = getById("t19"); + t19.setSelectionRange(0, 0); + setFocusTo("t18", gChildWindow); + + gLastFocusMethod = fm.FLAG_BYMOVEFOCUS; + expectFocusShift(() => newFocus = fm.moveFocus(null, null, fm.MOVEFOCUS_FORWARD, 0), + gChildWindow, t19, true, "moveFocus to next input"); + is(t19.selectionStart, 0, "input focused after moveFocus selectionStart"); + is(t19.selectionEnd, 5, "input focused after moveFocus selectionEnd"); + + t19.setSelectionRange(0, 0); + setFocusTo("t18", gChildWindow); + gLastFocusMethod = fm.FLAG_BYKEY; + expectFocusShift(() => newFocus = fm.moveFocus(null, null, fm.MOVEFOCUS_FORWARD, fm.FLAG_BYKEY), + gChildWindow, t19, true, "moveFocus to next input by key"); + is(t19.selectionStart, 0, "input focused after moveFocus by key selectionStart"); + is(t19.selectionEnd, 5, "input focused after moveFocus by key selectionEnd"); +} + +function otherWindowFocused(otherWindow) +{ + var expectedElement = getById("t9"); + + is(fm.activeWindow, otherWindow, "other activeWindow"); + is(fm.focusedWindow, otherWindow, "other focusedWindow"); + is(window.document.hasFocus(), false, "when lowered document hasFocus"); + var focusedWindow = {}; + is(fm.getFocusedElementForWindow(window, false, focusedWindow), + expectedElement, "when lowered getFocusedElementForWindow"); + is(focusedWindow.value, window, "when lowered getFocusedElementForWindow frame"); + is(document.activeElement.id, expectedElement.id, "when lowered activeElement"); + is(window.document.commandDispatcher.focusedWindow, window, " commandDispatcher in other window focusedWindow"); + is(window.document.commandDispatcher.focusedElement, expectedElement, " commandDispatcher in other window focusedElement"); + + compareEvents("deactivate: outer-document-window blur: t9 blur: outer-document blur: outer-window", + otherWindow, null, "other window opened"); + + otherWindow.document.getElementById("other").focus(); + + for (var idx = kTabbableSteps; idx >= 1; idx--) { + expectedElement = getById("t" + idx); + if (!expectedElement) // skip elements that were removed in doRemoveTests() + continue; + if ((navigator.platform.indexOf("Mac") == 0) && (idx == kBeforeTabboxIndex + 1)) + continue; + + expectedElement.focus(); + + is(fm.focusedElement.id, "other", "when lowered focusedElement t" + idx); + is(fm.focusedWindow, otherWindow, "when lowered focusedWindow t" + idx); + + var checkWindow = expectedElement.ownerGlobal; + is(fm.getFocusedElementForWindow(checkWindow, false, {}).id, expectedElement.id, + "when lowered getFocusedElementForWindow t" + idx); + is(checkWindow.document.activeElement.id, expectedElement.id, "when lowered activeElement t" + idx); + if (checkWindow != window) { + is(fm.getFocusedElementForWindow(window, false, {}), $("childframe"), + "when lowered parent getFocusedElementForWindow t" + idx); + is(document.activeElement.id, "childframe", + "when lowered parent activeElement t" + idx); + } + } + + gEvents = gEvents.replace(/commandupdate: cu\s?/g, ""); + is(gEvents, "", "when lowered no events fired"); + + var other = otherWindow.document.getElementById("other"); + other.focus(); + is(fm.focusedElement, other, "focus method in second window"); + + otherWindow.close(); + + getById("n2").focus(); + + // next, check modal dialogs + // XXXndeakin Bug 621399 - the modal dialog test as well as later tests sometime fail + // on Windows 8 so just end the test here. + if (navigator.userAgent.includes("Windows NT 6.2")) { + done(); + } + else { + window.openDialog("focus_window2.xhtml", "_blank", "chrome,modal", modalWindowOpened); + } +} + +function modalWindowOpened(modalWindow) +{ + var elem = modalWindow.document.getElementById("other"); + if (gPartialTabbing) + elem.focus(); + else + synthesizeKey("KEY_Tab", {}, modalWindow); + is(fm.activeWindow, modalWindow, "modal activeWindow"); + is(fm.focusedElement, elem, "modal focusedElement"); + + modalWindow.close(); + SimpleTest.waitForFocus(modalWindowClosed); +} + +function modalWindowClosed() +{ + is(fm.activeWindow, window, "modal window closed activeWindow"); + is(fm.focusedElement, getById("n2"), "modal window closed focusedElement"); + + window.arguments[0].framesetWindowLoaded = framesetWindowLoaded; + window.arguments[0].open("focus_frameset.html", "_blank", "width=400,height=400,toolbar=no"); +} + +function framesetWindowLoaded(framesetWindow) +{ + gLastFocus = null; + gLastFocusWindow = framesetWindow; + gEvents = ""; + + is(fm.activeWindow, getTopWindow(framesetWindow), "frameset window active"); + gOldExpectedWindow = getTopWindow(framesetWindow); + + gMoveToFocusFrame = true; + for (var idx = 1; idx <= 8; idx++) { + gNewExpectedWindow = framesetWindow.frames[(idx - 1) >> 1]; + if (idx % 2) + initEvents(gNewExpectedWindow); + expectFocusShift(() => synthesizeKey("KEY_Tab", {}, framesetWindow), + gNewExpectedWindow, getById("f" + idx), true, "frameset tab key f" + idx); + gMoveToFocusFrame = false; + gOldExpectedWindow = gNewExpectedWindow; + } + + gNewExpectedWindow = framesetWindow.frames[0]; + expectFocusShift(() => synthesizeKey("KEY_Tab", {}, framesetWindow), + gNewExpectedWindow, getById("f1"), true, "frameset tab key wrap to start"); + gOldExpectedWindow = gNewExpectedWindow; + gNewExpectedWindow = framesetWindow.frames[3]; + expectFocusShift(() => synthesizeKey("KEY_Tab", {shiftKey: true}, framesetWindow), + gNewExpectedWindow, getById("f8"), true, "frameset shift tab key wrap to end"); + + for (idx = 7; idx >= 1; idx--) { + gOldExpectedWindow = gNewExpectedWindow; + gNewExpectedWindow = framesetWindow.frames[(idx - 1) >> 1]; + expectFocusShift(() => synthesizeKey("KEY_Tab", {shiftKey: true}, framesetWindow), + gNewExpectedWindow, getById("f" + idx), true, "frameset shift tab key f" + idx); + } + + // document shifting + // XXXndeakin P3 ctrl+tab doesn't seem to be testable currently for some reason + gNewExpectedWindow = framesetWindow.frames[1]; + expectFocusShift(() => synthesizeKey("KEY_F6", {ctrlKey: true}, framesetWindow), + gNewExpectedWindow, getById("f3"), true, "switch document forward with f6"); + gOldExpectedWindow = gNewExpectedWindow; + gNewExpectedWindow = framesetWindow.frames[2]; + expectFocusShift(() => synthesizeKey("KEY_F6", {}, framesetWindow), + gNewExpectedWindow, getById("f5"), true, "switch document forward with ctrl+tab"); + gOldExpectedWindow = gNewExpectedWindow; + gNewExpectedWindow = framesetWindow.frames[3]; + expectFocusShift(() => synthesizeKey("KEY_F6", {ctrlKey: true}, framesetWindow), + gNewExpectedWindow, getById("f7"), true, "switch document forward with ctrl+f6"); + gOldExpectedWindow = gNewExpectedWindow; + gNewExpectedWindow = framesetWindow.frames[0]; + expectFocusShift(() => synthesizeKey("KEY_F6", {ctrlKey: true}, framesetWindow), + gNewExpectedWindow, getById("f1"), true, "switch document forward and wrap"); + +// going backwards by document and wrapping doesn't currently work, but didn't work +// before the focus reworking either + +/* + gOldExpectedWindow = gNewExpectedWindow; + gNewExpectedWindow = framesetWindow.frames[3]; + expectFocusShift(() => synthesizeKey("KEY_F6", { ctrlKey: true, shiftKey: true }, framesetWindow), + gNewExpectedWindow, getById("f7"), true, "switch document backward and wrap"); + */ + + fm.moveFocus(framesetWindow.frames[3], null, fm.MOVEFOCUS_ROOT, 0); + gEvents = ""; + + gOldExpectedWindow = gNewExpectedWindow; + gNewExpectedWindow = framesetWindow.frames[2]; + expectFocusShift(() => synthesizeKey("KEY_F6", {ctrlKey: true, shiftKey: true}, framesetWindow), + gNewExpectedWindow, getById("f5"), true, "switch document backward with f6"); + gOldExpectedWindow = gNewExpectedWindow; + gNewExpectedWindow = framesetWindow.frames[1]; + expectFocusShift(() => synthesizeKey("KEY_F6", {ctrlKey: true, shiftKey: true}, framesetWindow), + gNewExpectedWindow, getById("f3"), true, "switch document backward with ctrl+tab"); + gOldExpectedWindow = gNewExpectedWindow; + gNewExpectedWindow = framesetWindow.frames[0]; + expectFocusShift(() => synthesizeKey("KEY_F6", {ctrlKey: true, shiftKey: true}, framesetWindow), + gNewExpectedWindow, getById("f1"), true, "switch document backward with ctrl+f6"); + + // skip the window switching tests for now on Linux, as raising and lowering + // a window is asynchronous there + if (!navigator.platform.includes("Linux")) { + window.openDialog("focus_window2.xhtml", "_blank", "chrome", switchWindowTest, framesetWindow); + } + else { + gOldExpectedWindow = null; + gNewExpectedWindow = null; + framesetWindow.close(); + SimpleTest.waitForFocus(doWindowNoRootTest); + } +} + +// test switching between two windows +function switchWindowTest(otherWindow, framesetWindow) +{ + initEvents(otherWindow); + var otherElement = otherWindow.document.getElementById("other"); + otherElement.focus(); + + framesetWindow.frames[1].document.getElementById("f4").focus(); + + is(fm.focusedElement, otherElement, "focus after inactive window focus"); + + gLastFocus = otherElement; + gLastFocusWindow = otherWindow; + gEvents = ""; + gOldExpectedWindow = otherWindow; + gNewExpectedWindow = framesetWindow.frames[1]; + + expectFocusShift(() => gNewExpectedWindow.focus(), + gNewExpectedWindow, getById("f4"), true, "switch to frame in another window"); + is(fm.getFocusedElementForWindow(otherWindow, false, {}).id, "other", "inactive window has focused element"); + + gOldExpectedWindow = framesetWindow.frames[1]; + gNewExpectedWindow = otherWindow; + expectFocusShift(() => otherWindow.focus(), + gNewExpectedWindow, getById("other"), true, "switch to another window"); + + var topWindow = getTopWindow(framesetWindow); + + ok(topWindow.document.commandDispatcher.getControllerForCommand("cmd_copy"), + "getControllerForCommand for focused window set"); + ok(otherWindow.document.commandDispatcher.getControllerForCommand("cmd_copy"), + "getControllerForCommand for non-focused window set"); + ok(topWindow.document.commandDispatcher.getControllerForCommand("cmd_copy") != + otherWindow.document.commandDispatcher.getControllerForCommand("cmd_copy"), + "getControllerForCommand for two windows different"); + ok(topWindow.document.commandDispatcher.getControllers() != + otherWindow.document.commandDispatcher.getControllers(), + "getControllers for two windows different"); + + gOldExpectedWindow = otherWindow; + gNewExpectedWindow = framesetWindow.frames[1]; + expectFocusShift(() => topWindow.focus(), + gNewExpectedWindow, getById("f4"), true, "switch to frame activeWindow"); + + fm.clearFocus(otherWindow); + gOldExpectedWindow = gNewExpectedWindow; + gNewExpectedWindow = otherWindow; + expectFocusShift(() => fm.setFocus(otherElement, fm.FLAG_RAISE), + gNewExpectedWindow, getById("other"), true, "switch to window with raise"); + + getTopWindow(framesetWindow).document.commandDispatcher.focusedWindow = gOldExpectedWindow; + is(fm.activeWindow, gNewExpectedWindow, "setting commandDispatcher focusedWindow doesn't raise window"); + + fm.moveFocus(otherWindow, null, fm.MOVEFOCUS_FORWARD, 0); + var otherInput = otherWindow.document.getElementById("other-input"); + otherInput.setSelectionRange(2, 3); + topWindow.focus(); + otherWindow.focus(); + is(otherInput.selectionStart, 2, "selectionStart after input focus and window raise"); + is(otherInput.selectionEnd, 3, "selectionEnd after input focus and window raise"); + is(fm.getLastFocusMethod(null), fm.FLAG_BYMOVEFOCUS, "last focus method after input focus and window raise"); + + fm.clearFocus(otherWindow); + + // test to ensure that a synthetic event won't move focus + var synevent = new FocusEvent("focus", {}); + otherInput.dispatchEvent(synevent); + is(synevent.type, "focus", "event.type after synthetic focus event"); + is(synevent.target, otherInput, "event.target after synthetic focus event"); + is(fm.focusedElement, null, "focusedElement after synthetic focus event"); + is(otherWindow.document.activeElement, otherWindow.document.documentElement, + "document.activeElement after synthetic focus event"); + + // check accessing a focus event after the event has finishing firing + function continueTest(event) { + is(event.type, "focus", "event.type after accessing focus event in timeout"); + is(event.target, otherInput, "event.target after accessing focus event in timeout"); + + gOldExpectedWindow = null; + gNewExpectedWindow = null; + otherWindow.close(); + framesetWindow.close(); + + SimpleTest.waitForFocus(doWindowNoRootTest); + } + + function inputFocused(event) { + otherInput.removeEventListener("focus", inputFocused, true); + setTimeout(continueTest, 0, event); + } + + otherInput.addEventListener("focus", inputFocused, true); + otherInput.focus(); +} + +// open a window with no root element +var noRootWindow = null; +function doWindowNoRootTest() +{ + addEventListener("focus", doFrameSwitchingTests, true); + noRootWindow = window.open("window_focus_inner.xhtml", "_blank", "chrome,width=100,height=100"); +} + +// these tests check when focus is moved between a tree of frames to ensure +// that the focus is in the right place at each event step. +function doFrameSwitchingTests() +{ + removeEventListener("focus", doFrameSwitchingTests, true); + noRootWindow.close(); + + var framea = document.getElementById("ifa"); + var frameb = document.getElementById("ifb"); + framea.style.MozUserFocus = ""; + frameb.style.MozUserFocus = ""; + + window.removeEventListener("focus", eventOccured, true); + window.removeEventListener("blur", eventOccured, true); + + var inputa = framea.contentDocument.body.firstChild; + inputa.focus(); + + addFrameSwitchingListeners(framea); + addFrameSwitchingListeners(frameb); + var framec = framea.contentDocument.body.lastChild; + addFrameSwitchingListeners(framec); + + var framed = framec.contentDocument.body.lastChild; + addFrameSwitchingListeners(framed); + + var inputc = framec.contentDocument.body.firstChild; + + var expectedMainWindowFocus = framea; + + // An element in the immediate parent frame is focused. Focus an element in + // the child. The child should be focused and the parent's current focus should + // be the child iframe. + gEventMatched = true; + is(fm.getFocusedElementForWindow(window, false, {}), expectedMainWindowFocus, + "parent of framea has iframe focused"); + gExpectedEvents = [[inputa, "blur", null, framea.contentWindow, window, framea], + [framea.contentDocument, "blur", null, null, window, framea], + [framea.contentWindow, "blur", null, null, window, framea], + [framec.contentDocument, "focus", null, framec.contentWindow, window, framea], + [framec.contentWindow, "focus", null, framec.contentWindow, window, framea], + [inputc, "focus", inputc, framec.contentWindow, window, framea]]; + inputc.focus(); + ok(gEventMatched && !gExpectedEvents.length, "frame switch from parent input to child input" + gExpectedEvents); + + // An element in a child is focused. Focus an element in the immediate + // parent. + gEventMatched = true; + gExpectedEvents = [[inputc, "blur", null, framec.contentWindow, window, framea], + [framec.contentDocument, "blur", null, null, window, framea], + [framec.contentWindow, "blur", null, null, window, framea], + [framea.contentDocument, "focus", null, framea.contentWindow, window, framea], + [framea.contentWindow, "focus", null, framea.contentWindow, window, framea], + [inputa, "focus", inputa, framea.contentWindow, window, framea]]; + inputa.focus(); + ok(gEventMatched && !gExpectedEvents.length, "frame switch from child input to parent input"); + + // An element in a frame is focused. Focus an element in a sibling frame. + // The common ancestor of the two frames should have its focused node + // cleared after the element is blurred. + var inputb = frameb.contentDocument.body.firstChild; + + gEventMatched = true; + gExpectedEvents = [[inputa, "blur", null, framea.contentWindow, window, framea], + [framea.contentDocument, "blur", null, null, window, null], + [framea.contentWindow, "blur", null, null, window, null], + [frameb.contentDocument, "focus", null, frameb.contentWindow, window, frameb], + [frameb.contentWindow, "focus", null, frameb.contentWindow, window, frameb], + [inputb, "focus", inputb, frameb.contentWindow, window, frameb]]; + inputb.focus(); + ok(gEventMatched && !gExpectedEvents.length, "frame switch from input to sibling frame"); + is(fm.getFocusedElementForWindow(framea.contentWindow, false, {}), inputa, + "blurred frame still has input as focus"); + + // focus a descendant in a sibling + var inputd = framed.contentDocument.body.firstChild; + gEventMatched = true; + gExpectedEvents = [[inputb, "blur", null, frameb.contentWindow, window, frameb], + [frameb.contentDocument, "blur", null, null, window, null], + [frameb.contentWindow, "blur", null, null, window, null], + [framed.contentDocument, "focus", null, framed.contentWindow, window, framea], + [framed.contentWindow, "focus", null, framed.contentWindow, window, framea], + [inputd, "focus", inputd, framed.contentWindow, window, framea]]; + inputd.focus(); + ok(gEventMatched && !gExpectedEvents.length, "frame switch from input to sibling descendant"); + is(fm.getFocusedElementForWindow(framea.contentWindow, false, {}), framec, + "sibling parent focus has shifted to frame"); + + // focus an ancestor + gEventMatched = true; + gExpectedEvents = [[inputd, "blur", null, framed.contentWindow, window, framea], + [framed.contentDocument, "blur", null, null, window, framea], + [framed.contentWindow, "blur", null, null, window, framea], + [framea.contentDocument, "focus", null, framea.contentWindow, window, framea], + [framea.contentWindow, "focus", null, framea.contentWindow, window, framea], + [inputa, "focus", inputa, framea.contentWindow, window, framea]]; + inputa.focus(); + ok(gEventMatched && !gExpectedEvents.length, "frame switch from child input to ancestor"); + + // focus a descendant + gEventMatched = true; + gExpectedEvents = [[inputa, "blur", null, framea.contentWindow, window, framea], + [framea.contentDocument, "blur", null, null, window, framea], + [framea.contentWindow, "blur", null, null, window, framea], + [framed.contentDocument, "focus", null, framed.contentWindow, window, framea], + [framed.contentWindow, "focus", null, framed.contentWindow, window, framea], + [inputd, "focus", inputd, framed.contentWindow, window, framea]]; + inputd.focus(); + ok(gEventMatched && !gExpectedEvents.length, "frame switch from child input to ancestor"); + is(fm.getFocusedElementForWindow(framea.contentWindow, false, {}), framec, + "parent focus has shifted to frame"); + + // focus a sibling frame by setting focusedWindow + gEventMatched = true; + gExpectedEvents = [[inputd, "blur", null, framed.contentWindow, window, framea], + [framed.contentDocument, "blur", null, null, window, null], + [framed.contentWindow, "blur", null, null, window, null], + [frameb.contentDocument, "focus", null, frameb.contentWindow, window, frameb], + [frameb.contentWindow, "focus", null, frameb.contentWindow, window, frameb]]; + fm.focusedWindow = frameb.contentWindow; + ok(gEventMatched && !gExpectedEvents.length, "frame switch using focusedWindow"); + + // clear the focus in an unfocused frame + gEventMatched = true; + gExpectedEvents = []; + fm.clearFocus(framec.contentWindow); + ok(gEventMatched && !gExpectedEvents.length, "clearFocus in unfocused frame"); + + // focus a sibling frame by setting focusedWindow when no element is focused in that frame + gEventMatched = true; + gExpectedEvents = [[frameb.contentDocument, "blur", null, null, window, null], + [frameb.contentWindow, "blur", null, null, window, null], + [framec.contentDocument, "focus", null, framec.contentWindow, window, framea], + [framec.contentWindow, "focus", null, framec.contentWindow, window, framea]]; + fm.focusedWindow = framec.contentWindow; + ok(gEventMatched && !gExpectedEvents.length, "frame switch using focusedWindow with no element focused"); + is(fm.getFocusedElementForWindow(framea.contentWindow, false, {}), framec, + "parent focus has shifted to frame using focusedWindow"); + + // focus the parent frame by setting focusedWindow. This should have no effect. + gEventMatched = true; + gExpectedEvents = []; + fm.focusedWindow = framea.contentWindow; + ok(gEventMatched && !gExpectedEvents.length, "frame switch to parent using focusedWindow"); + + // clear the focus in the parent frame + gEventMatched = true; + gExpectedEvents = [[framec.contentDocument, "blur", null, null, window, framea], + [framec.contentWindow, "blur", null, null, window, framea], + [framea.contentDocument, "focus", null, framea.contentWindow, window, framea], + [framea.contentWindow, "focus", null, framea.contentWindow, window, framea]]; + fm.clearFocus(framea.contentWindow); + ok(gEventMatched && !gExpectedEvents.length, "clearFocus in parent frame"); + + // clear the focus in an unfocused child frame + gEventMatched = true; + gExpectedEvents = []; + fm.clearFocus(framed.contentWindow); + ok(gEventMatched && !gExpectedEvents.length, "clearFocus in unfocused child frame"); + + var exh = false; + try { + fm.focusedWindow = null; + } + catch (ex) { exh = true; } + is(exh, true, "focusedWindow set to null"); + is(fm.focusedWindow, framea.contentWindow, "window not changed when focusedWindow set to null"); + + doFrameHistoryTests() +} + +function doFrameHistoryTests() +{ + let frame = $("childframe"); + frame.setAttribute("maychangeremoteness", "true"); + let loaded = BrowserTestUtils.browserLoaded(frame, true); + let firstLocation = "https://example.org/chrome/dom/tests/mochitest/chrome/child_focus_frame.html"; + frame.src = firstLocation; + loaded.then(() => { + return ContentTask.spawn(frame, {}, () => { + let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager); + let t20 = content.document.getElementById("t20"); + t20.focus(); + let goneBack = new Promise(resolve => { + content.addEventListener("pageshow", ({ persisted }) => { + resolve({ location: content.location.href, persisted }); + }, { once: true }); + }); + // make sure that loading a new page and then going back maintains the focus + content.location = "data:text/html,<script>window.onload=function() {setTimeout(function () { history.back() }, 0);}</script>"; + return goneBack; + }); + }).then(({ location, persisted }) => { + is(location, firstLocation, "should go back to the right page"); + ok(persisted, "test relies on BFCache"); + return ContentTask.spawn(frame, {}, () => { + let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager); + let t20 = content.document.getElementById("t20"); + return fm.focusedElement === t20; + }); + }).then((focusCorrect) => { + if (SpecialPowers.Services.appinfo.sessionHistoryInParent) { + todo(focusCorrect, "focus restored after history back"); + } else { + ok(focusCorrect, "focus restored after history back"); + } + done(); + }); +} + +function addFrameSwitchingListeners(frame) +{ + frame.contentWindow.addEventListener("focus", frameSwitchingEventOccured, false); + frame.contentWindow.addEventListener("blur", frameSwitchingEventOccured, false); + frame.contentDocument.addEventListener("focus", frameSwitchingEventOccured, false); + frame.contentDocument.addEventListener("blur", frameSwitchingEventOccured, false); + + var node = frame.contentDocument.body.firstChild; + node.addEventListener("focus", frameSwitchingEventOccured, false); + node.addEventListener("blur", frameSwitchingEventOccured, false); +} + +function frameSwitchingEventOccured(event) +{ + if (!gExpectedEvents.length) { + gEventMatched = false; + return; + } + + try { + var events = gExpectedEvents.shift(); + is(event.target, events[0], "event target"); + is(event.type, events[1], "event type"); + is(fm.focusedElement, events[2], "focused element"); + is(fm.focusedWindow, events[3], "focused frame"); + if (events[4]) + is(fm.getFocusedElementForWindow(events[4], false, {}), events[5], "focused element in frame"); + + if (gEventMatched && event.target == events[0] && event.type == events[1] && + fm.focusedElement == events[2] && fm.focusedWindow == events[3]) { + if (!events[4] || fm.getFocusedElementForWindow(events[4], false, {}) == events[5]) + return; + } + } catch (ex) { ok(ex, "exception"); } + + gEventMatched = false; +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(startTest); + +]]> +</script> + +<commandset id="cu" + commandupdater="true" + events="focus" + oncommandupdate="eventOccured(event)"/> + +<!-- + The elements with ids starting with t are focusable and in the taborder. + The elements with ids starting with o are: + odd numbered ids - focusable but not part of the tab order + even numbered ids - not focusable with -moz-user-focus: ignore or disabled + The elements with ids starting with n are: + odd numbered ids - not focusable with -moz-user-focus: none + even numbered ids - focusable but not part of the tab order + --> +<vbox id="buttonbox"> +<hbox id="innerbox"> + <button id="t4" accesskey="h" label="no tabindex"/> + <button id="o1" accesskey="i" label="tabindex = -1" tabindex="-1"/> + <richlistbox id="t5" label="tabindex = 0" tabindex="0" style="width: 50px"> + <richlistitem height="10"/> + </richlistbox> + <button id="t1" label="tabindex = 2" tabindex="2"/> +</hbox> +<hbox> + <button id="o2" accesskey="o" style="-moz-user-focus: ignore;" label="no tabindex"/> + <button id="o4" style="-moz-user-focus: ignore;" label="no tabindex"/> + <button id="t6" style="-moz-user-focus: ignore;" label="tabindex = 0" tabindex="0"/> + <button id="t2" style="-moz-user-focus: ignore;" label="tabindex = 2" tabindex="2"/> +</hbox> +<hbox id="specialroot"> + <button id="t7" style="-moz-user-focus: normal;" label="no tabindex"/> + <button id="o3" style="-moz-user-focus: normal;" label="tabindex = -1" tabindex="-1"/> + <button id="t8" style="-moz-user-focus: normal;" label="tabindex = 0" tabindex="0"/> + <richlistbox id="t3" style="-moz-user-focus: normal; width: 50px" label="tabindex = 2" tabindex="2"> + <richlistitem style="height: 10px"/> + </richlistbox> +</hbox> +<hbox> + <button accesskey="p" style="display: none;"/> <button accesskey="q" style="visibility: collapse;"/> + <button style="display: none;" tabindex="2"/> <button style="visibility: collapse;" tabindex="2"/> +</hbox> +<hbox> + <button id="o20" accesskey="s" label="no tabindex" disabled="true"/> + <button id="o22" label="tabindex = -1" tabindex="-1" disabled="true"/> + <button id="o24" label="tabindex = 0" tabindex="0" disabled="true"/> + <button id="o26" label="tabindex = 2" tabindex="2" disabled="true"/> +</hbox> +</vbox> +<vbox> +<hbox> + <dropmarker id="o6" value="no tabindex"/> + <dropmarker id="o8" value="no tabindex"/> + <dropmarker id="o10" value="no tabindx"/> + <dropmarker id="o12" value="no tabindex"/> + <dropmarker id="t9" accesskey="r" style="-moz-user-focus: normal;" value="no tabindex" /> + <dropmarker id="t10" style="-moz-user-focus: normal;" value="no tabindex"/> + <dropmarker id="t11" style="-moz-user-focus: normal;" value="tabindex = 0" tabindex="0" /> + <dropmarker id="t12" style="-moz-user-focus: normal;" value="no tabindex"/> + <dropmarker id="o14" style="-moz-user-focus: ignore;" value="no tabindex"/> + <dropmarker id="o16" style="-moz-user-focus: ignore;" value="no tabindex"/> + <dropmarker id="n1" style="-moz-user-focus: none;" value="no tabindex"/> + <dropmarker id="n3" style="-moz-user-focus: none;" value="no tabindex"/> +</hbox> +</vbox> +<browser id="childframe" type="content" src="child_focus_frame.html" style="height: 195px"/> +<button id="t34"/> +<tabbox id="tabbox"> + <tabs><tab id="t35" label="One"/><tab id="tab2" label="Two"/></tabs> + <tabpanels> + <tabpanel> + <checkbox id="t36"/> + <button id="t37"/> + </tabpanel> + <tabpanel> + <checkbox id="htab1"/> + <button id="nohtab2" tabindex="7"/> + <checkbox id="htab2" tabindex="0"/> + </tabpanel> + </tabpanels> +</tabbox> +<hbox> +<panel> + <button id="inpopup1" label="One"/> + <input xmlns="http://www.w3.org/1999/xhtml" label="Two"/> +</panel> +<description label="o" accesskey="v"/> +<button id="t38"/> +<!-- The 't' element tests end here so it doesn't matter that these elements are tabbable --> +<label id="aj" value="j" accesskey="j" control="o9"/> +<label id="ak" accesskey="k" control="n4">k</label> +<checkbox id="o5"/><checkbox id="o7"/><hbox><checkbox id="o9"/></hbox> +<checkbox id="o13"/><checkbox id="o15"/><checkbox id="o17"/><checkbox id="o19"/><checkbox id="o21"/><checkbox id="o23"/><checkbox id="o25"/> +<checkbox id="n2"/><checkbox id="n4"/> +<richlistbox id="last" style="width: 20px; height: 20px"/> + +<iframe id="ifa" style="-moz-user-focus: ignore; width: 40px; height: 60px" type="content" + src="data:text/html,<input id=fra size='2'><input id='fra-b' size='2'> + <iframe src='data:text/html,<input id=frc><iframe src="data:text/html,<input id=frd>"></iframe>'></iframe>"/> +<iframe id="ifb" style="-moz-user-focus: ignore; width: 20px; height: 20px" + src="data:text/html,<input id=frd></iframe>"/> + +</hbox> +</window> |