1 /* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 * The contents of this file are subject to the Mozilla Public License Version
5 * 1.1 (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 * http://www.mozilla.org/MPL/
9 * Software distributed under the License is distributed on an "AS IS" basis,
10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 * for the specific language governing rights and limitations under the
14 * The Original Code is MozMill Test code.
16 * The Initial Developer of the Original Code is Mozilla Foundation.
17 * Portions created by the Initial Developer are Copyright (C) 2009
18 * the Initial Developer. All Rights Reserved.
21 * Henrik Skupin <hskupin@mozilla.com>
22 * Anthony Hughes <ahughes@mozilla.com>
24 * Alternatively, the contents of this file may be used under the terms of
25 * either the GNU General Public License Version 2 or later (the "GPL"), or
26 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 * in which case the provisions of the GPL or the LGPL are applicable instead
28 * of those above. If you wish to allow use of your version of this file only
29 * under the terms of either the GPL or the LGPL, and not to allow others to
30 * use your version of this file under the terms of the MPL, indicate your
31 * decision by deleting the provisions above and replace them with the notice
32 * and other provisions required by the GPL or the LGPL. If you do not delete
33 * the provisions above, a recipient may use your version of this file under
34 * the terms of any one of the MPL, the GPL or the LGPL.
36 * ***** END LICENSE BLOCK ***** */
40 * The TabbedBrowsingAPI adds support for accessing and interacting with tab elements
45 // Include required modules
46 var utils = require("utils");
47 var prefs = require("prefs");
51 const PREF_TABS_ANIMATE = "browser.tabs.animate";
53 const TABS_VIEW = '/id("main-window")/id("tab-view-deck")/{"flex":"1"}';
54 const TABS_BROWSER = TABS_VIEW + '/id("browser")/id("appcontent")/id("content")';
55 const TABS_TOOLBAR = TABS_VIEW + '/id("navigator-toolbox")/id("TabsToolbar")';
56 const TABS_TABS = TABS_TOOLBAR + '/id("tabbrowser-tabs")';
57 const TABS_ARROW_SCROLLBOX = TABS_TABS + '/anon({"anonid":"arrowscrollbox"})';
58 const TABS_STRIP = TABS_ARROW_SCROLLBOX + '/anon({"anonid":"scrollbox"})/anon({"flex":"1"})';
61 * Close all tabs and open about:blank
63 * @param {MozMillController} controller
64 * MozMillController of the window to operate on
66 function closeAllTabs(controller)
68 var browser = new tabBrowser(controller);
69 browser.closeAllTabs();
73 * Check and return all open tabs with the specified URL
75 * @param {string} aUrl
78 * @returns Array of tabs
80 function getTabsWithURL(aUrl) {
83 var uri = utils.createURI(aUrl, null, null);
85 var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
86 getService(Ci.nsIWindowMediator);
87 var winEnum = wm.getEnumerator("navigator:browser");
89 // Iterate through all windows
90 while (winEnum.hasMoreElements()) {
91 var window = winEnum.getNext();
93 // Don't check windows which are about to close or don't have gBrowser set
94 if (window.closed || !("gBrowser" in window))
97 // Iterate through all tabs in the current window
98 var browsers = window.gBrowser.browsers;
99 for (var i = 0; i < browsers.length; i++) {
100 var browser = browsers[i];
101 if (browser.currentURI.equals(uri)) {
103 controller : new mozmill.controller.MozMillController(window),
116 * @param {MozMillController} controller
117 * MozMill controller of the window to operate on
119 function tabBrowser(controller)
121 this._controller = controller;
122 this._tabs = this.getElement({type: "tabs"});
126 * Tabbed Browser class
128 tabBrowser.prototype = {
130 * Returns the MozMill controller
132 * @returns Mozmill controller
133 * @type {MozMillController}
136 return this._controller;
140 * Get the amount of open tabs
142 * @returns Number of tabs
146 return this._tabs.getNode().itemCount;
150 * Get the currently selected tab index
152 * @returns Index of currently selected tab
155 get selectedIndex() {
156 return this._tabs.getNode().selectedIndex;
160 * Select the tab with the given index
162 * @param {number} index
163 * Index of the tab which should be selected
165 set selectedIndex(index) {
166 this._controller.click(this.getTab(index));
170 * Close all tabs of the window except the last one and open a blank page.
172 closeAllTabs : function tabBrowser_closeAllTabs()
174 while (this._controller.tabs.length > 1) {
175 this.closeTab({type: "menu"});
178 this._controller.open("about:blank");
179 this._controller.waitForPageLoad();
185 * @param {object} aEvent
186 * The event specifies how to close a tab
187 * Elements: type - Type of event (closeButton, menu, middleClick, shortcut)
188 * [optional - default: menu]
190 closeTab : function tabBrowser_closeTab(aEvent) {
191 var event = aEvent || { };
192 var type = (event.type == undefined) ? "menu" : event.type;
194 // Disable tab closing animation for default behavior
195 prefs.preferences.setPref(PREF_TABS_ANIMATE, false);
197 // Add event listener to wait until the tab has been closed
198 var self = { closed: false };
199 function checkTabClosed() { self.closed = true; }
200 this._controller.window.addEventListener("TabClose", checkTabClosed, false);
204 var button = this.getElement({type: "tabs_tabCloseButton",
205 subtype: "tab", value: this.getTab()});
206 this._controller.click(button);
209 var menuitem = new elementslib.Elem(this._controller.menus['file-menu'].menu_close);
210 this._controller.click(menuitem);
213 var tab = this.getTab(event.index);
214 this._controller.middleClick(tab);
217 var cmdKey = utils.getEntity(this.getDtds(), "closeCmd.key");
218 this._controller.keypress(null, cmdKey, {accelKey: true});
221 throw new Error(arguments.callee.name + ": Unknown event type - " + type);
225 this._controller.waitForEval("subject.tab.closed == true", TIMEOUT, 100,
228 this._controller.window.removeEventListener("TabClose", checkTabClosed, false);
229 prefs.preferences.clearUserPref(PREF_TABS_ANIMATE);
234 * Gets all the needed external DTD urls as an array
236 * @returns Array of external DTD urls
239 getDtds : function tabBrowser_getDtds() {
240 var dtds = ["chrome://browser/locale/browser.dtd",
241 "chrome://browser/locale/tabbrowser.dtd",
242 "chrome://global/locale/global.dtd"];
247 * Retrieve an UI element based on the given spec
249 * @param {object} spec
250 * Information of the UI element which should be retrieved
251 * type: General type information
252 * subtype: Specific element or property
253 * value: Value of the element or property
254 * @returns Element which has been created
257 getElement : function tabBrowser_getElement(spec) {
258 var document = this._controller.window.document;
263 * subtype: subtype to match
264 * value: value to match
267 elem = new elementslib.Lookup(this._controller.window.document,
270 case "tabs_allTabsButton":
271 elem = new elementslib.Lookup(this._controller.window.document,
272 TABS_TOOLBAR + '/id("alltabs-button")');
274 case "tabs_allTabsPopup":
275 elem = new elementslib.Lookup(this._controller.window.document, TABS_TOOLBAR +
276 '/id("alltabs-button")/id("alltabs-popup")');
278 case "tabs_newTabButton":
279 elem = new elementslib.Lookup(this._controller.window.document,
280 TABS_ARROW_SCROLLBOX + '/anon({"class":"tabs-newtab-button"})');
282 case "tabs_scrollButton":
283 elem = new elementslib.Lookup(this._controller.window.document,
284 TABS_ARROW_SCROLLBOX +
285 '/anon({"anonid":"scrollbutton-' + spec.subtype + '"})');
288 elem = new elementslib.Lookup(this._controller.window.document, TABS_STRIP);
291 switch (spec.subtype) {
293 elem = new elementslib.Elem(this._tabs.getNode().getItemAtIndex(spec.value));
297 case "tabs_tabCloseButton":
298 var node = document.getAnonymousElementByAttribute(
299 spec.value.getNode(),
303 elem = new elementslib.Elem(node);
305 case "tabs_tabFavicon":
306 var node = document.getAnonymousElementByAttribute(
307 spec.value.getNode(),
312 elem = new elementslib.Elem(node);
314 case "tabs_tabPanel":
315 var panelId = spec.value.getNode().getAttribute("linkedpanel");
316 elem = new elementslib.Lookup(this._controller.window.document, TABS_BROWSER +
317 '/anon({"anonid":"tabbox"})/anon({"anonid":"panelcontainer"})' +
318 '/{"id":"' + panelId + '"}');
321 throw new Error(arguments.callee.name + ": Unknown element type - " + spec.type);
328 * Get the tab at the specified index
330 * @param {number} index
332 * @returns The requested tab
335 getTab : function tabBrowser_getTab(index) {
336 if (index === undefined)
337 index = this.selectedIndex;
339 return this.getElement({type: "tabs_tab", subtype: "index", value: index});
343 * Creates the child element of the tab's notification bar
345 * @param {number} tabIndex
346 * (Optional) Index of the tab to check
347 * @param {string} elemString
348 * (Optional) Lookup string of the notification bar's child element
349 * @return The created child element
352 getTabPanelElement : function tabBrowser_getTabPanelElement(tabIndex, elemString)
354 var index = tabIndex ? tabIndex : this.selectedIndex;
355 var elemStr = elemString ? elemString : "";
357 // Get the tab panel and check if an element has to be fetched
358 var panel = this.getElement({type: "tabs_tabPanel", subtype: "tab", value: this.getTab(index)});
359 var elem = new elementslib.Lookup(this._controller.window.document, panel.expression + elemStr);
365 * Open element (link) in a new tab
367 * @param {object} aEvent
368 * The event specifies how to open the element in a new tab
369 * Elements: type - Type of event (contextMenu, middleClick)
370 * [optional - default: middleClick]
371 * target - Element to click on
372 * [optional - default: -]
374 openInNewTab : function tabBrowser_openInNewTab(aEvent) {
375 var event = aEvent || { };
376 var type = (event.type == undefined) ? "middleClick" : event.type;
377 var target = event.target;
379 // Disable tab closing animation for default behavior
380 prefs.preferences.setPref(PREF_TABS_ANIMATE, false);
382 // Add event listener to wait until the tab has been opened
383 var self = { opened: false };
384 function checkTabOpened() { self.opened = true; }
385 this._controller.window.addEventListener("TabOpen", checkTabOpened, false);
389 var contextMenuItem = new elementslib.ID(this._controller.window.document,
390 "context-openlinkintab");
391 this._controller.rightClick(target);
392 this._controller.click(contextMenuItem);
393 utils.closeContentAreaContextMenu(this._controller);
396 this._controller.middleClick(target);
399 throw new Error(arguments.callee.name + ": Unknown event type - " + type);
403 this._controller.waitForEval("subject.tab.opened == true", TIMEOUT, 100,
406 this._controller.window.removeEventListener("TabOpen", checkTabOpened, false);
407 prefs.preferences.clearUserPref(PREF_TABS_ANIMATE);
414 * @param {object} aEvent
415 * The event specifies how to open a new tab (menu, shortcut,
416 * Elements: type - Type of event (menu, newTabButton, shortcut, tabStrip)
417 * [optional - default: menu]
419 openTab : function tabBrowser_openTab(aEvent) {
420 var event = aEvent || { };
421 var type = (event.type == undefined) ? "menu" : event.type;
423 // Disable tab closing animation for default behavior
424 prefs.preferences.setPref(PREF_TABS_ANIMATE, false);
426 // Add event listener to wait until the tab has been opened
427 var self = { opened: false };
428 function checkTabOpened() { self.opened = true; }
429 this._controller.window.addEventListener("TabOpen", checkTabOpened, false);
433 var menuitem = new elementslib.Elem(this._controller.menus['file-menu'].menu_newNavigatorTab);
434 this._controller.click(menuitem);
437 var cmdKey = utils.getEntity(this.getDtds(), "tabCmd.commandkey");
438 this._controller.keypress(null, cmdKey, {accelKey: true});
441 var newTabButton = this.getElement({type: "tabs_newTabButton"});
442 this._controller.click(newTabButton);
445 var tabStrip = this.getElement({type: "tabs_strip"});
447 // RTL-locales need to be treated separately
448 if (utils.getEntity(this.getDtds(), "locale.dir") == "rtl") {
449 // XXX: Workaround until bug 537968 has been fixed
450 this._controller.click(tabStrip, 100, 3);
451 // Todo: Calculate the correct x position
452 this._controller.doubleClick(tabStrip, 100, 3);
454 // XXX: Workaround until bug 537968 has been fixed
455 this._controller.click(tabStrip, tabStrip.getNode().clientWidth - 100, 3);
456 // Todo: Calculate the correct x position
457 this._controller.doubleClick(tabStrip, tabStrip.getNode().clientWidth - 100, 3);
461 throw new Error(arguments.callee.name + ": Unknown event type - " + type);
465 this._controller.waitForEval("subject.tab.opened == true", TIMEOUT, 100,
468 this._controller.window.removeEventListener("TabOpen", checkTabOpened, false);
469 prefs.preferences.clearUserPref(PREF_TABS_ANIMATE);
474 * Waits for a particular tab panel element to display and stop animating
476 * @param {number} tabIndex
477 * Index of the tab to check
478 * @param {string} elemString
479 * Lookup string of the tab panel element
481 waitForTabPanel: function tabBrowser_waitForTabPanel(tabIndex, elemString) {
482 // Get the specified tab panel element
483 var tabPanel = this.getTabPanelElement(tabIndex, elemString);
485 // Get the style information for the tab panel element
486 var style = this._controller.window.getComputedStyle(tabPanel.getNode(), null);
488 // Wait for the top margin to be 0px - ie. has stopped animating
489 // XXX: A notification bar starts at a negative pixel margin and drops down
490 // to 0px. This creates a race condition where a test may click
491 // before the notification bar appears at it's anticipated screen location
492 this._controller.waitFor(function () {
493 return style.marginTop == '0px';
494 }, "Expected notification bar to be visible: '" + elemString + "' ");
498 // Export of functions
499 exports.closeAllTabs = closeAllTabs;
500 exports.getTabsWithURL = getTabsWithURL;
503 exports.tabBrowser = tabBrowser;