]> git.donarmstrong.com Git - dactyl.git/blob - common/tests/functional/shared-modules/addons.js
Initial import of 1.0~b6
[dactyl.git] / common / tests / functional / shared-modules / addons.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 the 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  *   Geo Mealer <gmealer@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 // Include required modules
39 var domUtils = require("dom-utils");
40 var prefs = require("prefs");
41 var tabs = require("tabs");
42 var utils = require("utils");
43
44
45 const TIMEOUT = 5000;
46 const TIMEOUT_DOWNLOAD = 15000;
47 const TIMEOUT_SEARCH = 30000;
48
49 var pm = Cc["@mozilla.org/permissionmanager;1"].
50          getService(Ci.nsIPermissionManager);
51
52 // AMO Preview site
53 const AMO_PREVIEW_DOMAIN = "addons.allizom.org";
54 const AMO_PREVIEW_SITE = "https://" + AMO_PREVIEW_DOMAIN;
55
56 // Available search filters
57 const SEARCH_FILTER = [
58   "local",
59   "remote"
60 ];
61
62 // Preferences which have to be changed to make sure we do not interact with the
63 // official AMO page but the preview site instead
64 const AMO_PREFERENCES = [
65   {name: "extensions.getAddons.browseAddons", old: "addons.mozilla.org", new: AMO_PREVIEW_DOMAIN},
66   {name: "extensions.getAddons.recommended.browseURL", old: "addons.mozilla.org", new: AMO_PREVIEW_DOMAIN},
67   {name: "extensions.getAddons.recommended.url", old: "services.addons.mozilla.org", new: AMO_PREVIEW_DOMAIN},
68   {name: "extensions.getAddons.search.browseURL", old: "addons.mozilla.org", new: AMO_PREVIEW_DOMAIN},
69   {name: "extensions.getAddons.search.url", old: "services.addons.mozilla.org", new: AMO_PREVIEW_DOMAIN},
70   {name: "extensions.getMoreThemesURL", old: "addons.mozilla.org", new: AMO_PREVIEW_DOMAIN}
71 ];
72
73 /**
74  * Constructor
75  */
76 function addonsManager(aController) {
77   this._controller = aController;
78   this._tabBrowser = new tabs.tabBrowser(this._controller);
79 }
80
81 /**
82  * Addons Manager class
83  */
84 addonsManager.prototype = {
85
86   ///////////////////////////////
87   // Global section
88   ///////////////////////////////
89
90   /**
91    * Get the controller of the window
92    *
93    * @returns Mozmill Controller
94    * @type {MozMillController}
95    */
96   get controller() {
97     return this._controller;
98   },
99
100   /**
101    * Gets all the needed external DTD urls as an array
102    *
103    * @returns URL's of external DTD files
104    * @type {array of string}
105    */
106   get dtds() {
107     var dtds = [
108       "chrome://mozapps/locale/extensions/extensions.dtd",
109       "chrome://browser/locale/browser.dtd"
110     ];
111
112     return dtds;
113   },
114
115   /**
116    * Open the Add-ons Manager
117    *
118    * @param {object} aSpec
119    *        Information how to open the Add-ons Manager
120    *        Elements: type    - Event, can be menu, or shortcut
121    *                            [optional - default: menu]
122    *                  waitFor - Wait until the Add-ons Manager has been opened
123    *                            [optional - default: true]
124    *
125    *
126    * @returns Reference the tab with the Add-ons Manager open
127    * @type {object}
128    *       Elements: controller - Mozmill Controller of the window
129    *                 index - Index of the tab
130    */
131   open : function addonsManager_open(aSpec) {
132     var spec = aSpec || { };
133     var type = (spec.type == undefined) ? "menu" : spec.type;
134     var waitFor = (spec.waitFor == undefined) ? true : spec.waitFor;
135
136     switch (type) {
137       case "menu":
138         var menuItem = new elementslib.Elem(this._controller.
139                                             menus["tools-menu"].menu_openAddons);
140         this._controller.click(menuItem);
141         break;
142       case "shortcut":
143         var cmdKey = utils.getEntity(this.dtds, "addons.commandkey");
144         this._controller.keypress(null, cmdKey, {accelKey: true, shiftKey: true});
145         break;
146       default:
147         throw new Error(arguments.callee.name + ": Unknown event type - " +
148                         event.type);
149     }
150
151     return waitFor ? this.waitForOpened() : null;
152   },
153
154   /**
155    * Check if the Add-ons Manager is open
156    *
157    * @returns True if the Add-ons Manager is open
158    * @type {boolean}
159    */
160   get isOpen() {
161     return (this.getTabs().length > 0);
162   },
163
164   /**
165    * Waits until the Addons Manager has been opened and returns its controller
166    *
167    * @param {object} aSpec
168    *        Object with parameters for customization
169    *        Elements: timeout - Duration to wait for the target state
170    *                            [optional - default: 5s]
171    *
172    * @returns Currently selected tab
173    */
174   waitForOpened : function addonsManager_waitforOpened(aSpec) {
175     var spec = aSpec || { };
176     var timeout = (spec.timeout == undefined) ? TIMEOUT : spec.timeout;
177
178     // TODO: restore after 1.5.1 has landed
179     // var self = this;
180     //
181     // mozmill.utils.waitFor(function() {
182     //   return self.isOpen;
183     // }, timeout, 100, "Add-ons Manager has been opened");
184
185     mozmill.utils.waitForEval("subject.isOpen", timeout, 100, this);
186
187     // The first tab found will be the selected one
188     var tab = this.getTabs()[0];
189     tab.controller.waitForPageLoad();
190
191     return tab;
192   },
193
194   /**
195    * Close the Addons Manager
196    *
197    * @param {object} aSpec
198    *        Information about the event to send
199    *        Elements: type - Event type (closeButton, menu, middleClick, shortcut)
200    */
201   close : function addonsManager_close(aSpec) {
202     this._tabBrowser.closeTab(aSpec);
203   },
204
205   /**
206    * Retrieves the list of open add-ons manager tabs
207    *
208    * @returns List of open tabs
209    * @type {array of object}
210    *       Elements: controller - MozMillController
211    *                 index      - Index of the tab
212    */
213   getTabs : function addonsManager_getTabs() {
214     return tabs.getTabsWithURL("about:addons");
215   },
216
217   /**
218    * Opens the utils button menu and clicks the specified menu entry
219    *
220    * @param {object} aSpec
221    *        Information about the menu
222    *        Elements: item - menu item to click (updateNow, viewUpdates,
223    *                         installFromFile, autoUpdateDefault,
224    *                         resetAddonUpdatesToAutomatic,
225    *                         resetAddonUpdatesToManual)
226    */
227   handleUtilsButton : function addonsManager_handleUtilsButton(aSpec) {
228     var spec = aSpec || { };
229     var item = spec.item;
230
231     if (!item)
232       throw new Error(arguments.callee.name + ": Menu item not specified.");
233
234     var button = this.getElement({type: "utilsButton"});
235     var menu = this.getElement({type: "utilsButton_menu"});
236
237     try {
238       this._controller.click(button);
239
240       // Click the button and wait until menu has been opened
241
242       // TODO: restore after 1.5.1 has landed
243       // mozmill.utils.waitFor(function() {
244       //   return menu.getNode() && menu.getNode().state == "open";
245       // }, TIMEOUT, 100, "Menu of utils button has been opened.");
246
247       mozmill.utils.waitForEval("subject && subject.state == 'open'",
248                                 TIMEOUT, 100, menu.getNode());
249
250       // Click the given menu entry and make sure the
251       var menuItem = this.getElement({
252         type: "utilsButton_menuItem",
253         value: "#utils-" + item
254       });
255
256       this._controller.click(menuItem);
257     } finally {
258       // Make sure the menu has been closed
259       this._controller.keypress(menu, "VK_ESCAPE", {});
260
261       // TODO: restore after 1.5.1 has landed
262       // mozmill.utils.waitFor(function() {
263       //   return menu.getNode() && menu.getNode().state == "closed";
264       // }, TIMEOUT, 100, "Menu of utils button has been closed.");
265
266       mozmill.utils.waitForEval("subject && subject.state == 'closed'",
267                                 TIMEOUT, 100, menu.getNode());
268     }
269   },
270
271
272   ///////////////////////////////
273   // Add-on section
274   ///////////////////////////////
275
276   /**
277    * Check if the specified add-on is compatible
278    *
279    * @param {object} aSpec
280    *        Information on which add-on to operate on
281    *        Elements: addon - Add-on element
282    *
283    * @returns True if the add-on is compatible
284    * @type {ElemBase}
285    */
286   isAddonCompatible : function addonsManager_isAddonCompatible(aSpec) {
287     var spec = aSpec || { };
288     var addon = spec.addon;
289
290     if (!addon)
291       throw new Error(arguments.callee.name + ": Add-on not specified.");
292
293     // XXX: Bug 599702 doens't give enough information which type of notification
294     return addon.getNode().getAttribute("notification") != "warning";
295   },
296
297   /**
298    * Check if the specified add-on is enabled
299    *
300    * @param {object} aSpec
301    *        Information on which add-on to operate on
302    *        Elements: addon - Add-on element
303    *
304    * @returns True if the add-on is enabled
305    * @type {ElemBase}
306    */
307   isAddonEnabled : function addonsManager_isAddonEnabled(aSpec) {
308     var spec = aSpec || { };
309     var addon = spec.addon;
310
311     if (!addon)
312       throw new Error(arguments.callee.name + ": Add-on not specified.");
313
314     return addon.getNode().getAttribute("active") == "true";
315   },
316
317   /**
318    * Check if the specified add-on is installed
319    *
320    * @param {object} aSpec
321    *        Information on which add-on to operate on
322    *        Elements: addon - Add-on element
323    *
324    * @returns True if the add-on is installed
325    * @type {ElemBase}
326    */
327   isAddonInstalled : function addonsManager_isAddonInstalled(aSpec) {
328     var spec = aSpec || { };
329     var addon = spec.addon;
330
331     if (!addon)
332       throw new Error(arguments.callee.name + ": Add-on not specified.");
333
334     // Bug 600502 : Add-ons in search view are not initialized correctly
335     return addon.getNode().getAttribute("remote") == "false" &&
336            addon.getNode().getAttribute("status") == "installed";
337   },
338
339   /**
340    * Enables the specified add-on
341    *
342    * @param {object} aSpec
343    *        Information on which add-on to operate on
344    *        Elements: addon - Add-on element
345    */
346   enableAddon : function addonsManager_enableAddon(aSpec) {
347     var spec = aSpec || { };
348     spec.button = "enable";
349
350     var button = this.getAddonButton(spec);
351     this._controller.click(button);
352   },
353
354   /**
355    * Disables the specified add-on
356    *
357    * @param {object} aSpec
358    *        Information on which add-on to operate on
359    *        Elements: addon - Add-on element
360    */
361   disableAddon : function addonsManager_disableAddon(aSpec) {
362     var spec = aSpec || { };
363     spec.button = "disable";
364
365     var button = this.getAddonButton(spec);
366     this._controller.click(button);
367   },
368
369   /**
370    * Installs the specified add-on
371    *
372    * @param {object} aSpec
373    *        Information on which add-on to operate on
374    *        Elements: addon   - Add-on element
375    *                  waitFor - Wait until the category has been selected
376    *                            [optional - default: true]
377    *                  timeout - Duration to wait for the download
378    *                            [optional - default: 15s]
379    */
380   installAddon : function addonsManager_installAddon(aSpec) {
381     var spec = aSpec || { };
382     var addon = spec.addon;
383     var timeout = spec.timeout;
384     var button = "install";
385     var waitFor = (spec.waitFor == undefined) ? true : spec.waitFor;
386
387     var button = this.getAddonButton({addon: addon, button: button});
388     this._controller.click(button);
389
390     if (waitFor)
391       this.waitForDownloaded({addon: addon, timeout: timeout});
392   },
393
394   /**
395    * Removes the specified add-on
396    *
397    * @param {object} aSpec
398    *        Information on which add-on to operate on
399    *        Elements: addon - Add-on element
400    */
401   removeAddon : function addonsManager_removeAddon(aSpec) {
402     var spec = aSpec || { };
403     spec.button = "remove";
404
405     var button = this.getAddonButton(spec);
406     this._controller.click(button);
407   },
408
409   /**
410    * Undo the last action performed for the given add-on
411    *
412    * @param {object} aSpec
413    *        Information on which add-on to operate on
414    *        Elements: addon - Add-on element
415    */
416   undo : function addonsManager_undo(aSpec) {
417     var spec = aSpec || { };
418     spec.link = "undo";
419
420     var link = this.getAddonLink(spec);
421     this._controller.click(link);
422   },
423
424   /**
425    * Returns the addons from the currently selected view which match the
426    * filter criteria
427    *
428    * @param {object} aSpec
429    *        Information about the filter to apply
430    *        Elements: attribute - DOM attribute of the wanted addon
431    *                              [optional - default: ""]
432    *                  value     - Value of the DOM attribute
433    *                              [optional - default: ""]
434    *
435    * @returns List of addons
436    * @type {array of ElemBase}
437    */
438   getAddons : function addonsManager_addons(aSpec) {
439     var spec = aSpec || {};
440
441     return this.getElements({
442       type: "addons",
443       subtype: spec.attribute,
444       value: spec.value,
445       parent: this.selectedView
446     });
447   },
448
449   /**
450    * Returns the element of the specified add-ons button
451    *
452    * @param {object} aSpec
453    *        Information on which add-on to operate on
454    *        Elements: addon  - Add-on element
455    *                  button - Button (disable, enable, preferences, remove)
456    *
457    * @returns Add-on button
458    * @type {ElemBase}
459    */
460   getAddonButton : function addonsManager_getAddonButton(aSpec) {
461     var spec = aSpec || { };
462     var addon = spec.addon;
463     var button = spec.button;
464
465     if (!button)
466       throw new Error(arguments.callee.name + ": Button not specified.");
467
468     return this.getAddonChildElement({addon: addon, type: button + "Button"});
469   },
470
471   /**
472    * Returns the element of the specified add-ons link
473    *
474    * @param {object} aSpec
475    *        Information on which add-on to operate on
476    *        Elements: addon - Add-on element
477    *                  link  - Link
478    *                            List view (more, restart, undo)
479    *                            Detail view (findUpdates, restart, undo)
480    *
481    * @return Add-on link
482    * @type {ElemBase}
483    */
484   getAddonLink : function addonsManager_getAddonLink(aSpec) {
485     var spec = aSpec || { };
486     var addon = spec.addon;
487     var link = spec.link;
488
489     if (!link)
490       throw new Error(arguments.callee.name + ": Link not specified.");
491
492     return this.getAddonChildElement({addon: addon, type: link + "Link"});
493   },
494
495   /**
496    * Returns the element of the specified add-ons radio group
497    *
498    * @param {object} aSpec
499    *        Information on which add-on to operate on
500    *        Elements: addon      - Add-on element
501    *                  radiogroup - Radiogroup
502    *                                 Detail View (autoUpdate)
503    *
504    * @returns Add-on radiogroup
505    * @type {ElemBase}
506    */
507   getAddonRadiogroup : function addonsManager_getAddonRadiogroup(aSpec) {
508     var spec = aSpec || { };
509     var addon = spec.addon;
510     var radiogroup = spec.radiogroup;
511
512     if (!radiogroup)
513       throw new Error(arguments.callee.name + ": Radiogroup not specified.");
514
515     return this.getAddonChildElement({addon: addon, type: radiogroup + "Radiogroup"});
516   },
517
518   /**
519    * Retrieve the given child element of the specified add-on
520    *
521    * @param {object} aSpec
522    *        Information for getting the add-ons child node
523    *        Elements: addon     - Add-on element
524    *                  type      - Type of the element
525    *                              [optional - default: use attribute/value]
526    *                  attribute - DOM attribute of the node
527    *                  value     - Value of the DOM attribute
528    *
529    * @returns Element
530    * @type {ElemBase}
531    */
532   getAddonChildElement : function addonsManager_getAddonChildElement(aSpec) {
533     var spec = aSpec || { };
534     var addon = spec.addon;
535     var attribute = spec.attribute;
536     var value = spec.value;
537     var type = spec.type;
538
539     if (!addon)
540       throw new Error(arguments.callee.name + ": Add-on not specified.");
541
542     // If no type has been set retrieve a general element which needs an
543     // attribute and value
544     if (!type) {
545       type = "element";
546
547       if (!attribute)
548         throw new Error(arguments.callee.name + ": DOM attribute not specified.");
549       if (!value)
550         throw new Error(arguments.callee.name + ": Value not specified.");
551     }
552
553     // For the details view the elements don't have anonymous nodes
554     if (this.selectedView.getNode().id == "detail-view") {
555       return this.getElement({
556         type: "detailView_" + type,
557         subtype: attribute,
558         value: value
559       });
560     } else {
561       return this.getElement({
562         type: "listView_" + type,
563         subtype: attribute,
564         value: value,
565         parent: addon
566       });
567     }
568   },
569
570   /**
571    * Wait until the specified add-on has been downloaded
572    *
573    * @param {object} aSpec
574    *        Object with parameters for customization
575    *        Elements: addon   - Add-on element to wait for being downloaded
576    *                  timeout - Duration to wait for the target state
577    *                            [optional - default: 15s]
578    */
579   waitForDownloaded : function addonsManager_waitForDownloaded(aSpec) {
580     var spec = aSpec || { };
581     var addon = spec.addon;
582     var timeout = (spec.timeout == undefined) ? TIMEOUT_DOWNLOAD : spec.timeout;
583
584     if (!addon)
585       throw new Error(arguments.callee.name + ": Add-on not specified.");
586
587     var self = this;
588     var node = addon.getNode();
589
590     // TODO: restore after 1.5.1 has landed
591     // mozmill.utils.waitFor(function () {
592     //   return node.getAttribute("pending") == "install" &&
593     //          node.getAttribute("status") != "installing";
594     // }, timeout, 100, "'" + node.getAttribute("name") + "' has been downloaded");
595
596     mozmill.utils.waitForEval("subject.getAttribute('pending') == 'install' &&" +
597                               "subject.getAttribute('status') != 'installing'",
598                               timeout, 100, node);
599   },
600
601
602   ///////////////////////////////
603   // Category section
604   ///////////////////////////////
605
606   /**
607    * Retrieve the currently selected category
608    *
609    * @returns Element which represents the currently selected category
610    * @type {ElemBase}
611    */
612   get selectedCategory() {
613     return this.getCategories({attribute: "selected", value: "true"})[0];
614   },
615
616   /**
617    * Returns the categories which match the filter criteria
618    *
619    * @param {object} aSpec
620    *        Information about the filter to apply
621    *        Elements: attribute - DOM attribute of the wanted category
622    *                              [optional - default: ""]
623    *                  value     - Value of the DOM attribute
624    *                              [optional - default: ""]
625    *
626    * @returns List of categories
627    * @type {array of ElemBase}
628    */
629   getCategories : function addonsManager_categories(aSpec) {
630     var spec = aSpec || { };
631
632     var categories = this.getElements({
633       type: "categories",
634       subtype: spec.attribute,
635       value: spec.value
636     });
637
638     if (categories.length == 0)
639       throw new Error(arguments.callee.name + ": Categories could not be found.");
640
641     return categories;
642   },
643
644   /**
645    * Get the category element for the specified id
646    *
647    * @param {object} aSpec
648    *        Information for getting a category
649    *        Elements: id - Category id (search, discover, languages,
650    *                       searchengines, extensions, themes, plugins,
651    *                       availableUpdates, recentUpdates)
652    *
653    * @returns Category
654    * @type {ElemBase}
655    */
656   getCategoryById : function addonsManager_getCategoryById(aSpec) {
657     var spec = aSpec || { };
658     var id = spec.id;
659
660     if (!id)
661       throw new Error(arguments.callee.name + ": Category ID not specified.");
662
663     return this.getCategories({
664       attribute: "id",
665       value: "category-" + id
666     })[0];
667   },
668
669   /**
670    * Get the ID of the given category element
671    *
672    * @param {object} aSpec
673    *        Information for getting a category
674    *        Elements: category - Category to get the id from
675    *
676    * @returns Category Id
677    * @type {string}
678    */
679   getCategoryId : function addonsManager_getCategoryId(aSpec) {
680     var spec = aSpec || { };
681     var category = spec.category;
682
683     if (!category)
684       throw new Error(arguments.callee.name + ": Category not specified.");
685
686     return category.getNode().id;
687   },
688
689   /**
690    * Select the given category
691    *
692    * @param {object} aSpec
693    *        Information for selecting a category
694    *        Elements: category - Category element
695    *                  waitFor  - Wait until the category has been selected
696    *                             [optional - default: true]
697    */
698   setCategory : function addonsManager_setCategory(aSpec) {
699     var spec = aSpec || { };
700     var category = spec.category;
701     var waitFor = (spec.waitFor == undefined) ? true : spec.waitFor;
702
703     if (!category)
704       throw new Error(arguments.callee.name + ": Category not specified.");
705
706     this._controller.click(category);
707
708     if (waitFor)
709       this.waitForCategory({category: category});
710   },
711
712   /**
713    * Select the category with the given id
714    *
715    * @param {object} aSpec
716    *        Information for selecting a category
717    *        Elements: id      - Category id (search, discover, languages,
718    *                            searchengines, extensions, themes, plugins,
719    *                            availableUpdates, recentUpdates)
720    *                  waitFor - Wait until the category has been selected
721    *                            [optional - default: true]
722    */
723   setCategoryById : function addonsManager_setCategoryById(aSpec) {
724     var spec = aSpec || { };
725     var id = spec.id;
726     var waitFor = (spec.waitFor == undefined) ? true : spec.waitFor;
727
728     if (!id)
729       throw new Error(arguments.callee.name + ": Category ID not specified.");
730
731     // Retrieve the category and set it as active
732     var category = this.getCategoryById({id: id});
733     if (category)
734       this.setCategory({category: category, waitFor: waitFor});
735     else
736       throw new Error(arguments.callee.name + ": Category '" + id + " not found.");
737   },
738
739   /**
740    * Wait until the specified category has been selected
741    *
742    * @param {object} aSpec
743    *        Object with parameters for customization
744    *        Elements: category - Category element to wait for
745    *                  timeout - Duration to wait for the target state
746    *                            [optional - default: 5s]
747    */
748   waitForCategory : function addonsManager_waitForCategory(aSpec) {
749     var spec = aSpec || { };
750     var category = spec.category;
751     var timeout = (spec.timeout == undefined) ? TIMEOUT : spec.timeout;
752
753     if (!category)
754       throw new Error(arguments.callee.name + ": Category not specified.");
755
756     // TODO: restore after 1.5.1 has landed
757     // var self = this;
758     // mozmill.utils.waitFor(function () {
759     //   return self.selectedCategory.getNode() == category.getNode();
760     // }, timeout, 100, "Category '" + category.getNode().id + "' has been set");
761
762     mozmill.utils.waitForEval("subject.self.selectedCategory.getNode() == subject.aCategory.getNode()",
763                                timeout, 100,
764                                {self: this, aCategory: category});
765   },
766
767   ///////////////////////////////
768   // Search section
769   ///////////////////////////////
770
771   /**
772    * Clear the search field
773    */
774   clearSearchField : function addonsManager_clearSearchField() {
775     var textbox = this.getElement({type: "search_textbox"});
776     var cmdKey = utils.getEntity(this.dtds, "selectAllCmd.key");
777
778     this._controller.keypress(textbox, cmdKey, {accelKey: true});
779     this._controller.keypress(textbox, 'VK_DELETE', {});
780   },
781
782   /**
783    * Search for a specified add-on
784    *
785    * @param {object} aSpec
786    *        Information to execute the search
787    *        Elements: value   - Search term
788    *                  timeout - Duration to wait for search results
789    *                            [optional - default: 30s]
790    *                  waitFor - Wait until the search has been finished
791    *                            [optional - default: true]
792    */
793   search : function addonsManager_search(aSpec) {
794     var spec = aSpec || { };
795     var value = spec.value;
796     var timeout = (spec.timeout == undefined) ? TIMEOUT_SEARCH : spec.timeout;
797     var waitFor = (spec.waitFor == undefined) ? true : spec.waitFor;
798
799     if (!value)
800       throw new Error(arguments.callee.name + ": Search term not specified.");
801
802     var textbox = this.getElement({type: "search_textbox"});
803
804     this.clearSearchField();
805     this._controller.type(textbox, value);
806     this._controller.keypress(textbox, "VK_RETURN", {});
807
808     if (waitFor)
809       this.waitForSearchFinished();
810   },
811
812   /**
813    * Check if a search is active
814    *
815    * @returns State of the search
816    * @type {boolean}
817    */
818   get isSearching() {
819     var throbber = this.getElement({type: "search_throbber"});
820     return throbber.getNode().hasAttribute("active");
821   },
822
823   /**
824    * Retrieve the currently selected search filter
825    *
826    * @returns Element which represents the currently selected search filter
827    * @type {ElemBase}
828    */
829   get selectedSearchFilter() {
830     var filter = this.getSearchFilter({attribute: "selected", value: "true"});
831
832     return (filter.length > 0) ? filter[0] : undefined;
833   },
834
835   /**
836    * Set the currently selected search filter status
837    *
838    * @param {string} aValue
839    *        Filter for the search results (local, remote)
840    */
841   set selectedSearchFilter(aValue) {
842     var filter = this.getSearchFilter({attribute: "value", value: aValue});
843
844     if (SEARCH_FILTER.indexOf(aValue) == -1)
845       throw new Error(arguments.callee.name + ": '" + aValue +
846                       "' is not a valid search filter");
847
848     if (filter.length > 0) {
849       this._controller.click(filter[0]);
850       this.waitForSearchFilter({filter: filter[0]});
851     }
852   },
853
854   /**
855    * Returns the available search filters which match the filter criteria
856    *
857    * @param {object} aSpec
858    *        Information about the filter to apply
859    *        Elements: attribute - DOM attribute of the wanted filter
860    *                              [optional - default: ""]
861    *                  value     - Value of the DOM attribute
862    *                              [optional - default: ""]
863    *
864    * @returns List of search filters
865    * @type {array of ElemBase}
866    */
867   getSearchFilter : function addonsManager_getSearchFilter(aSpec) {
868     var spec = aSpec || { };
869
870     return this.getElements({
871       type: "search_filterRadioButtons",
872       subtype: spec.attribute,
873       value: spec.value
874     });
875   },
876
877   /**
878    * Get the search filter element for the specified value
879    *
880    * @param {string} aValue
881    *        Search filter value (local, remote)
882    *
883    * @returns Search filter element
884    * @type {ElemBase}
885    */
886   getSearchFilterByValue : function addonsManager_getSearchFilterByValue(aValue) {
887     if (!aValue)
888       throw new Error(arguments.callee.name + ": Search filter value not specified.");
889
890     return this.getElement({
891       type: "search_filterRadioGroup",
892       subtype: "value",
893       value: aValue
894     });
895   },
896
897   /**
898    * Get the value of the given search filter element
899    *
900    * @param {object} aSpec
901    *        Information for getting the views matched by the criteria
902    *        Elements: filter - Filter element
903    *
904    * @returns Value of the search filter
905    * @type {string}
906    */
907   getSearchFilterValue : function addonsManager_getSearchFilterValue(aSpec) {
908     var spec = aSpec || { };
909     var filter = spec.filter;
910
911     if (!filter)
912       throw new Error(arguments.callee.name + ": Search filter not specified.");
913
914     return filter.getNode().value;
915   },
916
917   /**
918    * Waits until the specified search filter has been selected
919    *
920    * @param {object} aSpec
921    *        Object with parameters for customization
922    *        Elements: filter  - Filter element to wait for
923    *                  timeout - Duration to wait for the target state
924    *                            [optional - default: 5s]
925    */
926   waitForSearchFilter : function addonsManager_waitForSearchFilter(aSpec) {
927     var spec = aSpec || { };
928     var filter = spec.filter;
929     var timeout = (spec.timeout == undefined) ? TIMEOUT : spec.timeout;
930
931     if (!filter)
932       throw new Error(arguments.callee.name + ": Search filter not specified.");
933
934     // TODO: restore after 1.5.1 has landed
935     // var self = this;
936     //
937     // mozmill.utils.waitFor(function () {
938     //   return self.selectedSearchFilter.getNode() == filter.getNode();
939     // }, timeout, 100, "Search filter '" + filter.getNode().value + "' has been set");
940
941     mozmill.utils.waitForEval("subject.self.selectedSearchFilter.getNode() == subject.aFilter.getNode()",
942                               timeout, 100,
943                               {self: this, aFilter: filter});
944   },
945
946   /**
947    * Returns the list of add-ons found by the selected filter
948    *
949    * @returns List of add-ons
950    * @type {ElemBase}
951    */
952   getSearchResults : function addonsManager_getSearchResults() {
953     var filterValue = this.getSearchFilterValue({
954       filter: this.selectedSearchFilter
955     });
956
957     switch (filterValue) {
958       case "local":
959         return this.getAddons({attribute: "status", value: "installed"});
960       case "remote":
961         return this.getAddons({attribute: "remote", value: "true"});
962       default:
963         throw new Error(arguments.callee.name + ": Unknown search filter '" +
964                         filterValue + "' selected");
965     }
966   },
967
968   /**
969    * Waits until the active search has been finished
970    *
971    * @param {object} aSpec
972    *        Object with parameters for customization
973    *        Elements: timeout - Duration to wait for the target state
974    */
975   waitForSearchFinished : function addonsManager_waitForSearchFinished(aSpec) {
976     var spec = aSpec || { };
977     var timeout = (spec.timeout == undefined) ? TIMEOUT_SEARCH : spec.timeout;
978
979     // TODO: restore after 1.5.1 has landed
980     // var self = this;
981     //
982     // mozmill.utils.waitFor(function () {
983     //   return self.isSearching == false;
984     // }, timeout, 100, "Search has been finished");
985
986     mozmill.utils.waitForEval("subject.isSearching == false",
987                               timeout, 100, this);
988   },
989
990   ///////////////////////////////
991   // View section
992   ///////////////////////////////
993
994   /**
995    * Returns the views which match the filter criteria
996    *
997    * @param {object} aSpec
998    *        Information for getting the views matched by the criteria
999    *        Elements: attribute - DOM attribute of the node
1000    *                              [optional - default: ""]
1001    *                  value     - Value of the DOM attribute
1002    *                              [optional - default: ""]
1003    *
1004    * @returns Filtered list of views
1005    * @type {array of ElemBase}
1006    */
1007   getViews : function addonsManager_getViews(aSpec) {
1008     var spec = aSpec || { };
1009     var attribute = spec.attribute;
1010     var value = spec.value;
1011
1012     return this.getElements({type: "views", subtype: attribute, value: value});
1013   },
1014
1015   /**
1016    * Check if the details view is active
1017    *
1018    * @returns True if the default view is selected
1019    * @type {boolean}
1020    */
1021   get isDetailViewActive() {
1022     return (this.selectedView.getNode().id == "detail-view");
1023   },
1024
1025   /**
1026    * Retrieve the currently used view
1027    *
1028    * @returns Element which represents the currently selected view
1029    * @type {ElemBase}
1030    */
1031   get selectedView() {
1032     var viewDeck = this.getElement({type: "viewDeck"});
1033     var views = this.getViews();
1034
1035     return views[viewDeck.getNode().selectedIndex];
1036   },
1037
1038
1039   ///////////////////////////////
1040   // UI Elements section
1041   ///////////////////////////////
1042
1043   /**
1044    * Retrieve an UI element based on the given specification
1045    *
1046    * @param {object} aSpec
1047    *        Information of the UI elements which should be retrieved
1048    *        Elements: type     - Identifier of the element
1049    *                  subtype  - Attribute of the element to filter
1050    *                             [optional - default: ""]
1051    *                  value    - Value of the attribute to filter
1052    *                             [optional - default: ""]
1053    *                  parent   - Parent of the to find element
1054    *                             [optional - default: document]
1055    *
1056    * @returns Element which has been found
1057    * @type {ElemBase}
1058    */
1059   getElement : function addonsManager_getElement(aSpec) {
1060     var elements = this.getElements(aSpec);
1061
1062     return (elements.length > 0) ? elements[0] : undefined;
1063   },
1064
1065   /**
1066    * Retrieve list of UI elements based on the given specification
1067    *
1068    * @param {object} aSpec
1069    *        Information of the UI elements which should be retrieved
1070    *        Elements: type     - Identifier of the element
1071    *                  subtype  - Attribute of the element to filter
1072    *                             [optional - default: ""]
1073    *                  value    - Value of the attribute to filter
1074    *                             [optional - default: ""]
1075    *                  parent   - Parent of the to find element
1076    *                             [optional - default: document]
1077    *
1078    * @returns Elements which have been found
1079    * @type {array of ElemBase}
1080    */
1081   getElements : function addonsManager_getElements(aSpec) {
1082     var spec = aSpec || { };
1083     var type = spec.type;
1084     var subtype = spec.subtype;
1085     var value = spec.value;
1086     var parent = spec.parent;
1087
1088     var root = parent ? parent.getNode() : this._controller.tabs.activeTab;
1089     var nodeCollector = new domUtils.nodeCollector(root);
1090
1091     switch (type) {
1092       // Add-ons
1093       case "addons":
1094         nodeCollector.queryNodes(".addon").filterByDOMProperty(subtype, value);
1095         break;
1096       case "addonsList":
1097         nodeCollector.queryNodes("#addon-list");
1098         break;
1099       // Categories
1100       case "categoriesList":
1101         nodeCollector.queryNodes("#categories");
1102         break;
1103       case "categories":
1104         nodeCollector.queryNodes(".category").filterByDOMProperty(subtype, value);
1105         break;
1106       // Detail view
1107       case "detailView_element":
1108         nodeCollector.queryNodes(value);
1109         break;
1110       case "detailView_disableButton":
1111         nodeCollector.queryNodes("#detail-disable");
1112         break;
1113       case "detailView_enableButton":
1114         nodeCollector.queryNodes("#detail-enable");
1115         break;
1116       case "detailView_installButton":
1117         nodeCollector.queryNodes("#detail-install");
1118         break;
1119       case "detailView_preferencesButton":
1120         nodeCollector.queryNodes("#detail-prefs");
1121         break;
1122       case "detailView_removeButton":
1123         nodeCollector.queryNodes("#detail-uninstall");
1124         break;
1125       case "detailView_findUpdatesLink":
1126         nodeCollector.queryNodes("#detail-findUpdates");
1127         break;
1128       // Bug 599771 - button-link's are missing id or anonid
1129       //case "detailView_restartLink":
1130       //  nodeCollector.queryNodes("#detail-restart");
1131       //  break;
1132       case "detailView_undoLink":
1133         nodeCollector.queryNodes("#detail-undo");
1134         break;
1135       case "detailView_findUpdatesRadiogroup":
1136         nodeCollector.queryNodes("#detail-findUpdates");
1137         break;
1138       // List view
1139       case "listView_element":
1140         nodeCollector.queryAnonymousNodes(subtype, value);
1141         break;
1142       case "listView_disableButton":
1143         nodeCollector.queryAnonymousNodes("anonid", "disable-btn");
1144         break;
1145       case "listView_enableButton":
1146         nodeCollector.queryAnonymousNodes("anonid", "enable-btn");
1147         break;
1148       case "listView_installButton":
1149         // There is another binding we will have to skip
1150         nodeCollector.queryAnonymousNodes("anonid", "install-status");
1151         nodeCollector.root = nodeCollector.nodes[0];
1152         nodeCollector.queryAnonymousNodes("anonid", "install-remote");
1153         break;
1154       case "listView_preferencesButton":
1155         nodeCollector.queryAnonymousNodes("anonid", "preferences-btn");
1156         break;
1157       case "listView_removeButton":
1158         nodeCollector.queryAnonymousNodes("anonid", "remove-btn");
1159         break;
1160       case "listView_moreLink":
1161         // Bug 599771 - button-link's are missing id or anonid
1162         nodeCollector.queryAnonymousNodes("class", "details button-link");
1163         break;
1164       // Bug 599771 - button-link's are missing id or anonid
1165       //case "listView_restartLink":
1166       //  nodeCollector.queryAnonymousNodes("anonid", "restart");
1167       //  break;
1168       case "listView_undoLink":
1169         nodeCollector.queryAnonymousNodes("anonid", "undo");
1170         break;
1171       case "listView_cancelDownload":
1172         // There is another binding we will have to skip
1173         nodeCollector.queryAnonymousNodes("anonid", "install-status");
1174         nodeCollector.root = nodeCollector.nodes[0];
1175         nodeCollector.queryAnonymousNodes("anonid", "cancel");
1176         break;
1177       case "listView_pauseDownload":
1178         // There is another binding we will have to skip
1179         nodeCollector.queryAnonymousNodes("anonid", "install-status");
1180         nodeCollector.root = nodeCollector.nodes[0];
1181         nodeCollector.queryAnonymousNodes("anonid", "pause");
1182         break;
1183       case "listView_progressDownload":
1184         // There is another binding we will have to skip
1185         nodeCollector.queryAnonymousNodes("anonid", "install-status");
1186         nodeCollector.root = nodeCollector.nodes[0];
1187         nodeCollector.queryAnonymousNodes("anonid", "progress");
1188         break;
1189       // Search
1190       // Bug 599775 - Controller needs to handle radio groups correctly
1191       // Means for now we have to use the radio buttons
1192       case "search_filterRadioButtons":
1193         nodeCollector.queryNodes(".search-filter-radio").filterByDOMProperty(subtype, value);
1194         break;
1195       case "search_filterRadioGroup":
1196         nodeCollector.queryNodes("#search-filter-radiogroup");
1197         break;
1198       case "search_textbox":
1199         nodeCollector.queryNodes("#header-search");
1200         break;
1201       case "search_throbber":
1202         nodeCollector.queryNodes("#header-searching");
1203         break;
1204       // Utils
1205       case "utilsButton":
1206         nodeCollector.queryNodes("#header-utils-btn");
1207         break;
1208       case "utilsButton_menu":
1209         nodeCollector.queryNodes("#utils-menu");
1210         break;
1211       case "utilsButton_menuItem":
1212         nodeCollector.queryNodes(value);
1213         break;
1214       // Views
1215       case "viewDeck":
1216         nodeCollector.queryNodes("#view-port");
1217         break;
1218       case "views":
1219         nodeCollector.queryNodes(".view-pane").filterByDOMProperty(subtype, value);
1220         break;
1221       default:
1222         throw new Error(arguments.callee.name + ": Unknown element type - " + spec.type);
1223     }
1224
1225     return nodeCollector.elements;
1226   }
1227 };
1228
1229 /**
1230  * Whitelist permission for the specified domain
1231  * @param {string} aDomain
1232  *        The domain to add the permission for
1233  */
1234 function addToWhiteList(aDomain) {
1235   pm.add(utils.createURI(aDomain),
1236          "install",
1237          Ci.nsIPermissionManager.ALLOW_ACTION);
1238 }
1239
1240 /**
1241  * Remove whitelist permission for the specified host
1242  * @param {string} aHost
1243  *        The host whose permission will be removed
1244  */
1245 function removeFromWhiteList(aHost) {
1246   pm.remove(aHost, "install");
1247 }
1248
1249 /**
1250  * Reset all preferences which point to the preview sub domain
1251  */
1252 function resetAmoPreviewUrls() {
1253   var prefSrv = prefs.preferences;
1254
1255   for each (var preference in AMO_PREFERENCES) {
1256     prefSrv.clearUserPref(preference.name);
1257   }
1258 }
1259
1260 /**
1261  *  Updates all necessary preferences to the preview sub domain
1262  */
1263 function useAmoPreviewUrls() {
1264   var prefSrv = prefs.preferences;
1265
1266   for each (var preference in AMO_PREFERENCES) {
1267     var pref = prefSrv.getPref(preference.name, "");
1268     prefSrv.setPref(preference.name,
1269                     pref.replace(preference.old, preference.new));
1270   }
1271 }
1272
1273 // Export of variables
1274 exports.AMO_PREVIEW_DOMAIN = AMO_PREVIEW_DOMAIN;
1275 exports.AMO_PREVIEW_SITE = AMO_PREVIEW_SITE;
1276
1277 // Export of functions
1278 exports.addToWhiteList = addToWhiteList;
1279 exports.removeFromWhiteList = removeFromWhiteList;
1280 exports.resetAmoPreviewUrls = resetAmoPreviewUrls;
1281 exports.useAmoPreviewUrls = useAmoPreviewUrls;
1282
1283 // Export of classes
1284 exports.addonsManager = addonsManager;