From c237cd28b93a2a2c0d192f53bb530e7fddbf8c1c Mon Sep 17 00:00:00 2001 From: Shane Caraveo Date: Thu, 9 May 2013 12:59:57 -0700 Subject: [PATCH] bug 862314 prevent double install of providers, r=markh --- .../test/social/browser_social_activation.js | 214 +++++++++++++----- .../content/test/social/social_activate.html | 4 +- toolkit/components/social/SocialService.jsm | 35 ++- 3 files changed, 185 insertions(+), 68 deletions(-) diff --git a/browser/base/content/test/social/browser_social_activation.js b/browser/base/content/test/social/browser_social_activation.js index 87315026839..a59cfb151cc 100644 --- a/browser/base/content/test/social/browser_social_activation.js +++ b/browser/base/content/test/social/browser_social_activation.js @@ -42,7 +42,9 @@ function postTestCleanup(callback) { } function addBuiltinManifest(manifest) { - setManifestPref("social.manifest."+manifest.origin, manifest); + let prefname = getManifestPrefname(manifest); + setBuiltinManifestPref(prefname, manifest); + return prefname; } function addTab(url, callback) { @@ -80,6 +82,79 @@ function activateIFrameProvider(domain, callback) { }); } +function waitForProviderLoad(cb) { + Services.obs.addObserver(function providerSet(subject, topic, data) { + Services.obs.removeObserver(providerSet, "social:provider-set"); + info("social:provider-set observer was notified"); + waitForCondition(function() { + let sbrowser = document.getElementById("social-sidebar-browser"); + return Social.provider && + Social.provider.profile && + Social.provider.profile.displayName && + sbrowser.docShellIsActive; + }, function() { + // executeSoon to let the browser UI observers run first + executeSoon(cb); + }, + "waitForProviderLoad: provider profile was not set"); + }, "social:provider-set", false); +} + + +function getAddonItemInList(aId, aList) { + var item = aList.firstChild; + while (item) { + if ("mAddon" in item && item.mAddon.id == aId) { + aList.ensureElementIsVisible(item); + return item; + } + item = item.nextSibling; + } + return null; +} + +function clickAddonRemoveButton(tab, aCallback) { + AddonManager.getAddonsByTypes(["service"], function(aAddons) { + let addon = aAddons[0]; + + let doc = tab.linkedBrowser.contentDocument; + let list = doc.getElementById("addon-list"); + + let item = getAddonItemInList(addon.id, list); + isnot(item, null, "Should have found the add-on in the list"); + + var button = doc.getAnonymousElementByAttribute(item, "anonid", "remove-btn"); + isnot(button, null, "Should have a remove button"); + ok(!button.disabled, "Button should not be disabled"); + + EventUtils.synthesizeMouseAtCenter(button, { }, doc.defaultView); + + // Force XBL to apply + item.clientTop; + + is(item.getAttribute("pending"), "uninstall", "Add-on should be uninstalling"); + + executeSoon(function() { aCallback(addon); }); + }); +} + +function activateOneProvider(manifest, finishActivation, aCallback) { + activateProvider(manifest.origin, function() { + waitForProviderLoad(function() { + ok(!SocialUI.activationPanel.hidden, "activation panel is showing"); + is(Social.provider.origin, manifest.origin, "new provider is active"); + checkSocialUI(); + + if (finishActivation) + document.getElementById("social-activation-button").click(); + else + document.getElementById("social-undoactivation-button").click(); + + executeSoon(aCallback); + }); + }); +} + let gTestDomains = ["https://example.com", "https://test1.example.com", "https://test2.example.com"]; let gProviders = [ { @@ -139,41 +214,26 @@ var tests = { testActivationFirstProvider: function(next) { Services.prefs.setCharPref("social.whitelist", gTestDomains.join(",")); // first up we add a manifest entry for a single provider. - activateProvider(gTestDomains[0], function() { - ok(!SocialUI.activationPanel.hidden, "activation panel should be showing"); - is(Social.provider.origin, gTestDomains[0], "new provider is active"); + activateOneProvider(gProviders[0], false, function() { + // we deactivated leaving no providers left, so Social is disabled. + ok(!Social.provider, "should be no provider left after disabling"); checkSocialUI(); - // hit "undo" - document.getElementById("social-undoactivation-button").click(); - executeSoon(function() { - // we deactivated leaving no providers left, so Social is disabled. - ok(!Social.provider, "should be no provider left after disabling"); - checkSocialUI(); - Services.prefs.clearUserPref("social.whitelist"); - next(); - }) + Services.prefs.clearUserPref("social.whitelist"); + next(); }); }, testActivationBuiltin: function(next) { - let prefname = getManifestPrefname(gProviders[0]); - setBuiltinManifestPref(prefname, gProviders[0]); + let prefname = addBuiltinManifest(gProviders[0]); is(SocialService.getOriginActivationType(gTestDomains[0]), "builtin", "manifest is builtin"); // first up we add a manifest entry for a single provider. - activateProvider(gTestDomains[0], function() { - ok(!SocialUI.activationPanel.hidden, "activation panel should be showing"); - is(Social.provider.origin, gTestDomains[0], "new provider is active"); + activateOneProvider(gProviders[0], false, function() { + // we deactivated leaving no providers left, so Social is disabled. + ok(!Social.provider, "should be no provider left after disabling"); checkSocialUI(); - // hit "undo" - document.getElementById("social-undoactivation-button").click(); - executeSoon(function() { - // we deactivated leaving no providers left, so Social is disabled. - ok(!Social.provider, "should be no provider left after disabling"); - checkSocialUI(); - resetBuiltinManifestPref(prefname); - next(); - }) - }, true); + resetBuiltinManifestPref(prefname); + next(); + }); }, testActivationMultipleProvider: function(next) { @@ -188,20 +248,14 @@ var tests = { Social.provider = Social.providers[1]; checkSocialUI(); // activate the last provider. - addBuiltinManifest(gProviders[2]); - activateProvider(gTestDomains[2], function() { - ok(!SocialUI.activationPanel.hidden, "activation panel should be showing"); - is(Social.provider.origin, gTestDomains[2], "new provider is active"); + let prefname = addBuiltinManifest(gProviders[2]); + activateOneProvider(gProviders[2], false, function() { + // we deactivated - the first provider should be enabled. + is(Social.provider.origin, Social.providers[1].origin, "original provider should have been reactivated"); checkSocialUI(); - // hit "undo" - document.getElementById("social-undoactivation-button").click(); - executeSoon(function() { - // we deactivated - the first provider should be enabled. - is(Social.provider.origin, Social.providers[1].origin, "original provider should have been reactivated"); - checkSocialUI(); - Services.prefs.clearUserPref("social.whitelist"); - next(); - }); + Services.prefs.clearUserPref("social.whitelist"); + resetBuiltinManifestPref(prefname); + next(); }); }); }); @@ -216,23 +270,75 @@ var tests = { // activate the last provider. addBuiltinManifest(gProviders[2]); activateProvider(gTestDomains[2], function() { - ok(!SocialUI.activationPanel.hidden, "activation panel should be showing"); - is(Social.provider.origin, gTestDomains[2], "new provider is active"); - checkSocialUI(); - // A bit contrived, but set a new provider current while the - // activation ui is up. - Social.provider = Social.providers[1]; - // hit "undo" - document.getElementById("social-undoactivation-button").click(); - executeSoon(function() { - // we deactivated - the same provider should be enabled. - is(Social.provider.origin, Social.providers[1].origin, "original provider still be active"); + waitForProviderLoad(function() { + ok(!SocialUI.activationPanel.hidden, "activation panel is showing"); + is(Social.provider.origin, gTestDomains[2], "new provider is active"); checkSocialUI(); - Services.prefs.clearUserPref("social.whitelist"); - next(); + // A bit contrived, but set a new provider current while the + // activation ui is up. + Social.provider = Social.providers[1]; + // hit "undo" + document.getElementById("social-undoactivation-button").click(); + executeSoon(function() { + // we deactivated - the same provider should be enabled. + is(Social.provider.origin, Social.providers[1].origin, "original provider still be active"); + checkSocialUI(); + Services.prefs.clearUserPref("social.whitelist"); + next(); + }); }); }); }); }); }, + + testAddonManagerDoubleInstall: function(next) { + Services.prefs.setCharPref("social.whitelist", gTestDomains.join(",")); + // Create a new tab and load about:addons + let blanktab = gBrowser.addTab(); + gBrowser.selectedTab = blanktab; + BrowserOpenAddonsMgr('addons://list/service'); + + is(blanktab, gBrowser.selectedTab, "Current tab should be blank tab"); + + gBrowser.selectedBrowser.addEventListener("load", function tabLoad() { + gBrowser.selectedBrowser.removeEventListener("load", tabLoad, true); + let browser = blanktab.linkedBrowser; + is(browser.currentURI.spec, "about:addons", "about:addons should load into blank tab."); + + addBuiltinManifest(gProviders[0]); + activateOneProvider(gProviders[0], true, function() { + gBrowser.removeTab(gBrowser.selectedTab); + tabsToRemove.pop(); + // uninstall the provider + clickAddonRemoveButton(blanktab, function(addon) { + checkSocialUI(); + activateOneProvider(gProviders[0], true, function() { + + // after closing the addons tab, verify provider is still installed + gBrowser.tabContainer.addEventListener("TabClose", function onTabClose() { + gBrowser.tabContainer.removeEventListener("TabClose", onTabClose); + AddonManager.getAddonsByTypes(["service"], function(aAddons) { + is(aAddons.length, 1, "there can be only one"); + Services.prefs.clearUserPref("social.whitelist"); + next(); + }); + }); + + // verify only one provider in list + AddonManager.getAddonsByTypes(["service"], function(aAddons) { + is(aAddons.length, 1, "there can be only one"); + + let doc = blanktab.linkedBrowser.contentDocument; + let list = doc.getElementById("addon-list"); + is(list.childNodes.length, 1, "only one addon is displayed"); + + gBrowser.removeTab(blanktab); + }); + + }); + }); + }); + }, true); + } } diff --git a/browser/base/content/test/social/social_activate.html b/browser/base/content/test/social/social_activate.html index 380785fd5e7..040e1a7589d 100644 --- a/browser/base/content/test/social/social_activate.html +++ b/browser/base/content/test/social/social_activate.html @@ -12,8 +12,8 @@ var data = { "icon64URL": "chrome://branding/content/icon64.png", // at least one of these must be defined - "workerURL": "/browser/browser/base/content/test/social/social_sidebar.html", - "sidebarURL": "/browser/browser/base/content/test/social/social_worker.js", + "sidebarURL": "/browser/browser/base/content/test/social/social_sidebar.html", + "workerURL": "/browser/browser/base/content/test/social/social_worker.js", // should be available for display purposes "description": "A short paragraph about this provider", diff --git a/toolkit/components/social/SocialService.jsm b/toolkit/components/social/SocialService.jsm index 6c13840cd48..30e6f614989 100644 --- a/toolkit/components/social/SocialService.jsm +++ b/toolkit/components/social/SocialService.jsm @@ -501,7 +501,6 @@ this.SocialService = { }, installProvider: function(aDOMDocument, data, installCallback) { - let sourceURI = aDOMDocument.location.href; let installOrigin = aDOMDocument.nodePrincipal.origin; let id = getAddonIDFromOrigin(installOrigin); @@ -510,6 +509,21 @@ this.SocialService = { throw new Error("installProvider: provider with origin [" + installOrigin + "] is blocklisted"); + AddonManager.getAddonByID(id, function(aAddon) { + if (aAddon && aAddon.userDisabled) { + aAddon.cancelUninstall(); + aAddon.userDisabled = false; + } + schedule(function () { + this._installProvider(aDOMDocument, data, installCallback); + }.bind(this)); + }.bind(this)); + }, + + _installProvider: function(aDOMDocument, data, installCallback) { + let sourceURI = aDOMDocument.location.href; + let installOrigin = aDOMDocument.nodePrincipal.origin; + let installType = this.getOriginActivationType(installOrigin); let manifest; if (data) { @@ -852,6 +866,7 @@ function AddonInstaller(sourceURI, aManifest, installCallback) { aManifest.updateDate = Date.now(); // get the existing manifest for installDate let manifest = SocialServiceInternal.getManifestByOrigin(aManifest.origin); + let isNewInstall = !manifest; if (manifest && manifest.installDate) aManifest.installDate = manifest.installDate; else @@ -860,15 +875,19 @@ function AddonInstaller(sourceURI, aManifest, installCallback) { this.sourceURI = sourceURI; this.install = function() { let addon = this.addon; - AddonManagerPrivate.callInstallListeners("onExternalInstall", null, addon, null, false); - AddonManagerPrivate.callAddonListeners("onInstalling", addon, false); + if (isNewInstall) { + AddonManagerPrivate.callInstallListeners("onExternalInstall", null, addon, null, false); + AddonManagerPrivate.callAddonListeners("onInstalling", addon, false); + } let string = Cc["@mozilla.org/supports-string;1"]. createInstance(Ci.nsISupportsString); string.data = JSON.stringify(aManifest); Services.prefs.setComplexValue(getPrefnameFromOrigin(aManifest.origin), Ci.nsISupportsString, string); - AddonManagerPrivate.callAddonListeners("onInstalled", addon); + if (isNewInstall) { + AddonManagerPrivate.callAddonListeners("onInstalled", addon); + } installCallback(aManifest); }; this.cancel = function() { @@ -1104,14 +1123,6 @@ AddonWrapper.prototype = { }, cancelUninstall: function() { - let prefName = getPrefnameFromOrigin(this.manifest.origin); - if (Services.prefs.prefHasUserValue(prefName)) - throw new Error(this.manifest.name + " is not marked to be uninstalled"); - // ensure we're set into prefs - let string = Cc["@mozilla.org/supports-string;1"]. - createInstance(Ci.nsISupportsString); - string.data = JSON.stringify(this.manifest); - Services.prefs.setComplexValue(prefName, Ci.nsISupportsString, string); this._pending -= AddonManager.PENDING_UNINSTALL; AddonManagerPrivate.callAddonListeners("onOperationCancelled", this); }