Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 let Cu = Components.utils;
8 let Cc = Components.classes;
9 let Ci = Components.interfaces;
10 let CC = Components.Constructor;
12 Cu.import("resource://gre/modules/osfile.jsm");
13 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
15 let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
17 let promise;
19 function debug(aMsg) {
20 /*
21 Cc["@mozilla.org/consoleservice;1"]
22 .getService(Ci.nsIConsoleService)
23 .logStringMessage("--*-- WebappsActor : " + aMsg);
24 */
25 }
27 function PackageUploadActor(aPath, aFile) {
28 this._path = aPath;
29 this._file = aFile;
30 this.size = 0;
31 }
33 PackageUploadActor.prototype = {
34 actorPrefix: "packageUploadActor",
36 /**
37 * This method isn't exposed to the client.
38 * It is meant to be called by server code, in order to get
39 * access to the temporary file out of the actor ID.
40 */
41 getFilePath: function () {
42 return this._path;
43 },
45 /**
46 * This method allows you to upload a piece of file.
47 * It expects a chunk argument that is the a string to write to the file.
48 */
49 chunk: function (aRequest) {
50 let chunk = aRequest.chunk;
51 if (!chunk || chunk.length <= 0) {
52 return {error: "parameterError",
53 message: "Missing or invalid chunk argument"};
54 }
55 // Translate the string used to transfer the chunk over JSON
56 // back to a typed array
57 let data = new Uint8Array(chunk.length);
58 for (let i = 0, l = chunk.length; i < l ; i++) {
59 data[i] = chunk.charCodeAt(i);
60 }
61 return this._file.write(data)
62 .then((written) => {
63 this.size += written;
64 return {
65 written: written,
66 size: this.size
67 };
68 });
69 },
71 /**
72 * This method needs to be called, when you are done uploading
73 * chunks, before trying to access/use the temporary file.
74 * Otherwise, the file may be partially written
75 * and also be locked.
76 */
77 done: function (aRequest) {
78 this._file.close();
79 return {};
80 },
82 /**
83 * This method allows you to delete the temporary file,
84 * when you are done using it.
85 */
86 remove: function (aRequest) {
87 this._cleanupFile();
88 return {};
89 },
91 _cleanupFile: function () {
92 try {
93 this._file.close();
94 } catch(e) {}
95 try {
96 OS.File.remove(this._path);
97 } catch(e) {}
98 }
99 };
101 /**
102 * The request types this actor can handle.
103 */
104 PackageUploadActor.prototype.requestTypes = {
105 "chunk": PackageUploadActor.prototype.chunk,
106 "done": PackageUploadActor.prototype.done,
107 "remove": PackageUploadActor.prototype.remove
108 };
110 /**
111 * Creates a WebappsActor. WebappsActor provides remote access to
112 * install apps.
113 */
114 function WebappsActor(aConnection) {
115 debug("init");
116 // Load actor dependencies lazily as this actor require extra environnement
117 // preparation to work (like have a profile setup in xpcshell tests)
119 Cu.import("resource://gre/modules/Webapps.jsm");
120 Cu.import("resource://gre/modules/AppsUtils.jsm");
121 Cu.import("resource://gre/modules/FileUtils.jsm");
123 // Keep reference of already created app actors.
124 // key: app frame message manager, value: ContentActor's grip() value
125 this._appActorsMap = new Map();
127 this.conn = aConnection;
128 this._uploads = [];
129 this._actorPool = new ActorPool(this.conn);
130 this.conn.addActorPool(this._actorPool);
131 }
133 WebappsActor.prototype = {
134 actorPrefix: "webapps",
136 disconnect: function () {
137 // When we stop using this actor, we should ensure removing all files.
138 for (let upload of this._uploads) {
139 upload.remove();
140 }
141 this._uploads = null;
143 this.conn.removeActorPool(this._actorPool);
144 this._actorPool = null;
145 this.conn = null;
146 },
148 _registerApp: function wa_actorRegisterApp(aDeferred, aApp, aId, aDir) {
149 debug("registerApp");
150 let reg = DOMApplicationRegistry;
151 let self = this;
153 // Clean up the deprecated manifest cache if needed.
154 if (aId in reg._manifestCache) {
155 delete reg._manifestCache[aId];
156 }
158 aApp.installTime = Date.now();
159 aApp.installState = "installed";
160 aApp.removable = true;
161 aApp.id = aId;
162 aApp.basePath = reg.getWebAppsBasePath();
163 aApp.localId = (aId in reg.webapps) ? reg.webapps[aId].localId
164 : reg._nextLocalId();
166 reg.webapps[aId] = aApp;
167 reg.updatePermissionsForApp(aId);
169 reg._readManifests([{ id: aId }]).then((aResult) => {
170 let manifest = aResult[0].manifest;
171 aApp.name = manifest.name;
172 reg.updateAppHandlers(null, manifest, aApp);
174 reg._saveApps().then(() => {
175 aApp.manifest = manifest;
177 // Needed to evict manifest cache on content side
178 // (has to be dispatched first, otherwise other messages like
179 // Install:Return:OK are going to use old manifest version)
180 reg.broadcastMessage("Webapps:UpdateState", {
181 app: aApp,
182 manifest: manifest,
183 manifestURL: aApp.manifestURL
184 });
185 reg.broadcastMessage("Webapps:FireEvent", {
186 eventType: ["downloadsuccess", "downloadapplied"],
187 manifestURL: aApp.manifestURL
188 });
189 reg.broadcastMessage("Webapps:AddApp", { id: aId, app: aApp });
190 reg.broadcastMessage("Webapps:Install:Return:OK", {
191 app: aApp,
192 oid: "foo",
193 requestID: "bar"
194 });
196 Services.obs.notifyObservers(null, "webapps-installed",
197 JSON.stringify({ manifestURL: aApp.manifestURL }));
199 delete aApp.manifest;
200 aDeferred.resolve({ appId: aId, path: aDir.path });
202 // We can't have appcache for packaged apps.
203 if (!aApp.origin.startsWith("app://")) {
204 reg.startOfflineCacheDownload(new ManifestHelper(manifest, aApp.origin));
205 }
206 });
207 // Cleanup by removing the temporary directory.
208 if (aDir.exists())
209 aDir.remove(true);
210 });
211 },
213 _sendError: function wa_actorSendError(aDeferred, aMsg, aId) {
214 debug("Sending error: " + aMsg);
215 aDeferred.resolve({
216 error: "installationFailed",
217 message: aMsg,
218 appId: aId
219 });
220 },
222 _getAppType: function wa_actorGetAppType(aType) {
223 let type = Ci.nsIPrincipal.APP_STATUS_INSTALLED;
225 if (aType) {
226 type = aType == "privileged" ? Ci.nsIPrincipal.APP_STATUS_PRIVILEGED
227 : aType == "certified" ? Ci.nsIPrincipal.APP_STATUS_CERTIFIED
228 : Ci.nsIPrincipal.APP_STATUS_INSTALLED;
229 }
231 return type;
232 },
234 uploadPackage: function () {
235 debug("uploadPackage\n");
236 let tmpDir = FileUtils.getDir("TmpD", ["file-upload"], true, false);
237 if (!tmpDir.exists() || !tmpDir.isDirectory()) {
238 return {error: "fileAccessError",
239 message: "Unable to create temporary folder"};
240 }
241 let tmpFile = tmpDir;
242 tmpFile.append("package.zip");
243 tmpFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0666", 8));
244 if (!tmpFile.exists() || !tmpDir.isFile()) {
245 return {error: "fileAccessError",
246 message: "Unable to create temporary file"};
247 }
249 return OS.File.open(tmpFile.path, { write: true, truncate: true })
250 .then((file) => {
251 let actor = new PackageUploadActor(tmpFile.path, file);
252 this._actorPool.addActor(actor);
253 this._uploads.push(actor);
254 return { actor: actor.actorID };
255 });
256 },
258 installHostedApp: function wa_actorInstallHosted(aDir, aId, aReceipts,
259 aManifest, aMetadata) {
260 debug("installHostedApp");
261 let self = this;
262 let deferred = promise.defer();
264 function readManifest() {
265 if (aManifest) {
266 return promise.resolve(aManifest);
267 } else {
268 let manFile = OS.Path.join(aDir.path, "manifest.webapp");
269 return AppsUtils.loadJSONAsync(manFile);
270 }
271 }
272 function checkSideloading(aManifest) {
273 return self._getAppType(aManifest.type);
274 }
275 function writeManifest(aAppType) {
276 // Move manifest.webapp to the destination directory.
277 // The destination directory for this app.
278 let installDir = DOMApplicationRegistry._getAppDir(aId);
279 if (aManifest) {
280 let manFile = OS.Path.join(installDir.path, "manifest.webapp");
281 return DOMApplicationRegistry._writeFile(manFile, JSON.stringify(aManifest)).then(() => {
282 return aAppType;
283 });
284 } else {
285 let manFile = aDir.clone();
286 manFile.append("manifest.webapp");
287 manFile.moveTo(installDir, "manifest.webapp");
288 }
289 return null;
290 }
291 function readMetadata(aAppType) {
292 if (aMetadata) {
293 return { metadata: aMetadata, appType: aAppType };
294 }
295 // Read the origin and manifest url from metadata.json
296 let metaFile = OS.Path.join(aDir.path, "metadata.json");
297 return AppsUtils.loadJSONAsync(metaFile).then((aMetadata) => {
298 if (!aMetadata) {
299 throw("Error parsing metadata.json.");
300 }
301 if (!aMetadata.origin) {
302 throw("Missing 'origin' property in metadata.json");
303 }
304 return { metadata: aMetadata, appType: aAppType };
305 });
306 }
307 let runnable = {
308 run: function run() {
309 try {
310 readManifest().
311 then(writeManifest).
312 then(checkSideloading).
313 then(readMetadata).
314 then(function ({ metadata, appType }) {
315 let origin = metadata.origin;
316 let manifestURL = metadata.manifestURL ||
317 origin + "/manifest.webapp";
318 // Create a fake app object with the minimum set of properties we need.
319 let app = {
320 origin: origin,
321 installOrigin: metadata.installOrigin || origin,
322 manifestURL: manifestURL,
323 appStatus: appType,
324 receipts: aReceipts,
325 };
327 self._registerApp(deferred, app, aId, aDir);
328 }, function (error) {
329 self._sendError(deferred, error, aId);
330 });
331 } catch(e) {
332 // If anything goes wrong, just send it back.
333 self._sendError(deferred, e.toString(), aId);
334 }
335 }
336 }
338 Services.tm.currentThread.dispatch(runnable,
339 Ci.nsIThread.DISPATCH_NORMAL);
340 return deferred.promise;
341 },
343 installPackagedApp: function wa_actorInstallPackaged(aDir, aId, aReceipts) {
344 debug("installPackagedApp");
345 let self = this;
346 let deferred = promise.defer();
348 let runnable = {
349 run: function run() {
350 try {
351 // Open the app zip package
352 let zipFile = aDir.clone();
353 zipFile.append("application.zip");
354 let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"]
355 .createInstance(Ci.nsIZipReader);
356 zipReader.open(zipFile);
358 // Read app manifest `manifest.webapp` from `application.zip`
359 let istream = zipReader.getInputStream("manifest.webapp");
360 let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
361 .createInstance(Ci.nsIScriptableUnicodeConverter);
362 converter.charset = "UTF-8";
363 let jsonString = converter.ConvertToUnicode(
364 NetUtil.readInputStreamToString(istream, istream.available())
365 );
367 let manifest;
368 try {
369 manifest = JSON.parse(jsonString);
370 } catch(e) {
371 self._sendError(deferred, "Error Parsing manifest.webapp: " + e, aId);
372 }
374 let appType = self._getAppType(manifest.type);
376 // Privileged and certified packaged apps can setup a custom origin
377 // via `origin` manifest property
378 let id = aId;
379 if (appType >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED &&
380 manifest.origin !== undefined) {
381 let uri;
382 try {
383 uri = Services.io.newURI(manifest.origin, null, null);
384 } catch(e) {
385 self._sendError(deferred, "Invalid origin in webapp's manifest", aId);
386 }
388 if (uri.scheme != "app") {
389 self._sendError(deferred, "Invalid origin in webapp's manifest", aId);
390 }
391 id = uri.prePath.substring(6);
392 }
394 // Only after security checks are made and after final app id is computed
395 // we can move application.zip to the destination directory, and
396 // extract manifest.webapp there.
397 let installDir = DOMApplicationRegistry._getAppDir(id);
398 let manFile = installDir.clone();
399 manFile.append("manifest.webapp");
400 zipReader.extract("manifest.webapp", manFile);
401 zipReader.close();
402 zipFile.moveTo(installDir, "application.zip");
404 let origin = "app://" + id;
405 let manifestURL = origin + "/manifest.webapp";
407 // Refresh application.zip content (e.g. reinstall app), as done here:
408 // http://hg.mozilla.org/mozilla-central/annotate/aaefec5d34f8/dom/apps/src/Webapps.jsm#l1125
409 // Do it in parent process for the simulator
410 let jar = installDir.clone();
411 jar.append("application.zip");
412 Services.obs.notifyObservers(jar, "flush-cache-entry", null);
414 // And then in app content process
415 // This function will be evaluated in the scope of the content process
416 // frame script. That will flush the jar cache for this app and allow
417 // loading fresh updated resources if we reload its document.
418 let FlushFrameScript = function (path) {
419 let jar = Components.classes["@mozilla.org/file/local;1"]
420 .createInstance(Components.interfaces.nsILocalFile);
421 jar.initWithPath(path);
422 let obs = Components.classes["@mozilla.org/observer-service;1"]
423 .getService(Components.interfaces.nsIObserverService);
424 obs.notifyObservers(jar, "flush-cache-entry", null);
425 };
426 for each (let frame in self._appFrames()) {
427 if (frame.getAttribute("mozapp") == manifestURL) {
428 let mm = frame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
429 mm.loadFrameScript("data:," +
430 encodeURIComponent("(" + FlushFrameScript.toString() + ")" +
431 "('" + jar.path + "')"), false);
432 }
433 }
435 // Create a fake app object with the minimum set of properties we need.
436 let app = {
437 origin: origin,
438 installOrigin: origin,
439 manifestURL: manifestURL,
440 appStatus: appType,
441 receipts: aReceipts,
442 }
444 self._registerApp(deferred, app, id, aDir);
445 } catch(e) {
446 // If anything goes wrong, just send it back.
447 self._sendError(deferred, e.toString(), aId);
448 }
449 }
450 }
452 Services.tm.currentThread.dispatch(runnable,
453 Ci.nsIThread.DISPATCH_NORMAL);
454 return deferred.promise;
455 },
457 /**
458 * @param appId : The id of the app we want to install. We will look for
459 * the files for the app in $TMP/b2g/$appId :
460 * For packaged apps: application.zip
461 * For hosted apps: metadata.json and manifest.webapp
462 */
463 install: function wa_actorInstall(aRequest) {
464 debug("install");
466 let appId = aRequest.appId;
467 let reg = DOMApplicationRegistry;
468 if (!appId) {
469 appId = reg.makeAppId();
470 }
472 // Check that we are not overriding a preinstalled application.
473 if (appId in reg.webapps && reg.webapps[appId].removable === false) {
474 return { error: "badParameterType",
475 message: "The application " + appId + " can't be overriden."
476 }
477 }
479 let appDir = FileUtils.getDir("TmpD", ["b2g", appId], false, false);
481 if (aRequest.upload) {
482 // Ensure creating the directory (recursively)
483 appDir = FileUtils.getDir("TmpD", ["b2g", appId], true, false);
484 let actor = this.conn.getActor(aRequest.upload);
485 if (!actor) {
486 return { error: "badParameter",
487 message: "Unable to find upload actor '" + aRequest.upload
488 + "'" };
489 }
490 let appFile = FileUtils.File(actor.getFilePath());
491 if (!appFile.exists()) {
492 return { error: "badParameter",
493 message: "The uploaded file doesn't exist on device" };
494 }
495 appFile.moveTo(appDir, "application.zip");
496 } else if ((!appDir || !appDir.exists()) &&
497 !aRequest.manifest && !aRequest.metadata) {
498 return { error: "badParameterType",
499 message: "missing directory " + appDir.path
500 };
501 }
503 let testFile = appDir.clone();
504 testFile.append("application.zip");
506 let receipts = (aRequest.receipts && Array.isArray(aRequest.receipts))
507 ? aRequest.receipts
508 : [];
510 if (testFile.exists()) {
511 return this.installPackagedApp(appDir, appId, receipts);
512 }
514 let manifest, metadata;
515 let missing =
516 ["manifest.webapp", "metadata.json"]
517 .some(function(aName) {
518 testFile = appDir.clone();
519 testFile.append(aName);
520 return !testFile.exists();
521 });
522 if (missing) {
523 if (aRequest.manifest && aRequest.metadata &&
524 aRequest.metadata.origin) {
525 manifest = aRequest.manifest;
526 metadata = aRequest.metadata;
527 } else {
528 try {
529 appDir.remove(true);
530 } catch(e) {}
531 return { error: "badParameterType",
532 message: "hosted app file and manifest/metadata fields " +
533 "are missing"
534 };
535 }
536 }
538 return this.installHostedApp(appDir, appId, receipts, manifest, metadata);
539 },
541 getAll: function wa_actorGetAll(aRequest) {
542 debug("getAll");
544 let deferred = promise.defer();
545 let reg = DOMApplicationRegistry;
546 reg.getAll(apps => {
547 deferred.resolve({ apps: this._filterAllowedApps(apps) });
548 });
550 return deferred.promise;
551 },
553 getApp: function wa_actorGetApp(aRequest) {
554 debug("getApp");
556 let manifestURL = aRequest.manifestURL;
557 if (!manifestURL) {
558 return { error: "missingParameter",
559 message: "missing parameter manifestURL" };
560 }
562 let reg = DOMApplicationRegistry;
563 let app = reg.getAppByManifestURL(manifestURL);
564 if (!app) {
565 return { error: "appNotFound" };
566 }
568 return this._isAppAllowedForURL(app.manifestURL).then(allowed => {
569 if (!allowed) {
570 return { error: "forbidden" };
571 }
572 return reg.getManifestFor(manifestURL).then(function (manifest) {
573 app.manifest = manifest;
574 return { app: app };
575 });
576 });
577 },
579 _areCertifiedAppsAllowed: function wa__areCertifiedAppsAllowed() {
580 let pref = "devtools.debugger.forbid-certified-apps";
581 return !Services.prefs.getBoolPref(pref);
582 },
584 _isAppAllowedForManifest: function wa__isAppAllowedForManifest(aManifest) {
585 if (this._areCertifiedAppsAllowed()) {
586 return true;
587 }
588 let type = this._getAppType(aManifest.type);
589 return type !== Ci.nsIPrincipal.APP_STATUS_CERTIFIED;
590 },
592 _filterAllowedApps: function wa__filterAllowedApps(aApps) {
593 return aApps.filter(app => this._isAppAllowedForManifest(app.manifest));
594 },
596 _isAppAllowedForURL: function wa__isAppAllowedForURL(aManifestURL) {
597 return this._findManifestByURL(aManifestURL).then(manifest => {
598 return this._isAppAllowedForManifest(manifest);
599 });
600 },
602 uninstall: function wa_actorUninstall(aRequest) {
603 debug("uninstall");
605 let manifestURL = aRequest.manifestURL;
606 if (!manifestURL) {
607 return { error: "missingParameter",
608 message: "missing parameter manifestURL" };
609 }
611 let deferred = promise.defer();
612 let reg = DOMApplicationRegistry;
613 reg.uninstall(
614 manifestURL,
615 function onsuccess() {
616 deferred.resolve({});
617 },
618 function onfailure(reason) {
619 deferred.resolve({ error: reason });
620 }
621 );
623 return deferred.promise;
624 },
626 _findManifestByURL: function wa__findManifestByURL(aManifestURL) {
627 let deferred = promise.defer();
629 let reg = DOMApplicationRegistry;
630 let id = reg._appIdForManifestURL(aManifestURL);
632 reg._readManifests([{ id: id }]).then((aResults) => {
633 deferred.resolve(aResults[0].manifest);
634 });
636 return deferred.promise;
637 },
639 getIconAsDataURL: function (aRequest) {
640 debug("getIconAsDataURL");
642 let manifestURL = aRequest.manifestURL;
643 if (!manifestURL) {
644 return { error: "missingParameter",
645 message: "missing parameter manifestURL" };
646 }
648 let reg = DOMApplicationRegistry;
649 let app = reg.getAppByManifestURL(manifestURL);
650 if (!app) {
651 return { error: "wrongParameter",
652 message: "No application for " + manifestURL };
653 }
655 let deferred = promise.defer();
657 this._findManifestByURL(manifestURL).then(jsonManifest => {
658 let manifest = new ManifestHelper(jsonManifest, app.origin);
659 let iconURL = manifest.iconURLForSize(aRequest.size || 128);
660 if (!iconURL) {
661 deferred.resolve({
662 error: "noIcon",
663 message: "This app has no icon"
664 });
665 return;
666 }
668 // Download the URL as a blob
669 // bug 899177: there is a bug with xhr and app:// and jar:// uris
670 // that ends up forcing the content type to application/xml.
671 let req = Cc['@mozilla.org/xmlextras/xmlhttprequest;1']
672 .createInstance(Ci.nsIXMLHttpRequest);
673 req.open("GET", iconURL, false);
674 req.responseType = "blob";
676 try {
677 req.send(null);
678 } catch(e) {
679 deferred.resolve({
680 error: "noIcon",
681 message: "The icon file '" + iconURL + "' doesn't exist"
682 });
683 return;
684 }
686 // Convert the blog to a base64 encoded data URI
687 let reader = Cc["@mozilla.org/files/filereader;1"]
688 .createInstance(Ci.nsIDOMFileReader);
689 reader.onload = function () {
690 deferred.resolve({
691 url: reader.result
692 });
693 };
694 reader.onerror = function () {
695 deferred.resolve({
696 error: reader.error.name,
697 message: String(reader.error)
698 });
699 };
700 reader.readAsDataURL(req.response);
701 });
703 return deferred.promise;
704 },
706 launch: function wa_actorLaunch(aRequest) {
707 debug("launch");
709 let manifestURL = aRequest.manifestURL;
710 if (!manifestURL) {
711 return { error: "missingParameter",
712 message: "missing parameter manifestURL" };
713 }
715 let deferred = promise.defer();
717 DOMApplicationRegistry.launch(
718 aRequest.manifestURL,
719 aRequest.startPoint || "",
720 Date.now(),
721 function onsuccess() {
722 deferred.resolve({});
723 },
724 function onfailure(reason) {
725 deferred.resolve({ error: reason });
726 });
728 return deferred.promise;
729 },
731 close: function wa_actorLaunch(aRequest) {
732 debug("close");
734 let manifestURL = aRequest.manifestURL;
735 if (!manifestURL) {
736 return { error: "missingParameter",
737 message: "missing parameter manifestURL" };
738 }
740 let reg = DOMApplicationRegistry;
741 let app = reg.getAppByManifestURL(manifestURL);
742 if (!app) {
743 return { error: "missingParameter",
744 message: "No application for " + manifestURL };
745 }
747 reg.close(app);
749 return {};
750 },
752 _appFrames: function () {
753 // For now, we only support app frames on b2g
754 if (Services.appinfo.ID != "{3c2e2abc-06d4-11e1-ac3b-374f68613e61}") {
755 return;
756 }
757 // Register the system app
758 let chromeWindow = Services.wm.getMostRecentWindow('navigator:browser');
759 let systemAppFrame = chromeWindow.shell.contentBrowser;
760 yield systemAppFrame;
762 // Register apps hosted in the system app. i.e. the homescreen, all regular
763 // apps and the keyboard.
764 // Bookmark apps and other system app internal frames like captive portal
765 // are also hosted in system app, but they are not using mozapp attribute.
766 let frames = systemAppFrame.contentDocument.querySelectorAll("iframe[mozapp]");
767 for (let i = 0; i < frames.length; i++) {
768 yield frames[i];
769 }
770 },
772 listRunningApps: function (aRequest) {
773 debug("listRunningApps\n");
775 let appPromises = [];
776 let apps = [];
778 for each (let frame in this._appFrames()) {
779 let manifestURL = frame.getAttribute("mozapp");
781 appPromises.push(this._isAppAllowedForURL(manifestURL).then(allowed => {
782 if (allowed) {
783 apps.push(manifestURL);
784 }
785 }));
786 }
788 return promise.all(appPromises).then(() => {
789 return { apps: apps };
790 });
791 },
793 getAppActor: function ({ manifestURL }) {
794 debug("getAppActor\n");
796 let appFrame = null;
797 for each (let frame in this._appFrames()) {
798 if (frame.getAttribute("mozapp") == manifestURL) {
799 appFrame = frame;
800 break;
801 }
802 }
804 let notFoundError = {
805 error: "appNotFound",
806 message: "Unable to find any opened app whose manifest " +
807 "is '" + manifestURL + "'"
808 };
810 if (!appFrame) {
811 return notFoundError;
812 }
814 return this._isAppAllowedForURL(manifestURL).then(allowed => {
815 if (!allowed) {
816 return notFoundError;
817 }
819 // Only create a new actor, if we haven't already
820 // instanciated one for this connection.
821 let map = this._appActorsMap;
822 let mm = appFrame.QueryInterface(Ci.nsIFrameLoaderOwner)
823 .frameLoader
824 .messageManager;
825 let actor = map.get(mm);
826 if (!actor) {
827 let onConnect = actor => {
828 map.set(mm, actor);
829 return { actor: actor };
830 };
831 let onDisconnect = mm => {
832 map.delete(mm);
833 };
834 return DebuggerServer.connectToChild(this.conn, appFrame, onDisconnect)
835 .then(onConnect);
836 }
838 return { actor: actor };
839 });
840 },
842 watchApps: function () {
843 this._openedApps = new Set();
844 // For now, app open/close events are only implement on b2g
845 if (Services.appinfo.ID == "{3c2e2abc-06d4-11e1-ac3b-374f68613e61}") {
846 let chromeWindow = Services.wm.getMostRecentWindow('navigator:browser');
847 let systemAppFrame = chromeWindow.getContentWindow();
848 systemAppFrame.addEventListener("appwillopen", this);
849 systemAppFrame.addEventListener("appterminated", this);
850 }
851 Services.obs.addObserver(this, "webapps-installed", false);
852 Services.obs.addObserver(this, "webapps-uninstall", false);
854 return {};
855 },
857 unwatchApps: function () {
858 this._openedApps = null;
859 if (Services.appinfo.ID == "{3c2e2abc-06d4-11e1-ac3b-374f68613e61}") {
860 let chromeWindow = Services.wm.getMostRecentWindow('navigator:browser');
861 let systemAppFrame = chromeWindow.getContentWindow();
862 systemAppFrame.removeEventListener("appwillopen", this);
863 systemAppFrame.removeEventListener("appterminated", this);
864 }
865 Services.obs.removeObserver(this, "webapps-installed", false);
866 Services.obs.removeObserver(this, "webapps-uninstall", false);
868 return {};
869 },
871 handleEvent: function (event) {
872 let manifestURL;
873 switch(event.type) {
874 case "appwillopen":
875 manifestURL = event.detail.manifestURL;
877 // Ignore the event if we already received an appwillopen for this app
878 // (appwillopen is also fired when the app has been moved to background
879 // and get back to foreground)
880 if (this._openedApps.has(manifestURL)) {
881 return;
882 }
883 this._openedApps.add(manifestURL);
885 this._isAppAllowedForURL(manifestURL).then(allowed => {
886 if (allowed) {
887 this.conn.send({ from: this.actorID,
888 type: "appOpen",
889 manifestURL: manifestURL
890 });
891 }
892 });
894 break;
896 case "appterminated":
897 manifestURL = event.detail.manifestURL;
898 this._openedApps.delete(manifestURL);
900 this._isAppAllowedForURL(manifestURL).then(allowed => {
901 if (allowed) {
902 this.conn.send({ from: this.actorID,
903 type: "appClose",
904 manifestURL: manifestURL
905 });
906 }
907 });
909 break;
910 }
911 },
913 observe: function (subject, topic, data) {
914 let app = JSON.parse(data);
915 if (topic == "webapps-installed") {
916 this.conn.send({ from: this.actorID,
917 type: "appInstall",
918 manifestURL: app.manifestURL
919 });
920 } else if (topic == "webapps-uninstall") {
921 this.conn.send({ from: this.actorID,
922 type: "appUninstall",
923 manifestURL: app.manifestURL
924 });
925 }
926 }
927 };
929 /**
930 * The request types this actor can handle.
931 */
932 WebappsActor.prototype.requestTypes = {
933 "install": WebappsActor.prototype.install,
934 "uploadPackage": WebappsActor.prototype.uploadPackage,
935 "getAll": WebappsActor.prototype.getAll,
936 "getApp": WebappsActor.prototype.getApp,
937 "launch": WebappsActor.prototype.launch,
938 "close": WebappsActor.prototype.close,
939 "uninstall": WebappsActor.prototype.uninstall,
940 "listRunningApps": WebappsActor.prototype.listRunningApps,
941 "getAppActor": WebappsActor.prototype.getAppActor,
942 "watchApps": WebappsActor.prototype.watchApps,
943 "unwatchApps": WebappsActor.prototype.unwatchApps,
944 "getIconAsDataURL": WebappsActor.prototype.getIconAsDataURL
945 };
947 DebuggerServer.addGlobalActor(WebappsActor, "webappsActor");