bug 786133 allow install of social providers from AMO and web, r=felipe

This commit is contained in:
Shane Caraveo 2013-03-05 11:24:30 -08:00
parent 4ba30c9fe9
commit ceb799bb8e
12 changed files with 644 additions and 34 deletions

File diff suppressed because one or more lines are too long

View File

@ -111,7 +111,7 @@
<command id="Social:SharePage" oncommand="SocialShareButton.sharePage();" disabled="true"/>
<command id="Social:UnsharePage" oncommand="SocialShareButton.unsharePage();"/>
<command id="Social:ToggleSidebar" oncommand="Social.toggleSidebar();"/>
<command id="Social:ToggleNotifications" oncommand="Social.toggleNotifications();"/>
<command id="Social:ToggleNotifications" oncommand="Social.toggleNotifications();" hidden="true"/>
<command id="Social:FocusChat" oncommand="SocialChatBar.focus();" hidden="true" disabled="true"/>
<command id="Social:Toggle" oncommand="Social.toggle();" hidden="true"/>
<command id="Social:Addons" oncommand="BrowserOpenAddonsMgr('addons://list/service');"/>

View File

@ -202,19 +202,21 @@ let SocialUI = {
// This handles "ActivateSocialFeature" events fired against content documents
// in this window.
_activationEventHandler: function SocialUI_activationHandler(e) {
let targetDoc = e.target;
// Event must be fired against the document
let targetDoc;
let node;
if (e.target instanceof HTMLDocument) {
// version 0 support
targetDoc = e.target;
node = targetDoc.documentElement
} else {
targetDoc = e.target.ownerDocument;
node = e.target;
}
if (!(targetDoc instanceof HTMLDocument))
return;
// Ignore events fired in background tabs
if (targetDoc.defaultView.top != content)
return;
// Check that the associated document's origin is in our whitelist
let providerOrigin = targetDoc.nodePrincipal.origin;
if (!Social.canActivateOrigin(providerOrigin))
// Ignore events fired in background tabs or iframes
if (targetDoc.defaultView != content)
return;
// If we are in PB mode, we silently do nothing (bug 829404 exists to
@ -228,11 +230,34 @@ let SocialUI = {
return;
Social.lastEventReceived = now;
// We only want to activate if it is as a result of user input.
let dwu = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
if (!dwu.isHandlingUserInput) {
Cu.reportError("attempt to activate provider without user input from " + targetDoc.nodePrincipal.origin);
return;
}
let data = node.getAttribute("data-service");
if (data) {
try {
data = JSON.parse(data);
} catch(e) {
Cu.reportError("Social Service manifest parse error: "+e);
return;
}
}
Social.installProvider(targetDoc.location.href, data, function(manifest) {
this.doActivation(manifest.origin);
}.bind(this));
},
doActivation: function SocialUI_doActivation(origin) {
// Keep track of the old provider in case of undo
let oldOrigin = Social.provider ? Social.provider.origin : "";
// Enable the social functionality, and indicate that it was activated
Social.activateFromOrigin(providerOrigin, function(provider) {
Social.activateFromOrigin(origin, function(provider) {
// Provider to activate may not have been found
if (!provider)
return;
@ -271,6 +296,7 @@ let SocialUI = {
let oldOrigin = this.notificationPanel.getAttribute("oldorigin");
Social.deactivateFromOrigin(origin, oldOrigin);
this.notificationPanel.hidePopup();
Social.uninstallProvider(origin);
},
get notificationPanel() {
@ -365,7 +391,7 @@ let SocialChatBar = {
let command = document.getElementById("Social:FocusChat");
if (!this.isAvailable) {
this.chatbar.removeAll();
command.hidden = true;
this.chatbar.hidden = command.hidden = true;
} else {
this.chatbar.hidden = command.hidden = document.mozFullScreen;
}

View File

@ -16,6 +16,7 @@ _BROWSER_FILES = \
blocklistEmpty.xml \
browser_blocklist.js \
browser_addons.js \
browser_social_activation.js \
browser_social_perwindowPB.js \
browser_social_toolbar.js \
browser_social_shareButton.js \
@ -27,6 +28,8 @@ _BROWSER_FILES = \
browser_social_chatwindowfocus.js \
browser_social_multiprovider.js \
browser_social_errorPage.js \
social_activate.html \
social_activate_iframe.html \
social_panel.html \
social_share_image.png \
social_sidebar.html \

View File

@ -6,6 +6,7 @@ let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).So
const ADDON_TYPE_SERVICE = "service";
const ID_SUFFIX = "@services.mozilla.org";
const STRING_TYPE_NAME = "type.%ID%.name";
const XPINSTALL_URL = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
let manifest = { // normal provider
name: "provider 1",
@ -14,17 +15,55 @@ let manifest = { // normal provider
workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
iconURL: "https://example.com/browser/browser/base/content/test/moz.png"
};
let manifest2 = { // used for testing install
name: "provider 2",
origin: "https://example1.com",
sidebarURL: "https://example1.com/browser/browser/base/content/test/social/social_sidebar.html",
workerURL: "https://example1.com/browser/browser/base/content/test/social/social_worker.js",
iconURL: "https://example1.com/browser/browser/base/content/test/moz.png"
};
function test() {
waitForExplicitFinish();
Services.prefs.setCharPref("social.manifest.good", JSON.stringify(manifest));
Services.prefs.setBoolPref("social.remote-install.enabled", true);
runSocialTests(tests, undefined, undefined, function () {
Services.prefs.clearUserPref("social.remote-install.enabled");
Services.prefs.clearUserPref("social.manifest.good");
// just in case the tests failed, clear these here as well
Services.prefs.clearUserPref("social.whitelist");
Services.prefs.clearUserPref("social.directories");
finish();
});
}
function installListener(next) {
let expectEvent = "onInstalling";
return {
onInstalling: function(addon) {
is(expectEvent, "onInstalling", "install started");
is(addon.manifest.origin, manifest2.origin, "provider about to be installed");
expectEvent = "onInstalled";
},
onInstalled: function(addon) {
is(addon.manifest.origin, manifest2.origin, "provider installed");
expectEvent = "onUninstalling";
},
onUninstalling: function(addon) {
is(expectEvent, "onUninstalling", "uninstall started");
is(addon.manifest.origin, manifest2.origin, "provider about to be uninstalled");
expectEvent = "onUninstalled";
},
onUninstalled: function(addon) {
is(expectEvent, "onUninstalled", "provider has been uninstalled");
is(addon.manifest.origin, manifest2.origin, "provider uninstalled");
AddonManager.removeAddonListener(this);
next();
}
};
}
var tests = {
testInstalledProviders: function(next) {
// tests that our builtin manfests are actually available to the addon
@ -129,5 +168,69 @@ var tests = {
next();
});
});
},
testForeignInstall: function(next) {
AddonManager.addAddonListener(installListener(next));
// we expect the addon install dialog to appear, we need to accept the
// install from the dialog.
let windowListener = {
onWindowTitleChange: function() {},
onCloseWindow: function() {},
onOpenWindow: function(window) {
var domwindow = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindow);
var self = this;
waitForFocus(function() {
self.windowReady(domwindow);
}, domwindow);
},
windowReady: function(window) {
if (window.document.location.href == XPINSTALL_URL) {
// Initially the accept button is disabled on a countdown timer
var button = window.document.documentElement.getButton("accept");
button.disabled = false;
window.document.documentElement.acceptDialog();
Services.wm.removeListener(windowListener);
}
}
};
Services.wm.addListener(windowListener);
let installFrom = "https://example1.com";
Services.prefs.setCharPref("social.whitelist", "");
is(SocialService.getOriginActivationType(installFrom), "foreign", "testing foriegn install");
Social.installProvider(installFrom, manifest2, function(addonManifest) {
Services.prefs.clearUserPref("social.whitelist");
SocialService.addBuiltinProvider(addonManifest.origin, function(provider) {
Social.uninstallProvider(addonManifest.origin);
});
});
},
testWhitelistInstall: function(next) {
AddonManager.addAddonListener(installListener(next));
let installFrom = "https://example1.com";
Services.prefs.setCharPref("social.whitelist", installFrom);
is(SocialService.getOriginActivationType(installFrom), "whitelist", "testing whitelist install");
Social.installProvider(installFrom, manifest2, function(addonManifest) {
Services.prefs.clearUserPref("social.whitelist");
SocialService.addBuiltinProvider(addonManifest.origin, function(provider) {
Social.uninstallProvider(addonManifest.origin);
});
});
},
testDirectoryInstall: function(next) {
AddonManager.addAddonListener(installListener(next));
let installFrom = "https://addons.allizom.org";
Services.prefs.setCharPref("social.directories", installFrom);
is(SocialService.getOriginActivationType(installFrom), "directory", "testing directory install");
Social.installProvider(installFrom, manifest2, function(addonManifest) {
Services.prefs.clearUserPref("social.directories");
SocialService.addBuiltinProvider(addonManifest.origin, function(provider) {
Social.uninstallProvider(addonManifest.origin);
});
});
}
}

View File

@ -96,6 +96,28 @@ var tests = {
}
});
},
testInstallingBlockedProvider: function(next) {
function finish(good) {
ok(good, "Unable to add blocklisted provider");
Services.prefs.clearUserPref("social.whitelist");
setAndUpdateBlocklist(blocklistEmpty, next);
}
let installFrom = "https://bad.com";
// whitelist to avoid the 3rd party install dialog, we only want to test
// the blocklist inside installProvider.
Services.prefs.setCharPref("social.whitelist", installFrom);
setAndUpdateBlocklist(blocklistURL, function() {
try {
// expecting an exception when attempting to install a hard blocked
// provider
Social.installProvider(installFrom, manifest_bad, function(addonManifest) {
finish(false);
});
} catch(e) {
finish(true);
}
});
},
testBlockingExistingProvider: function(next) {
addWindowListener(URI_EXTENSION_BLOCKLIST_DIALOG, function(win) {

View File

@ -0,0 +1,217 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
let tabsToRemove = [];
function postTestCleanup(callback) {
Social.provider = null;
// any tabs opened by the test.
for (let tab of tabsToRemove)
gBrowser.removeTab(tab);
tabsToRemove = [];
// theses tests use the notification panel but don't bother waiting for it
// to fully open - the end result is that the panel might stay open
SocialUI.notificationPanel.hidePopup();
Services.prefs.clearUserPref("social.whitelist");
// all providers may have had their manifests added.
for (let manifest of gProviders)
Services.prefs.clearUserPref("social.manifest." + manifest.origin);
// all the providers may have been added.
let numRemoved = 0;
let cbRemoved = function() {
if (++numRemoved == gProviders.length) {
executeSoon(function() {
is(Social.providers.length, 0, "all providers removed");
callback();
});
}
}
for (let [i, manifest] of Iterator(gProviders)) {
try {
SocialService.removeProvider(manifest.origin, cbRemoved);
} catch(ex) {
// this test didn't add this provider - that's ok.
cbRemoved();
}
}
}
function addBuiltinManifest(manifest) {
Services.prefs.setCharPref("social.manifest." + manifest.origin, JSON.stringify(manifest));
}
function addTab(url, callback) {
let tab = gBrowser.selectedTab = gBrowser.addTab(url, {skipAnimation: true});
tab.linkedBrowser.addEventListener("load", function tabLoad(event) {
tab.linkedBrowser.removeEventListener("load", tabLoad, true);
tabsToRemove.push(tab);
executeSoon(function() {callback(tab)});
}, true);
}
function sendActivationEvent(tab, callback) {
// hack Social.lastEventReceived so we don't hit the "too many events" check.
Social.lastEventReceived = 0;
let doc = tab.linkedBrowser.contentDocument;
// if our test has a frame, use it
if (doc.defaultView.frames[0])
doc = doc.defaultView.frames[0].document;
let button = doc.getElementById("activation");
EventUtils.synthesizeMouseAtCenter(button, {}, doc.defaultView);
executeSoon(callback);
}
function activateProvider(domain, callback) {
let activationURL = domain+"/browser/browser/base/content/test/social/social_activate.html"
addTab(activationURL, function(tab) {
sendActivationEvent(tab, callback);
});
}
function activateIFrameProvider(domain, callback) {
let activationURL = domain+"/browser/browser/base/content/test/social/social_activate_iframe.html"
addTab(activationURL, function(tab) {
sendActivationEvent(tab, callback);
});
}
let gTestDomains = ["https://example.com", "https://test1.example.com", "https://test2.example.com"];
let gProviders = [
{
name: "provider 1",
origin: "https://example.com",
sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html?provider1",
workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js#no-profile,no-recommend",
iconURL: "chrome://branding/content/icon48.png"
},
{
name: "provider 2",
origin: "https://test1.example.com",
sidebarURL: "https://test1.example.com/browser/browser/base/content/test/social/social_sidebar.html?provider2",
workerURL: "https://test1.example.com/browser/browser/base/content/test/social/social_worker.js#no-profile,no-recommend",
iconURL: "chrome://branding/content/icon64.png"
},
{
name: "provider 3",
origin: "https://test2.example.com",
sidebarURL: "https://test2.example.com/browser/browser/base/content/test/social/social_sidebar.html?provider2",
workerURL: "https://test2.example.com/browser/browser/base/content/test/social/social_worker.js#no-profile,no-recommend",
iconURL: "chrome://branding/content/about-logo.png"
}
];
function test() {
waitForExplicitFinish();
runSocialTests(tests, undefined, postTestCleanup);
}
var tests = {
testActivationWrongOrigin: function(next) {
// At this stage none of our providers exist, so we expect failure.
Services.prefs.setBoolPref("social.remote-install.enabled", false);
activateProvider(gTestDomains[0], function() {
is(SocialUI.enabled, false, "SocialUI is not enabled");
ok(SocialUI.notificationPanel.hidden, "activation panel still hidden");
checkSocialUI();
Services.prefs.clearUserPref("social.remote-install.enabled");
next();
});
},
testIFrameActivation: function(next) {
Services.prefs.setCharPref("social.whitelist", gTestDomains.join(","));
activateIFrameProvider(gTestDomains[0], function() {
is(SocialUI.enabled, false, "SocialUI is not enabled");
ok(!Social.provider, "provider is not installed");
ok(SocialUI.notificationPanel.hidden, "activation panel still hidden");
checkSocialUI();
Services.prefs.clearUserPref("social.whitelist");
next();
});
},
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.notificationPanel.hidden, "activation panel should be showing");
is(Social.provider.origin, gTestDomains[0], "new provider is active");
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();
})
});
},
testActivationMultipleProvider: function(next) {
// The trick with this test is to make sure that Social.providers[1] is
// the current provider when doing the undo - this makes sure that the
// Social code doesn't fallback to Social.providers[0], which it will
// do in some cases (but those cases do not include what this test does)
// first enable the 2 providers
Services.prefs.setCharPref("social.whitelist", gTestDomains.join(","));
SocialService.addProvider(gProviders[0], function() {
SocialService.addProvider(gProviders[1], function() {
Social.provider = Social.providers[1];
checkSocialUI();
// activate the last provider.
addBuiltinManifest(gProviders[2]);
activateProvider(gTestDomains[2], function() {
ok(!SocialUI.notificationPanel.hidden, "activation panel should be showing");
is(Social.provider.origin, gTestDomains[2], "new provider is active");
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();
});
});
});
});
},
testRemoveNonCurrentProvider: function(next) {
Services.prefs.setCharPref("social.whitelist", gTestDomains.join(","));
SocialService.addProvider(gProviders[0], function() {
SocialService.addProvider(gProviders[1], function() {
Social.provider = Social.providers[1];
checkSocialUI();
// activate the last provider.
addBuiltinManifest(gProviders[2]);
activateProvider(gTestDomains[2], function() {
ok(!SocialUI.notificationPanel.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");
checkSocialUI();
Services.prefs.clearUserPref("social.whitelist");
next();
});
});
});
});
},
}

View File

@ -81,7 +81,7 @@ function runSocialTestWithProvider(manifest, callback) {
SocialService.removeProvider(origin, cb);
} catch (ex) {
// Ignore "provider doesn't exist" errors.
if (ex.message == "SocialService.removeProvider: no provider with this origin exists!")
if (ex.message.indexOf("SocialService.removeProvider: no provider with origin") == 0)
return;
info("Failed to clean up provider " + origin + ": " + ex);
}
@ -181,7 +181,7 @@ function checkSocialUI(win) {
isbool(win.SocialSidebar.opened, enabled, "social sidebar open?");
isbool(win.SocialChatBar.isAvailable, enabled && Social.haveLoggedInUser(), "chatbar available?");
isbool(!win.SocialChatBar.chatbar.hidden, enabled && Social.haveLoggedInUser(), "chatbar visible?");
isbool(!win.SocialShareButton.shareButton.hidden, enabled && provider.recommendInfo, "share button visible?");
isbool(!win.SocialShareButton.shareButton.hidden, enabled && Social.haveLoggedInUser() && provider.recommendInfo, "share button visible?");
isbool(!doc.getElementById("social-toolbar-item").hidden, enabled, "toolbar items visible?");
if (enabled)
is(win.SocialToolbar.button.style.listStyleImage, 'url("' + provider.iconURL + '")', "toolbar button has provider icon");
@ -191,7 +191,7 @@ function checkSocialUI(win) {
isbool(!doc.getElementById("Social:ToggleNotifications").hidden, enabled, "Social:ToggleNotifications visible?");
isbool(!doc.getElementById("Social:FocusChat").hidden, enabled && Social.haveLoggedInUser(), "Social:FocusChat visible?");
isbool(doc.getElementById("Social:FocusChat").getAttribute("disabled"), enabled ? "false" : "true", "Social:FocusChat disabled?");
is(doc.getElementById("Social:SharePage").getAttribute("disabled"), enabled && provider.recommendInfo ? "false" : "true", "Social:SharePage visible?");
is(doc.getElementById("Social:SharePage").getAttribute("disabled"), enabled && Social.haveLoggedInUser() && provider.recommendInfo ? "false" : "true", "Social:SharePage visible?");
// broadcasters.
isbool(!doc.getElementById("socialActiveBroadcaster").hidden, enabled, "socialActiveBroadcaster hidden?");

View File

@ -0,0 +1,39 @@
<html>
<head>
<title>Activation test</title>
</head>
<script>
// icons from http://findicons.com/icon/158311/firefox?id=356182 by ipapun
var data = {
// currently required
"name": "Demo Social Service",
"iconURL": "chrome://branding/content/icon16.png",
"icon32URL": "chrome://branding/content/favicon32.png",
"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",
// should be available for display purposes
"description": "A short paragraph about this provider",
"author": "Shane Caraveo, Mozilla",
// optional
"version": "1.0"
}
function activate(node) {
node.setAttribute("data-service", JSON.stringify(data));
var event = new CustomEvent("ActivateSocialFeature");
node.dispatchEvent(event);
}
</script>
<body>
nothing to see here
<button id="activation" onclick="activate(this)">Activate The Demo Provider</button>
</body>
</html>

View File

@ -0,0 +1,11 @@
<html>
<head>
<title>Activation iframe test</title>
</head>
<body>
<iframe src="social_activate.html"/>
</body>
</html>

View File

@ -173,6 +173,14 @@ this.Social = {
return null;
},
installProvider: function(origin ,sourceURI, data, installCallback) {
SocialService.installProvider(origin ,sourceURI, data, installCallback);
},
uninstallProvider: function(origin) {
SocialService.uninstallProvider(origin);
},
// Activation functionality
activateFromOrigin: function (origin, callback) {
// For now only "builtin" providers can be activated. It's OK if the
@ -200,10 +208,6 @@ this.Social = {
SocialService.removeProvider(origin);
},
canActivateOrigin: function (origin) {
return SocialService.canActivateOrigin(origin);
},
// Sharing functionality
_getShareablePageUrl: function Social_getShareablePageUrl(aURI) {
let uri = aURI.clone();

View File

@ -20,6 +20,10 @@ XPCOMUtils.defineLazyModuleGetter(this, "WorkerAPI", "resource://gre/modules/Wor
XPCOMUtils.defineLazyModuleGetter(this, "MozSocialAPI", "resource://gre/modules/MozSocialAPI.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask", "resource://gre/modules/DeferredTask.jsm");
XPCOMUtils.defineLazyServiceGetter(this, 'bs',
"@mozilla.org/extensions/blocklist;1",
"nsIBlocklistService");
/**
* The SocialService is the public API to social providers - it tracks which
* providers are installed and enabled, and is the entry-point for access to
@ -54,6 +58,25 @@ let SocialServiceInternal = {
}
}
return null;
},
getManifestPrefname: function(origin) {
// Retrieve the prefname for a given origin/manifest.
// If no existing pref, return a generated prefname.
let MANIFEST_PREFS = Services.prefs.getBranch("social.manifest.");
let prefs = MANIFEST_PREFS.getChildList("", []);
for (let pref of prefs) {
try {
var manifest = JSON.parse(MANIFEST_PREFS.getCharPref(pref));
if (manifest.origin == origin) {
return pref;
}
} catch (err) {
Cu.reportError("SocialService: failed to load manifest: " + pref +
", exception: " + err);
}
}
let originUri = Services.io.newURI(origin, null, null);
return originUri.hostPort.replace('.','-');
}
};
@ -271,12 +294,21 @@ this.SocialService = {
});
},
canActivateOrigin: function canActivateOrigin(origin) {
getOriginActivationType: function(origin) {
for (let manifest in SocialServiceInternal.manifests) {
if (manifest.origin == origin)
return true;
return 'builtin';
}
return false;
let whitelist = Services.prefs.getCharPref("social.whitelist").split(',');
if (whitelist.indexOf(origin) >= 0)
return 'whitelist';
let directories = Services.prefs.getCharPref("social.directories").split(',');
if (directories.indexOf(origin) >= 0)
return 'directory';
return 'foreign';
},
_providerListeners: new Map(),
@ -295,6 +327,110 @@ this.SocialService = {
Components.utils.reportError("SocialService: provider listener threw an exception: " + ex);
}
}
},
_manifestFromData: function(type, data, principal) {
let sameOriginRequired = ['workerURL', 'sidebarURL'];
if (type == 'directory') {
// directory provided manifests must have origin in manifest, use that
if (!data['origin']) {
Cu.reportError("SocialService.manifestFromData directory service provided manifest without origin.");
return null;
}
let URI = Services.io.newURI(data.origin, null, null);
principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(URI);
}
// force/fixup origin
data.origin = principal.origin;
// workerURL, sidebarURL is required and must be same-origin
// iconURL and name are required
// iconURL may be a different origin (CDN or data url support) if this is
// a whitelisted or directory listed provider
if (!data['workerURL'] || !data['sidebarURL']) {
Cu.reportError("SocialService.manifestFromData manifest missing required workerURL or sidebarURL.");
return null;
}
if (!data['name'] || !data['iconURL']) {
Cu.reportError("SocialService.manifestFromData manifest missing name or iconURL.");
return null;
}
for (let url of sameOriginRequired) {
if (data[url]) {
try {
data[url] = Services.io.newURI(principal.URI.resolve(data[url]), null, null).spec;
} catch(e) {
Cu.reportError("SocialService.manifestFromData same-origin missmatch in manifest for " + principal.origin);
return null;
}
}
}
return data;
},
installProvider: function(sourceURI, data, installCallback) {
let URI = Services.io.newURI(sourceURI, null, null);
let principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(URI);
let installOrigin = principal.origin;
let id = getAddonIDFromOrigin(installOrigin);
if (bs.getAddonBlocklistState(id, data.version || "0") == Ci.nsIBlocklistService.STATE_BLOCKED)
throw new Error("installProvider: provider with origin [" +
installOrigin + "] is blocklisted");
let installType = this.getOriginActivationType(installOrigin);
let manifest;
if (data) {
// if we get data, we MUST have a valid manifest generated from the data
manifest = this._manifestFromData(installType, data, principal);
if (!manifest)
throw new Error("SocialService.installProvider: service configuration is invalid from " + sourceURI);
}
switch(installType) {
case "foreign":
if (!Services.prefs.getBoolPref("social.remote-install.enabled"))
throw new Error("Remote install of services is disabled");
if (!manifest)
throw new Error("Cannot install provider without manifest data");
let args = {};
args.url = this.url;
args.installs = [new AddonInstaller(sourceURI, manifest, installCallback)];
args.wrappedJSObject = args;
// Bug 836452, get something better than the scary addon dialog
Services.ww.openWindow(this.window, "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul",
null, "chrome,modal,centerscreen", args);
break;
case "builtin":
// for builtin, we already have a manifest, but it can be overridden
// we need to return the manifest in the installcallback, so fetch
// it if we have it. If there is no manifest data for the builtin,
// the install request MUST be from the provider, otherwise we have
// no way to know what provider we're trying to enable. This is
// primarily an issue for "version zero" providers that did not
// send the manifest with the dom event for activation.
if (!manifest)
manifest = SocialServiceInternal.getManifestByOrigin(installOrigin);
case "directory":
// a manifest is requried, and will have been vetted by reviewers
case "whitelist":
// a manifest is required, we'll catch a missing manifest below.
if (!manifest)
throw new Error("Cannot install provider without manifest data");
let installer = new AddonInstaller(sourceURI, manifest, installCallback);
installer.install();
break;
default:
throw new Error("SocialService.installProvider: Invalid install type "+installType+"\n");
break;
}
},
uninstallProvider: function(origin) {
let manifest = SocialServiceInternal.getManifestByOrigin(origin);
let addon = new AddonWrapper(manifest);
addon.uninstall();
}
};
@ -312,8 +448,6 @@ function SocialProvider(input) {
throw new Error("SocialProvider must be passed an origin");
let id = getAddonIDFromOrigin(input.origin);
let bs = Cc["@mozilla.org/extensions/blocklist;1"].
getService(Ci.nsIBlocklistService);
if (bs.getAddonBlocklistState(id, input.version || "0") == Ci.nsIBlocklistService.STATE_BLOCKED)
throw new Error("SocialProvider: provider with origin [" +
input.origin + "] is blocklisted");
@ -576,14 +710,32 @@ function getAddonIDFromOrigin(origin) {
return originUri.host + ID_SUFFIX;
}
function getPrefnameFromOrigin(origin) {
return "social.manifest." + SocialServiceInternal.getManifestPrefname(origin);
}
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);
Services.prefs.setCharPref(getPrefnameFromOrigin(aManifest.origin), JSON.stringify(aManifest));
AddonManagerPrivate.callAddonListeners("onInstalled", addon);
installCallback(aManifest);
};
this.cancel = function() {
Services.prefs.clearUserPref(getPrefnameFromOrigin(aManifest.origin))
},
this.addon = new AddonWrapper(aManifest);
};
var SocialAddonProvider = {
startup: function() {},
shutdown: function() {},
updateAddonAppDisabledStates: function() {
let bs = Cc["@mozilla.org/extensions/blocklist;1"].
getService(Ci.nsIBlocklistService);
// we wont bother with "enabling" services that are released from blocklist
for (let manifest of SocialServiceInternal.manifests) {
try {
@ -615,6 +767,14 @@ var SocialAddonProvider = {
return;
}
aCallback([new AddonWrapper(a) for each (a in SocialServiceInternal.manifests)]);
},
removeAddon: function(aAddon) {
AddonManagerPrivate.callAddonListeners("onUninstalling", aAddon, false);
aAddon.pendingOperations |= AddonManager.PENDING_UNINSTALL;
Services.prefs.clearUserPref(getPrefnameFromOrigin(aAddon.manifest.origin));
aAddon.pendingOperations -= AddonManager.PENDING_UNINSTALL;
AddonManagerPrivate.callAddonListeners("onUninstalled", aAddon);
}
}
@ -666,14 +826,10 @@ AddonWrapper.prototype = {
},
get blocklistState() {
let bs = Cc["@mozilla.org/extensions/blocklist;1"].
getService(Ci.nsIBlocklistService);
return bs.getAddonBlocklistState(this.id, this.version || "0");
},
get blocklistURL() {
let bs = Cc["@mozilla.org/extensions/blocklist;1"].
getService(Ci.nsIBlocklistService);
return bs.getAddonBlocklistURL(this.id, this.version || "0");
},
@ -698,7 +854,9 @@ AddonWrapper.prototype = {
get permissions() {
let permissions = 0;
// XXX we will not have install until BUG 786133 lands
// any "user defined" manifest can be removed
if (Services.prefs.prefHasUserValue(getPrefnameFromOrigin(this.manifest.origin)))
permissions = AddonManager.PERM_CAN_UNINSTALL;
if (!this.appDisabled) {
if (this.userDisabled) {
permissions |= AddonManager.PERM_CAN_ENABLE;
@ -786,11 +944,26 @@ AddonWrapper.prototype = {
},
uninstall: function() {
// XXX we will not uninstall until BUG 786133 lands
let prefName = getPrefnameFromOrigin(this.manifest.origin);
if (Services.prefs.prefHasUserValue(prefName)) {
if (ActiveProviders.has(this.manifest.origin)) {
SocialService.removeProvider(this.manifest.origin, function() {
SocialAddonProvider.removeAddon(this);
}.bind(this));
} else {
SocialAddonProvider.removeAddon(this);
}
}
},
cancelUninstall: function() {
// XXX we will not uninstall until BUG 786133 lands
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
Services.prefs.setCharPref(prefName, JSON.stringify(this.manifest));
this._pending -= AddonManager.PENDING_UNINSTALL;
AddonManagerPrivate.callAddonListeners("onOperationCancelled", this);
}
};