diff options
-rwxr-xr-x | emcc | 4 | ||||
-rw-r--r-- | emscripten-version.txt | 2 | ||||
-rw-r--r-- | src/library.js | 13 | ||||
-rw-r--r-- | src/library_fs.js | 5 | ||||
-rw-r--r-- | src/library_idbfs.js | 3 | ||||
-rw-r--r-- | src/library_memfs.js | 169 | ||||
-rw-r--r-- | src/library_sdl.js | 2 | ||||
-rw-r--r-- | src/modules.js | 25 | ||||
-rw-r--r-- | src/settings.js | 4 | ||||
-rw-r--r-- | system/local/include/README.txt | 2 | ||||
-rw-r--r-- | tests/test_browser.py | 7 | ||||
-rw-r--r-- | tests/test_core.py | 32 | ||||
-rwxr-xr-x | tools/ffdb.py | 501 | ||||
-rw-r--r-- | tools/js-optimizer.js | 5 | ||||
-rw-r--r-- | tools/shared.py | 2 |
15 files changed, 560 insertions, 216 deletions
@@ -1376,6 +1376,8 @@ try: if shared.Settings.ASM_JS and shared.Settings.DLOPEN_SUPPORT: assert shared.Settings.DISABLE_EXCEPTION_CATCHING, 'no exceptions support with dlopen in asm yet' + assert not (bind and shared.Settings.NO_DYNAMIC_EXECUTION), 'NO_DYNAMIC_EXECUTION disallows embind' + if proxy_to_worker: shared.Settings.PROXY_TO_WORKER = 1 @@ -1692,7 +1694,7 @@ try: logging.debug('wrote memory initialization to %s', memfile) else: logging.debug('did not see memory initialization') - elif shared.Settings.USE_TYPED_ARRAYS == 2 and not shared.Settings.MAIN_MODULE and not shared.Settings.SIDE_MODULE: + elif shared.Settings.USE_TYPED_ARRAYS == 2 and not shared.Settings.MAIN_MODULE and not shared.Settings.SIDE_MODULE and debug_level < 4: # not writing a binary init, but we can at least optimize them by splitting them up src = open(final).read() src = shared.JS.optimize_initializer(src) diff --git a/emscripten-version.txt b/emscripten-version.txt index de59d0c6..5f079d01 100644 --- a/emscripten-version.txt +++ b/emscripten-version.txt @@ -1,2 +1,2 @@ -1.19.1 +1.19.2 diff --git a/src/library.js b/src/library.js index c17952b3..1f7fd37d 100644 --- a/src/library.js +++ b/src/library.js @@ -3166,7 +3166,7 @@ LibraryManager.library = { #endif environ: 'allocate(1, "i32*", ALLOC_STATIC)', __environ__deps: ['environ'], - __environ: '_environ', + __environ: 'environ', __buildEnvironment__deps: ['__environ'], __buildEnvironment: function(env) { // WARNING: Arbitrary limit! @@ -3388,9 +3388,20 @@ LibraryManager.library = { memcpy__sig: 'iiii', memcpy__deps: ['emscripten_memcpy_big'], memcpy: function(dest, src, num) { +#if USE_TYPED_ARRAYS == 0 + {{{ makeCopyValues('dest', 'src', 'num', 'null') }}}; + return num; +#endif +#if USE_TYPED_ARRAYS == 1 + {{{ makeCopyValues('dest', 'src', 'num', 'null') }}}; + return num; +#endif + dest = dest|0; src = src|0; num = num|0; var ret = 0; +#if USE_TYPED_ARRAYS if ((num|0) >= 4096) return _emscripten_memcpy_big(dest|0, src|0, num|0)|0; +#endif ret = dest|0; if ((dest&3) == (src&3)) { while (dest & 3) { diff --git a/src/library_fs.js b/src/library_fs.js index 5f7f1dea..1fff6348 100644 --- a/src/library_fs.js +++ b/src/library_fs.js @@ -1480,6 +1480,7 @@ mergeInto(LibraryManager.library, { // WARNING: Can't read binary files in V8's d8 or tracemonkey's js, as // read() will try to parse UTF8. obj.contents = intArrayFromString(Module['read'](obj.url), true); + obj.usedBytes = obj.contents.length; } catch (e) { success = false; } @@ -1601,6 +1602,10 @@ mergeInto(LibraryManager.library, { node.contents = null; node.url = properties.url; } + // Add a function that defers querying the file size until it is asked the first time. + Object.defineProperty(node, "usedBytes", { + get: function() { return this.contents.length; } + }); // override each stream op with one that tries to force load the lazy file first var stream_ops = {}; var keys = Object.keys(node.stream_ops); diff --git a/src/library_idbfs.js b/src/library_idbfs.js index 91015e77..8082c196 100644 --- a/src/library_idbfs.js +++ b/src/library_idbfs.js @@ -135,6 +135,9 @@ mergeInto(LibraryManager.library, { if (FS.isDir(stat.mode)) { return callback(null, { timestamp: stat.mtime, mode: stat.mode }); } else if (FS.isFile(stat.mode)) { + // Performance consideration: storing a normal JavaScript array to a IndexedDB is much slower than storing a typed array. + // Therefore always convert the file contents to a typed array first before writing the data to IndexedDB. + node.contents = MEMFS.getFileDataAsTypedArray(node); return callback(null, { timestamp: stat.mtime, mode: stat.mode, contents: node.contents }); } else { return callback(new Error('node type not supported')); diff --git a/src/library_memfs.js b/src/library_memfs.js index 95c3ae65..24d6cc22 100644 --- a/src/library_memfs.js +++ b/src/library_memfs.js @@ -2,11 +2,6 @@ mergeInto(LibraryManager.library, { $MEMFS__deps: ['$FS'], $MEMFS: { ops_table: null, - - // content modes - CONTENT_OWNING: 1, // contains a subarray into the heap, and we own it, without copying (note: someone else needs to free() it, if that is necessary) - CONTENT_FLEXIBLE: 2, // has been modified or never set to anything, and is a flexible js array that can grow/shrink - CONTENT_FIXED: 3, // contains some fixed-size content written into it, in a typed array mount: function(mount) { return MEMFS.createNode(null, '/', {{{ cDefine('S_IFDIR') }}} | 511 /* 0777 */, 0); }, @@ -71,8 +66,11 @@ mergeInto(LibraryManager.library, { } else if (FS.isFile(node.mode)) { node.node_ops = MEMFS.ops_table.file.node; node.stream_ops = MEMFS.ops_table.file.stream; - node.contents = []; - node.contentMode = MEMFS.CONTENT_FLEXIBLE; + node.usedBytes = 0; // The actual number of bytes used in the typed array, as opposed to contents.buffer.byteLength which gives the whole capacity. + // When the byte data of the file is populated, this will point to either a typed array, or a normal JS array. Typed arrays are preferred + // for performance, and used by default. However, typed arrays are not resizable like normal JS arrays are, so there is a small disk size + // penalty involved for appending file writes that continuously grow a file similar to std::vector capacity vs used -scheme. + node.contents = null; } else if (FS.isLink(node.mode)) { node.node_ops = MEMFS.ops_table.link.node; node.stream_ops = MEMFS.ops_table.link.stream; @@ -87,13 +85,88 @@ mergeInto(LibraryManager.library, { } return node; }, - ensureFlexible: function(node) { - if (node.contentMode !== MEMFS.CONTENT_FLEXIBLE) { - var contents = node.contents; - node.contents = Array.prototype.slice.call(contents); - node.contentMode = MEMFS.CONTENT_FLEXIBLE; + + // Given a file node, returns its file data converted to a regular JS array. You should treat this as read-only. + getFileDataAsRegularArray: function(node) { +#if USE_TYPED_ARRAYS == 2 + if (node.contents && node.contents.subarray) { + var arr = []; + for (var i = 0; i < node.usedBytes; ++i) arr.push(node.contents[i]); + return arr; // Returns a copy of the original data. + } +#endif + return node.contents; // No-op, the file contents are already in a JS array. Return as-is. + }, + +#if USE_TYPED_ARRAYS == 2 + // Given a file node, returns its file data converted to a typed array. + getFileDataAsTypedArray: function(node) { + if (node.contents && node.contents.subarray) return node.contents.subarray(0, node.usedBytes); // Make sure to not return excess unused bytes. + return new Uint8Array(node.contents); + }, +#endif + + // Allocates a new backing store for the given node so that it can fit at least newSize amount of bytes. + // May allocate more, to provide automatic geometric increase and amortized linear performance appending writes. + // Never shrinks the storage. + expandFileStorage: function(node, newCapacity) { +#if USE_TYPED_ARRAYS == 2 + +#if !MEMFS_APPEND_TO_TYPED_ARRAYS + // If we are asked to expand the size of a file that already exists, revert to using a standard JS array to store the file + // instead of a typed array. This makes resizing the array more flexible because we can just .push() elements at the back to + // increase the size. + if (node.contents && node.contents.subarray && newCapacity > node.contents.length) { + node.contents = MEMFS.getFileDataAsRegularArray(node); + node.usedBytes = node.contents.length; // We might be writing to a lazy-loaded file which had overridden this property, so force-reset it. } +#endif + + if (!node.contents || node.contents.subarray) { // Keep using a typed array if creating a new storage, or if old one was a typed array as well. + var prevCapacity = node.contents ? node.contents.buffer.byteLength : 0; + if (prevCapacity >= newCapacity) return; // No need to expand, the storage was already large enough. + // Don't expand strictly to the given requested limit if it's only a very small increase, but instead geometrically grow capacity. + // For small filesizes (<1MB), perform size*2 geometric increase, but for large sizes, do a much more conservative size*1.125 increase to + // avoid overshooting the allocation cap by a very large margin. + var CAPACITY_DOUBLING_MAX = 1024 * 1024; + newCapacity = Math.max(newCapacity, (prevCapacity * (prevCapacity < CAPACITY_DOUBLING_MAX ? 2.0 : 1.125)) | 0); + if (prevCapacity != 0) newCapacity = Math.max(newCapacity, 256); // At minimum allocate 256b for each file when expanding. + var oldContents = node.contents; + node.contents = new Uint8Array(newCapacity); // Allocate new storage. + if (node.usedBytes > 0) node.contents.set(oldContents.subarray(0, node.usedBytes), 0); // Copy old data over to the new storage. + return; + } +#endif + // Not using a typed array to back the file storage. Use a standard JS array instead. + if (!node.contents && newCapacity > 0) node.contents = []; + while (node.contents.length < newCapacity) node.contents.push(0); }, + + // Performs an exact resize of the backing file storage to the given size, if the size is not exactly this, the storage is fully reallocated. + resizeFileStorage: function(node, newSize) { + if (node.usedBytes == newSize) return; + if (newSize == 0) { + node.contents = null; // Fully decommit when requesting a resize to zero. + node.usedBytes = 0; + return; + } + +#if USE_TYPED_ARRAYS == 2 + if (!node.contents || node.contents.subarray) { // Resize a typed array if that is being used as the backing store. + var oldContents = node.contents; + node.contents = new Uint8Array(new ArrayBuffer(newSize)); // Allocate new storage. + node.contents.set(oldContents.subarray(0, Math.min(newSize, node.usedBytes))); // Copy old data over to the new storage. + node.usedBytes = newSize; + return; + } +#endif + // Backing with a JS array. + if (!node.contents) node.contents = []; + if (node.contents.length > newSize) node.contents.length = newSize; + else while (node.contents.length < newSize) node.contents.push(0); + node.usedBytes = newSize; + }, + node_ops: { getattr: function(node) { var attr = {}; @@ -108,7 +181,7 @@ mergeInto(LibraryManager.library, { if (FS.isDir(node.mode)) { attr.size = 4096; } else if (FS.isFile(node.mode)) { - attr.size = node.contents.length; + attr.size = node.usedBytes; } else if (FS.isLink(node.mode)) { attr.size = node.link.length; } else { @@ -131,10 +204,7 @@ mergeInto(LibraryManager.library, { node.timestamp = attr.timestamp; } if (attr.size !== undefined) { - MEMFS.ensureFlexible(node); - var contents = node.contents; - if (attr.size < contents.length) contents.length = attr.size; - else while (attr.size > contents.length) contents.push(0); + MEMFS.resizeFileStorage(node, attr.size); } }, lookup: function(parent, name) { @@ -198,9 +268,8 @@ mergeInto(LibraryManager.library, { stream_ops: { read: function(stream, buffer, offset, length, position) { var contents = stream.node.contents; - if (position >= contents.length) - return 0; - var size = Math.min(contents.length - position, length); + if (position >= stream.node.usedBytes) return 0; + var size = Math.min(stream.node.usedBytes - position, length); assert(size >= 0); #if USE_TYPED_ARRAYS == 2 if (size > 8 && contents.subarray) { // non-trivial, and typed array @@ -208,47 +277,53 @@ mergeInto(LibraryManager.library, { } else #endif { - for (var i = 0; i < size; i++) { - buffer[offset + i] = contents[position + i]; - } + for (var i = 0; i < size; i++) buffer[offset + i] = contents[position + i]; } return size; }, + + // Writes the byte range (buffer[offset], buffer[offset+length]) to offset 'position' into the file pointed by 'stream' write: function(stream, buffer, offset, length, position, canOwn) { + if (!length) return 0; var node = stream.node; node.timestamp = Date.now(); - var contents = node.contents; + #if USE_TYPED_ARRAYS == 2 - if (length && contents.length === 0 && position === 0 && buffer.subarray) { - // just replace it with the new data -#if ASSERTIONS - assert(buffer.length); -#endif - if (canOwn && offset === 0) { - node.contents = buffer; // this could be a subarray of Emscripten HEAP, or allocated from some other source. - node.contentMode = (buffer.buffer === HEAP8.buffer) ? MEMFS.CONTENT_OWNING : MEMFS.CONTENT_FIXED; - } else { - node.contents = new Uint8Array(buffer.subarray(offset, offset+length)); - node.contentMode = MEMFS.CONTENT_FIXED; + if (buffer.subarray && (!node.contents || node.contents.subarray)) { // This write is from a typed array to a typed array? + if (canOwn) { // Can we just reuse the buffer we are given? + node.contents = buffer.subarray(offset, offset + length); + node.usedBytes = length; + return length; + } else if (node.usedBytes === 0) { // If this first write to an empty file, do a fast set since we don't need to care about old data. + node.contents = new Uint8Array(buffer.subarray(offset, offset + length)); + node.usedBytes = length; + return length; + } else if (position + length <= node.usedBytes) { // Writing to an already allocated and used subrange of the file? + node.contents.set(buffer.subarray(offset, offset + length), position); + return length; } - return length; } #endif - MEMFS.ensureFlexible(node); - var contents = node.contents; - while (contents.length < position) contents.push(0); - for (var i = 0; i < length; i++) { - contents[position + i] = buffer[offset + i]; - } + // Appending to an existing file and we need to reallocate, or source data did not come as a typed array. + MEMFS.expandFileStorage(node, position+length); +#if USE_TYPED_ARRAYS == 2 + if (node.contents.subarray && buffer.subarray) node.contents.set(buffer.subarray(offset, offset + length), position); // Use typed array write if available. + else +#endif + for (var i = 0; i < length; i++) { + node.contents[position + i] = buffer[offset + i]; // Or fall back to manual write if not. + } + node.usedBytes = Math.max(node.usedBytes, position+length); return length; }, + llseek: function(stream, offset, whence) { var position = offset; if (whence === 1) { // SEEK_CUR. position += stream.position; } else if (whence === 2) { // SEEK_END. if (FS.isFile(stream.node.mode)) { - position += stream.node.contents.length; + position += stream.node.usedBytes; } } if (position < 0) { @@ -259,10 +334,8 @@ mergeInto(LibraryManager.library, { return position; }, allocate: function(stream, offset, length) { - MEMFS.ensureFlexible(stream.node); - var contents = stream.node.contents; - var limit = offset + length; - while (limit > contents.length) contents.push(0); + MEMFS.expandFileStorage(stream.node, offset + length); + stream.node.usedBytes = Math.max(stream.node.usedBytes, offset + length); }, mmap: function(stream, buffer, offset, length, position, prot, flags) { if (!FS.isFile(stream.node.mode)) { @@ -280,7 +353,7 @@ mergeInto(LibraryManager.library, { ptr = contents.byteOffset; } else { // Try to avoid unnecessary slices. - if (position > 0 || position + length < contents.length) { + if (position > 0 || position + length < stream.node.usedBytes) { if (contents.subarray) { contents = contents.subarray(position, position + length); } else { diff --git a/src/library_sdl.js b/src/library_sdl.js index a01b3c6c..7145a7ba 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -2601,7 +2601,7 @@ var LibrarySDL = { if (info && info.audio) { info.audio.pause(); } else { - Module.printErr('Mix_Pause: no sound found for channel: ' + channel); + //Module.printErr('Mix_Pause: no sound found for channel: ' + channel); } }, diff --git a/src/modules.js b/src/modules.js index fd5c23cd..56f4c827 100644 --- a/src/modules.js +++ b/src/modules.js @@ -443,6 +443,31 @@ var LibraryManager = { } } + // apply synonyms. these are typically not speed-sensitive, and doing it this way makes it possible to not include hacks in the compiler + // (and makes it simpler to switch between SDL verisons, fastcomp and non-fastcomp, etc.). + var lib = LibraryManager.library; + libloop: for (var x in lib) { + if (x.lastIndexOf('__') > 0) continue; // ignore __deps, __* + if (lib[x + '__asm']) continue; // ignore asm library functions, those need to be fully optimized + if (typeof lib[x] === 'string') { + var target = x; + while (typeof lib[target] === 'string') { + if (lib[target].indexOf('(') >= 0) continue libloop; + target = lib[target]; + } + if (typeof lib[target] === 'undefined' || typeof lib[target] === 'function') { + if (target.indexOf('Math_') < 0) { + lib[x] = new Function('return _' + target + '.apply(null, arguments)'); + if (!lib[x + '__deps']) lib[x + '__deps'] = []; + lib[x + '__deps'].push(target); + } else { + lib[x] = new Function('return ' + target + '.apply(null, arguments)'); + } + continue; + } + } + } + /* // export code for CallHandlers.h printErr('============================'); diff --git a/src/settings.js b/src/settings.js index bdb149e3..a8761d90 100644 --- a/src/settings.js +++ b/src/settings.js @@ -323,6 +323,10 @@ var FS_LOG = 0; // Log all FS operations. This is especially helpful when you'r // so that you can create a virtual file system with all of the required files. var CASE_INSENSITIVE_FS = 0; // If set to nonzero, the provided virtual filesystem if treated case-insensitive, like // Windows and OSX do. If set to 0, the VFS is case-sensitive, like on Linux. +var MEMFS_APPEND_TO_TYPED_ARRAYS = 0; // If set to nonzero, MEMFS will always utilize typed arrays as the backing store + // for appending data to files. The default behavior is to use typed arrays for files + // when the file size doesn't change after initial creation, and for files that do + // change size, use normal JS arrays instead. var USE_BSS = 1; // https://en.wikipedia.org/wiki/.bss // When enabled, 0-initialized globals are sorted to the end of the globals list, diff --git a/system/local/include/README.txt b/system/local/include/README.txt new file mode 100644 index 00000000..09bd03e0 --- /dev/null +++ b/system/local/include/README.txt @@ -0,0 +1,2 @@ +You may place local includes that you require here and they will +be found by the default compiler search path. diff --git a/tests/test_browser.py b/tests/test_browser.py index c8e07b25..0771cb1b 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -1096,9 +1096,10 @@ keydown(100);keyup(100); // trigger the end shutil.move('test.html', 'third.html') def test_fs_idbfs_sync(self): - secret = str(time.time()) - self.btest(path_from_root('tests', 'fs', 'test_idbfs_sync.c'), '1', force_c=True, args=['-DFIRST', '-DSECRET=\'' + secret + '\'', '-s', '''EXPORTED_FUNCTIONS=['_main', '_success']''']) - self.btest(path_from_root('tests', 'fs', 'test_idbfs_sync.c'), '1', force_c=True, args=['-DSECRET=\'' + secret + '\'', '-s', '''EXPORTED_FUNCTIONS=['_main', '_success']''']) + for mode in [[], ['-s', 'MEMFS_APPEND_TO_TYPED_ARRAYS=1']]: + secret = str(time.time()) + self.btest(path_from_root('tests', 'fs', 'test_idbfs_sync.c'), '1', force_c=True, args=mode + ['-DFIRST', '-DSECRET=\'' + secret + '\'', '-s', '''EXPORTED_FUNCTIONS=['_main', '_success']''']) + self.btest(path_from_root('tests', 'fs', 'test_idbfs_sync.c'), '1', force_c=True, args=mode + ['-DSECRET=\'' + secret + '\'', '-s', '''EXPORTED_FUNCTIONS=['_main', '_success']''']) def test_sdl_pumpevents(self): # key events should be detected using SDL_PumpEvents diff --git a/tests/test_core.py b/tests/test_core.py index 76a87066..13b6c0f3 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -4030,11 +4030,14 @@ def process(filename): src = open(path_from_root('tests', 'files.cpp'), 'r').read() mem_file = 'src.cpp.o.js.mem' - try_delete(mem_file) - self.do_run(src, ('size: 7\ndata: 100,-56,50,25,10,77,123\nloop: 100 -56 50 25 10 77 123 \ninput:hi there!\ntexto\n$\n5 : 10,30,20,11,88\nother=some data.\nseeked=me da.\nseeked=ata.\nseeked=ta.\nfscanfed: 10 - hello\nok.\ntexte\n', 'size: 7\ndata: 100,-56,50,25,10,77,123\nloop: 100 -56 50 25 10 77 123 \ninput:hi there!\ntexto\ntexte\n$\n5 : 10,30,20,11,88\nother=some data.\nseeked=me da.\nseeked=ata.\nseeked=ta.\nfscanfed: 10 - hello\nok.\n'), - post_build=post, extra_emscripten_args=['-H', 'libc/fcntl.h']) - if self.emcc_args and '--memory-init-file' in self.emcc_args: - assert os.path.exists(mem_file) + orig_args = self.emcc_args + for mode in [[], ['-s', 'MEMFS_APPEND_TO_TYPED_ARRAYS=1']]: + self.emcc_args = orig_args + mode + try_delete(mem_file) + self.do_run(src, ('size: 7\ndata: 100,-56,50,25,10,77,123\nloop: 100 -56 50 25 10 77 123 \ninput:hi there!\ntexto\n$\n5 : 10,30,20,11,88\nother=some data.\nseeked=me da.\nseeked=ata.\nseeked=ta.\nfscanfed: 10 - hello\nok.\ntexte\n', 'size: 7\ndata: 100,-56,50,25,10,77,123\nloop: 100 -56 50 25 10 77 123 \ninput:hi there!\ntexto\ntexte\n$\n5 : 10,30,20,11,88\nother=some data.\nseeked=me da.\nseeked=ata.\nseeked=ta.\nfscanfed: 10 - hello\nok.\n'), + post_build=post, extra_emscripten_args=['-H', 'libc/fcntl.h']) + if self.emcc_args and '--memory-init-file' in self.emcc_args: + assert os.path.exists(mem_file) def test_files_m(self): # Test for Module.stdin etc. @@ -4077,7 +4080,10 @@ def process(filename): test_path = path_from_root('tests', 'core', 'test_fwrite_0') src, output = (test_path + s for s in ('.in', '.out')) - self.do_run_from_file(src, output) + orig_args = self.emcc_args if self.emcc_args else [] + for mode in [[], ['-s', 'MEMFS_APPEND_TO_TYPED_ARRAYS=1']]: + self.emcc_args = orig_args + mode + self.do_run_from_file(src, output) def test_fgetc_ungetc(self): src = open(path_from_root('tests', 'stdio', 'test_fgetc_ungetc.c'), 'r').read() @@ -4275,7 +4281,10 @@ def process(filename): if self.emcc_args is None: return self.skip('requires libcxx') test_path = path_from_root('tests', 'core', 'test_wprintf') src, output = (test_path + s for s in ('.c', '.out')) - self.do_run_from_file(src, output) + orig_args = self.emcc_args + for mode in [[], ['-s', 'MEMFS_APPEND_TO_TYPED_ARRAYS=1']]: + self.emcc_args = orig_args + mode + self.do_run_from_file(src, output) def test_direct_string_constant_usage(self): if self.emcc_args is None: return self.skip('requires libcxx') @@ -5150,7 +5159,7 @@ def process(filename): \'\'\' FS.createDataFile('/', 'paper.pdf', eval(Module.read('paper.pdf.js')), true, false); Module.callMain(Module.arguments); - Module.print("Data: " + JSON.stringify(FS.root.contents['filename-1.ppm'].contents.map(function(x) { return unSign(x, 8) }))); + Module.print("Data: " + JSON.stringify(MEMFS.getFileDataAsRegularArray(FS.root.contents['filename-1.ppm']).map(function(x) { return unSign(x, 8) }))); \'\'\' ) src.close() @@ -5200,7 +5209,7 @@ def process(filename): )) ).replace( '// {{POST_RUN_ADDITIONS}}', - "Module.print('Data: ' + JSON.stringify(FS.analyzePath('image.raw').object.contents));" + "Module.print('Data: ' + JSON.stringify(MEMFS.getFileDataAsRegularArray(FS.analyzePath('image.raw').object)));" ) open(filename, 'w').write(src) ''' @@ -6419,10 +6428,15 @@ def process(filename): if (i < 10) throw i; // line 5 } + #include <iostream> + #include <string> + int main() { + std::string x = "ok"; // add libc++ stuff to make this big, test for #2410 int i; scanf("%d", &i); foo(i); + std::cout << x << std::endl; return 0; } ''' diff --git a/tools/ffdb.py b/tools/ffdb.py index 2171cb0e..9734014e 100755 --- a/tools/ffdb.py +++ b/tools/ffdb.py @@ -2,6 +2,7 @@ import socket, json, sys, uuid, datetime, time, logging, cgi, zipfile, os, tempfile, atexit, subprocess, re, base64, struct, imghdr +ADB = 'adb' # Path to the adb executable 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 @@ -62,7 +63,18 @@ def logv(msg): # 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) + try: + if len(read_queue) == 0: + read_queue += b2g_socket.recv(65536*2) + except KeyboardInterrupt: + print ' Aborted by user' + sys.exit(1) + except Exception, e: + if e[0] == 57: # Socket is not connected + print 'Error! Failed to receive data from the device: socket is not connected!' + sys.exit(1) + else: + raise payload = '' while ':' in read_queue: semicolon = read_queue.index(':') @@ -77,6 +89,8 @@ def read_b2g_response(print_errors_to_console = True): # 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'] + else: + break return payload # Sends a command to the B2G device and waits for the response and returns it as a JSON dict. @@ -150,8 +164,269 @@ def print_applist(applist, running_app_manifests, print_removable): num_printed += 1 return num_printed +def adb_devices(): + try: + devices = subprocess.check_output([ADB, 'devices']) + devices = devices.strip().split('\n')[1:] + devices = map(lambda x: x.strip().split('\t'), devices) + return devices + except Exception, e: + return [] + +def b2g_get_prefs_filename(): + return subprocess.check_output([ADB, 'shell', 'echo', '-n', '/data/b2g/mozilla/*.default/prefs.js']) + +def b2g_get_prefs_data(): + return subprocess.check_output([ADB, 'shell', 'cat', '/data/b2g/mozilla/*.default/prefs.js']) + +def b2g_get_pref(sub): + prefs_data = b2g_get_prefs_data().split('\n') + # Filter to find all prefs that have the substring 'sub' in them. + r = re.compile('user_pref\w*\(\w*"([^"]*)"\w*,\w*([^\)]*)') + for line in prefs_data: + m = r.match(line) + if m and (sub is None or sub in m.group(1)): + print m.group(1) + ': ' + m.group(2).strip() + +def b2g_set_pref(pref, value): + prefs_data = b2g_get_prefs_data().split('\n') + # Remove any old value of this pref. + r = re.compile('user_pref\w*\(\w*"([^"]*)"\w*,\w*([^\)]*)') + new_prefs_data = [] + for line in prefs_data: + m = r.match(line) + if not m or m.group(1) != pref: + new_prefs_data += [line] + + if value != None: + print 'Setting pref "' + pref + '" = ' + value + new_prefs_data += ['user_pref("' + pref + '", ' + value + ');'] + else: + print 'Unsetting pref "' + pref + '"' + (oshandle, tempfilename) = tempfile.mkstemp(suffix='.js', prefix='ffdb_temp_') + os.write(oshandle, '\n'.join(new_prefs_data)); + + # Write the new pref + subprocess.check_output([ADB, 'shell', 'stop', 'b2g']) + subprocess.check_output([ADB, 'push', tempfilename, b2g_get_prefs_filename()]) + subprocess.check_output([ADB, 'shell', 'start', 'b2g']) + print 'Rebooting phone...' + + def delete_temp_file(): + os.remove(tempfilename) + atexit.register(delete_temp_file) + +def get_packaged_app_manifest(target_app_path): + if os.path.isdir(target_app_path): + manifest_file = os.path.join(target_app_path, 'manifest.webapp') + if not os.path.isfile(manifest_file): + print "Error: Failed to find FFOS packaged app manifest file '" + manifest_file + "'! That directory does not contain a packaged app?" + sys.exit(1) + return json.loads(open(manifest_file, 'r').read()) + elif target_app_path.endswith('.zip') and os.path.isfile(target_app_path): + try: + z = zipfile.ZipFile(target_app_path, "r") + bytes = z.read('manifest.webapp') + except Exception, e: + print "Error: Failed to read FFOS packaged app manifest file 'manifest.webapp' in zip file '" + target_app_path + "'! Error: " + str(e) + sys.exit(1) + return None + return json.loads(str(bytes)) + else: + print "Error: Path '" + target_app_path + "' is neither a directory or a .zip file to represent the location of a FFOS packaged app!" + sys.exit(1) + return None + +def b2g_install(target_app_path): + 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) + + app_local_id = str(uuid.uuid4()) + reply = send_b2g_cmd(webappsActorName, 'install', { 'appId': app_local_id, '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) + if not 'appId' in reply: + print 'Error: Application install failed! ' + str(reply) + sys.exit() + return reply['appId'] + +def b2g_app_command(app_command, app_name): + apps = b2g_get_appslist() + for app in apps: + if str(app['localId']) == app_name or app['name'] == app_name or app['manifestURL'] == app_name or app['id'] == app_name: + send_b2g_cmd(webappsActorName, app_command, { 'manifestURL': app['manifestURL'] }) + return 0 + print 'Error! Application "' + app_name + '" was not found! Use the \'list\' command to find installed applications.' + return 1 + +def b2g_memory(app_name): + apps = b2g_get_appslist() + appActor = '' + for app in apps: + if str(app['localId']) == app_name or app['name'] == app_name or app['manifestURL'] == app_name or app['id'] == app_name: + appActor = send_b2g_cmd(webappsActorName, 'getAppActor', { 'manifestURL': app['manifestURL'] }) + break + if 'actor' in appActor: + memoryActor = appActor['actor']['memoryActor'] + measure = send_b2g_cmd(memoryActor, 'measure') + for k,v in measure.items(): + if k != 'from': + if k in ['otherSize', 'jsStringsSize', 'jsObjectsSize', 'styleSize', 'jsOtherSize', 'domSize', 'total']: # These are formatted in bytes + print k + ': ' + sizeof_fmt(v) + else: + print k + ': ' + str(v) + +def b2g_log(app_name, clear=False): + apps = b2g_get_appslist() + appActor = '' + for app in apps: + if str(app['localId']) == app_name or app['name'] == app_name or app['manifestURL'] == app_name or app['id'] == app_name: + appActor = send_b2g_cmd(webappsActorName, 'getAppActor', { 'manifestURL': app['manifestURL'] }) + break + if 'actor' in appActor: + consoleActor = appActor['actor']['consoleActor'] + + if clear: + send_b2g_cmd(consoleActor, 'clearMessagesCache') + print 'Cleared message log.' + return 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!' + +def b2g_screenshot(filename): + global deviceActorName + data_reply = send_b2g_cmd(deviceActorName, 'screenshotToDataURL') + data = data_reply['value'] + if not isinstance(data, basestring): # The device is sending the screenshot in multiple fragments since it's too long to fit in one message? + data_get_actor = data['actor'] + data_len = int(data['length']) + data = data['initial'] + chunk_size = 65000 + pos = len(data) + # Pull and assemble individual screenshot fragments. + 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! + + # Expected format is "data:image/png;base64,<base64data>" + delim = re.search(",", data).start() + data_format = data[: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[delim+1:] + + 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] + if check != 0x0d0a1a0a: + return (-1, -1) + return struct.unpack('>ii', head[16:24]) + + width, height = get_png_image_size(filename) + if width <= 0 or height <= 0: + print >> sys.stderr, "Wrote " + sizeof_fmt(len(binary_data)) + " to file '" + filename + "', but the contents may be corrupted!" + else: + print "Wrote " + sizeof_fmt(len(binary_data)) + " to file '" + filename + "' (" + str(width) + 'x' + str(height) + ' pixels).' + +def b2g_get_description(desc): + global deviceActorName + data_reply = send_b2g_cmd(deviceActorName, 'getDescription') + # First try an exact match to requested desc + if desc and desc in data_reply['value']: + print desc + ': ' + str(data_reply['value'][desc]) + else: # Print all with case-insensitive substring search + for k,v in data_reply['value'].items(): + if not desc or desc.lower() in k.lower(): + print k + ': ' + str(v) + def main(): - global b2g_socket, webappsActorName, HOST, PORT, VERBOSE + global b2g_socket, webappsActorName, deviceActorName, HOST, PORT, VERBOSE, ADB 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. @@ -160,17 +435,35 @@ def main(): 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. + launch <app> [--log]: Starts the given application. If already running, brings to front. If the --log option is passed, ffdb will + start persistently logging the execution of the given application. 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. + install <path> [--run] [--log]: 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. + If the --run option is passed, the given application is immediately launched after the installation finishes. + If the --log option is passed, ffdb will start persistently logging the execution of the installed application. 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. + memory <app>: Dumps a memory usage summary for the given application. 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. + get [pref]: Fetches the value of the given developer pref option from the FFOS device and prints it to console. The parameter pref + is optional and may either be the full name of a pref, or a substring to search for. All matching prefs will be printed. + If no pref parameter is given, all prefs are printed. + NOTE: This function (currently at least) only reports prefs that have been explicitly set and don't have their default value. + set <pref> <value>: Writes the given pref option to the FFOS device and restarts the B2G process on it for the change to take effect. + unset <pref>: Removes the given pref option from the FFOS device and restarts the B2G process on it for the change to take effect. + + hide-prompt: Permanently removes the remote debugging connection dialog from showing up, and reboots the phone. This command is + provided for conveniency, and is the same as calling './ffdb.py set devtools.debugger.prompt-connection false' + restore-prompt: Restores the remote debugging connection dialog prompt to its default state. + + desc [desc]: Fetches the value of the given device description field. These fields are read-only and describe the current system. + If the optional desc parameter is omitted, all device descriptions are printed. Otherwise the given description is + printed if it is an exact match, or all descriptions containing desc as the substring are printed. Options: Additionally, the following options may be passed to control FFDB execution: @@ -209,13 +502,23 @@ def main(): sys.argv = filter(lambda x: len(x) > 0, sys.argv) + # Double-check that the device is found via adb: + if (HOST == 'localhost' or HOST == '127.0.0.1') and not connect_to_simulator: + devices = adb_devices() + if len(devices) == 0: + print 'Error! Failed to connect to B2G device debugger socket at address ' + HOST + ':' + str(PORT) + ' and no devices were detected via adb. Please double-check the following and try again: ' + print ' 1) The device is powered on and connected to the computer with an USB cable.' + print ' 2) ADB and DevTools debugging is enabled on the device. (Settings -> Developer -> Debugging via USB: "ADB and DevTools"' + print ' 3) The device is listed when you run "adb devices" on the command line.' + print ' 4) When launching ffdb, remember to acknowledge the "incoming debug connection" dialog if it pops up on the device.' + sys.exit(1) 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'] + 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) + '!' @@ -242,6 +545,9 @@ def main(): logv('Connected. Handshake: ' + str(handshake)) data = send_b2g_cmd('root', 'listTabs') + if not 'deviceActor' in data: + print 'Error! Debugging connection was not available. Make sure that the "Remote debugging" developer option on the device is set to "ADB and Devtools".' + sys.exit(1) deviceActorName = data['deviceActor'] logv('deviceActor: ' + deviceActorName) webappsActorName = data['webappsActor'] @@ -250,9 +556,8 @@ def main(): send_b2g_cmd(deviceActorName, 'getDescription') send_b2g_cmd(deviceActorName, 'getRawPermissionsTable') - apps = b2g_get_appslist() - if sys.argv[1] == 'list': + apps = b2g_get_appslist() running_app_manifests = b2g_get_runningapps() printed_apps = apps print_only_running = '--running' in sys.argv and not '--all' in sys.argv @@ -273,61 +578,27 @@ def main(): 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 + ret = b2g_app_command(sys.argv[1], sys.argv[2]) + if ret == 0 and '--log' in sys.argv: + b2g_log(sys.argv[2]) 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) + # Kill and uninstall old running app execution before starting. + if '--run' in sys.argv: + app_manifest = get_packaged_app_manifest(target_app_path) + b2g_app_command('close', app_manifest['name']) + b2g_app_command('uninstall', app_manifest['name']) + # Upload package + app_id = b2g_install(target_app_path) + # Launch it immediately if requested. + if '--run' in sys.argv: + b2g_app_command('launch', app_id) + # Don't quit, but keep logging the app if requested. + if '--log' in sys.argv: + b2g_log(app_id) elif sys.argv[1] == 'navigate': if len(sys.argv) < 3: print 'Error! No URL given! Usage: ' + sys.argv[0] + ' ' + sys.argv[1] + ' <url>' @@ -343,55 +614,10 @@ def main(): 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!' + clear = '-c' in sys.argv or '-clear' in sys.argv or '--clear' in sys.argv + b2g_log(sys.argv[2], clear) + elif sys.argv[1] == 'memory': + b2g_memory(sys.argv[2]) elif sys.argv[1] == 'screenshot': if len(sys.argv) >= 3: filename = sys.argv[2] @@ -401,46 +627,31 @@ def main(): 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 + "'!" + b2g_screenshot(filename) + elif sys.argv[1] == 'get': + b2g_get_pref(sys.argv[2] if len(sys.argv) >= 3 else None) + elif sys.argv[1] == 'set': + if len(sys.argv) < 3: + print 'Error! No pref name to set given! Usage: ' + sys.argv[0] + ' ' + sys.argv[1] + ' <pref> <value>' 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] - if check != 0x0d0a1a0a: - return (-1, -1) - return struct.unpack('>ii', head[16:24]) - - width, height = get_png_image_size(filename) - if width <= 0 or height <= 0: - print >> sys.stderr, "Wrote " + sizeof_fmt(len(binary_data)) + " to file '" + filename + "', but the contents may be corrupted!" - else: - print "Wrote " + sizeof_fmt(len(binary_data)) + " to file '" + filename + "' (" + str(width) + 'x' + str(height) + ' pixels).' + if len(sys.argv) < 4: + print 'Error! No value given to set! Usage: ' + sys.argv[0] + ' ' + sys.argv[1] + ' <pref> <value>' + sys.exit(1) + if len(sys.argv) > 4: + print 'Error! Too many arguments given (' + str(sys.argv) + '), need exactly four! Usage: ' + sys.argv[0] + ' ' + sys.argv[1] + ' <pref> <value>' + sys.exit(1) + b2g_set_pref(sys.argv[2], sys.argv[3]) + elif sys.argv[1] == 'unset': + if len(sys.argv) < 3: + print 'Error! No pref name given! Usage: ' + sys.argv[0] + ' ' + sys.argv[1] + ' <pref>' + sys.exit(1) + b2g_set_pref(sys.argv[2], None) + elif sys.argv[1] == 'hide-prompt': + b2g_set_pref('devtools.debugger.prompt-connection', 'false') + elif sys.argv[1] == 'restore-prompt': + b2g_set_pref('devtools.debugger.prompt-connection', None) + elif sys.argv[1] == 'desc': + b2g_get_description(sys.argv[2] if len(sys.argv) >= 3 else None) else: print "Unknown command '" + sys.argv[1] + "'! Pass --help for instructions." diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 9b8387be..d3121bc8 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -128,10 +128,8 @@ load('utility.js'); // Utilities -var FUNCTION = set('defun', 'function'); var LOOP = set('do', 'while', 'for'); var LOOP_FLOW = set('break', 'continue'); -var ASSIGN_OR_ALTER = set('assign', 'unary-postfix', 'unary-prefix'); var CONTROL_FLOW = set('do', 'while', 'for', 'if', 'switch'); var NAME_OR_NUM = set('name', 'num'); var ASSOCIATIVE_BINARIES = set('+', '*', '|', '&', '^'); @@ -145,10 +143,7 @@ var COMMABLE = set('assign', 'binary', 'unary-prefix', 'unary-postfix', 'name', var FUNCTIONS_THAT_ALWAYS_THROW = set('abort', '___resumeException', '___cxa_throw', '___cxa_rethrow'); -var NULL_NODE = ['name', 'null']; var UNDEFINED_NODE = ['unary-prefix', 'void', ['num', 0]]; -var TRUE_NODE = ['unary-prefix', '!', ['num', 0]]; -var FALSE_NODE = ['unary-prefix', '!', ['num', 1]]; var GENERATED_FUNCTIONS_MARKER = '// EMSCRIPTEN_GENERATED_FUNCTIONS'; var generatedFunctions = false; // whether we have received only generated functions diff --git a/tools/shared.py b/tools/shared.py index 5ef1c4ef..eda58304 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -706,10 +706,8 @@ if USE_EMSDK: path_from_root('system', 'include', 'compat'), path_from_root('system', 'include'), path_from_root('system', 'include', 'emscripten'), - path_from_root('system', 'include', 'bsd'), # posix stuff path_from_root('system', 'include', 'libc'), path_from_root('system', 'include', 'gfx'), - path_from_root('system', 'include', 'net'), path_from_root('system', 'include', 'SDL'), ] |