/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is MozMill Test code. * * The Initial Developer of the Original Code is the Mozilla Foundation. * Portions created by the Initial Developer are Copyright (C) 2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Henrik Skupin * Geo Mealer * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ var MODULE_NAME = 'AddonsAPI'; const RELATIVE_ROOT = '.'; const MODULE_REQUIRES = ['DOMUtilsAPI', 'PrefsAPI', 'TabbedBrowsingAPI', 'UtilsAPI']; const TIMEOUT = 5000; const TIMEOUT_DOWNLOAD = 15000; const TIMEOUT_SEARCH = 30000; // Available search filters const SEARCH_FILTER = [ "local", "remote" ]; // Preferences which have to be changed to make sure we do not interact with the // official AMO page but preview.addons.mozilla.org instead const AMO_PREFERENCES = [ {name: "extensions.getAddons.browseAddons", old: "addons.mozilla.org", new: "preview.addons.mozilla.org"}, {name: "extensions.getAddons.recommended.browseURL", old: "addons.mozilla.org", new: "preview.addons.mozilla.org"}, {name: "extensions.getAddons.recommended.url", old: "services.addons.mozilla.org", new: "preview.addons.mozilla.org"}, {name: "extensions.getAddons.search.browseURL", old: "addons.mozilla.org", new: "preview.addons.mozilla.org"}, {name: "extensions.getAddons.search.url", old: "services.addons.mozilla.org", new: "preview.addons.mozilla.org"}, {name: "extensions.getMoreThemesURL", old: "addons.mozilla.org", new: "preview.addons.mozilla.org"} ]; /** * Constructor */ function addonsManager(aController) { this._DOMUtilsAPI = collector.getModule('DOMUtilsAPI'); this._TabbedBrowsingAPI = collector.getModule('TabbedBrowsingAPI'); this._UtilsAPI = collector.getModule('UtilsAPI'); this._controller = aController; this._tabBrowser = new this._TabbedBrowsingAPI.tabBrowser(this._controller); } /** * Addons Manager class */ addonsManager.prototype = { /////////////////////////////// // Global section /////////////////////////////// /** * Get the controller of the window * * @returns Mozmill Controller * @type {MozMillController} */ get controller() { return this._controller; }, /** * Gets all the needed external DTD urls as an array * * @returns URL's of external DTD files * @type {array of string} */ get dtds() { var dtds = [ "chrome://mozapps/locale/extensions/extensions.dtd", "chrome://browser/locale/browser.dtd" ]; return dtds; }, /** * Open the Add-ons Manager * * @param {object} aSpec * Information how to open the Add-ons Manager * Elements: type - Event, can be menu, or shortcut * [optional - default: menu] * waitFor - Wait until the Add-ons Manager has been opened * [optional - default: true] * * * @returns Reference the tab with the Add-ons Manager open * @type {object} * Elements: controller - Mozmill Controller of the window * index - Index of the tab */ open : function addonsManager_open(aSpec) { var spec = aSpec || { }; var type = (spec.type == undefined) ? "menu" : spec.type; var waitFor = (spec.waitFor == undefined) ? true : spec.waitFor; switch (type) { case "menu": var menuItem = new elementslib.Elem(this._controller. menus["tools-menu"].menu_openAddons); this._controller.click(menuItem); break; case "shortcut": var cmdKey = this._UtilsAPI.getEntity(this.dtds, "addons.commandkey"); this._controller.keypress(null, cmdKey, {accelKey: true, shiftKey: true}); break; default: throw new Error(arguments.callee.name + ": Unknown event type - " + event.type); } return waitFor ? this.waitForOpened() : null; }, /** * Check if the Add-ons Manager is open * * @returns True if the Add-ons Manager is open * @type {boolean} */ get isOpen() { return (this.getTabs().length > 0); }, /** * Waits until the Addons Manager has been opened and returns its controller * * @param {object} aSpec * Object with parameters for customization * Elements: timeout - Duration to wait for the target state * [optional - default: 5s] * * @returns Currently selected tab */ waitForOpened : function addonsManager_waitforOpened(aSpec) { var spec = aSpec || { }; var timeout = (spec.timeout == undefined) ? TIMEOUT : spec.timeout; // TODO: restore after 1.5.1 has landed // var self = this; // // mozmill.utils.waitFor(function() { // return self.isOpen; // }, timeout, 100, "Add-ons Manager has been opened"); mozmill.utils.waitForEval("subject.isOpen", timeout, 100, this); // The first tab found will be the selected one var tab = this.getTabs()[0]; tab.controller.waitForPageLoad(); return tab; }, /** * Close the Addons Manager * * @param {object} aSpec * Information about the event to send * Elements: type - Event type (closeButton, menu, middleClick, shortcut) */ close : function addonsManager_close(aSpec) { this._tabBrowser.closeTab(aSpec); }, /** * Retrieves the list of open add-ons manager tabs * * @returns List of open tabs * @type {array of object} * Elements: controller - MozMillController * index - Index of the tab */ getTabs : function addonsManager_getTabs() { return this._TabbedBrowsingAPI.getTabsWithURL("about:addons"); }, /** * Opens the utils button menu and clicks the specified menu entry * * @param {object} aSpec * Information about the menu * Elements: item - menu item to click (updateNow, viewUpdates, * installFromFile, autoUpdateDefault, * resetAddonUpdatesToAutomatic, * resetAddonUpdatesToManual) */ handleUtilsButton : function addonsManager_handleUtilsButton(aSpec) { var spec = aSpec || { }; var item = spec.item; if (!item) throw new Error(arguments.callee.name + ": Menu item not specified."); var button = this.getElement({type: "utilsButton"}); var menu = this.getElement({type: "utilsButton_menu"}); try { this._controller.click(button); // Click the button and wait until menu has been opened // TODO: restore after 1.5.1 has landed // mozmill.utils.waitFor(function() { // return menu.getNode() && menu.getNode().state == "open"; // }, TIMEOUT, 100, "Menu of utils button has been opened."); mozmill.utils.waitForEval("subject && subject.state == 'open'", TIMEOUT, 100, menu.getNode()); // Click the given menu entry and make sure the var menuItem = this.getElement({ type: "utilsButton_menuItem", value: "#utils-" + item }); this._controller.click(menuItem); } finally { // Make sure the menu has been closed this._controller.keypress(menu, "VK_ESCAPE", {}); // TODO: restore after 1.5.1 has landed // mozmill.utils.waitFor(function() { // return menu.getNode() && menu.getNode().state == "closed"; // }, TIMEOUT, 100, "Menu of utils button has been closed."); mozmill.utils.waitForEval("subject && subject.state == 'closed'", TIMEOUT, 100, menu.getNode()); } }, /////////////////////////////// // Add-on section /////////////////////////////// /** * Check if the specified add-on is compatible * * @param {object} aSpec * Information on which add-on to operate on * Elements: addon - Add-on element * * @returns True if the add-on is compatible * @type {ElemBase} */ isAddonCompatible : function addonsManager_isAddonCompatible(aSpec) { var spec = aSpec || { }; var addon = spec.addon; if (!addon) throw new Error(arguments.callee.name + ": Add-on not specified."); // XXX: Bug 599702 doens't give enough information which type of notification return addon.getNode().getAttribute("notification") != "warning"; }, /** * Check if the specified add-on is enabled * * @param {object} aSpec * Information on which add-on to operate on * Elements: addon - Add-on element * * @returns True if the add-on is enabled * @type {ElemBase} */ isAddonEnabled : function addonsManager_isAddonEnabled(aSpec) { var spec = aSpec || { }; var addon = spec.addon; if (!addon) throw new Error(arguments.callee.name + ": Add-on not specified."); return addon.getNode().getAttribute("active") == "true"; }, /** * Check if the specified add-on is installed * * @param {object} aSpec * Information on which add-on to operate on * Elements: addon - Add-on element * * @returns True if the add-on is installed * @type {ElemBase} */ isAddonInstalled : function addonsManager_isAddonInstalled(aSpec) { var spec = aSpec || { }; var addon = spec.addon; if (!addon) throw new Error(arguments.callee.name + ": Add-on not specified."); // Bug 600502 : Add-ons in search view are not initialized correctly return addon.getNode().getAttribute("remote") == "false" && addon.getNode().getAttribute("status") == "installed"; }, /** * Enables the specified add-on * * @param {object} aSpec * Information on which add-on to operate on * Elements: addon - Add-on element */ enableAddon : function addonsManager_enableAddon(aSpec) { var spec = aSpec || { }; spec.button = "enable"; var button = this.getAddonButton(spec); this._controller.click(button); }, /** * Disables the specified add-on * * @param {object} aSpec * Information on which add-on to operate on * Elements: addon - Add-on element */ disableAddon : function addonsManager_disableAddon(aSpec) { var spec = aSpec || { }; spec.button = "disable"; var button = this.getAddonButton(spec); this._controller.click(button); }, /** * Installs the specified add-on * * @param {object} aSpec * Information on which add-on to operate on * Elements: addon - Add-on element * waitFor - Wait until the category has been selected * [optional - default: true] * timeout - Duration to wait for the download * [optional - default: 15s] */ installAddon : function addonsManager_installAddon(aSpec) { var spec = aSpec || { }; var addon = spec.addon; var timeout = spec.timeout; var button = "install"; var waitFor = (spec.waitFor == undefined) ? true : spec.waitFor; var button = this.getAddonButton({addon: addon, button: button}); this._controller.click(button); if (waitFor) this.waitForDownloaded({addon: addon, timeout: timeout}); }, /** * Removes the specified add-on * * @param {object} aSpec * Information on which add-on to operate on * Elements: addon - Add-on element */ removeAddon : function addonsManager_removeAddon(aSpec) { var spec = aSpec || { }; spec.button = "remove"; var button = this.getAddonButton(spec); this._controller.click(button); }, /** * Undo the last action performed for the given add-on * * @param {object} aSpec * Information on which add-on to operate on * Elements: addon - Add-on element */ undo : function addonsManager_undo(aSpec) { var spec = aSpec || { }; spec.link = "undo"; var link = this.getAddonLink(spec); this._controller.click(link); }, /** * Returns the addons from the currently selected view which match the * filter criteria * * @param {object} aSpec * Information about the filter to apply * Elements: attribute - DOM attribute of the wanted addon * [optional - default: ""] * value - Value of the DOM attribute * [optional - default: ""] * * @returns List of addons * @type {array of ElemBase} */ getAddons : function addonsManager_addons(aSpec) { var spec = aSpec || {}; return this.getElements({ type: "addons", subtype: spec.attribute, value: spec.value, parent: this.selectedView }); }, /** * Returns the element of the specified add-ons button * * @param {object} aSpec * Information on which add-on to operate on * Elements: addon - Add-on element * button - Button (disable, enable, preferences, remove) * * @returns Add-on button * @type {ElemBase} */ getAddonButton : function addonsManager_getAddonButton(aSpec) { var spec = aSpec || { }; var addon = spec.addon; var button = spec.button; if (!button) throw new Error(arguments.callee.name + ": Button not specified."); return this.getAddonChildElement({addon: addon, type: button + "Button"}); }, /** * Returns the element of the specified add-ons link * * @param {object} aSpec * Information on which add-on to operate on * Elements: addon - Add-on element * link - Link * List view (more, restart, undo) * Detail view (findUpdates, restart, undo) * * @return Add-on link * @type {ElemBase} */ getAddonLink : function addonsManager_getAddonLink(aSpec) { var spec = aSpec || { }; var addon = spec.addon; var link = spec.link; if (!link) throw new Error(arguments.callee.name + ": Link not specified."); return this.getAddonChildElement({addon: addon, type: link + "Link"}); }, /** * Returns the element of the specified add-ons radio group * * @param {object} aSpec * Information on which add-on to operate on * Elements: addon - Add-on element * radiogroup - Radiogroup * Detail View (autoUpdate) * * @returns Add-on radiogroup * @type {ElemBase} */ getAddonRadiogroup : function addonsManager_getAddonRadiogroup(aSpec) { var spec = aSpec || { }; var addon = spec.addon; var radiogroup = spec.radiogroup; if (!radiogroup) throw new Error(arguments.callee.name + ": Radiogroup not specified."); return this.getAddonChildElement({addon: addon, type: radiogroup + "Radiogroup"}); }, /** * Retrieve the given child element of the specified add-on * * @param {object} aSpec * Information for getting the add-ons child node * Elements: addon - Add-on element * type - Type of the element * [optional - default: use attribute/value] * attribute - DOM attribute of the node * value - Value of the DOM attribute * * @returns Element * @type {ElemBase} */ getAddonChildElement : function addonsManager_getAddonChildElement(aSpec) { var spec = aSpec || { }; var addon = spec.addon; var attribute = spec.attribute; var value = spec.value; var type = spec.type; if (!addon) throw new Error(arguments.callee.name + ": Add-on not specified."); // If no type has been set retrieve a general element which needs an // attribute and value if (!type) { type = "element"; if (!attribute) throw new Error(arguments.callee.name + ": DOM attribute not specified."); if (!value) throw new Error(arguments.callee.name + ": Value not specified."); } // For the details view the elements don't have anonymous nodes if (this.selectedView.getNode().id == "detail-view") { return this.getElement({ type: "detailView_" + type, subtype: attribute, value: value }); } else { return this.getElement({ type: "listView_" + type, subtype: attribute, value: value, parent: addon }); } }, /** * Wait until the specified add-on has been downloaded * * @param {object} aSpec * Object with parameters for customization * Elements: addon - Add-on element to wait for being downloaded * timeout - Duration to wait for the target state * [optional - default: 15s] */ waitForDownloaded : function addonsManager_waitForDownloaded(aSpec) { var spec = aSpec || { }; var addon = spec.addon; var timeout = (spec.timeout == undefined) ? TIMEOUT_DOWNLOAD : spec.timeout; if (!addon) throw new Error(arguments.callee.name + ": Add-on not specified."); var self = this; var node = addon.getNode(); // TODO: restore after 1.5.1 has landed // mozmill.utils.waitFor(function () { // return node.getAttribute("pending") == "install" && // node.getAttribute("status") != "installing"; // }, timeout, 100, "'" + node.getAttribute("name") + "' has been downloaded"); mozmill.utils.waitForEval("subject.getAttribute('pending') == 'install' &&" + "subject.getAttribute('status') != 'installing'", timeout, 100, node); }, /////////////////////////////// // Category section /////////////////////////////// /** * Retrieve the currently selected category * * @returns Element which represents the currently selected category * @type {ElemBase} */ get selectedCategory() { return this.getCategories({attribute: "selected", value: "true"})[0]; }, /** * Returns the categories which match the filter criteria * * @param {object} aSpec * Information about the filter to apply * Elements: attribute - DOM attribute of the wanted category * [optional - default: ""] * value - Value of the DOM attribute * [optional - default: ""] * * @returns List of categories * @type {array of ElemBase} */ getCategories : function addonsManager_categories(aSpec) { var spec = aSpec || { }; var categories = this.getElements({ type: "categories", subtype: spec.attribute, value: spec.value }); if (categories.length == 0) throw new Error(arguments.callee.name + ": Categories could not be found."); return categories; }, /** * Get the category element for the specified id * * @param {object} aSpec * Information for getting a category * Elements: id - Category id (search, discover, languages, * searchengines, extensions, themes, plugins, * availableUpdates, recentUpdates) * * @returns Category * @type {ElemBase} */ getCategoryById : function addonsManager_getCategoryById(aSpec) { var spec = aSpec || { }; var id = spec.id; if (!id) throw new Error(arguments.callee.name + ": Category ID not specified."); return this.getCategories({ attribute: "id", value: "category-" + id })[0]; }, /** * Get the ID of the given category element * * @param {object} aSpec * Information for getting a category * Elements: category - Category to get the id from * * @returns Category Id * @type {string} */ getCategoryId : function addonsManager_getCategoryId(aSpec) { var spec = aSpec || { }; var category = spec.category; if (!category) throw new Error(arguments.callee.name + ": Category not specified."); return category.getNode().id; }, /** * Select the given category * * @param {object} aSpec * Information for selecting a category * Elements: category - Category element * waitFor - Wait until the category has been selected * [optional - default: true] */ setCategory : function addonsManager_setCategory(aSpec) { var spec = aSpec || { }; var category = spec.category; var waitFor = (spec.waitFor == undefined) ? true : spec.waitFor; if (!category) throw new Error(arguments.callee.name + ": Category not specified."); this._controller.click(category); if (waitFor) this.waitForCategory({category: category}); }, /** * Select the category with the given id * * @param {object} aSpec * Information for selecting a category * Elements: id - Category id (search, discover, languages, * searchengines, extensions, themes, plugins, * availableUpdates, recentUpdates) * waitFor - Wait until the category has been selected * [optional - default: true] */ setCategoryById : function addonsManager_setCategoryById(aSpec) { var spec = aSpec || { }; var id = spec.id; var waitFor = (spec.waitFor == undefined) ? true : spec.waitFor; if (!id) throw new Error(arguments.callee.name + ": Category ID not specified."); // Retrieve the category and set it as active var category = this.getCategoryById({id: id}); if (category) this.setCategory({category: category, waitFor: waitFor}); else throw new Error(arguments.callee.name + ": Category '" + id + " not found."); }, /** * Wait until the specified category has been selected * * @param {object} aSpec * Object with parameters for customization * Elements: category - Category element to wait for * timeout - Duration to wait for the target state * [optional - default: 5s] */ waitForCategory : function addonsManager_waitForCategory(aSpec) { var spec = aSpec || { }; var category = spec.category; var timeout = (spec.timeout == undefined) ? TIMEOUT : spec.timeout; if (!category) throw new Error(arguments.callee.name + ": Category not specified."); // TODO: restore after 1.5.1 has landed // var self = this; // mozmill.utils.waitFor(function () { // return self.selectedCategory.getNode() == category.getNode(); // }, timeout, 100, "Category '" + category.getNode().id + "' has been set"); mozmill.utils.waitForEval("subject.self.selectedCategory.getNode() == subject.aCategory.getNode()", timeout, 100, {self: this, aCategory: category}); }, /////////////////////////////// // Search section /////////////////////////////// /** * Clear the search field */ clearSearchField : function addonsManager_clearSearchField() { var textbox = this.getElement({type: "search_textbox"}); var cmdKey = this._UtilsAPI.getEntity(this.dtds, "selectAllCmd.key"); this._controller.keypress(textbox, cmdKey, {accelKey: true}); this._controller.keypress(textbox, 'VK_DELETE', {}); }, /** * Search for a specified add-on * * @param {object} aSpec * Information to execute the search * Elements: value - Search term * timeout - Duration to wait for search results * [optional - default: 30s] * waitFor - Wait until the search has been finished * [optional - default: true] */ search : function addonsManager_search(aSpec) { var spec = aSpec || { }; var value = spec.value; var timeout = (spec.timeout == undefined) ? TIMEOUT_SEARCH : spec.timeout; var waitFor = (spec.waitFor == undefined) ? true : spec.waitFor; if (!value) throw new Error(arguments.callee.name + ": Search term not specified."); var textbox = this.getElement({type: "search_textbox"}); this.clearSearchField(); this._controller.type(textbox, value); this._controller.keypress(textbox, "VK_RETURN", {}); if (waitFor) this.waitForSearchFinished(); }, /** * Check if a search is active * * @returns State of the search * @type {boolean} */ get isSearching() { var throbber = this.getElement({type: "search_throbber"}); return throbber.getNode().hasAttribute("active"); }, /** * Retrieve the currently selected search filter * * @returns Element which represents the currently selected search filter * @type {ElemBase} */ get selectedSearchFilter() { var filter = this.getSearchFilter({attribute: "selected", value: "true"}); return (filter.length > 0) ? filter[0] : undefined; }, /** * Set the currently selected search filter status * * @param {string} aValue * Filter for the search results (local, remote) */ set selectedSearchFilter(aValue) { var filter = this.getSearchFilter({attribute: "value", value: aValue}); if (SEARCH_FILTER.indexOf(aValue) == -1) throw new Error(arguments.callee.name + ": '" + aValue + "' is not a valid search filter"); if (filter.length > 0) { this._controller.click(filter[0]); this.waitForSearchFilter({filter: filter[0]}); } }, /** * Returns the available search filters which match the filter criteria * * @param {object} aSpec * Information about the filter to apply * Elements: attribute - DOM attribute of the wanted filter * [optional - default: ""] * value - Value of the DOM attribute * [optional - default: ""] * * @returns List of search filters * @type {array of ElemBase} */ getSearchFilter : function addonsManager_getSearchFilter(aSpec) { var spec = aSpec || { }; return this.getElements({ type: "search_filterRadioButtons", subtype: spec.attribute, value: spec.value }); }, /** * Get the search filter element for the specified value * * @param {string} aValue * Search filter value (local, remote) * * @returns Search filter element * @type {ElemBase} */ getSearchFilterByValue : function addonsManager_getSearchFilterByValue(aValue) { if (!aValue) throw new Error(arguments.callee.name + ": Search filter value not specified."); return this.getElement({ type: "search_filterRadioGroup", subtype: "value", value: aValue }); }, /** * Get the value of the given search filter element * * @param {object} aSpec * Information for getting the views matched by the criteria * Elements: filter - Filter element * * @returns Value of the search filter * @type {string} */ getSearchFilterValue : function addonsManager_getSearchFilterValue(aSpec) { var spec = aSpec || { }; var filter = spec.filter; if (!filter) throw new Error(arguments.callee.name + ": Search filter not specified."); return filter.getNode().value; }, /** * Waits until the specified search filter has been selected * * @param {object} aSpec * Object with parameters for customization * Elements: filter - Filter element to wait for * timeout - Duration to wait for the target state * [optional - default: 5s] */ waitForSearchFilter : function addonsManager_waitForSearchFilter(aSpec) { var spec = aSpec || { }; var filter = spec.filter; var timeout = (spec.timeout == undefined) ? TIMEOUT : spec.timeout; if (!filter) throw new Error(arguments.callee.name + ": Search filter not specified."); // TODO: restore after 1.5.1 has landed // var self = this; // // mozmill.utils.waitFor(function () { // return self.selectedSearchFilter.getNode() == filter.getNode(); // }, timeout, 100, "Search filter '" + filter.getNode().value + "' has been set"); mozmill.utils.waitForEval("subject.self.selectedSearchFilter.getNode() == subject.aFilter.getNode()", timeout, 100, {self: this, aFilter: filter}); }, /** * Returns the list of add-ons found by the selected filter * * @returns List of add-ons * @type {ElemBase} */ getSearchResults : function addonsManager_getSearchResults() { var filterValue = this.getSearchFilterValue({ filter: this.selectedSearchFilter }); switch (filterValue) { case "local": return this.getAddons({attribute: "status", value: "installed"}); case "remote": return this.getAddons({attribute: "remote", value: "true"}); default: throw new Error(arguments.callee.name + ": Unknown search filter '" + filterValue + "' selected"); } }, /** * Waits until the active search has been finished * * @param {object} aSpec * Object with parameters for customization * Elements: timeout - Duration to wait for the target state */ waitForSearchFinished : function addonsManager_waitForSearchFinished(aSpec) { var spec = aSpec || { }; var timeout = (spec.timeout == undefined) ? TIMEOUT_SEARCH : spec.timeout; // TODO: restore after 1.5.1 has landed // var self = this; // // mozmill.utils.waitFor(function () { // return self.isSearching == false; // }, timeout, 100, "Search has been finished"); mozmill.utils.waitForEval("subject.isSearching == false", timeout, 100, this); }, /////////////////////////////// // View section /////////////////////////////// /** * Returns the views which match the filter criteria * * @param {object} aSpec * Information for getting the views matched by the criteria * Elements: attribute - DOM attribute of the node * [optional - default: ""] * value - Value of the DOM attribute * [optional - default: ""] * * @returns Filtered list of views * @type {array of ElemBase} */ getViews : function addonsManager_getViews(aSpec) { var spec = aSpec || { }; var attribute = spec.attribute; var value = spec.value; return this.getElements({type: "views", subtype: attribute, value: value}); }, /** * Check if the details view is active * * @returns True if the default view is selected * @type {boolean} */ get isDetailViewActive() { return (this.selectedView.getNode().id == "detail-view"); }, /** * Retrieve the currently used view * * @returns Element which represents the currently selected view * @type {ElemBase} */ get selectedView() { var viewDeck = this.getElement({type: "viewDeck"}); var views = this.getViews(); return views[viewDeck.getNode().selectedIndex]; }, /////////////////////////////// // UI Elements section /////////////////////////////// /** * Retrieve an UI element based on the given specification * * @param {object} aSpec * Information of the UI elements which should be retrieved * Elements: type - Identifier of the element * subtype - Attribute of the element to filter * [optional - default: ""] * value - Value of the attribute to filter * [optional - default: ""] * parent - Parent of the to find element * [optional - default: document] * * @returns Element which has been found * @type {ElemBase} */ getElement : function addonsManager_getElement(aSpec) { var elements = this.getElements(aSpec); return (elements.length > 0) ? elements[0] : undefined; }, /** * Retrieve list of UI elements based on the given specification * * @param {object} aSpec * Information of the UI elements which should be retrieved * Elements: type - Identifier of the element * subtype - Attribute of the element to filter * [optional - default: ""] * value - Value of the attribute to filter * [optional - default: ""] * parent - Parent of the to find element * [optional - default: document] * * @returns Elements which have been found * @type {array of ElemBase} */ getElements : function addonsManager_getElements(aSpec) { var spec = aSpec || { }; var type = spec.type; var subtype = spec.subtype; var value = spec.value; var parent = spec.parent; var root = parent ? parent.getNode() : this._controller.tabs.activeTab; var nodeCollector = new this._DOMUtilsAPI.nodeCollector(root); switch (type) { // Add-ons case "addons": nodeCollector.queryNodes(".addon").filterByDOMProperty(subtype, value); break; case "addonsList": nodeCollector.queryNodes("#addon-list"); break; // Categories case "categoriesList": nodeCollector.queryNodes("#categories"); break; case "categories": nodeCollector.queryNodes(".category").filterByDOMProperty(subtype, value); break; // Detail view case "detailView_element": nodeCollector.queryNodes(value); break; case "detailView_disableButton": nodeCollector.queryNodes("#detail-disable"); break; case "detailView_enableButton": nodeCollector.queryNodes("#detail-enable"); break; case "detailView_installButton": nodeCollector.queryNodes("#detail-install"); break; case "detailView_preferencesButton": nodeCollector.queryNodes("#detail-prefs"); break; case "detailView_removeButton": nodeCollector.queryNodes("#detail-uninstall"); break; case "detailView_findUpdatesLink": nodeCollector.queryNodes("#detail-findUpdates"); break; // Bug 599771 - button-link's are missing id or anonid //case "detailView_restartLink": // nodeCollector.queryNodes("#detail-restart"); // break; case "detailView_undoLink": nodeCollector.queryNodes("#detail-undo"); break; case "detailView_findUpdatesRadiogroup": nodeCollector.queryNodes("#detail-findUpdates"); break; // List view case "listView_element": nodeCollector.queryAnonymousNodes(subtype, value); break; case "listView_disableButton": nodeCollector.queryAnonymousNodes("anonid", "disable-btn"); break; case "listView_enableButton": nodeCollector.queryAnonymousNodes("anonid", "enable-btn"); break; case "listView_installButton": // There is another binding we will have to skip nodeCollector.queryAnonymousNodes("anonid", "install-status"); nodeCollector.root = nodeCollector.nodes[0]; nodeCollector.queryAnonymousNodes("anonid", "install-remote"); break; case "listView_preferencesButton": nodeCollector.queryAnonymousNodes("anonid", "preferences-btn"); break; case "listView_removeButton": nodeCollector.queryAnonymousNodes("anonid", "remove-btn"); break; case "listView_moreLink": // Bug 599771 - button-link's are missing id or anonid nodeCollector.queryAnonymousNodes("class", "details button-link"); break; // Bug 599771 - button-link's are missing id or anonid //case "listView_restartLink": // nodeCollector.queryAnonymousNodes("anonid", "restart"); // break; case "listView_undoLink": nodeCollector.queryAnonymousNodes("anonid", "undo"); break; case "listView_cancelDownload": // There is another binding we will have to skip nodeCollector.queryAnonymousNodes("anonid", "install-status"); nodeCollector.root = nodeCollector.nodes[0]; nodeCollector.queryAnonymousNodes("anonid", "cancel"); break; case "listView_pauseDownload": // There is another binding we will have to skip nodeCollector.queryAnonymousNodes("anonid", "install-status"); nodeCollector.root = nodeCollector.nodes[0]; nodeCollector.queryAnonymousNodes("anonid", "pause"); break; case "listView_progressDownload": // There is another binding we will have to skip nodeCollector.queryAnonymousNodes("anonid", "install-status"); nodeCollector.root = nodeCollector.nodes[0]; nodeCollector.queryAnonymousNodes("anonid", "progress"); break; // Search // Bug 599775 - Controller needs to handle radio groups correctly // Means for now we have to use the radio buttons case "search_filterRadioButtons": nodeCollector.queryNodes(".search-filter-radio").filterByDOMProperty(subtype, value); break; case "search_filterRadioGroup": nodeCollector.queryNodes("#search-filter-radiogroup"); break; case "search_textbox": nodeCollector.queryNodes("#header-search"); break; case "search_throbber": nodeCollector.queryNodes("#header-searching"); break; // Utils case "utilsButton": nodeCollector.queryNodes("#header-utils-btn"); break; case "utilsButton_menu": nodeCollector.queryNodes("#utils-menu"); break; case "utilsButton_menuItem": nodeCollector.queryNodes(value); break; // Views case "viewDeck": nodeCollector.queryNodes("#view-port"); break; case "views": nodeCollector.queryNodes(".view-pane").filterByDOMProperty(subtype, value); break; default: throw new Error(arguments.callee.name + ": Unknown element type - " + spec.type); } return nodeCollector.elements; } }; /** * Updates all necessary preferences to the preview sub domain */ function useAmoPreviewUrls() { var prefSrv = collector.getModule('PrefsAPI').preferences; for each (var preference in AMO_PREFERENCES) { var pref = prefSrv.getPref(preference.name, ""); prefSrv.setPref(preference.name, pref.replace(preference.old, preference.new)); } } /** * Reset all preferences which point to the preview sub domain */ function resetAmoPreviewUrls() { var prefSrv = collector.getModule('PrefsAPI').preferences; for each (var preference in AMO_PREFERENCES) { prefSrv.clearUserPref(preference.name); } }