Bumping manifests a=b2g-bump
[gecko.git] / dom / apps / Webapps.js
blob35a7fcfd896febd45ed9027b037eded25dc5b97c
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 const Cc = Components.classes;
6 const Ci = Components.interfaces;
7 const Cu = Components.utils;
8 const Cr = Components.results;
10 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
11 Cu.import("resource://gre/modules/Services.jsm");
12 Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
13 Cu.import("resource://gre/modules/AppsUtils.jsm");
14 Cu.import("resource://gre/modules/BrowserElementPromptService.jsm");
15 Cu.import("resource://gre/modules/AppsServiceChild.jsm");
16 Cu.import("resource://gre/modules/Preferences.jsm");
18 XPCOMUtils.defineLazyServiceGetter(this, "appsService",
19                                    "@mozilla.org/AppsService;1",
20                                    "nsIAppsService");
22 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
23                                    "@mozilla.org/childprocessmessagemanager;1",
24                                    "nsIMessageSender");
26 function debug(aMsg) {
27   dump("-*- Webapps.js " + aMsg + "\n");
30 function convertAppsArray(aApps, aWindow) {
31   let apps = new aWindow.Array();
32   aApps.forEach((aApp) => {
33     let obj = createContentApplicationObject(aWindow, aApp);
34     apps.push(obj);
35   });
36   return apps;
39 function WebappsRegistry() {
42 WebappsRegistry.prototype = {
43   __proto__: DOMRequestIpcHelper.prototype,
45   receiveMessage: function(aMessage) {
46     let msg = aMessage.json;
47     let req;
48     if (aMessage.name != "Webapps:AdditionalLanguageChange") {
49       if (msg.oid != this._id) {
50         return
51       }
53       if (aMessage.name == "Webapps:GetAdditionalLanguages:Return" ||
54         aMessage.name == "Webapps:GetLocalizationResource:Return") {
55         req = this.takePromiseResolver(msg.requestID);
56       } else {
57         req = this.getRequest(msg.requestID);
58       }
59       if (!req) {
60         return;
61       }
62     }
64     let app = msg.app;
65     switch (aMessage.name) {
66       case "Webapps:Install:Return:OK":
67         this.removeMessageListeners("Webapps:Install:Return:KO");
68         Services.DOMRequest.fireSuccess(req, createContentApplicationObject(this._window, app));
69         cpmm.sendAsyncMessage("Webapps:Install:Return:Ack",
70                               { manifestURL : app.manifestURL });
71         break;
72       case "Webapps:Install:Return:KO":
73         this.removeMessageListeners(aMessage.name);
74         Services.DOMRequest.fireError(req, msg.error || "DENIED");
75         break;
76       case "Webapps:GetSelf:Return:OK":
77         this.removeMessageListeners(aMessage.name);
78         if (msg.apps.length) {
79           app = msg.apps[0];
80           Services.DOMRequest.fireSuccess(req, createContentApplicationObject(this._window, app));
81         } else {
82           Services.DOMRequest.fireSuccess(req, null);
83         }
84         break;
85       case "Webapps:CheckInstalled:Return:OK":
86         this.removeMessageListeners(aMessage.name);
87         Services.DOMRequest.fireSuccess(req, msg.app);
88         break;
89       case "Webapps:GetInstalled:Return:OK":
90         this.removeMessageListeners(aMessage.name);
91         Services.DOMRequest.fireSuccess(req, convertAppsArray(msg.apps, this._window));
92         break;
93       case "Webapps:AdditionalLanguageChange":
94         // Check if the current page is from the app receiving the event.
95         let manifestURL = AppsUtils.getAppManifestURLFromWindow(this._window);
96         if (manifestURL && manifestURL == msg.manifestURL) {
97           // Let's dispatch an "additionallanguageschange" event on the document.
98           let doc = this._window.document;
99           let event = doc.createEvent("CustomEvent");
100           event.initCustomEvent("additionallanguageschange", true, true,
101                                 Cu.cloneInto(msg.languages, this._window));
102           doc.dispatchEvent(event);
103         }
104         break;
105       case "Webapps:GetLocalizationResource:Return":
106         this.removeMessageListeners(["Webapps:GetLocalizationResource:Return"]);
107         if (msg.error) {
108           req.reject(new this._window.DOMError(msg.error));
109         } else {
110           req.resolve(Cu.cloneInto(msg.data, this._window));
111         }
112         break;
113     }
114     this.removeRequest(msg.requestID);
115   },
117   _getOrigin: function(aURL) {
118     let uri = Services.io.newURI(aURL, null, null);
119     return uri.prePath;
120   },
122   // Checks that the URL scheme is appropriate (http or https) and
123   // asynchronously fire an error on the DOM Request if it isn't.
124   _validateURL: function(aURL, aRequest) {
125     let uri;
126     let res;
128     try {
129       uri = Services.io.newURI(aURL, null, null);
130       if (uri.schemeIs("http") || uri.schemeIs("https")) {
131         res = uri.spec;
132       }
133     } catch(e) {
134       Services.DOMRequest.fireErrorAsync(aRequest, "INVALID_URL");
135       return false;
136     }
138     // The scheme is incorrect, fire DOMRequest error.
139     if (!res) {
140       Services.DOMRequest.fireErrorAsync(aRequest, "INVALID_URL");
141       return false;
142     }
144     return uri.spec;
145   },
147   // Checks that we run as a foreground page, and fire an error on the
148   // DOM Request if we aren't.
149   _ensureForeground: function(aRequest) {
150     let docShell = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
151                                .getInterface(Ci.nsIWebNavigation)
152                                .QueryInterface(Ci.nsIDocShell);
153     if (docShell.isActive) {
154       return true;
155     }
157     Services.DOMRequest.fireErrorAsync(aRequest, "BACKGROUND_APP");
158     return false;
159   },
161   _prepareInstall: function(aURL, aRequest, aParams, isPackage) {
162     let installURL = this._window.location.href;
163     let requestID = this.getRequestId(aRequest);
164     let receipts = (aParams && aParams.receipts &&
165                     Array.isArray(aParams.receipts)) ? aParams.receipts
166                                                      : [];
167     let categories = (aParams && aParams.categories &&
168                       Array.isArray(aParams.categories)) ? aParams.categories
169                                                          : [];
171     let principal = this._window.document.nodePrincipal;
173     return { app: {
174                     installOrigin: this._getOrigin(installURL),
175                     origin: this._getOrigin(aURL),
176                     manifestURL: aURL,
177                     receipts: receipts,
178                     categories: categories
179                   },
181              from: installURL,
182              oid: this._id,
183              requestID: requestID,
184              appId: principal.appId,
185              isBrowser: principal.isInBrowserElement,
186              isPackage: isPackage
187            };
188   },
190   // mozIDOMApplicationRegistry implementation
192   install: function(aURL, aParams) {
193     let request = this.createRequest();
195     let uri = this._validateURL(aURL, request);
197     if (uri && this._ensureForeground(request)) {
198       this.addMessageListeners("Webapps:Install:Return:KO");
199       cpmm.sendAsyncMessage("Webapps:Install",
200                             this._prepareInstall(uri, request, aParams, false));
201     }
203     return request;
204   },
206   getSelf: function() {
207     let request = this.createRequest();
208     this.addMessageListeners("Webapps:GetSelf:Return:OK");
209     cpmm.sendAsyncMessage("Webapps:GetSelf", { origin: this._getOrigin(this._window.location.href),
210                                                appId: this._window.document.nodePrincipal.appId,
211                                                oid: this._id,
212                                                requestID: this.getRequestId(request) });
213     return request;
214   },
216   checkInstalled: function(aManifestURL) {
217     let manifestURL = Services.io.newURI(aManifestURL, null, this._window.document.baseURIObject);
219     let request = this.createRequest();
221     try {
222       this._window.document.nodePrincipal.checkMayLoad(manifestURL, true,
223                                                        false);
224     } catch (ex) {
225       Services.DOMRequest.fireErrorAsync(request, "CROSS_ORIGIN_CHECK_NOT_ALLOWED");
226       return request;
227     }
229     this.addMessageListeners("Webapps:CheckInstalled:Return:OK");
230     cpmm.sendAsyncMessage("Webapps:CheckInstalled", { origin: this._getOrigin(this._window.location.href),
231                                                       manifestURL: manifestURL.spec,
232                                                       oid: this._id,
233                                                       requestID: this.getRequestId(request) });
234     return request;
235   },
237   getInstalled: function() {
238     let request = this.createRequest();
239     this.addMessageListeners("Webapps:GetInstalled:Return:OK");
240     cpmm.sendAsyncMessage("Webapps:GetInstalled", { origin: this._getOrigin(this._window.location.href),
241                                                     oid: this._id,
242                                                     requestID: this.getRequestId(request) });
243     return request;
244   },
246   get mgmt() {
247     if (!this.hasMgmtPrivilege) {
248       return null;
249     }
251     if (!this._mgmt) {
252       let mgmt = Cc["@mozilla.org/webapps/manager;1"]
253                    .createInstance(Ci.nsISupports);
254       mgmt.wrappedJSObject.init(this._window, this.hasFullMgmtPrivilege);
255       mgmt.wrappedJSObject._windowId = this._id;
256       this._mgmt = mgmt.__DOM_IMPL__
257         ? mgmt.__DOM_IMPL__
258         : this._window.DOMApplicationsManager._create(this._window, mgmt.wrappedJSObject);
259     }
260     return this._mgmt;
261   },
263   uninit: function() {
264     this._mgmt = null;
265     cpmm.sendAsyncMessage("Webapps:UnregisterForMessages",
266                           ["Webapps:Install:Return:OK",
267                            "Webapps:AdditionalLanguageChange"]);
268   },
270   installPackage: function(aURL, aParams) {
271     let request = this.createRequest();
273     let uri = this._validateURL(aURL, request);
275     if (uri && this._ensureForeground(request)) {
276       this.addMessageListeners("Webapps:Install:Return:KO");
277       cpmm.sendAsyncMessage("Webapps:InstallPackage",
278                             this._prepareInstall(uri, request, aParams, true));
279     }
281     return request;
282   },
284   _getCurrentAppManifestURL: function() {
285     let appId = this._window.document.nodePrincipal.appId;
286     if (appId === Ci.nsIScriptSecurityManager.NO_APP_ID) {
287       return null;
288     }
290     return appsService.getManifestURLByLocalId(appId);
291   },
293   getAdditionalLanguages: function() {
294     let manifestURL = AppsUtils.getAppManifestURLFromWindow(this._window);
296     return new this._window.Promise((aResolve, aReject) => {
297       if (!manifestURL) {
298         aReject("NotInApp");
299       } else {
300         let langs = DOMApplicationRegistry.getAdditionalLanguages(manifestURL);
301         aResolve(Cu.cloneInto(langs, this._window));
302       }
303     });
304   },
306   getLocalizationResource: function(aLanguage, aVersion, aPath, aType) {
307     let manifestURL = AppsUtils.getAppManifestURLFromWindow(this._window);
309     if (!manifestURL) {
310       return new Promise((aResolve, aReject) => {
311         aReject("NotInApp");
312       });
313     }
315     this.addMessageListeners(["Webapps:GetLocalizationResource:Return"]);
316     return this.createPromise((aResolve, aReject) => {
317       cpmm.sendAsyncMessage("Webapps:GetLocalizationResource", {
318         manifestURL: manifestURL,
319         lang: aLanguage,
320         version: aVersion,
321         path: aPath,
322         dataType: aType,
323         oid: this._id,
324         requestID: this.getPromiseResolverId({
325           resolve: aResolve,
326           reject: aReject
327         })
328       });
329     });
330   },
332   // nsIDOMGlobalPropertyInitializer implementation
333   init: function(aWindow) {
334     const prefs = new Preferences();
336     this._window = aWindow;
338     this.initDOMRequestHelper(aWindow, ["Webapps:Install:Return:OK",
339                                         "Webapps:AdditionalLanguageChange"]);
341     let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
342                            .getInterface(Ci.nsIDOMWindowUtils);
343     this._id = util.outerWindowID;
344     cpmm.sendAsyncMessage("Webapps:RegisterForMessages",
345                           { messages: ["Webapps:Install:Return:OK",
346                                        "Webapps:AdditionalLanguageChange"]});
348     let principal = aWindow.document.nodePrincipal;
349     let appId = principal.appId;
350     let app = appId && appsService.getAppByLocalId(appId);
352     let isCurrentHomescreen = app &&
353       app.manifestURL == prefs.get("dom.mozApps.homescreenURL") &&
354       app.appStatus != Ci.nsIPrincipal.APP_STATUS_NOT_INSTALLED;
356     let hasWebappsPermission = Ci.nsIPermissionManager.ALLOW_ACTION ==
357       Services.perms.testExactPermissionFromPrincipal(
358         principal, "webapps-manage");
360     let hasHomescreenPermission = Ci.nsIPermissionManager.ALLOW_ACTION ==
361       Services.perms.testExactPermissionFromPrincipal(
362         principal, "homescreen-webapps-manage");
364     this.hasMgmtPrivilege = hasWebappsPermission ||
365          (isCurrentHomescreen && hasHomescreenPermission);
366     this.hasFullMgmtPrivilege = hasWebappsPermission;
367   },
369   classID: Components.ID("{fff440b3-fae2-45c1-bf03-3b5a2e432270}"),
371   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
372                                          Ci.nsISupports,
373                                          Ci.nsIObserver,
374                                          Ci.nsIDOMGlobalPropertyInitializer])
378   * DOMApplication object
379   */
381 function createApplicationObject(aWindow, aApp) {
382   let app = Cc["@mozilla.org/webapps/application;1"]
383               .createInstance(Ci.nsISupports);
384   app.wrappedJSObject.init(aWindow, aApp);
385   return app;
388 function createContentApplicationObject(aWindow, aApp) {
389   return createApplicationObject(aWindow, aApp).wrappedJSObject
390          ._prepareForContent();
393 function WebappsApplication() {
394   this.wrappedJSObject = this;
397 WebappsApplication.prototype = {
398   __proto__: DOMRequestIpcHelper.prototype,
400   init: function(aWindow, aApp) {
401     this._window = aWindow;
403     let proxyHandler = DOMApplicationRegistry.addDOMApp(this,
404                                                         aApp.manifestURL,
405                                                         aApp.id);
406     this._proxy = new Proxy(this, proxyHandler);
408     this.initDOMRequestHelper(aWindow);
409   },
411   get _appStatus() {
412     return this._proxy.appStatus;
413   },
415   get downloadAvailable() {
416     return this._proxy.downloadAvailable;
417   },
419   get downloading() {
420     return this._proxy.downloading;
421   },
423   get downloadSize() {
424     return this._proxy.downloadSize;
425   },
427   get installOrigin() {
428     return this._proxy.installOrigin;
429   },
431   get installState() {
432     return this._proxy.installState;
433   },
435   get installTime() {
436     return this._proxy.installTime;
437   },
439   get lastUpdateCheck() {
440     return this._proxy.lastUpdateCheck;
441   },
443   get manifestURL() {
444     return this._proxy.manifestURL;
445   },
447   get origin() {
448     return this._proxy.origin;
449   },
451   get progress() {
452     return this._proxy.progress;
453   },
455   get readyToApplyDownload() {
456     return this._proxy.readyToApplyDownload;
457   },
459   get removable() {
460     return this._proxy.removable;
461   },
463   get updateTime() {
464     return this._proxy.updateTime;
465   },
467   get manifest() {
468     return WrappedManifestCache.get(this.manifestURL,
469                                     this._proxy.manifest,
470                                     this._window,
471                                     this.innerWindowID);
472   },
474   get updateManifest() {
475     return this._proxy.updateManifest ?
476       Cu.cloneInto(this._proxy.updateManifest, this._window) : null;
477   },
479   set onprogress(aCallback) {
480     this.__DOM_IMPL__.setEventHandler("onprogress", aCallback);
481   },
483   get onprogress() {
484     return this.__DOM_IMPL__.getEventHandler("onprogress");
485   },
487   set ondownloadsuccess(aCallback) {
488     this.__DOM_IMPL__.setEventHandler("ondownloadsuccess", aCallback);
489   },
491   get ondownloadsuccess() {
492     return this.__DOM_IMPL__.getEventHandler("ondownloadsuccess");
493   },
495   set ondownloaderror(aCallback) {
496     this.__DOM_IMPL__.setEventHandler("ondownloaderror", aCallback);
497   },
499   get ondownloaderror() {
500     return this.__DOM_IMPL__.getEventHandler("ondownloaderror");
501   },
503   set ondownloadavailable(aCallback) {
504     this.__DOM_IMPL__.setEventHandler("ondownloadavailable", aCallback);
505   },
507   get ondownloadavailable() {
508     return this.__DOM_IMPL__.getEventHandler("ondownloadavailable");
509   },
511   set ondownloadapplied(aCallback) {
512     this.__DOM_IMPL__.setEventHandler("ondownloadapplied", aCallback);
513   },
515   get ondownloadapplied() {
516     return this.__DOM_IMPL__.getEventHandler("ondownloadapplied");
517   },
519   get receipts() {
520     return this._proxy.receipts || [];
521   },
523   get downloadError() {
524     // Only return DOMError when we have an error.
525     if (!this._proxy.downloadError) {
526       return null;
527     }
528     return new this._window.DOMError(this._proxy.downloadError);
529   },
531   get enabled() {
532     return this._proxy.enabled;
533   },
535   download: function() {
536     cpmm.sendAsyncMessage("Webapps:Download",
537                           { manifestURL: this.manifestURL });
538   },
540   cancelDownload: function() {
541     cpmm.sendAsyncMessage("Webapps:CancelDownload",
542                           { manifestURL: this.manifestURL });
543   },
545   checkForUpdate: function() {
546     let request = this.createRequest();
548     cpmm.sendAsyncMessage("Webapps:CheckForUpdate",
549                           { manifestURL: this.manifestURL,
550                             oid: this._id,
551                             requestID: this.getRequestId(request) });
552     return request;
553   },
555   launch: function(aStartPoint) {
556     let request = this.createRequest();
557     this.addMessageListeners(["Webapps:Launch:Return:OK",
558                               "Webapps:Launch:Return:KO"]);
559     cpmm.sendAsyncMessage("Webapps:Launch", { origin: this.origin,
560                                               manifestURL: this.manifestURL,
561                                               startPoint: aStartPoint || "",
562                                               oid: this._id,
563                                               timestamp: Date.now(),
564                                               requestID: this.getRequestId(request) });
565     return request;
566   },
568   clearBrowserData: function() {
569     let request = this.createRequest();
570     let browserChild =
571       BrowserElementPromptService.getBrowserElementChildForWindow(this._window);
572     if (browserChild) {
573       this.addMessageListeners("Webapps:ClearBrowserData:Return");
574       browserChild.messageManager.sendAsyncMessage("Webapps:ClearBrowserData", {
575         manifestURL: this.manifestURL,
576         oid: this._id,
577         requestID: this.getRequestId(request)
578       });
579     } else {
580       Services.DOMRequest.fireErrorAsync(request, "NO_CLEARABLE_BROWSER");
581     }
582     return request;
583   },
585   connect: function(aKeyword, aRules) {
586     this.addMessageListeners(["Webapps:Connect:Return:OK",
587                               "Webapps:Connect:Return:KO"]);
588     return this.createPromise(function (aResolve, aReject) {
589       cpmm.sendAsyncMessage("Webapps:Connect", {
590         keyword: aKeyword,
591         rules: aRules,
592         manifestURL: this.manifestURL,
593         outerWindowID: this._id,
594         requestID: this.getPromiseResolverId({
595           resolve: aResolve,
596           reject: aReject
597         })
598       });
599     }.bind(this));
600   },
602   getConnections: function() {
603     this.addMessageListeners("Webapps:GetConnections:Return:OK");
604     return this.createPromise(function (aResolve, aReject) {
605       cpmm.sendAsyncMessage("Webapps:GetConnections", {
606         manifestURL: this.manifestURL,
607         outerWindowID: this._id,
608         requestID: this.getPromiseResolverId({
609           resolve: aResolve,
610           reject: aReject
611         })
612       });
613     }.bind(this));
614   },
616   addReceipt: function(receipt) {
617     let request = this.createRequest();
619     this.addMessageListeners(["Webapps:AddReceipt:Return:OK",
620                               "Webapps:AddReceipt:Return:KO"]);
622     cpmm.sendAsyncMessage("Webapps:AddReceipt", { manifestURL: this.manifestURL,
623                                                   receipt: receipt,
624                                                   oid: this._id,
625                                                   requestID: this.getRequestId(request) });
627     return request;
628   },
630   removeReceipt: function(receipt) {
631     let request = this.createRequest();
633     this.addMessageListeners(["Webapps:RemoveReceipt:Return:OK",
634                               "Webapps:RemoveReceipt:Return:KO"]);
636     cpmm.sendAsyncMessage("Webapps:RemoveReceipt", { manifestURL: this.manifestURL,
637                                                      receipt: receipt,
638                                                      oid: this._id,
639                                                      requestID: this.getRequestId(request) });
641     return request;
642   },
644   replaceReceipt: function(oldReceipt, newReceipt) {
645     let request = this.createRequest();
647     this.addMessageListeners(["Webapps:ReplaceReceipt:Return:OK",
648                               "Webapps:ReplaceReceipt:Return:KO"]);
650     cpmm.sendAsyncMessage("Webapps:ReplaceReceipt", { manifestURL: this.manifestURL,
651                                                       newReceipt: newReceipt,
652                                                       oldReceipt: oldReceipt,
653                                                       oid: this._id,
654                                                       requestID: this.getRequestId(request) });
656     return request;
657   },
659   export: function() {
660     this.addMessageListeners(["Webapps:Export:Return"]);
661     return this.createPromise((aResolve, aReject) => {
662       cpmm.sendAsyncMessage("Webapps:Export",
663         { manifestURL: this.manifestURL,
664           oid: this._id,
665           requestID: this.getPromiseResolverId({
666             resolve: aResolve,
667             reject: aReject
668           })
669         });
670     });
671   },
673   getLocalizedValue: function(aProperty, aLang, aEntryPoint) {
674     this.addMessageListeners(["Webapps:GetLocalizedValue:Return"]);
675     return this.createPromise((aResolve, aReject) => {
676       cpmm.sendAsyncMessage("Webapps:GetLocalizedValue",
677         { manifestURL: this.manifestURL,
678           oid: this._id,
679           topId: this._topId,
680           property: aProperty,
681           lang: aLang,
682           entryPoint: aEntryPoint,
683           requestID: this.getPromiseResolverId({
684             resolve: aResolve,
685             reject: aReject
686           })
687         });
688     });
689   },
691   _prepareForContent: function() {
692     if (this.__DOM_IMPL__) {
693       return this.__DOM_IMPL__;
694     }
695     return this._window.DOMApplication._create(this._window, this.wrappedJSObject);
696   },
698   uninit: function() {
699     WrappedManifestCache.evict(this.manifestURL, this.innerWindowID);
700   },
702   _fireEvent: function(aName) {
703     let obj = this._prepareForContent();
704     let event = new this._window.MozApplicationEvent(aName, {
705       application: obj
706     });
707     obj.dispatchEvent(event);
708   },
710   _fireRequestResult: function(aMessage, aIsError) {
711     let req;
712     let msg = aMessage.data;
713     req = this.takeRequest(msg.requestID);
714     if (!req) {
715       return;
716     }
718     aIsError ? Services.DOMRequest.fireError(req, msg.error)
719              : Services.DOMRequest.fireSuccess(req, msg.result);
720   },
722   receiveMessage: function(aMessage) {
723     let msg = aMessage.json;
724     let req;
725     if (aMessage.name == "Webapps:Connect:Return:OK" ||
726         aMessage.name == "Webapps:Connect:Return:KO" ||
727         aMessage.name == "Webapps:GetConnections:Return:OK" ||
728         aMessage.name == "Webapps:Export:Return" ||
729         aMessage.name == "Webapps:GetLocalizedValue:Return") {
730       req = this.takePromiseResolver(msg.requestID);
731     } else {
732       req = this.takeRequest(msg.requestID);
733     }
735     if (msg.oid !== this._id || !req) {
736       return;
737     }
739     switch (aMessage.name) {
740       case "Webapps:Launch:Return:KO":
741         this.removeMessageListeners(["Webapps:Launch:Return:OK",
742                                      "Webapps:Launch:Return:KO"]);
743         Services.DOMRequest.fireError(req, msg.error);
744         break;
745       case "Webapps:Launch:Return:OK":
746         this.removeMessageListeners(["Webapps:Launch:Return:OK",
747                                      "Webapps:Launch:Return:KO"]);
748         Services.DOMRequest.fireSuccess(req, null);
749         break;
750       case "Webapps:ClearBrowserData:Return":
751         this.removeMessageListeners(aMessage.name);
752         Services.DOMRequest.fireSuccess(req, null);
753         break;
754       case "Webapps:Connect:Return:OK":
755         this.removeMessageListeners(["Webapps:Connect:Return:OK",
756                                      "Webapps:Connect:Return:KO"]);
757         let messagePorts = new this._window.Array();
758         msg.messagePortIDs.forEach((aPortID) => {
759           let port = new this._window.MozInterAppMessagePort(aPortID);
760           messagePorts.push(port);
761         });
762         req.resolve(messagePorts);
763         break;
764       case "Webapps:Connect:Return:KO":
765         this.removeMessageListeners(["Webapps:Connect:Return:OK",
766                                      "Webapps:Connect:Return:KO"]);
767         req.reject("No connections registered");
768         break;
769       case "Webapps:GetConnections:Return:OK":
770         this.removeMessageListeners(aMessage.name);
771         let connections = new this._window.Array();
772         msg.connections.forEach((aConnection) => {
773           let connection =
774             new this._window.MozInterAppConnection(aConnection.keyword,
775                                                    aConnection.pubAppManifestURL,
776                                                    aConnection.subAppManifestURL);
777           connections.push(connection);
778         });
779         req.resolve(connections);
780         break;
781       case "Webapps:AddReceipt:Return:OK":
782         this.removeMessageListeners(["Webapps:AddReceipt:Return:OK",
783                                      "Webapps:AddReceipt:Return:KO"]);
784         this.__DOM_IMPL__._clearCachedReceiptsValue();
785         this._proxy.receipts = msg.receipts;
786         Services.DOMRequest.fireSuccess(req, null);
787         break;
788       case "Webapps:AddReceipt:Return:KO":
789         this.removeMessageListeners(["Webapps:AddReceipt:Return:OK",
790                                      "Webapps:AddReceipt:Return:KO"]);
791         Services.DOMRequest.fireError(req, msg.error);
792         break;
793       case "Webapps:RemoveReceipt:Return:OK":
794         this.removeMessageListeners(["Webapps:RemoveReceipt:Return:OK",
795                                      "Webapps:RemoveReceipt:Return:KO"]);
796         this.__DOM_IMPL__._clearCachedReceiptsValue();
797         this._proxy.receipts = msg.receipts;
798         Services.DOMRequest.fireSuccess(req, null);
799         break;
800       case "Webapps:RemoveReceipt:Return:KO":
801         this.removeMessageListeners(["Webapps:RemoveReceipt:Return:OK",
802                                      "Webapps:RemoveReceipt:Return:KO"]);
803         Services.DOMRequest.fireError(req, msg.error);
804         break;
805       case "Webapps:ReplaceReceipt:Return:OK":
806         this.removeMessageListeners(["Webapps:ReplaceReceipt:Return:OK",
807                                      "Webapps:ReplaceReceipt:Return:KO"]);
808         this.__DOM_IMPL__._clearCachedReceiptsValue();
809         this._proxy.receipts = msg.receipts;
810         Services.DOMRequest.fireSuccess(req, null);
811         break;
812       case "Webapps:ReplaceReceipt:Return:KO":
813         this.removeMessageListeners(["Webapps:ReplaceReceipt:Return:OK",
814                                      "Webapps:ReplaceReceipt:Return:KO"]);
815         Services.DOMRequest.fireError(req, msg.error);
816         break;
817       case "Webapps:Export:Return":
818         this.removeMessageListeners(["Webapps:Export:Return"]);
819         if (msg.success) {
820           req.resolve(Cu.cloneInto(msg.blob, this._window));
821         } else {
822           req.reject(new this._window.DOMError(msg.error || ""));
823         }
824         break;
825       case "Webapps:GetLocalizedValue:Return":
826         this.removeMessageListeners(["Webapps:GetLocalizedValue:Return"]);
827         if (msg.success) {
828           req.resolve(msg.value);
829         } else {
830           req.reject(new this._window.DOMError(msg.error || ""));
831         }
832         break;
833     }
834   },
836   classID: Components.ID("{723ed303-7757-4fb0-b261-4f78b1f6bd22}"),
838   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
839                                          Ci.nsIObserver,
840                                          Ci.nsISupportsWeakReference])
844   * DOMApplicationsManager object
845   */
846 function WebappsApplicationMgmt() {
847   this.wrappedJSObject = this;
850 WebappsApplicationMgmt.prototype = {
851   __proto__: DOMRequestIpcHelper.prototype,
853   init: function(aWindow, aHasFullMgmtPrivilege) {
854     this._window = aWindow;
856     this.initDOMRequestHelper(aWindow, ["Webapps:Uninstall:Return:OK",
857                                         "Webapps:Uninstall:Broadcast:Return:OK",
858                                         "Webapps:Uninstall:Return:KO",
859                                         "Webapps:Install:Return:OK",
860                                         "Webapps:GetNotInstalled:Return:OK",
861                                         "Webapps:Import:Return",
862                                         "Webapps:ExtractManifest:Return",
863                                         "Webapps:SetEnabled:Return"]);
864     cpmm.sendAsyncMessage("Webapps:RegisterForMessages",
865                           {
866                             messages: ["Webapps:Install:Return:OK",
867                                        "Webapps:Uninstall:Return:OK",
868                                        "Webapps:Uninstall:Broadcast:Return:OK",
869                                        "Webapps:SetEnabled:Return"]
870                           }
871                          );
873     if (!aHasFullMgmtPrivilege) {
874       this.getNotInstalled = null;
875       this.applyDownload = null;
876     }
877   },
879   uninit: function() {
880     cpmm.sendAsyncMessage("Webapps:UnregisterForMessages",
881                           ["Webapps:Install:Return:OK",
882                            "Webapps:Uninstall:Return:OK",
883                            "Webapps:Uninstall:Broadcast:Return:OK",
884                            "Webapps:SetEnabled:Return"]);
885   },
887   applyDownload: function(aApp) {
888     if (!aApp.readyToApplyDownload) {
889       return;
890     }
892     let principal = this._window.document.nodePrincipal;
894     cpmm.sendAsyncMessage("Webapps:ApplyDownload",
895                           { manifestURL: aApp.manifestURL },
896                           null, principal);
897   },
899   uninstall: function(aApp) {
900     let request = this.createRequest();
901     let principal = this._window.document.nodePrincipal;
903     cpmm.sendAsyncMessage("Webapps:Uninstall", {
904       origin: aApp.origin,
905       manifestURL: aApp.manifestURL,
906       oid: this._id,
907       from: this._window.location.href,
908       windowId: this._windowId,
909       requestID: this.getRequestId(request)
910     }, null, principal);
912     return request;
913   },
915   getAll: function() {
916     let request = this.createRequest();
917     let window = this._window;
918     DOMApplicationRegistry.getAll((aApps) => {
919       Services.DOMRequest.fireSuccessAsync(request,
920                                            convertAppsArray(aApps, window));
921     });
923     return request;
924   },
926   getNotInstalled: function() {
927     let request = this.createRequest();
928     let principal = this._window.document.nodePrincipal;
930     cpmm.sendAsyncMessage("Webapps:GetNotInstalled", {
931       oid: this._id,
932       requestID: this.getRequestId(request)
933     }, null, principal);
935     return request;
936   },
938   import: function(aBlob) {
939     let principal = this._window.document.nodePrincipal;
940     return this.createPromise((aResolve, aReject) => {
941       cpmm.sendAsyncMessage("Webapps:Import",
942         { blob: aBlob,
943           oid: this._id,
944           requestID: this.getPromiseResolverId({
945             resolve: aResolve,
946             reject: aReject
947           })}, null, principal);
948     });
949   },
951   extractManifest: function(aBlob) {
952     let principal = this._window.document.nodePrincipal;
953     return this.createPromise((aResolve, aReject) => {
954       cpmm.sendAsyncMessage("Webapps:ExtractManifest",
955         { blob: aBlob,
956           oid: this._id,
957           requestID: this.getPromiseResolverId({
958             resolve: aResolve,
959             reject: aReject
960           })}, null, principal);
961     });
962   },
964   setEnabled: function(aApp, aValue) {
965     let principal = this._window.document.nodePrincipal;
967     cpmm.sendAsyncMessage("Webapps:SetEnabled",
968                           { manifestURL: aApp.manifestURL,
969                             enabled: aValue }, null, principal);
970   },
972   get oninstall() {
973     return this.__DOM_IMPL__.getEventHandler("oninstall");
974   },
976   get onuninstall() {
977     return this.__DOM_IMPL__.getEventHandler("onuninstall");
978   },
980   get onenabledstatechange() {
981     return this.__DOM_IMPL__.getEventHandler("onenabledstatechange");
982   },
984   set oninstall(aCallback) {
985     this.__DOM_IMPL__.setEventHandler("oninstall", aCallback);
986   },
988   set onuninstall(aCallback) {
989     this.__DOM_IMPL__.setEventHandler("onuninstall", aCallback);
990   },
992   set onenabledstatechange(aCallback) {
993     this.__DOM_IMPL__.setEventHandler("onenabledstatechange", aCallback);
994   },
996   receiveMessage: function(aMessage) {
997     let msg = aMessage.data;
998     let req;
1000     if (["Webapps:Import:Return",
1001          "Webapps:ExtractManifest:Return"]
1002          .indexOf(aMessage.name) != -1) {
1003       req = this.takePromiseResolver(msg.requestID);
1004     } else {
1005       req = this.getRequest(msg.requestID);
1006     }
1008     // We want Webapps:Install:Return:OK, Webapps:Uninstall:Broadcast:Return:OK
1009     // and Webapps:SetEnabled:Return
1010     // to be broadcasted to all instances of mozApps.mgmt.
1011     if (!((msg.oid == this._id && req) ||
1012           aMessage.name == "Webapps:Install:Return:OK" ||
1013           aMessage.name == "Webapps:Uninstall:Broadcast:Return:OK" ||
1014           aMessage.name == "Webapps:SetEnabled:Return")) {
1015       return;
1016     }
1018     switch (aMessage.name) {
1019       case "Webapps:GetNotInstalled:Return:OK":
1020         Services.DOMRequest.fireSuccess(req, convertAppsArray(msg.apps, this._window));
1021         break;
1022       case "Webapps:Install:Return:OK":
1023         {
1024           let app = createContentApplicationObject(this._window, msg.app);
1025           let event =
1026             new this._window.MozApplicationEvent("install", { application: app });
1027           this.__DOM_IMPL__.dispatchEvent(event);
1028         }
1029         break;
1030       case "Webapps:Uninstall:Broadcast:Return:OK":
1031         {
1032           let app = createContentApplicationObject(this._window, msg);
1033           let event =
1034             new this._window.MozApplicationEvent("uninstall", { application : app });
1035           this.__DOM_IMPL__.dispatchEvent(event);
1036         }
1037         break;
1038       case "Webapps:Uninstall:Return:OK":
1039         Services.DOMRequest.fireSuccess(req, msg.manifestURL);
1040         break;
1041       case "Webapps:Uninstall:Return:KO":
1042         Services.DOMRequest.fireError(req, "NOT_INSTALLED");
1043         break;
1044       case "Webapps:Import:Return":
1045         if (msg.success) {
1046           req.resolve(createContentApplicationObject(this._window, msg.app));
1047         } else {
1048           req.reject(new this._window.DOMError(msg.error || ""));
1049         }
1050         break;
1051       case "Webapps:ExtractManifest:Return":
1052         if (msg.success) {
1053           req.resolve(Cu.cloneInto(msg.manifest, this._window));
1054         } else {
1055           req.reject(new this._window.DOMError(msg.error || ""));
1056         }
1057         break;
1058       case "Webapps:SetEnabled:Return":
1059         {
1060           let app = createContentApplicationObject(this._window, msg);
1061           let event =
1062             new this._window.MozApplicationEvent("enabledstatechange", { application : app });
1063           this.__DOM_IMPL__.dispatchEvent(event);
1064         }
1065         break;
1066     }
1067     if (aMessage.name !== "Webapps:Uninstall:Broadcast:Return:OK") {
1068       this.removeRequest(msg.requestID);
1069     }
1070   },
1072   classID: Components.ID("{8c1bca96-266f-493a-8d57-ec7a95098c15}"),
1074   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
1075                                          Ci.nsIObserver,
1076                                          Ci.nsISupportsWeakReference])
1079 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WebappsRegistry,
1080                                                      WebappsApplicationMgmt,
1081                                                      WebappsApplication]);