deleted file mode 100644
--- a/devtools/shared/apps/app-actor-front.js
+++ /dev/null
@@ -1,817 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const {Ci, Cc, Cr} = require("chrome");
-const {OS} = require("resource://gre/modules/osfile.jsm");
-const {FileUtils} = require("resource://gre/modules/FileUtils.jsm");
-const {NetUtil} = require("resource://gre/modules/NetUtil.jsm");
-const promise = require("promise");
-const defer = require("devtools/shared/defer");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const EventEmitter = require("devtools/shared/old-event-emitter");
-
-// Bug 1188401: When loaded from xpcshell tests, we do not have browser/ files
-// and can't load target.js. Should be fixed by bug 912121.
-loader.lazyRequireGetter(this, "TargetFactory", "devtools/client/framework/target", true);
-
-// XXX: bug 912476 make this module a real protocol.js front
-// by converting webapps actor to protocol.js
-
-const PR_USEC_PER_MSEC = 1000;
-const PR_RDWR = 0x04;
-const PR_CREATE_FILE = 0x08;
-const PR_TRUNCATE = 0x20;
-
-const CHUNK_SIZE = 10000;
-
-const appTargets = new Map();
-
-function addDirToZip(writer, dir, basePath) {
- let files = dir.directoryEntries;
-
- while (files.hasMoreElements()) {
- let file = files.getNext().QueryInterface(Ci.nsIFile);
-
- if (file.isHidden() ||
- file.isSpecial() ||
- file.equals(writer.file)) {
- continue;
- }
-
- if (file.isDirectory()) {
- writer.addEntryDirectory(basePath + file.leafName + "/",
- file.lastModifiedTime * PR_USEC_PER_MSEC,
- true);
- addDirToZip(writer, file, basePath + file.leafName + "/");
- } else {
- writer.addEntryFile(basePath + file.leafName,
- Ci.nsIZipWriter.COMPRESSION_DEFAULT,
- file,
- true);
- }
- }
-}
-
-function getResultText(code) {
- /*
- * If it ever becomes necessary to convert the nsresult to a useful
- * string here, we'll need an API for that.
- */
- return { name: "Error code", message: code + "" };
-}
-
-function zipDirectory(zipFile, dirToArchive) {
- let deferred = defer();
- let writer = Cc["@mozilla.org/zipwriter;1"].createInstance(Ci.nsIZipWriter);
- writer.open(zipFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE);
-
- addDirToZip(writer, dirToArchive, "");
-
- writer.processQueue({
- onStartRequest: function onStartRequest(request, context) {},
- onStopRequest: (request, context, status) => {
- if (status == Cr.NS_OK) {
- writer.close();
- deferred.resolve(zipFile);
- } else {
- let { name, message } = getResultText(status);
- deferred.reject(name + ": " + message);
- }
- }
- }, null);
-
- return deferred.promise;
-}
-
-function uploadPackage(client, webappsActor, packageFile, progressCallback) {
- if (client.traits.bulk) {
- return uploadPackageBulk(client, webappsActor, packageFile, progressCallback);
- }
- return uploadPackageJSON(client, webappsActor, packageFile, progressCallback);
-}
-
-function uploadPackageJSON(client, webappsActor, packageFile, progressCallback) {
- let deferred = defer();
-
- let request = {
- to: webappsActor,
- type: "uploadPackage"
- };
- client.request(request, (res) => {
- openFile(res.actor);
- });
-
- let fileSize;
- let bytesRead = 0;
-
- function emitProgress() {
- progressCallback({
- bytesSent: bytesRead,
- totalBytes: fileSize
- });
- }
-
- function openFile(actor) {
- let openedFile;
- OS.File.open(packageFile.path).then(file => {
- openedFile = file;
- return openedFile.stat();
- }).then(fileInfo => {
- fileSize = fileInfo.size;
- emitProgress();
- uploadChunk(actor, openedFile);
- });
- }
- function uploadChunk(actor, file) {
- file.read(CHUNK_SIZE).then(function (bytes) {
- bytesRead += bytes.length;
- emitProgress();
- // To work around the fact that JSON.stringify translates the typed
- // array to object, we are encoding the typed array here into a string
- let chunk = String.fromCharCode.apply(null, bytes);
-
- let chunkRequest = {
- to: actor,
- type: "chunk",
- chunk,
- };
- client.request(chunkRequest, (res) => {
- if (bytes.length == CHUNK_SIZE) {
- uploadChunk(actor, file);
- } else {
- file.close().then(function () {
- endsUpload(actor);
- });
- }
- });
- });
- }
- function endsUpload(actor) {
- let doneRequest = {
- to: actor,
- type: "done"
- };
- client.request(doneRequest, (res) => {
- deferred.resolve(actor);
- });
- }
- return deferred.promise;
-}
-
-function uploadPackageBulk(client, webappsActor, packageFile, progressCallback) {
- let deferred = defer();
-
- let request = {
- to: webappsActor,
- type: "uploadPackage",
- bulk: true
- };
- client.request(request, (res) => {
- startBulkUpload(res.actor);
- });
-
- function startBulkUpload(actor) {
- console.log("Starting bulk upload");
- let fileSize = packageFile.fileSize;
- console.log("File size: " + fileSize);
-
- let streamRequest = client.startBulkRequest({
- actor: actor,
- type: "stream",
- length: fileSize
- });
-
- streamRequest.on("bulk-send-ready", ({copyFrom}) => {
- NetUtil.asyncFetch({
- uri: NetUtil.newURI(packageFile),
- loadUsingSystemPrincipal: true
- }, function (inputStream) {
- let copying = copyFrom(inputStream);
- copying.on("progress", (e, progress) => {
- progressCallback(progress);
- });
- copying.then(() => {
- console.log("Bulk upload done");
- inputStream.close();
- deferred.resolve(actor);
- });
- });
- });
- }
-
- return deferred.promise;
-}
-
-function removeServerTemporaryFile(client, fileActor) {
- let request = {
- to: fileActor,
- type: "remove"
- };
- client.request(request);
-}
-
-/**
- * progressCallback argument:
- * Function called as packaged app installation proceeds.
- * The progress object passed to this function contains:
- * * bytesSent: The number of bytes sent so far
- * * totalBytes: The total number of bytes to send
- */
-function installPackaged(client, webappsActor, packagePath, appId, progressCallback) {
- let deferred = defer();
- let file = FileUtils.File(packagePath);
- let packagePromise;
- if (file.isDirectory()) {
- let tmpZipFile = FileUtils.getDir("TmpD", [], true);
- tmpZipFile.append("application.zip");
- tmpZipFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
- packagePromise = zipDirectory(tmpZipFile, file);
- } else {
- packagePromise = promise.resolve(file);
- }
- packagePromise.then((zipFile) => {
- uploadPackage(client, webappsActor, zipFile, progressCallback).then((fileActor) => {
- let request = {
- to: webappsActor,
- type: "install",
- appId: appId,
- upload: fileActor
- };
- client.request(request, (res) => {
- // If the install method immediatly fails,
- // reject immediatly the installPackaged promise.
- // Otherwise, wait for webappsEvent for completion
- if (res.error) {
- deferred.reject(res);
- }
- if ("error" in res) {
- deferred.reject({error: res.error, message: res.message});
- } else {
- deferred.resolve({appId: res.appId});
- }
- });
- // Ensure deleting the temporary package file, but only if that a temporary
- // package created when we pass a directory as `packagePath`
- if (zipFile != file) {
- zipFile.remove(false);
- }
- // In case of success or error, ensure deleting the temporary package file
- // also created on the device, but only once install request is done
- deferred.promise.then(
- () => removeServerTemporaryFile(client, fileActor),
- () => removeServerTemporaryFile(client, fileActor));
- });
- });
- return deferred.promise;
-}
-exports.installPackaged = installPackaged;
-
-function installHosted(client, webappsActor, appId, metadata, manifest) {
- let deferred = defer();
- let request = {
- to: webappsActor,
- type: "install",
- appId: appId,
- metadata: metadata,
- manifest: manifest
- };
- client.request(request, (res) => {
- if (res.error) {
- deferred.reject(res);
- }
- if ("error" in res) {
- deferred.reject({error: res.error, message: res.message});
- } else {
- deferred.resolve({appId: res.appId});
- }
- });
- return deferred.promise;
-}
-exports.installHosted = installHosted;
-
-function getTargetForApp(client, webappsActor, manifestURL) {
- // Ensure always returning the exact same JS object for a target
- // of the same app in order to show only one toolbox per app and
- // avoid re-creating lot of objects twice.
- let existingTarget = appTargets.get(manifestURL);
- if (existingTarget) {
- return promise.resolve(existingTarget);
- }
-
- let deferred = defer();
- let request = {
- to: webappsActor,
- type: "getAppActor",
- manifestURL: manifestURL,
- };
- client.request(request, (res) => {
- if (res.error) {
- deferred.reject(res.error);
- } else {
- let options = {
- form: res.actor,
- client: client,
- chrome: false
- };
-
- TargetFactory.forRemoteTab(options).then((target) => {
- target.isApp = true;
- appTargets.set(manifestURL, target);
- target.on("close", () => {
- appTargets.delete(manifestURL);
- });
- deferred.resolve(target);
- }, (error) => {
- deferred.reject(error);
- });
- }
- });
- return deferred.promise;
-}
-exports.getTargetForApp = getTargetForApp;
-
-function reloadApp(client, webappsActor, manifestURL) {
- return getTargetForApp(
- client, webappsActor, manifestURL
- ).then((target) => {
- // Request the ContentActor to reload the app
- let request = {
- to: target.form.actor,
- type: "reload",
- options: {
- force: true
- },
- manifestURL,
- };
- return client.request(request);
- }, () => {
- throw new Error("Not running");
- });
-}
-exports.reloadApp = reloadApp;
-
-function launchApp(client, webappsActor, manifestURL) {
- return client.request({
- to: webappsActor,
- type: "launch",
- manifestURL: manifestURL
- });
-}
-exports.launchApp = launchApp;
-
-function closeApp(client, webappsActor, manifestURL) {
- return client.request({
- to: webappsActor,
- type: "close",
- manifestURL: manifestURL
- });
-}
-exports.closeApp = closeApp;
-
-function getTarget(client, form) {
- let deferred = defer();
- let options = {
- form: form,
- client: client,
- chrome: false
- };
-
- TargetFactory.forRemoteTab(options).then((target) => {
- target.isApp = true;
- deferred.resolve(target);
- }, (error) => {
- deferred.reject(error);
- });
- return deferred.promise;
-}
-
-/**
- * `App` instances are client helpers to manage a given app
- * and its the tab actors
- */
-function App(client, webappsActor, manifest) {
- this.client = client;
- this.webappsActor = webappsActor;
- this.manifest = manifest;
-
- // This attribute is managed by the AppActorFront
- this.running = false;
-
- this.iconURL = null;
-}
-
-App.prototype = {
- getForm: function () {
- if (this._form) {
- return promise.resolve(this._form);
- }
- let request = {
- to: this.webappsActor,
- type: "getAppActor",
- manifestURL: this.manifest.manifestURL
- };
- return this.client.request(request).then(res => {
- this._form = res.actor;
- return this._form;
- });
- },
-
- getTarget: function () {
- if (this._target) {
- return promise.resolve(this._target);
- }
- return this.getForm().then(
- (form) => getTarget(this.client, form)
- ).then((target) => {
- target.on("close", () => {
- delete this._form;
- delete this._target;
- });
- this._target = target;
- return this._target;
- });
- },
-
- launch: function () {
- return launchApp(this.client, this.webappsActor,
- this.manifest.manifestURL);
- },
-
- reload: function () {
- return reloadApp(this.client, this.webappsActor,
- this.manifest.manifestURL);
- },
-
- close: function () {
- return closeApp(this.client, this.webappsActor,
- this.manifest.manifestURL);
- },
-
- getIcon: function () {
- if (this.iconURL) {
- return promise.resolve(this.iconURL);
- }
-
- let deferred = defer();
-
- let request = {
- to: this.webappsActor,
- type: "getIconAsDataURL",
- manifestURL: this.manifest.manifestURL
- };
-
- this.client.request(request, res => {
- if (res.error) {
- deferred.reject(res.message || res.error);
- } else if (res.url) {
- this.iconURL = res.url;
- deferred.resolve(res.url);
- } else {
- deferred.reject("Unable to fetch app icon");
- }
- });
-
- return deferred.promise;
- }
-};
-
-/**
- * `AppActorFront` is a client for the webapps actor.
- */
-function AppActorFront(client, form) {
- this.client = client;
- this.actor = form.webappsActor;
-
- this._clientListener = this._clientListener.bind(this);
- this._onInstallProgress = this._onInstallProgress.bind(this);
-
- this._listeners = [];
- EventEmitter.decorate(this);
-}
-
-AppActorFront.prototype = {
- /**
- * List `App` instances for all currently running apps.
- */
- get runningApps() {
- if (!this._apps) {
- throw new Error("Can't get running apps before calling watchApps.");
- }
- let r = new Map();
- for (let [manifestURL, app] of this._apps) {
- if (app.running) {
- r.set(manifestURL, app);
- }
- }
- return r;
- },
-
- /**
- * List `App` instances for all installed apps.
- */
- get apps() {
- if (!this._apps) {
- throw new Error("Can't get apps before calling watchApps.");
- }
- return this._apps;
- },
-
- /**
- * Returns a `App` object instance for the given manifest URL
- * (and cache it per AppActorFront object)
- */
- _getApp: function (manifestURL) {
- let app = this._apps ? this._apps.get(manifestURL) : null;
- if (app) {
- return promise.resolve(app);
- }
- let request = {
- to: this.actor,
- type: "getApp",
- manifestURL,
- };
- return this.client.request(request).then(res => {
- app = new App(this.client, this.actor, res.app);
- if (this._apps) {
- this._apps.set(manifestURL, app);
- }
- return app;
- }, e => {
- console.error("Unable to retrieve app", manifestURL, e);
- });
- },
-
- /**
- * Starts watching for app opening/closing installing/uninstalling.
- * Needs to be called before using `apps` or `runningApps` attributes.
- */
- watchApps: function (listener) {
- // Fixes race between two references to the same front
- // calling watchApps at the same time
- if (this._loadingPromise) {
- return this._loadingPromise;
- }
-
- // Only call watchApps for the first listener being register,
- // for all next ones, just send fake appOpen events for already
- // opened apps
- if (this._apps) {
- this.runningApps.forEach((app, manifestURL) => {
- listener("appOpen", app);
- });
- return promise.resolve();
- }
-
- // First retrieve all installed apps and create
- // related `App` object for each
- let request = {
- to: this.actor,
- type: "getAll"
- };
- this._loadingPromise = this.client.request(request).then(res => {
- delete this._loadingPromise;
- this._apps = new Map();
- for (let a of res.apps) {
- let app = new App(this.client, this.actor, a);
- this._apps.set(a.manifestURL, app);
- }
- }).then(() => {
- // Then retrieve all running apps in order to flag them as running
- let listRequest = {
- to: this.actor,
- type: "listRunningApps"
- };
- return this.client.request(listRequest).then(res => res.apps);
- }).then(apps => {
- let promises = apps.map(manifestURL => {
- // _getApp creates `App` instance and register it to AppActorFront
- return this._getApp(manifestURL).then(app => {
- app.running = true;
- // Fake appOpen event for all already opened
- this._notifyListeners("appOpen", app);
- });
- });
- return promise.all(promises);
- }).then(() => {
- // Finally ask to receive all app events
- return this._listenAppEvents(listener);
- });
- return this._loadingPromise;
- },
-
- fetchIcons: function () {
- // On demand, retrieve apps icons in order to be able
- // to synchronously retrieve it on `App` objects
- let promises = [];
- for (let [, app] of this._apps) {
- promises.push(app.getIcon());
- }
-
- return DevToolsUtils.settleAll(promises)
- .catch(() => {});
- },
-
- _listenAppEvents: function (listener) {
- this._listeners.push(listener);
-
- if (this._listeners.length > 1) {
- return promise.resolve();
- }
-
- let client = this.client;
- let f = this._clientListener;
- client.addListener("appOpen", f);
- client.addListener("appClose", f);
- client.addListener("appInstall", f);
- client.addListener("appUninstall", f);
-
- let request = {
- to: this.actor,
- type: "watchApps"
- };
- return this.client.request(request);
- },
-
- _unlistenAppEvents: function (listener) {
- let idx = this._listeners.indexOf(listener);
- if (idx != -1) {
- this._listeners.splice(idx, 1);
- }
-
- // Until we released all listener, we don't ask to stop sending events
- if (this._listeners.length != 0) {
- return promise.resolve();
- }
-
- let client = this.client;
- let f = this._clientListener;
- client.removeListener("appOpen", f);
- client.removeListener("appClose", f);
- client.removeListener("appInstall", f);
- client.removeListener("appUninstall", f);
-
- // Remove `_apps` in order to allow calling watchApps again
- // and repopulate the apps Map.
- delete this._apps;
-
- let request = {
- to: this.actor,
- type: "unwatchApps"
- };
- return this.client.request(request);
- },
-
- _clientListener: function (type, message) {
- let { manifestURL } = message;
-
- // Reset the app object to get a fresh copy when we (re)install the app.
- if (type == "appInstall" && this._apps && this._apps.has(manifestURL)) {
- this._apps.delete(manifestURL);
- }
-
- this._getApp(manifestURL).then((app) => {
- switch (type) {
- case "appOpen":
- app.running = true;
- this._notifyListeners("appOpen", app);
- break;
- case "appClose":
- app.running = false;
- this._notifyListeners("appClose", app);
- break;
- case "appInstall":
- // The call to _getApp is going to create App object
-
- // This app may have been running while being installed, so check the list
- // of running apps again to get the right answer.
- let request = {
- to: this.actor,
- type: "listRunningApps"
- };
- this.client.request(request).then(res => {
- if (res.apps.includes(manifestURL)) {
- app.running = true;
- this._notifyListeners("appInstall", app);
- this._notifyListeners("appOpen", app);
- } else {
- this._notifyListeners("appInstall", app);
- }
- });
- break;
- case "appUninstall":
- // Fake a appClose event if we didn't got one before uninstall
- if (app.running) {
- app.running = false;
- this._notifyListeners("appClose", app);
- }
- this._apps.delete(manifestURL);
- this._notifyListeners("appUninstall", app);
- break;
- }
- });
- },
-
- _notifyListeners: function (type, app) {
- this._listeners.forEach(f => {
- f(type, app);
- });
- },
-
- unwatchApps: function (listener) {
- return this._unlistenAppEvents(listener);
- },
-
- /*
- * Install a packaged app.
- *
- * Events are going to be emitted on the front
- * as install progresses. Events will have the following fields:
- * * bytesSent: The number of bytes sent so far
- * * totalBytes: The total number of bytes to send
- */
- installPackaged: function (packagePath, appId) {
- let request = () => {
- return installPackaged(this.client, this.actor, packagePath, appId,
- this._onInstallProgress)
- .then(response => ({
- appId: response.appId,
- manifestURL: "app://" + response.appId + "/manifest.webapp"
- }));
- };
- return this._install(request);
- },
-
- _onInstallProgress: function (progress) {
- this.emit("install-progress", progress);
- },
-
- _install: function (request) {
- let deferred = defer();
- let finalAppId = null, manifestURL = null;
- let installs = {};
-
- // We need to resolve only once the request is done *AND*
- // once we receive the related appInstall message for
- // the same manifestURL
- let resolve = app => {
- this._unlistenAppEvents(listener);
- installs = null;
- deferred.resolve({ app: app, appId: finalAppId });
- };
-
- // Listen for appInstall event, in order to resolve with
- // the matching app object.
- let listener = (type, app) => {
- if (type == "appInstall") {
- // Resolves immediately if the request has already resolved
- // or just flag the installed app to eventually resolve
- // when the request gets its response.
- if (app.manifest.manifestURL === manifestURL) {
- resolve(app);
- } else {
- installs[app.manifest.manifestURL] = app;
- }
- }
- };
- // Execute the request
- this._listenAppEvents(listener).then(request).then(response => {
- finalAppId = response.appId;
- manifestURL = response.manifestURL;
-
- // Resolves immediately if the appInstall event
- // was dispatched during the request.
- if (manifestURL in installs) {
- resolve(installs[manifestURL]);
- }
- }, deferred.reject);
-
- return deferred.promise;
- },
-
- /*
- * Install a hosted app.
- *
- * Events are going to be emitted on the front
- * as install progresses. Events will have the following fields:
- * * bytesSent: The number of bytes sent so far
- * * totalBytes: The total number of bytes to send
- */
- installHosted: function (appId, metadata, manifest) {
- let manifestURL = metadata.manifestURL ||
- metadata.origin + "/manifest.webapp";
- let request = () => {
- return installHosted(
- this.client, this.actor, appId, metadata, manifest
- ).then(response => ({
- appId: response.appId,
- manifestURL: manifestURL
- }));
- };
- return this._install(request);
- }
-};
-
-exports.AppActorFront = AppActorFront;