|
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/. */ |
|
4 |
|
5 this.EXPORTED_SYMBOLS = ["SocialService"]; |
|
6 |
|
7 const { classes: Cc, interfaces: Ci, utils: Cu } = Components; |
|
8 |
|
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"); |
|
13 |
|
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"; |
|
18 |
|
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"); |
|
24 |
|
25 XPCOMUtils.defineLazyServiceGetter(this, "etld", |
|
26 "@mozilla.org/network/effective-tld-service;1", |
|
27 "nsIEffectiveTLDService"); |
|
28 |
|
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 */ |
|
34 |
|
35 // Internal helper methods and state |
|
36 let SocialServiceInternal = { |
|
37 get enabled() this.providerArray.length > 0, |
|
38 |
|
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 = {}; |
|
92 |
|
93 for (let p of SocialServiceInternal.providerArray) { |
|
94 p.frecency = 0; |
|
95 providers[p.domain] = p; |
|
96 hosts.push(p.domain); |
|
97 }; |
|
98 |
|
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 ); |
|
105 |
|
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 }; |
|
133 |
|
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 }); |
|
152 |
|
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'; |
|
157 |
|
158 let whitelist = Services.prefs.getCharPref("social.whitelist").split(','); |
|
159 if (whitelist.indexOf(origin) >= 0) |
|
160 return 'whitelist'; |
|
161 |
|
162 let directories = Services.prefs.getCharPref("social.directories").split(','); |
|
163 if (directories.indexOf(origin) >= 0) |
|
164 return 'directory'; |
|
165 |
|
166 return 'foreign'; |
|
167 } |
|
168 |
|
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 }, |
|
180 |
|
181 has: function (origin) { |
|
182 return (origin in this._providers); |
|
183 }, |
|
184 |
|
185 add: function (origin) { |
|
186 this._providers[origin] = 1; |
|
187 this._deferredTask.arm(); |
|
188 }, |
|
189 |
|
190 delete: function (origin) { |
|
191 delete this._providers[origin]; |
|
192 this._deferredTask.arm(); |
|
193 }, |
|
194 |
|
195 flush: function () { |
|
196 this._deferredTask.disarm(); |
|
197 this._persist(); |
|
198 }, |
|
199 |
|
200 get _deferredTask() { |
|
201 delete this._deferredTask; |
|
202 return this._deferredTask = new DeferredTask(this._persist.bind(this), 0); |
|
203 }, |
|
204 |
|
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 }; |
|
213 |
|
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 |
|
270 |
|
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 } |
|
286 |
|
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; |
|
294 |
|
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 } |
|
316 |
|
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 } |
|
332 |
|
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); |
|
339 |
|
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 } |
|
349 |
|
350 function schedule(callback) { |
|
351 Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL); |
|
352 } |
|
353 |
|
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 }, |
|
372 |
|
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 }, |
|
397 |
|
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"); |
|
402 |
|
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); |
|
408 |
|
409 this.getOrderedProviderList(function (providers) { |
|
410 this._notifyProviderListeners("provider-enabled", provider.origin, providers); |
|
411 if (onDone) |
|
412 onDone(provider); |
|
413 }.bind(this)); |
|
414 }, |
|
415 |
|
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!"); |
|
421 |
|
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; |
|
430 |
|
431 ActiveProviders.delete(provider.origin); |
|
432 |
|
433 delete SocialServiceInternal.providers[origin]; |
|
434 // disable the api if we have no enabled providers |
|
435 MozSocialAPI.enabled = SocialServiceInternal.enabled; |
|
436 |
|
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 } |
|
444 |
|
445 this.getOrderedProviderList(function (providers) { |
|
446 this._notifyProviderListeners("provider-disabled", origin, providers); |
|
447 if (onDone) |
|
448 onDone(); |
|
449 }.bind(this)); |
|
450 }, |
|
451 |
|
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 }, |
|
459 |
|
460 // Returns an unordered array of installed providers |
|
461 getProviderList: function(onDone) { |
|
462 schedule(function () { |
|
463 onDone(SocialServiceInternal.providerArray); |
|
464 }); |
|
465 }, |
|
466 |
|
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 }, |
|
475 |
|
476 // Returns an array of installed providers, sorted by frecency |
|
477 getOrderedProviderList: function(onDone) { |
|
478 SocialServiceInternal.orderedProviders(onDone); |
|
479 }, |
|
480 |
|
481 getOriginActivationType: function (origin) { |
|
482 return getOriginActivationType(origin); |
|
483 }, |
|
484 |
|
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 }, |
|
492 |
|
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 }, |
|
502 |
|
503 _manifestFromData: function(type, data, principal) { |
|
504 let featureURLs = ['workerURL', 'sidebarURL', 'shareURL', 'statusURL', 'markURL']; |
|
505 let resolveURLs = featureURLs.concat(['postActivationURL']); |
|
506 |
|
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; |
|
518 |
|
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 }, |
|
548 |
|
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 }, |
|
560 |
|
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); |
|
568 |
|
569 let productName = brandBundle.GetStringFromName("brandShortName"); |
|
570 |
|
571 let message = browserBundle.formatStringFromName("service.install.description", |
|
572 [requestingURI.host, productName], 2); |
|
573 |
|
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 }; |
|
581 |
|
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 }, |
|
590 |
|
591 installProvider: function(aDOMDocument, data, installCallback) { |
|
592 let manifest; |
|
593 let installOrigin = aDOMDocument.nodePrincipal.origin; |
|
594 |
|
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); |
|
601 |
|
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 } |
|
607 |
|
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 }, |
|
622 |
|
623 _installProvider: function(aDOMDocument, manifest, installCallback) { |
|
624 let sourceURI = aDOMDocument.location.href; |
|
625 let installOrigin = aDOMDocument.nodePrincipal.origin; |
|
626 |
|
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"); |
|
635 |
|
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 }, |
|
670 |
|
671 createWrapper: function(manifest) { |
|
672 return new AddonWrapper(manifest); |
|
673 }, |
|
674 |
|
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); |
|
688 |
|
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); |
|
694 |
|
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 } |
|
705 |
|
706 }, |
|
707 |
|
708 uninstallProvider: function(origin, aCallback) { |
|
709 let manifest = SocialService.getManifestByOrigin(origin); |
|
710 let addon = new AddonWrapper(manifest); |
|
711 addon.uninstall(aCallback); |
|
712 } |
|
713 }; |
|
714 |
|
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"); |
|
728 |
|
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"); |
|
733 |
|
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; |
|
752 |
|
753 let activationType = getOriginActivationType(input.origin); |
|
754 this.blessed = activationType == "builtin" || |
|
755 activationType == "whitelist"; |
|
756 |
|
757 try { |
|
758 this.domain = etld.getBaseDomainFromHost(originUri.host); |
|
759 } catch(e) { |
|
760 this.domain = originUri.host; |
|
761 } |
|
762 } |
|
763 |
|
764 SocialProvider.prototype = { |
|
765 reload: function() { |
|
766 this._terminate(); |
|
767 this._activate(); |
|
768 Services.obs.notifyObservers(null, "social:provider-reload", this.origin); |
|
769 }, |
|
770 |
|
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; |
|
781 |
|
782 this._enabled = enable; |
|
783 |
|
784 if (enable) { |
|
785 this._activate(); |
|
786 } else { |
|
787 this._terminate(); |
|
788 } |
|
789 }, |
|
790 |
|
791 get manifest() { |
|
792 return SocialService.getManifestByOrigin(this.origin); |
|
793 }, |
|
794 |
|
795 // Reference to a workerAPI object for this provider. Null if the provider has |
|
796 // no FrameWorker, or is disabled. |
|
797 workerAPI: null, |
|
798 |
|
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, |
|
814 |
|
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, |
|
820 |
|
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; |
|
827 |
|
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); |
|
832 |
|
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 } |
|
841 |
|
842 if (profile.iconURL) |
|
843 this.iconURL = profile.iconURL; |
|
844 |
|
845 if (!profile.displayName) |
|
846 profile.displayName = profile.userName; |
|
847 |
|
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 } |
|
856 |
|
857 Services.obs.notifyObservers(null, "social:profile-changed", this.origin); |
|
858 if (accountChanged) |
|
859 closeAllChatWindows(this); |
|
860 }, |
|
861 |
|
862 haveLoggedInUser: function () { |
|
863 return !!(this.profile && this.profile.userName); |
|
864 }, |
|
865 |
|
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; |
|
875 |
|
876 Services.obs.notifyObservers(null, "social:ambient-notification-changed", this.origin); |
|
877 }, |
|
878 |
|
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 }, |
|
887 |
|
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 }, |
|
904 |
|
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 }, |
|
923 |
|
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 }, |
|
953 |
|
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 } |
|
971 |
|
972 function getAddonIDFromOrigin(origin) { |
|
973 let originUri = Services.io.newURI(origin, null, null); |
|
974 return originUri.host + ID_SUFFIX; |
|
975 } |
|
976 |
|
977 function getPrefnameFromOrigin(origin) { |
|
978 return "social.manifest." + SocialServiceInternal.getManifestPrefname(origin); |
|
979 } |
|
980 |
|
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; |
|
990 |
|
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 } |
|
998 |
|
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); |
|
1003 |
|
1004 if (isNewInstall) { |
|
1005 AddonManagerPrivate.callAddonListeners("onInstalled", addon); |
|
1006 } |
|
1007 installCallback(aManifest); |
|
1008 }; |
|
1009 this.cancel = function() { |
|
1010 Services.prefs.clearUserPref(getPrefnameFromOrigin(aManifest.origin)) |
|
1011 }, |
|
1012 this.addon = new AddonWrapper(aManifest); |
|
1013 }; |
|
1014 |
|
1015 var SocialAddonProvider = { |
|
1016 startup: function() {}, |
|
1017 |
|
1018 shutdown: function() {}, |
|
1019 |
|
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); |
|
1028 } |
|
1029 } |
|
1030 } catch(e) { |
|
1031 Cu.reportError(e); |
|
1032 } |
|
1033 } |
|
1034 }, |
|
1035 |
|
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; |
|
1041 } |
|
1042 } |
|
1043 aCallback(null); |
|
1044 }, |
|
1045 |
|
1046 getAddonsByTypes: function(aTypes, aCallback) { |
|
1047 if (aTypes && aTypes.indexOf(ADDON_TYPE_SERVICE) == -1) { |
|
1048 aCallback([]); |
|
1049 return; |
|
1050 } |
|
1051 aCallback([new AddonWrapper(a) for each (a in SocialServiceInternal.manifests)]); |
|
1052 }, |
|
1053 |
|
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); |
|
1063 } |
|
1064 } |
|
1065 |
|
1066 |
|
1067 function AddonWrapper(aManifest) { |
|
1068 this.manifest = aManifest; |
|
1069 this.id = getAddonIDFromOrigin(this.manifest.origin); |
|
1070 this._pending = AddonManager.PENDING_NONE; |
|
1071 } |
|
1072 AddonWrapper.prototype = { |
|
1073 get type() { |
|
1074 return ADDON_TYPE_SERVICE; |
|
1075 }, |
|
1076 |
|
1077 get appDisabled() { |
|
1078 return this.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED; |
|
1079 }, |
|
1080 |
|
1081 set softDisabled(val) { |
|
1082 this.userDisabled = val; |
|
1083 }, |
|
1084 |
|
1085 get softDisabled() { |
|
1086 return this.userDisabled; |
|
1087 }, |
|
1088 |
|
1089 get isCompatible() { |
|
1090 return true; |
|
1091 }, |
|
1092 |
|
1093 get isPlatformCompatible() { |
|
1094 return true; |
|
1095 }, |
|
1096 |
|
1097 get scope() { |
|
1098 return AddonManager.SCOPE_PROFILE; |
|
1099 }, |
|
1100 |
|
1101 get foreignInstall() { |
|
1102 return false; |
|
1103 }, |
|
1104 |
|
1105 isCompatibleWith: function(appVersion, platformVersion) { |
|
1106 return true; |
|
1107 }, |
|
1108 |
|
1109 get providesUpdatesSecurely() { |
|
1110 return true; |
|
1111 }, |
|
1112 |
|
1113 get blocklistState() { |
|
1114 return Services.blocklist.getAddonBlocklistState(this); |
|
1115 }, |
|
1116 |
|
1117 get blocklistURL() { |
|
1118 return Services.blocklist.getAddonBlocklistURL(this); |
|
1119 }, |
|
1120 |
|
1121 get screenshots() { |
|
1122 return []; |
|
1123 }, |
|
1124 |
|
1125 get pendingOperations() { |
|
1126 return this._pending || AddonManager.PENDING_NONE; |
|
1127 }, |
|
1128 set pendingOperations(val) { |
|
1129 this._pending = val; |
|
1130 }, |
|
1131 |
|
1132 get operationsRequiringRestart() { |
|
1133 return AddonManager.OP_NEEDS_RESTART_NONE; |
|
1134 }, |
|
1135 |
|
1136 get size() { |
|
1137 return null; |
|
1138 }, |
|
1139 |
|
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; |
|
1150 } |
|
1151 } |
|
1152 return permissions; |
|
1153 }, |
|
1154 |
|
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 }, |
|
1163 |
|
1164 get isActive() { |
|
1165 return ActiveProviders.has(this.manifest.origin); |
|
1166 }, |
|
1167 |
|
1168 get name() { |
|
1169 return this.manifest.name; |
|
1170 }, |
|
1171 get version() { |
|
1172 return this.manifest.version ? this.manifest.version : ""; |
|
1173 }, |
|
1174 |
|
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 }, |
|
1191 |
|
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 }, |
|
1204 |
|
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 }, |
|
1211 |
|
1212 get creator() { |
|
1213 return new AddonManagerPrivate.AddonAuthor(this.manifest.author); |
|
1214 }, |
|
1215 |
|
1216 get userDisabled() { |
|
1217 return this.appDisabled || !ActiveProviders.has(this.manifest.origin); |
|
1218 }, |
|
1219 |
|
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); |
|
1227 } |
|
1228 return val; |
|
1229 }, |
|
1230 |
|
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); |
|
1240 } |
|
1241 } else { |
|
1242 schedule(aCallback); |
|
1243 } |
|
1244 }, |
|
1245 |
|
1246 cancelUninstall: function() { |
|
1247 this._pending -= AddonManager.PENDING_UNINSTALL; |
|
1248 AddonManagerPrivate.callAddonListeners("onOperationCancelled", this); |
|
1249 } |
|
1250 }; |
|
1251 |
|
1252 |
|
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 ]); |