]> git.donarmstrong.com Git - dactyl.git/blob - common/tests/functional/shared-modules/tabs.js
Initial import of 1.0~b6
[dactyl.git] / common / tests / functional / shared-modules / tabs.js
1 /* ***** BEGIN LICENSE BLOCK *****
2  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3  *
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/
8  *
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
12  * License.
13  *
14  * The Original Code is MozMill Test code.
15  *
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.
19  *
20  * Contributor(s):
21  *   Henrik Skupin <hskupin@mozilla.com>
22  *   Anthony Hughes <ahughes@mozilla.com>
23  *
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.
35  *
36  * ***** END LICENSE BLOCK ***** */
37
38 /**
39  * @fileoverview
40  * The TabbedBrowsingAPI adds support for accessing and interacting with tab elements
41  *
42  * @version 1.0.0
43  */
44
45 // Include required modules
46 var utils = require("utils");
47 var prefs = require("prefs");
48
49 const TIMEOUT = 5000;
50
51 const PREF_TABS_ANIMATE = "browser.tabs.animate";
52
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"})';
59
60 /**
61  * Close all tabs and open about:blank
62  *
63  * @param {MozMillController} controller
64  *        MozMillController of the window to operate on
65  */
66 function closeAllTabs(controller)
67 {
68   var browser = new tabBrowser(controller);
69   browser.closeAllTabs();
70 }
71
72 /**
73  * Check and return all open tabs with the specified URL
74  *
75  * @param {string} aUrl
76  *        URL to check for
77  *
78  * @returns Array of tabs
79  */
80 function getTabsWithURL(aUrl) {
81   var tabs = [ ];
82
83   var uri = utils.createURI(aUrl, null, null);
84
85   var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
86            getService(Ci.nsIWindowMediator);
87   var winEnum = wm.getEnumerator("navigator:browser");
88
89   // Iterate through all windows
90   while (winEnum.hasMoreElements()) {
91     var window = winEnum.getNext();
92
93     // Don't check windows which are about to close or don't have gBrowser set
94     if (window.closed || !("gBrowser" in window))
95       continue;
96
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)) {
102         tabs.push({
103           controller : new mozmill.controller.MozMillController(window),
104           index : i
105         });
106       }
107     }
108   }
109
110   return tabs;
111 }
112
113 /**
114  * Constructor
115  *
116  * @param {MozMillController} controller
117  *        MozMill controller of the window to operate on
118  */
119 function tabBrowser(controller)
120 {
121   this._controller = controller;
122   this._tabs = this.getElement({type: "tabs"});
123 }
124
125 /**
126  * Tabbed Browser class
127  */
128 tabBrowser.prototype = {
129   /**
130    * Returns the MozMill controller
131    *
132    * @returns Mozmill controller
133    * @type {MozMillController}
134    */
135   get controller() {
136     return this._controller;
137   },
138
139   /**
140    * Get the amount of open tabs
141    *
142    * @returns Number of tabs
143    * @type {number}
144    */
145   get length() {
146     return this._tabs.getNode().itemCount;
147   },
148
149   /**
150    * Get the currently selected tab index
151    *
152    * @returns Index of currently selected tab
153    * @type {number}
154    */
155   get selectedIndex() {
156     return this._tabs.getNode().selectedIndex;
157   },
158
159   /**
160    * Select the tab with the given index
161    *
162    * @param {number} index
163    *        Index of the tab which should be selected
164    */
165   set selectedIndex(index) {
166     this._controller.click(this.getTab(index));
167   },
168
169   /**
170    * Close all tabs of the window except the last one and open a blank page.
171    */
172   closeAllTabs : function tabBrowser_closeAllTabs()
173   {
174     while (this._controller.tabs.length > 1) {
175       this.closeTab({type: "menu"});
176     }
177
178     this._controller.open("about:blank");
179     this._controller.waitForPageLoad();
180   },
181
182   /**
183    * Close an open tab
184    *
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]
189    */
190   closeTab : function tabBrowser_closeTab(aEvent) {
191     var event = aEvent || { };
192     var type = (event.type == undefined) ? "menu" : event.type;
193
194     // Disable tab closing animation for default behavior
195     prefs.preferences.setPref(PREF_TABS_ANIMATE, false);
196
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);
201
202     switch (type) {
203       case "closeButton":
204         var button = this.getElement({type: "tabs_tabCloseButton",
205                                      subtype: "tab", value: this.getTab()});
206         this._controller.click(button);
207         break;
208       case "menu":
209         var menuitem = new elementslib.Elem(this._controller.menus['file-menu'].menu_close);
210         this._controller.click(menuitem);
211         break;
212       case "middleClick":
213         var tab = this.getTab(event.index);
214         this._controller.middleClick(tab);
215         break;
216       case "shortcut":
217         var cmdKey = utils.getEntity(this.getDtds(), "closeCmd.key");
218         this._controller.keypress(null, cmdKey, {accelKey: true});
219         break;
220       default:
221         throw new Error(arguments.callee.name + ": Unknown event type - " + type);
222     }
223
224     try {
225       this._controller.waitForEval("subject.tab.closed == true", TIMEOUT, 100,
226                                    {tab: self});
227     } finally {
228       this._controller.window.removeEventListener("TabClose", checkTabClosed, false);
229       prefs.preferences.clearUserPref(PREF_TABS_ANIMATE);
230     }
231   },
232
233   /**
234    * Gets all the needed external DTD urls as an array
235    *
236    * @returns Array of external DTD urls
237    * @type [string]
238    */
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"];
243     return dtds;
244   },
245
246   /**
247    * Retrieve an UI element based on the given spec
248    *
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
255    * @type {ElemBase}
256    */
257   getElement : function tabBrowser_getElement(spec) {
258     var document = this._controller.window.document;
259     var elem = null;
260
261     switch(spec.type) {
262       /**
263        * subtype: subtype to match
264        * value: value to match
265        */
266       case "tabs":
267         elem = new elementslib.Lookup(this._controller.window.document,
268                                       TABS_TABS);
269         break;
270       case "tabs_allTabsButton":
271         elem = new elementslib.Lookup(this._controller.window.document,
272                                       TABS_TOOLBAR + '/id("alltabs-button")');
273         break;
274       case "tabs_allTabsPopup":
275         elem = new elementslib.Lookup(this._controller.window.document, TABS_TOOLBAR +
276                                       '/id("alltabs-button")/id("alltabs-popup")');
277         break;
278       case "tabs_newTabButton":
279         elem = new elementslib.Lookup(this._controller.window.document,
280                                       TABS_ARROW_SCROLLBOX + '/anon({"class":"tabs-newtab-button"})');
281         break;
282       case "tabs_scrollButton":
283         elem = new elementslib.Lookup(this._controller.window.document,
284                                       TABS_ARROW_SCROLLBOX +
285                                       '/anon({"anonid":"scrollbutton-' + spec.subtype + '"})');
286         break;
287       case "tabs_strip":
288         elem = new elementslib.Lookup(this._controller.window.document, TABS_STRIP);
289         break;
290       case "tabs_tab":
291         switch (spec.subtype) {
292           case "index":
293             elem = new elementslib.Elem(this._tabs.getNode().getItemAtIndex(spec.value));
294             break;
295         }
296         break;
297       case "tabs_tabCloseButton":
298         var node = document.getAnonymousElementByAttribute(
299                      spec.value.getNode(),
300                      "anonid",
301                      "close-button"
302                    );
303         elem = new elementslib.Elem(node);
304         break;
305       case "tabs_tabFavicon":
306         var node = document.getAnonymousElementByAttribute(
307                      spec.value.getNode(),
308                      "class",
309                      "tab-icon-image"
310                    );
311
312         elem = new elementslib.Elem(node);
313         break;
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 + '"}');
319         break;
320       default:
321         throw new Error(arguments.callee.name + ": Unknown element type - " + spec.type);
322     }
323
324     return elem;
325   },
326
327   /**
328    * Get the tab at the specified index
329    *
330    * @param {number} index
331    *        Index of the tab
332    * @returns The requested tab
333    * @type {ElemBase}
334    */
335   getTab : function tabBrowser_getTab(index) {
336     if (index === undefined)
337       index = this.selectedIndex;
338
339     return this.getElement({type: "tabs_tab", subtype: "index", value: index});
340   },
341
342   /**
343    * Creates the child element of the tab's notification bar
344    *
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
350    * @type {ElemBase}
351    */
352   getTabPanelElement : function tabBrowser_getTabPanelElement(tabIndex, elemString)
353   {
354     var index = tabIndex ? tabIndex : this.selectedIndex;
355     var elemStr = elemString ? elemString : "";
356
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);
360
361     return elem;
362   },
363
364   /**
365    * Open element (link) in a new tab
366    *
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: -]
373    */
374   openInNewTab : function tabBrowser_openInNewTab(aEvent) {
375     var event = aEvent || { };
376     var type = (event.type == undefined) ? "middleClick" : event.type;
377     var target = event.target;
378
379     // Disable tab closing animation for default behavior
380     prefs.preferences.setPref(PREF_TABS_ANIMATE, false);
381
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);
386
387     switch (type) {
388       case "contextMenu":
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);
394         break;
395       case "middleClick":
396         this._controller.middleClick(target);
397         break;
398       default:
399         throw new Error(arguments.callee.name + ": Unknown event type - " + type);
400     }
401
402     try {
403       this._controller.waitForEval("subject.tab.opened == true", TIMEOUT, 100,
404                                    {tab: self});
405     } finally {
406       this._controller.window.removeEventListener("TabOpen", checkTabOpened, false);
407       prefs.preferences.clearUserPref(PREF_TABS_ANIMATE);
408     }
409   },
410
411   /**
412    * Open a new tab
413    *
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]
418    */
419   openTab : function tabBrowser_openTab(aEvent) {
420     var event = aEvent || { };
421     var type = (event.type == undefined) ? "menu" : event.type;
422
423     // Disable tab closing animation for default behavior
424     prefs.preferences.setPref(PREF_TABS_ANIMATE, false);
425
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);
430
431     switch (type) {
432       case "menu":
433         var menuitem = new elementslib.Elem(this._controller.menus['file-menu'].menu_newNavigatorTab);
434         this._controller.click(menuitem);
435         break;
436       case "shortcut":
437         var cmdKey = utils.getEntity(this.getDtds(), "tabCmd.commandkey");
438         this._controller.keypress(null, cmdKey, {accelKey: true});
439         break;
440       case "newTabButton":
441         var newTabButton = this.getElement({type: "tabs_newTabButton"});
442         this._controller.click(newTabButton);
443         break;
444       case "tabStrip":
445         var tabStrip = this.getElement({type: "tabs_strip"});
446
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);
453         } else {
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);
458         }
459         break;
460       default:
461         throw new Error(arguments.callee.name + ": Unknown event type - " + type);
462     }
463
464     try {
465       this._controller.waitForEval("subject.tab.opened == true", TIMEOUT, 100,
466                                    {tab: self});
467     } finally {
468       this._controller.window.removeEventListener("TabOpen", checkTabOpened, false);
469       prefs.preferences.clearUserPref(PREF_TABS_ANIMATE);
470     }
471   },
472
473   /**
474    * Waits for a particular tab panel element to display and stop animating
475    *
476    * @param {number} tabIndex
477    *        Index of the tab to check
478    * @param {string} elemString
479    *        Lookup string of the tab panel element
480    */
481   waitForTabPanel: function tabBrowser_waitForTabPanel(tabIndex, elemString) {
482     // Get the specified tab panel element
483     var tabPanel = this.getTabPanelElement(tabIndex, elemString);
484
485     // Get the style information for the tab panel element
486     var style = this._controller.window.getComputedStyle(tabPanel.getNode(), null);
487
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 + "' ");
495   }
496 }
497
498 // Export of functions
499 exports.closeAllTabs = closeAllTabs;
500 exports.getTabsWithURL = getTabsWithURL;
501
502 // Export of classes
503 exports.tabBrowser = tabBrowser;