1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/components/social/SocialService.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1257 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +this.EXPORTED_SYMBOLS = ["SocialService"]; 1.9 + 1.10 +const { classes: Cc, interfaces: Ci, utils: Cu } = Components; 1.11 + 1.12 +Cu.import("resource://gre/modules/Services.jsm"); 1.13 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.14 +Cu.import("resource://gre/modules/AddonManager.jsm"); 1.15 +Cu.import("resource://gre/modules/PlacesUtils.jsm"); 1.16 + 1.17 +const URI_EXTENSION_STRINGS = "chrome://mozapps/locale/extensions/extensions.properties"; 1.18 +const ADDON_TYPE_SERVICE = "service"; 1.19 +const ID_SUFFIX = "@services.mozilla.org"; 1.20 +const STRING_TYPE_NAME = "type.%ID%.name"; 1.21 + 1.22 +XPCOMUtils.defineLazyModuleGetter(this, "getFrameWorkerHandle", "resource://gre/modules/FrameWorker.jsm"); 1.23 +XPCOMUtils.defineLazyModuleGetter(this, "WorkerAPI", "resource://gre/modules/WorkerAPI.jsm"); 1.24 +XPCOMUtils.defineLazyModuleGetter(this, "MozSocialAPI", "resource://gre/modules/MozSocialAPI.jsm"); 1.25 +XPCOMUtils.defineLazyModuleGetter(this, "closeAllChatWindows", "resource://gre/modules/MozSocialAPI.jsm"); 1.26 +XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask", "resource://gre/modules/DeferredTask.jsm"); 1.27 + 1.28 +XPCOMUtils.defineLazyServiceGetter(this, "etld", 1.29 + "@mozilla.org/network/effective-tld-service;1", 1.30 + "nsIEffectiveTLDService"); 1.31 + 1.32 +/** 1.33 + * The SocialService is the public API to social providers - it tracks which 1.34 + * providers are installed and enabled, and is the entry-point for access to 1.35 + * the provider itself. 1.36 + */ 1.37 + 1.38 +// Internal helper methods and state 1.39 +let SocialServiceInternal = { 1.40 + get enabled() this.providerArray.length > 0, 1.41 + 1.42 + get providerArray() { 1.43 + return [p for ([, p] of Iterator(this.providers))]; 1.44 + }, 1.45 + get manifests() { 1.46 + // Retrieve the manifests of installed providers from prefs 1.47 + let MANIFEST_PREFS = Services.prefs.getBranch("social.manifest."); 1.48 + let prefs = MANIFEST_PREFS.getChildList("", []); 1.49 + for (let pref of prefs) { 1.50 + // we only consider manifests in user level prefs to be *installed* 1.51 + if (!MANIFEST_PREFS.prefHasUserValue(pref)) 1.52 + continue; 1.53 + try { 1.54 + var manifest = JSON.parse(MANIFEST_PREFS.getComplexValue(pref, Ci.nsISupportsString).data); 1.55 + if (manifest && typeof(manifest) == "object" && manifest.origin) 1.56 + yield manifest; 1.57 + } catch (err) { 1.58 + Cu.reportError("SocialService: failed to load manifest: " + pref + 1.59 + ", exception: " + err); 1.60 + } 1.61 + } 1.62 + }, 1.63 + getManifestPrefname: function(origin) { 1.64 + // Retrieve the prefname for a given origin/manifest. 1.65 + // If no existing pref, return a generated prefname. 1.66 + let MANIFEST_PREFS = Services.prefs.getBranch("social.manifest."); 1.67 + let prefs = MANIFEST_PREFS.getChildList("", []); 1.68 + for (let pref of prefs) { 1.69 + try { 1.70 + var manifest = JSON.parse(MANIFEST_PREFS.getComplexValue(pref, Ci.nsISupportsString).data); 1.71 + if (manifest.origin == origin) { 1.72 + return pref; 1.73 + } 1.74 + } catch (err) { 1.75 + Cu.reportError("SocialService: failed to load manifest: " + pref + 1.76 + ", exception: " + err); 1.77 + } 1.78 + } 1.79 + let originUri = Services.io.newURI(origin, null, null); 1.80 + return originUri.hostPort.replace('.','-'); 1.81 + }, 1.82 + orderedProviders: function(aCallback) { 1.83 + if (SocialServiceInternal.providerArray.length < 2) { 1.84 + schedule(function () { 1.85 + aCallback(SocialServiceInternal.providerArray); 1.86 + }); 1.87 + return; 1.88 + } 1.89 + // query moz_hosts for frecency. since some providers may not have a 1.90 + // frecency entry, we need to later sort on our own. We use the providers 1.91 + // object below as an easy way to later record the frecency on the provider 1.92 + // object from the query results. 1.93 + let hosts = []; 1.94 + let providers = {}; 1.95 + 1.96 + for (let p of SocialServiceInternal.providerArray) { 1.97 + p.frecency = 0; 1.98 + providers[p.domain] = p; 1.99 + hosts.push(p.domain); 1.100 + }; 1.101 + 1.102 + // cannot bind an array to stmt.params so we have to build the string 1.103 + let stmt = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase) 1.104 + .DBConnection.createAsyncStatement( 1.105 + "SELECT host, frecency FROM moz_hosts WHERE host IN (" + 1.106 + [ '"' + host + '"' for each (host in hosts) ].join(",") + ") " 1.107 + ); 1.108 + 1.109 + try { 1.110 + stmt.executeAsync({ 1.111 + handleResult: function(aResultSet) { 1.112 + let row; 1.113 + while ((row = aResultSet.getNextRow())) { 1.114 + let rh = row.getResultByName("host"); 1.115 + let frecency = row.getResultByName("frecency"); 1.116 + providers[rh].frecency = parseInt(frecency) || 0; 1.117 + } 1.118 + }, 1.119 + handleError: function(aError) { 1.120 + Cu.reportError(aError.message + " (Result = " + aError.result + ")"); 1.121 + }, 1.122 + handleCompletion: function(aReason) { 1.123 + // the query may not have returned all our providers, so we have 1.124 + // stamped the frecency on the provider and sort here. This makes sure 1.125 + // all enabled providers get sorted even with frecency zero. 1.126 + let providerList = SocialServiceInternal.providerArray; 1.127 + // reverse sort 1.128 + aCallback(providerList.sort(function(a, b) b.frecency - a.frecency)); 1.129 + } 1.130 + }); 1.131 + } finally { 1.132 + stmt.finalize(); 1.133 + } 1.134 + } 1.135 +}; 1.136 + 1.137 +XPCOMUtils.defineLazyGetter(SocialServiceInternal, "providers", function () { 1.138 + initService(); 1.139 + let providers = {}; 1.140 + for (let manifest of this.manifests) { 1.141 + try { 1.142 + if (ActiveProviders.has(manifest.origin)) { 1.143 + // enable the api when a provider is enabled 1.144 + MozSocialAPI.enabled = true; 1.145 + let provider = new SocialProvider(manifest); 1.146 + providers[provider.origin] = provider; 1.147 + } 1.148 + } catch (err) { 1.149 + Cu.reportError("SocialService: failed to load provider: " + manifest.origin + 1.150 + ", exception: " + err); 1.151 + } 1.152 + } 1.153 + return providers; 1.154 +}); 1.155 + 1.156 +function getOriginActivationType(origin) { 1.157 + let prefname = SocialServiceInternal.getManifestPrefname(origin); 1.158 + if (Services.prefs.getDefaultBranch("social.manifest.").getPrefType(prefname) == Services.prefs.PREF_STRING) 1.159 + return 'builtin'; 1.160 + 1.161 + let whitelist = Services.prefs.getCharPref("social.whitelist").split(','); 1.162 + if (whitelist.indexOf(origin) >= 0) 1.163 + return 'whitelist'; 1.164 + 1.165 + let directories = Services.prefs.getCharPref("social.directories").split(','); 1.166 + if (directories.indexOf(origin) >= 0) 1.167 + return 'directory'; 1.168 + 1.169 + return 'foreign'; 1.170 +} 1.171 + 1.172 +let ActiveProviders = { 1.173 + get _providers() { 1.174 + delete this._providers; 1.175 + this._providers = {}; 1.176 + try { 1.177 + let pref = Services.prefs.getComplexValue("social.activeProviders", 1.178 + Ci.nsISupportsString); 1.179 + this._providers = JSON.parse(pref); 1.180 + } catch(ex) {} 1.181 + return this._providers; 1.182 + }, 1.183 + 1.184 + has: function (origin) { 1.185 + return (origin in this._providers); 1.186 + }, 1.187 + 1.188 + add: function (origin) { 1.189 + this._providers[origin] = 1; 1.190 + this._deferredTask.arm(); 1.191 + }, 1.192 + 1.193 + delete: function (origin) { 1.194 + delete this._providers[origin]; 1.195 + this._deferredTask.arm(); 1.196 + }, 1.197 + 1.198 + flush: function () { 1.199 + this._deferredTask.disarm(); 1.200 + this._persist(); 1.201 + }, 1.202 + 1.203 + get _deferredTask() { 1.204 + delete this._deferredTask; 1.205 + return this._deferredTask = new DeferredTask(this._persist.bind(this), 0); 1.206 + }, 1.207 + 1.208 + _persist: function () { 1.209 + let string = Cc["@mozilla.org/supports-string;1"]. 1.210 + createInstance(Ci.nsISupportsString); 1.211 + string.data = JSON.stringify(this._providers); 1.212 + Services.prefs.setComplexValue("social.activeProviders", 1.213 + Ci.nsISupportsString, string); 1.214 + } 1.215 +}; 1.216 + 1.217 +function migrateSettings() { 1.218 + let activeProviders, enabled; 1.219 + try { 1.220 + activeProviders = Services.prefs.getCharPref("social.activeProviders"); 1.221 + } catch(e) { 1.222 + // not set, we'll check if we need to migrate older prefs 1.223 + } 1.224 + if (Services.prefs.prefHasUserValue("social.enabled")) { 1.225 + enabled = Services.prefs.getBoolPref("social.enabled"); 1.226 + } 1.227 + if (activeProviders) { 1.228 + // migration from fx21 to fx22 or later 1.229 + // ensure any *builtin* provider in activeproviders is in user level prefs 1.230 + for (let origin in ActiveProviders._providers) { 1.231 + let prefname; 1.232 + let manifest; 1.233 + let defaultManifest; 1.234 + try { 1.235 + prefname = getPrefnameFromOrigin(origin); 1.236 + manifest = JSON.parse(Services.prefs.getComplexValue(prefname, Ci.nsISupportsString).data); 1.237 + } catch(e) { 1.238 + // Our preference is missing or bad, remove from ActiveProviders and 1.239 + // continue. This is primarily an error-case and should only be 1.240 + // reached by either messing with preferences or hitting the one or 1.241 + // two days of nightly that ran into it, so we'll flush right away. 1.242 + ActiveProviders.delete(origin); 1.243 + ActiveProviders.flush(); 1.244 + continue; 1.245 + } 1.246 + let needsUpdate = !manifest.updateDate; 1.247 + // fx23 may have built-ins with shareURL 1.248 + try { 1.249 + defaultManifest = Services.prefs.getDefaultBranch(null) 1.250 + .getComplexValue(prefname, Ci.nsISupportsString).data; 1.251 + defaultManifest = JSON.parse(defaultManifest); 1.252 + } catch(e) { 1.253 + // not a built-in, continue 1.254 + } 1.255 + if (defaultManifest) { 1.256 + if (defaultManifest.shareURL && !manifest.shareURL) { 1.257 + manifest.shareURL = defaultManifest.shareURL; 1.258 + needsUpdate = true; 1.259 + } 1.260 + if (defaultManifest.version && (!manifest.version || defaultManifest.version > manifest.version)) { 1.261 + manifest = defaultManifest; 1.262 + needsUpdate = true; 1.263 + } 1.264 + } 1.265 + if (needsUpdate) { 1.266 + // the provider was installed with an older build, so we will update the 1.267 + // timestamp and ensure the manifest is in user prefs 1.268 + delete manifest.builtin; 1.269 + // we're potentially updating for share, so always mark the updateDate 1.270 + manifest.updateDate = Date.now(); 1.271 + if (!manifest.installDate) 1.272 + manifest.installDate = 0; // we don't know when it was installed 1.273 + 1.274 + let string = Cc["@mozilla.org/supports-string;1"]. 1.275 + createInstance(Ci.nsISupportsString); 1.276 + string.data = JSON.stringify(manifest); 1.277 + Services.prefs.setComplexValue(prefname, Ci.nsISupportsString, string); 1.278 + } 1.279 + // as of fx 29, we no longer rely on social.enabled. migration from prior 1.280 + // versions should disable all service addons if social.enabled=false 1.281 + if (enabled === false) { 1.282 + ActiveProviders.delete(origin); 1.283 + } 1.284 + } 1.285 + ActiveProviders.flush(); 1.286 + Services.prefs.clearUserPref("social.enabled"); 1.287 + return; 1.288 + } 1.289 + 1.290 + // primary migration from pre-fx21 1.291 + let active; 1.292 + try { 1.293 + active = Services.prefs.getBoolPref("social.active"); 1.294 + } catch(e) {} 1.295 + if (!active) 1.296 + return; 1.297 + 1.298 + // primary difference from SocialServiceInternal.manifests is that we 1.299 + // only read the default branch here. 1.300 + let manifestPrefs = Services.prefs.getDefaultBranch("social.manifest."); 1.301 + let prefs = manifestPrefs.getChildList("", []); 1.302 + for (let pref of prefs) { 1.303 + try { 1.304 + let manifest; 1.305 + try { 1.306 + manifest = JSON.parse(manifestPrefs.getComplexValue(pref, Ci.nsISupportsString).data); 1.307 + } catch(e) { 1.308 + // bad or missing preference, we wont update this one. 1.309 + continue; 1.310 + } 1.311 + if (manifest && typeof(manifest) == "object" && manifest.origin) { 1.312 + // our default manifests have been updated with the builtin flags as of 1.313 + // fx22, delete it so we can set the user-pref 1.314 + delete manifest.builtin; 1.315 + if (!manifest.updateDate) { 1.316 + manifest.updateDate = Date.now(); 1.317 + manifest.installDate = 0; // we don't know when it was installed 1.318 + } 1.319 + 1.320 + let string = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString); 1.321 + string.data = JSON.stringify(manifest); 1.322 + // pref here is just the branch name, set the full pref name 1.323 + Services.prefs.setComplexValue("social.manifest." + pref, Ci.nsISupportsString, string); 1.324 + ActiveProviders.add(manifest.origin); 1.325 + ActiveProviders.flush(); 1.326 + // social.active was used at a time that there was only one 1.327 + // builtin, we'll assume that is still the case 1.328 + return; 1.329 + } 1.330 + } catch (err) { 1.331 + Cu.reportError("SocialService: failed to load manifest: " + pref + ", exception: " + err); 1.332 + } 1.333 + } 1.334 +} 1.335 + 1.336 +function initService() { 1.337 + Services.obs.addObserver(function xpcomShutdown() { 1.338 + ActiveProviders.flush(); 1.339 + SocialService._providerListeners = null; 1.340 + Services.obs.removeObserver(xpcomShutdown, "xpcom-shutdown"); 1.341 + }, "xpcom-shutdown", false); 1.342 + 1.343 + try { 1.344 + migrateSettings(); 1.345 + } catch(e) { 1.346 + // no matter what, if migration fails we do not want to render social 1.347 + // unusable. Worst case scenario is that, when upgrading Firefox, previously 1.348 + // enabled providers are not migrated. 1.349 + Cu.reportError("Error migrating social settings: " + e); 1.350 + } 1.351 +} 1.352 + 1.353 +function schedule(callback) { 1.354 + Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL); 1.355 +} 1.356 + 1.357 +// Public API 1.358 +this.SocialService = { 1.359 + get hasEnabledProviders() { 1.360 + // used as an optimization during startup, can be used to check if further 1.361 + // initialization should be done (e.g. creating the instances of 1.362 + // SocialProvider and turning on UI). ActiveProviders may have changed and 1.363 + // not yet flushed so we check the active providers array 1.364 + for (let p in ActiveProviders._providers) { 1.365 + return true; 1.366 + }; 1.367 + return false; 1.368 + }, 1.369 + get enabled() { 1.370 + return SocialServiceInternal.enabled; 1.371 + }, 1.372 + set enabled(val) { 1.373 + throw new Error("not allowed to set SocialService.enabled"); 1.374 + }, 1.375 + 1.376 + // Adds and activates a builtin provider. The provider may or may not have 1.377 + // previously been added. onDone is always called - with null if no such 1.378 + // provider exists, or the activated provider on success. 1.379 + addBuiltinProvider: function addBuiltinProvider(origin, onDone) { 1.380 + if (SocialServiceInternal.providers[origin]) { 1.381 + schedule(function() { 1.382 + onDone(SocialServiceInternal.providers[origin]); 1.383 + }); 1.384 + return; 1.385 + } 1.386 + let manifest = SocialService.getManifestByOrigin(origin); 1.387 + if (manifest) { 1.388 + let addon = new AddonWrapper(manifest); 1.389 + AddonManagerPrivate.callAddonListeners("onEnabling", addon, false); 1.390 + addon.pendingOperations |= AddonManager.PENDING_ENABLE; 1.391 + this.addProvider(manifest, onDone); 1.392 + addon.pendingOperations -= AddonManager.PENDING_ENABLE; 1.393 + AddonManagerPrivate.callAddonListeners("onEnabled", addon); 1.394 + return; 1.395 + } 1.396 + schedule(function() { 1.397 + onDone(null); 1.398 + }); 1.399 + }, 1.400 + 1.401 + // Adds a provider given a manifest, and returns the added provider. 1.402 + addProvider: function addProvider(manifest, onDone) { 1.403 + if (SocialServiceInternal.providers[manifest.origin]) 1.404 + throw new Error("SocialService.addProvider: provider with this origin already exists"); 1.405 + 1.406 + // enable the api when a provider is enabled 1.407 + MozSocialAPI.enabled = true; 1.408 + let provider = new SocialProvider(manifest); 1.409 + SocialServiceInternal.providers[provider.origin] = provider; 1.410 + ActiveProviders.add(provider.origin); 1.411 + 1.412 + this.getOrderedProviderList(function (providers) { 1.413 + this._notifyProviderListeners("provider-enabled", provider.origin, providers); 1.414 + if (onDone) 1.415 + onDone(provider); 1.416 + }.bind(this)); 1.417 + }, 1.418 + 1.419 + // Removes a provider with the given origin, and notifies when the removal is 1.420 + // complete. 1.421 + removeProvider: function removeProvider(origin, onDone) { 1.422 + if (!(origin in SocialServiceInternal.providers)) 1.423 + throw new Error("SocialService.removeProvider: no provider with origin " + origin + " exists!"); 1.424 + 1.425 + let provider = SocialServiceInternal.providers[origin]; 1.426 + let manifest = SocialService.getManifestByOrigin(origin); 1.427 + let addon = manifest && new AddonWrapper(manifest); 1.428 + if (addon) { 1.429 + AddonManagerPrivate.callAddonListeners("onDisabling", addon, false); 1.430 + addon.pendingOperations |= AddonManager.PENDING_DISABLE; 1.431 + } 1.432 + provider.enabled = false; 1.433 + 1.434 + ActiveProviders.delete(provider.origin); 1.435 + 1.436 + delete SocialServiceInternal.providers[origin]; 1.437 + // disable the api if we have no enabled providers 1.438 + MozSocialAPI.enabled = SocialServiceInternal.enabled; 1.439 + 1.440 + if (addon) { 1.441 + // we have to do this now so the addon manager ui will update an uninstall 1.442 + // correctly. 1.443 + addon.pendingOperations -= AddonManager.PENDING_DISABLE; 1.444 + AddonManagerPrivate.callAddonListeners("onDisabled", addon); 1.445 + AddonManagerPrivate.notifyAddonChanged(addon.id, ADDON_TYPE_SERVICE, false); 1.446 + } 1.447 + 1.448 + this.getOrderedProviderList(function (providers) { 1.449 + this._notifyProviderListeners("provider-disabled", origin, providers); 1.450 + if (onDone) 1.451 + onDone(); 1.452 + }.bind(this)); 1.453 + }, 1.454 + 1.455 + // Returns a single provider object with the specified origin. The provider 1.456 + // must be "installed" (ie, in ActiveProviders) 1.457 + getProvider: function getProvider(origin, onDone) { 1.458 + schedule((function () { 1.459 + onDone(SocialServiceInternal.providers[origin] || null); 1.460 + }).bind(this)); 1.461 + }, 1.462 + 1.463 + // Returns an unordered array of installed providers 1.464 + getProviderList: function(onDone) { 1.465 + schedule(function () { 1.466 + onDone(SocialServiceInternal.providerArray); 1.467 + }); 1.468 + }, 1.469 + 1.470 + getManifestByOrigin: function(origin) { 1.471 + for (let manifest of SocialServiceInternal.manifests) { 1.472 + if (origin == manifest.origin) { 1.473 + return manifest; 1.474 + } 1.475 + } 1.476 + return null; 1.477 + }, 1.478 + 1.479 + // Returns an array of installed providers, sorted by frecency 1.480 + getOrderedProviderList: function(onDone) { 1.481 + SocialServiceInternal.orderedProviders(onDone); 1.482 + }, 1.483 + 1.484 + getOriginActivationType: function (origin) { 1.485 + return getOriginActivationType(origin); 1.486 + }, 1.487 + 1.488 + _providerListeners: new Map(), 1.489 + registerProviderListener: function registerProviderListener(listener) { 1.490 + this._providerListeners.set(listener, 1); 1.491 + }, 1.492 + unregisterProviderListener: function unregisterProviderListener(listener) { 1.493 + this._providerListeners.delete(listener); 1.494 + }, 1.495 + 1.496 + _notifyProviderListeners: function (topic, origin, providers) { 1.497 + for (let [listener, ] of this._providerListeners) { 1.498 + try { 1.499 + listener(topic, origin, providers); 1.500 + } catch (ex) { 1.501 + Components.utils.reportError("SocialService: provider listener threw an exception: " + ex); 1.502 + } 1.503 + } 1.504 + }, 1.505 + 1.506 + _manifestFromData: function(type, data, principal) { 1.507 + let featureURLs = ['workerURL', 'sidebarURL', 'shareURL', 'statusURL', 'markURL']; 1.508 + let resolveURLs = featureURLs.concat(['postActivationURL']); 1.509 + 1.510 + if (type == 'directory') { 1.511 + // directory provided manifests must have origin in manifest, use that 1.512 + if (!data['origin']) { 1.513 + Cu.reportError("SocialService.manifestFromData directory service provided manifest without origin."); 1.514 + return null; 1.515 + } 1.516 + let URI = Services.io.newURI(data.origin, null, null); 1.517 + principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(URI); 1.518 + } 1.519 + // force/fixup origin 1.520 + data.origin = principal.origin; 1.521 + 1.522 + // iconURL and name are required 1.523 + // iconURL may be a different origin (CDN or data url support) if this is 1.524 + // a whitelisted or directory listed provider 1.525 + let providerHasFeatures = [url for (url of featureURLs) if (data[url])].length > 0; 1.526 + if (!providerHasFeatures) { 1.527 + Cu.reportError("SocialService.manifestFromData manifest missing required urls."); 1.528 + return null; 1.529 + } 1.530 + if (!data['name'] || !data['iconURL']) { 1.531 + Cu.reportError("SocialService.manifestFromData manifest missing name or iconURL."); 1.532 + return null; 1.533 + } 1.534 + for (let url of resolveURLs) { 1.535 + if (data[url]) { 1.536 + try { 1.537 + let resolved = Services.io.newURI(principal.URI.resolve(data[url]), null, null); 1.538 + if (!(resolved.schemeIs("http") || resolved.schemeIs("https"))) { 1.539 + Cu.reportError("SocialService.manifestFromData unsupported scheme '" + resolved.scheme + "' for " + principal.origin); 1.540 + return null; 1.541 + } 1.542 + data[url] = resolved.spec; 1.543 + } catch(e) { 1.544 + Cu.reportError("SocialService.manifestFromData unable to resolve '" + url + "' for " + principal.origin); 1.545 + return null; 1.546 + } 1.547 + } 1.548 + } 1.549 + return data; 1.550 + }, 1.551 + 1.552 + _getChromeWindow: function(aWindow) { 1.553 + var chromeWin = aWindow 1.554 + .QueryInterface(Ci.nsIInterfaceRequestor) 1.555 + .getInterface(Ci.nsIWebNavigation) 1.556 + .QueryInterface(Ci.nsIDocShellTreeItem) 1.557 + .rootTreeItem 1.558 + .QueryInterface(Ci.nsIInterfaceRequestor) 1.559 + .getInterface(Ci.nsIDOMWindow) 1.560 + .QueryInterface(Ci.nsIDOMChromeWindow); 1.561 + return chromeWin; 1.562 + }, 1.563 + 1.564 + _showInstallNotification: function(aDOMDocument, aAddonInstaller) { 1.565 + let brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties"); 1.566 + let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties"); 1.567 + let requestingWindow = aDOMDocument.defaultView.top; 1.568 + let chromeWin = this._getChromeWindow(requestingWindow).wrappedJSObject; 1.569 + let browser = chromeWin.gBrowser.getBrowserForDocument(aDOMDocument); 1.570 + let requestingURI = Services.io.newURI(aDOMDocument.location.href, null, null); 1.571 + 1.572 + let productName = brandBundle.GetStringFromName("brandShortName"); 1.573 + 1.574 + let message = browserBundle.formatStringFromName("service.install.description", 1.575 + [requestingURI.host, productName], 2); 1.576 + 1.577 + let action = { 1.578 + label: browserBundle.GetStringFromName("service.install.ok.label"), 1.579 + accessKey: browserBundle.GetStringFromName("service.install.ok.accesskey"), 1.580 + callback: function() { 1.581 + aAddonInstaller.install(); 1.582 + }, 1.583 + }; 1.584 + 1.585 + let options = { 1.586 + learnMoreURL: Services.urlFormatter.formatURLPref("app.support.baseURL") + "social-api", 1.587 + }; 1.588 + let anchor = "servicesInstall-notification-icon"; 1.589 + let notificationid = "servicesInstall"; 1.590 + chromeWin.PopupNotifications.show(browser, notificationid, message, anchor, 1.591 + action, [], options); 1.592 + }, 1.593 + 1.594 + installProvider: function(aDOMDocument, data, installCallback) { 1.595 + let manifest; 1.596 + let installOrigin = aDOMDocument.nodePrincipal.origin; 1.597 + 1.598 + if (data) { 1.599 + let installType = getOriginActivationType(installOrigin); 1.600 + // if we get data, we MUST have a valid manifest generated from the data 1.601 + manifest = this._manifestFromData(installType, data, aDOMDocument.nodePrincipal); 1.602 + if (!manifest) 1.603 + throw new Error("SocialService.installProvider: service configuration is invalid from " + aDOMDocument.location.href); 1.604 + 1.605 + let addon = new AddonWrapper(manifest); 1.606 + if (addon && addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) 1.607 + throw new Error("installProvider: provider with origin [" + 1.608 + installOrigin + "] is blocklisted"); 1.609 + } 1.610 + 1.611 + let id = getAddonIDFromOrigin(installOrigin); 1.612 + AddonManager.getAddonByID(id, function(aAddon) { 1.613 + if (aAddon && aAddon.userDisabled) { 1.614 + aAddon.cancelUninstall(); 1.615 + aAddon.userDisabled = false; 1.616 + } 1.617 + schedule(function () { 1.618 + this._installProvider(aDOMDocument, manifest, aManifest => { 1.619 + this._notifyProviderListeners("provider-installed", aManifest.origin); 1.620 + installCallback(aManifest); 1.621 + }); 1.622 + }.bind(this)); 1.623 + }.bind(this)); 1.624 + }, 1.625 + 1.626 + _installProvider: function(aDOMDocument, manifest, installCallback) { 1.627 + let sourceURI = aDOMDocument.location.href; 1.628 + let installOrigin = aDOMDocument.nodePrincipal.origin; 1.629 + 1.630 + let installType = getOriginActivationType(installOrigin); 1.631 + let installer; 1.632 + switch(installType) { 1.633 + case "foreign": 1.634 + if (!Services.prefs.getBoolPref("social.remote-install.enabled")) 1.635 + throw new Error("Remote install of services is disabled"); 1.636 + if (!manifest) 1.637 + throw new Error("Cannot install provider without manifest data"); 1.638 + 1.639 + installer = new AddonInstaller(sourceURI, manifest, installCallback); 1.640 + this._showInstallNotification(aDOMDocument, installer); 1.641 + break; 1.642 + case "builtin": 1.643 + // for builtin, we already have a manifest, but it can be overridden 1.644 + // we need to return the manifest in the installcallback, so fetch 1.645 + // it if we have it. If there is no manifest data for the builtin, 1.646 + // the install request MUST be from the provider, otherwise we have 1.647 + // no way to know what provider we're trying to enable. This is 1.648 + // primarily an issue for "version zero" providers that did not 1.649 + // send the manifest with the dom event for activation. 1.650 + if (!manifest) { 1.651 + let prefname = getPrefnameFromOrigin(installOrigin); 1.652 + manifest = Services.prefs.getDefaultBranch(null) 1.653 + .getComplexValue(prefname, Ci.nsISupportsString).data; 1.654 + manifest = JSON.parse(manifest); 1.655 + // ensure we override a builtin manifest by having a different value in it 1.656 + if (manifest.builtin) 1.657 + delete manifest.builtin; 1.658 + } 1.659 + case "directory": 1.660 + // a manifest is requried, and will have been vetted by reviewers 1.661 + case "whitelist": 1.662 + // a manifest is required, we'll catch a missing manifest below. 1.663 + if (!manifest) 1.664 + throw new Error("Cannot install provider without manifest data"); 1.665 + installer = new AddonInstaller(sourceURI, manifest, installCallback); 1.666 + this._showInstallNotification(aDOMDocument, installer); 1.667 + break; 1.668 + default: 1.669 + throw new Error("SocialService.installProvider: Invalid install type "+installType+"\n"); 1.670 + break; 1.671 + } 1.672 + }, 1.673 + 1.674 + createWrapper: function(manifest) { 1.675 + return new AddonWrapper(manifest); 1.676 + }, 1.677 + 1.678 + /** 1.679 + * updateProvider is used from the worker to self-update. Since we do not 1.680 + * have knowledge of the currently selected provider here, we will notify 1.681 + * the front end to deal with any reload. 1.682 + */ 1.683 + updateProvider: function(aUpdateOrigin, aManifest) { 1.684 + let originUri = Services.io.newURI(aUpdateOrigin, null, null); 1.685 + let principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(originUri); 1.686 + let installType = this.getOriginActivationType(aUpdateOrigin); 1.687 + // if we get data, we MUST have a valid manifest generated from the data 1.688 + let manifest = this._manifestFromData(installType, aManifest, principal); 1.689 + if (!manifest) 1.690 + throw new Error("SocialService.installProvider: service configuration is invalid from " + aUpdateOrigin); 1.691 + 1.692 + // overwrite the preference 1.693 + let string = Cc["@mozilla.org/supports-string;1"]. 1.694 + createInstance(Ci.nsISupportsString); 1.695 + string.data = JSON.stringify(manifest); 1.696 + Services.prefs.setComplexValue(getPrefnameFromOrigin(manifest.origin), Ci.nsISupportsString, string); 1.697 + 1.698 + // overwrite the existing provider then notify the front end so it can 1.699 + // handle any reload that might be necessary. 1.700 + if (ActiveProviders.has(manifest.origin)) { 1.701 + let provider = new SocialProvider(manifest); 1.702 + SocialServiceInternal.providers[provider.origin] = provider; 1.703 + // update the cache and ui, reload provider if necessary 1.704 + this.getOrderedProviderList(providers => { 1.705 + this._notifyProviderListeners("provider-update", provider.origin, providers); 1.706 + }); 1.707 + } 1.708 + 1.709 + }, 1.710 + 1.711 + uninstallProvider: function(origin, aCallback) { 1.712 + let manifest = SocialService.getManifestByOrigin(origin); 1.713 + let addon = new AddonWrapper(manifest); 1.714 + addon.uninstall(aCallback); 1.715 + } 1.716 +}; 1.717 + 1.718 +/** 1.719 + * The SocialProvider object represents a social provider, and allows 1.720 + * access to its FrameWorker (if it has one). 1.721 + * 1.722 + * @constructor 1.723 + * @param {jsobj} object representing the manifest file describing this provider 1.724 + * @param {bool} boolean indicating whether this provider is "built in" 1.725 + */ 1.726 +function SocialProvider(input) { 1.727 + if (!input.name) 1.728 + throw new Error("SocialProvider must be passed a name"); 1.729 + if (!input.origin) 1.730 + throw new Error("SocialProvider must be passed an origin"); 1.731 + 1.732 + let addon = new AddonWrapper(input); 1.733 + if (addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) 1.734 + throw new Error("SocialProvider: provider with origin [" + 1.735 + input.origin + "] is blocklisted"); 1.736 + 1.737 + this.name = input.name; 1.738 + this.iconURL = input.iconURL; 1.739 + this.icon32URL = input.icon32URL; 1.740 + this.icon64URL = input.icon64URL; 1.741 + this.workerURL = input.workerURL; 1.742 + this.sidebarURL = input.sidebarURL; 1.743 + this.shareURL = input.shareURL; 1.744 + this.statusURL = input.statusURL; 1.745 + this.markURL = input.markURL; 1.746 + this.markedIcon = input.markedIcon; 1.747 + this.unmarkedIcon = input.unmarkedIcon; 1.748 + this.postActivationURL = input.postActivationURL; 1.749 + this.origin = input.origin; 1.750 + let originUri = Services.io.newURI(input.origin, null, null); 1.751 + this.principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(originUri); 1.752 + this.ambientNotificationIcons = {}; 1.753 + this.errorState = null; 1.754 + this.frecency = 0; 1.755 + 1.756 + let activationType = getOriginActivationType(input.origin); 1.757 + this.blessed = activationType == "builtin" || 1.758 + activationType == "whitelist"; 1.759 + 1.760 + try { 1.761 + this.domain = etld.getBaseDomainFromHost(originUri.host); 1.762 + } catch(e) { 1.763 + this.domain = originUri.host; 1.764 + } 1.765 +} 1.766 + 1.767 +SocialProvider.prototype = { 1.768 + reload: function() { 1.769 + this._terminate(); 1.770 + this._activate(); 1.771 + Services.obs.notifyObservers(null, "social:provider-reload", this.origin); 1.772 + }, 1.773 + 1.774 + // Provider enabled/disabled state. Disabled providers do not have active 1.775 + // connections to their FrameWorkers. 1.776 + _enabled: false, 1.777 + get enabled() { 1.778 + return this._enabled; 1.779 + }, 1.780 + set enabled(val) { 1.781 + let enable = !!val; 1.782 + if (enable == this._enabled) 1.783 + return; 1.784 + 1.785 + this._enabled = enable; 1.786 + 1.787 + if (enable) { 1.788 + this._activate(); 1.789 + } else { 1.790 + this._terminate(); 1.791 + } 1.792 + }, 1.793 + 1.794 + get manifest() { 1.795 + return SocialService.getManifestByOrigin(this.origin); 1.796 + }, 1.797 + 1.798 + // Reference to a workerAPI object for this provider. Null if the provider has 1.799 + // no FrameWorker, or is disabled. 1.800 + workerAPI: null, 1.801 + 1.802 + // Contains information related to the user's profile. Populated by the 1.803 + // workerAPI via updateUserProfile. 1.804 + // Properties: 1.805 + // iconURL, portrait, userName, displayName, profileURL 1.806 + // See https://github.com/mozilla/socialapi-dev/blob/develop/docs/socialAPI.md 1.807 + // A value of null or an empty object means 'user not logged in'. 1.808 + // A value of undefined means the service has not yet told us the status of 1.809 + // the profile (ie, the service is still loading/initing, or the provider has 1.810 + // no FrameWorker) 1.811 + // This distinction might be used to cache certain data between runs - eg, 1.812 + // browser-social.js caches the notification icons so they can be displayed 1.813 + // quickly at startup without waiting for the provider to initialize - 1.814 + // 'undefined' means 'ok to use cached values' versus 'null' meaning 'cached 1.815 + // values aren't to be used as the user is logged out'. 1.816 + profile: undefined, 1.817 + 1.818 + // Map of objects describing the provider's notification icons, whose 1.819 + // properties include: 1.820 + // name, iconURL, counter, contentPanel 1.821 + // See https://developer.mozilla.org/en-US/docs/Social_API 1.822 + ambientNotificationIcons: null, 1.823 + 1.824 + // Called by the workerAPI to update our profile information. 1.825 + updateUserProfile: function(profile) { 1.826 + if (!profile) 1.827 + profile = {}; 1.828 + let accountChanged = !this.profile || this.profile.userName != profile.userName; 1.829 + this.profile = profile; 1.830 + 1.831 + // Sanitize the portrait from any potential script-injection. 1.832 + if (profile.portrait) { 1.833 + try { 1.834 + let portraitUri = Services.io.newURI(profile.portrait, null, null); 1.835 + 1.836 + let scheme = portraitUri ? portraitUri.scheme : ""; 1.837 + if (scheme != "data" && scheme != "http" && scheme != "https") { 1.838 + profile.portrait = ""; 1.839 + } 1.840 + } catch (ex) { 1.841 + profile.portrait = ""; 1.842 + } 1.843 + } 1.844 + 1.845 + if (profile.iconURL) 1.846 + this.iconURL = profile.iconURL; 1.847 + 1.848 + if (!profile.displayName) 1.849 + profile.displayName = profile.userName; 1.850 + 1.851 + // if no userName, consider this a logged out state, emtpy the 1.852 + // users ambient notifications. notify both profile and ambient 1.853 + // changes to clear everything 1.854 + if (!profile.userName) { 1.855 + this.profile = {}; 1.856 + this.ambientNotificationIcons = {}; 1.857 + Services.obs.notifyObservers(null, "social:ambient-notification-changed", this.origin); 1.858 + } 1.859 + 1.860 + Services.obs.notifyObservers(null, "social:profile-changed", this.origin); 1.861 + if (accountChanged) 1.862 + closeAllChatWindows(this); 1.863 + }, 1.864 + 1.865 + haveLoggedInUser: function () { 1.866 + return !!(this.profile && this.profile.userName); 1.867 + }, 1.868 + 1.869 + // Called by the workerAPI to add/update a notification icon. 1.870 + setAmbientNotification: function(notification) { 1.871 + if (!this.profile.userName) 1.872 + throw new Error("unable to set notifications while logged out"); 1.873 + if (!this.ambientNotificationIcons[notification.name] && 1.874 + Object.keys(this.ambientNotificationIcons).length >= 3) { 1.875 + throw new Error("ambient notification limit reached"); 1.876 + } 1.877 + this.ambientNotificationIcons[notification.name] = notification; 1.878 + 1.879 + Services.obs.notifyObservers(null, "social:ambient-notification-changed", this.origin); 1.880 + }, 1.881 + 1.882 + // Internal helper methods 1.883 + _activate: function _activate() { 1.884 + // Initialize the workerAPI and its port first, so that its initialization 1.885 + // occurs before any other messages are processed by other ports. 1.886 + let workerAPIPort = this.getWorkerPort(); 1.887 + if (workerAPIPort) 1.888 + this.workerAPI = new WorkerAPI(this, workerAPIPort); 1.889 + }, 1.890 + 1.891 + _terminate: function _terminate() { 1.892 + closeAllChatWindows(this); 1.893 + if (this.workerURL) { 1.894 + try { 1.895 + getFrameWorkerHandle(this.workerURL).terminate(); 1.896 + } catch (e) { 1.897 + Cu.reportError("SocialProvider FrameWorker termination failed: " + e); 1.898 + } 1.899 + } 1.900 + if (this.workerAPI) { 1.901 + this.workerAPI.terminate(); 1.902 + } 1.903 + this.errorState = null; 1.904 + this.workerAPI = null; 1.905 + this.profile = undefined; 1.906 + }, 1.907 + 1.908 + /** 1.909 + * Instantiates a FrameWorker for the provider if one doesn't exist, and 1.910 + * returns a reference to a new port to that FrameWorker. 1.911 + * 1.912 + * Returns null if this provider has no workerURL, or is disabled. 1.913 + * 1.914 + * @param {DOMWindow} window (optional) 1.915 + */ 1.916 + getWorkerPort: function getWorkerPort(window) { 1.917 + if (!this.workerURL || !this.enabled) 1.918 + return null; 1.919 + // Only allow localStorage in the frameworker for blessed providers 1.920 + let allowLocalStorage = this.blessed; 1.921 + let handle = getFrameWorkerHandle(this.workerURL, window, 1.922 + "SocialProvider:" + this.origin, this.origin, 1.923 + allowLocalStorage); 1.924 + return handle.port; 1.925 + }, 1.926 + 1.927 + /** 1.928 + * Checks if a given URI is of the same origin as the provider. 1.929 + * 1.930 + * Returns true or false. 1.931 + * 1.932 + * @param {URI or string} uri 1.933 + */ 1.934 + isSameOrigin: function isSameOrigin(uri, allowIfInheritsPrincipal) { 1.935 + if (!uri) 1.936 + return false; 1.937 + if (typeof uri == "string") { 1.938 + try { 1.939 + uri = Services.io.newURI(uri, null, null); 1.940 + } catch (ex) { 1.941 + // an invalid URL can't be loaded! 1.942 + return false; 1.943 + } 1.944 + } 1.945 + try { 1.946 + this.principal.checkMayLoad( 1.947 + uri, // the thing to check. 1.948 + false, // reportError - we do our own reporting when necessary. 1.949 + allowIfInheritsPrincipal 1.950 + ); 1.951 + return true; 1.952 + } catch (ex) { 1.953 + return false; 1.954 + } 1.955 + }, 1.956 + 1.957 + /** 1.958 + * Resolve partial URLs for a provider. 1.959 + * 1.960 + * Returns nsIURI object or null on failure 1.961 + * 1.962 + * @param {string} url 1.963 + */ 1.964 + resolveUri: function resolveUri(url) { 1.965 + try { 1.966 + let fullURL = this.principal.URI.resolve(url); 1.967 + return Services.io.newURI(fullURL, null, null); 1.968 + } catch (ex) { 1.969 + Cu.reportError("mozSocial: failed to resolve window URL: " + url + "; " + ex); 1.970 + return null; 1.971 + } 1.972 + } 1.973 +} 1.974 + 1.975 +function getAddonIDFromOrigin(origin) { 1.976 + let originUri = Services.io.newURI(origin, null, null); 1.977 + return originUri.host + ID_SUFFIX; 1.978 +} 1.979 + 1.980 +function getPrefnameFromOrigin(origin) { 1.981 + return "social.manifest." + SocialServiceInternal.getManifestPrefname(origin); 1.982 +} 1.983 + 1.984 +function AddonInstaller(sourceURI, aManifest, installCallback) { 1.985 + aManifest.updateDate = Date.now(); 1.986 + // get the existing manifest for installDate 1.987 + let manifest = SocialService.getManifestByOrigin(aManifest.origin); 1.988 + let isNewInstall = !manifest; 1.989 + if (manifest && manifest.installDate) 1.990 + aManifest.installDate = manifest.installDate; 1.991 + else 1.992 + aManifest.installDate = aManifest.updateDate; 1.993 + 1.994 + this.sourceURI = sourceURI; 1.995 + this.install = function() { 1.996 + let addon = this.addon; 1.997 + if (isNewInstall) { 1.998 + AddonManagerPrivate.callInstallListeners("onExternalInstall", null, addon, null, false); 1.999 + AddonManagerPrivate.callAddonListeners("onInstalling", addon, false); 1.1000 + } 1.1001 + 1.1002 + let string = Cc["@mozilla.org/supports-string;1"]. 1.1003 + createInstance(Ci.nsISupportsString); 1.1004 + string.data = JSON.stringify(aManifest); 1.1005 + Services.prefs.setComplexValue(getPrefnameFromOrigin(aManifest.origin), Ci.nsISupportsString, string); 1.1006 + 1.1007 + if (isNewInstall) { 1.1008 + AddonManagerPrivate.callAddonListeners("onInstalled", addon); 1.1009 + } 1.1010 + installCallback(aManifest); 1.1011 + }; 1.1012 + this.cancel = function() { 1.1013 + Services.prefs.clearUserPref(getPrefnameFromOrigin(aManifest.origin)) 1.1014 + }, 1.1015 + this.addon = new AddonWrapper(aManifest); 1.1016 +}; 1.1017 + 1.1018 +var SocialAddonProvider = { 1.1019 + startup: function() {}, 1.1020 + 1.1021 + shutdown: function() {}, 1.1022 + 1.1023 + updateAddonAppDisabledStates: function() { 1.1024 + // we wont bother with "enabling" services that are released from blocklist 1.1025 + for (let manifest of SocialServiceInternal.manifests) { 1.1026 + try { 1.1027 + if (ActiveProviders.has(manifest.origin)) { 1.1028 + let addon = new AddonWrapper(manifest); 1.1029 + if (addon.blocklistState != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) { 1.1030 + SocialService.removeProvider(manifest.origin); 1.1031 + } 1.1032 + } 1.1033 + } catch(e) { 1.1034 + Cu.reportError(e); 1.1035 + } 1.1036 + } 1.1037 + }, 1.1038 + 1.1039 + getAddonByID: function(aId, aCallback) { 1.1040 + for (let manifest of SocialServiceInternal.manifests) { 1.1041 + if (aId == getAddonIDFromOrigin(manifest.origin)) { 1.1042 + aCallback(new AddonWrapper(manifest)); 1.1043 + return; 1.1044 + } 1.1045 + } 1.1046 + aCallback(null); 1.1047 + }, 1.1048 + 1.1049 + getAddonsByTypes: function(aTypes, aCallback) { 1.1050 + if (aTypes && aTypes.indexOf(ADDON_TYPE_SERVICE) == -1) { 1.1051 + aCallback([]); 1.1052 + return; 1.1053 + } 1.1054 + aCallback([new AddonWrapper(a) for each (a in SocialServiceInternal.manifests)]); 1.1055 + }, 1.1056 + 1.1057 + removeAddon: function(aAddon, aCallback) { 1.1058 + AddonManagerPrivate.callAddonListeners("onUninstalling", aAddon, false); 1.1059 + aAddon.pendingOperations |= AddonManager.PENDING_UNINSTALL; 1.1060 + Services.prefs.clearUserPref(getPrefnameFromOrigin(aAddon.manifest.origin)); 1.1061 + aAddon.pendingOperations -= AddonManager.PENDING_UNINSTALL; 1.1062 + AddonManagerPrivate.callAddonListeners("onUninstalled", aAddon); 1.1063 + SocialService._notifyProviderListeners("provider-uninstalled", aAddon.manifest.origin); 1.1064 + if (aCallback) 1.1065 + schedule(aCallback); 1.1066 + } 1.1067 +} 1.1068 + 1.1069 + 1.1070 +function AddonWrapper(aManifest) { 1.1071 + this.manifest = aManifest; 1.1072 + this.id = getAddonIDFromOrigin(this.manifest.origin); 1.1073 + this._pending = AddonManager.PENDING_NONE; 1.1074 +} 1.1075 +AddonWrapper.prototype = { 1.1076 + get type() { 1.1077 + return ADDON_TYPE_SERVICE; 1.1078 + }, 1.1079 + 1.1080 + get appDisabled() { 1.1081 + return this.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED; 1.1082 + }, 1.1083 + 1.1084 + set softDisabled(val) { 1.1085 + this.userDisabled = val; 1.1086 + }, 1.1087 + 1.1088 + get softDisabled() { 1.1089 + return this.userDisabled; 1.1090 + }, 1.1091 + 1.1092 + get isCompatible() { 1.1093 + return true; 1.1094 + }, 1.1095 + 1.1096 + get isPlatformCompatible() { 1.1097 + return true; 1.1098 + }, 1.1099 + 1.1100 + get scope() { 1.1101 + return AddonManager.SCOPE_PROFILE; 1.1102 + }, 1.1103 + 1.1104 + get foreignInstall() { 1.1105 + return false; 1.1106 + }, 1.1107 + 1.1108 + isCompatibleWith: function(appVersion, platformVersion) { 1.1109 + return true; 1.1110 + }, 1.1111 + 1.1112 + get providesUpdatesSecurely() { 1.1113 + return true; 1.1114 + }, 1.1115 + 1.1116 + get blocklistState() { 1.1117 + return Services.blocklist.getAddonBlocklistState(this); 1.1118 + }, 1.1119 + 1.1120 + get blocklistURL() { 1.1121 + return Services.blocklist.getAddonBlocklistURL(this); 1.1122 + }, 1.1123 + 1.1124 + get screenshots() { 1.1125 + return []; 1.1126 + }, 1.1127 + 1.1128 + get pendingOperations() { 1.1129 + return this._pending || AddonManager.PENDING_NONE; 1.1130 + }, 1.1131 + set pendingOperations(val) { 1.1132 + this._pending = val; 1.1133 + }, 1.1134 + 1.1135 + get operationsRequiringRestart() { 1.1136 + return AddonManager.OP_NEEDS_RESTART_NONE; 1.1137 + }, 1.1138 + 1.1139 + get size() { 1.1140 + return null; 1.1141 + }, 1.1142 + 1.1143 + get permissions() { 1.1144 + let permissions = 0; 1.1145 + // any "user defined" manifest can be removed 1.1146 + if (Services.prefs.prefHasUserValue(getPrefnameFromOrigin(this.manifest.origin))) 1.1147 + permissions = AddonManager.PERM_CAN_UNINSTALL; 1.1148 + if (!this.appDisabled) { 1.1149 + if (this.userDisabled) { 1.1150 + permissions |= AddonManager.PERM_CAN_ENABLE; 1.1151 + } else { 1.1152 + permissions |= AddonManager.PERM_CAN_DISABLE; 1.1153 + } 1.1154 + } 1.1155 + return permissions; 1.1156 + }, 1.1157 + 1.1158 + findUpdates: function(listener, reason, appVersion, platformVersion) { 1.1159 + if ("onNoCompatibilityUpdateAvailable" in listener) 1.1160 + listener.onNoCompatibilityUpdateAvailable(this); 1.1161 + if ("onNoUpdateAvailable" in listener) 1.1162 + listener.onNoUpdateAvailable(this); 1.1163 + if ("onUpdateFinished" in listener) 1.1164 + listener.onUpdateFinished(this); 1.1165 + }, 1.1166 + 1.1167 + get isActive() { 1.1168 + return ActiveProviders.has(this.manifest.origin); 1.1169 + }, 1.1170 + 1.1171 + get name() { 1.1172 + return this.manifest.name; 1.1173 + }, 1.1174 + get version() { 1.1175 + return this.manifest.version ? this.manifest.version : ""; 1.1176 + }, 1.1177 + 1.1178 + get iconURL() { 1.1179 + return this.manifest.icon32URL ? this.manifest.icon32URL : this.manifest.iconURL; 1.1180 + }, 1.1181 + get icon64URL() { 1.1182 + return this.manifest.icon64URL; 1.1183 + }, 1.1184 + get icons() { 1.1185 + let icons = { 1.1186 + 16: this.manifest.iconURL 1.1187 + }; 1.1188 + if (this.manifest.icon32URL) 1.1189 + icons[32] = this.manifest.icon32URL; 1.1190 + if (this.manifest.icon64URL) 1.1191 + icons[64] = this.manifest.icon64URL; 1.1192 + return icons; 1.1193 + }, 1.1194 + 1.1195 + get description() { 1.1196 + return this.manifest.description; 1.1197 + }, 1.1198 + get homepageURL() { 1.1199 + return this.manifest.homepageURL; 1.1200 + }, 1.1201 + get defaultLocale() { 1.1202 + return this.manifest.defaultLocale; 1.1203 + }, 1.1204 + get selectedLocale() { 1.1205 + return this.manifest.selectedLocale; 1.1206 + }, 1.1207 + 1.1208 + get installDate() { 1.1209 + return this.manifest.installDate ? new Date(this.manifest.installDate) : null; 1.1210 + }, 1.1211 + get updateDate() { 1.1212 + return this.manifest.updateDate ? new Date(this.manifest.updateDate) : null; 1.1213 + }, 1.1214 + 1.1215 + get creator() { 1.1216 + return new AddonManagerPrivate.AddonAuthor(this.manifest.author); 1.1217 + }, 1.1218 + 1.1219 + get userDisabled() { 1.1220 + return this.appDisabled || !ActiveProviders.has(this.manifest.origin); 1.1221 + }, 1.1222 + 1.1223 + set userDisabled(val) { 1.1224 + if (val == this.userDisabled) 1.1225 + return val; 1.1226 + if (val) { 1.1227 + SocialService.removeProvider(this.manifest.origin); 1.1228 + } else if (!this.appDisabled) { 1.1229 + SocialService.addBuiltinProvider(this.manifest.origin); 1.1230 + } 1.1231 + return val; 1.1232 + }, 1.1233 + 1.1234 + uninstall: function(aCallback) { 1.1235 + let prefName = getPrefnameFromOrigin(this.manifest.origin); 1.1236 + if (Services.prefs.prefHasUserValue(prefName)) { 1.1237 + if (ActiveProviders.has(this.manifest.origin)) { 1.1238 + SocialService.removeProvider(this.manifest.origin, function() { 1.1239 + SocialAddonProvider.removeAddon(this, aCallback); 1.1240 + }.bind(this)); 1.1241 + } else { 1.1242 + SocialAddonProvider.removeAddon(this, aCallback); 1.1243 + } 1.1244 + } else { 1.1245 + schedule(aCallback); 1.1246 + } 1.1247 + }, 1.1248 + 1.1249 + cancelUninstall: function() { 1.1250 + this._pending -= AddonManager.PENDING_UNINSTALL; 1.1251 + AddonManagerPrivate.callAddonListeners("onOperationCancelled", this); 1.1252 + } 1.1253 +}; 1.1254 + 1.1255 + 1.1256 +AddonManagerPrivate.registerProvider(SocialAddonProvider, [ 1.1257 + new AddonManagerPrivate.AddonType(ADDON_TYPE_SERVICE, URI_EXTENSION_STRINGS, 1.1258 + STRING_TYPE_NAME, 1.1259 + AddonManager.VIEW_TYPE_LIST, 10000) 1.1260 +]);