diff --git a/toolkit/mozapps/extensions/content/extensions.js b/toolkit/mozapps/extensions/content/extensions.js index 8bc052b8b802..b9c930e3ee1b 100644 --- a/toolkit/mozapps/extensions/content/extensions.js +++ b/toolkit/mozapps/extensions/content/extensions.js @@ -88,6 +88,9 @@ XPCOMUtils.defineLazyGetter(gStrings, "appVersion", function() { window.addEventListener("load", initialize, false); window.addEventListener("unload", shutdown, false); +var gPendingInitializations = 1; +__defineGetter__("gIsInitializing", function() gPendingInitializations > 0); + function initialize() { gCategories.initialize(); gHeader.initialize(); @@ -105,6 +108,19 @@ function initialize() { } gViewController.loadView(view); + notifyInitialized(); +} + +function notifyInitialized() { + if (!gIsInitializing) + return; + + gPendingInitializations--; + if (!gIsInitializing) { + var event = document.createEvent("Events"); + event.initEvent("Initialized", true, true); + document.dispatchEvent(event); + } } function shutdown() { @@ -258,6 +274,10 @@ var gViewController = { return {type: viewType, param: decodeURIComponent(viewParam)}; }, + get isLoading() { + return this.currentViewObj.node.hasAttribute("loading"); + }, + loadView: function(aViewId) { if (aViewId == this.currentViewId) return; @@ -616,6 +636,39 @@ function createItem(aObj, aIsInstall, aRequiresRestart) { return item; } +function getAddonsAndInstalls(aType, aCallback) { + var addonTypes = null, installTypes = null; + if (aType != null) { + addonTypes = [aType]; + installTypes = [aType]; + if (aType == "extension") { + addonTypes.push("bootstrapped"); + installTypes = addonTypes.concat(""); + } + } + + var addons = null, installs = null; + + AddonManager.getAddonsByTypes(addonTypes, function(aAddonsList) { + addons = aAddonsList; + if (installs != null) + aCallback(addons, installs); + }); + + AddonManager.getInstallsByTypes(installTypes, function(aInstallsList) { + // skip over upgrade installs and non-active installs + installs = aInstallsList.filter(function(aInstall) { + return !(aInstall.existingAddon || + aInstall.state == AddonManager.STATE_AVAILABLE); + }); + + if (addons != null) + aCallback(addons, installs) + }); + + return {addon: addonTypes, install: installTypes}; +} + var gCategories = { node: null, @@ -648,16 +701,18 @@ var gCategories = { }, false); var maybeHidden = ["addons://list/locale", "addons://list/searchengine"]; + gPendingInitializations += maybeHidden.length; maybeHidden.forEach(function(aId) { var type = gViewController.parseViewId(aId).param; - AddonManager.getAddonsByTypes([type], function(aAddonsList) { - if (aAddonsList.length > 0) { + getAddonsAndInstalls(type, function(aAddonsList, aInstallsList) { + if (aAddonsList.length > 0 || aInstallsList.length > 0) { self.get(aId).hidden = false; + notifyInitialized(); return; } gEventManager.registerInstallListener({ - onNewInstall: function(aInstall) { + onDownloadStarted: function(aInstall) { this._maybeShowCategory(aInstall); }, @@ -680,6 +735,8 @@ var gCategories = { } } }); + + notifyInitialized(); }); }); }, @@ -795,6 +852,7 @@ var gDiscoverView = { .getService(Ci.nsIURLFormatter) .formatURLPref(PREF_DISCOVERURL); + gPendingInitializations++; AddonManager.getAllAddons(function(aAddons) { var list = {}; aAddons.forEach(function(aAddon) { @@ -809,6 +867,7 @@ var gDiscoverView = { }); gDiscoverView._browser.homePage = url + "#" + JSON.stringify(list); + notifyInitialized(); }); }, @@ -996,33 +1055,21 @@ var gListView = { gHeader.setName(gStrings.ext.GetStringFromName("header-" + aType)); this.showEmptyNotice(false); - this._types = [aType]; - this._installTypes = [aType]; - if (aType == "extension") { - this._types.push("bootstrapped"); - this._installTypes = this._types.concat(""); - } - while (this._listBox.itemCount > 0) this._listBox.removeItemAt(0); var self = this; - var addons = null, installs = null; - - function updateList() { - if (addons == null || installs == null) + var types = getAddonsAndInstalls(aType, function(aAddonsList, aInstallsList) { + if (gViewController && aRequest != gViewController.currentViewRequest) return; - for (let i = 0; i < addons.length; i++) { - let item = createItem(addons[i]); + for (let i = 0; i < aAddonsList.length; i++) { + let item = createItem(aAddonsList[i]); self._listBox.appendChild(item); } - for (let i = 0; i < installs.length; i++) { - // skip over upgrade installs - if (installs[i].existingAddon) - continue; - let item = createItem(installs[i], true); + for (let i = 0; i < aInstallsList.length; i++) { + let item = createItem(aInstallsList[i], true); self._listBox.appendChild(item); } @@ -1031,25 +1078,13 @@ var gListView = { else self.showEmptyNotice(true); + gEventManager.registerInstallListener(self); gViewController.updateCommands(); gViewController.notifyViewChanged(); - } - - - AddonManager.getAddonsByTypes(this._types, function(aAddonsList) { - if (gViewController && aRequest != gViewController.currentViewRequest) - return; - addons = aAddonsList; - updateList(); }); - AddonManager.getInstallsByTypes(this._installTypes, function(aInstallsList) { - if (gViewController && aRequest != gViewController.currentViewRequest) - return; - installs = aInstallsList; - updateList(); - gEventManager.registerInstallListener(self); - }); + this._types = types.addon; + this._installTypes = types.install; }, hide: function() { @@ -1277,7 +1312,7 @@ var gDragDrop = { }, onDrop: function(aEvent) { - var dataTransfer = aEvent.dataTransfer; + var dataTransfer = aEvent.dataTransfer; var urls = []; // Convert every dropped item into a url diff --git a/toolkit/mozapps/extensions/test/browser/Makefile.in b/toolkit/mozapps/extensions/test/browser/Makefile.in index 592d14dd25db..cb33e1ffd64c 100644 --- a/toolkit/mozapps/extensions/test/browser/Makefile.in +++ b/toolkit/mozapps/extensions/test/browser/Makefile.in @@ -49,6 +49,7 @@ _TEST_FILES = \ head.js \ browser_bug562890.js \ browser_bug562899.js \ + browser_bug572561.js \ browser_dragdrop.js \ browser_sorting.js \ browser_updatessl.js \ diff --git a/toolkit/mozapps/extensions/test/browser/browser_bug562899.js b/toolkit/mozapps/extensions/test/browser/browser_bug562899.js index 60311e7ea094..1d83ec17f22e 100644 --- a/toolkit/mozapps/extensions/test/browser/browser_bug562899.js +++ b/toolkit/mozapps/extensions/test/browser/browser_bug562899.js @@ -28,10 +28,10 @@ function test() { } function end_test() { - gManagerWindow.close(); - LightweightThemeManager.forgetUsedTheme("test"); - - finish(); + close_manager(gManagerWindow, function() { + LightweightThemeManager.forgetUsedTheme("test"); + finish(); + }); } // Tests that loading a second view before the first has not finished loading diff --git a/toolkit/mozapps/extensions/test/browser/browser_bug572561.js b/toolkit/mozapps/extensions/test/browser/browser_bug572561.js new file mode 100644 index 000000000000..75aae1b80ef8 --- /dev/null +++ b/toolkit/mozapps/extensions/test/browser/browser_bug572561.js @@ -0,0 +1,94 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Tests that the locale category is shown if there are no locale packs +// installed but some are pending install + +var gManagerWindow; +var gProvider; +var gInstallProperties = [{ + name: "Locale Category Test", + type: "locale" +}]; +var gInstall; +var gExpectedCancel = false; +var gTestInstallListener = { + onInstallStarted: function(aInstall) { + check_hidden(false); + }, + + onInstallEnded: function(aInstall) { + check_hidden(false); + run_next_test(); + }, + + onInstallCancelled: function(aInstall) { + ok(gExpectedCancel, "Should expect install cancel"); + check_hidden(false); + run_next_test(); + }, + + onInstallFailed: function(aInstall) { + ok(false, "Did not expect onInstallFailed"); + } +}; + +function test() { + waitForExplicitFinish(); + + gProvider = new MockProvider(); + + open_manager(null, function(aWindow) { + gManagerWindow = aWindow; + run_next_test(); + }); +} + +function end_test() { + close_manager(gManagerWindow, function() { + finish(); + }); +} + +function check_hidden(aExpectedHidden) { + var category = gManagerWindow.gCategories.get("addons://list/locale"); + is(category.hidden, aExpectedHidden, "Should have correct hidden state"); +} + +// Tests that a non-active install does not make the locale category show +add_test(function() { + check_hidden(true); + gInstall = gProvider.createInstalls(gInstallProperties)[0]; + gInstall.addTestListener(gTestInstallListener); + check_hidden(true); + run_next_test(); +}); + +// Test that restarting the add-on manager with a non-active install +// does not cause the locale category to show +add_test(function() { + restart_manager(gManagerWindow, null, function(aWindow) { + gManagerWindow = aWindow; + check_hidden(true); + run_next_test(); + }); +}); + +// Test that installing the install shows the locale category +add_test(function() { + gInstall.install(); +}); + +// Test that restarting the add-on manager does not cause the locale category +// to become hidden +add_test(function() { + restart_manager(gManagerWindow, null, function(aWindow) { + gManagerWindow = aWindow; + check_hidden(false); + + gExpectedCancel = true; + gInstall.cancel(); + }); +}); + diff --git a/toolkit/mozapps/extensions/test/browser/browser_dragdrop.js b/toolkit/mozapps/extensions/test/browser/browser_dragdrop.js index b7e68530f918..8ed01ef672d6 100644 --- a/toolkit/mozapps/extensions/test/browser/browser_dragdrop.js +++ b/toolkit/mozapps/extensions/test/browser/browser_dragdrop.js @@ -79,9 +79,9 @@ function test() { } function end_test() { - gManagerWindow.close(); - - finish(); + close_manager(gManagerWindow, function() { + finish(); + }); } function test_confirmation(aWindow, aExpectedURLs) { diff --git a/toolkit/mozapps/extensions/test/browser/browser_sorting.js b/toolkit/mozapps/extensions/test/browser/browser_sorting.js index 34efa1f6a95a..eecbb262ccfa 100644 --- a/toolkit/mozapps/extensions/test/browser/browser_sorting.js +++ b/toolkit/mozapps/extensions/test/browser/browser_sorting.js @@ -49,9 +49,9 @@ function test() { } function end_test() { - gManagerWindow.close(); - - finish(); + close_manager(gManagerWindow, function() { + finish(); + }); } function check_order(aExpectedOrder) { diff --git a/toolkit/mozapps/extensions/test/browser/head.js b/toolkit/mozapps/extensions/test/browser/head.js index 011a99dafd4b..443e48aaa82c 100644 --- a/toolkit/mozapps/extensions/test/browser/head.js +++ b/toolkit/mozapps/extensions/test/browser/head.js @@ -48,7 +48,7 @@ function get_addon_file_url(aFilename) { } function wait_for_view_load(aManagerWindow, aCallback) { - if (!aManagerWindow.gViewController.currentViewObj.node.hasAttribute("loading")) { + if (!aManagerWindow.gViewController.isLoading) { aCallback(aManagerWindow); return; } @@ -67,7 +67,15 @@ function open_manager(aView, aCallback) { ok(aManagerWindow != null, "Should have an add-ons manager window"); is(aManagerWindow.location, MANAGER_URI, "Should be displaying the correct UI"); - wait_for_view_load(aManagerWindow, aCallback); + if (!aManagerWindow.gIsInitializing) { + wait_for_view_load(aManagerWindow, aCallback); + return; + } + + aManagerWindow.document.addEventListener("Initialized", function() { + aManagerWindow.document.removeEventListener("Initialized", arguments.callee, false); + wait_for_view_load(aManagerWindow, aCallback); + }, false); } if ("switchToTabHavingURI" in window) { @@ -77,12 +85,28 @@ function open_manager(aView, aCallback) { return; } - openDialog("about:addons").addEventListener("load", function() { + openDialog(MANAGER_URI).addEventListener("load", function() { this.removeEventListener("load", arguments.callee, false); setup_manager(this); }, false); } +function close_manager(aManagerWindow, aCallback) { + ok(aManagerWindow != null, "Should have an add-ons manager window to close"); + is(aManagerWindow.location, MANAGER_URI, "Should be closing window with correct URI"); + + aManagerWindow.addEventListener("unload", function() { + this.removeEventListener("unload", arguments.callee, false); + aCallback(); + }, false); + + aManagerWindow.close(); +} + +function restart_manager(aManagerWindow, aView, aCallback) { + close_manager(aManagerWindow, function() { open_manager(aView, aCallback); }); +} + function CertOverrideListener(host, bits) { this.host = host; this.bits = bits; @@ -186,14 +210,32 @@ MockProvider.prototype = { null, false) }, + /** + * Adds an add-on install to the list of installs that this provider exposes + * to the AddonManager, dispatching appropriate events in the process. + * + * @param aInstall + * The add-on install to add + */ + addInstall: function MP_addInstall(aInstall) { + this.installs.push(aInstall); + + if (!this.started) + return; + + aInstall.callListeners("onNewInstall"); + }, + /** * Creates a set of mock add-on objects and adds them to the list of add-ons * managed by this provider. * * @param aAddonProperties * An array of objects containing properties describing the add-ons + * @return Array of the new MockAddons */ createAddons: function MP_createAddons(aAddonProperties) { + var newAddons = []; aAddonProperties.forEach(function(aAddonProp) { var addon = new MockAddon(aAddonProp.id); for (var prop in aAddonProp) { @@ -202,7 +244,32 @@ MockProvider.prototype = { addon[prop] = aAddonProp[prop]; } this.addAddon(addon); + newAddons.push(addon); }, this); + + return newAddons; + }, + + /** + * Creates a set of mock add-on install objects and adds them to the list + * of installs managed by this provider. + * + * @param aInstallProperties + * An array of objects containing properties describing the installs + * @return Array of the new MockInstalls + */ + createInstalls: function MP_createInstalls(aInstallProperties) { + var newInstalls = []; + aInstallProperties.forEach(function(aInstallProp) { + var install = new MockInstall(); + for (var prop in aInstallProp) { + install[prop] = aInstallProp[prop]; + } + this.addInstall(install); + newInstalls.push(install); + }, this); + + return newInstalls; }, /***** AddonProvider implementation *****/ @@ -287,8 +354,13 @@ MockProvider.prototype = { */ getInstallsByTypes: function MP_getInstallsByTypes(aTypes, aCallback) { var installs = this.installs.filter(function(aInstall) { + // Appear to have actually removed cancelled installs from the provider + if (aInstall.state == AddonManager.STATE_CANCELLED) + return false; + if (aTypes && aTypes.length > 0 && aTypes.indexOf(aInstall.type) == -1) return false; + return true; }); this._delayCallback(aCallback, installs); @@ -454,3 +526,109 @@ MockAddon.prototype = { // To be implemented when needed } }; + +/***** Mock AddonInstall object for the Mock Provider *****/ + +function MockInstall(aName, aType) { + this.name = aName || ""; + this.type = aType || "extension"; + this.version = "1.0"; + this.iconURL = ""; + this.infoURL = ""; + this.state = AddonManager.STATE_AVAILABLE; + this.error = 0; + this.sourceURL = ""; + this.file = null; + this.progress = 0; + this.maxProgress = -1; + this.certificate = null; + this.certName = ""; + this.existingAddon = null; + this.addon = null; + this.listeners = []; + + // Another type of install listener for tests that want to check the results + // of code run from standard install listeners + this.testListeners = []; +} + +MockInstall.prototype = { + install: function() { + switch (this.state) { + case AddonManager.STATE_AVAILABLE: + case AddonManager.STATE_DOWNLOADED: + // Downloading to be implemented when needed + + this.state = AddonManager.STATE_INSTALLING; + if (!this.callListeners("onInstallStarted")) { + // Reverting to STATE_DOWNLOADED instead to be implemented when needed + this.state = AddonManager.STATE_CANCELLED; + this.callListeners("onInstallCancelled"); + return; + } + + // Adding addon to MockProvider to be implemented when needed + this.addon = new MockAddon("", this.name, this.type); + this.state = AddonManager.STATE_INSTALLED; + this.callListeners("onInstallEnded"); + break; + case AddonManager.STATE_DOWNLOADING: + case AddonManager.STATE_CHECKING: + case AddonManger.STATE_INSTALLING: + // Installation is already running + return; + default: + ok(false, "Cannot start installing when state = " + this.state); + } + }, + + cancel: function() { + switch (this.state) { + case AddonManager.STATE_AVAILABLE: + this.state = AddonManager.STATE_CANCELLED; + break; + case AddonManager.STATE_INSTALLED: + this.state = AddonManager.STATE_CANCELLED; + this.callListeners("onInstallCancelled"); + break; + default: + // Handling cancelling when downloading to be implemented when needed + ok(false, "Cannot cancel when state = " + this.state); + } + }, + + + addListener: function(aListener) { + if (!this.listeners.some(function(i) i == aListener)) + this.listeners.push(aListener); + }, + + removeListener: function(aListener) { + this.listeners = this.listeners.filter(function(i) i != aListener); + }, + + addTestListener: function(aListener) { + if (!this.testListeners.some(function(i) i == aListener)) + this.testListeners.push(aListener); + }, + + removeTestListener: function(aListener) { + this.testListeners = this.testListeners.filter(function(i) i != aListener); + }, + + callListeners: function(aMethod) { + var result = AddonManagerPrivate.callInstallListeners(aMethod, this.listeners, + this, this.addon); + + // Call test listeners after standard listeners to remove race condition + // between standard and test listeners + this.testListeners.forEach(function(aListener) { + if (aMethod in aListener) + if (aListener[aMethod].call(aListener, this, this.addon) === false) + result = false; + }); + + return result; + } +}; +