michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: this.EXPORTED_SYMBOLS = ["SocialService"]; michael@0: michael@0: const { classes: Cc, interfaces: Ci, utils: Cu } = Components; michael@0: michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/AddonManager.jsm"); michael@0: Cu.import("resource://gre/modules/PlacesUtils.jsm"); michael@0: michael@0: const URI_EXTENSION_STRINGS = "chrome://mozapps/locale/extensions/extensions.properties"; michael@0: const ADDON_TYPE_SERVICE = "service"; michael@0: const ID_SUFFIX = "@services.mozilla.org"; michael@0: const STRING_TYPE_NAME = "type.%ID%.name"; michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "getFrameWorkerHandle", "resource://gre/modules/FrameWorker.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "WorkerAPI", "resource://gre/modules/WorkerAPI.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "MozSocialAPI", "resource://gre/modules/MozSocialAPI.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "closeAllChatWindows", "resource://gre/modules/MozSocialAPI.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask", "resource://gre/modules/DeferredTask.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "etld", michael@0: "@mozilla.org/network/effective-tld-service;1", michael@0: "nsIEffectiveTLDService"); michael@0: michael@0: /** michael@0: * The SocialService is the public API to social providers - it tracks which michael@0: * providers are installed and enabled, and is the entry-point for access to michael@0: * the provider itself. michael@0: */ michael@0: michael@0: // Internal helper methods and state michael@0: let SocialServiceInternal = { michael@0: get enabled() this.providerArray.length > 0, michael@0: michael@0: get providerArray() { michael@0: return [p for ([, p] of Iterator(this.providers))]; michael@0: }, michael@0: get manifests() { michael@0: // Retrieve the manifests of installed providers from prefs michael@0: let MANIFEST_PREFS = Services.prefs.getBranch("social.manifest."); michael@0: let prefs = MANIFEST_PREFS.getChildList("", []); michael@0: for (let pref of prefs) { michael@0: // we only consider manifests in user level prefs to be *installed* michael@0: if (!MANIFEST_PREFS.prefHasUserValue(pref)) michael@0: continue; michael@0: try { michael@0: var manifest = JSON.parse(MANIFEST_PREFS.getComplexValue(pref, Ci.nsISupportsString).data); michael@0: if (manifest && typeof(manifest) == "object" && manifest.origin) michael@0: yield manifest; michael@0: } catch (err) { michael@0: Cu.reportError("SocialService: failed to load manifest: " + pref + michael@0: ", exception: " + err); michael@0: } michael@0: } michael@0: }, michael@0: getManifestPrefname: function(origin) { michael@0: // Retrieve the prefname for a given origin/manifest. michael@0: // If no existing pref, return a generated prefname. michael@0: let MANIFEST_PREFS = Services.prefs.getBranch("social.manifest."); michael@0: let prefs = MANIFEST_PREFS.getChildList("", []); michael@0: for (let pref of prefs) { michael@0: try { michael@0: var manifest = JSON.parse(MANIFEST_PREFS.getComplexValue(pref, Ci.nsISupportsString).data); michael@0: if (manifest.origin == origin) { michael@0: return pref; michael@0: } michael@0: } catch (err) { michael@0: Cu.reportError("SocialService: failed to load manifest: " + pref + michael@0: ", exception: " + err); michael@0: } michael@0: } michael@0: let originUri = Services.io.newURI(origin, null, null); michael@0: return originUri.hostPort.replace('.','-'); michael@0: }, michael@0: orderedProviders: function(aCallback) { michael@0: if (SocialServiceInternal.providerArray.length < 2) { michael@0: schedule(function () { michael@0: aCallback(SocialServiceInternal.providerArray); michael@0: }); michael@0: return; michael@0: } michael@0: // query moz_hosts for frecency. since some providers may not have a michael@0: // frecency entry, we need to later sort on our own. We use the providers michael@0: // object below as an easy way to later record the frecency on the provider michael@0: // object from the query results. michael@0: let hosts = []; michael@0: let providers = {}; michael@0: michael@0: for (let p of SocialServiceInternal.providerArray) { michael@0: p.frecency = 0; michael@0: providers[p.domain] = p; michael@0: hosts.push(p.domain); michael@0: }; michael@0: michael@0: // cannot bind an array to stmt.params so we have to build the string michael@0: let stmt = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase) michael@0: .DBConnection.createAsyncStatement( michael@0: "SELECT host, frecency FROM moz_hosts WHERE host IN (" + michael@0: [ '"' + host + '"' for each (host in hosts) ].join(",") + ") " michael@0: ); michael@0: michael@0: try { michael@0: stmt.executeAsync({ michael@0: handleResult: function(aResultSet) { michael@0: let row; michael@0: while ((row = aResultSet.getNextRow())) { michael@0: let rh = row.getResultByName("host"); michael@0: let frecency = row.getResultByName("frecency"); michael@0: providers[rh].frecency = parseInt(frecency) || 0; michael@0: } michael@0: }, michael@0: handleError: function(aError) { michael@0: Cu.reportError(aError.message + " (Result = " + aError.result + ")"); michael@0: }, michael@0: handleCompletion: function(aReason) { michael@0: // the query may not have returned all our providers, so we have michael@0: // stamped the frecency on the provider and sort here. This makes sure michael@0: // all enabled providers get sorted even with frecency zero. michael@0: let providerList = SocialServiceInternal.providerArray; michael@0: // reverse sort michael@0: aCallback(providerList.sort(function(a, b) b.frecency - a.frecency)); michael@0: } michael@0: }); michael@0: } finally { michael@0: stmt.finalize(); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: XPCOMUtils.defineLazyGetter(SocialServiceInternal, "providers", function () { michael@0: initService(); michael@0: let providers = {}; michael@0: for (let manifest of this.manifests) { michael@0: try { michael@0: if (ActiveProviders.has(manifest.origin)) { michael@0: // enable the api when a provider is enabled michael@0: MozSocialAPI.enabled = true; michael@0: let provider = new SocialProvider(manifest); michael@0: providers[provider.origin] = provider; michael@0: } michael@0: } catch (err) { michael@0: Cu.reportError("SocialService: failed to load provider: " + manifest.origin + michael@0: ", exception: " + err); michael@0: } michael@0: } michael@0: return providers; michael@0: }); michael@0: michael@0: function getOriginActivationType(origin) { michael@0: let prefname = SocialServiceInternal.getManifestPrefname(origin); michael@0: if (Services.prefs.getDefaultBranch("social.manifest.").getPrefType(prefname) == Services.prefs.PREF_STRING) michael@0: return 'builtin'; michael@0: michael@0: let whitelist = Services.prefs.getCharPref("social.whitelist").split(','); michael@0: if (whitelist.indexOf(origin) >= 0) michael@0: return 'whitelist'; michael@0: michael@0: let directories = Services.prefs.getCharPref("social.directories").split(','); michael@0: if (directories.indexOf(origin) >= 0) michael@0: return 'directory'; michael@0: michael@0: return 'foreign'; michael@0: } michael@0: michael@0: let ActiveProviders = { michael@0: get _providers() { michael@0: delete this._providers; michael@0: this._providers = {}; michael@0: try { michael@0: let pref = Services.prefs.getComplexValue("social.activeProviders", michael@0: Ci.nsISupportsString); michael@0: this._providers = JSON.parse(pref); michael@0: } catch(ex) {} michael@0: return this._providers; michael@0: }, michael@0: michael@0: has: function (origin) { michael@0: return (origin in this._providers); michael@0: }, michael@0: michael@0: add: function (origin) { michael@0: this._providers[origin] = 1; michael@0: this._deferredTask.arm(); michael@0: }, michael@0: michael@0: delete: function (origin) { michael@0: delete this._providers[origin]; michael@0: this._deferredTask.arm(); michael@0: }, michael@0: michael@0: flush: function () { michael@0: this._deferredTask.disarm(); michael@0: this._persist(); michael@0: }, michael@0: michael@0: get _deferredTask() { michael@0: delete this._deferredTask; michael@0: return this._deferredTask = new DeferredTask(this._persist.bind(this), 0); michael@0: }, michael@0: michael@0: _persist: function () { michael@0: let string = Cc["@mozilla.org/supports-string;1"]. michael@0: createInstance(Ci.nsISupportsString); michael@0: string.data = JSON.stringify(this._providers); michael@0: Services.prefs.setComplexValue("social.activeProviders", michael@0: Ci.nsISupportsString, string); michael@0: } michael@0: }; michael@0: michael@0: function migrateSettings() { michael@0: let activeProviders, enabled; michael@0: try { michael@0: activeProviders = Services.prefs.getCharPref("social.activeProviders"); michael@0: } catch(e) { michael@0: // not set, we'll check if we need to migrate older prefs michael@0: } michael@0: if (Services.prefs.prefHasUserValue("social.enabled")) { michael@0: enabled = Services.prefs.getBoolPref("social.enabled"); michael@0: } michael@0: if (activeProviders) { michael@0: // migration from fx21 to fx22 or later michael@0: // ensure any *builtin* provider in activeproviders is in user level prefs michael@0: for (let origin in ActiveProviders._providers) { michael@0: let prefname; michael@0: let manifest; michael@0: let defaultManifest; michael@0: try { michael@0: prefname = getPrefnameFromOrigin(origin); michael@0: manifest = JSON.parse(Services.prefs.getComplexValue(prefname, Ci.nsISupportsString).data); michael@0: } catch(e) { michael@0: // Our preference is missing or bad, remove from ActiveProviders and michael@0: // continue. This is primarily an error-case and should only be michael@0: // reached by either messing with preferences or hitting the one or michael@0: // two days of nightly that ran into it, so we'll flush right away. michael@0: ActiveProviders.delete(origin); michael@0: ActiveProviders.flush(); michael@0: continue; michael@0: } michael@0: let needsUpdate = !manifest.updateDate; michael@0: // fx23 may have built-ins with shareURL michael@0: try { michael@0: defaultManifest = Services.prefs.getDefaultBranch(null) michael@0: .getComplexValue(prefname, Ci.nsISupportsString).data; michael@0: defaultManifest = JSON.parse(defaultManifest); michael@0: } catch(e) { michael@0: // not a built-in, continue michael@0: } michael@0: if (defaultManifest) { michael@0: if (defaultManifest.shareURL && !manifest.shareURL) { michael@0: manifest.shareURL = defaultManifest.shareURL; michael@0: needsUpdate = true; michael@0: } michael@0: if (defaultManifest.version && (!manifest.version || defaultManifest.version > manifest.version)) { michael@0: manifest = defaultManifest; michael@0: needsUpdate = true; michael@0: } michael@0: } michael@0: if (needsUpdate) { michael@0: // the provider was installed with an older build, so we will update the michael@0: // timestamp and ensure the manifest is in user prefs michael@0: delete manifest.builtin; michael@0: // we're potentially updating for share, so always mark the updateDate michael@0: manifest.updateDate = Date.now(); michael@0: if (!manifest.installDate) michael@0: manifest.installDate = 0; // we don't know when it was installed michael@0: michael@0: let string = Cc["@mozilla.org/supports-string;1"]. michael@0: createInstance(Ci.nsISupportsString); michael@0: string.data = JSON.stringify(manifest); michael@0: Services.prefs.setComplexValue(prefname, Ci.nsISupportsString, string); michael@0: } michael@0: // as of fx 29, we no longer rely on social.enabled. migration from prior michael@0: // versions should disable all service addons if social.enabled=false michael@0: if (enabled === false) { michael@0: ActiveProviders.delete(origin); michael@0: } michael@0: } michael@0: ActiveProviders.flush(); michael@0: Services.prefs.clearUserPref("social.enabled"); michael@0: return; michael@0: } michael@0: michael@0: // primary migration from pre-fx21 michael@0: let active; michael@0: try { michael@0: active = Services.prefs.getBoolPref("social.active"); michael@0: } catch(e) {} michael@0: if (!active) michael@0: return; michael@0: michael@0: // primary difference from SocialServiceInternal.manifests is that we michael@0: // only read the default branch here. michael@0: let manifestPrefs = Services.prefs.getDefaultBranch("social.manifest."); michael@0: let prefs = manifestPrefs.getChildList("", []); michael@0: for (let pref of prefs) { michael@0: try { michael@0: let manifest; michael@0: try { michael@0: manifest = JSON.parse(manifestPrefs.getComplexValue(pref, Ci.nsISupportsString).data); michael@0: } catch(e) { michael@0: // bad or missing preference, we wont update this one. michael@0: continue; michael@0: } michael@0: if (manifest && typeof(manifest) == "object" && manifest.origin) { michael@0: // our default manifests have been updated with the builtin flags as of michael@0: // fx22, delete it so we can set the user-pref michael@0: delete manifest.builtin; michael@0: if (!manifest.updateDate) { michael@0: manifest.updateDate = Date.now(); michael@0: manifest.installDate = 0; // we don't know when it was installed michael@0: } michael@0: michael@0: let string = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString); michael@0: string.data = JSON.stringify(manifest); michael@0: // pref here is just the branch name, set the full pref name michael@0: Services.prefs.setComplexValue("social.manifest." + pref, Ci.nsISupportsString, string); michael@0: ActiveProviders.add(manifest.origin); michael@0: ActiveProviders.flush(); michael@0: // social.active was used at a time that there was only one michael@0: // builtin, we'll assume that is still the case michael@0: return; michael@0: } michael@0: } catch (err) { michael@0: Cu.reportError("SocialService: failed to load manifest: " + pref + ", exception: " + err); michael@0: } michael@0: } michael@0: } michael@0: michael@0: function initService() { michael@0: Services.obs.addObserver(function xpcomShutdown() { michael@0: ActiveProviders.flush(); michael@0: SocialService._providerListeners = null; michael@0: Services.obs.removeObserver(xpcomShutdown, "xpcom-shutdown"); michael@0: }, "xpcom-shutdown", false); michael@0: michael@0: try { michael@0: migrateSettings(); michael@0: } catch(e) { michael@0: // no matter what, if migration fails we do not want to render social michael@0: // unusable. Worst case scenario is that, when upgrading Firefox, previously michael@0: // enabled providers are not migrated. michael@0: Cu.reportError("Error migrating social settings: " + e); michael@0: } michael@0: } michael@0: michael@0: function schedule(callback) { michael@0: Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL); michael@0: } michael@0: michael@0: // Public API michael@0: this.SocialService = { michael@0: get hasEnabledProviders() { michael@0: // used as an optimization during startup, can be used to check if further michael@0: // initialization should be done (e.g. creating the instances of michael@0: // SocialProvider and turning on UI). ActiveProviders may have changed and michael@0: // not yet flushed so we check the active providers array michael@0: for (let p in ActiveProviders._providers) { michael@0: return true; michael@0: }; michael@0: return false; michael@0: }, michael@0: get enabled() { michael@0: return SocialServiceInternal.enabled; michael@0: }, michael@0: set enabled(val) { michael@0: throw new Error("not allowed to set SocialService.enabled"); michael@0: }, michael@0: michael@0: // Adds and activates a builtin provider. The provider may or may not have michael@0: // previously been added. onDone is always called - with null if no such michael@0: // provider exists, or the activated provider on success. michael@0: addBuiltinProvider: function addBuiltinProvider(origin, onDone) { michael@0: if (SocialServiceInternal.providers[origin]) { michael@0: schedule(function() { michael@0: onDone(SocialServiceInternal.providers[origin]); michael@0: }); michael@0: return; michael@0: } michael@0: let manifest = SocialService.getManifestByOrigin(origin); michael@0: if (manifest) { michael@0: let addon = new AddonWrapper(manifest); michael@0: AddonManagerPrivate.callAddonListeners("onEnabling", addon, false); michael@0: addon.pendingOperations |= AddonManager.PENDING_ENABLE; michael@0: this.addProvider(manifest, onDone); michael@0: addon.pendingOperations -= AddonManager.PENDING_ENABLE; michael@0: AddonManagerPrivate.callAddonListeners("onEnabled", addon); michael@0: return; michael@0: } michael@0: schedule(function() { michael@0: onDone(null); michael@0: }); michael@0: }, michael@0: michael@0: // Adds a provider given a manifest, and returns the added provider. michael@0: addProvider: function addProvider(manifest, onDone) { michael@0: if (SocialServiceInternal.providers[manifest.origin]) michael@0: throw new Error("SocialService.addProvider: provider with this origin already exists"); michael@0: michael@0: // enable the api when a provider is enabled michael@0: MozSocialAPI.enabled = true; michael@0: let provider = new SocialProvider(manifest); michael@0: SocialServiceInternal.providers[provider.origin] = provider; michael@0: ActiveProviders.add(provider.origin); michael@0: michael@0: this.getOrderedProviderList(function (providers) { michael@0: this._notifyProviderListeners("provider-enabled", provider.origin, providers); michael@0: if (onDone) michael@0: onDone(provider); michael@0: }.bind(this)); michael@0: }, michael@0: michael@0: // Removes a provider with the given origin, and notifies when the removal is michael@0: // complete. michael@0: removeProvider: function removeProvider(origin, onDone) { michael@0: if (!(origin in SocialServiceInternal.providers)) michael@0: throw new Error("SocialService.removeProvider: no provider with origin " + origin + " exists!"); michael@0: michael@0: let provider = SocialServiceInternal.providers[origin]; michael@0: let manifest = SocialService.getManifestByOrigin(origin); michael@0: let addon = manifest && new AddonWrapper(manifest); michael@0: if (addon) { michael@0: AddonManagerPrivate.callAddonListeners("onDisabling", addon, false); michael@0: addon.pendingOperations |= AddonManager.PENDING_DISABLE; michael@0: } michael@0: provider.enabled = false; michael@0: michael@0: ActiveProviders.delete(provider.origin); michael@0: michael@0: delete SocialServiceInternal.providers[origin]; michael@0: // disable the api if we have no enabled providers michael@0: MozSocialAPI.enabled = SocialServiceInternal.enabled; michael@0: michael@0: if (addon) { michael@0: // we have to do this now so the addon manager ui will update an uninstall michael@0: // correctly. michael@0: addon.pendingOperations -= AddonManager.PENDING_DISABLE; michael@0: AddonManagerPrivate.callAddonListeners("onDisabled", addon); michael@0: AddonManagerPrivate.notifyAddonChanged(addon.id, ADDON_TYPE_SERVICE, false); michael@0: } michael@0: michael@0: this.getOrderedProviderList(function (providers) { michael@0: this._notifyProviderListeners("provider-disabled", origin, providers); michael@0: if (onDone) michael@0: onDone(); michael@0: }.bind(this)); michael@0: }, michael@0: michael@0: // Returns a single provider object with the specified origin. The provider michael@0: // must be "installed" (ie, in ActiveProviders) michael@0: getProvider: function getProvider(origin, onDone) { michael@0: schedule((function () { michael@0: onDone(SocialServiceInternal.providers[origin] || null); michael@0: }).bind(this)); michael@0: }, michael@0: michael@0: // Returns an unordered array of installed providers michael@0: getProviderList: function(onDone) { michael@0: schedule(function () { michael@0: onDone(SocialServiceInternal.providerArray); michael@0: }); michael@0: }, michael@0: michael@0: getManifestByOrigin: function(origin) { michael@0: for (let manifest of SocialServiceInternal.manifests) { michael@0: if (origin == manifest.origin) { michael@0: return manifest; michael@0: } michael@0: } michael@0: return null; michael@0: }, michael@0: michael@0: // Returns an array of installed providers, sorted by frecency michael@0: getOrderedProviderList: function(onDone) { michael@0: SocialServiceInternal.orderedProviders(onDone); michael@0: }, michael@0: michael@0: getOriginActivationType: function (origin) { michael@0: return getOriginActivationType(origin); michael@0: }, michael@0: michael@0: _providerListeners: new Map(), michael@0: registerProviderListener: function registerProviderListener(listener) { michael@0: this._providerListeners.set(listener, 1); michael@0: }, michael@0: unregisterProviderListener: function unregisterProviderListener(listener) { michael@0: this._providerListeners.delete(listener); michael@0: }, michael@0: michael@0: _notifyProviderListeners: function (topic, origin, providers) { michael@0: for (let [listener, ] of this._providerListeners) { michael@0: try { michael@0: listener(topic, origin, providers); michael@0: } catch (ex) { michael@0: Components.utils.reportError("SocialService: provider listener threw an exception: " + ex); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: _manifestFromData: function(type, data, principal) { michael@0: let featureURLs = ['workerURL', 'sidebarURL', 'shareURL', 'statusURL', 'markURL']; michael@0: let resolveURLs = featureURLs.concat(['postActivationURL']); michael@0: michael@0: if (type == 'directory') { michael@0: // directory provided manifests must have origin in manifest, use that michael@0: if (!data['origin']) { michael@0: Cu.reportError("SocialService.manifestFromData directory service provided manifest without origin."); michael@0: return null; michael@0: } michael@0: let URI = Services.io.newURI(data.origin, null, null); michael@0: principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(URI); michael@0: } michael@0: // force/fixup origin michael@0: data.origin = principal.origin; michael@0: michael@0: // iconURL and name are required michael@0: // iconURL may be a different origin (CDN or data url support) if this is michael@0: // a whitelisted or directory listed provider michael@0: let providerHasFeatures = [url for (url of featureURLs) if (data[url])].length > 0; michael@0: if (!providerHasFeatures) { michael@0: Cu.reportError("SocialService.manifestFromData manifest missing required urls."); michael@0: return null; michael@0: } michael@0: if (!data['name'] || !data['iconURL']) { michael@0: Cu.reportError("SocialService.manifestFromData manifest missing name or iconURL."); michael@0: return null; michael@0: } michael@0: for (let url of resolveURLs) { michael@0: if (data[url]) { michael@0: try { michael@0: let resolved = Services.io.newURI(principal.URI.resolve(data[url]), null, null); michael@0: if (!(resolved.schemeIs("http") || resolved.schemeIs("https"))) { michael@0: Cu.reportError("SocialService.manifestFromData unsupported scheme '" + resolved.scheme + "' for " + principal.origin); michael@0: return null; michael@0: } michael@0: data[url] = resolved.spec; michael@0: } catch(e) { michael@0: Cu.reportError("SocialService.manifestFromData unable to resolve '" + url + "' for " + principal.origin); michael@0: return null; michael@0: } michael@0: } michael@0: } michael@0: return data; michael@0: }, michael@0: michael@0: _getChromeWindow: function(aWindow) { michael@0: var chromeWin = aWindow michael@0: .QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsIDocShellTreeItem) michael@0: .rootTreeItem michael@0: .QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIDOMWindow) michael@0: .QueryInterface(Ci.nsIDOMChromeWindow); michael@0: return chromeWin; michael@0: }, michael@0: michael@0: _showInstallNotification: function(aDOMDocument, aAddonInstaller) { michael@0: let brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties"); michael@0: let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties"); michael@0: let requestingWindow = aDOMDocument.defaultView.top; michael@0: let chromeWin = this._getChromeWindow(requestingWindow).wrappedJSObject; michael@0: let browser = chromeWin.gBrowser.getBrowserForDocument(aDOMDocument); michael@0: let requestingURI = Services.io.newURI(aDOMDocument.location.href, null, null); michael@0: michael@0: let productName = brandBundle.GetStringFromName("brandShortName"); michael@0: michael@0: let message = browserBundle.formatStringFromName("service.install.description", michael@0: [requestingURI.host, productName], 2); michael@0: michael@0: let action = { michael@0: label: browserBundle.GetStringFromName("service.install.ok.label"), michael@0: accessKey: browserBundle.GetStringFromName("service.install.ok.accesskey"), michael@0: callback: function() { michael@0: aAddonInstaller.install(); michael@0: }, michael@0: }; michael@0: michael@0: let options = { michael@0: learnMoreURL: Services.urlFormatter.formatURLPref("app.support.baseURL") + "social-api", michael@0: }; michael@0: let anchor = "servicesInstall-notification-icon"; michael@0: let notificationid = "servicesInstall"; michael@0: chromeWin.PopupNotifications.show(browser, notificationid, message, anchor, michael@0: action, [], options); michael@0: }, michael@0: michael@0: installProvider: function(aDOMDocument, data, installCallback) { michael@0: let manifest; michael@0: let installOrigin = aDOMDocument.nodePrincipal.origin; michael@0: michael@0: if (data) { michael@0: let installType = getOriginActivationType(installOrigin); michael@0: // if we get data, we MUST have a valid manifest generated from the data michael@0: manifest = this._manifestFromData(installType, data, aDOMDocument.nodePrincipal); michael@0: if (!manifest) michael@0: throw new Error("SocialService.installProvider: service configuration is invalid from " + aDOMDocument.location.href); michael@0: michael@0: let addon = new AddonWrapper(manifest); michael@0: if (addon && addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) michael@0: throw new Error("installProvider: provider with origin [" + michael@0: installOrigin + "] is blocklisted"); michael@0: } michael@0: michael@0: let id = getAddonIDFromOrigin(installOrigin); michael@0: AddonManager.getAddonByID(id, function(aAddon) { michael@0: if (aAddon && aAddon.userDisabled) { michael@0: aAddon.cancelUninstall(); michael@0: aAddon.userDisabled = false; michael@0: } michael@0: schedule(function () { michael@0: this._installProvider(aDOMDocument, manifest, aManifest => { michael@0: this._notifyProviderListeners("provider-installed", aManifest.origin); michael@0: installCallback(aManifest); michael@0: }); michael@0: }.bind(this)); michael@0: }.bind(this)); michael@0: }, michael@0: michael@0: _installProvider: function(aDOMDocument, manifest, installCallback) { michael@0: let sourceURI = aDOMDocument.location.href; michael@0: let installOrigin = aDOMDocument.nodePrincipal.origin; michael@0: michael@0: let installType = getOriginActivationType(installOrigin); michael@0: let installer; michael@0: switch(installType) { michael@0: case "foreign": michael@0: if (!Services.prefs.getBoolPref("social.remote-install.enabled")) michael@0: throw new Error("Remote install of services is disabled"); michael@0: if (!manifest) michael@0: throw new Error("Cannot install provider without manifest data"); michael@0: michael@0: installer = new AddonInstaller(sourceURI, manifest, installCallback); michael@0: this._showInstallNotification(aDOMDocument, installer); michael@0: break; michael@0: case "builtin": michael@0: // for builtin, we already have a manifest, but it can be overridden michael@0: // we need to return the manifest in the installcallback, so fetch michael@0: // it if we have it. If there is no manifest data for the builtin, michael@0: // the install request MUST be from the provider, otherwise we have michael@0: // no way to know what provider we're trying to enable. This is michael@0: // primarily an issue for "version zero" providers that did not michael@0: // send the manifest with the dom event for activation. michael@0: if (!manifest) { michael@0: let prefname = getPrefnameFromOrigin(installOrigin); michael@0: manifest = Services.prefs.getDefaultBranch(null) michael@0: .getComplexValue(prefname, Ci.nsISupportsString).data; michael@0: manifest = JSON.parse(manifest); michael@0: // ensure we override a builtin manifest by having a different value in it michael@0: if (manifest.builtin) michael@0: delete manifest.builtin; michael@0: } michael@0: case "directory": michael@0: // a manifest is requried, and will have been vetted by reviewers michael@0: case "whitelist": michael@0: // a manifest is required, we'll catch a missing manifest below. michael@0: if (!manifest) michael@0: throw new Error("Cannot install provider without manifest data"); michael@0: installer = new AddonInstaller(sourceURI, manifest, installCallback); michael@0: this._showInstallNotification(aDOMDocument, installer); michael@0: break; michael@0: default: michael@0: throw new Error("SocialService.installProvider: Invalid install type "+installType+"\n"); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: createWrapper: function(manifest) { michael@0: return new AddonWrapper(manifest); michael@0: }, michael@0: michael@0: /** michael@0: * updateProvider is used from the worker to self-update. Since we do not michael@0: * have knowledge of the currently selected provider here, we will notify michael@0: * the front end to deal with any reload. michael@0: */ michael@0: updateProvider: function(aUpdateOrigin, aManifest) { michael@0: let originUri = Services.io.newURI(aUpdateOrigin, null, null); michael@0: let principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(originUri); michael@0: let installType = this.getOriginActivationType(aUpdateOrigin); michael@0: // if we get data, we MUST have a valid manifest generated from the data michael@0: let manifest = this._manifestFromData(installType, aManifest, principal); michael@0: if (!manifest) michael@0: throw new Error("SocialService.installProvider: service configuration is invalid from " + aUpdateOrigin); michael@0: michael@0: // overwrite the preference michael@0: let string = Cc["@mozilla.org/supports-string;1"]. michael@0: createInstance(Ci.nsISupportsString); michael@0: string.data = JSON.stringify(manifest); michael@0: Services.prefs.setComplexValue(getPrefnameFromOrigin(manifest.origin), Ci.nsISupportsString, string); michael@0: michael@0: // overwrite the existing provider then notify the front end so it can michael@0: // handle any reload that might be necessary. michael@0: if (ActiveProviders.has(manifest.origin)) { michael@0: let provider = new SocialProvider(manifest); michael@0: SocialServiceInternal.providers[provider.origin] = provider; michael@0: // update the cache and ui, reload provider if necessary michael@0: this.getOrderedProviderList(providers => { michael@0: this._notifyProviderListeners("provider-update", provider.origin, providers); michael@0: }); michael@0: } michael@0: michael@0: }, michael@0: michael@0: uninstallProvider: function(origin, aCallback) { michael@0: let manifest = SocialService.getManifestByOrigin(origin); michael@0: let addon = new AddonWrapper(manifest); michael@0: addon.uninstall(aCallback); michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * The SocialProvider object represents a social provider, and allows michael@0: * access to its FrameWorker (if it has one). michael@0: * michael@0: * @constructor michael@0: * @param {jsobj} object representing the manifest file describing this provider michael@0: * @param {bool} boolean indicating whether this provider is "built in" michael@0: */ michael@0: function SocialProvider(input) { michael@0: if (!input.name) michael@0: throw new Error("SocialProvider must be passed a name"); michael@0: if (!input.origin) michael@0: throw new Error("SocialProvider must be passed an origin"); michael@0: michael@0: let addon = new AddonWrapper(input); michael@0: if (addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) michael@0: throw new Error("SocialProvider: provider with origin [" + michael@0: input.origin + "] is blocklisted"); michael@0: michael@0: this.name = input.name; michael@0: this.iconURL = input.iconURL; michael@0: this.icon32URL = input.icon32URL; michael@0: this.icon64URL = input.icon64URL; michael@0: this.workerURL = input.workerURL; michael@0: this.sidebarURL = input.sidebarURL; michael@0: this.shareURL = input.shareURL; michael@0: this.statusURL = input.statusURL; michael@0: this.markURL = input.markURL; michael@0: this.markedIcon = input.markedIcon; michael@0: this.unmarkedIcon = input.unmarkedIcon; michael@0: this.postActivationURL = input.postActivationURL; michael@0: this.origin = input.origin; michael@0: let originUri = Services.io.newURI(input.origin, null, null); michael@0: this.principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(originUri); michael@0: this.ambientNotificationIcons = {}; michael@0: this.errorState = null; michael@0: this.frecency = 0; michael@0: michael@0: let activationType = getOriginActivationType(input.origin); michael@0: this.blessed = activationType == "builtin" || michael@0: activationType == "whitelist"; michael@0: michael@0: try { michael@0: this.domain = etld.getBaseDomainFromHost(originUri.host); michael@0: } catch(e) { michael@0: this.domain = originUri.host; michael@0: } michael@0: } michael@0: michael@0: SocialProvider.prototype = { michael@0: reload: function() { michael@0: this._terminate(); michael@0: this._activate(); michael@0: Services.obs.notifyObservers(null, "social:provider-reload", this.origin); michael@0: }, michael@0: michael@0: // Provider enabled/disabled state. Disabled providers do not have active michael@0: // connections to their FrameWorkers. michael@0: _enabled: false, michael@0: get enabled() { michael@0: return this._enabled; michael@0: }, michael@0: set enabled(val) { michael@0: let enable = !!val; michael@0: if (enable == this._enabled) michael@0: return; michael@0: michael@0: this._enabled = enable; michael@0: michael@0: if (enable) { michael@0: this._activate(); michael@0: } else { michael@0: this._terminate(); michael@0: } michael@0: }, michael@0: michael@0: get manifest() { michael@0: return SocialService.getManifestByOrigin(this.origin); michael@0: }, michael@0: michael@0: // Reference to a workerAPI object for this provider. Null if the provider has michael@0: // no FrameWorker, or is disabled. michael@0: workerAPI: null, michael@0: michael@0: // Contains information related to the user's profile. Populated by the michael@0: // workerAPI via updateUserProfile. michael@0: // Properties: michael@0: // iconURL, portrait, userName, displayName, profileURL michael@0: // See https://github.com/mozilla/socialapi-dev/blob/develop/docs/socialAPI.md michael@0: // A value of null or an empty object means 'user not logged in'. michael@0: // A value of undefined means the service has not yet told us the status of michael@0: // the profile (ie, the service is still loading/initing, or the provider has michael@0: // no FrameWorker) michael@0: // This distinction might be used to cache certain data between runs - eg, michael@0: // browser-social.js caches the notification icons so they can be displayed michael@0: // quickly at startup without waiting for the provider to initialize - michael@0: // 'undefined' means 'ok to use cached values' versus 'null' meaning 'cached michael@0: // values aren't to be used as the user is logged out'. michael@0: profile: undefined, michael@0: michael@0: // Map of objects describing the provider's notification icons, whose michael@0: // properties include: michael@0: // name, iconURL, counter, contentPanel michael@0: // See https://developer.mozilla.org/en-US/docs/Social_API michael@0: ambientNotificationIcons: null, michael@0: michael@0: // Called by the workerAPI to update our profile information. michael@0: updateUserProfile: function(profile) { michael@0: if (!profile) michael@0: profile = {}; michael@0: let accountChanged = !this.profile || this.profile.userName != profile.userName; michael@0: this.profile = profile; michael@0: michael@0: // Sanitize the portrait from any potential script-injection. michael@0: if (profile.portrait) { michael@0: try { michael@0: let portraitUri = Services.io.newURI(profile.portrait, null, null); michael@0: michael@0: let scheme = portraitUri ? portraitUri.scheme : ""; michael@0: if (scheme != "data" && scheme != "http" && scheme != "https") { michael@0: profile.portrait = ""; michael@0: } michael@0: } catch (ex) { michael@0: profile.portrait = ""; michael@0: } michael@0: } michael@0: michael@0: if (profile.iconURL) michael@0: this.iconURL = profile.iconURL; michael@0: michael@0: if (!profile.displayName) michael@0: profile.displayName = profile.userName; michael@0: michael@0: // if no userName, consider this a logged out state, emtpy the michael@0: // users ambient notifications. notify both profile and ambient michael@0: // changes to clear everything michael@0: if (!profile.userName) { michael@0: this.profile = {}; michael@0: this.ambientNotificationIcons = {}; michael@0: Services.obs.notifyObservers(null, "social:ambient-notification-changed", this.origin); michael@0: } michael@0: michael@0: Services.obs.notifyObservers(null, "social:profile-changed", this.origin); michael@0: if (accountChanged) michael@0: closeAllChatWindows(this); michael@0: }, michael@0: michael@0: haveLoggedInUser: function () { michael@0: return !!(this.profile && this.profile.userName); michael@0: }, michael@0: michael@0: // Called by the workerAPI to add/update a notification icon. michael@0: setAmbientNotification: function(notification) { michael@0: if (!this.profile.userName) michael@0: throw new Error("unable to set notifications while logged out"); michael@0: if (!this.ambientNotificationIcons[notification.name] && michael@0: Object.keys(this.ambientNotificationIcons).length >= 3) { michael@0: throw new Error("ambient notification limit reached"); michael@0: } michael@0: this.ambientNotificationIcons[notification.name] = notification; michael@0: michael@0: Services.obs.notifyObservers(null, "social:ambient-notification-changed", this.origin); michael@0: }, michael@0: michael@0: // Internal helper methods michael@0: _activate: function _activate() { michael@0: // Initialize the workerAPI and its port first, so that its initialization michael@0: // occurs before any other messages are processed by other ports. michael@0: let workerAPIPort = this.getWorkerPort(); michael@0: if (workerAPIPort) michael@0: this.workerAPI = new WorkerAPI(this, workerAPIPort); michael@0: }, michael@0: michael@0: _terminate: function _terminate() { michael@0: closeAllChatWindows(this); michael@0: if (this.workerURL) { michael@0: try { michael@0: getFrameWorkerHandle(this.workerURL).terminate(); michael@0: } catch (e) { michael@0: Cu.reportError("SocialProvider FrameWorker termination failed: " + e); michael@0: } michael@0: } michael@0: if (this.workerAPI) { michael@0: this.workerAPI.terminate(); michael@0: } michael@0: this.errorState = null; michael@0: this.workerAPI = null; michael@0: this.profile = undefined; michael@0: }, michael@0: michael@0: /** michael@0: * Instantiates a FrameWorker for the provider if one doesn't exist, and michael@0: * returns a reference to a new port to that FrameWorker. michael@0: * michael@0: * Returns null if this provider has no workerURL, or is disabled. michael@0: * michael@0: * @param {DOMWindow} window (optional) michael@0: */ michael@0: getWorkerPort: function getWorkerPort(window) { michael@0: if (!this.workerURL || !this.enabled) michael@0: return null; michael@0: // Only allow localStorage in the frameworker for blessed providers michael@0: let allowLocalStorage = this.blessed; michael@0: let handle = getFrameWorkerHandle(this.workerURL, window, michael@0: "SocialProvider:" + this.origin, this.origin, michael@0: allowLocalStorage); michael@0: return handle.port; michael@0: }, michael@0: michael@0: /** michael@0: * Checks if a given URI is of the same origin as the provider. michael@0: * michael@0: * Returns true or false. michael@0: * michael@0: * @param {URI or string} uri michael@0: */ michael@0: isSameOrigin: function isSameOrigin(uri, allowIfInheritsPrincipal) { michael@0: if (!uri) michael@0: return false; michael@0: if (typeof uri == "string") { michael@0: try { michael@0: uri = Services.io.newURI(uri, null, null); michael@0: } catch (ex) { michael@0: // an invalid URL can't be loaded! michael@0: return false; michael@0: } michael@0: } michael@0: try { michael@0: this.principal.checkMayLoad( michael@0: uri, // the thing to check. michael@0: false, // reportError - we do our own reporting when necessary. michael@0: allowIfInheritsPrincipal michael@0: ); michael@0: return true; michael@0: } catch (ex) { michael@0: return false; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Resolve partial URLs for a provider. michael@0: * michael@0: * Returns nsIURI object or null on failure michael@0: * michael@0: * @param {string} url michael@0: */ michael@0: resolveUri: function resolveUri(url) { michael@0: try { michael@0: let fullURL = this.principal.URI.resolve(url); michael@0: return Services.io.newURI(fullURL, null, null); michael@0: } catch (ex) { michael@0: Cu.reportError("mozSocial: failed to resolve window URL: " + url + "; " + ex); michael@0: return null; michael@0: } michael@0: } michael@0: } michael@0: michael@0: function getAddonIDFromOrigin(origin) { michael@0: let originUri = Services.io.newURI(origin, null, null); michael@0: return originUri.host + ID_SUFFIX; michael@0: } michael@0: michael@0: function getPrefnameFromOrigin(origin) { michael@0: return "social.manifest." + SocialServiceInternal.getManifestPrefname(origin); michael@0: } michael@0: michael@0: function AddonInstaller(sourceURI, aManifest, installCallback) { michael@0: aManifest.updateDate = Date.now(); michael@0: // get the existing manifest for installDate michael@0: let manifest = SocialService.getManifestByOrigin(aManifest.origin); michael@0: let isNewInstall = !manifest; michael@0: if (manifest && manifest.installDate) michael@0: aManifest.installDate = manifest.installDate; michael@0: else michael@0: aManifest.installDate = aManifest.updateDate; michael@0: michael@0: this.sourceURI = sourceURI; michael@0: this.install = function() { michael@0: let addon = this.addon; michael@0: if (isNewInstall) { michael@0: AddonManagerPrivate.callInstallListeners("onExternalInstall", null, addon, null, false); michael@0: AddonManagerPrivate.callAddonListeners("onInstalling", addon, false); michael@0: } michael@0: michael@0: let string = Cc["@mozilla.org/supports-string;1"]. michael@0: createInstance(Ci.nsISupportsString); michael@0: string.data = JSON.stringify(aManifest); michael@0: Services.prefs.setComplexValue(getPrefnameFromOrigin(aManifest.origin), Ci.nsISupportsString, string); michael@0: michael@0: if (isNewInstall) { michael@0: AddonManagerPrivate.callAddonListeners("onInstalled", addon); michael@0: } michael@0: installCallback(aManifest); michael@0: }; michael@0: this.cancel = function() { michael@0: Services.prefs.clearUserPref(getPrefnameFromOrigin(aManifest.origin)) michael@0: }, michael@0: this.addon = new AddonWrapper(aManifest); michael@0: }; michael@0: michael@0: var SocialAddonProvider = { michael@0: startup: function() {}, michael@0: michael@0: shutdown: function() {}, michael@0: michael@0: updateAddonAppDisabledStates: function() { michael@0: // we wont bother with "enabling" services that are released from blocklist michael@0: for (let manifest of SocialServiceInternal.manifests) { michael@0: try { michael@0: if (ActiveProviders.has(manifest.origin)) { michael@0: let addon = new AddonWrapper(manifest); michael@0: if (addon.blocklistState != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) { michael@0: SocialService.removeProvider(manifest.origin); michael@0: } michael@0: } michael@0: } catch(e) { michael@0: Cu.reportError(e); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: getAddonByID: function(aId, aCallback) { michael@0: for (let manifest of SocialServiceInternal.manifests) { michael@0: if (aId == getAddonIDFromOrigin(manifest.origin)) { michael@0: aCallback(new AddonWrapper(manifest)); michael@0: return; michael@0: } michael@0: } michael@0: aCallback(null); michael@0: }, michael@0: michael@0: getAddonsByTypes: function(aTypes, aCallback) { michael@0: if (aTypes && aTypes.indexOf(ADDON_TYPE_SERVICE) == -1) { michael@0: aCallback([]); michael@0: return; michael@0: } michael@0: aCallback([new AddonWrapper(a) for each (a in SocialServiceInternal.manifests)]); michael@0: }, michael@0: michael@0: removeAddon: function(aAddon, aCallback) { michael@0: AddonManagerPrivate.callAddonListeners("onUninstalling", aAddon, false); michael@0: aAddon.pendingOperations |= AddonManager.PENDING_UNINSTALL; michael@0: Services.prefs.clearUserPref(getPrefnameFromOrigin(aAddon.manifest.origin)); michael@0: aAddon.pendingOperations -= AddonManager.PENDING_UNINSTALL; michael@0: AddonManagerPrivate.callAddonListeners("onUninstalled", aAddon); michael@0: SocialService._notifyProviderListeners("provider-uninstalled", aAddon.manifest.origin); michael@0: if (aCallback) michael@0: schedule(aCallback); michael@0: } michael@0: } michael@0: michael@0: michael@0: function AddonWrapper(aManifest) { michael@0: this.manifest = aManifest; michael@0: this.id = getAddonIDFromOrigin(this.manifest.origin); michael@0: this._pending = AddonManager.PENDING_NONE; michael@0: } michael@0: AddonWrapper.prototype = { michael@0: get type() { michael@0: return ADDON_TYPE_SERVICE; michael@0: }, michael@0: michael@0: get appDisabled() { michael@0: return this.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED; michael@0: }, michael@0: michael@0: set softDisabled(val) { michael@0: this.userDisabled = val; michael@0: }, michael@0: michael@0: get softDisabled() { michael@0: return this.userDisabled; michael@0: }, michael@0: michael@0: get isCompatible() { michael@0: return true; michael@0: }, michael@0: michael@0: get isPlatformCompatible() { michael@0: return true; michael@0: }, michael@0: michael@0: get scope() { michael@0: return AddonManager.SCOPE_PROFILE; michael@0: }, michael@0: michael@0: get foreignInstall() { michael@0: return false; michael@0: }, michael@0: michael@0: isCompatibleWith: function(appVersion, platformVersion) { michael@0: return true; michael@0: }, michael@0: michael@0: get providesUpdatesSecurely() { michael@0: return true; michael@0: }, michael@0: michael@0: get blocklistState() { michael@0: return Services.blocklist.getAddonBlocklistState(this); michael@0: }, michael@0: michael@0: get blocklistURL() { michael@0: return Services.blocklist.getAddonBlocklistURL(this); michael@0: }, michael@0: michael@0: get screenshots() { michael@0: return []; michael@0: }, michael@0: michael@0: get pendingOperations() { michael@0: return this._pending || AddonManager.PENDING_NONE; michael@0: }, michael@0: set pendingOperations(val) { michael@0: this._pending = val; michael@0: }, michael@0: michael@0: get operationsRequiringRestart() { michael@0: return AddonManager.OP_NEEDS_RESTART_NONE; michael@0: }, michael@0: michael@0: get size() { michael@0: return null; michael@0: }, michael@0: michael@0: get permissions() { michael@0: let permissions = 0; michael@0: // any "user defined" manifest can be removed michael@0: if (Services.prefs.prefHasUserValue(getPrefnameFromOrigin(this.manifest.origin))) michael@0: permissions = AddonManager.PERM_CAN_UNINSTALL; michael@0: if (!this.appDisabled) { michael@0: if (this.userDisabled) { michael@0: permissions |= AddonManager.PERM_CAN_ENABLE; michael@0: } else { michael@0: permissions |= AddonManager.PERM_CAN_DISABLE; michael@0: } michael@0: } michael@0: return permissions; michael@0: }, michael@0: michael@0: findUpdates: function(listener, reason, appVersion, platformVersion) { michael@0: if ("onNoCompatibilityUpdateAvailable" in listener) michael@0: listener.onNoCompatibilityUpdateAvailable(this); michael@0: if ("onNoUpdateAvailable" in listener) michael@0: listener.onNoUpdateAvailable(this); michael@0: if ("onUpdateFinished" in listener) michael@0: listener.onUpdateFinished(this); michael@0: }, michael@0: michael@0: get isActive() { michael@0: return ActiveProviders.has(this.manifest.origin); michael@0: }, michael@0: michael@0: get name() { michael@0: return this.manifest.name; michael@0: }, michael@0: get version() { michael@0: return this.manifest.version ? this.manifest.version : ""; michael@0: }, michael@0: michael@0: get iconURL() { michael@0: return this.manifest.icon32URL ? this.manifest.icon32URL : this.manifest.iconURL; michael@0: }, michael@0: get icon64URL() { michael@0: return this.manifest.icon64URL; michael@0: }, michael@0: get icons() { michael@0: let icons = { michael@0: 16: this.manifest.iconURL michael@0: }; michael@0: if (this.manifest.icon32URL) michael@0: icons[32] = this.manifest.icon32URL; michael@0: if (this.manifest.icon64URL) michael@0: icons[64] = this.manifest.icon64URL; michael@0: return icons; michael@0: }, michael@0: michael@0: get description() { michael@0: return this.manifest.description; michael@0: }, michael@0: get homepageURL() { michael@0: return this.manifest.homepageURL; michael@0: }, michael@0: get defaultLocale() { michael@0: return this.manifest.defaultLocale; michael@0: }, michael@0: get selectedLocale() { michael@0: return this.manifest.selectedLocale; michael@0: }, michael@0: michael@0: get installDate() { michael@0: return this.manifest.installDate ? new Date(this.manifest.installDate) : null; michael@0: }, michael@0: get updateDate() { michael@0: return this.manifest.updateDate ? new Date(this.manifest.updateDate) : null; michael@0: }, michael@0: michael@0: get creator() { michael@0: return new AddonManagerPrivate.AddonAuthor(this.manifest.author); michael@0: }, michael@0: michael@0: get userDisabled() { michael@0: return this.appDisabled || !ActiveProviders.has(this.manifest.origin); michael@0: }, michael@0: michael@0: set userDisabled(val) { michael@0: if (val == this.userDisabled) michael@0: return val; michael@0: if (val) { michael@0: SocialService.removeProvider(this.manifest.origin); michael@0: } else if (!this.appDisabled) { michael@0: SocialService.addBuiltinProvider(this.manifest.origin); michael@0: } michael@0: return val; michael@0: }, michael@0: michael@0: uninstall: function(aCallback) { michael@0: let prefName = getPrefnameFromOrigin(this.manifest.origin); michael@0: if (Services.prefs.prefHasUserValue(prefName)) { michael@0: if (ActiveProviders.has(this.manifest.origin)) { michael@0: SocialService.removeProvider(this.manifest.origin, function() { michael@0: SocialAddonProvider.removeAddon(this, aCallback); michael@0: }.bind(this)); michael@0: } else { michael@0: SocialAddonProvider.removeAddon(this, aCallback); michael@0: } michael@0: } else { michael@0: schedule(aCallback); michael@0: } michael@0: }, michael@0: michael@0: cancelUninstall: function() { michael@0: this._pending -= AddonManager.PENDING_UNINSTALL; michael@0: AddonManagerPrivate.callAddonListeners("onOperationCancelled", this); michael@0: } michael@0: }; michael@0: michael@0: michael@0: AddonManagerPrivate.registerProvider(SocialAddonProvider, [ michael@0: new AddonManagerPrivate.AddonType(ADDON_TYPE_SERVICE, URI_EXTENSION_STRINGS, michael@0: STRING_TYPE_NAME, michael@0: AddonManager.VIEW_TYPE_LIST, 10000) michael@0: ]);