зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1435791
- Remove webapps actor front. r=jdescottes
MozReview-Commit-ID: A21zlETw5Iz --HG-- extra : rebase_source : c7653bb64707fb3c0501b2bf21be3d838d3f78f3
This commit is contained in:
Родитель
dc9ab39924
Коммит
61355e5dd1
|
@ -177,9 +177,6 @@ var UI = {
|
|||
this.updateTitle();
|
||||
this.updateCommands();
|
||||
break;
|
||||
case "install-progress":
|
||||
this.updateProgress(Math.round(100 * details.bytesSent / details.totalBytes));
|
||||
break;
|
||||
case "runtime-targets":
|
||||
this.autoSelectProject();
|
||||
break;
|
||||
|
@ -213,13 +210,6 @@ var UI = {
|
|||
_busyOperationDescription: null,
|
||||
_busyPromise: null,
|
||||
|
||||
updateProgress: function (percent) {
|
||||
let progress = document.querySelector("#action-busy-determined");
|
||||
progress.mode = "determined";
|
||||
progress.value = percent;
|
||||
this.setupBusyTimeout();
|
||||
},
|
||||
|
||||
busy: function () {
|
||||
let win = document.querySelector("window");
|
||||
win.classList.add("busy");
|
||||
|
|
|
@ -13,7 +13,6 @@ const {AppProjects} = require("devtools/client/webide/modules/app-projects");
|
|||
const TabStore = require("devtools/client/webide/modules/tab-store");
|
||||
const {AppValidator} = require("devtools/client/webide/modules/app-validator");
|
||||
const {ConnectionManager, Connection} = require("devtools/shared/client/connection-manager");
|
||||
const {AppActorFront} = require("devtools/shared/apps/app-actor-front");
|
||||
const {getDeviceFront} = require("devtools/shared/fronts/device");
|
||||
const {getPreferenceFront} = require("devtools/shared/fronts/preference");
|
||||
const {Task} = require("devtools/shared/task");
|
||||
|
@ -55,8 +54,6 @@ var AppManager = exports.AppManager = {
|
|||
RuntimeScanners.enable();
|
||||
this._rebuildRuntimeList();
|
||||
|
||||
this.onInstallProgress = this.onInstallProgress.bind(this);
|
||||
|
||||
this._telemetry = new Telemetry();
|
||||
},
|
||||
|
||||
|
@ -93,10 +90,6 @@ var AppManager = exports.AppManager = {
|
|||
* |cancel| callback that will abort the project change if desired.
|
||||
* connection:
|
||||
* The connection status has changed (connected, disconnected, etc.)
|
||||
* install-progress:
|
||||
* A project being installed to a runtime has made further progress. This
|
||||
* event contains additional details about exactly how far the process is
|
||||
* when such information is available.
|
||||
* project:
|
||||
* The selected project has changed.
|
||||
* project-started:
|
||||
|
@ -111,8 +104,6 @@ var AppManager = exports.AppManager = {
|
|||
* name, manifest details, etc.
|
||||
* runtime:
|
||||
* The selected runtime has changed.
|
||||
* runtime-apps-icons:
|
||||
* The list of URLs for the runtime app icons are available.
|
||||
* runtime-global-actors:
|
||||
* The list of global actors for the entire runtime (but not actors for a
|
||||
* specific tab or app) are now available, so we can test for features
|
||||
|
@ -160,38 +151,12 @@ var AppManager = exports.AppManager = {
|
|||
}
|
||||
|
||||
if (!this.connected) {
|
||||
if (this._appsFront) {
|
||||
this._appsFront.off("install-progress", this.onInstallProgress);
|
||||
this._appsFront.unwatchApps();
|
||||
this._appsFront = null;
|
||||
}
|
||||
this._listTabsResponse = null;
|
||||
} else {
|
||||
this.connection.client.listTabs().then((response) => {
|
||||
if (response.webappsActor) {
|
||||
let front = new AppActorFront(this.connection.client,
|
||||
response);
|
||||
front.on("install-progress", this.onInstallProgress);
|
||||
front.watchApps(() => this.checkIfProjectIsRunning())
|
||||
.then(() => {
|
||||
// This can't be done earlier as many operations
|
||||
// in the apps actor require watchApps to be called
|
||||
// first.
|
||||
this._appsFront = front;
|
||||
this._listTabsResponse = response;
|
||||
this._recordRuntimeInfo();
|
||||
this.update("runtime-global-actors");
|
||||
})
|
||||
.then(() => {
|
||||
this.checkIfProjectIsRunning();
|
||||
this.update("runtime-targets", { type: "apps" });
|
||||
front.fetchIcons().then(() => this.update("runtime-apps-icons"));
|
||||
});
|
||||
} else {
|
||||
this._listTabsResponse = response;
|
||||
this._recordRuntimeInfo();
|
||||
this.update("runtime-global-actors");
|
||||
}
|
||||
this._listTabsResponse = response;
|
||||
this._recordRuntimeInfo();
|
||||
this.update("runtime-global-actors");
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -211,10 +176,6 @@ var AppManager = exports.AppManager = {
|
|||
}
|
||||
},
|
||||
|
||||
onInstallProgress: function (event, details) {
|
||||
this.update("install-progress", details);
|
||||
},
|
||||
|
||||
isProjectRunning: function () {
|
||||
if (this.selectedProject.type == "mainProcess" ||
|
||||
this.selectedProject.type == "tab") {
|
||||
|
|
|
@ -42,7 +42,6 @@ ProjectList.prototype = {
|
|||
// See AppManager.update() for descriptions of what these events mean.
|
||||
switch (what) {
|
||||
case "project-removed":
|
||||
case "runtime-apps-icons":
|
||||
case "runtime-targets":
|
||||
case "connection":
|
||||
this.update(details);
|
||||
|
|
|
@ -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;
|
|
@ -4,6 +4,5 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
'app-actor-front.js',
|
||||
'Devices.jsm'
|
||||
)
|
||||
|
|
Загрузка…
Ссылка в новой задаче