|
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