In a following patch, all DevTools moz.build files will use DevToolsModules to install JS modules at a path that corresponds directly to their source tree location. Here we rewrite all require and import calls to match the new location that these files are installed to.
835 lines
23 KiB
JavaScript
835 lines
23 KiB
JavaScript
const {Ci, Cc, Cu, Cr} = require("chrome");
|
|
Cu.import("resource://gre/modules/osfile.jsm");
|
|
const {Services} = Cu.import("resource://gre/modules/Services.jsm");
|
|
const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm");
|
|
const {NetUtil} = Cu.import("resource://gre/modules/NetUtil.jsm");
|
|
const promise = require("promise");
|
|
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
|
const EventEmitter = require("devtools/shared/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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convert an XPConnect result code to its name and message.
|
|
* We have to extract them from an exception per bug 637307 comment 5.
|
|
*/
|
|
function getResultText(code) {
|
|
let regexp =
|
|
/^\[Exception... "(.*)" nsresult: "0x[0-9a-fA-F]* \((.*)\)" location: ".*" data: .*\]$/;
|
|
let ex = Cc["@mozilla.org/js/xpc/Exception;1"].
|
|
createInstance(Ci.nsIXPCException);
|
|
ex.initialize(null, code, null, null, null, null);
|
|
let [, message, name] = regexp.exec(ex.toString());
|
|
return { name: name, message: message };
|
|
}
|
|
|
|
function zipDirectory(zipFile, dirToArchive) {
|
|
let deferred = promise.defer();
|
|
let writer = Cc["@mozilla.org/zipwriter;1"].createInstance(Ci.nsIZipWriter);
|
|
writer.open(zipFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE);
|
|
|
|
this.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);
|
|
} else {
|
|
return uploadPackageJSON(client, webappsActor, packageFile, progressCallback);
|
|
}
|
|
}
|
|
|
|
function uploadPackageJSON(client, webappsActor, packageFile, progressCallback) {
|
|
let deferred = promise.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 request = {
|
|
to: actor,
|
|
type: "chunk",
|
|
chunk: chunk
|
|
};
|
|
client.request(request, (res) => {
|
|
if (bytes.length == CHUNK_SIZE) {
|
|
uploadChunk(actor, file);
|
|
} else {
|
|
file.close().then(function () {
|
|
endsUpload(actor);
|
|
});
|
|
}
|
|
});
|
|
});
|
|
}
|
|
function endsUpload(actor) {
|
|
let request = {
|
|
to: actor,
|
|
type: "done"
|
|
};
|
|
client.request(request, (res) => {
|
|
deferred.resolve(actor);
|
|
});
|
|
}
|
|
return deferred.promise;
|
|
}
|
|
|
|
function uploadPackageBulk(client, webappsActor, packageFile, progressCallback) {
|
|
let deferred = promise.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 request = client.startBulkRequest({
|
|
actor: actor,
|
|
type: "stream",
|
|
length: fileSize
|
|
});
|
|
|
|
request.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 = promise.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 = promise.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 = promise.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: 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 = promise.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 => {
|
|
return this._form = res.actor;
|
|
});
|
|
},
|
|
|
|
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;
|
|
});
|
|
return this._target = 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 = promise.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);
|
|
} else {
|
|
let request = {
|
|
to: this.actor,
|
|
type: "getApp",
|
|
manifestURL: manifestURL
|
|
};
|
|
return this.client.request(request)
|
|
.then(res => {
|
|
let 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"
|
|
};
|
|
return 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 request = {
|
|
to: this.actor,
|
|
type: "listRunningApps"
|
|
};
|
|
return this.client.request(request)
|
|
.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);
|
|
});
|
|
},
|
|
|
|
fetchIcons: function () {
|
|
// On demand, retrieve apps icons in order to be able
|
|
// to synchronously retrieve it on `App` objects
|
|
let promises = [];
|
|
for (let [manifestURL, app] of this._apps) {
|
|
promises.push(app.getIcon());
|
|
}
|
|
|
|
return DevToolsUtils.settleAll(promises)
|
|
.then(null, () => {});
|
|
},
|
|
|
|
_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.indexOf(manifestURL) !== -1) {
|
|
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;
|
|
default:
|
|
return;
|
|
}
|
|
});
|
|
},
|
|
|
|
_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 = promise.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;
|
|
}
|
|
}
|
|
};
|
|
this._listenAppEvents(listener)
|
|
// Execute the request
|
|
.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;
|