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.

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

mercurial