michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: http://creativecommons.org/publicdomain/zero/1.0/ */ michael@0: michael@0: const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; michael@0: michael@0: // Some tests here assume that all restored tabs are loaded without waiting for michael@0: // the user to bring them to the foreground. We ensure this by resetting the michael@0: // related preference (see the "firefox.js" defaults file for details). michael@0: Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", false); michael@0: registerCleanupFunction(function () { michael@0: Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand"); michael@0: }); michael@0: michael@0: // ---------- michael@0: function createEmptyGroupItem(contentWindow, width, height, padding, animate) { michael@0: let pageBounds = contentWindow.Items.getPageBounds(); michael@0: pageBounds.inset(padding, padding); michael@0: michael@0: let box = new contentWindow.Rect(pageBounds); michael@0: box.width = width; michael@0: box.height = height; michael@0: michael@0: let emptyGroupItem = michael@0: new contentWindow.GroupItem([], { bounds: box, immediately: !animate }); michael@0: michael@0: return emptyGroupItem; michael@0: } michael@0: michael@0: // ---------- michael@0: function createGroupItemWithTabs(win, width, height, padding, urls, animate) { michael@0: let contentWindow = win.TabView.getContentWindow(); michael@0: let groupItemCount = contentWindow.GroupItems.groupItems.length; michael@0: // create empty group item michael@0: let groupItem = createEmptyGroupItem(contentWindow, width, height, padding, animate); michael@0: ok(groupItem.isEmpty(), "This group is empty"); michael@0: is(contentWindow.GroupItems.groupItems.length, ++groupItemCount, michael@0: "The number of groups is increased by 1"); michael@0: // add blank items michael@0: contentWindow.UI.setActive(groupItem); michael@0: let t = 0; michael@0: urls.forEach( function(url) { michael@0: let newItem = win.gBrowser.loadOneTab(url)._tabViewTabItem; michael@0: ok(newItem.container, "Created element "+t+":"+newItem.container); michael@0: ++t; michael@0: }); michael@0: // to set one of tabItem to be active since we load tabs into a group michael@0: // in a non-standard flow. michael@0: contentWindow.UI.setActive(groupItem); michael@0: return groupItem; michael@0: } michael@0: michael@0: // ---------- michael@0: function createGroupItemWithBlankTabs(win, width, height, padding, numNewTabs, animate) { michael@0: let urls = []; michael@0: while(numNewTabs--) michael@0: urls.push("about:blank"); michael@0: return createGroupItemWithTabs(win, width, height, padding, urls, animate); michael@0: } michael@0: michael@0: // ---------- michael@0: function closeGroupItem(groupItem, callback) { michael@0: if (callback) { michael@0: groupItem.addSubscriber("close", function onClose() { michael@0: groupItem.removeSubscriber("close", onClose); michael@0: executeSoon(callback); michael@0: }); michael@0: } michael@0: michael@0: if (groupItem.getChildren().length) { michael@0: groupItem.addSubscriber("groupHidden", function onHide() { michael@0: groupItem.removeSubscriber("groupHidden", onHide); michael@0: groupItem.closeHidden(); michael@0: }); michael@0: } michael@0: michael@0: groupItem.closeAll(); michael@0: } michael@0: michael@0: // ---------- michael@0: function afterAllTabItemsUpdated(callback, win) { michael@0: win = win || window; michael@0: let tabItems = win.document.getElementById("tab-view").contentWindow.TabItems; michael@0: let counter = 0; michael@0: michael@0: for (let a = 0; a < win.gBrowser.tabs.length; a++) { michael@0: let tabItem = win.gBrowser.tabs[a]._tabViewTabItem; michael@0: if (tabItem) { michael@0: let tab = win.gBrowser.tabs[a]; michael@0: counter++; michael@0: tabItem.addSubscriber("updated", function onUpdated() { michael@0: tabItem.removeSubscriber("updated", onUpdated); michael@0: if (--counter == 0) michael@0: callback(); michael@0: }); michael@0: tabItems.update(tab); michael@0: } michael@0: } michael@0: if (counter == 0) michael@0: callback(); michael@0: } michael@0: michael@0: // --------- michael@0: function newWindowWithTabView(shownCallback, loadCallback, width, height) { michael@0: let winWidth = width || 800; michael@0: let winHeight = height || 800; michael@0: let win = window.openDialog(getBrowserURL(), "_blank", michael@0: "chrome,all,dialog=no,height=" + winHeight + michael@0: ",width=" + winWidth, "about:blank"); michael@0: michael@0: whenWindowLoaded(win, function () { michael@0: if (loadCallback) michael@0: loadCallback(win); michael@0: }); michael@0: michael@0: whenDelayedStartupFinished(win, function () { michael@0: showTabView(function () shownCallback(win), win); michael@0: }); michael@0: } michael@0: michael@0: // ---------- michael@0: function afterAllTabsLoaded(callback, win) { michael@0: const TAB_STATE_NEEDS_RESTORE = 1; michael@0: michael@0: win = win || window; michael@0: michael@0: let stillToLoad = 0; michael@0: let restoreHiddenTabs = Services.prefs.getBoolPref( michael@0: "browser.sessionstore.restore_hidden_tabs"); michael@0: michael@0: function onLoad() { michael@0: this.removeEventListener("load", onLoad, true); michael@0: stillToLoad--; michael@0: if (!stillToLoad) michael@0: executeSoon(callback); michael@0: } michael@0: michael@0: for (let a = 0; a < win.gBrowser.tabs.length; a++) { michael@0: let tab = win.gBrowser.tabs[a]; michael@0: let browser = tab.linkedBrowser; michael@0: michael@0: let isRestorable = !(tab.hidden && !restoreHiddenTabs && michael@0: browser.__SS_restoreState && michael@0: browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE); michael@0: michael@0: if (isRestorable && browser.webProgress.isLoadingDocument) { michael@0: stillToLoad++; michael@0: browser.addEventListener("load", onLoad, true); michael@0: } michael@0: } michael@0: michael@0: if (!stillToLoad) michael@0: executeSoon(callback); michael@0: } michael@0: michael@0: // ---------- michael@0: function showTabView(callback, win) { michael@0: win = win || window; michael@0: michael@0: if (win.TabView.isVisible()) { michael@0: waitForFocus(callback, win); michael@0: return; michael@0: } michael@0: michael@0: whenTabViewIsShown(function () { michael@0: waitForFocus(callback, win); michael@0: }, win); michael@0: michael@0: win.TabView.show(); michael@0: } michael@0: michael@0: // ---------- michael@0: function hideTabView(callback, win) { michael@0: win = win || window; michael@0: michael@0: if (!win.TabView.isVisible()) { michael@0: if (callback) michael@0: callback(); michael@0: return; michael@0: } michael@0: michael@0: if (callback) michael@0: whenTabViewIsHidden(callback, win); michael@0: michael@0: win.TabView.hide(); michael@0: } michael@0: michael@0: // ---------- michael@0: function whenTabViewIsHidden(callback, win) { michael@0: win = win || window; michael@0: michael@0: if (!win.TabView.isVisible()) { michael@0: callback(); michael@0: return; michael@0: } michael@0: michael@0: win.addEventListener('tabviewhidden', function onHidden() { michael@0: win.removeEventListener('tabviewhidden', onHidden, false); michael@0: callback(); michael@0: }, false); michael@0: } michael@0: michael@0: // ---------- michael@0: function whenTabViewIsShown(callback, win) { michael@0: win = win || window; michael@0: michael@0: if (win.TabView.isVisible()) { michael@0: callback(); michael@0: return; michael@0: } michael@0: michael@0: win.addEventListener('tabviewshown', function onShown() { michael@0: win.removeEventListener('tabviewshown', onShown, false); michael@0: callback(); michael@0: }, false); michael@0: } michael@0: michael@0: // ---------- michael@0: function hideSearch(callback, win) { michael@0: win = win || window; michael@0: michael@0: let contentWindow = win.TabView.getContentWindow(); michael@0: if (!contentWindow.Search.isEnabled()) { michael@0: if (callback) michael@0: callback(); michael@0: return; michael@0: } michael@0: michael@0: if (callback) michael@0: whenSearchIsDisabled(callback, win); michael@0: michael@0: contentWindow.Search.hide(); michael@0: } michael@0: michael@0: // ---------- michael@0: function whenSearchIsEnabled(callback, win) { michael@0: win = win || window; michael@0: michael@0: let contentWindow = win.TabView.getContentWindow(); michael@0: if (contentWindow.Search.isEnabled()) { michael@0: callback(); michael@0: return; michael@0: } michael@0: michael@0: contentWindow.addEventListener("tabviewsearchenabled", function onSearchEnabled() { michael@0: contentWindow.removeEventListener("tabviewsearchenabled", onSearchEnabled, false); michael@0: callback(); michael@0: }, false); michael@0: } michael@0: michael@0: // ---------- michael@0: function whenSearchIsDisabled(callback, win) { michael@0: win = win || window; michael@0: michael@0: let contentWindow = win.TabView.getContentWindow(); michael@0: if (!contentWindow.Search.isEnabled()) { michael@0: callback(); michael@0: return; michael@0: } michael@0: michael@0: contentWindow.addEventListener("tabviewsearchdisabled", function onSearchDisabled() { michael@0: contentWindow.removeEventListener("tabviewsearchdisabled", onSearchDisabled, false); michael@0: callback(); michael@0: }, false); michael@0: } michael@0: michael@0: // ---------- michael@0: function hideGroupItem(groupItem, callback) { michael@0: if (groupItem.hidden) { michael@0: if (callback) michael@0: callback(); michael@0: return; michael@0: } michael@0: michael@0: if (callback) { michael@0: groupItem.addSubscriber("groupHidden", function onHide() { michael@0: groupItem.removeSubscriber("groupHidden", onHide); michael@0: callback(); michael@0: }); michael@0: } michael@0: michael@0: groupItem.closeAll(); michael@0: } michael@0: michael@0: // ---------- michael@0: function unhideGroupItem(groupItem, callback) { michael@0: if (!groupItem.hidden) { michael@0: if (callback) michael@0: callback(); michael@0: return; michael@0: } michael@0: michael@0: if (callback) { michael@0: groupItem.addSubscriber("groupShown", function onShown() { michael@0: groupItem.removeSubscriber("groupShown", onShown); michael@0: callback(); michael@0: }); michael@0: } michael@0: michael@0: groupItem._unhide(); michael@0: } michael@0: michael@0: // ---------- michael@0: function whenWindowLoaded(win, callback) { michael@0: win.addEventListener("load", function onLoad() { michael@0: win.removeEventListener("load", onLoad, false); michael@0: executeSoon(callback); michael@0: }, false); michael@0: } michael@0: michael@0: // ---------- michael@0: function whenWindowStateReady(win, callback) { michael@0: win.addEventListener("SSWindowStateReady", function onReady() { michael@0: win.removeEventListener("SSWindowStateReady", onReady, false); michael@0: executeSoon(callback); michael@0: }, false); michael@0: } michael@0: michael@0: // ---------- michael@0: function whenDelayedStartupFinished(win, callback) { michael@0: let topic = "browser-delayed-startup-finished"; michael@0: Services.obs.addObserver(function onStartup(aSubject) { michael@0: if (win != aSubject) michael@0: return; michael@0: michael@0: Services.obs.removeObserver(onStartup, topic); michael@0: executeSoon(callback); michael@0: }, topic, false); michael@0: } michael@0: michael@0: // ---------- michael@0: function newWindowWithState(state, callback) { michael@0: const ss = Cc["@mozilla.org/browser/sessionstore;1"] michael@0: .getService(Ci.nsISessionStore); michael@0: michael@0: let opts = "chrome,all,dialog=no,height=800,width=800"; michael@0: let win = window.openDialog(getBrowserURL(), "_blank", opts, "about:blank"); michael@0: michael@0: let numConditions = 2; michael@0: let check = function () { michael@0: if (!--numConditions) michael@0: callback(win); michael@0: }; michael@0: michael@0: whenDelayedStartupFinished(win, function () { michael@0: ss.setWindowState(win, JSON.stringify(state), true); michael@0: win.close(); michael@0: // Give it time to close michael@0: executeSoon(function() { michael@0: win = ss.undoCloseWindow(0); michael@0: michael@0: whenWindowLoaded(win, function () { michael@0: afterAllTabsLoaded(check, win); michael@0: }); michael@0: michael@0: whenDelayedStartupFinished(win, check); michael@0: }); michael@0: }); michael@0: } michael@0: michael@0: // ---------- michael@0: function restoreTab(callback, index, win) { michael@0: win = win || window; michael@0: michael@0: let tab = win.undoCloseTab(index || 0); michael@0: let tabItem = tab._tabViewTabItem; michael@0: michael@0: let finalize = function () { michael@0: afterAllTabsLoaded(function () callback(tab), win); michael@0: }; michael@0: michael@0: if (tabItem._reconnected) { michael@0: finalize(); michael@0: return; michael@0: } michael@0: michael@0: tab._tabViewTabItem.addSubscriber("reconnected", function onReconnected() { michael@0: tab._tabViewTabItem.removeSubscriber("reconnected", onReconnected); michael@0: finalize(); michael@0: }); michael@0: } michael@0: michael@0: // ---------- michael@0: function goToNextGroup(win) { michael@0: win = win || window; michael@0: michael@0: let utils = michael@0: win.QueryInterface(Ci.nsIInterfaceRequestor). michael@0: getInterface(Ci.nsIDOMWindowUtils); michael@0: michael@0: const masks = Ci.nsIDOMNSEvent; michael@0: let mval = 0; michael@0: mval |= masks.CONTROL_MASK; michael@0: michael@0: utils.sendKeyEvent("keypress", 0, 96, mval); michael@0: } michael@0: michael@0: // ---------- michael@0: function whenAppTabIconAdded(groupItem, callback) { michael@0: groupItem.addSubscriber("appTabIconAdded", function onAppTabIconAdded() { michael@0: groupItem.removeSubscriber("appTabIconAdded", onAppTabIconAdded); michael@0: executeSoon(callback); michael@0: }); michael@0: } michael@0: michael@0: /** michael@0: * Chrome windows aren't closed synchronously. Provide a helper method to close michael@0: * a window and wait until we received the "domwindowclosed" notification for it. michael@0: */ michael@0: function promiseWindowClosed(win) { michael@0: let deferred = Promise.defer(); michael@0: michael@0: Services.obs.addObserver(function obs(subject, topic) { michael@0: if (subject == win) { michael@0: Services.obs.removeObserver(obs, topic); michael@0: deferred.resolve(); michael@0: } michael@0: }, "domwindowclosed", false); michael@0: michael@0: win.close(); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: // ---------- michael@0: function waitForOnBeforeUnloadDialog(browser, callback) { michael@0: browser.addEventListener("DOMWillOpenModalDialog", function onModalDialog() { michael@0: browser.removeEventListener("DOMWillOpenModalDialog", onModalDialog, true); michael@0: michael@0: executeSoon(() => { michael@0: let stack = browser.parentNode; michael@0: let dialogs = stack.getElementsByTagNameNS(XUL_NS, "tabmodalprompt"); michael@0: let {button0, button1} = dialogs[0].ui; michael@0: callback(button0, button1); michael@0: }); michael@0: }, true); michael@0: } michael@0: michael@0: /** michael@0: * Overrides browser.js' OpenBrowserWindow() function to enforce an initial michael@0: * tab different from about:home to not hit the network. michael@0: */ michael@0: function OpenBrowserWindow(aOptions) { michael@0: let features = ""; michael@0: let url = "about:blank"; michael@0: michael@0: if (aOptions && aOptions.private || false) { michael@0: features = ",private"; michael@0: url = "about:privatebrowsing"; michael@0: } michael@0: michael@0: return openDialog(getBrowserURL(), "", "chrome,all,dialog=no" + features, url); michael@0: }