зеркало из https://github.com/mozilla/gecko-dev.git
Bug 911785 - Implement a webapps actor front to ease app install r=paul
This commit is contained in:
Родитель
5a519eba6f
Коммит
7b9414310e
|
@ -55,6 +55,7 @@ var BuiltinProvider = {
|
|||
"devtools": "resource:///modules/devtools",
|
||||
"devtools/server": "resource://gre/modules/devtools/server",
|
||||
"devtools/toolkit/webconsole": "resource://gre/modules/devtools/toolkit/webconsole",
|
||||
"devtools/app-actor-front": "resource://gre/modules/devtools/app-actor-front.js",
|
||||
"devtools/styleinspector/css-logic": "resource://gre/modules/devtools/styleinspector/css-logic",
|
||||
"devtools/client": "resource://gre/modules/devtools/client",
|
||||
|
||||
|
@ -95,6 +96,7 @@ var SrcdirProvider = {
|
|||
let toolkitURI = this.fileURI(OS.Path.join(srcdir, "toolkit", "devtools"));
|
||||
let serverURI = this.fileURI(OS.Path.join(srcdir, "toolkit", "devtools", "server"));
|
||||
let webconsoleURI = this.fileURI(OS.Path.join(srcdir, "toolkit", "devtools", "webconsole"));
|
||||
let appActorURI = this.fileURI(OS.Path.join(srcdir, "toolkit", "devtools", "apps", "app-actor-front.js"));
|
||||
let cssLogicURI = this.fileURI(OS.Path.join(toolkitURI, "styleinspector", "css-logic"));
|
||||
let clientURI = this.fileURI(OS.Path.join(srcdir, "toolkit", "devtools", "client"));
|
||||
let mainURI = this.fileURI(OS.Path.join(srcdir, "browser", "devtools", "main.js"));
|
||||
|
@ -107,6 +109,7 @@ var SrcdirProvider = {
|
|||
"": "resource://gre/modules/commonjs/",
|
||||
"devtools/server": serverURI,
|
||||
"devtools/toolkit/webconsole": webconsoleURI,
|
||||
"devtools/app-actor-front": appActorURI,
|
||||
"devtools/client": clientURI,
|
||||
"devtools": devtoolsURI,
|
||||
"devtools/styleinspector/css-logic": cssLogicURI,
|
||||
|
|
|
@ -0,0 +1,216 @@
|
|||
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 promise = require("sdk/core/promise");
|
||||
|
||||
// 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;
|
||||
|
||||
function addDirToZip(writer, dir, basePath) {
|
||||
let files = dir.directoryEntries;
|
||||
|
||||
while (files.hasMoreElements()) {
|
||||
let file = files.getNext().QueryInterface(Ci.nsIFile);
|
||||
|
||||
if (file.isHidden() ||
|
||||
file.isSymlink() ||
|
||||
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 getResultTest(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) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
let request = {
|
||||
to: webappsActor,
|
||||
type: "uploadPackage"
|
||||
};
|
||||
client.request(request, (res) => {
|
||||
openFile(res.actor);
|
||||
});
|
||||
|
||||
function openFile(actor) {
|
||||
OS.File.open(packageFile.path)
|
||||
.then(function (file) {
|
||||
uploadChunk(actor, file);
|
||||
});
|
||||
}
|
||||
function uploadChunk(actor, file) {
|
||||
file.read(CHUNK_SIZE)
|
||||
.then(function (bytes) {
|
||||
// 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 removeServerTemporaryFile(client, fileActor) {
|
||||
let request = {
|
||||
to: fileActor,
|
||||
type: "remove"
|
||||
};
|
||||
client.request(request, function (aResponse) {
|
||||
console.error("Failed removing server side temporary package file", aResponse);
|
||||
});
|
||||
}
|
||||
|
||||
function installPackaged(client, webappsActor, packagePath, appId) {
|
||||
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)
|
||||
.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);
|
||||
}
|
||||
});
|
||||
client.addOneTimeListener("webappsEvent", function (aState, aType, aPacket) {
|
||||
if ("error" in aType)
|
||||
deferred.reject({error: aType.error, message: aType.message});
|
||||
else
|
||||
deferred.resolve({appId: aType.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, removeServerTemporaryFile);
|
||||
});
|
||||
});
|
||||
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);
|
||||
}
|
||||
});
|
||||
client.addOneTimeListener("webappsEvent", function (aState, aType, aPacket) {
|
||||
if ("error" in aType)
|
||||
deferred.reject({error: aType.error, message: aType.message});
|
||||
else
|
||||
deferred.resolve({appId: aType.appId});
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
exports.installHosted = installHosted;
|
||||
|
|
@ -8,5 +8,6 @@ TEST_DIRS += ['tests']
|
|||
JS_MODULES_PATH = 'modules/devtools'
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'Simulator.jsm'
|
||||
'Simulator.jsm',
|
||||
'app-actor-front.js'
|
||||
]
|
||||
|
|
|
@ -98,12 +98,13 @@ function do_get_webappsdir() {
|
|||
var webappsDir = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
|
||||
webappsDir.append("test_webapps");
|
||||
if (!webappsDir.exists())
|
||||
webappsDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
|
||||
webappsDir.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("755", 8));
|
||||
|
||||
var coreAppsDir = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
|
||||
coreAppsDir.append("test_coreapps");
|
||||
if (!coreAppsDir.exists())
|
||||
coreAppsDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
|
||||
coreAppsDir.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("755", 8));
|
||||
var tmpDir = Services.dirsvc.get("TmpD", Ci.nsILocalFile);
|
||||
|
||||
// Register our own provider for the profile directory.
|
||||
// It will return our special docshell profile directory.
|
||||
|
@ -116,6 +117,7 @@ function do_get_webappsdir() {
|
|||
else if (prop == "coreAppsDir") {
|
||||
return coreAppsDir.clone();
|
||||
}
|
||||
return tmpDir.clone();
|
||||
throw Cr.NS_ERROR_FAILURE;
|
||||
},
|
||||
QueryInterface: function(iid) {
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
const {require} = devtools;
|
||||
const {installHosted, installPackaged} = require("devtools/app-actor-front");
|
||||
|
||||
let gAppId = "actor-test";
|
||||
const APP_ORIGIN = "app://" + gAppId;
|
||||
|
@ -177,105 +180,35 @@ add_test(function testUninstall() {
|
|||
});
|
||||
|
||||
add_test(function testFileUploadInstall() {
|
||||
function createUpload() {
|
||||
let request = {
|
||||
type: "uploadPackage"
|
||||
};
|
||||
webappActorRequest(request, function (aResponse) {
|
||||
getPackageContent(aResponse.actor);
|
||||
});
|
||||
}
|
||||
function getPackageContent(uploadActor) {
|
||||
let packageFile = do_get_file("data/app.zip");
|
||||
OS.File.read(packageFile.path)
|
||||
.then(function (bytes) {
|
||||
// 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 content = String.fromCharCode.apply(null, bytes);
|
||||
uploadChunk(uploadActor, content);
|
||||
});
|
||||
}
|
||||
function uploadChunk(uploadActor, content) {
|
||||
let request = {
|
||||
to: uploadActor,
|
||||
type: "chunk",
|
||||
chunk: content
|
||||
};
|
||||
gClient.request(request, function (aResponse) {
|
||||
endsUpload(uploadActor);
|
||||
});
|
||||
}
|
||||
function endsUpload(uploadActor, content) {
|
||||
let request = {
|
||||
to: uploadActor,
|
||||
type: "done"
|
||||
};
|
||||
gClient.request(request, function (aResponse) {
|
||||
installApp(uploadActor);
|
||||
});
|
||||
}
|
||||
function installApp(uploadActor) {
|
||||
let request = {type: "install", appId: gAppId, upload: uploadActor};
|
||||
webappActorRequest(request, function (aResponse) {
|
||||
do_check_eq(aResponse.appId, gAppId);
|
||||
});
|
||||
gClient.addOneTimeListener("webappsEvent", function listener(aState, aType, aPacket) {
|
||||
do_check_eq(aType.appId, gAppId);
|
||||
if ("error" in aType) {
|
||||
do_print("Error: " + aType.error);
|
||||
}
|
||||
if ("message" in aType) {
|
||||
do_print("Error message: " + aType.message);
|
||||
}
|
||||
do_check_eq("error" in aType, false);
|
||||
|
||||
removeUpload(uploadActor);
|
||||
});
|
||||
}
|
||||
function removeUpload(uploadActor, content) {
|
||||
let request = {
|
||||
to: uploadActor,
|
||||
type: "remove"
|
||||
};
|
||||
gClient.request(request, function (aResponse) {
|
||||
let packageFile = do_get_file("data/app.zip");
|
||||
installPackaged(gClient, gActor, packageFile.path, gAppId)
|
||||
.then(function ({ appId }) {
|
||||
do_check_eq(appId, gAppId);
|
||||
run_next_test();
|
||||
}, function (e) {
|
||||
do_throw("Failed install uploaded packaged app: " + e.error + ": " + e.message);
|
||||
});
|
||||
}
|
||||
createUpload();
|
||||
});
|
||||
|
||||
add_test(function testInstallHosted() {
|
||||
gAppId = "hosted-app";
|
||||
let request = {
|
||||
type: "install",
|
||||
appId: gAppId,
|
||||
manifest: {
|
||||
name: "My hosted app"
|
||||
},
|
||||
metadata: {
|
||||
origin: "http://foo.com",
|
||||
installOrigin: "http://metadata.foo.com",
|
||||
manifestURL: "http://foo.com/metadata/manifest.webapp"
|
||||
}
|
||||
let metadata = {
|
||||
origin: "http://foo.com",
|
||||
installOrigin: "http://metadata.foo.com",
|
||||
manifestURL: "http://foo.com/metadata/manifest.webapp"
|
||||
};
|
||||
webappActorRequest(request, function (aResponse) {
|
||||
do_check_eq(aResponse.appId, gAppId);
|
||||
});
|
||||
|
||||
// The install request is asynchronous and send back an event to tell
|
||||
// if the installation succeed or failed
|
||||
gClient.addOneTimeListener("webappsEvent", function listener(aState, aType, aPacket) {
|
||||
do_check_eq(aType.appId, gAppId);
|
||||
if ("error" in aType) {
|
||||
do_print("Error: " + aType.error);
|
||||
let manifest = {
|
||||
name: "My hosted app"
|
||||
};
|
||||
installHosted(gClient, gActor, gAppId, metadata, manifest).then(
|
||||
function ({ appId }) {
|
||||
do_check_eq(appId, gAppId);
|
||||
run_next_test();
|
||||
},
|
||||
function (e) {
|
||||
do_throw("Failed installing hosted app: " + e.error + ": " + e.message);
|
||||
}
|
||||
if ("message" in aType) {
|
||||
do_print("Error message: " + aType.message);
|
||||
}
|
||||
do_check_eq("error" in aType, false);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
);
|
||||
});
|
||||
|
||||
add_test(function testCheckHostedApp() {
|
||||
|
|
Загрузка…
Ссылка в новой задаче