toolkit/components/social/SocialService.jsm

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
michael@0 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 this.EXPORTED_SYMBOLS = ["SocialService"];
michael@0 6
michael@0 7 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
michael@0 8
michael@0 9 Cu.import("resource://gre/modules/Services.jsm");
michael@0 10 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 11 Cu.import("resource://gre/modules/AddonManager.jsm");
michael@0 12 Cu.import("resource://gre/modules/PlacesUtils.jsm");
michael@0 13
michael@0 14 const URI_EXTENSION_STRINGS = "chrome://mozapps/locale/extensions/extensions.properties";
michael@0 15 const ADDON_TYPE_SERVICE = "service";
michael@0 16 const ID_SUFFIX = "@services.mozilla.org";
michael@0 17 const STRING_TYPE_NAME = "type.%ID%.name";
michael@0 18
michael@0 19 XPCOMUtils.defineLazyModuleGetter(this, "getFrameWorkerHandle", "resource://gre/modules/FrameWorker.jsm");
michael@0 20 XPCOMUtils.defineLazyModuleGetter(this, "WorkerAPI", "resource://gre/modules/WorkerAPI.jsm");
michael@0 21 XPCOMUtils.defineLazyModuleGetter(this, "MozSocialAPI", "resource://gre/modules/MozSocialAPI.jsm");
michael@0 22 XPCOMUtils.defineLazyModuleGetter(this, "closeAllChatWindows", "resource://gre/modules/MozSocialAPI.jsm");
michael@0 23 XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask", "resource://gre/modules/DeferredTask.jsm");
michael@0 24
michael@0 25 XPCOMUtils.defineLazyServiceGetter(this, "etld",
michael@0 26 "@mozilla.org/network/effective-tld-service;1",
michael@0 27 "nsIEffectiveTLDService");
michael@0 28
michael@0 29 /**
michael@0 30 * The SocialService is the public API to social providers - it tracks which
michael@0 31 * providers are installed and enabled, and is the entry-point for access to
michael@0 32 * the provider itself.
michael@0 33 */
michael@0 34
michael@0 35 // Internal helper methods and state
michael@0 36 let SocialServiceInternal = {
michael@0 37 get enabled() this.providerArray.length > 0,
michael@0 38
michael@0 39 get providerArray() {
michael@0 40 return [p for ([, p] of Iterator(this.providers))];
michael@0 41 },
michael@0 42 get manifests() {
michael@0 43 // Retrieve the manifests of installed providers from prefs
michael@0 44 let MANIFEST_PREFS = Services.prefs.getBranch("social.manifest.");
michael@0 45 let prefs = MANIFEST_PREFS.getChildList("", []);
michael@0 46 for (let pref of prefs) {
michael@0 47 // we only consider manifests in user level prefs to be *installed*
michael@0 48 if (!MANIFEST_PREFS.prefHasUserValue(pref))
michael@0 49 continue;
michael@0 50 try {
michael@0 51 var manifest = JSON.parse(MANIFEST_PREFS.getComplexValue(pref, Ci.nsISupportsString).data);
michael@0 52 if (manifest && typeof(manifest) == "object" && manifest.origin)
michael@0 53 yield manifest;
michael@0 54 } catch (err) {
michael@0 55 Cu.reportError("SocialService: failed to load manifest: " + pref +
michael@0 56 ", exception: " + err);
michael@0 57 }
michael@0 58 }
michael@0 59 },
michael@0 60 getManifestPrefname: function(origin) {
michael@0 61 // Retrieve the prefname for a given origin/manifest.
michael@0 62 // If no existing pref, return a generated prefname.
michael@0 63 let MANIFEST_PREFS = Services.prefs.getBranch("social.manifest.");
michael@0 64 let prefs = MANIFEST_PREFS.getChildList("", []);
michael@0 65 for (let pref of prefs) {
michael@0 66 try {
michael@0 67 var manifest = JSON.parse(MANIFEST_PREFS.getComplexValue(pref, Ci.nsISupportsString).data);
michael@0 68 if (manifest.origin == origin) {
michael@0 69 return pref;
michael@0 70 }
michael@0 71 } catch (err) {
michael@0 72 Cu.reportError("SocialService: failed to load manifest: " + pref +
michael@0 73 ", exception: " + err);
michael@0 74 }
michael@0 75 }
michael@0 76 let originUri = Services.io.newURI(origin, null, null);
michael@0 77 return originUri.hostPort.replace('.','-');
michael@0 78 },
michael@0 79 orderedProviders: function(aCallback) {
michael@0 80 if (SocialServiceInternal.providerArray.length < 2) {
michael@0 81 schedule(function () {
michael@0 82 aCallback(SocialServiceInternal.providerArray);
michael@0 83 });
michael@0 84 return;
michael@0 85 }
michael@0 86 // query moz_hosts for frecency. since some providers may not have a
michael@0 87 // frecency entry, we need to later sort on our own. We use the providers
michael@0 88 // object below as an easy way to later record the frecency on the provider
michael@0 89 // object from the query results.
michael@0 90 let hosts = [];
michael@0 91 let providers = {};
michael@0 92
michael@0 93 for (let p of SocialServiceInternal.providerArray) {
michael@0 94 p.frecency = 0;
michael@0 95 providers[p.domain] = p;
michael@0 96 hosts.push(p.domain);
michael@0 97 };
michael@0 98
michael@0 99 // cannot bind an array to stmt.params so we have to build the string
michael@0 100 let stmt = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
michael@0 101 .DBConnection.createAsyncStatement(
michael@0 102 "SELECT host, frecency FROM moz_hosts WHERE host IN (" +
michael@0 103 [ '"' + host + '"' for each (host in hosts) ].join(",") + ") "
michael@0 104 );
michael@0 105
michael@0 106 try {
michael@0 107 stmt.executeAsync({
michael@0 108 handleResult: function(aResultSet) {
michael@0 109 let row;
michael@0 110 while ((row = aResultSet.getNextRow())) {
michael@0 111 let rh = row.getResultByName("host");
michael@0 112 let frecency = row.getResultByName("frecency");
michael@0 113 providers[rh].frecency = parseInt(frecency) || 0;
michael@0 114 }
michael@0 115 },
michael@0 116 handleError: function(aError) {
michael@0 117 Cu.reportError(aError.message + " (Result = " + aError.result + ")");
michael@0 118 },
michael@0 119 handleCompletion: function(aReason) {
michael@0 120 // the query may not have returned all our providers, so we have
michael@0 121 // stamped the frecency on the provider and sort here. This makes sure
michael@0 122 // all enabled providers get sorted even with frecency zero.
michael@0 123 let providerList = SocialServiceInternal.providerArray;
michael@0 124 // reverse sort
michael@0 125 aCallback(providerList.sort(function(a, b) b.frecency - a.frecency));
michael@0 126 }
michael@0 127 });
michael@0 128 } finally {
michael@0 129 stmt.finalize();
michael@0 130 }
michael@0 131 }
michael@0 132 };
michael@0 133
michael@0 134 XPCOMUtils.defineLazyGetter(SocialServiceInternal, "providers", function () {
michael@0 135 initService();
michael@0 136 let providers = {};
michael@0 137 for (let manifest of this.manifests) {
michael@0 138 try {
michael@0 139 if (ActiveProviders.has(manifest.origin)) {
michael@0 140 // enable the api when a provider is enabled
michael@0 141 MozSocialAPI.enabled = true;
michael@0 142 let provider = new SocialProvider(manifest);
michael@0 143 providers[provider.origin] = provider;
michael@0 144 }
michael@0 145 } catch (err) {
michael@0 146 Cu.reportError("SocialService: failed to load provider: " + manifest.origin +
michael@0 147 ", exception: " + err);
michael@0 148 }
michael@0 149 }
michael@0 150 return providers;
michael@0 151 });
michael@0 152
michael@0 153 function getOriginActivationType(origin) {
michael@0 154 let prefname = SocialServiceInternal.getManifestPrefname(origin);
michael@0 155 if (Services.prefs.getDefaultBranch("social.manifest.").getPrefType(prefname) == Services.prefs.PREF_STRING)
michael@0 156 return 'builtin';
michael@0 157
michael@0 158 let whitelist = Services.prefs.getCharPref("social.whitelist").split(',');
michael@0 159 if (whitelist.indexOf(origin) >= 0)
michael@0 160 return 'whitelist';
michael@0 161
michael@0 162 let directories = Services.prefs.getCharPref("social.directories").split(',');
michael@0 163 if (directories.indexOf(origin) >= 0)
michael@0 164 return 'directory';
michael@0 165
michael@0 166 return 'foreign';
michael@0 167 }
michael@0 168
michael@0 169 let ActiveProviders = {
michael@0 170 get _providers() {
michael@0 171 delete this._providers;
michael@0 172 this._providers = {};
michael@0 173 try {
michael@0 174 let pref = Services.prefs.getComplexValue("social.activeProviders",
michael@0 175 Ci.nsISupportsString);
michael@0 176 this._providers = JSON.parse(pref);
michael@0 177 } catch(ex) {}
michael@0 178 return this._providers;
michael@0 179 },
michael@0 180
michael@0 181 has: function (origin) {
michael@0 182 return (origin in this._providers);
michael@0 183 },
michael@0 184
michael@0 185 add: function (origin) {
michael@0 186 this._providers[origin] = 1;
michael@0 187 this._deferredTask.arm();
michael@0 188 },
michael@0 189
michael@0 190 delete: function (origin) {
michael@0 191 delete this._providers[origin];
michael@0 192 this._deferredTask.arm();
michael@0 193 },
michael@0 194
michael@0 195 flush: function () {
michael@0 196 this._deferredTask.disarm();
michael@0 197 this._persist();
michael@0 198 },
michael@0 199
michael@0 200 get _deferredTask() {
michael@0 201 delete this._deferredTask;
michael@0 202 return this._deferredTask = new DeferredTask(this._persist.bind(this), 0);
michael@0 203 },
michael@0 204
michael@0 205 _persist: function () {
michael@0 206 let string = Cc["@mozilla.org/supports-string;1"].
michael@0 207 createInstance(Ci.nsISupportsString);
michael@0 208 string.data = JSON.stringify(this._providers);
michael@0 209 Services.prefs.setComplexValue("social.activeProviders",
michael@0 210 Ci.nsISupportsString, string);
michael@0 211 }
michael@0 212 };
michael@0 213
michael@0 214 function migrateSettings() {
michael@0 215 let activeProviders, enabled;
michael@0 216 try {
michael@0 217 activeProviders = Services.prefs.getCharPref("social.activeProviders");
michael@0 218 } catch(e) {
michael@0 219 // not set, we'll check if we need to migrate older prefs
michael@0 220 }
michael@0 221 if (Services.prefs.prefHasUserValue("social.enabled")) {
michael@0 222 enabled = Services.prefs.getBoolPref("social.enabled");
michael@0 223 }
michael@0 224 if (activeProviders) {
michael@0 225 // migration from fx21 to fx22 or later
michael@0 226 // ensure any *builtin* provider in activeproviders is in user level prefs
michael@0 227 for (let origin in ActiveProviders._providers) {
michael@0 228 let prefname;
michael@0 229 let manifest;
michael@0 230 let defaultManifest;
michael@0 231 try {
michael@0 232 prefname = getPrefnameFromOrigin(origin);
michael@0 233 manifest = JSON.parse(Services.prefs.getComplexValue(prefname, Ci.nsISupportsString).data);
michael@0 234 } catch(e) {
michael@0 235 // Our preference is missing or bad, remove from ActiveProviders and
michael@0 236 // continue. This is primarily an error-case and should only be
michael@0 237 // reached by either messing with preferences or hitting the one or
michael@0 238 // two days of nightly that ran into it, so we'll flush right away.
michael@0 239 ActiveProviders.delete(origin);
michael@0 240 ActiveProviders.flush();
michael@0 241 continue;
michael@0 242 }
michael@0 243 let needsUpdate = !manifest.updateDate;
michael@0 244 // fx23 may have built-ins with shareURL
michael@0 245 try {
michael@0 246 defaultManifest = Services.prefs.getDefaultBranch(null)
michael@0 247 .getComplexValue(prefname, Ci.nsISupportsString).data;
michael@0 248 defaultManifest = JSON.parse(defaultManifest);
michael@0 249 } catch(e) {
michael@0 250 // not a built-in, continue
michael@0 251 }
michael@0 252 if (defaultManifest) {
michael@0 253 if (defaultManifest.shareURL && !manifest.shareURL) {
michael@0 254 manifest.shareURL = defaultManifest.shareURL;
michael@0 255 needsUpdate = true;
michael@0 256 }
michael@0 257 if (defaultManifest.version && (!manifest.version || defaultManifest.version > manifest.version)) {
michael@0 258 manifest = defaultManifest;
michael@0 259 needsUpdate = true;
michael@0 260 }
michael@0 261 }
michael@0 262 if (needsUpdate) {
michael@0 263 // the provider was installed with an older build, so we will update the
michael@0 264 // timestamp and ensure the manifest is in user prefs
michael@0 265 delete manifest.builtin;
michael@0 266 // we're potentially updating for share, so always mark the updateDate
michael@0 267 manifest.updateDate = Date.now();
michael@0 268 if (!manifest.installDate)
michael@0 269 manifest.installDate = 0; // we don't know when it was installed
michael@0 270
michael@0 271 let string = Cc["@mozilla.org/supports-string;1"].
michael@0 272 createInstance(Ci.nsISupportsString);
michael@0 273 string.data = JSON.stringify(manifest);
michael@0 274 Services.prefs.setComplexValue(prefname, Ci.nsISupportsString, string);
michael@0 275 }
michael@0 276 // as of fx 29, we no longer rely on social.enabled. migration from prior
michael@0 277 // versions should disable all service addons if social.enabled=false
michael@0 278 if (enabled === false) {
michael@0 279 ActiveProviders.delete(origin);
michael@0 280 }
michael@0 281 }
michael@0 282 ActiveProviders.flush();
michael@0 283 Services.prefs.clearUserPref("social.enabled");
michael@0 284 return;
michael@0 285 }
michael@0 286
michael@0 287 // primary migration from pre-fx21
michael@0 288 let active;
michael@0 289 try {
michael@0 290 active = Services.prefs.getBoolPref("social.active");
michael@0 291 } catch(e) {}
michael@0 292 if (!active)
michael@0 293 return;
michael@0 294
michael@0 295 // primary difference from SocialServiceInternal.manifests is that we
michael@0 296 // only read the default branch here.
michael@0 297 let manifestPrefs = Services.prefs.getDefaultBranch("social.manifest.");
michael@0 298 let prefs = manifestPrefs.getChildList("", []);
michael@0 299 for (let pref of prefs) {
michael@0 300 try {
michael@0 301 let manifest;
michael@0 302 try {
michael@0 303 manifest = JSON.parse(manifestPrefs.getComplexValue(pref, Ci.nsISupportsString).data);
michael@0 304 } catch(e) {
michael@0 305 // bad or missing preference, we wont update this one.
michael@0 306 continue;
michael@0 307 }
michael@0 308 if (manifest && typeof(manifest) == "object" && manifest.origin) {
michael@0 309 // our default manifests have been updated with the builtin flags as of
michael@0 310 // fx22, delete it so we can set the user-pref
michael@0 311 delete manifest.builtin;
michael@0 312 if (!manifest.updateDate) {
michael@0 313 manifest.updateDate = Date.now();
michael@0 314 manifest.installDate = 0; // we don't know when it was installed
michael@0 315 }
michael@0 316
michael@0 317 let string = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
michael@0 318 string.data = JSON.stringify(manifest);
michael@0 319 // pref here is just the branch name, set the full pref name
michael@0 320 Services.prefs.setComplexValue("social.manifest." + pref, Ci.nsISupportsString, string);
michael@0 321 ActiveProviders.add(manifest.origin);
michael@0 322 ActiveProviders.flush();
michael@0 323 // social.active was used at a time that there was only one
michael@0 324 // builtin, we'll assume that is still the case
michael@0 325 return;
michael@0 326 }
michael@0 327 } catch (err) {
michael@0 328 Cu.reportError("SocialService: failed to load manifest: " + pref + ", exception: " + err);
michael@0 329 }
michael@0 330 }
michael@0 331 }
michael@0 332
michael@0 333 function initService() {
michael@0 334 Services.obs.addObserver(function xpcomShutdown() {
michael@0 335 ActiveProviders.flush();
michael@0 336 SocialService._providerListeners = null;
michael@0 337 Services.obs.removeObserver(xpcomShutdown, "xpcom-shutdown");
michael@0 338 }, "xpcom-shutdown", false);
michael@0 339
michael@0 340 try {
michael@0 341 migrateSettings();
michael@0 342 } catch(e) {
michael@0 343 // no matter what, if migration fails we do not want to render social
michael@0 344 // unusable. Worst case scenario is that, when upgrading Firefox, previously
michael@0 345 // enabled providers are not migrated.
michael@0 346 Cu.reportError("Error migrating social settings: " + e);
michael@0 347 }
michael@0 348 }
michael@0 349
michael@0 350 function schedule(callback) {
michael@0 351 Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL);
michael@0 352 }
michael@0 353
michael@0 354 // Public API
michael@0 355 this.SocialService = {
michael@0 356 get hasEnabledProviders() {
michael@0 357 // used as an optimization during startup, can be used to check if further
michael@0 358 // initialization should be done (e.g. creating the instances of
michael@0 359 // SocialProvider and turning on UI). ActiveProviders may have changed and
michael@0 360 // not yet flushed so we check the active providers array
michael@0 361 for (let p in ActiveProviders._providers) {
michael@0 362 return true;
michael@0 363 };
michael@0 364 return false;
michael@0 365 },
michael@0 366 get enabled() {
michael@0 367 return SocialServiceInternal.enabled;
michael@0 368 },
michael@0 369 set enabled(val) {
michael@0 370 throw new Error("not allowed to set SocialService.enabled");
michael@0 371 },
michael@0 372
michael@0 373 // Adds and activates a builtin provider. The provider may or may not have
michael@0 374 // previously been added. onDone is always called - with null if no such
michael@0 375 // provider exists, or the activated provider on success.
michael@0 376 addBuiltinProvider: function addBuiltinProvider(origin, onDone) {
michael@0 377 if (SocialServiceInternal.providers[origin]) {
michael@0 378 schedule(function() {
michael@0 379 onDone(SocialServiceInternal.providers[origin]);
michael@0 380 });
michael@0 381 return;
michael@0 382 }
michael@0 383 let manifest = SocialService.getManifestByOrigin(origin);
michael@0 384 if (manifest) {
michael@0 385 let addon = new AddonWrapper(manifest);
michael@0 386 AddonManagerPrivate.callAddonListeners("onEnabling", addon, false);
michael@0 387 addon.pendingOperations |= AddonManager.PENDING_ENABLE;
michael@0 388 this.addProvider(manifest, onDone);
michael@0 389 addon.pendingOperations -= AddonManager.PENDING_ENABLE;
michael@0 390 AddonManagerPrivate.callAddonListeners("onEnabled", addon);
michael@0 391 return;
michael@0 392 }
michael@0 393 schedule(function() {
michael@0 394 onDone(null);
michael@0 395 });
michael@0 396 },
michael@0 397
michael@0 398 // Adds a provider given a manifest, and returns the added provider.
michael@0 399 addProvider: function addProvider(manifest, onDone) {
michael@0 400 if (SocialServiceInternal.providers[manifest.origin])
michael@0 401 throw new Error("SocialService.addProvider: provider with this origin already exists");
michael@0 402
michael@0 403 // enable the api when a provider is enabled
michael@0 404 MozSocialAPI.enabled = true;
michael@0 405 let provider = new SocialProvider(manifest);
michael@0 406 SocialServiceInternal.providers[provider.origin] = provider;
michael@0 407 ActiveProviders.add(provider.origin);
michael@0 408
michael@0 409 this.getOrderedProviderList(function (providers) {
michael@0 410 this._notifyProviderListeners("provider-enabled", provider.origin, providers);
michael@0 411 if (onDone)
michael@0 412 onDone(provider);
michael@0 413 }.bind(this));
michael@0 414 },
michael@0 415
michael@0 416 // Removes a provider with the given origin, and notifies when the removal is
michael@0 417 // complete.
michael@0 418 removeProvider: function removeProvider(origin, onDone) {
michael@0 419 if (!(origin in SocialServiceInternal.providers))
michael@0 420 throw new Error("SocialService.removeProvider: no provider with origin " + origin + " exists!");
michael@0 421
michael@0 422 let provider = SocialServiceInternal.providers[origin];
michael@0 423 let manifest = SocialService.getManifestByOrigin(origin);
michael@0 424 let addon = manifest && new AddonWrapper(manifest);
michael@0 425 if (addon) {
michael@0 426 AddonManagerPrivate.callAddonListeners("onDisabling", addon, false);
michael@0 427 addon.pendingOperations |= AddonManager.PENDING_DISABLE;
michael@0 428 }
michael@0 429 provider.enabled = false;
michael@0 430
michael@0 431 ActiveProviders.delete(provider.origin);
michael@0 432
michael@0 433 delete SocialServiceInternal.providers[origin];
michael@0 434 // disable the api if we have no enabled providers
michael@0 435 MozSocialAPI.enabled = SocialServiceInternal.enabled;
michael@0 436
michael@0 437 if (addon) {
michael@0 438 // we have to do this now so the addon manager ui will update an uninstall
michael@0 439 // correctly.
michael@0 440 addon.pendingOperations -= AddonManager.PENDING_DISABLE;
michael@0 441 AddonManagerPrivate.callAddonListeners("onDisabled", addon);
michael@0 442 AddonManagerPrivate.notifyAddonChanged(addon.id, ADDON_TYPE_SERVICE, false);
michael@0 443 }
michael@0 444
michael@0 445 this.getOrderedProviderList(function (providers) {
michael@0 446 this._notifyProviderListeners("provider-disabled", origin, providers);
michael@0 447 if (onDone)
michael@0 448 onDone();
michael@0 449 }.bind(this));
michael@0 450 },
michael@0 451
michael@0 452 // Returns a single provider object with the specified origin. The provider
michael@0 453 // must be "installed" (ie, in ActiveProviders)
michael@0 454 getProvider: function getProvider(origin, onDone) {
michael@0 455 schedule((function () {
michael@0 456 onDone(SocialServiceInternal.providers[origin] || null);
michael@0 457 }).bind(this));
michael@0 458 },
michael@0 459
michael@0 460 // Returns an unordered array of installed providers
michael@0 461 getProviderList: function(onDone) {
michael@0 462 schedule(function () {
michael@0 463 onDone(SocialServiceInternal.providerArray);
michael@0 464 });
michael@0 465 },
michael@0 466
michael@0 467 getManifestByOrigin: function(origin) {
michael@0 468 for (let manifest of SocialServiceInternal.manifests) {
michael@0 469 if (origin == manifest.origin) {
michael@0 470 return manifest;
michael@0 471 }
michael@0 472 }
michael@0 473 return null;
michael@0 474 },
michael@0 475
michael@0 476 // Returns an array of installed providers, sorted by frecency
michael@0 477 getOrderedProviderList: function(onDone) {
michael@0 478 SocialServiceInternal.orderedProviders(onDone);
michael@0 479 },
michael@0 480
michael@0 481 getOriginActivationType: function (origin) {
michael@0 482 return getOriginActivationType(origin);
michael@0 483 },
michael@0 484
michael@0 485 _providerListeners: new Map(),
michael@0 486 registerProviderListener: function registerProviderListener(listener) {
michael@0 487 this._providerListeners.set(listener, 1);
michael@0 488 },
michael@0 489 unregisterProviderListener: function unregisterProviderListener(listener) {
michael@0 490 this._providerListeners.delete(listener);
michael@0 491 },
michael@0 492
michael@0 493 _notifyProviderListeners: function (topic, origin, providers) {
michael@0 494 for (let [listener, ] of this._providerListeners) {
michael@0 495 try {
michael@0 496 listener(topic, origin, providers);
michael@0 497 } catch (ex) {
michael@0 498 Components.utils.reportError("SocialService: provider listener threw an exception: " + ex);
michael@0 499 }
michael@0 500 }
michael@0 501 },
michael@0 502
michael@0 503 _manifestFromData: function(type, data, principal) {
michael@0 504 let featureURLs = ['workerURL', 'sidebarURL', 'shareURL', 'statusURL', 'markURL'];
michael@0 505 let resolveURLs = featureURLs.concat(['postActivationURL']);
michael@0 506
michael@0 507 if (type == 'directory') {
michael@0 508 // directory provided manifests must have origin in manifest, use that
michael@0 509 if (!data['origin']) {
michael@0 510 Cu.reportError("SocialService.manifestFromData directory service provided manifest without origin.");
michael@0 511 return null;
michael@0 512 }
michael@0 513 let URI = Services.io.newURI(data.origin, null, null);
michael@0 514 principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(URI);
michael@0 515 }
michael@0 516 // force/fixup origin
michael@0 517 data.origin = principal.origin;
michael@0 518
michael@0 519 // iconURL and name are required
michael@0 520 // iconURL may be a different origin (CDN or data url support) if this is
michael@0 521 // a whitelisted or directory listed provider
michael@0 522 let providerHasFeatures = [url for (url of featureURLs) if (data[url])].length > 0;
michael@0 523 if (!providerHasFeatures) {
michael@0 524 Cu.reportError("SocialService.manifestFromData manifest missing required urls.");
michael@0 525 return null;
michael@0 526 }
michael@0 527 if (!data['name'] || !data['iconURL']) {
michael@0 528 Cu.reportError("SocialService.manifestFromData manifest missing name or iconURL.");
michael@0 529 return null;
michael@0 530 }
michael@0 531 for (let url of resolveURLs) {
michael@0 532 if (data[url]) {
michael@0 533 try {
michael@0 534 let resolved = Services.io.newURI(principal.URI.resolve(data[url]), null, null);
michael@0 535 if (!(resolved.schemeIs("http") || resolved.schemeIs("https"))) {
michael@0 536 Cu.reportError("SocialService.manifestFromData unsupported scheme '" + resolved.scheme + "' for " + principal.origin);
michael@0 537 return null;
michael@0 538 }
michael@0 539 data[url] = resolved.spec;
michael@0 540 } catch(e) {
michael@0 541 Cu.reportError("SocialService.manifestFromData unable to resolve '" + url + "' for " + principal.origin);
michael@0 542 return null;
michael@0 543 }
michael@0 544 }
michael@0 545 }
michael@0 546 return data;
michael@0 547 },
michael@0 548
michael@0 549 _getChromeWindow: function(aWindow) {
michael@0 550 var chromeWin = aWindow
michael@0 551 .QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 552 .getInterface(Ci.nsIWebNavigation)
michael@0 553 .QueryInterface(Ci.nsIDocShellTreeItem)
michael@0 554 .rootTreeItem
michael@0 555 .QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 556 .getInterface(Ci.nsIDOMWindow)
michael@0 557 .QueryInterface(Ci.nsIDOMChromeWindow);
michael@0 558 return chromeWin;
michael@0 559 },
michael@0 560
michael@0 561 _showInstallNotification: function(aDOMDocument, aAddonInstaller) {
michael@0 562 let brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties");
michael@0 563 let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
michael@0 564 let requestingWindow = aDOMDocument.defaultView.top;
michael@0 565 let chromeWin = this._getChromeWindow(requestingWindow).wrappedJSObject;
michael@0 566 let browser = chromeWin.gBrowser.getBrowserForDocument(aDOMDocument);
michael@0 567 let requestingURI = Services.io.newURI(aDOMDocument.location.href, null, null);
michael@0 568
michael@0 569 let productName = brandBundle.GetStringFromName("brandShortName");
michael@0 570
michael@0 571 let message = browserBundle.formatStringFromName("service.install.description",
michael@0 572 [requestingURI.host, productName], 2);
michael@0 573
michael@0 574 let action = {
michael@0 575 label: browserBundle.GetStringFromName("service.install.ok.label"),
michael@0 576 accessKey: browserBundle.GetStringFromName("service.install.ok.accesskey"),
michael@0 577 callback: function() {
michael@0 578 aAddonInstaller.install();
michael@0 579 },
michael@0 580 };
michael@0 581
michael@0 582 let options = {
michael@0 583 learnMoreURL: Services.urlFormatter.formatURLPref("app.support.baseURL") + "social-api",
michael@0 584 };
michael@0 585 let anchor = "servicesInstall-notification-icon";
michael@0 586 let notificationid = "servicesInstall";
michael@0 587 chromeWin.PopupNotifications.show(browser, notificationid, message, anchor,
michael@0 588 action, [], options);
michael@0 589 },
michael@0 590
michael@0 591 installProvider: function(aDOMDocument, data, installCallback) {
michael@0 592 let manifest;
michael@0 593 let installOrigin = aDOMDocument.nodePrincipal.origin;
michael@0 594
michael@0 595 if (data) {
michael@0 596 let installType = getOriginActivationType(installOrigin);
michael@0 597 // if we get data, we MUST have a valid manifest generated from the data
michael@0 598 manifest = this._manifestFromData(installType, data, aDOMDocument.nodePrincipal);
michael@0 599 if (!manifest)
michael@0 600 throw new Error("SocialService.installProvider: service configuration is invalid from " + aDOMDocument.location.href);
michael@0 601
michael@0 602 let addon = new AddonWrapper(manifest);
michael@0 603 if (addon && addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED)
michael@0 604 throw new Error("installProvider: provider with origin [" +
michael@0 605 installOrigin + "] is blocklisted");
michael@0 606 }
michael@0 607
michael@0 608 let id = getAddonIDFromOrigin(installOrigin);
michael@0 609 AddonManager.getAddonByID(id, function(aAddon) {
michael@0 610 if (aAddon && aAddon.userDisabled) {
michael@0 611 aAddon.cancelUninstall();
michael@0 612 aAddon.userDisabled = false;
michael@0 613 }
michael@0 614 schedule(function () {
michael@0 615 this._installProvider(aDOMDocument, manifest, aManifest => {
michael@0 616 this._notifyProviderListeners("provider-installed", aManifest.origin);
michael@0 617 installCallback(aManifest);
michael@0 618 });
michael@0 619 }.bind(this));
michael@0 620 }.bind(this));
michael@0 621 },
michael@0 622
michael@0 623 _installProvider: function(aDOMDocument, manifest, installCallback) {
michael@0 624 let sourceURI = aDOMDocument.location.href;
michael@0 625 let installOrigin = aDOMDocument.nodePrincipal.origin;
michael@0 626
michael@0 627 let installType = getOriginActivationType(installOrigin);
michael@0 628 let installer;
michael@0 629 switch(installType) {
michael@0 630 case "foreign":
michael@0 631 if (!Services.prefs.getBoolPref("social.remote-install.enabled"))
michael@0 632 throw new Error("Remote install of services is disabled");
michael@0 633 if (!manifest)
michael@0 634 throw new Error("Cannot install provider without manifest data");
michael@0 635
michael@0 636 installer = new AddonInstaller(sourceURI, manifest, installCallback);
michael@0 637 this._showInstallNotification(aDOMDocument, installer);
michael@0 638 break;
michael@0 639 case "builtin":
michael@0 640 // for builtin, we already have a manifest, but it can be overridden
michael@0 641 // we need to return the manifest in the installcallback, so fetch
michael@0 642 // it if we have it. If there is no manifest data for the builtin,
michael@0 643 // the install request MUST be from the provider, otherwise we have
michael@0 644 // no way to know what provider we're trying to enable. This is
michael@0 645 // primarily an issue for "version zero" providers that did not
michael@0 646 // send the manifest with the dom event for activation.
michael@0 647 if (!manifest) {
michael@0 648 let prefname = getPrefnameFromOrigin(installOrigin);
michael@0 649 manifest = Services.prefs.getDefaultBranch(null)
michael@0 650 .getComplexValue(prefname, Ci.nsISupportsString).data;
michael@0 651 manifest = JSON.parse(manifest);
michael@0 652 // ensure we override a builtin manifest by having a different value in it
michael@0 653 if (manifest.builtin)
michael@0 654 delete manifest.builtin;
michael@0 655 }
michael@0 656 case "directory":
michael@0 657 // a manifest is requried, and will have been vetted by reviewers
michael@0 658 case "whitelist":
michael@0 659 // a manifest is required, we'll catch a missing manifest below.
michael@0 660 if (!manifest)
michael@0 661 throw new Error("Cannot install provider without manifest data");
michael@0 662 installer = new AddonInstaller(sourceURI, manifest, installCallback);
michael@0 663 this._showInstallNotification(aDOMDocument, installer);
michael@0 664 break;
michael@0 665 default:
michael@0 666 throw new Error("SocialService.installProvider: Invalid install type "+installType+"\n");
michael@0 667 break;
michael@0 668 }
michael@0 669 },
michael@0 670
michael@0 671 createWrapper: function(manifest) {
michael@0 672 return new AddonWrapper(manifest);
michael@0 673 },
michael@0 674
michael@0 675 /**
michael@0 676 * updateProvider is used from the worker to self-update. Since we do not
michael@0 677 * have knowledge of the currently selected provider here, we will notify
michael@0 678 * the front end to deal with any reload.
michael@0 679 */
michael@0 680 updateProvider: function(aUpdateOrigin, aManifest) {
michael@0 681 let originUri = Services.io.newURI(aUpdateOrigin, null, null);
michael@0 682 let principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(originUri);
michael@0 683 let installType = this.getOriginActivationType(aUpdateOrigin);
michael@0 684 // if we get data, we MUST have a valid manifest generated from the data
michael@0 685 let manifest = this._manifestFromData(installType, aManifest, principal);
michael@0 686 if (!manifest)
michael@0 687 throw new Error("SocialService.installProvider: service configuration is invalid from " + aUpdateOrigin);
michael@0 688
michael@0 689 // overwrite the preference
michael@0 690 let string = Cc["@mozilla.org/supports-string;1"].
michael@0 691 createInstance(Ci.nsISupportsString);
michael@0 692 string.data = JSON.stringify(manifest);
michael@0 693 Services.prefs.setComplexValue(getPrefnameFromOrigin(manifest.origin), Ci.nsISupportsString, string);
michael@0 694
michael@0 695 // overwrite the existing provider then notify the front end so it can
michael@0 696 // handle any reload that might be necessary.
michael@0 697 if (ActiveProviders.has(manifest.origin)) {
michael@0 698 let provider = new SocialProvider(manifest);
michael@0 699 SocialServiceInternal.providers[provider.origin] = provider;
michael@0 700 // update the cache and ui, reload provider if necessary
michael@0 701 this.getOrderedProviderList(providers => {
michael@0 702 this._notifyProviderListeners("provider-update", provider.origin, providers);
michael@0 703 });
michael@0 704 }
michael@0 705
michael@0 706 },
michael@0 707
michael@0 708 uninstallProvider: function(origin, aCallback) {
michael@0 709 let manifest = SocialService.getManifestByOrigin(origin);
michael@0 710 let addon = new AddonWrapper(manifest);
michael@0 711 addon.uninstall(aCallback);
michael@0 712 }
michael@0 713 };
michael@0 714
michael@0 715 /**
michael@0 716 * The SocialProvider object represents a social provider, and allows
michael@0 717 * access to its FrameWorker (if it has one).
michael@0 718 *
michael@0 719 * @constructor
michael@0 720 * @param {jsobj} object representing the manifest file describing this provider
michael@0 721 * @param {bool} boolean indicating whether this provider is "built in"
michael@0 722 */
michael@0 723 function SocialProvider(input) {
michael@0 724 if (!input.name)
michael@0 725 throw new Error("SocialProvider must be passed a name");
michael@0 726 if (!input.origin)
michael@0 727 throw new Error("SocialProvider must be passed an origin");
michael@0 728
michael@0 729 let addon = new AddonWrapper(input);
michael@0 730 if (addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED)
michael@0 731 throw new Error("SocialProvider: provider with origin [" +
michael@0 732 input.origin + "] is blocklisted");
michael@0 733
michael@0 734 this.name = input.name;
michael@0 735 this.iconURL = input.iconURL;
michael@0 736 this.icon32URL = input.icon32URL;
michael@0 737 this.icon64URL = input.icon64URL;
michael@0 738 this.workerURL = input.workerURL;
michael@0 739 this.sidebarURL = input.sidebarURL;
michael@0 740 this.shareURL = input.shareURL;
michael@0 741 this.statusURL = input.statusURL;
michael@0 742 this.markURL = input.markURL;
michael@0 743 this.markedIcon = input.markedIcon;
michael@0 744 this.unmarkedIcon = input.unmarkedIcon;
michael@0 745 this.postActivationURL = input.postActivationURL;
michael@0 746 this.origin = input.origin;
michael@0 747 let originUri = Services.io.newURI(input.origin, null, null);
michael@0 748 this.principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(originUri);
michael@0 749 this.ambientNotificationIcons = {};
michael@0 750 this.errorState = null;
michael@0 751 this.frecency = 0;
michael@0 752
michael@0 753 let activationType = getOriginActivationType(input.origin);
michael@0 754 this.blessed = activationType == "builtin" ||
michael@0 755 activationType == "whitelist";
michael@0 756
michael@0 757 try {
michael@0 758 this.domain = etld.getBaseDomainFromHost(originUri.host);
michael@0 759 } catch(e) {
michael@0 760 this.domain = originUri.host;
michael@0 761 }
michael@0 762 }
michael@0 763
michael@0 764 SocialProvider.prototype = {
michael@0 765 reload: function() {
michael@0 766 this._terminate();
michael@0 767 this._activate();
michael@0 768 Services.obs.notifyObservers(null, "social:provider-reload", this.origin);
michael@0 769 },
michael@0 770
michael@0 771 // Provider enabled/disabled state. Disabled providers do not have active
michael@0 772 // connections to their FrameWorkers.
michael@0 773 _enabled: false,
michael@0 774 get enabled() {
michael@0 775 return this._enabled;
michael@0 776 },
michael@0 777 set enabled(val) {
michael@0 778 let enable = !!val;
michael@0 779 if (enable == this._enabled)
michael@0 780 return;
michael@0 781
michael@0 782 this._enabled = enable;
michael@0 783
michael@0 784 if (enable) {
michael@0 785 this._activate();
michael@0 786 } else {
michael@0 787 this._terminate();
michael@0 788 }
michael@0 789 },
michael@0 790
michael@0 791 get manifest() {
michael@0 792 return SocialService.getManifestByOrigin(this.origin);
michael@0 793 },
michael@0 794
michael@0 795 // Reference to a workerAPI object for this provider. Null if the provider has
michael@0 796 // no FrameWorker, or is disabled.
michael@0 797 workerAPI: null,
michael@0 798
michael@0 799 // Contains information related to the user's profile. Populated by the
michael@0 800 // workerAPI via updateUserProfile.
michael@0 801 // Properties:
michael@0 802 // iconURL, portrait, userName, displayName, profileURL
michael@0 803 // See https://github.com/mozilla/socialapi-dev/blob/develop/docs/socialAPI.md
michael@0 804 // A value of null or an empty object means 'user not logged in'.
michael@0 805 // A value of undefined means the service has not yet told us the status of
michael@0 806 // the profile (ie, the service is still loading/initing, or the provider has
michael@0 807 // no FrameWorker)
michael@0 808 // This distinction might be used to cache certain data between runs - eg,
michael@0 809 // browser-social.js caches the notification icons so they can be displayed
michael@0 810 // quickly at startup without waiting for the provider to initialize -
michael@0 811 // 'undefined' means 'ok to use cached values' versus 'null' meaning 'cached
michael@0 812 // values aren't to be used as the user is logged out'.
michael@0 813 profile: undefined,
michael@0 814
michael@0 815 // Map of objects describing the provider's notification icons, whose
michael@0 816 // properties include:
michael@0 817 // name, iconURL, counter, contentPanel
michael@0 818 // See https://developer.mozilla.org/en-US/docs/Social_API
michael@0 819 ambientNotificationIcons: null,
michael@0 820
michael@0 821 // Called by the workerAPI to update our profile information.
michael@0 822 updateUserProfile: function(profile) {
michael@0 823 if (!profile)
michael@0 824 profile = {};
michael@0 825 let accountChanged = !this.profile || this.profile.userName != profile.userName;
michael@0 826 this.profile = profile;
michael@0 827
michael@0 828 // Sanitize the portrait from any potential script-injection.
michael@0 829 if (profile.portrait) {
michael@0 830 try {
michael@0 831 let portraitUri = Services.io.newURI(profile.portrait, null, null);
michael@0 832
michael@0 833 let scheme = portraitUri ? portraitUri.scheme : "";
michael@0 834 if (scheme != "data" && scheme != "http" && scheme != "https") {
michael@0 835 profile.portrait = "";
michael@0 836 }
michael@0 837 } catch (ex) {
michael@0 838 profile.portrait = "";
michael@0 839 }
michael@0 840 }
michael@0 841
michael@0 842 if (profile.iconURL)
michael@0 843 this.iconURL = profile.iconURL;
michael@0 844
michael@0 845 if (!profile.displayName)
michael@0 846 profile.displayName = profile.userName;
michael@0 847
michael@0 848 // if no userName, consider this a logged out state, emtpy the
michael@0 849 // users ambient notifications. notify both profile and ambient
michael@0 850 // changes to clear everything
michael@0 851 if (!profile.userName) {
michael@0 852 this.profile = {};
michael@0 853 this.ambientNotificationIcons = {};
michael@0 854 Services.obs.notifyObservers(null, "social:ambient-notification-changed", this.origin);
michael@0 855 }
michael@0 856
michael@0 857 Services.obs.notifyObservers(null, "social:profile-changed", this.origin);
michael@0 858 if (accountChanged)
michael@0 859 closeAllChatWindows(this);
michael@0 860 },
michael@0 861
michael@0 862 haveLoggedInUser: function () {
michael@0 863 return !!(this.profile && this.profile.userName);
michael@0 864 },
michael@0 865
michael@0 866 // Called by the workerAPI to add/update a notification icon.
michael@0 867 setAmbientNotification: function(notification) {
michael@0 868 if (!this.profile.userName)
michael@0 869 throw new Error("unable to set notifications while logged out");
michael@0 870 if (!this.ambientNotificationIcons[notification.name] &&
michael@0 871 Object.keys(this.ambientNotificationIcons).length >= 3) {
michael@0 872 throw new Error("ambient notification limit reached");
michael@0 873 }
michael@0 874 this.ambientNotificationIcons[notification.name] = notification;
michael@0 875
michael@0 876 Services.obs.notifyObservers(null, "social:ambient-notification-changed", this.origin);
michael@0 877 },
michael@0 878
michael@0 879 // Internal helper methods
michael@0 880 _activate: function _activate() {
michael@0 881 // Initialize the workerAPI and its port first, so that its initialization
michael@0 882 // occurs before any other messages are processed by other ports.
michael@0 883 let workerAPIPort = this.getWorkerPort();
michael@0 884 if (workerAPIPort)
michael@0 885 this.workerAPI = new WorkerAPI(this, workerAPIPort);
michael@0 886 },
michael@0 887
michael@0 888 _terminate: function _terminate() {
michael@0 889 closeAllChatWindows(this);
michael@0 890 if (this.workerURL) {
michael@0 891 try {
michael@0 892 getFrameWorkerHandle(this.workerURL).terminate();
michael@0 893 } catch (e) {
michael@0 894 Cu.reportError("SocialProvider FrameWorker termination failed: " + e);
michael@0 895 }
michael@0 896 }
michael@0 897 if (this.workerAPI) {
michael@0 898 this.workerAPI.terminate();
michael@0 899 }
michael@0 900 this.errorState = null;
michael@0 901 this.workerAPI = null;
michael@0 902 this.profile = undefined;
michael@0 903 },
michael@0 904
michael@0 905 /**
michael@0 906 * Instantiates a FrameWorker for the provider if one doesn't exist, and
michael@0 907 * returns a reference to a new port to that FrameWorker.
michael@0 908 *
michael@0 909 * Returns null if this provider has no workerURL, or is disabled.
michael@0 910 *
michael@0 911 * @param {DOMWindow} window (optional)
michael@0 912 */
michael@0 913 getWorkerPort: function getWorkerPort(window) {
michael@0 914 if (!this.workerURL || !this.enabled)
michael@0 915 return null;
michael@0 916 // Only allow localStorage in the frameworker for blessed providers
michael@0 917 let allowLocalStorage = this.blessed;
michael@0 918 let handle = getFrameWorkerHandle(this.workerURL, window,
michael@0 919 "SocialProvider:" + this.origin, this.origin,
michael@0 920 allowLocalStorage);
michael@0 921 return handle.port;
michael@0 922 },
michael@0 923
michael@0 924 /**
michael@0 925 * Checks if a given URI is of the same origin as the provider.
michael@0 926 *
michael@0 927 * Returns true or false.
michael@0 928 *
michael@0 929 * @param {URI or string} uri
michael@0 930 */
michael@0 931 isSameOrigin: function isSameOrigin(uri, allowIfInheritsPrincipal) {
michael@0 932 if (!uri)
michael@0 933 return false;
michael@0 934 if (typeof uri == "string") {
michael@0 935 try {
michael@0 936 uri = Services.io.newURI(uri, null, null);
michael@0 937 } catch (ex) {
michael@0 938 // an invalid URL can't be loaded!
michael@0 939 return false;
michael@0 940 }
michael@0 941 }
michael@0 942 try {
michael@0 943 this.principal.checkMayLoad(
michael@0 944 uri, // the thing to check.
michael@0 945 false, // reportError - we do our own reporting when necessary.
michael@0 946 allowIfInheritsPrincipal
michael@0 947 );
michael@0 948 return true;
michael@0 949 } catch (ex) {
michael@0 950 return false;
michael@0 951 }
michael@0 952 },
michael@0 953
michael@0 954 /**
michael@0 955 * Resolve partial URLs for a provider.
michael@0 956 *
michael@0 957 * Returns nsIURI object or null on failure
michael@0 958 *
michael@0 959 * @param {string} url
michael@0 960 */
michael@0 961 resolveUri: function resolveUri(url) {
michael@0 962 try {
michael@0 963 let fullURL = this.principal.URI.resolve(url);
michael@0 964 return Services.io.newURI(fullURL, null, null);
michael@0 965 } catch (ex) {
michael@0 966 Cu.reportError("mozSocial: failed to resolve window URL: " + url + "; " + ex);
michael@0 967 return null;
michael@0 968 }
michael@0 969 }
michael@0 970 }
michael@0 971
michael@0 972 function getAddonIDFromOrigin(origin) {
michael@0 973 let originUri = Services.io.newURI(origin, null, null);
michael@0 974 return originUri.host + ID_SUFFIX;
michael@0 975 }
michael@0 976
michael@0 977 function getPrefnameFromOrigin(origin) {
michael@0 978 return "social.manifest." + SocialServiceInternal.getManifestPrefname(origin);
michael@0 979 }
michael@0 980
michael@0 981 function AddonInstaller(sourceURI, aManifest, installCallback) {
michael@0 982 aManifest.updateDate = Date.now();
michael@0 983 // get the existing manifest for installDate
michael@0 984 let manifest = SocialService.getManifestByOrigin(aManifest.origin);
michael@0 985 let isNewInstall = !manifest;
michael@0 986 if (manifest && manifest.installDate)
michael@0 987 aManifest.installDate = manifest.installDate;
michael@0 988 else
michael@0 989 aManifest.installDate = aManifest.updateDate;
michael@0 990
michael@0 991 this.sourceURI = sourceURI;
michael@0 992 this.install = function() {
michael@0 993 let addon = this.addon;
michael@0 994 if (isNewInstall) {
michael@0 995 AddonManagerPrivate.callInstallListeners("onExternalInstall", null, addon, null, false);
michael@0 996 AddonManagerPrivate.callAddonListeners("onInstalling", addon, false);
michael@0 997 }
michael@0 998
michael@0 999 let string = Cc["@mozilla.org/supports-string;1"].
michael@0 1000 createInstance(Ci.nsISupportsString);
michael@0 1001 string.data = JSON.stringify(aManifest);
michael@0 1002 Services.prefs.setComplexValue(getPrefnameFromOrigin(aManifest.origin), Ci.nsISupportsString, string);
michael@0 1003
michael@0 1004 if (isNewInstall) {
michael@0 1005 AddonManagerPrivate.callAddonListeners("onInstalled", addon);
michael@0 1006 }
michael@0 1007 installCallback(aManifest);
michael@0 1008 };
michael@0 1009 this.cancel = function() {
michael@0 1010 Services.prefs.clearUserPref(getPrefnameFromOrigin(aManifest.origin))
michael@0 1011 },
michael@0 1012 this.addon = new AddonWrapper(aManifest);
michael@0 1013 };
michael@0 1014
michael@0 1015 var SocialAddonProvider = {
michael@0 1016 startup: function() {},
michael@0 1017
michael@0 1018 shutdown: function() {},
michael@0 1019
michael@0 1020 updateAddonAppDisabledStates: function() {
michael@0 1021 // we wont bother with "enabling" services that are released from blocklist
michael@0 1022 for (let manifest of SocialServiceInternal.manifests) {
michael@0 1023 try {
michael@0 1024 if (ActiveProviders.has(manifest.origin)) {
michael@0 1025 let addon = new AddonWrapper(manifest);
michael@0 1026 if (addon.blocklistState != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) {
michael@0 1027 SocialService.removeProvider(manifest.origin);
michael@0 1028 }
michael@0 1029 }
michael@0 1030 } catch(e) {
michael@0 1031 Cu.reportError(e);
michael@0 1032 }
michael@0 1033 }
michael@0 1034 },
michael@0 1035
michael@0 1036 getAddonByID: function(aId, aCallback) {
michael@0 1037 for (let manifest of SocialServiceInternal.manifests) {
michael@0 1038 if (aId == getAddonIDFromOrigin(manifest.origin)) {
michael@0 1039 aCallback(new AddonWrapper(manifest));
michael@0 1040 return;
michael@0 1041 }
michael@0 1042 }
michael@0 1043 aCallback(null);
michael@0 1044 },
michael@0 1045
michael@0 1046 getAddonsByTypes: function(aTypes, aCallback) {
michael@0 1047 if (aTypes && aTypes.indexOf(ADDON_TYPE_SERVICE) == -1) {
michael@0 1048 aCallback([]);
michael@0 1049 return;
michael@0 1050 }
michael@0 1051 aCallback([new AddonWrapper(a) for each (a in SocialServiceInternal.manifests)]);
michael@0 1052 },
michael@0 1053
michael@0 1054 removeAddon: function(aAddon, aCallback) {
michael@0 1055 AddonManagerPrivate.callAddonListeners("onUninstalling", aAddon, false);
michael@0 1056 aAddon.pendingOperations |= AddonManager.PENDING_UNINSTALL;
michael@0 1057 Services.prefs.clearUserPref(getPrefnameFromOrigin(aAddon.manifest.origin));
michael@0 1058 aAddon.pendingOperations -= AddonManager.PENDING_UNINSTALL;
michael@0 1059 AddonManagerPrivate.callAddonListeners("onUninstalled", aAddon);
michael@0 1060 SocialService._notifyProviderListeners("provider-uninstalled", aAddon.manifest.origin);
michael@0 1061 if (aCallback)
michael@0 1062 schedule(aCallback);
michael@0 1063 }
michael@0 1064 }
michael@0 1065
michael@0 1066
michael@0 1067 function AddonWrapper(aManifest) {
michael@0 1068 this.manifest = aManifest;
michael@0 1069 this.id = getAddonIDFromOrigin(this.manifest.origin);
michael@0 1070 this._pending = AddonManager.PENDING_NONE;
michael@0 1071 }
michael@0 1072 AddonWrapper.prototype = {
michael@0 1073 get type() {
michael@0 1074 return ADDON_TYPE_SERVICE;
michael@0 1075 },
michael@0 1076
michael@0 1077 get appDisabled() {
michael@0 1078 return this.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED;
michael@0 1079 },
michael@0 1080
michael@0 1081 set softDisabled(val) {
michael@0 1082 this.userDisabled = val;
michael@0 1083 },
michael@0 1084
michael@0 1085 get softDisabled() {
michael@0 1086 return this.userDisabled;
michael@0 1087 },
michael@0 1088
michael@0 1089 get isCompatible() {
michael@0 1090 return true;
michael@0 1091 },
michael@0 1092
michael@0 1093 get isPlatformCompatible() {
michael@0 1094 return true;
michael@0 1095 },
michael@0 1096
michael@0 1097 get scope() {
michael@0 1098 return AddonManager.SCOPE_PROFILE;
michael@0 1099 },
michael@0 1100
michael@0 1101 get foreignInstall() {
michael@0 1102 return false;
michael@0 1103 },
michael@0 1104
michael@0 1105 isCompatibleWith: function(appVersion, platformVersion) {
michael@0 1106 return true;
michael@0 1107 },
michael@0 1108
michael@0 1109 get providesUpdatesSecurely() {
michael@0 1110 return true;
michael@0 1111 },
michael@0 1112
michael@0 1113 get blocklistState() {
michael@0 1114 return Services.blocklist.getAddonBlocklistState(this);
michael@0 1115 },
michael@0 1116
michael@0 1117 get blocklistURL() {
michael@0 1118 return Services.blocklist.getAddonBlocklistURL(this);
michael@0 1119 },
michael@0 1120
michael@0 1121 get screenshots() {
michael@0 1122 return [];
michael@0 1123 },
michael@0 1124
michael@0 1125 get pendingOperations() {
michael@0 1126 return this._pending || AddonManager.PENDING_NONE;
michael@0 1127 },
michael@0 1128 set pendingOperations(val) {
michael@0 1129 this._pending = val;
michael@0 1130 },
michael@0 1131
michael@0 1132 get operationsRequiringRestart() {
michael@0 1133 return AddonManager.OP_NEEDS_RESTART_NONE;
michael@0 1134 },
michael@0 1135
michael@0 1136 get size() {
michael@0 1137 return null;
michael@0 1138 },
michael@0 1139
michael@0 1140 get permissions() {
michael@0 1141 let permissions = 0;
michael@0 1142 // any "user defined" manifest can be removed
michael@0 1143 if (Services.prefs.prefHasUserValue(getPrefnameFromOrigin(this.manifest.origin)))
michael@0 1144 permissions = AddonManager.PERM_CAN_UNINSTALL;
michael@0 1145 if (!this.appDisabled) {
michael@0 1146 if (this.userDisabled) {
michael@0 1147 permissions |= AddonManager.PERM_CAN_ENABLE;
michael@0 1148 } else {
michael@0 1149 permissions |= AddonManager.PERM_CAN_DISABLE;
michael@0 1150 }
michael@0 1151 }
michael@0 1152 return permissions;
michael@0 1153 },
michael@0 1154
michael@0 1155 findUpdates: function(listener, reason, appVersion, platformVersion) {
michael@0 1156 if ("onNoCompatibilityUpdateAvailable" in listener)
michael@0 1157 listener.onNoCompatibilityUpdateAvailable(this);
michael@0 1158 if ("onNoUpdateAvailable" in listener)
michael@0 1159 listener.onNoUpdateAvailable(this);
michael@0 1160 if ("onUpdateFinished" in listener)
michael@0 1161 listener.onUpdateFinished(this);
michael@0 1162 },
michael@0 1163
michael@0 1164 get isActive() {
michael@0 1165 return ActiveProviders.has(this.manifest.origin);
michael@0 1166 },
michael@0 1167
michael@0 1168 get name() {
michael@0 1169 return this.manifest.name;
michael@0 1170 },
michael@0 1171 get version() {
michael@0 1172 return this.manifest.version ? this.manifest.version : "";
michael@0 1173 },
michael@0 1174
michael@0 1175 get iconURL() {
michael@0 1176 return this.manifest.icon32URL ? this.manifest.icon32URL : this.manifest.iconURL;
michael@0 1177 },
michael@0 1178 get icon64URL() {
michael@0 1179 return this.manifest.icon64URL;
michael@0 1180 },
michael@0 1181 get icons() {
michael@0 1182 let icons = {
michael@0 1183 16: this.manifest.iconURL
michael@0 1184 };
michael@0 1185 if (this.manifest.icon32URL)
michael@0 1186 icons[32] = this.manifest.icon32URL;
michael@0 1187 if (this.manifest.icon64URL)
michael@0 1188 icons[64] = this.manifest.icon64URL;
michael@0 1189 return icons;
michael@0 1190 },
michael@0 1191
michael@0 1192 get description() {
michael@0 1193 return this.manifest.description;
michael@0 1194 },
michael@0 1195 get homepageURL() {
michael@0 1196 return this.manifest.homepageURL;
michael@0 1197 },
michael@0 1198 get defaultLocale() {
michael@0 1199 return this.manifest.defaultLocale;
michael@0 1200 },
michael@0 1201 get selectedLocale() {
michael@0 1202 return this.manifest.selectedLocale;
michael@0 1203 },
michael@0 1204
michael@0 1205 get installDate() {
michael@0 1206 return this.manifest.installDate ? new Date(this.manifest.installDate) : null;
michael@0 1207 },
michael@0 1208 get updateDate() {
michael@0 1209 return this.manifest.updateDate ? new Date(this.manifest.updateDate) : null;
michael@0 1210 },
michael@0 1211
michael@0 1212 get creator() {
michael@0 1213 return new AddonManagerPrivate.AddonAuthor(this.manifest.author);
michael@0 1214 },
michael@0 1215
michael@0 1216 get userDisabled() {
michael@0 1217 return this.appDisabled || !ActiveProviders.has(this.manifest.origin);
michael@0 1218 },
michael@0 1219
michael@0 1220 set userDisabled(val) {
michael@0 1221 if (val == this.userDisabled)
michael@0 1222 return val;
michael@0 1223 if (val) {
michael@0 1224 SocialService.removeProvider(this.manifest.origin);
michael@0 1225 } else if (!this.appDisabled) {
michael@0 1226 SocialService.addBuiltinProvider(this.manifest.origin);
michael@0 1227 }
michael@0 1228 return val;
michael@0 1229 },
michael@0 1230
michael@0 1231 uninstall: function(aCallback) {
michael@0 1232 let prefName = getPrefnameFromOrigin(this.manifest.origin);
michael@0 1233 if (Services.prefs.prefHasUserValue(prefName)) {
michael@0 1234 if (ActiveProviders.has(this.manifest.origin)) {
michael@0 1235 SocialService.removeProvider(this.manifest.origin, function() {
michael@0 1236 SocialAddonProvider.removeAddon(this, aCallback);
michael@0 1237 }.bind(this));
michael@0 1238 } else {
michael@0 1239 SocialAddonProvider.removeAddon(this, aCallback);
michael@0 1240 }
michael@0 1241 } else {
michael@0 1242 schedule(aCallback);
michael@0 1243 }
michael@0 1244 },
michael@0 1245
michael@0 1246 cancelUninstall: function() {
michael@0 1247 this._pending -= AddonManager.PENDING_UNINSTALL;
michael@0 1248 AddonManagerPrivate.callAddonListeners("onOperationCancelled", this);
michael@0 1249 }
michael@0 1250 };
michael@0 1251
michael@0 1252
michael@0 1253 AddonManagerPrivate.registerProvider(SocialAddonProvider, [
michael@0 1254 new AddonManagerPrivate.AddonType(ADDON_TYPE_SERVICE, URI_EXTENSION_STRINGS,
michael@0 1255 STRING_TYPE_NAME,
michael@0 1256 AddonManager.VIEW_TYPE_LIST, 10000)
michael@0 1257 ]);

mercurial