Bug 944451 - Land simulator addon into mozilla-central. r=vingtetun, r=gps, r=paul

This commit is contained in:
Alexandre Poirot 2014-03-03 14:50:40 -05:00
parent a73c9a8836
commit 46c8d27779
17 changed files with 2726 additions and 0 deletions

View File

@ -2,11 +2,16 @@
# 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/.
include $(topsrcdir)/toolkit/mozapps/installer/package-name.mk
installer:
@$(MAKE) -C b2g/installer installer
package:
@$(MAKE) -C b2g/installer
ifdef FXOS_SIMULATOR
$(PYTHON) $(srcdir)/b2g/simulator/build_xpi.py $(MOZ_PKG_PLATFORM)
endif
install::
@echo 'B2G can't be installed directly.'

145
b2g/simulator/build_xpi.py Normal file
View File

@ -0,0 +1,145 @@
# 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/.
# Generate xpi for the simulator addon by:
# - building a special gaia profile for it, as we need:
# * more languages, and,
# * less apps
# than b2g desktop's one
# - retrieve usefull app version metadata from the build system
# - finally, use addon sdk's cfx tool to build the addon xpi
# that ships:
# * a small firefox addon registering to the app manager
# * b2g desktop runtime
# * gaia profile
import sys, os, re, subprocess
from mozbuild.preprocessor import Preprocessor
from mozbuild.base import MozbuildObject
from mozbuild.util import ensureParentDir
from zipfile import ZipFile
from distutils.version import LooseVersion
ftp_root_path = "/pub/mozilla.org/labs/fxos-simulator"
UPDATE_LINK = "https://ftp.mozilla.org" + ftp_root_path + "/%(update_path)s/%(xpi_name)s"
UPDATE_URL = "https://ftp.mozilla.org" + ftp_root_path + "/%(update_path)s/update.rdf"
XPI_NAME = "fxos-simulator-%(version)s-%(platform)s.xpi"
class GaiaBuilder(object):
def __init__(self, build, gaia_path):
self.build = build
self.gaia_path = gaia_path
def clean(self):
self.build._run_make(target="clean", directory=self.gaia_path)
def profile(self, env):
self.build._run_make(target="profile", directory=self.gaia_path, num_jobs=1, silent=False, append_env=env)
def override_prefs(self, srcfile):
# Note that each time we call `make profile` in gaia, a fresh new pref file is created
# cat srcfile >> profile/user.js
with open(os.path.join(self.gaia_path, "profile", "user.js"), "a") as userJs:
userJs.write(open(srcfile).read())
def process_package_overload(src, dst, version, app_buildid):
ensureParentDir(dst)
# First replace numeric version like '1.3'
# Then replace with 'slashed' version like '1_4'
# Finally set the full length addon version like 1.3.20131230
defines = {
"NUM_VERSION": version,
"SLASH_VERSION": version.replace(".", "_"),
"FULL_VERSION": ("%s.%s" % (version, app_buildid))
}
pp = Preprocessor(defines=defines)
pp.do_filter("substitution")
with open(dst, "w") as output:
with open(src, "r") as input:
pp.processFile(input=input, output=output)
def add_dir_to_zip(zip, top, pathInZip, blacklist=()):
zf = ZipFile(zip, "a")
for dirpath, subdirs, files in os.walk(top):
dir_relpath = os.path.relpath(dirpath, top)
if dir_relpath.startswith(blacklist):
continue
zf.write(dirpath, os.path.join(pathInZip, dir_relpath))
for filename in files:
relpath = os.path.join(dir_relpath, filename)
if relpath in blacklist:
continue
zf.write(os.path.join(dirpath, filename),
os.path.join(pathInZip, relpath))
zf.close()
def main(platform):
build = MozbuildObject.from_environment()
topsrcdir = build.topsrcdir
distdir = build.distdir
srcdir = os.path.join(topsrcdir, "b2g", "simulator")
app_buildid = open(os.path.join(build.topobjdir, "config", "buildid")).read().strip()
# The simulator uses a shorter version string,
# it only keeps the major version digits A.B
# whereas MOZ_B2G_VERSION is A.B.C.D
b2g_version = build.config_environment.defines["MOZ_B2G_VERSION"].replace('"', '')
version = ".".join(str(n) for n in LooseVersion(b2g_version).version[0:2])
# Build a gaia profile specific to the simulator in order to:
# - disable the FTU
# - set custom prefs to enable devtools debugger server
# - set custom settings to disable lockscreen and screen timeout
# - only ship production apps
gaia_path = build.config_environment.substs["GAIADIR"]
builder = GaiaBuilder(build, gaia_path)
builder.clean()
env = {
"NOFTU": "1",
"GAIA_APP_TARGET": "production",
"SETTINGS_PATH": os.path.join(srcdir, "custom-settings.json")
}
builder.profile(env)
builder.override_prefs(os.path.join(srcdir, "custom-prefs.js"))
# Substitute version strings in the package manifest overload file
manifest_overload = os.path.join(build.topobjdir, "b2g", "simulator", "package-overload.json")
process_package_overload(os.path.join(srcdir, "package-overload.json.in"),
manifest_overload,
version,
app_buildid)
# Build the simulator addon xpi
xpi_name = XPI_NAME % {"version": version, "platform": platform}
xpi_path = os.path.join(distdir, xpi_name)
update_path = "%s/%s" % (version, platform)
update_link = UPDATE_LINK % {"update_path": update_path, "xpi_name": xpi_name}
update_url = UPDATE_URL % {"update_path": update_path}
subprocess.check_call([
build.virtualenv_manager.python_path, os.path.join(topsrcdir, "addon-sdk", "source", "bin", "cfx"), "xpi", \
"--pkgdir", srcdir, \
"--manifest-overload", manifest_overload, \
"--strip-sdk", \
"--update-link", update_link, \
"--update-url", update_url, \
"--static-args", "{\"label\": \"Firefox OS %s\"}" % version, \
"--output-file", xpi_path \
])
# Ship b2g-desktop, but prevent its gaia profile to be shipped in the xpi
add_dir_to_zip(xpi_path, os.path.join(distdir, "b2g"), "b2g", ("gaia"))
# Then ship our own gaia profile
add_dir_to_zip(xpi_path, os.path.join(gaia_path, "profile"), "profile")
if __name__ == '__main__':
if 2 != len(sys.argv):
print("""Usage:
python {0} MOZ_PKG_PLATFORM
""".format(sys.argv[0]))
sys.exit(1)
main(*sys.argv[1:])

View File

@ -0,0 +1,2 @@
user_pref("devtools.debugger.prompt-connection", false);
user_pref("devtools.debugger.forbid-certified-apps", false);

View File

@ -0,0 +1,6 @@
{
"debugger.remote-mode": "adb-devtools",
"screen.timeout": 0,
"lockscreen.enabled": false,
"lockscreen.locked": false
}

BIN
b2g/simulator/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
b2g/simulator/icon64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

52
b2g/simulator/lib/main.js Normal file
View File

@ -0,0 +1,52 @@
/* 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/.
*/
const { Cc, Ci, Cu } = require("chrome");
const { SimulatorProcess } = require("./simulator-process");
const Promise = require("sdk/core/promise");
const Self = require("sdk/self");
const System = require("sdk/system");
const { Simulator } = Cu.import("resource://gre/modules/devtools/Simulator.jsm");
let process;
function launch({ port }) {
// Close already opened simulation
if (process) {
return close().then(launch.bind(null, { port: port }));
}
process = SimulatorProcess();
process.remoteDebuggerPort = port;
process.run();
return Promise.resolve();
}
function close() {
if (!process) {
return Promise.resolve();
}
let p = process;
process = null;
return p.kill();
}
// Load data generated at build time that
// expose various information about the runtime we ship
let appinfo = System.staticArgs;
Simulator.register(appinfo.label, {
appinfo: appinfo,
launch: launch,
close: close
});
require("sdk/system/unload").when(function () {
Simulator.unregister(appinfo.label);
close();
});

View File

@ -0,0 +1,182 @@
/* 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 { Cc, Ci, Cu, ChromeWorker } = require("chrome");
Cu.import("resource://gre/modules/Services.jsm");
const { EventTarget } = require("sdk/event/target");
const { emit, off } = require("sdk/event/core");
const { Class } = require("sdk/core/heritage");
const Environment = require("sdk/system/environment").env;
const Runtime = require("sdk/system/runtime");
const Self = require("sdk/self");
const URL = require("sdk/url");
const Subprocess = require("subprocess");
const Promise = require("sdk/core/promise");
const { rootURI: ROOT_URI } = require('@loader/options');
const PROFILE_URL = ROOT_URI + "profile/";
const BIN_URL = ROOT_URI + "b2g/";
// Log subprocess error and debug messages to the console. This logs messages
// for all consumers of the API. We trim the messages because they sometimes
// have trailing newlines. And note that registerLogHandler actually registers
// an error handler, despite its name.
Subprocess.registerLogHandler(
function(s) console.error("subprocess: " + s.trim())
);
Subprocess.registerDebugHandler(
function(s) console.debug("subprocess: " + s.trim())
);
exports.SimulatorProcess = Class({
extends: EventTarget,
initialize: function initialize(options) {
EventTarget.prototype.initialize.call(this, options);
this.on("stdout", function onStdout(data) console.log(data.trim()));
this.on("stderr", function onStderr(data) console.error(data.trim()));
},
// check if b2g is running
get isRunning() !!this.process,
/**
* Start the process and connect the debugger client.
*/
run: function() {
// kill before start if already running
if (this.process != null) {
this.process
.kill()
.then(this.run.bind(this));
return;
}
// resolve b2g binaries path (raise exception if not found)
let b2gExecutable = this.b2gExecutable;
this.once("stdout", function () {
if (Runtime.OS == "Darwin") {
console.debug("WORKAROUND run osascript to show b2g-desktop window"+
" on Runtime.OS=='Darwin'");
// Escape double quotes and escape characters for use in AppleScript.
let path = b2gExecutable.path
.replace(/\\/g, "\\\\").replace(/\"/g, '\\"');
Subprocess.call({
command: "/usr/bin/osascript",
arguments: ["-e", 'tell application "' + path + '" to activate'],
});
}
});
let environment;
if (Runtime.OS == "Linux") {
environment = ["TMPDIR=" + Services.dirsvc.get("TmpD",Ci.nsIFile).path];
if ("DISPLAY" in Environment) {
environment.push("DISPLAY=" + Environment.DISPLAY);
}
}
// spawn a b2g instance
this.process = Subprocess.call({
command: b2gExecutable,
arguments: this.b2gArguments,
environment: environment,
// emit stdout event
stdout: (function(data) {
emit(this, "stdout", data);
}).bind(this),
// emit stderr event
stderr: (function(data) {
emit(this, "stderr", data);
}).bind(this),
// on b2g instance exit, reset tracked process, remoteDebuggerPort and
// shuttingDown flag, then finally emit an exit event
done: (function(result) {
console.log(this.b2gFilename + " terminated with " + result.exitCode);
this.process = null;
emit(this, "exit", result.exitCode);
}).bind(this)
});
},
// request a b2g instance kill
kill: function() {
let deferred = Promise.defer();
if (this.process) {
this.once("exit", (exitCode) => {
this.shuttingDown = false;
deferred.resolve(exitCode);
});
if (!this.shuttingDown) {
this.shuttingDown = true;
emit(this, "kill", null);
this.process.kill();
}
return deferred.promise;
} else {
return Promise.resolve(undefined);
}
},
// compute current b2g filename
get b2gFilename() {
return this._executable ? this._executableFilename : "B2G";
},
// compute current b2g file handle
get b2gExecutable() {
if (this._executable) return this._executable;
let bin = URL.toFilename(BIN_URL);
let executables = {
WINNT: "b2g-bin.exe",
Darwin: "Contents/MacOS/b2g-bin",
Linux: "b2g-bin",
};
console.log("bin url: "+bin+"/"+executables[Runtime.OS]);
let path = bin + "/" + executables[Runtime.OS];
let executable = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
executable.initWithPath(path);
if (!executable.exists()) {
// B2G binaries not found
throw Error("b2g-desktop Executable not found.");
}
this._executable = executable;
this._executableFilename = "b2g-bin";
return executable;
},
// compute b2g CLI arguments
get b2gArguments() {
let args = [];
let profile = URL.toFilename(PROFILE_URL);
args.push("-profile", profile);
Cu.reportError(profile);
// NOTE: push dbgport option on the b2g-desktop commandline
args.push("-dbgport", "" + this.remoteDebuggerPort);
// Ignore eventual zombie instances of b2g that are left over
args.push("-no-remote");
return args;
},
});

View File

@ -0,0 +1,7 @@
{
"id": "fxos_@SLASH_VERSION@_simulator@mozilla.org",
"name": "fxos_@SLASH_VERSION@_simulator",
"version": "@FULL_VERSION@",
"fullName": "Firefox OS @NUM_VERSION@ Simulator",
"description": "a Firefox OS @NUM_VERSION@ simulator"
}

View File

@ -0,0 +1,37 @@
{
"id": "fxos_simulator@mozilla.org",
"name": "fxos_simulator",
"version": "1.0.dev",
"fullName": "Firefox OS Simulator",
"label": "Firefox OS",
"description": "a Firefox OS simulator",
"author": "Myk Melez (https://github.com/mykmelez)",
"contributors": [
"Alexandre Poirot (https://github.com/ochameau)",
"Anant Narayanan (https://github.com/anantn)",
"Brandon Kase (https://github.com/bkase)",
"Breck Yunits (https://github.com/breck7)",
"César Carruitero (https://github.com/ccarruitero)",
"David Gomes (https://github.com/davidgomes)",
"Fabrice Desré (https://github.com/fabricedesre)",
"Fraser Tweedale (https://github.com/frasertweedale)",
"Harald Kirschner (https://github.com/digitarald)",
"Jérémie Patonnier (https://github.com/JeremiePat)",
"J. Ryan Stinnett (https://github.com/jryans)",
"Kan-Ru Chen (陳侃如) (https://github.com/kanru)",
"Louis Stowasser (https://github.com/louisstow)",
"Luca Greco (https://github.com/rpl)",
"Matthew Claypotch (https://github.com/potch)",
"Matthew Riley MacPherson (https://github.com/tofumatt)",
"Nick Desaulniers (https://github.com/nickdesaulniers)",
"Soumen Ganguly (https://github.com/SoumenG)",
"Sudheesh Singanamalla (https://github.com/sudheesh001)",
"Victor Bjelkholm (https://github.com/VictorBjelkholm)"
],
"permissions": {
"private-browsing": true
},
"license": "MPL 2.0",
"unpack": true,
"dependencies": ["subprocess"]
}

View File

@ -0,0 +1,124 @@
<h2>What's that?</h2>
Simply package enigmail hard work on providing IPC feature in mozilla platform.
So we are able to launch child proccesses from javascript,
and in our case, from addon-sdk libraries :)
<h2>Sample of code:</h2>
This object allows to start a process, and read/write data to/from it
using stdin/stdout/stderr streams.
Usage example:
const subprocess = require("subprocess");
var p = subprocess.call({
command: '/bin/foo',
arguments: ['-v', 'foo'],
environment: [ "XYZ=abc", "MYVAR=def" ],
charset: 'UTF-8',
workdir: '/home/foo',
//stdin: "some value to write to stdin\nfoobar",
stdin: function(stdin) {
stdin.write("some value to write to stdin\nfoobar");
stdin.close();
},
stdout: function(data) {
dump("got data on stdout:" + data + "\n");
},
stderr: function(data) {
dump("got data on stderr:" + data + "\n");
},
done: function(result) {
dump("process terminated with " + result.exitCode + "\n");
},
mergeStderr: false
});
p.wait(); // wait for the subprocess to terminate
// this will block the main thread,
// only do if you can wait that long
Description of parameters:
--------------------------
Apart from <command>, all arguments are optional.
command: either a |nsIFile| object pointing to an executable file or a
String containing the platform-dependent path to an executable
file.
arguments: optional string array containing the arguments to the command.
environment: optional string array containing environment variables to pass
to the command. The array elements must have the form
"VAR=data". Please note that if environment is defined, it
replaces any existing environment variables for the subprocess.
charset: Output is decoded with given charset and a string is returned.
If charset is undefined, "UTF-8" is used as default.
To get binary data, set this to null and the returned string
is not decoded in any way.
workdir: optional; String containing the platform-dependent path to a
directory to become the current working directory of the subprocess.
stdin: optional input data for the process to be passed on standard
input. stdin can either be a string or a function.
A |string| gets written to stdin and stdin gets closed;
A |function| gets passed an object with write and close function.
Please note that the write() function will return almost immediately;
data is always written asynchronously on a separate thread.
stdout: an optional function that can receive output data from the
process. The stdout-function is called asynchronously; it can be
called mutliple times during the execution of a process.
At a minimum at each occurance of \n or \r.
Please note that null-characters might need to be escaped
with something like 'data.replace(/\0/g, "\\0");'.
stderr: an optional function that can receive stderr data from the
process. The stderr-function is called asynchronously; it can be
called mutliple times during the execution of a process. Please
note that null-characters might need to be escaped with
something like 'data.replace(/\0/g, "\\0");'.
(on windows it only gets called once right now)
done: optional function that is called when the process has terminated.
The exit code from the process available via result.exitCode. If
stdout is not defined, then the output from stdout is available
via result.stdout. stderr data is in result.stderr
mergeStderr: optional boolean value. If true, stderr is merged with stdout;
no data will be provided to stderr.
Description of object returned by subprocess.call(...)
------------------------------------------------------
The object returned by subprocess.call offers a few methods that can be
executed:
wait(): waits for the subprocess to terminate. It is not required to use
wait; done will be called in any case when the subprocess terminated.
kill(hardKill): kill the subprocess. Any open pipes will be closed and
done will be called.
hardKill [ignored on Windows]:
- false: signal the process terminate (SIGTERM)
- true: kill the process (SIGKILL)
Other methods in subprocess
---------------------------
registerDebugHandler(functionRef): register a handler that is called to get
debugging information
registerLogHandler(functionRef): register a handler that is called to get error
messages
example:
subprocess.registerLogHandler( function(s) { dump(s); } );
<h2>Credits:</h2>
All enigmail team working on IPC component.
The Initial Developer of this code is Jan Gerber.
Portions created by Jan Gerber <j@mailb.org>,
Patrick Brunschwig (author of almost all code) <patrick@mozilla-enigmail.org>,
Ramalingam Saravanan (from enigmail team) <svn@xmlterm.org>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,248 @@
/* 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/.
*/
/*
* ChromeWorker Object subprocess.jsm on Unix-like systems (Linux, Mac OS X, ...)
* to process stdin/stdout/stderr on separate threads.
*
*/
// Being a ChromeWorker object, implicitly uses the following:
// Components.utils.import("resource://gre/modules/ctypes.jsm");
'use strict';
const BufferSize = 1024;
var libc = null;
var libcFunc = {};
/*
struct pollfd {
int fd; // file descriptor
short events; // events to look for
short revents; // events returned
};
*/
var pollfd = new ctypes.StructType("pollfd",
[ {'fd': ctypes.int},
{'events': ctypes.short},
{'revents': ctypes.short}
]);
var WriteBuffer = ctypes.uint8_t.array(BufferSize);
var ReadBuffer = ctypes.char.array(BufferSize);
const POLLIN = 0x0001;
const POLLOUT = 0x0004;
const POLLERR = 0x0008; // some poll error occurred
const POLLHUP = 0x0010; // file descriptor was "hung up"
const POLLNVAL = 0x0020; // requested events "invalid"
const WNOHANG = 0x01;
const pid_t = ctypes.int32_t;
const INDEFINITE = -1;
const NOWAIT = 0;
const WAITTIME = 200 // wait time for poll() in ms
function initLibc(libName) {
postMessage({msg: "debug", data: "initialising library with "+ libName});
libc = ctypes.open(libName);
libcFunc.pollFds = pollfd.array(1);
// int poll(struct pollfd fds[], nfds_t nfds, int timeout);
libcFunc.poll = libc.declare("poll",
ctypes.default_abi,
ctypes.int,
libcFunc.pollFds,
ctypes.unsigned_int,
ctypes.int);
//ssize_t write(int fd, const void *buf, size_t count);
// NOTE: buf is declared as array of unsigned int8 instead of char to avoid
// implicit charset conversion
libcFunc.write = libc.declare("write",
ctypes.default_abi,
ctypes.int,
ctypes.int,
WriteBuffer,
ctypes.int);
//int read(int fd, void *buf, size_t count);
libcFunc.read = libc.declare("read",
ctypes.default_abi,
ctypes.int,
ctypes.int,
ReadBuffer,
ctypes.int);
//int pipe(int pipefd[2]);
libcFunc.pipefd = ctypes.int.array(2);
//int close(int fd);
libcFunc.close = libc.declare("close",
ctypes.default_abi,
ctypes.int,
ctypes.int);
//pid_t waitpid(pid_t pid, int *status, int options);
libcFunc.waitpid = libc.declare("waitpid",
ctypes.default_abi,
pid_t,
pid_t,
ctypes.int.ptr,
ctypes.int);
}
function closePipe(pipe) {
libcFunc.close(pipe);
}
function writePipe(pipe, data) {
postMessage({msg: "debug", data: "trying to write to "+pipe});
let numChunks = Math.floor(data.length / BufferSize);
let pData = new WriteBuffer();
for (var chunk = 0; chunk <= numChunks; chunk ++) {
let numBytes = chunk < numChunks ? BufferSize : data.length - chunk * BufferSize;
for (var i=0; i < numBytes; i++) {
pData[i] = data.charCodeAt(chunk * BufferSize + i) % 256;
}
let bytesWritten = libcFunc.write(pipe, pData, numBytes);
if (bytesWritten != numBytes) {
closePipe();
libc.close();
postMessage({ msg: "error", data: "error: wrote "+bytesWritten+" instead of "+numBytes+" bytes"});
close();
}
}
postMessage({msg: "info", data: "wrote "+data.length+" bytes of data"});
}
function readString(data, length, charset) {
var string = '', bytes = [];
for(var i = 0;i < length; i++) {
if(data[i] == 0 && charset != "null") // stop on NULL character for non-binary data
break;
bytes.push(data[i]);
}
return bytes;
}
function readPipe(pipe, charset, pid) {
var p = new libcFunc.pollFds;
p[0].fd = pipe;
p[0].events = POLLIN | POLLERR | POLLHUP;
p[0].revents = 0;
var pollTimeout = WAITTIME;
var exitCode = -1;
var readCount = 0;
var result, status = ctypes.int();
result = 0;
const i=0;
while (true) {
if (result == 0) {
result = libcFunc.waitpid(pid, status.address(), WNOHANG);
if (result > 0) {
pollTimeout = NOWAIT;
exitCode = parseInt(status.value);
postMessage({msg: "debug", data: "waitpid signaled subprocess stop, exitcode="+status.value });
}
}
var r = libcFunc.poll(p, 1, pollTimeout);
if (r > 0) {
if (p[i].revents & POLLIN) {
postMessage({msg: "debug", data: "reading next chunk"});
readCount = readPolledFd(p[i].fd, charset);
if (readCount == 0) break;
}
if (p[i].revents & POLLHUP) {
postMessage({msg: "debug", data: "poll returned HUP"});
break;
}
else if (p[i].revents & POLLERR) {
postMessage({msg: "error", data: "poll returned error"});
break;
}
else if (p[i].revents != POLLIN) {
postMessage({msg: "error", data: "poll returned "+p[i]});
break;
}
}
else
if (pollTimeout == 0 || r < 0) break;
}
// continue reading until the buffer is empty
while (readCount > 0) {
readCount = readPolledFd(pipe, charset);
}
libcFunc.close(pipe);
postMessage({msg: "done", data: exitCode });
libc.close();
close();
}
function readPolledFd(pipe, charset) {
var line = new ReadBuffer();
var r = libcFunc.read(pipe, line, BufferSize);
if (r > 0) {
var c = readString(line, r, charset);
postMessage({msg: "data", data: c, count: c.length});
}
return r;
}
onmessage = function (event) {
switch (event.data.msg) {
case "init":
initLibc(event.data.libc);
break;
case "read":
initLibc(event.data.libc);
readPipe(event.data.pipe, event.data.charset, event.data.pid);
break;
case "write":
// data contents:
// msg: 'write'
// data: the data (string) to write
// pipe: ptr to pipe
writePipe(event.data.pipe, event.data.data);
postMessage({msg: "info", data: "WriteOK"});
break;
case "close":
postMessage({msg: "debug", data: "closing stdin\n"});
closePipe(event.data.pipe);
postMessage({msg: "info", data: "ClosedOK"});
break;
case "stop":
libc.close(); // do not use libc after this point
close();
break;
default:
throw("error: Unknown command"+event.data.msg+"\n");
}
return;
};

View File

@ -0,0 +1,206 @@
/* 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/.
*/
/*
* ChromeWorker Object subprocess.jsm on Windows to process stdin/stdout/stderr
* on separate threads.
*
*/
// Being a ChromeWorker object, implicitly uses the following:
// Components.utils.import("resource://gre/modules/ctypes.jsm");
'use strict';
const BufferSize = 1024;
const BOOL = ctypes.bool;
const HANDLE = ctypes.size_t;
const DWORD = ctypes.uint32_t;
const LPDWORD = DWORD.ptr;
const PVOID = ctypes.voidptr_t;
const LPVOID = PVOID;
/*
typedef struct _OVERLAPPED {
ULONG_PTR Internal;
ULONG_PTR InternalHigh;
union {
struct {
DWORD Offset;
DWORD OffsetHigh;
};
PVOID Pointer;
};
HANDLE hEvent;
} OVERLAPPED, *LPOVERLAPPED;
*/
const OVERLAPPED = new ctypes.StructType("OVERLAPPED");
var ReadFileBuffer = ctypes.char.array(BufferSize);
var WriteFileBuffer = ctypes.uint8_t.array(BufferSize);
var kernel32dll = null;
var libFunc = {};
function initLib(libName) {
if (ctypes.size_t.size == 8) {
var WinABI = ctypes.default_abi;
} else {
var WinABI = ctypes.winapi_abi;
}
kernel32dll = ctypes.open(libName);
/*
BOOL WINAPI WriteFile(
__in HANDLE hFile,
__in LPCVOID lpBuffer,
__in DWORD nNumberOfBytesToWrite,
__out_opt LPDWORD lpNumberOfBytesWritten,
__inout_opt LPOVERLAPPED lpOverlapped
);
NOTE: lpBuffer is declared as array of unsigned int8 instead of char to avoid
implicit charset conversion
*/
libFunc.WriteFile = kernel32dll.declare("WriteFile",
WinABI,
BOOL,
HANDLE,
WriteFileBuffer,
DWORD,
LPDWORD,
OVERLAPPED.ptr
);
/*
BOOL WINAPI ReadFile(
__in HANDLE hFile,
__out LPVOID ReadFileBuffer,
__in DWORD nNumberOfBytesToRead,
__out_opt LPDWORD lpNumberOfBytesRead,
__inout_opt LPOVERLAPPED lpOverlapped
);
*/
libFunc.ReadFile = kernel32dll.declare("ReadFile",
WinABI,
BOOL,
HANDLE,
ReadFileBuffer,
DWORD,
LPDWORD,
OVERLAPPED.ptr
);
/*
BOOL WINAPI CloseHandle(
__in HANDLE hObject
);
*/
libFunc.CloseHandle = kernel32dll.declare("CloseHandle",
WinABI,
BOOL,
HANDLE
);
}
function writePipe(pipe, data) {
var bytesWritten = DWORD(0);
var pData = new WriteFileBuffer();
var numChunks = Math.floor(data.length / BufferSize);
for (var chunk = 0; chunk <= numChunks; chunk ++) {
var numBytes = chunk < numChunks ? BufferSize : data.length - chunk * BufferSize;
for (var i=0; i < numBytes; i++) {
pData[i] = data.charCodeAt(chunk * BufferSize + i) % 256;
}
var r = libFunc.WriteFile(pipe, pData, numBytes, bytesWritten.address(), null);
if (bytesWritten.value != numBytes)
throw("error: wrote "+bytesWritten.value+" instead of "+numBytes+" bytes");
}
postMessage("wrote "+data.length+" bytes of data");
}
function readString(data, length, charset) {
var string = '', bytes = [];
for(var i = 0;i < length; i++) {
if(data[i] == 0 && charset != "null") // stop on NULL character for non-binary data
break;
bytes.push(data[i]);
}
return bytes;
}
function readPipe(pipe, charset) {
while (true) {
var bytesRead = DWORD(0);
var line = new ReadFileBuffer();
var r = libFunc.ReadFile(pipe, line, BufferSize, bytesRead.address(), null);
if (!r) {
// stop if we get an error (such as EOF reached)
postMessage({msg: "info", data: "ReadFile failed"});
break;
}
if (bytesRead.value > 0) {
var c = readString(line, bytesRead.value, charset);
postMessage({msg: "data", data: c, count: c.length});
}
else {
break;
}
}
libFunc.CloseHandle(pipe);
postMessage({msg: "done"});
kernel32dll.close();
close();
}
onmessage = function (event) {
let pipePtr;
switch (event.data.msg) {
case "init":
initLib(event.data.libc);
break;
case "write":
// data contents:
// msg: 'write'
// data: the data (string) to write
// pipe: ptr to pipe
pipePtr = HANDLE.ptr(event.data.pipe);
writePipe(pipePtr.contents, event.data.data);
postMessage("WriteOK");
break;
case "read":
initLib(event.data.libc);
pipePtr = HANDLE.ptr(event.data.pipe);
readPipe(pipePtr.contents, event.data.charset);
break;
case "close":
pipePtr = HANDLE.ptr(event.data.pipe);
postMessage("closing stdin\n");
if (libFunc.CloseHandle(pipePtr.contents)) {
postMessage("ClosedOK");
}
else
postMessage("Could not close stdin handle");
break;
case "stop":
kernel32dll.close();
close();
break;
default:
throw("error: Unknown command"+event.data.msg+"\n");
}
return;
};

View File

@ -0,0 +1,15 @@
{
"name": "subprocess",
"license": "MPL 1.1/GPL 2.0/LGPL 2.1",
"author": "Alexandre Poirot",
"contributors": [
"Jan Gerber (original creator) <j@mailb.org>",
"Patrick Brunschwig (author of almost all code) <patrick@mozilla-enigmail.org>",
"Ramalingam Saravanan (from enigmail team) <svn@xmlterm.org>"
],
"version": "0.1.1",
"dependencies": [
"api-utils"
],
"description": "Addon-sdk package for subprocess xpcom components from enigmail. Allow to run process, manipulate stdin/out and kill it."
}

View File

@ -0,0 +1,144 @@
/* 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/.
*/
const { Cc, Ci } = require("chrome");
const subprocess = require("subprocess");
const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
// For now, only test on windows
if (env.get('OS') && env.get('OS').match(/Windows/)) {
exports.testWindows = function (test) {
test.waitUntilDone();
let envTestValue = "OK";
let gotStdout = false;
var p = subprocess.call({
// Retrieve windows cmd.exe path from env
command: env.get('ComSpec'),
// In order to execute a simple "echo" function
arguments: ['/C', 'echo %ENV_TEST%'], // ' & type CON' should display stdin, but doesn't work
// Printing an environnement variable set here by the parent process
environment: ['ENV_TEST='+envTestValue],
stdin: function(stdin) {
// Win32 command line is not really made for stdin
// So it doesn't seems to work as it's hard to retrieve stdin
stdin.write("stdin");
stdin.close();
},
stdout: function(data) {
test.assert(!gotStdout,"don't get stdout twice");
test.assertEqual(data,envTestValue+"\r\n","stdout contains the environment variable");
gotStdout = true;
},
stderr: function(data) {
test.fail("shouldn't get stderr");
},
done: function() {
test.assert(gotStdout, "got stdout before finished");
test.done();
},
mergeStderr: false
});
}
exports.testWindowsStderr = function (test) {
test.waitUntilDone();
let gotStderr = false;
var p = subprocess.call({
command: env.get('ComSpec'),
arguments: ['/C', 'nonexistent'],
stdout: function(data) {
test.fail("shouldn't get stdout");
},
stderr: function(data) {
test.assert(!gotStderr,"don't get stderr twice");
test.assertEqual(
data,
"'nonexistent' is not recognized as an internal or external command,\r\n" +
"operable program or batch file.\r\n",
"stderr contains the error message"
);
gotStderr = true;
},
done: function() {
test.assert(gotStderr, "got stderr before finished");
test.done();
},
mergeStderr: false
});
}
}
if (env.get('USER') && env.get('SHELL')) {
exports.testUnix = function (test) {
test.waitUntilDone();
let envTestValue = "OK";
let gotStdout = false;
var p = subprocess.call({
command: '/bin/sh',
// Print stdin and our env variable
//arguments: ['-c', 'echo $@ $ENV_TEST'],
environment: ['ENV_TEST='+envTestValue],
stdin: function(stdin) {
stdin.write("echo $ENV_TEST");
stdin.close();
},
stdout: function(data) {
test.assert(!gotStdout,"don't get stdout twice");
test.assertEqual(data,envTestValue+"\n","stdout contains the environment variable");
gotStdout = true;
},
stderr: function(data) {
test.fail("shouldn't get stderr");
},
done: function() {
test.assert(gotStdout, "got stdout before finished");
test.done();
},
mergeStderr: false
});
}
exports.testUnixStderr = function (test) {
test.waitUntilDone();
let gotStderr = false;
var p = subprocess.call({
// Hope that we don't have to give absolute path on linux ...
command: '/bin/sh',
arguments: ['nonexistent'],
stdout: function(data) {
test.fail("shouldn't get stdout");
},
stderr: function(data) {
test.assert(!gotStderr,"don't get stderr twice");
// There is two variant of error message
if (data == "/bin/sh: 0: Can't open nonexistent\n")
test.pass("stderr containes the expected error message");
else
test.assertEqual(data, "/bin/sh: nonexistent: No such file or directory\n",
"stderr contains the error message");
gotStderr = true;
},
done: function() {
test.assert(gotStderr, "got stderr before finished");
test.done();
},
mergeStderr: false
});
}
}

View File

@ -164,11 +164,20 @@ if test -z "$PERL" -o "$PERL" = ":"; then
AC_MSG_ERROR([perl not found in \$PATH])
fi
if test -n "$GAIADIR" -a ! -d "$GAIADIR" ; then
AC_MSG_ERROR([GAIADIR '$GAIADIR' isn't a valid directory])
fi
AC_SUBST(GAIADIR)
if test -n "$GAIADIR" ; then
AC_DEFINE(PACKAGE_GAIA)
fi
if test -n "$FXOS_SIMULATOR" -a -z "$GAIADIR" ; then
AC_MSG_ERROR([FXOS_SIMULATOR=1 requires GAIADIR to be defined])
fi
AC_SUBST(FXOS_SIMULATOR)
MOZ_ARG_WITH_STRING(gonk,
[ --with-gonk=DIR
location of gonk dir],
@ -4160,6 +4169,7 @@ AC_SUBST(MOZ_BUILD_APP)
AC_SUBST(MOZ_PHOENIX)
AC_SUBST(MOZ_XULRUNNER)
AC_SUBST(MOZ_B2G)
AC_SUBST(MOZ_B2G_VERSION)
AC_DEFINE_UNQUOTED(MOZ_BUILD_APP,$MOZ_BUILD_APP)