summaryrefslogtreecommitdiffstats
path: root/browser/base/content/test/general/browser_tabfocus.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/base/content/test/general/browser_tabfocus.js')
-rw-r--r--browser/base/content/test/general/browser_tabfocus.js565
1 files changed, 565 insertions, 0 deletions
diff --git a/browser/base/content/test/general/browser_tabfocus.js b/browser/base/content/test/general/browser_tabfocus.js
new file mode 100644
index 000000000..4042421e8
--- /dev/null
+++ b/browser/base/content/test/general/browser_tabfocus.js
@@ -0,0 +1,565 @@
+/*
+ * This test checks that focus is adjusted properly when switching tabs.
+ */
+
+var testPage1 = "<html id='html1'><body id='body1'><button id='button1'>Tab 1</button></body></html>";
+var testPage2 = "<html id='html2'><body id='body2'><button id='button2'>Tab 2</button></body></html>";
+var testPage3 = "<html id='html3'><body id='body3'><button id='button3'>Tab 3</button></body></html>";
+
+const fm = Services.focus;
+
+function EventStore() {
+ this["main-window"] = [];
+ this["window1"] = [];
+ this["window2"] = [];
+}
+
+EventStore.prototype = {
+ "push": function (event) {
+ if (event.indexOf("1") > -1) {
+ this["window1"].push(event);
+ } else if (event.indexOf("2") > -1) {
+ this["window2"].push(event);
+ } else {
+ this["main-window"].push(event);
+ }
+ }
+}
+
+var tab1 = null;
+var tab2 = null;
+var browser1 = null;
+var browser2 = null;
+var _lastfocus;
+var _lastfocuswindow = null;
+var actualEvents = new EventStore();
+var expectedEvents = new EventStore();
+var currentTestName = "";
+var _expectedElement = null;
+var _expectedWindow = null;
+
+var currentPromiseResolver = null;
+
+function* getFocusedElementForBrowser(browser, dontCheckExtraFocus = false)
+{
+ if (gMultiProcessBrowser) {
+ return new Promise((resolve, reject) => {
+ messageManager.addMessageListener("Browser:GetCurrentFocus", function getCurrentFocus(message) {
+ messageManager.removeMessageListener("Browser:GetCurrentFocus", getCurrentFocus);
+ resolve(message.data.details);
+ });
+
+ // The dontCheckExtraFocus flag is used to indicate not to check some
+ // additional focus related properties. This is needed as both URLs are
+ // loaded using the same child process and share focus managers.
+ browser.messageManager.sendAsyncMessage("Browser:GetFocusedElement",
+ { dontCheckExtraFocus : dontCheckExtraFocus });
+ });
+ }
+ var focusedWindow = {};
+ var node = fm.getFocusedElementForWindow(browser.contentWindow, false, focusedWindow);
+ return "Focus is " + (node ? node.id : "<none>");
+}
+
+function focusInChild()
+{
+ var contentFM = Components.classes["@mozilla.org/focus-manager;1"].
+ getService(Components.interfaces.nsIFocusManager);
+
+ function getWindowDocId(target)
+ {
+ return (String(target.location).indexOf("1") >= 0) ? "window1" : "window2";
+ }
+
+ function eventListener(event) {
+ var id;
+ if (event.target instanceof Components.interfaces.nsIDOMWindow)
+ id = getWindowDocId(event.originalTarget) + "-window";
+ else if (event.target instanceof Components.interfaces.nsIDOMDocument)
+ id = getWindowDocId(event.originalTarget) + "-document";
+ else
+ id = event.originalTarget.id;
+ sendSyncMessage("Browser:FocusChanged", { details : event.type + ": " + id });
+ }
+
+ addEventListener("focus", eventListener, true);
+ addEventListener("blur", eventListener, true);
+
+ addMessageListener("Browser:ChangeFocus", function changeFocus(message) {
+ content.document.getElementById(message.data.id)[message.data.type]();
+ });
+
+ addMessageListener("Browser:GetFocusedElement", function getFocusedElement(message) {
+ var focusedWindow = {};
+ var node = contentFM.getFocusedElementForWindow(content, false, focusedWindow);
+ var details = "Focus is " + (node ? node.id : "<none>");
+
+ /* Check focus manager properties. Add an error onto the string if they are
+ not what is expected which will cause matching to fail in the parent process. */
+ let doc = content.document;
+ if (!message.data.dontCheckExtraFocus) {
+ if (contentFM.focusedElement != node) {
+ details += "<ERROR: focusedElement doesn't match>";
+ }
+ if (contentFM.focusedWindow && contentFM.focusedWindow != content) {
+ details += "<ERROR: focusedWindow doesn't match>";
+ }
+ if ((contentFM.focusedWindow == content) != doc.hasFocus()) {
+ details += "<ERROR: child hasFocus() is not correct>";
+ }
+ if ((contentFM.focusedElement && doc.activeElement != contentFM.focusedElement) ||
+ (!contentFM.focusedElement && doc.activeElement != doc.body)) {
+ details += "<ERROR: child activeElement is not correct>";
+ }
+ }
+
+ sendSyncMessage("Browser:GetCurrentFocus", { details : details });
+ });
+}
+
+function focusElementInChild(elementid, type)
+{
+ let browser = (elementid.indexOf("1") >= 0) ? browser1 : browser2;
+ if (gMultiProcessBrowser) {
+ browser.messageManager.sendAsyncMessage("Browser:ChangeFocus",
+ { id: elementid, type: type });
+ }
+ else {
+ browser.contentDocument.getElementById(elementid)[type]();
+ }
+}
+
+add_task(function*() {
+ tab1 = gBrowser.addTab();
+ browser1 = gBrowser.getBrowserForTab(tab1);
+
+ tab2 = gBrowser.addTab();
+ browser2 = gBrowser.getBrowserForTab(tab2);
+
+ yield promiseTabLoadEvent(tab1, "data:text/html," + escape(testPage1));
+ yield promiseTabLoadEvent(tab2, "data:text/html," + escape(testPage2));
+
+ var childFocusScript = "data:,(" + focusInChild.toString() + ")();";
+ browser1.messageManager.loadFrameScript(childFocusScript, true);
+ browser2.messageManager.loadFrameScript(childFocusScript, true);
+
+ gURLBar.focus();
+ yield SimpleTest.promiseFocus();
+
+ if (gMultiProcessBrowser) {
+ messageManager.addMessageListener("Browser:FocusChanged", message => {
+ actualEvents.push(message.data.details);
+ compareFocusResults();
+ });
+ }
+
+ _lastfocus = "urlbar";
+ _lastfocuswindow = "main-window";
+
+ window.addEventListener("focus", _browser_tabfocus_test_eventOccured, true);
+ window.addEventListener("blur", _browser_tabfocus_test_eventOccured, true);
+
+ // make sure that the focus initially starts out blank
+ var focusedWindow = {};
+
+ let focused = yield getFocusedElementForBrowser(browser1);
+ is(focused, "Focus is <none>", "initial focus in tab 1");
+
+ focused = yield getFocusedElementForBrowser(browser2);
+ is(focused, "Focus is <none>", "initial focus in tab 2");
+
+ is(document.activeElement, gURLBar.inputField, "focus after loading two tabs");
+
+ yield* expectFocusShiftAfterTabSwitch(tab2, "window2", null, true,
+ "after tab change, focus in new tab");
+
+ focused = yield getFocusedElementForBrowser(browser2);
+ is(focused, "Focus is <none>", "focusedElement after tab change, focus in new tab");
+
+ // switching tabs when nothing in the new tab is focused
+ // should focus the browser
+ yield* expectFocusShiftAfterTabSwitch(tab1, "window1", null, true,
+ "after tab change, focus in original tab");
+
+ focused = yield getFocusedElementForBrowser(browser1);
+ is(focused, "Focus is <none>", "focusedElement after tab change, focus in original tab");
+
+ // focusing a button in the current tab should focus it
+ yield expectFocusShift(() => focusElementInChild("button1", "focus"),
+ "window1", "button1", true,
+ "after button focused");
+
+ focused = yield getFocusedElementForBrowser(browser1);
+ is(focused, "Focus is button1", "focusedElement in first browser after button focused");
+
+ // focusing a button in a background tab should not change the actual
+ // focus, but should set the focus that would be in that background tab to
+ // that button.
+ yield expectFocusShift(() => focusElementInChild("button2", "focus"),
+ "window1", "button1", false,
+ "after button focus in unfocused tab");
+
+ focused = yield getFocusedElementForBrowser(browser1, false);
+ is(focused, "Focus is button1", "focusedElement in first browser after button focus in unfocused tab");
+ focused = yield getFocusedElementForBrowser(browser2, true);
+ is(focused, "Focus is button2", "focusedElement in second browser after button focus in unfocused tab");
+
+ // switching tabs should now make the button in the other tab focused
+ yield* expectFocusShiftAfterTabSwitch(tab2, "window2", "button2", true,
+ "after tab change with button focused");
+
+ // blurring an element in a background tab should not change the active
+ // focus, but should clear the focus in that tab.
+ yield expectFocusShift(() => focusElementInChild("button1", "blur"),
+ "window2", "button2", false,
+ "focusedWindow after blur in unfocused tab");
+
+ focused = yield getFocusedElementForBrowser(browser1, true);
+ is(focused, "Focus is <none>", "focusedElement in first browser after focus in unfocused tab");
+ focused = yield getFocusedElementForBrowser(browser2, false);
+ is(focused, "Focus is button2", "focusedElement in second browser after focus in unfocused tab");
+
+ // When focus is in the tab bar, it should be retained there
+ yield expectFocusShift(() => gBrowser.selectedTab.focus(),
+ "main-window", "tab2", true,
+ "focusing tab element");
+ yield* expectFocusShiftAfterTabSwitch(tab1, "main-window", "tab1", true,
+ "tab change when selected tab element was focused");
+
+ let switchWaiter;
+ if (gMultiProcessBrowser) {
+ switchWaiter = new Promise((resolve, reject) => {
+ gBrowser.addEventListener("TabSwitchDone", function listener() {
+ gBrowser.removeEventListener("TabSwitchDone", listener);
+ executeSoon(resolve);
+ });
+ });
+ }
+
+ yield* expectFocusShiftAfterTabSwitch(tab2, "main-window", "tab2", true,
+ "another tab change when selected tab element was focused");
+
+ // When this a remote browser, wait for the paint on the second browser so that
+ // any post tab-switching stuff has time to complete before blurring the tab.
+ // Otherwise, the _adjustFocusAfterTabSwitch in tabbrowser gets confused and
+ // isn't sure what tab is really focused.
+ if (gMultiProcessBrowser) {
+ yield switchWaiter;
+ }
+
+ yield expectFocusShift(() => gBrowser.selectedTab.blur(),
+ "main-window", null, true,
+ "blurring tab element");
+
+ // focusing the url field should switch active focus away from the browser but
+ // not clear what would be the focus in the browser
+ focusElementInChild("button1", "focus");
+
+ yield expectFocusShift(() => gURLBar.focus(),
+ "main-window", "urlbar", true,
+ "focusedWindow after url field focused");
+ focused = yield getFocusedElementForBrowser(browser1, true);
+ is(focused, "Focus is button1", "focusedElement after url field focused, first browser");
+ focused = yield getFocusedElementForBrowser(browser2, true);
+ is(focused, "Focus is button2", "focusedElement after url field focused, second browser");
+
+ yield expectFocusShift(() => gURLBar.blur(),
+ "main-window", null, true,
+ "blurring url field");
+
+ // when a chrome element is focused, switching tabs to a tab with a button
+ // with the current focus should focus the button
+ yield* expectFocusShiftAfterTabSwitch(tab1, "window1", "button1", true,
+ "after tab change, focus in url field, button focused in new tab");
+
+ focused = yield getFocusedElementForBrowser(browser1, false);
+ is(focused, "Focus is button1", "after switch tab, focus in unfocused tab, first browser");
+ focused = yield getFocusedElementForBrowser(browser2, true);
+ is(focused, "Focus is button2", "after switch tab, focus in unfocused tab, second browser");
+
+ // blurring an element in the current tab should clear the active focus
+ yield expectFocusShift(() => focusElementInChild("button1", "blur"),
+ "window1", null, true,
+ "after blur in focused tab");
+
+ focused = yield getFocusedElementForBrowser(browser1, false);
+ is(focused, "Focus is <none>", "focusedWindow after blur in focused tab, child");
+ focusedWindow = {};
+ is(fm.getFocusedElementForWindow(window, false, focusedWindow), browser1, "focusedElement after blur in focused tab, parent");
+
+ // blurring an non-focused url field should have no effect
+ yield expectFocusShift(() => gURLBar.blur(),
+ "window1", null, false,
+ "after blur in unfocused url field");
+
+ focusedWindow = {};
+ is(fm.getFocusedElementForWindow(window, false, focusedWindow), browser1, "focusedElement after blur in unfocused url field");
+
+ // switch focus to a tab with a currently focused element
+ yield* expectFocusShiftAfterTabSwitch(tab2, "window2", "button2", true,
+ "after switch from unfocused to focused tab");
+ focused = yield getFocusedElementForBrowser(browser2, true);
+ is(focused, "Focus is button2", "focusedElement after switch from unfocused to focused tab");
+
+ // clearing focus on the chrome window should switch the focus to the
+ // chrome window
+ yield expectFocusShift(() => fm.clearFocus(window),
+ "main-window", null, true,
+ "after switch to chrome with no focused element");
+
+ focusedWindow = {};
+ is(fm.getFocusedElementForWindow(window, false, focusedWindow), null, "focusedElement after switch to chrome with no focused element");
+
+ // switch focus to another tab when neither have an active focus
+ yield* expectFocusShiftAfterTabSwitch(tab1, "window1", null, true,
+ "focusedWindow after tab switch from no focus to no focus");
+
+ focused = yield getFocusedElementForBrowser(browser1, false);
+ is(focused, "Focus is <none>", "after tab switch from no focus to no focus, first browser");
+ focused = yield getFocusedElementForBrowser(browser2, true);
+ is(focused, "Focus is button2", "after tab switch from no focus to no focus, second browser");
+
+ // next, check whether navigating forward, focusing the urlbar and then
+ // navigating back maintains the focus in the urlbar.
+ yield expectFocusShift(() => focusElementInChild("button1", "focus"),
+ "window1", "button1", true,
+ "focus button");
+
+ yield promiseTabLoadEvent(tab1, "data:text/html," + escape(testPage3));
+
+ // now go back again
+ gURLBar.focus();
+
+ yield new Promise((resolve, reject) => {
+ window.addEventListener("pageshow", function navigationOccured(event) {
+ window.removeEventListener("pageshow", navigationOccured, true);
+ resolve();
+ }, true);
+ document.getElementById('Browser:Back').doCommand();
+ });
+
+ is(window.document.activeElement, gURLBar.inputField, "urlbar still focused after navigating back");
+
+ // Document navigation with F6 does not yet work in mutli-process browsers.
+ if (!gMultiProcessBrowser) {
+ gURLBar.focus();
+ actualEvents = new EventStore();
+ _lastfocus = "urlbar";
+ _lastfocuswindow = "main-window";
+
+ yield expectFocusShift(() => EventUtils.synthesizeKey("VK_F6", { }),
+ "window1", "html1",
+ true, "switch document forward with f6");
+
+ EventUtils.synthesizeKey("VK_F6", { });
+ is(fm.focusedWindow, window, "switch document forward again with f6");
+
+ browser1.style.MozUserFocus = "ignore";
+ browser1.clientWidth;
+ EventUtils.synthesizeKey("VK_F6", { });
+ is(fm.focusedWindow, window, "switch document forward again with f6 when browser non-focusable");
+
+ browser1.style.MozUserFocus = "normal";
+ browser1.clientWidth;
+ }
+
+ window.removeEventListener("focus", _browser_tabfocus_test_eventOccured, true);
+ window.removeEventListener("blur", _browser_tabfocus_test_eventOccured, true);
+
+ gBrowser.removeCurrentTab();
+ gBrowser.removeCurrentTab();
+
+ finish();
+});
+
+function _browser_tabfocus_test_eventOccured(event)
+{
+ function getWindowDocId(target)
+ {
+ if (target == browser1.contentWindow || target == browser1.contentDocument) {
+ return "window1";
+ }
+ if (target == browser2.contentWindow || target == browser2.contentDocument) {
+ return "window2";
+ }
+ return "main-window";
+ }
+
+ var id;
+
+ // Some focus events from the child bubble up? Ignore them.
+ if (Cu.isCrossProcessWrapper(event.originalTarget))
+ return;
+
+ if (event.target instanceof Window)
+ id = getWindowDocId(event.originalTarget) + "-window";
+ else if (event.target instanceof Document)
+ id = getWindowDocId(event.originalTarget) + "-document";
+ else if (event.target.id == "urlbar" && event.originalTarget.localName == "input")
+ id = "urlbar";
+ else if (event.originalTarget.localName == "browser")
+ id = (event.originalTarget == browser1) ? "browser1" : "browser2";
+ else if (event.originalTarget.localName == "tab")
+ id = (event.originalTarget == tab1) ? "tab1" : "tab2";
+ else
+ id = event.originalTarget.id;
+
+ actualEvents.push(event.type + ": " + id);
+ compareFocusResults();
+}
+
+function getId(element)
+{
+ if (!element) {
+ return null;
+ }
+
+ if (element.localName == "browser") {
+ return element == browser1 ? "browser1" : "browser2";
+ }
+
+ if (element.localName == "tab") {
+ return element == tab1 ? "tab1" : "tab2";
+ }
+
+ return (element.localName == "input") ? "urlbar" : element.id;
+}
+
+function compareFocusResults()
+{
+ if (!currentPromiseResolver)
+ return;
+
+ let winIds = ["main-window", "window1", "window2"];
+
+ for (let winId of winIds) {
+ if (actualEvents[winId].length < expectedEvents[winId].length)
+ return;
+ }
+
+ for (let winId of winIds) {
+ for (let e = 0; e < expectedEvents.length; e++) {
+ is(actualEvents[winId][e], expectedEvents[winId][e], currentTestName + " events [event " + e + "]");
+ }
+ actualEvents[winId] = [];
+ }
+
+ // Use executeSoon as this will be called during a focus/blur event handler
+ executeSoon(() => {
+ let matchWindow = window;
+ if (gMultiProcessBrowser) {
+ is(_expectedWindow, "main-window", "main-window is always expected");
+ }
+ else if (_expectedWindow != "main-window") {
+ matchWindow = (_expectedWindow == "window1" ? browser1.contentWindow : browser2.contentWindow);
+ }
+
+ var focusedElement = fm.focusedElement;
+ is(getId(focusedElement), _expectedElement, currentTestName + " focusedElement");
+ is(fm.focusedWindow, matchWindow, currentTestName + " focusedWindow");
+ var focusedWindow = {};
+ is(getId(fm.getFocusedElementForWindow(matchWindow, false, focusedWindow)),
+ _expectedElement, currentTestName + " getFocusedElementForWindow");
+ is(focusedWindow.value, matchWindow, currentTestName + " getFocusedElementForWindow frame");
+ is(matchWindow.document.hasFocus(), true, currentTestName + " hasFocus");
+ var expectedActive = _expectedElement;
+ if (!expectedActive) {
+ expectedActive = matchWindow.document instanceof XULDocument ?
+ "main-window" : getId(matchWindow.document.body);
+ }
+ is(getId(matchWindow.document.activeElement), expectedActive, currentTestName + " activeElement");
+
+ currentPromiseResolver();
+ currentPromiseResolver = null;
+ });
+}
+
+function* expectFocusShiftAfterTabSwitch(tab, expectedWindow, expectedElement, focusChanged, testid)
+{
+ let tabSwitchPromise = null;
+ yield expectFocusShift(() => { tabSwitchPromise = BrowserTestUtils.switchTab(gBrowser, tab) },
+ expectedWindow, expectedElement, focusChanged, testid)
+ yield tabSwitchPromise;
+}
+
+function* expectFocusShift(callback, expectedWindow, expectedElement, focusChanged, testid)
+{
+ currentPromiseResolver = null;
+ currentTestName = testid;
+
+ expectedEvents = new EventStore();
+
+ if (focusChanged) {
+ _expectedElement = expectedElement;
+ _expectedWindow = expectedWindow;
+
+ // When the content is in a child process, the expected element in the chrome window
+ // will always be the urlbar or a browser element.
+ if (gMultiProcessBrowser) {
+ if (_expectedWindow == "window1") {
+ _expectedElement = "browser1";
+ }
+ else if (_expectedWindow == "window2") {
+ _expectedElement = "browser2";
+ }
+ _expectedWindow = "main-window";
+ }
+
+ if (gMultiProcessBrowser && _lastfocuswindow != "main-window" &&
+ _lastfocuswindow != expectedWindow) {
+ let browserid = _lastfocuswindow == "window1" ? "browser1" : "browser2";
+ expectedEvents.push("blur: " + browserid);
+ }
+
+ var newElementIsFocused = (expectedElement && !expectedElement.startsWith("html"));
+ if (newElementIsFocused && gMultiProcessBrowser &&
+ _lastfocuswindow != "main-window" &&
+ expectedWindow == "main-window") {
+ // When switching from a child to a chrome element, the focus on the element will arrive first.
+ expectedEvents.push("focus: " + expectedElement);
+ newElementIsFocused = false;
+ }
+
+ if (_lastfocus && _lastfocus != _expectedElement)
+ expectedEvents.push("blur: " + _lastfocus);
+
+ if (_lastfocuswindow &&
+ _lastfocuswindow != expectedWindow) {
+
+ if (!gMultiProcessBrowser || _lastfocuswindow != "main-window") {
+ expectedEvents.push("blur: " + _lastfocuswindow + "-document");
+ expectedEvents.push("blur: " + _lastfocuswindow + "-window");
+ }
+ }
+
+ if (expectedWindow && _lastfocuswindow != expectedWindow) {
+ if (gMultiProcessBrowser && expectedWindow != "main-window") {
+ let browserid = expectedWindow == "window1" ? "browser1" : "browser2";
+ expectedEvents.push("focus: " + browserid);
+ }
+
+ if (!gMultiProcessBrowser || expectedWindow != "main-window") {
+ expectedEvents.push("focus: " + expectedWindow + "-document");
+ expectedEvents.push("focus: " + expectedWindow + "-window");
+ }
+ }
+
+ if (newElementIsFocused) {
+ expectedEvents.push("focus: " + expectedElement);
+ }
+
+ _lastfocus = expectedElement;
+ _lastfocuswindow = expectedWindow;
+ }
+
+ return new Promise((resolve, reject) => {
+ currentPromiseResolver = resolve;
+ callback();
+
+ // No events are expected, so resolve the promise immediately.
+ if (expectedEvents["main-window"].length + expectedEvents["window1"].length + expectedEvents["window2"].length == 0) {
+ currentPromiseResolver();
+ currentPromiseResolver = null;
+ }
+ });
+}