Mozilla Cross-Reference mozilla-central
mozilla/ dom/ apps/ Webapps.js
Hg Log
Hg Blame
Diff file
Raw file
view using tree:
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 const Cc = Components.classes;
6 const Ci = Components.interfaces;
7 const Cu = Components.utils;
8 const Cr = Components.results;
9 
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");
17 
18 XPCOMUtils.defineLazyServiceGetter(this, "appsService",
19                                    "@mozilla.org/AppsService;1",
20                                    "nsIAppsService");
21 
22 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
23                                    "@mozilla.org/childprocessmessagemanager;1",
24                                    "nsIMessageSender");
25 
26 function debug(aMsg) {
27   dump("-*- Webapps.js " + aMsg + "\n");
28 }
29 
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;
37 }
38 
39 function WebappsRegistry() {
40 }
41 
42 WebappsRegistry.prototype = {
43   __proto__: DOMRequestIpcHelper.prototype,
44 
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       }
52 
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     }
63 
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, createContentApplicationObject(this._window, 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   },
116 
117   _getOrigin: function(aURL) {
118     let uri = Services.io.newURI(aURL, null, null);
119     return uri.prePath;
120   },
121 
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;
127 
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     }
137 
138     // The scheme is incorrect, fire DOMRequest error.
139     if (!res) {
140       Services.DOMRequest.fireErrorAsync(aRequest, "INVALID_URL");
141       return false;
142     }
143 
144     return uri.spec;
145   },
146 
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     }
156 
157     Services.DOMRequest.fireErrorAsync(aRequest, "BACKGROUND_APP");
158     return false;
159   },
160 
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                                                          : [];
170 
171     let principal = this._window.document.nodePrincipal;
172 
173     return { app: {
174                     installOrigin: this._getOrigin(installURL),
175                     origin: this._getOrigin(aURL),
176                     manifestURL: aURL,
177                     receipts: receipts,
178                     categories: categories
179                   },
180 
181              from: installURL,
182              oid: this._id,
183              topId: this._topId,
184              requestID: requestID,
185              appId: principal.appId,
186              isBrowser: principal.isInIsolatedMozBrowserElement,
187              isPackage: isPackage
188            };
189   },
190 
191   // mozIDOMApplicationRegistry implementation
192 
193   install: function(aURL, aParams) {
194     let request = this.createRequest();
195 
196     let uri = this._validateURL(aURL, request);
197 
198     if (uri && this._ensureForeground(request)) {
199       this.addMessageListeners("Webapps:Install:Return:KO");
200       cpmm.sendAsyncMessage("Webapps:Install",
201                             this._prepareInstall(uri, request, aParams, false));
202     }
203 
204     return request;
205   },
206 
207   getSelf: function() {
208     let request = this.createRequest();
209     this.addMessageListeners("Webapps:GetSelf:Return:OK");
210     cpmm.sendAsyncMessage("Webapps:GetSelf", { origin: this._getOrigin(this._window.location.href),
211                                                appId: this._window.document.nodePrincipal.appId,
212                                                oid: this._id, topId: this._topId,
213                                                requestID: this.getRequestId(request) });
214     return request;
215   },
216 
217   checkInstalled: function(aManifestURL) {
218     let manifestURL = Services.io.newURI(aManifestURL, null, this._window.document.baseURIObject);
219 
220     let request = this.createRequest();
221 
222     try {
223       this._window.document.nodePrincipal.checkMayLoad(manifestURL, true,
224                                                        false);
225     } catch (ex) {
226       Services.DOMRequest.fireErrorAsync(request, "CROSS_ORIGIN_CHECK_NOT_ALLOWED");
227       return request;
228     }
229 
230     this.addMessageListeners("Webapps:CheckInstalled:Return:OK");
231     cpmm.sendAsyncMessage("Webapps:CheckInstalled", { origin: this._getOrigin(this._window.location.href),
232                                                       manifestURL: manifestURL.spec,
233                                                       oid: this._id, topId: this._topId,
234                                                       requestID: this.getRequestId(request) });
235     return request;
236   },
237 
238   getInstalled: function() {
239     let request = this.createRequest();
240     this.addMessageListeners("Webapps:GetInstalled:Return:OK");
241     cpmm.sendAsyncMessage("Webapps:GetInstalled", { origin: this._getOrigin(this._window.location.href),
242                                                     oid: this._id, topId: this._topId,
243                                                     requestID: this.getRequestId(request) });
244     return request;
245   },
246 
247   get mgmt() {
248     if (!this.hasMgmtPrivilege) {
249       return null;
250     }
251 
252     if (!this._mgmt) {
253       let mgmt = Cc["@mozilla.org/webapps/manager;1"]
254                    .createInstance(Ci.nsISupports);
255       mgmt.wrappedJSObject.init(this._window, this.hasFullMgmtPrivilege);
256       mgmt.wrappedJSObject._windowId = this._id;
257       mgmt.wrappedJSObject._topId = this._topId;
258       this._mgmt = mgmt.__DOM_IMPL__
259         ? mgmt.__DOM_IMPL__
260         : this._window.DOMApplicationsManager._create(this._window, mgmt.wrappedJSObject);
261     }
262     return this._mgmt;
263   },
264 
265   uninit: function() {
266     this._mgmt = null;
267     cpmm.sendAsyncMessage("Webapps:UnregisterForMessages",
268                           ["Webapps:Install:Return:OK",
269                            "Webapps:AdditionalLanguageChange"]);
270     this._window.removeEventListener("pagehide", this);
271   },
272 
273   installPackage: function(aURL, aParams) {
274     let request = this.createRequest();
275 
276     let uri = this._validateURL(aURL, request);
277 
278     if (uri && this._ensureForeground(request)) {
279       this.addMessageListeners("Webapps:Install:Return:KO");
280       cpmm.sendAsyncMessage("Webapps:InstallPackage",
281                             this._prepareInstall(uri, request, aParams, true));
282     }
283 
284     return request;
285   },
286 
287   _getCurrentAppManifestURL: function() {
288     let appId = this._window.document.nodePrincipal.appId;
289     if (appId === Ci.nsIScriptSecurityManager.NO_APP_ID) {
290       return null;
291     }
292 
293     return appsService.getManifestURLByLocalId(appId);
294   },
295 
296   getAdditionalLanguages: function() {
297     let manifestURL = AppsUtils.getAppManifestURLFromWindow(this._window);
298 
299     return new this._window.Promise((aResolve, aReject) => {
300       if (!manifestURL) {
301         aReject("NotInApp");
302       } else {
303         let langs = DOMApplicationRegistry.getAdditionalLanguages(manifestURL);
304         aResolve(Cu.cloneInto(langs, this._window));
305       }
306     });
307   },
308 
309   getLocalizationResource: function(aLanguage, aVersion, aPath, aType) {
310     let manifestURL = AppsUtils.getAppManifestURLFromWindow(this._window);
311 
312     if (!manifestURL) {
313       return new Promise((aResolve, aReject) => {
314         aReject("NotInApp");
315       });
316     }
317 
318     this.addMessageListeners(["Webapps:GetLocalizationResource:Return"]);
319     return this.createPromiseWithId((aResolverId) => {
320       cpmm.sendAsyncMessage("Webapps:GetLocalizationResource", {
321         manifestURL: manifestURL,
322         lang: aLanguage,
323         version: aVersion,
324         path: aPath,
325         dataType: aType,
326         oid: this._id,
327         topId: this._topId,
328         requestID: aResolverId
329       });
330     });
331   },
332 
333   // nsIDOMGlobalPropertyInitializer implementation
334   init: function(aWindow) {
335     const prefs = new Preferences();
336 
337     this._window = aWindow;
338     this._window.addEventListener("pagehide", this);
339 
340     this.initDOMRequestHelper(aWindow, ["Webapps:Install:Return:OK",
341                                         "Webapps:AdditionalLanguageChange"]);
342 
343     let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
344                            .getInterface(Ci.nsIDOMWindowUtils);
345     this._id = util.outerWindowID;
346 
347     let topUtil = this._window.top
348                               .QueryInterface(Ci.nsIInterfaceRequestor)
349                               .getInterface(Ci.nsIDOMWindowUtils);
350     this._topId = topUtil.outerWindowID;
351 
352     cpmm.sendAsyncMessage("Webapps:RegisterForMessages",
353                           { messages: ["Webapps:Install:Return:OK",
354                                        "Webapps:AdditionalLanguageChange"]});
355 
356     let principal = aWindow.document.nodePrincipal;
357     let appId = principal.appId;
358     let app = appId && appsService.getAppByLocalId(appId);
359 
360     let isCurrentHomescreen = app &&
361       app.manifestURL == prefs.get("dom.mozApps.homescreenURL") &&
362       app.appStatus != Ci.nsIPrincipal.APP_STATUS_NOT_INSTALLED;
363 
364     let hasWebappsPermission = Ci.nsIPermissionManager.ALLOW_ACTION ==
365       Services.perms.testExactPermissionFromPrincipal(
366         principal, "webapps-manage");
367 
368     let hasHomescreenPermission = Ci.nsIPermissionManager.ALLOW_ACTION ==
369       Services.perms.testExactPermissionFromPrincipal(
370         principal, "homescreen-webapps-manage");
371 
372     this.hasMgmtPrivilege = hasWebappsPermission ||
373          (isCurrentHomescreen && hasHomescreenPermission);
374     this.hasFullMgmtPrivilege = hasWebappsPermission;
375   },
376 
377   handleEvent(event) {
378     if (event.type == "pagehide" &&
379         event.target.defaultView == this._window) {
380       cpmm.sendAsyncMessage("Webapps:LocationChange", {
381         oid: this._id,
382       });
383     }
384   },
385 
386   classID: Components.ID("{fff440b3-fae2-45c1-bf03-3b5a2e432270}"),
387 
388   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
389                                          Ci.nsISupports,
390                                          Ci.nsIObserver,
391                                          Ci.nsIDOMGlobalPropertyInitializer])
392 }
393 
394 /**
395   * DOMApplication object
396   */
397 
398 function createApplicationObject(aWindow, aApp) {
399   let app = Cc["@mozilla.org/webapps/application;1"]
400               .createInstance(Ci.nsISupports);
401   app.wrappedJSObject.init(aWindow, aApp);
402   return app;
403 }
404 
405 function createContentApplicationObject(aWindow, aApp) {
406   return createApplicationObject(aWindow, aApp).wrappedJSObject
407          ._prepareForContent();
408 }
409 
410 function WebappsApplication() {
411   this.wrappedJSObject = this;
412 }
413 
414 WebappsApplication.prototype = {
415   __proto__: DOMRequestIpcHelper.prototype,
416 
417   init: function(aWindow, aApp) {
418     this._window = aWindow;
419 
420     let proxyHandler = DOMApplicationRegistry.addDOMApp(this,
421                                                         aApp.manifestURL,
422                                                         aApp.id);
423     this._proxy = new Proxy(this, proxyHandler);
424 
425     this.initDOMRequestHelper(aWindow);
426   },
427 
428   get _appStatus() {
429     return this._proxy.appStatus;
430   },
431 
432   get downloadAvailable() {
433     return this._proxy.downloadAvailable;
434   },
435 
436   get downloading() {
437     return this._proxy.downloading;
438   },
439 
440   get downloadSize() {
441     return this._proxy.downloadSize;
442   },
443 
444   get installOrigin() {
445     return this._proxy.installOrigin;
446   },
447 
448   get installState() {
449     return this._proxy.installState;
450   },
451 
452   get installTime() {
453     return this._proxy.installTime;
454   },
455 
456   get lastUpdateCheck() {
457     return this._proxy.lastUpdateCheck;
458   },
459 
460   get manifestURL() {
461     return this._proxy.manifestURL;
462   },
463 
464   get origin() {
465     return this._proxy.origin;
466   },
467 
468   get progress() {
469     return this._proxy.progress;
470   },
471 
472   get readyToApplyDownload() {
473     return this._proxy.readyToApplyDownload;
474   },
475 
476   get removable() {
477     return this._proxy.removable;
478   },
479 
480   get updateTime() {
481     return this._proxy.updateTime;
482   },
483 
484   get manifest() {
485     return WrappedManifestCache.get(this.manifestURL,
486                                     this._proxy.manifest,
487                                     this._window,
488                                     this.innerWindowID);
489   },
490 
491   get updateManifest() {
492     return this._proxy.updateManifest ?
493       Cu.cloneInto(this._proxy.updateManifest, this._window) : null;
494   },
495 
496   set onprogress(aCallback) {
497     this.__DOM_IMPL__.setEventHandler("onprogress", aCallback);
498   },
499 
500   get onprogress() {
501     return this.__DOM_IMPL__.getEventHandler("onprogress");
502   },
503 
504   set ondownloadsuccess(aCallback) {
505     this.__DOM_IMPL__.setEventHandler("ondownloadsuccess", aCallback);
506   },
507 
508   get ondownloadsuccess() {
509     return this.__DOM_IMPL__.getEventHandler("ondownloadsuccess");
510   },
511 
512   set ondownloaderror(aCallback) {
513     this.__DOM_IMPL__.setEventHandler("ondownloaderror", aCallback);
514   },
515 
516   get ondownloaderror() {
517     return this.__DOM_IMPL__.getEventHandler("ondownloaderror");
518   },
519 
520   set ondownloadavailable(aCallback) {
521     this.__DOM_IMPL__.setEventHandler("ondownloadavailable", aCallback);
522   },
523 
524   get ondownloadavailable() {
525     return this.__DOM_IMPL__.getEventHandler("ondownloadavailable");
526   },
527 
528   set ondownloadapplied(aCallback) {
529     this.__DOM_IMPL__.setEventHandler("ondownloadapplied", aCallback);
530   },
531 
532   get ondownloadapplied() {
533     return this.__DOM_IMPL__.getEventHandler("ondownloadapplied");
534   },
535 
536   get receipts() {
537     return this._proxy.receipts || [];
538   },
539 
540   get downloadError() {
541     // Only return DOMError when we have an error.
542     if (!this._proxy.downloadError) {
543       return null;
544     }
545     return new this._window.DOMError(this._proxy.downloadError);
546   },
547 
548   get enabled() {
549     let value = this._proxy.enabled;
550     return (value === undefined ? true : value);
551   },
552 
553   download: function() {
554     cpmm.sendAsyncMessage("Webapps:Download",
555                           { manifestURL: this.manifestURL });
556   },
557 
558   cancelDownload: function() {
559     cpmm.sendAsyncMessage("Webapps:CancelDownload",
560                           { manifestURL: this.manifestURL });
561   },
562 
563   checkForUpdate: function() {
564     let request = this.createRequest();
565 
566     cpmm.sendAsyncMessage("Webapps:CheckForUpdate",
567                           { manifestURL: this.manifestURL,
568                             oid: this._id,
569                             topId: this._topId,
570                             requestID: this.getRequestId(request) });
571     return request;
572   },
573 
574   launch: function(aStartPoint) {
575     let request = this.createRequest();
576     cpmm.sendAsyncMessage("Webapps:Launch", { origin: this.origin,
577                                               manifestURL: this.manifestURL,
578                                               startPoint: aStartPoint || "",
579                                               oid: this._id,
580                                               topId: this._topId,
581                                               timestamp: Date.now(),
582                                               requestID: this.getRequestId(request) });
583 
584     let manifestURL = AppsUtils.getAppManifestURLFromWindow(this._window);
585     if (manifestURL != this.manifestURL) {
586       Services.obs.notifyObservers(null, "will-launch-app", null);
587     }
588 
589     this.addMessageListeners(["Webapps:Launch:Return:OK",
590                               "Webapps:Launch:Return:KO"]);
591     return request;
592   },
593 
594   clearBrowserData: function() {
595     let request = this.createRequest();
596     let browserChild =
597       BrowserElementPromptService.getBrowserElementChildForWindow(this._window);
598     if (browserChild) {
599       this.addMessageListeners("Webapps:ClearBrowserData:Return");
600       browserChild.messageManager.sendAsyncMessage("Webapps:ClearBrowserData", {
601         manifestURL: this.manifestURL,
602         oid: this._id,
603         topId: this._topId,
604         requestID: this.getRequestId(request)
605       });
606     } else {
607       Services.DOMRequest.fireErrorAsync(request, "NO_CLEARABLE_BROWSER");
608     }
609     return request;
610   },
611 
612   connect: function(aKeyword, aRules) {
613     this.addMessageListeners(["Webapps:Connect:Return:OK",
614                               "Webapps:Connect:Return:KO"]);
615     return this.createPromiseWithId((aResolverId) => {
616       let from = this._window.location.origin + this._window.location.pathname;
617       cpmm.sendAsyncMessage("Webapps:Connect", {
618         keyword: aKeyword,
619         rules: aRules,
620         manifestURL: this.manifestURL,
621         pubPageURL: from,
622         outerWindowID: this._id,
623         topWindowID: this._topId,
624         requestID: aResolverId
625       });
626     });
627   },
628 
629   getConnections: function() {
630     this.addMessageListeners("Webapps:GetConnections:Return:OK");
631     return this.createPromiseWithId((aResolverId) => {
632       cpmm.sendAsyncMessage("Webapps:GetConnections", {
633         manifestURL: this.manifestURL,
634         outerWindowID: this._id,
635         topWindowID: this._topId,
636         requestID: aResolverId
637       });
638     });
639   },
640 
641   addReceipt: function(receipt) {
642     let request = this.createRequest();
643 
644     this.addMessageListeners(["Webapps:AddReceipt:Return:OK",
645                               "Webapps:AddReceipt:Return:KO"]);
646 
647     cpmm.sendAsyncMessage("Webapps:AddReceipt", { manifestURL: this.manifestURL,
648                                                   receipt: receipt,
649                                                   oid: this._id,
650                                                   topId: this._topId,
651                                                   requestID: this.getRequestId(request) });
652 
653     return request;
654   },
655 
656   removeReceipt: function(receipt) {
657     let request = this.createRequest();
658 
659     this.addMessageListeners(["Webapps:RemoveReceipt:Return:OK",
660                               "Webapps:RemoveReceipt:Return:KO"]);
661 
662     cpmm.sendAsyncMessage("Webapps:RemoveReceipt", { manifestURL: this.manifestURL,
663                                                      receipt: receipt,
664                                                      oid: this._id,
665                                                      topId: this._topId,
666                                                      requestID: this.getRequestId(request) });
667 
668     return request;
669   },
670 
671   replaceReceipt: function(oldReceipt, newReceipt) {
672     let request = this.createRequest();
673 
674     this.addMessageListeners(["Webapps:ReplaceReceipt:Return:OK",
675                               "Webapps:ReplaceReceipt:Return:KO"]);
676 
677     cpmm.sendAsyncMessage("Webapps:ReplaceReceipt", { manifestURL: this.manifestURL,
678                                                       newReceipt: newReceipt,
679                                                       oldReceipt: oldReceipt,
680                                                       oid: this._id,
681                                                       topId: this._topId,
682                                                       requestID: this.getRequestId(request) });
683 
684     return request;
685   },
686 
687   export: function() {
688     this.addMessageListeners(["Webapps:Export:Return"]);
689     return this.createPromiseWithId((aResolverId) => {
690       cpmm.sendAsyncMessage("Webapps:Export",
691         { manifestURL: this.manifestURL,
692           oid: this._id,
693           topId: this._topId,
694           requestID: aResolverId
695         });
696     });
697   },
698 
699   getLocalizedValue: function(aProperty, aLang, aEntryPoint) {
700     this.addMessageListeners(["Webapps:GetLocalizedValue:Return"]);
701     return this.createPromiseWithId((aResolverId) => {
702       cpmm.sendAsyncMessage("Webapps:GetLocalizedValue",
703         { manifestURL: this.manifestURL,
704           oid: this._id,
705           topId: this._topId,
706           property: aProperty,
707           lang: aLang,
708           entryPoint: aEntryPoint,
709           requestID: aResolverId
710         });
711     });
712   },
713 
714   _prepareForContent: function() {
715     if (this.__DOM_IMPL__) {
716       return this.__DOM_IMPL__;
717     }
718     return this._window.DOMApplication._create(this._window, this.wrappedJSObject);
719   },
720 
721   uninit: function() {
722     WrappedManifestCache.evict(this.manifestURL, this.innerWindowID);
723   },
724 
725   _fireEvent: function(aName) {
726     let obj = this._prepareForContent();
727     let event = new this._window.MozApplicationEvent(aName, {
728       application: obj
729     });
730     obj.dispatchEvent(event);
731   },
732 
733   _fireRequestResult: function(aMessage, aIsError) {
734     let req;
735     let msg = aMessage.data;
736     req = this.takeRequest(msg.requestID);
737     if (!req) {
738       return;
739     }
740 
741     aIsError ? Services.DOMRequest.fireError(req, msg.error)
742              : Services.DOMRequest.fireSuccess(req, msg.result);
743   },
744 
745   receiveMessage: function(aMessage) {
746     let msg = aMessage.json;
747     let req;
748     if (aMessage.name == "Webapps:Connect:Return:OK" ||
749         aMessage.name == "Webapps:Connect:Return:KO" ||
750         aMessage.name == "Webapps:GetConnections:Return:OK" ||
751         aMessage.name == "Webapps:Export:Return" ||
752         aMessage.name == "Webapps:GetLocalizedValue:Return") {
753       req = this.takePromiseResolver(msg.requestID);
754     } else {
755       req = this.takeRequest(msg.requestID);
756     }
757 
758     if (msg.oid !== this._id || !req) {
759       return;
760     }
761 
762     switch (aMessage.name) {
763       case "Webapps:Launch:Return:KO":
764         this.removeMessageListeners(["Webapps:Launch:Return:OK",
765                                      "Webapps:Launch:Return:KO"]);
766         Services.DOMRequest.fireError(req, msg.error);
767         break;
768       case "Webapps:Launch:Return:OK":
769         this.removeMessageListeners(["Webapps:Launch:Return:OK",
770                                      "Webapps:Launch:Return:KO"]);
771         Services.DOMRequest.fireSuccess(req, null);
772         break;
773       case "Webapps:ClearBrowserData:Return":
774         this.removeMessageListeners(aMessage.name);
775         Services.DOMRequest.fireSuccess(req, null);
776         break;
777       case "Webapps:Connect:Return:OK":
778         this.removeMessageListeners(["Webapps:Connect:Return:OK",
779                                      "Webapps:Connect:Return:KO"]);
780         let messagePorts = new this._window.Array();
781         msg.messagePortIDs.forEach((aPortID) => {
782           let port = new this._window.MozInterAppMessagePort(aPortID);
783           messagePorts.push(port);
784         });
785         req.resolve(messagePorts);
786         break;
787       case "Webapps:Connect:Return:KO":
788         this.removeMessageListeners(["Webapps:Connect:Return:OK",
789                                      "Webapps:Connect:Return:KO"]);
790         req.reject("No connections registered");
791         break;
792       case "Webapps:GetConnections:Return:OK":
793         this.removeMessageListeners(aMessage.name);
794         let connections = new this._window.Array();
795         msg.connections.forEach((aConnection) => {
796           let connection =
797             new this._window.MozInterAppConnection(aConnection.keyword,
798                                                    aConnection.pubAppManifestURL,
799                                                    aConnection.subAppManifestURL);
800           connections.push(connection);
801         });
802         req.resolve(connections);
803         break;
804       case "Webapps:AddReceipt:Return:OK":
805         this.removeMessageListeners(["Webapps:AddReceipt:Return:OK",
806                                      "Webapps:AddReceipt:Return:KO"]);
807         this.__DOM_IMPL__._clearCachedReceiptsValue();
808         this._proxy.receipts = msg.receipts;
809         Services.DOMRequest.fireSuccess(req, null);
810         break;
811       case "Webapps:AddReceipt:Return:KO":
812         this.removeMessageListeners(["Webapps:AddReceipt:Return:OK",
813                                      "Webapps:AddReceipt:Return:KO"]);
814         Services.DOMRequest.fireError(req, msg.error);
815         break;
816       case "Webapps:RemoveReceipt:Return:OK":
817         this.removeMessageListeners(["Webapps:RemoveReceipt:Return:OK",
818                                      "Webapps:RemoveReceipt:Return:KO"]);
819         this.__DOM_IMPL__._clearCachedReceiptsValue();
820         this._proxy.receipts = msg.receipts;
821         Services.DOMRequest.fireSuccess(req, null);
822         break;
823       case "Webapps:RemoveReceipt:Return:KO":
824         this.removeMessageListeners(["Webapps:RemoveReceipt:Return:OK",
825                                      "Webapps:RemoveReceipt:Return:KO"]);
826         Services.DOMRequest.fireError(req, msg.error);
827         break;
828       case "Webapps:ReplaceReceipt:Return:OK":
829         this.removeMessageListeners(["Webapps:ReplaceReceipt:Return:OK",
830                                      "Webapps:ReplaceReceipt:Return:KO"]);
831         this.__DOM_IMPL__._clearCachedReceiptsValue();
832         this._proxy.receipts = msg.receipts;
833         Services.DOMRequest.fireSuccess(req, null);
834         break;
835       case "Webapps:ReplaceReceipt:Return:KO":
836         this.removeMessageListeners(["Webapps:ReplaceReceipt:Return:OK",
837                                      "Webapps:ReplaceReceipt:Return:KO"]);
838         Services.DOMRequest.fireError(req, msg.error);
839         break;
840       case "Webapps:Export:Return":
841         this.removeMessageListeners(["Webapps:Export:Return"]);
842         if (msg.success) {
843           req.resolve(Cu.cloneInto(msg.blob, this._window));
844         } else {
845           req.reject(new this._window.DOMError(msg.error || ""));
846         }
847         break;
848       case "Webapps:GetLocalizedValue:Return":
849         this.removeMessageListeners(["Webapps:GetLocalizedValue:Return"]);
850         if (msg.success) {
851           req.resolve(msg.value);
852         } else {
853           req.reject(new this._window.DOMError(msg.error || ""));
854         }
855         break;
856     }
857   },
858 
859   classID: Components.ID("{723ed303-7757-4fb0-b261-4f78b1f6bd22}"),
860 
861   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
862                                          Ci.nsIObserver,
863                                          Ci.nsISupportsWeakReference])
864 }
865 
866 /**
867   * DOMApplicationsManager object
868   */
869 function WebappsApplicationMgmt() {
870   this.wrappedJSObject = this;
871 }
872 
873 WebappsApplicationMgmt.prototype = {
874   __proto__: DOMRequestIpcHelper.prototype,
875 
876   init: function(aWindow, aHasFullMgmtPrivilege) {
877     this._window = aWindow;
878 
879     this.initDOMRequestHelper(aWindow, ["Webapps:Uninstall:Return:OK",
880                                         "Webapps:Uninstall:Broadcast:Return:OK",
881                                         "Webapps:Uninstall:Return:KO",
882                                         "Webapps:Install:Return:OK",
883                                         "Webapps:GetIcon:Return",
884                                         "Webapps:Import:Return",
885                                         "Webapps:ExtractManifest:Return",
886                                         "Webapps:SetEnabled:Return"]);
887     cpmm.sendAsyncMessage("Webapps:RegisterForMessages",
888                           {
889                             messages: ["Webapps:Install:Return:OK",
890                                        "Webapps:Uninstall:Return:OK",
891                                        "Webapps:Uninstall:Broadcast:Return:OK",
892                                        "Webapps:SetEnabled:Return"]
893                           }
894                          );
895 
896     if (!aHasFullMgmtPrivilege) {
897       this.applyDownload = null;
898     }
899   },
900 
901   uninit: function() {
902     cpmm.sendAsyncMessage("Webapps:UnregisterForMessages",
903                           ["Webapps:Install:Return:OK",
904                            "Webapps:Uninstall:Return:OK",
905                            "Webapps:Uninstall:Broadcast:Return:OK",
906                            "Webapps:SetEnabled:Return"]);
907   },
908 
909   applyDownload: function(aApp) {
910     if (!aApp.readyToApplyDownload) {
911       return;
912     }
913 
914     let principal = this._window.document.nodePrincipal;
915 
916     cpmm.sendAsyncMessage("Webapps:ApplyDownload",
917                           { manifestURL: aApp.manifestURL },
918                           null, principal);
919   },
920 
921   uninstall: function(aApp) {
922     let request = this.createRequest();
923     let principal = this._window.document.nodePrincipal;
924 
925     cpmm.sendAsyncMessage("Webapps:Uninstall", {
926       origin: aApp.origin,
927       manifestURL: aApp.manifestURL,
928       oid: this._id,
929       topId: this._topId,
930       from: this._window.location.href,
931       windowId: this._windowId,
932       requestID: this.getRequestId(request)
933     }, null, principal);
934 
935     return request;
936   },
937 
938   getAll: function() {
939     let request = this.createRequest();
940     let window = this._window;
941     DOMApplicationRegistry.getAll((aApps) => {
942       Services.DOMRequest.fireSuccessAsync(request,
943                                            convertAppsArray(aApps, window));
944     });
945 
946     return request;
947   },
948 
949   getIcon: function(aApp, aIconID, aEntryPoint) {
950     return this.createPromiseWithId((aResolverId) => {
951       cpmm.sendAsyncMessage("Webapps:GetIcon", {
952         oid: this._id,
953         topId: this._topId,
954         manifestURL: aApp.manifestURL,
955         iconID: aIconID,
956         entryPoint: aEntryPoint,
957         requestID: aResolverId
958       });
959     });
960   },
961 
962   import: function(aBlob) {
963     let principal = this._window.document.nodePrincipal;
964     return this.createPromiseWithId((aResolverId) => {
965       cpmm.sendAsyncMessage("Webapps:Import",
966         { blob: aBlob,
967           oid: this._id,
968           topId: this._topId,
969           requestID: aResolverId
970         }, null, principal);
971     });
972   },
973 
974   extractManifest: function(aBlob) {
975     let principal = this._window.document.nodePrincipal;
976     return this.createPromiseWithId((aResolverId) => {
977       cpmm.sendAsyncMessage("Webapps:ExtractManifest",
978         { blob: aBlob,
979           oid: this._id,
980           topId: this._topId,
981           requestID: aResolverId
982         }, null, principal);
983     });
984   },
985 
986   setEnabled: function(aApp, aValue) {
987     let principal = this._window.document.nodePrincipal;
988 
989     cpmm.sendAsyncMessage("Webapps:SetEnabled",
990                           { manifestURL: aApp.manifestURL,
991                             enabled: aValue }, null, principal);
992   },
993 
994   get oninstall() {
995     return this.__DOM_IMPL__.getEventHandler("oninstall");
996   },
997 
998   get onuninstall() {
999     return this.__DOM_IMPL__.getEventHandler("onuninstall");
1000   },
1001 
1002   get onenabledstatechange() {
1003     return this.__DOM_IMPL__.getEventHandler("onenabledstatechange");
1004   },
1005 
1006   set oninstall(aCallback) {
1007     this.__DOM_IMPL__.setEventHandler("oninstall", aCallback);
1008   },
1009 
1010   set onuninstall(aCallback) {
1011     this.__DOM_IMPL__.setEventHandler("onuninstall", aCallback);
1012   },
1013 
1014   set onenabledstatechange(aCallback) {
1015     this.__DOM_IMPL__.setEventHandler("onenabledstatechange", aCallback);
1016   },
1017 
1018   receiveMessage: function(aMessage) {
1019     let msg = aMessage.data;
1020     let req;
1021 
1022     if (["Webapps:GetIcon:Return",
1023          "Webapps:Import:Return",
1024          "Webapps:ExtractManifest:Return"]
1025          .indexOf(aMessage.name) != -1) {
1026       req = this.takePromiseResolver(msg.requestID);
1027     } else {
1028       req = this.getRequest(msg.requestID);
1029     }
1030 
1031     // We want Webapps:Install:Return:OK, Webapps:Uninstall:Broadcast:Return:OK
1032     // and Webapps:SetEnabled:Return
1033     // to be broadcasted to all instances of mozApps.mgmt.
1034     if (!((msg.oid == this._id && req) ||
1035           aMessage.name == "Webapps:Install:Return:OK" ||
1036           aMessage.name == "Webapps:Uninstall:Broadcast:Return:OK" ||
1037           aMessage.name == "Webapps:SetEnabled:Return")) {
1038       return;
1039     }
1040 
1041     switch (aMessage.name) {
1042       case "Webapps:Install:Return:OK":
1043         {
1044           let app = createContentApplicationObject(this._window, msg.app);
1045           let event =
1046             new this._window.MozApplicationEvent("install", { application: app });
1047           this.__DOM_IMPL__.dispatchEvent(event);
1048         }
1049         break;
1050       case "Webapps:Uninstall:Broadcast:Return:OK":
1051         {
1052           let app = createContentApplicationObject(this._window, msg);
1053           let event =
1054             new this._window.MozApplicationEvent("uninstall", { application : app });
1055           this.__DOM_IMPL__.dispatchEvent(event);
1056         }
1057         break;
1058       case "Webapps:Uninstall:Return:OK":
1059         Services.DOMRequest.fireSuccess(req, msg.manifestURL);
1060         break;
1061       case "Webapps:Uninstall:Return:KO":
1062         Services.DOMRequest.fireError(req, "NOT_INSTALLED");
1063         break;
1064       case "Webapps:Import:Return":
1065         if (msg.success) {
1066           req.resolve(createContentApplicationObject(this._window, msg.app));
1067         } else {
1068           req.reject(new this._window.DOMError(msg.error || ""));
1069         }
1070         break;
1071       case "Webapps:ExtractManifest:Return":
1072         if (msg.success) {
1073           req.resolve(Cu.cloneInto(msg.manifest, this._window));
1074         } else {
1075           req.reject(new this._window.DOMError(msg.error || ""));
1076         }
1077         break;
1078       case "Webapps:SetEnabled:Return":
1079         {
1080           let app = createContentApplicationObject(this._window, msg);
1081           let event =
1082             new this._window.MozApplicationEvent("enabledstatechange", { application : app });
1083           this.__DOM_IMPL__.dispatchEvent(event);
1084         }
1085         break;
1086       case "Webapps:GetIcon:Return":
1087         if (msg.blob) {
1088           req.resolve(Cu.cloneInto(msg.blob, this._window));
1089         } else if (msg.error && msg.error == "NETWORK_ERROR"
1090                              && !this._window.navigator.onLine) {
1091           req.reject(new this._window.DOMError("NETWORK_OFFLINE"));
1092         } else {
1093           req.reject(new this._window.DOMError(msg.error || ""));
1094         }
1095         break;
1096     }
1097 
1098     if (aMessage.name !== "Webapps:Uninstall:Broadcast:Return:OK") {
1099       this.removeRequest(msg.requestID);
1100     }
1101   },
1102 
1103   classID: Components.ID("{8c1bca96-266f-493a-8d57-ec7a95098c15}"),
1104 
1105   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
1106                                          Ci.nsIObserver,
1107                                          Ci.nsISupportsWeakReference])
1108 }
1109 
1110 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WebappsRegistry,
1111                                                      WebappsApplicationMgmt,
1112                                                      WebappsApplication]);
1113 
view http://hg.mozilla.org/mozilla-central/rev/ /dom/apps/Webapps.js