aboutsummaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rwxr-xr-xtools/bindings_generator.py55
-rw-r--r--tools/bisect_pair_lines.py63
-rw-r--r--tools/eliminator/asm-eliminator-test-output.js129
-rw-r--r--tools/eliminator/asm-eliminator-test.js94
-rwxr-xr-xtools/ffdb.py453
-rw-r--r--tools/js-optimizer.js396
-rw-r--r--tools/js_optimizer.py28
-rw-r--r--tools/jsrun.py9
-rw-r--r--tools/shared.py189
-rw-r--r--tools/system_libs.py65
-rw-r--r--tools/test-js-optimizer-asm-last-output.js27
-rw-r--r--tools/test-js-optimizer-asm-last.js33
-rw-r--r--tools/test-js-optimizer-asm-pre-f32.js8
-rw-r--r--tools/test-js-optimizer-asm-pre-output-f32.js8
-rw-r--r--tools/test-js-optimizer-asm-pre-output.js78
-rw-r--r--tools/test-js-optimizer-asm-pre.js79
-rw-r--r--tools/test-js-optimizer-asm-regs-harder-output.js5
-rw-r--r--tools/test-js-optimizer-asm-regs-harder.js8
-rw-r--r--tools/webidl_binder.py433
19 files changed, 1945 insertions, 215 deletions
diff --git a/tools/bindings_generator.py b/tools/bindings_generator.py
index 5bf0996e..9bc8b929 100755
--- a/tools/bindings_generator.py
+++ b/tools/bindings_generator.py
@@ -1,6 +1,61 @@
#!/usr/bin/env python2
'''
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+XXX THIS IS DEPRECATED, see webidl_binder.py XXX
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Use CppHeaderParser to parse some C++ headers, and generate binding code for them.
Usage:
diff --git a/tools/bisect_pair_lines.py b/tools/bisect_pair_lines.py
new file mode 100644
index 00000000..f698ef2a
--- /dev/null
+++ b/tools/bisect_pair_lines.py
@@ -0,0 +1,63 @@
+'''
+Given two similar files, for example one with an additional optimization pass,
+and with different results, will bisect between them to find the smallest
+diff that makes the outputs different.
+Unlike bisect_pairs, this uses lines instead of diffs. We replace line by line. This assumes
+the programs differ on each line but lines have not been added or removed
+'''
+
+import os, sys, shutil
+from subprocess import Popen, PIPE, STDOUT
+
+__rootpath__ = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
+def path_from_root(*pathelems):
+ return os.path.join(__rootpath__, *pathelems)
+exec(open(path_from_root('tools', 'shared.py'), 'r').read())
+
+file1 = open(sys.argv[1]).read()
+file2 = open(sys.argv[2]).read()
+
+leftf = open('left', 'w')
+leftf.write(file1)
+leftf.close()
+
+rightf = open('right', 'w')
+rightf.write(file2)
+rightf.close()
+
+def run_code(name):
+ ret = run_js(name, stderr=PIPE, full_output=True)
+ # fix stack traces
+ ret = filter(lambda line: not line.startswith(' at ') and not name in line, ret.split('\n'))
+ return '\n'.join(ret)
+
+print 'running files'
+left_result = run_code('left')
+right_result = run_code('right') # right as in left-right, not as in correct
+assert left_result != right_result
+
+low = 0
+high = file1.count('\n')
+
+print 'beginning bisection, %d lines' % high
+
+left_lines = file1.split('\n')
+right_lines = file2.split('\n')
+
+while True:
+ mid = int((low + high)/2)
+ print low, high, ' current: %d' % mid,
+ open('middle', 'w').write('\n'.join(left_lines[:mid] + right_lines[mid:]))
+ shutil.copyfile('middle', 'middle' + str(mid))
+ result = run_code('middle')
+ print result == left_result, result == right_result#, 'XXX', left_result, 'YYY', result, 'ZZZ', right_result
+ if mid == low or mid == high: break
+ if result == right_result:
+ low = mid
+ elif result == left_result:
+ high = mid
+ else:
+ raise Exception('new result!?!?')
+
+print 'middle%d is like left, middle%d is like right' % (mid+1, mid)
+
diff --git a/tools/eliminator/asm-eliminator-test-output.js b/tools/eliminator/asm-eliminator-test-output.js
index 9caf99d0..ab4c13cc 100644
--- a/tools/eliminator/asm-eliminator-test-output.js
+++ b/tools/eliminator/asm-eliminator-test-output.js
@@ -24,7 +24,7 @@ function __Z11printResultPiS_j($needle, $haystack, $len) {
}
function _segment_holding($addr) {
$addr = $addr | 0;
- var $sp_0 = 0, $3 = 0, $12 = 0, $_0 = 0, label = 0;
+ var $sp_0 = 0, $3 = 0, $_0 = 0, label = 0;
$sp_0 = __gm_ + 444 | 0;
while (1) {
$3 = HEAP32[(($sp_0 | 0) & 16777215) >> 2] | 0;
@@ -35,13 +35,11 @@ function _segment_holding($addr) {
break;
}
}
- $12 = HEAP32[(($sp_0 + 8 | 0) & 16777215) >> 2] | 0;
- if (($12 | 0) == 0) {
+ $sp_0 = HEAP32[(($sp_0 + 8 | 0) & 16777215) >> 2] | 0;
+ if (($sp_0 | 0) == 0) {
$_0 = 0;
label = 1659;
break;
- } else {
- $sp_0 = $12;
}
}
if (label == 1659) {
@@ -147,27 +145,25 @@ function looop3() {
}
}
function looop4() {
- var i = 0, helper = 0;
+ var i = 0, i$looptemp = 0;
while (1) {
do_it();
- helper = i + 1 | 0;
- f(i, helper);
- if (condition()) {
- i = helper;
- } else {
+ i$looptemp = i;
+ i = i + 1 | 0;
+ f(i$looptemp, i);
+ if (!condition()) {
break;
}
}
}
function looop4b() {
- var i = 0, helper = 0;
+ var i = 0, i$looptemp = 0;
while (1) {
do_it();
- helper = i + 1 | 0;
- g(helper);
- if (condition(i)) {
- i = helper;
- } else {
+ i$looptemp = i;
+ i = i + 1 | 0;
+ g(i);
+ if (!condition(i$looptemp)) {
break;
}
}
@@ -251,24 +247,22 @@ function multiloop($n_0, $35) {
function multiloop2($n_0, $35) {
$n_0 = $n_0 | 0;
$35 = $35 | 0;
- var $p_0 = 0, $39 = 0, $41 = 0, $46 = 0;
+ var $p_0 = 0, $41 = 0, $p_0$looptemp = 0;
$n_0 = $35;
$p_0 = (HEAP32[$15 >> 2] | 0) + ($35 << 1) | 0;
while (1) {
- $39 = $p_0 - 2 | 0;
- $41 = HEAPU16[$39 >> 1] | 0;
+ $p_0$looptemp = $p_0;
+ $p_0 = $p_0 - 2 | 0;
+ $41 = HEAPU16[$p_0 >> 1] | 0;
if ($41 >>> 0 < $2 >>> 0) {
$_off0 = 0;
} else {
$_off0 = $41 - $2 & 65535;
}
- HEAP16[$39 >> 1] = $p_0;
- $46 = $n_0 - 1 | 0;
- if (($46 | 0) == 0) {
+ HEAP16[$p_0 >> 1] = $p_0$looptemp;
+ $n_0 = $n_0 - 1 | 0;
+ if (($n_0 | 0) == 0) {
break;
- } else {
- $n_0 = $46;
- $p_0 = $39;
}
}
}
@@ -822,7 +816,7 @@ function selfAssign() {
function elimOneLoopVar($argc, $argv) {
$argc = $argc | 0;
$argv = $argv | 0;
- var $arg$0 = 0, $call10 = Math_fround(0), $curri$012 = 0, $inc = 0, $j$010 = 0, $ok$0 = 0, $primes$011 = 0, $retval$0 = 0, $vararg_buffer1 = 0;
+ var $arg$0 = 0, $call10 = Math_fround(0), $curri$012 = 0, $j$010 = 0, $ok$0 = 0, $primes$011 = 0, $retval$0 = 0, $vararg_buffer1 = 0;
$curri$012 = 2;
$primes$011 = 0;
while (1) {
@@ -831,14 +825,12 @@ function elimOneLoopVar($argc, $argv) {
if ($call10 > Math_fround(+2)) {
$j$010 = 2;
while (1) {
- $inc = $j$010 + 1 | 0;
if ((($curri$012 | 0) % ($j$010 | 0) & -1 | 0) == 0) {
$ok$0 = 0;
break L15;
}
- if (Math_fround($inc | 0) < $call10) {
- $j$010 = $inc;
- } else {
+ $j$010 = $j$010 + 1 | 0;
+ if (!(Math_fround($j$010 | 0) < $call10)) {
$ok$0 = 1;
break;
}
@@ -901,4 +893,79 @@ function elimOneLoopVar4() {
}
}
}
+function elimOneLoopVarStillUsed() {
+ var $call10 = Math_fround(0), $curri$012 = 0, $j$010 = 0, $retval$0 = 0;
+ while (1) {
+ if ((($curri$012 | 0) % ($j$010 | 0) & -1 | 0) == 0) {
+ break;
+ }
+ $j$010 = $j$010 + 1 | 0;
+ if (!(Math_fround($j$010 | 0) < $call10)) {
+ break;
+ }
+ }
+ return $retval$0 | 0;
+}
+function elimOneLoopVarStillUsedSE() {
+ var $call10 = Math_fround(0), $curri$012 = 0, $j$010 = 0, $retval$0 = 0, $j$010$looptemp = 0;
+ while (1) {
+ $j$010$looptemp = $j$010;
+ $j$010 = $j$010 + sideeffect() | 0;
+ if ((($curri$012 | 0) % ($j$010$looptemp | 0) & -1 | 0) == 0) {
+ break;
+ }
+ if (!(Math_fround($j$010 | 0) < $call10)) {
+ break;
+ }
+ }
+ return $retval$0 | 0;
+}
+function elimOneLoopVar5() {
+ var $storemerge3$neg9 = 0, $18 = 0, $25 = 0, $26 = 0, $30 = 0, $jp = 0;
+ $storemerge3$neg9 = -1;
+ while (1) {
+ $25 = $jp + ($26 << 2) | 0;
+ HEAP32[$25 >> 2] = ($18 + $storemerge3$neg9 | 0) + (HEAP32[$25 >> 2] | 0) | 0;
+ $30 = $26 + 1 | 0;
+ if (($30 | 0) == 63) {
+ f($30);
+ break;
+ } else {
+ $storemerge3$neg9 = $18 ^ -1;
+ $26 = $30;
+ }
+ }
+}
+function loopVarWithContinue() {
+ var i = 0, i$looptemp = 0;
+ i = 0;
+ while (1) {
+ i$looptemp = i;
+ i = i + 1;
+ if (check()) {
+ i = i$looptemp + 1;
+ continue;
+ }
+ work(i);
+ work(i$looptemp);
+ work(i);
+ if (check()) {
+ break;
+ }
+ }
+}
+function helperExtraUse() {
+ var i = 0, i$looptemp = 0;
+ i = 0;
+ while (1) {
+ i$looptemp = i;
+ i = i + 1;
+ work(i$looptemp);
+ work(i);
+ if (check()) {
+ break;
+ }
+ }
+ return i;
+}
diff --git a/tools/eliminator/asm-eliminator-test.js b/tools/eliminator/asm-eliminator-test.js
index a3de3d9d..7b949c44 100644
--- a/tools/eliminator/asm-eliminator-test.js
+++ b/tools/eliminator/asm-eliminator-test.js
@@ -1131,5 +1131,97 @@ function elimOneLoopVar4() {
}
}
}
-// EMSCRIPTEN_GENERATED_FUNCTIONS: ["asm", "__Z11printResultPiS_j", "_segment_holding", "__ZN5identC2EiPKcPci", "_vec2Length", "exc", "label", "confuusion", "tempDouble", "_org_apache_harmony_luni_util_NumberConverter_freeFormat__", "__ZN23b2EdgeAndPolygonContact8EvaluateEP10b2ManifoldRK11b2TransformS4_", "_java_nio_charset_Charset_forNameInternal___java_lang_String", "looop2", "looop3", "looop4", "looop5", "looop6", "looop7", "looop8", "multiloop", "multiloop2", "tempDouble2", "watIf", "select2", "binary", "cute", "selfAssign", "elimOneLoopVar", "elimOneLoopVar2", "elimOneLoopVar3", "elimOneLoopVar4"]
+function elimOneLoopVarStillUsed() {
+ var $0 = 0, $1 = 0, $arg$0 = 0, $arrayidx = 0, $call10 = Math_fround(0), $cmp = 0, $cmp11 = 0, $cmp119 = 0, $cmp12 = 0, $cmp7 = 0, $conv = 0, $conv8 = Math_fround(0), $conv9 = Math_fround(0), $curri$012 = 0, $inc = 0, $inc14$primes$0 = 0, $inc16 = 0, $j$010 = 0, $ok$0 = 0;
+ var $primes$011 = 0, $rem = 0, $retval$0 = 0, $sub = 0, $vararg_buffer1 = 0, label = 0, sp = 0;
+ while (1) {
+ $rem = ($curri$012 | 0) % ($j$010 | 0) & -1;
+ $cmp12 = ($rem | 0) == 0;
+ $inc = $j$010 + 1 | 0;
+ if ($cmp12) {
+ $ok$0 = 0;
+ break;
+ }
+ $conv8 = Math_fround($inc | 0);
+ $cmp11 = $conv8 < $call10;
+ if ($cmp11) {
+ $j$010 = $inc;
+ } else {
+ break;
+ }
+ }
+ return $retval$0 | 0;
+}
+function elimOneLoopVarStillUsedSE() {
+ var $0 = 0, $1 = 0, $arg$0 = 0, $arrayidx = 0, $call10 = Math_fround(0), $cmp = 0, $cmp11 = 0, $cmp119 = 0, $cmp12 = 0, $cmp7 = 0, $conv = 0, $conv8 = Math_fround(0), $conv9 = Math_fround(0), $curri$012 = 0, $inc = 0, $inc14$primes$0 = 0, $inc16 = 0, $j$010 = 0, $ok$0 = 0;
+ var $primes$011 = 0, $rem = 0, $retval$0 = 0, $sub = 0, $vararg_buffer1 = 0, label = 0, sp = 0;
+ while (1) {
+ $rem = ($curri$012 | 0) % ($j$010 | 0) & -1;
+ $cmp12 = ($rem | 0) == 0;
+ $inc = $j$010 + sideeffect() | 0; // side effect!
+ if ($cmp12) {
+ $ok$0 = 0;
+ break;
+ }
+ $conv8 = Math_fround($inc | 0);
+ $cmp11 = $conv8 < $call10;
+ if ($cmp11) {
+ $j$010 = $inc;
+ } else {
+ break;
+ }
+ }
+ return $retval$0 | 0;
+}
+function elimOneLoopVar5() {
+ var $storemerge3$neg9 = 0, $18 = 0, $25 = 0, $26 = 0, $30 = 0, $jp = 0;
+ $storemerge3$neg9 = -1;
+ while (1) {
+ $25 = $jp + ($26 << 2) | 0;
+ HEAP32[$25 >> 2] = ($18 + $storemerge3$neg9 | 0) + (HEAP32[$25 >> 2] | 0) | 0;
+ $30 = $26 + 1 | 0;
+ if (($30 | 0) == 63) {
+ f($30); // loop var used here, so cannot be easily optimized
+ break;
+ } else {
+ $storemerge3$neg9 = $18 ^ -1;
+ $26 = $30;
+ }
+ }
+}
+function loopVarWithContinue() {
+ var i = 0, inc = 0;
+ i = 0;
+ while (1) {
+ inc = i + 1;
+ if (check()) {
+ i = i + 1;
+ continue;
+ }
+ work(inc);
+ work(i);
+ work(inc);
+ if (check()) {
+ break;
+ } else {
+ i = inc;
+ }
+ }
+}
+function helperExtraUse() {
+ var i = 0, inc = 0;
+ i = 0;
+ while (1) {
+ inc = i + 1;
+ work(i);
+ work(inc);
+ if (check()) {
+ break;
+ } else {
+ i = inc;
+ }
+ }
+ return inc;
+}
+// EMSCRIPTEN_GENERATED_FUNCTIONS: ["asm", "__Z11printResultPiS_j", "_segment_holding", "__ZN5identC2EiPKcPci", "_vec2Length", "exc", "label", "confuusion", "tempDouble", "_org_apache_harmony_luni_util_NumberConverter_freeFormat__", "__ZN23b2EdgeAndPolygonContact8EvaluateEP10b2ManifoldRK11b2TransformS4_", "_java_nio_charset_Charset_forNameInternal___java_lang_String", "looop2", "looop3", "looop4", "looop5", "looop6", "looop7", "looop8", "multiloop", "multiloop2", "tempDouble2", "watIf", "select2", "binary", "cute", "selfAssign", "elimOneLoopVar", "elimOneLoopVar2", "elimOneLoopVar3", "elimOneLoopVar4", "elimOneLoopVarStillUsed", "elimOneLoopVarStillUsedSE", "elimOneLoopVar5", "helperExtraUse"]
diff --git a/tools/ffdb.py b/tools/ffdb.py
new file mode 100755
index 00000000..2171cb0e
--- /dev/null
+++ b/tools/ffdb.py
@@ -0,0 +1,453 @@
+#!/usr/bin/env python
+
+import socket, json, sys, uuid, datetime, time, logging, cgi, zipfile, os, tempfile, atexit, subprocess, re, base64, struct, imghdr
+
+LOG_VERBOSE = False # Verbose printing enabled with --verbose
+HOST = 'localhost' # The remote host to connect to the B2G device
+PORT = 6000 # The port on the host on which the B2G device listens on
+b2g_socket = None # Python socket object for the active connection to the B2G device
+read_queue = '' # Inbound queue of partial data read so far from the device
+
+webappsActorName = None
+
+def sizeof_fmt(num):
+ for x in ['bytes','KB','MB','GB']:
+ if num < 1024.0:
+ return "%3.1f%s" % (num, x)
+ num /= 1024.0
+ return "%3.1f%s" % (num, 'TB')
+
+def zipdir(path, zipfilename):
+ try:
+ import zlib
+ zip_mode = zipfile.ZIP_DEFLATED
+ except:
+ zip_mode = zipfile.ZIP_STORED
+
+ zipf = zipfile.ZipFile(zipfilename, 'w', zip_mode)
+ files_to_compress = []
+ for root, dirs, files in os.walk(path):
+ for file in files:
+ files_to_compress += [(root, file)]
+
+ n = 1
+ for tuple in files_to_compress:
+ (root, file) = tuple
+ filename = os.path.join(root, file)
+ filesize = os.path.getsize(filename)
+ path_in_archive = os.path.relpath(filename, path)
+ print 'Compressing ' + str(n) + '/' + str(len(files_to_compress)) + ': "' + path_in_archive + '" (' + sizeof_fmt(filesize) + ')...'
+ n += 1
+ zipf.write(os.path.join(root, file), path_in_archive)
+ zipf.close()
+ print 'Done. '
+
+# Returns given log message formatted to be outputted on a HTML page.
+def format_html(msg):
+ if not msg.endswith('\n'):
+ msg += '\n'
+ msg = cgi.escape(msg)
+ msg = msg.replace('\r\n', '<br />').replace('\n', '<br />')
+ return msg
+
+# Prints a verbose log message to stdout channel. Only shown if run with --verbose.
+def logv(msg):
+ if LOG_VERBOSE:
+ sys.stdout.write(format_html(msg) + '\n')
+ sys.stdout.flush()
+
+# Reads data from the socket, and tries to parse what we have got so far as a JSON message.
+# The messages are of form "bytelength:{jsondict}", where bytelength tells how many bytes
+# there are in the data that comes after the colon.
+# Returns a JSON dictionary of the received message.
+def read_b2g_response(print_errors_to_console = True):
+ global read_queue, b2g_socket
+ read_queue += b2g_socket.recv(65536*2)
+ payload = ''
+ while ':' in read_queue:
+ semicolon = read_queue.index(':')
+ payload_len = int(read_queue[:semicolon])
+ if semicolon+1+payload_len > len(read_queue):
+ read_queue += b2g_socket.recv(65536*2)
+ continue
+ payload = read_queue[semicolon+1:semicolon+1+payload_len]
+ read_queue = read_queue[semicolon+1+payload_len:]
+ logv('Read a message of size ' + str(payload_len) + 'b from socket.')
+ payload = json.loads(payload)
+ # Log received errors immediately to console
+ if print_errors_to_console and 'error' in payload:
+ print >> sys.stderr, 'Received error "' + payload['error'] + '"! Reason: ' + payload['message']
+ return payload
+
+# Sends a command to the B2G device and waits for the response and returns it as a JSON dict.
+def send_b2g_cmd(to, cmd, data = {}, print_errors_to_console = True):
+ global b2g_socket
+ msg = { 'to': to, 'type': cmd}
+ msg = dict(msg.items() + data.items())
+ msg = json.dumps(msg, encoding='latin-1')
+ msg = msg.replace('\\\\', '\\')
+ msg = str(len(msg))+':'+msg
+ logv('Sending cmd:' + cmd + ' to:' + to)
+ b2g_socket.sendall(msg)
+ return read_b2g_response(print_errors_to_console)
+
+def escape_bytes(b):
+ return str(b)
+
+# Sends a data fragment of a packaged app upload. This is a special-case version of the send_b2g_cmd
+# command optimized for performance.
+def send_b2g_data_chunk(to, data_blob):
+ byte_str = []
+ e = '\u0000'
+ # '"' == 34
+ # '\' == 92
+ i = 0
+ while i < len(data_blob):
+ o = ord(data_blob[i])
+# if o == 34 or o == 92 or o >= 128 or o <= 32:#o <= 32 or o >= 36:# or o == ord('\\'):
+ if o <= 34 or o >= 128 or o == 92:
+ c = hex(o)[2:]
+ byte_str += e[:-len(c)] + c
+ else:
+ byte_str += data_blob[i]
+ i += 1
+ message = '{"to":"'+to+'","type":"chunk","chunk":"' + ''.join(byte_str) + '"}'
+ message = str(len(message)) + ':' + message
+ logv('{"to":"'+to+'","type":"chunk","chunk":"<data>"}')
+ b2g_socket.sendall(message)
+ return read_b2g_response()
+
+def send_b2g_bulk_data(to, data_blob):
+ message = 'bulk ' + to + ' stream ' + str(len(data_blob)) + ':'
+ logv(message)
+ b2g_socket.sendall(message)
+ b2g_socket.sendall(data_blob)
+ # It seems that B2G doesn't send any response JSON back after a bulk transfer is finished, so no read_b2g_response() here.
+
+# Queries the device for a list of all installed apps.
+def b2g_get_appslist():
+ global webappsActorName
+ apps = send_b2g_cmd(webappsActorName, 'getAll')
+ return apps['apps']
+
+# Queries the device for a list of all currently running apps.
+def b2g_get_runningapps():
+ global webappsActorName
+ apps = send_b2g_cmd(webappsActorName, 'listRunningApps')
+ return apps['apps'] # Returns manifestURLs of all running apps
+
+def print_applist(applist, running_app_manifests, print_removable):
+ num_printed = 0
+ for app in applist:
+ if print_removable or app['removable']: # Print only removable apps unless --all is specified, skip the built-in apps that can't be uninstalled.
+ if 'manifest' in app and 'version' in app['manifest']:
+ version = " version '" + app['manifest']['version'] + "'"
+ else:
+ version = ''
+ if app['manifestURL'] in running_app_manifests:
+ version += ' RUNNING'
+ print ' ' + str(app['localId']) + ': "' + app['name'] + '"' + version
+ num_printed += 1
+ return num_printed
+
+def main():
+ global b2g_socket, webappsActorName, HOST, PORT, VERBOSE
+ if len(sys.argv) < 2 or '--help' in sys.argv or 'help' in sys.argv or '-v' in sys.argv:
+ print '''Firefox OS Debug Bridge, a tool for automating FFOS device tasks from the command line.
+
+ Usage: ffdb.py <command>, where command is one of:
+
+ list [--running] [--all]: Prints out the user applications installed on the device.
+ If --running is passed, only the currently opened apps are shown.
+ If --all is specified, then also uninstallable system applications are listed.
+ launch <app>: Starts the given application. If already running, brings to front.
+ close <app>: Terminates the execution of the given application.
+ uninstall <app>: Removes the given application from the device.
+ install <path>: Uploads and installs a packaged app that resides in the given local directory.
+ <path> may either refer to a directory containing a packaged app, or to a prepackaged zip file.
+ log <app> [--clear]: Starts a persistent log listener that reads web console messages from the given application.
+ If --clear is passed, the message log for that application is cleared instead.
+ navigate <url>: Opens the given web page in the B2G browser.
+ screenshot [filename.png]: Takes a screenshot of the current contents displayed on the device. If an optional
+ filename is specified, the screenshot is saved to that file. Otherwise the filename
+ will be autogenerated.
+
+ Options: Additionally, the following options may be passed to control FFDB execution:
+
+ --host <hostname>: Specifies the target network address to connect to. Default: 'localhost'.
+ --port <number>: Specifies the network port to connect to. Default: 6000.
+ --verbose: Enables verbose printing, mostly useful for debugging.
+ --simulator: Signal that we will be connecting to a FFOS simulator and not a real device.
+
+ In the above, whenever a command requires an <app> to be specified, either the human-readable name,
+ localId or manifestURL of the application can be used.'''
+
+ sys.exit(0)
+
+ connect_to_simulator = False
+
+ options_with_value = ['--host', '--port']
+ options = options_with_value + ['--verbose', '--simulator']
+ # Process options
+ for i in range(0, len(sys.argv)):
+ if sys.argv[i] in options_with_value:
+ if i+1 >= sys.argv or sys.argv[i+1].startswith('-'):
+ print >> sys.stderr, "Missing value for option " + sys.argv[i] +'!'
+ sys.exit(1)
+ if sys.argv[i] == '--host':
+ HOST = sys.argv[i+1]
+ elif sys.argv[i] == '--port':
+ PORT = int(sys.argv[i+1])
+ elif sys.argv[i] == '--verbose':
+ VERBOSE = True
+ elif sys.argv[i] == '--simulator':
+ connect_to_simulator = True
+
+ # Clear the processed options so that parsing the commands below won't trip up on these.
+ if sys.argv[i] in options: sys.argv[i] = ''
+ if sys.argv[i] in options_with_value: sys.argv[i+1] = ''
+
+ sys.argv = filter(lambda x: len(x) > 0, sys.argv)
+
+ b2g_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ try:
+ b2g_socket.connect((HOST, PORT))
+ except Exception, e:
+ if e[0] == 61: # Connection refused
+ if (HOST == 'localhost' or HOST == '127.0.0.1') and not connect_to_simulator:
+ cmd = ['adb', 'forward', 'tcp:'+str(PORT), 'localfilesystem:/data/local/debugger-socket']
+ print 'Connection to ' + HOST + ':' + str(PORT) + ' refused, attempting to forward device debugger-socket to local address by calling ' + str(cmd) + ':'
+ else:
+ print 'Error! Failed to connect to B2G ' + ('simulator' if connect_to_simulator else 'device') + ' debugger socket at address ' + HOST + ':' + str(PORT) + '!'
+ sys.exit(1)
+ try:
+ retcode = subprocess.check_call(cmd)
+ except Exception, e:
+ print 'Error! Failed to execute adb: ' + str(e)
+ print "Check that the device is connected properly, call 'adb devices' to list the detected devices."
+ sys.exit(1)
+ if retcode is not 0:
+ print 'Error! Failed to connect to B2G device and executing adb failed with return code ' + retcode + '!'
+ sys.exit(1)
+ time.sleep(3)
+ # Try again:
+ try:
+ b2g_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ b2g_socket.connect((HOST, PORT))
+ except Exception, e:
+ print 'Error! Failed to connect to B2G device debugger socket at address ' + HOST + ':' + str(PORT) + '!'
+ sys.exit(1)
+
+ handshake = read_b2g_response()
+ logv('Connected. Handshake: ' + str(handshake))
+
+ data = send_b2g_cmd('root', 'listTabs')
+ deviceActorName = data['deviceActor']
+ logv('deviceActor: ' + deviceActorName)
+ webappsActorName = data['webappsActor']
+ logv('webappsActor: ' + webappsActorName)
+
+ send_b2g_cmd(deviceActorName, 'getDescription')
+ send_b2g_cmd(deviceActorName, 'getRawPermissionsTable')
+
+ apps = b2g_get_appslist()
+
+ if sys.argv[1] == 'list':
+ running_app_manifests = b2g_get_runningapps()
+ printed_apps = apps
+ print_only_running = '--running' in sys.argv and not '--all' in sys.argv
+ if print_only_running: # Print running apps only?
+ print 'Running applications by id:'
+ printed_apps = filter(lambda x: x['manifestURL'] in running_app_manifests, apps)
+ else:
+ print 'Installed applications by id:'
+ num_printed = print_applist(printed_apps, running_app_manifests, '--all' in sys.argv or print_only_running)
+ if num_printed == 0:
+ if print_only_running:
+ print ' No applications running.'
+ else:
+ print ' No applications installed.'
+ if not '--all' in sys.argv and not print_only_running:
+ print 'Not showing built-in apps that cannot be uninstalled. Pass --all to include those in the listing.'
+ elif sys.argv[1] == 'launch' or sys.argv[1] == 'close' or sys.argv[1] == 'uninstall' or sys.argv[1] == 'getAppActor':
+ if len(sys.argv) < 3:
+ print 'Error! No application name given! Usage: ' + sys.argv[0] + ' ' + sys.argv[1] + ' <app>'
+ return 1
+ for app in apps:
+ if str(app['localId']) == sys.argv[2] or app['name'] == sys.argv[2] or app['manifestURL'] == sys.argv[2]:
+ send_b2g_cmd(webappsActorName, sys.argv[1], { 'manifestURL': app['manifestURL'] })
+ return 0
+ print 'Error! Application "' + sys.argv[2] + '" was not found! Use the \'list\' command to find installed applications.'
+ return 1
+ elif sys.argv[1] == 'install':
+ if len(sys.argv) < 3:
+ print 'Error! No application path given! Usage: ' + sys.argv[0] + ' ' + sys.argv[1] + ' <path>'
+ return 1
+ target_app_path = sys.argv[2]
+ if os.path.isdir(target_app_path):
+ print 'Zipping up the contents of directory "' + target_app_path + '"...'
+ (oshandle, tempzip) = tempfile.mkstemp(suffix='.zip', prefix='ffdb_temp_')
+ zipdir(target_app_path, tempzip)
+ target_app_path = tempzip
+ # Remember to delete the temporary package after we quit.
+ def delete_temp_file():
+ os.remove(tempzip)
+ atexit.register(delete_temp_file)
+
+ print 'Uploading application package "' + target_app_path + '"...'
+ print 'Size of compressed package: ' + sizeof_fmt(os.path.getsize(target_app_path)) + '.'
+ app_file = open(target_app_path, 'rb')
+ data = app_file.read()
+ file_size = len(data)
+
+ uploadResponse = send_b2g_cmd(webappsActorName, 'uploadPackage', { 'bulk': 'true'}, print_errors_to_console = False) # This may fail if on old device.
+ start_time = time.time()
+ if 'actor' in uploadResponse and 'BulkActor' in uploadResponse['actor']: # New B2G 2.0 hotness: binary data transfer
+ packageUploadActor = uploadResponse['actor']
+ send_b2g_bulk_data(packageUploadActor, data)
+ else: # Old B2G 1.4 and older, serialize binary data in JSON text strings (SLOW!)
+ print 'Bulk upload is not supported, uploading binary data with old slow format. Consider flashing your device to FFOS 2.0 or newer to enjoy faster upload speeds.'
+ uploadResponse = send_b2g_cmd(webappsActorName, 'uploadPackage')
+ packageUploadActor = uploadResponse['actor']
+ chunk_size = 4*1024*1024
+ i = 0
+ while i < file_size:
+ chunk = data[i:i+chunk_size]
+
+ send_b2g_data_chunk(packageUploadActor, chunk)
+ i += chunk_size
+ bytes_uploaded = min(i, file_size)
+ cur_time = time.time()
+ secs_elapsed = cur_time - start_time
+ percentage_done = bytes_uploaded * 1.0 / file_size
+ total_time = secs_elapsed / percentage_done
+ time_left = total_time - secs_elapsed
+ print sizeof_fmt(bytes_uploaded) + " uploaded, {:5.1f} % done.".format(percentage_done*100.0) + ' Elapsed: ' + str(int(secs_elapsed)) + ' seconds. Time left: ' + str(datetime.timedelta(seconds=int(time_left))) + '. Data rate: {:5.2f} KB/second.'.format(bytes_uploaded / 1024.0 / secs_elapsed)
+
+ send_b2g_cmd(webappsActorName, 'install', { 'appId': str(uuid.uuid4()), 'upload': packageUploadActor })
+ cur_time = time.time()
+ secs_elapsed = cur_time - start_time
+ print 'Upload of ' + sizeof_fmt(file_size) + ' finished. Total time elapsed: ' + str(int(secs_elapsed)) + ' seconds. Data rate: {:5.2f} KB/second.'.format(file_size / 1024.0 / secs_elapsed)
+ elif sys.argv[1] == 'navigate':
+ if len(sys.argv) < 3:
+ print 'Error! No URL given! Usage: ' + sys.argv[0] + ' ' + sys.argv[1] + ' <url>'
+ return 1
+ browserActor = ''
+ for app in apps:
+ if app['name'] == 'Browser':
+ browserActor = send_b2g_cmd(webappsActorName, 'getAppActor', { 'manifestURL': app['manifestURL'] })
+ break
+ if 'actor' in browserActor:
+ browserActor = browserActor['actor']['actor']
+ send_b2g_cmd(browserActor, 'navigateTo', { 'url': sys.argv[2]})
+ else:
+ print 'Web browser is not running!'
+ elif sys.argv[1] == 'log':
+ appActor = ''
+ for app in apps:
+ if str(app['localId']) == sys.argv[2] or app['name'] == sys.argv[2] or app['manifestURL'] == sys.argv[2]:
+ appActor = send_b2g_cmd(webappsActorName, 'getAppActor', { 'manifestURL': app['manifestURL'] })
+ break
+ if 'actor' in appActor:
+ consoleActor = appActor['actor']['consoleActor']
+
+ if '-c' in sys.argv or '-clear' in sys.argv or '--clear' in sys.argv:
+ send_b2g_cmd(consoleActor, 'clearMessagesCache')
+ print 'Cleared message log.'
+ sys.exit(0)
+
+ msgs = send_b2g_cmd(consoleActor, 'startListeners', { 'listeners': ['PageError','ConsoleAPI','NetworkActivity','FileActivity'] })
+
+ def log_b2g_message(msg):
+ WARNING = '\033[93m'
+ FAIL = '\033[91m'
+ ENDC = '\033[0m'
+ BOLD = "\033[1m"
+ msgs = []
+ if 'type' in msg and msg['type'] == 'consoleAPICall':
+ msgs = [msg['message']]
+ elif 'messages' in msg:
+ msgs = msg['messages']
+
+ for m in msgs:
+ args = m['arguments']
+
+ for arg in args:
+ if m['level'] == 'log':
+ color = 'I/'
+ elif m['level'] == 'warn':
+ color = WARNING + 'W/'
+ elif m['level'] == 'error':
+ color = FAIL + 'E/'
+ else:
+ color = m['level'] + '/'
+
+ print color + str(m['functionName']) + '@' + str(m['filename']) + ':' + str(m['lineNumber']) + ': ' + str(arg) + ENDC
+
+ msgs = send_b2g_cmd(consoleActor, 'getCachedMessages', { 'messageTypes': ['PageError', 'ConsoleAPI'] })
+ log_b2g_message(msgs)
+
+ while True:
+ msg = read_b2g_response()
+ log_b2g_message(msg)
+ else:
+ print 'Application "' + sys.argv[2] + '" is not running!'
+ elif sys.argv[1] == 'screenshot':
+ if len(sys.argv) >= 3:
+ filename = sys.argv[2]
+ if not filename.endswith('.png'):
+ print >> sys.stderr, "Writing screenshots only to .png files are supported!"
+ sys.exit(1)
+ else:
+ filename = time.strftime("screen_%Y%m%d_%H%M%S.png", time.gmtime())
+
+ data_reply = send_b2g_cmd(deviceActorName, 'screenshotToDataURL')
+ data = data_reply['value']
+ data_get_actor = data['actor']
+ data_len = int(data['length'])
+ data_str = data['initial']
+ delim = re.search(",", data_str).start()
+ data_format = data_str[:delim]
+ if data_format != "data:image/png;base64":
+ print >> sys.stderr, "Error: Received screenshot from device in an unexpected format '" + data_format + "'!"
+ sys.exit(1)
+ data = data_str[delim+1:]
+ chunk_size = 65000
+ pos = len(data_str)
+ while pos < data_len:
+ bytes_to_read = min(data_len - pos, chunk_size)
+ data_reply = send_b2g_cmd(data_get_actor, 'substring', { 'start': str(pos), 'end': str(pos + bytes_to_read) })
+ if len(data_reply['substring']) != bytes_to_read:
+ print >> sys.stderr, 'Error! Expected to receive ' + str(bytes_to_read) + ' bytes of image data, but got ' + str(len(data_reply['substring'])) + ' bytes instead!'
+ sys.exit(1)
+ data += data_reply['substring']
+ pos += bytes_to_read
+ send_b2g_cmd(data_get_actor, 'release') # We need to explicitly free the screenshot image string from the device, or the Devtools connection leaks resources!
+ binary_data = base64.b64decode(data)
+ open(filename, 'wb').write(binary_data)
+
+ def get_png_image_size(filename):
+ fhandle = open(filename, 'rb')
+ head = fhandle.read(24)
+ if len(head) != 24:
+ return (-1, -1)
+ check = struct.unpack('>i', head[4:8])[0]