1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/tests/robocop_head.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1233 @@ 1.4 +/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim:set ts=2 sw=2 sts=2 et: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +/* 1.11 + * This file contains common code that is loaded before each test file(s). 1.12 + * See http://developer.mozilla.org/en/docs/Writing_xpcshell-based_unit_tests 1.13 + * for more information. 1.14 + */ 1.15 + 1.16 +const _XPCSHELL_TIMEOUT_MS = 5 * 60 * 1000; 1.17 + 1.18 +var _quit = false; 1.19 +var _passed = true; 1.20 +var _tests_pending = 0; 1.21 +var _passedChecks = 0, _falsePassedChecks = 0; 1.22 +var _todoChecks = 0; 1.23 +var _cleanupFunctions = []; 1.24 +var _pendingTimers = []; 1.25 +var _profileInitialized = false; 1.26 + 1.27 +function _dump(str) { 1.28 + let start = /^TEST-/.test(str) ? "\n" : ""; 1.29 + if (typeof _XPCSHELL_PROCESS == "undefined") { 1.30 + dump(start + str); 1.31 + } else { 1.32 + dump(start + _XPCSHELL_PROCESS + ": " + str); 1.33 + } 1.34 +} 1.35 + 1.36 +// Disable automatic network detection, so tests work correctly when 1.37 +// not connected to a network. 1.38 +let (ios = Components.classes["@mozilla.org/network/io-service;1"] 1.39 + .getService(Components.interfaces.nsIIOService2)) { 1.40 + ios.manageOfflineStatus = false; 1.41 + ios.offline = false; 1.42 +} 1.43 + 1.44 +// Determine if we're running on parent or child 1.45 +let runningInParent = true; 1.46 +try { 1.47 + runningInParent = Components.classes["@mozilla.org/xre/runtime;1"]. 1.48 + getService(Components.interfaces.nsIXULRuntime).processType 1.49 + == Components.interfaces.nsIXULRuntime.PROCESS_TYPE_DEFAULT; 1.50 +} 1.51 +catch (e) { } 1.52 + 1.53 +try { 1.54 + if (runningInParent) { 1.55 + let prefs = Components.classes["@mozilla.org/preferences-service;1"] 1.56 + .getService(Components.interfaces.nsIPrefBranch); 1.57 + 1.58 + // disable necko IPC security checks for xpcshell, as they lack the 1.59 + // docshells needed to pass them 1.60 + prefs.setBoolPref("network.disable.ipc.security", true); 1.61 + 1.62 + // Disable IPv6 lookups for 'localhost' on windows. 1.63 + if ("@mozilla.org/windows-registry-key;1" in Components.classes) { 1.64 + prefs.setCharPref("network.dns.ipv4OnlyDomains", "localhost"); 1.65 + } 1.66 + } 1.67 +} 1.68 +catch (e) { } 1.69 + 1.70 +// Enable crash reporting, if possible 1.71 +// We rely on the Python harness to set MOZ_CRASHREPORTER_NO_REPORT 1.72 +// and handle checking for minidumps. 1.73 +// Note that if we're in a child process, we don't want to init the 1.74 +// crashreporter component. 1.75 +try { // nsIXULRuntime is not available in some configurations. 1.76 + if (runningInParent && 1.77 + "@mozilla.org/toolkit/crash-reporter;1" in Components.classes) { 1.78 + // Remember to update </toolkit/crashreporter/test/unit/test_crashreporter.js> 1.79 + // too if you change this initial setting. 1.80 + let (crashReporter = 1.81 + Components.classes["@mozilla.org/toolkit/crash-reporter;1"] 1.82 + .getService(Components.interfaces.nsICrashReporter)) { 1.83 + crashReporter.enabled = true; 1.84 + crashReporter.minidumpPath = do_get_cwd(); 1.85 + } 1.86 + } 1.87 +} 1.88 +catch (e) { } 1.89 + 1.90 +/** 1.91 + * Date.now() is not necessarily monotonically increasing (insert sob story 1.92 + * about times not being the right tool to use for measuring intervals of time, 1.93 + * robarnold can tell all), so be wary of error by erring by at least 1.94 + * _timerFuzz ms. 1.95 + */ 1.96 +const _timerFuzz = 15; 1.97 + 1.98 +function _Timer(func, delay) { 1.99 + delay = Number(delay); 1.100 + if (delay < 0) 1.101 + do_throw("do_timeout() delay must be nonnegative"); 1.102 + 1.103 + if (typeof func !== "function") 1.104 + do_throw("string callbacks no longer accepted; use a function!"); 1.105 + 1.106 + this._func = func; 1.107 + this._start = Date.now(); 1.108 + this._delay = delay; 1.109 + 1.110 + var timer = Components.classes["@mozilla.org/timer;1"] 1.111 + .createInstance(Components.interfaces.nsITimer); 1.112 + timer.initWithCallback(this, delay + _timerFuzz, timer.TYPE_ONE_SHOT); 1.113 + 1.114 + // Keep timer alive until it fires 1.115 + _pendingTimers.push(timer); 1.116 +} 1.117 +_Timer.prototype = { 1.118 + QueryInterface: function(iid) { 1.119 + if (iid.equals(Components.interfaces.nsITimerCallback) || 1.120 + iid.equals(Components.interfaces.nsISupports)) 1.121 + return this; 1.122 + 1.123 + throw Components.results.NS_ERROR_NO_INTERFACE; 1.124 + }, 1.125 + 1.126 + notify: function(timer) { 1.127 + _pendingTimers.splice(_pendingTimers.indexOf(timer), 1); 1.128 + 1.129 + // The current nsITimer implementation can undershoot, but even if it 1.130 + // couldn't, paranoia is probably a virtue here given the potential for 1.131 + // random orange on tinderboxen. 1.132 + var end = Date.now(); 1.133 + var elapsed = end - this._start; 1.134 + if (elapsed >= this._delay) { 1.135 + try { 1.136 + this._func.call(null); 1.137 + } catch (e) { 1.138 + do_throw("exception thrown from do_timeout callback: " + e); 1.139 + } 1.140 + return; 1.141 + } 1.142 + 1.143 + // Timer undershot, retry with a little overshoot to try to avoid more 1.144 + // undershoots. 1.145 + var newDelay = this._delay - elapsed; 1.146 + do_timeout(newDelay, this._func); 1.147 + } 1.148 +}; 1.149 + 1.150 +function _do_main() { 1.151 + if (_quit) 1.152 + return; 1.153 + 1.154 + _dump("TEST-INFO | (xpcshell/head.js) | running event loop\n"); 1.155 + 1.156 + var thr = Components.classes["@mozilla.org/thread-manager;1"] 1.157 + .getService().currentThread; 1.158 + 1.159 + while (!_quit) 1.160 + thr.processNextEvent(true); 1.161 + 1.162 + while (thr.hasPendingEvents()) 1.163 + thr.processNextEvent(true); 1.164 +} 1.165 + 1.166 +function _do_quit() { 1.167 + _dump("TEST-INFO | (xpcshell/head.js) | exiting test\n"); 1.168 + 1.169 + _quit = true; 1.170 +} 1.171 + 1.172 +function _dump_exception_stack(stack) { 1.173 + stack.split("\n").forEach(function(frame) { 1.174 + if (!frame) 1.175 + return; 1.176 + // frame is of the form "fname(args)@file:line" 1.177 + let frame_regexp = new RegExp("(.*)\\(.*\\)@(.*):(\\d*)", "g"); 1.178 + let parts = frame_regexp.exec(frame); 1.179 + if (parts) 1.180 + dump("JS frame :: " + parts[2] + " :: " + (parts[1] ? parts[1] : "anonymous") 1.181 + + " :: line " + parts[3] + "\n"); 1.182 + else /* Could be a -e (command line string) style location. */ 1.183 + dump("JS frame :: " + frame + "\n"); 1.184 + }); 1.185 +} 1.186 + 1.187 +/** 1.188 + * Overrides idleService with a mock. Idle is commonly used for maintenance 1.189 + * tasks, thus if a test uses a service that requires the idle service, it will 1.190 + * start handling them. 1.191 + * This behaviour would cause random failures and slowdown tests execution, 1.192 + * for example by running database vacuum or cleanups for each test. 1.193 + * 1.194 + * @note Idle service is overridden by default. If a test requires it, it will 1.195 + * have to call do_get_idle() function at least once before use. 1.196 + */ 1.197 +var _fakeIdleService = { 1.198 + get registrar() { 1.199 + delete this.registrar; 1.200 + return this.registrar = 1.201 + Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar); 1.202 + }, 1.203 + contractID: "@mozilla.org/widget/idleservice;1", 1.204 + get CID() this.registrar.contractIDToCID(this.contractID), 1.205 + 1.206 + activate: function FIS_activate() 1.207 + { 1.208 + if (!this.originalFactory) { 1.209 + // Save original factory. 1.210 + this.originalFactory = 1.211 + Components.manager.getClassObject(Components.classes[this.contractID], 1.212 + Components.interfaces.nsIFactory); 1.213 + // Unregister original factory. 1.214 + this.registrar.unregisterFactory(this.CID, this.originalFactory); 1.215 + // Replace with the mock. 1.216 + this.registrar.registerFactory(this.CID, "Fake Idle Service", 1.217 + this.contractID, this.factory 1.218 + ); 1.219 + } 1.220 + }, 1.221 + 1.222 + deactivate: function FIS_deactivate() 1.223 + { 1.224 + if (this.originalFactory) { 1.225 + // Unregister the mock. 1.226 + this.registrar.unregisterFactory(this.CID, this.factory); 1.227 + // Restore original factory. 1.228 + this.registrar.registerFactory(this.CID, "Idle Service", 1.229 + this.contractID, this.originalFactory); 1.230 + delete this.originalFactory; 1.231 + } 1.232 + }, 1.233 + 1.234 + factory: { 1.235 + // nsIFactory 1.236 + createInstance: function (aOuter, aIID) 1.237 + { 1.238 + if (aOuter) { 1.239 + throw Components.results.NS_ERROR_NO_AGGREGATION; 1.240 + } 1.241 + return _fakeIdleService.QueryInterface(aIID); 1.242 + }, 1.243 + lockFactory: function (aLock) { 1.244 + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; 1.245 + }, 1.246 + QueryInterface: function(aIID) { 1.247 + if (aIID.equals(Components.interfaces.nsIFactory) || 1.248 + aIID.equals(Components.interfaces.nsISupports)) { 1.249 + return this; 1.250 + } 1.251 + throw Components.results.NS_ERROR_NO_INTERFACE; 1.252 + } 1.253 + }, 1.254 + 1.255 + // nsIIdleService 1.256 + get idleTime() 0, 1.257 + addIdleObserver: function () {}, 1.258 + removeIdleObserver: function () {}, 1.259 + 1.260 + QueryInterface: function(aIID) { 1.261 + // Useful for testing purposes, see test_get_idle.js. 1.262 + if (aIID.equals(Components.interfaces.nsIFactory)) { 1.263 + return this.factory; 1.264 + } 1.265 + if (aIID.equals(Components.interfaces.nsIIdleService) || 1.266 + aIID.equals(Components.interfaces.nsISupports)) { 1.267 + return this; 1.268 + } 1.269 + throw Components.results.NS_ERROR_NO_INTERFACE; 1.270 + } 1.271 +} 1.272 + 1.273 +/** 1.274 + * Restores the idle service factory if needed and returns the service's handle. 1.275 + * @return A handle to the idle service. 1.276 + */ 1.277 +function do_get_idle() { 1.278 + _fakeIdleService.deactivate(); 1.279 + return Components.classes[_fakeIdleService.contractID] 1.280 + .getService(Components.interfaces.nsIIdleService); 1.281 +} 1.282 + 1.283 +// Map resource://test/ to current working directory and 1.284 +// resource://testing-common/ to the shared test modules directory. 1.285 +function _register_protocol_handlers() { 1.286 + let (ios = Components.classes["@mozilla.org/network/io-service;1"] 1.287 + .getService(Components.interfaces.nsIIOService)) { 1.288 + let protocolHandler = 1.289 + ios.getProtocolHandler("resource") 1.290 + .QueryInterface(Components.interfaces.nsIResProtocolHandler); 1.291 + let curDirURI = ios.newFileURI(do_get_cwd()); 1.292 + protocolHandler.setSubstitution("test", curDirURI); 1.293 + 1.294 + if (this._TESTING_MODULES_DIR) { 1.295 + let modulesFile = Components.classes["@mozilla.org/file/local;1"]. 1.296 + createInstance(Components.interfaces.nsILocalFile); 1.297 + modulesFile.initWithPath(_TESTING_MODULES_DIR); 1.298 + 1.299 + if (!modulesFile.exists()) { 1.300 + throw new Error("Specified modules directory does not exist: " + 1.301 + _TESTING_MODULES_DIR); 1.302 + } 1.303 + 1.304 + if (!modulesFile.isDirectory()) { 1.305 + throw new Error("Specified modules directory is not a directory: " + 1.306 + _TESTING_MODULES_DIR); 1.307 + } 1.308 + 1.309 + let modulesURI = ios.newFileURI(modulesFile); 1.310 + 1.311 + protocolHandler.setSubstitution("testing-common", modulesURI); 1.312 + } 1.313 + } 1.314 +} 1.315 + 1.316 +function _execute_test() { 1.317 + _register_protocol_handlers(); 1.318 + 1.319 + // Override idle service by default. 1.320 + // Call do_get_idle() to restore the factory and get the service. 1.321 + _fakeIdleService.activate(); 1.322 + 1.323 + // Terminate asynchronous tests when a global timeout occurs. 1.324 + do_timeout(_XPCSHELL_TIMEOUT_MS, function _do_main_timeout() { 1.325 + try { 1.326 + do_throw("test timed out"); 1.327 + } catch (e if e == Components.results.NS_ERROR_ABORT) { 1.328 + // We don't want do_timeout to report the do_throw exception again. 1.329 + } 1.330 + }); 1.331 + 1.332 + // _HEAD_FILES is dynamically defined by <runxpcshelltests.py>. 1.333 + _load_files(_HEAD_FILES); 1.334 + // _TEST_FILE is dynamically defined by <runxpcshelltests.py>. 1.335 + _load_files(_TEST_FILE); 1.336 + 1.337 + try { 1.338 + do_test_pending(); 1.339 + run_test(); 1.340 + do_test_finished(); 1.341 + _do_main(); 1.342 + } catch (e) { 1.343 + _passed = false; 1.344 + // do_check failures are already logged and set _quit to true and throw 1.345 + // NS_ERROR_ABORT. If both of those are true it is likely this exception 1.346 + // has already been logged so there is no need to log it again. It's 1.347 + // possible that this will mask an NS_ERROR_ABORT that happens after a 1.348 + // do_check failure though. 1.349 + if (!_quit || e != Components.results.NS_ERROR_ABORT) { 1.350 + msg = "TEST-UNEXPECTED-FAIL | "; 1.351 + if (e.fileName) { 1.352 + msg += e.fileName; 1.353 + if (e.lineNumber) { 1.354 + msg += ":" + e.lineNumber; 1.355 + } 1.356 + } else { 1.357 + msg += "xpcshell/head.js"; 1.358 + } 1.359 + msg += " | " + e; 1.360 + if (e.stack) { 1.361 + _dump(msg + " - See following stack:\n"); 1.362 + _dump_exception_stack(e.stack); 1.363 + } 1.364 + else { 1.365 + _dump(msg + "\n"); 1.366 + } 1.367 + } 1.368 + } 1.369 + 1.370 + // _TAIL_FILES is dynamically defined by <runxpcshelltests.py>. 1.371 + _load_files(_TAIL_FILES); 1.372 + 1.373 + // Execute all of our cleanup functions. 1.374 + var func; 1.375 + while ((func = _cleanupFunctions.pop())) 1.376 + func(); 1.377 + 1.378 + // Restore idle service to avoid leaks. 1.379 + _fakeIdleService.deactivate(); 1.380 + 1.381 + if (!_passed) 1.382 + return; 1.383 + 1.384 + var truePassedChecks = _passedChecks - _falsePassedChecks; 1.385 + if (truePassedChecks > 0) { 1.386 + _dump("TEST-PASS | (xpcshell/head.js) | " + truePassedChecks + " (+ " + 1.387 + _falsePassedChecks + ") check(s) passed\n"); 1.388 + _dump("TEST-INFO | (xpcshell/head.js) | " + _todoChecks + 1.389 + " check(s) todo\n"); 1.390 + } else { 1.391 + // ToDo: switch to TEST-UNEXPECTED-FAIL when all tests have been updated. (Bug 496443) 1.392 + _dump("TEST-INFO | (xpcshell/head.js) | No (+ " + _falsePassedChecks + ") checks actually run\n"); 1.393 + } 1.394 +} 1.395 + 1.396 +/** 1.397 + * Loads files. 1.398 + * 1.399 + * @param aFiles Array of files to load. 1.400 + */ 1.401 +function _load_files(aFiles) { 1.402 + function loadTailFile(element, index, array) { 1.403 + load(element); 1.404 + } 1.405 + 1.406 + aFiles.forEach(loadTailFile); 1.407 +} 1.408 + 1.409 + 1.410 +/************** Functions to be used from the tests **************/ 1.411 + 1.412 +/** 1.413 + * Prints a message to the output log. 1.414 + */ 1.415 +function do_print(msg) { 1.416 + var caller_stack = Components.stack.caller; 1.417 + _dump("TEST-INFO | " + caller_stack.filename + " | " + msg + "\n"); 1.418 +} 1.419 + 1.420 +/** 1.421 + * Calls the given function at least the specified number of milliseconds later. 1.422 + * The callback will not undershoot the given time, but it might overshoot -- 1.423 + * don't expect precision! 1.424 + * 1.425 + * @param delay : uint 1.426 + * the number of milliseconds to delay 1.427 + * @param callback : function() : void 1.428 + * the function to call 1.429 + */ 1.430 +function do_timeout(delay, func) { 1.431 + new _Timer(func, Number(delay)); 1.432 +} 1.433 + 1.434 +function do_execute_soon(callback) { 1.435 + do_test_pending(); 1.436 + var tm = Components.classes["@mozilla.org/thread-manager;1"] 1.437 + .getService(Components.interfaces.nsIThreadManager); 1.438 + 1.439 + tm.mainThread.dispatch({ 1.440 + run: function() { 1.441 + try { 1.442 + callback(); 1.443 + } catch (e) { 1.444 + // do_check failures are already logged and set _quit to true and throw 1.445 + // NS_ERROR_ABORT. If both of those are true it is likely this exception 1.446 + // has already been logged so there is no need to log it again. It's 1.447 + // possible that this will mask an NS_ERROR_ABORT that happens after a 1.448 + // do_check failure though. 1.449 + if (!_quit || e != Components.results.NS_ERROR_ABORT) { 1.450 + _dump("TEST-UNEXPECTED-FAIL | (xpcshell/head.js) | " + e); 1.451 + if (e.stack) { 1.452 + dump(" - See following stack:\n"); 1.453 + _dump_exception_stack(e.stack); 1.454 + } 1.455 + else { 1.456 + dump("\n"); 1.457 + } 1.458 + _do_quit(); 1.459 + } 1.460 + } 1.461 + finally { 1.462 + do_test_finished(); 1.463 + } 1.464 + } 1.465 + }, Components.interfaces.nsIThread.DISPATCH_NORMAL); 1.466 +} 1.467 + 1.468 +function do_throw(text, stack) { 1.469 + if (!stack) 1.470 + stack = Components.stack.caller; 1.471 + 1.472 + _passed = false; 1.473 + _dump("TEST-UNEXPECTED-FAIL | " + stack.filename + " | " + text + 1.474 + " - See following stack:\n"); 1.475 + var frame = Components.stack; 1.476 + while (frame != null) { 1.477 + _dump(frame + "\n"); 1.478 + frame = frame.caller; 1.479 + } 1.480 + 1.481 + _do_quit(); 1.482 + throw Components.results.NS_ERROR_ABORT; 1.483 +} 1.484 + 1.485 +function do_throw_todo(text, stack) { 1.486 + if (!stack) 1.487 + stack = Components.stack.caller; 1.488 + 1.489 + _passed = false; 1.490 + _dump("TEST-UNEXPECTED-PASS | " + stack.filename + " | " + text + 1.491 + " - See following stack:\n"); 1.492 + var frame = Components.stack; 1.493 + while (frame != null) { 1.494 + _dump(frame + "\n"); 1.495 + frame = frame.caller; 1.496 + } 1.497 + 1.498 + _do_quit(); 1.499 + throw Components.results.NS_ERROR_ABORT; 1.500 +} 1.501 + 1.502 +function do_report_unexpected_exception(ex, text) { 1.503 + var caller_stack = Components.stack.caller; 1.504 + text = text ? text + " - " : ""; 1.505 + 1.506 + _passed = false; 1.507 + _dump("TEST-UNEXPECTED-FAIL | " + caller_stack.filename + " | " + text + 1.508 + "Unexpected exception " + ex + ", see following stack:\n" + ex.stack + 1.509 + "\n"); 1.510 + 1.511 + _do_quit(); 1.512 + throw Components.results.NS_ERROR_ABORT; 1.513 +} 1.514 + 1.515 +function do_note_exception(ex, text) { 1.516 + var caller_stack = Components.stack.caller; 1.517 + text = text ? text + " - " : ""; 1.518 + 1.519 + _dump("TEST-INFO | " + caller_stack.filename + " | " + text + 1.520 + "Swallowed exception " + ex + ", see following stack:\n" + ex.stack + 1.521 + "\n"); 1.522 +} 1.523 + 1.524 +function _do_check_neq(left, right, stack, todo) { 1.525 + if (!stack) 1.526 + stack = Components.stack.caller; 1.527 + 1.528 + var text = left + " != " + right; 1.529 + if (left == right) { 1.530 + if (!todo) { 1.531 + do_throw(text, stack); 1.532 + } else { 1.533 + ++_todoChecks; 1.534 + _dump("TEST-KNOWN-FAIL | " + stack.filename + " | [" + stack.name + 1.535 + " : " + stack.lineNumber + "] " + text +"\n"); 1.536 + } 1.537 + } else { 1.538 + if (!todo) { 1.539 + ++_passedChecks; 1.540 + _dump("TEST-PASS | " + stack.filename + " | [" + stack.name + " : " + 1.541 + stack.lineNumber + "] " + text + "\n"); 1.542 + } else { 1.543 + do_throw_todo(text, stack); 1.544 + } 1.545 + } 1.546 +} 1.547 + 1.548 +function do_check_neq(left, right, stack) { 1.549 + if (!stack) 1.550 + stack = Components.stack.caller; 1.551 + 1.552 + _do_check_neq(left, right, stack, false); 1.553 +} 1.554 + 1.555 +function todo_check_neq(left, right, stack) { 1.556 + if (!stack) 1.557 + stack = Components.stack.caller; 1.558 + 1.559 + _do_check_neq(left, right, stack, true); 1.560 +} 1.561 + 1.562 +function do_report_result(passed, text, stack, todo) { 1.563 + if (passed) { 1.564 + if (todo) { 1.565 + do_throw_todo(text, stack); 1.566 + } else { 1.567 + ++_passedChecks; 1.568 + _dump("TEST-PASS | " + stack.filename + " | [" + stack.name + " : " + 1.569 + stack.lineNumber + "] " + text + "\n"); 1.570 + } 1.571 + } else { 1.572 + if (todo) { 1.573 + ++_todoChecks; 1.574 + _dump("TEST-KNOWN-FAIL | " + stack.filename + " | [" + stack.name + 1.575 + " : " + stack.lineNumber + "] " + text +"\n"); 1.576 + } else { 1.577 + do_throw(text, stack); 1.578 + } 1.579 + } 1.580 +} 1.581 + 1.582 +function _do_check_eq(left, right, stack, todo) { 1.583 + if (!stack) 1.584 + stack = Components.stack.caller; 1.585 + 1.586 + var text = left + " == " + right; 1.587 + do_report_result(left == right, text, stack, todo); 1.588 +} 1.589 + 1.590 +function do_check_eq(left, right, stack) { 1.591 + if (!stack) 1.592 + stack = Components.stack.caller; 1.593 + 1.594 + _do_check_eq(left, right, stack, false); 1.595 +} 1.596 + 1.597 +function todo_check_eq(left, right, stack) { 1.598 + if (!stack) 1.599 + stack = Components.stack.caller; 1.600 + 1.601 + _do_check_eq(left, right, stack, true); 1.602 +} 1.603 + 1.604 +function do_check_true(condition, stack) { 1.605 + if (!stack) 1.606 + stack = Components.stack.caller; 1.607 + 1.608 + do_check_eq(condition, true, stack); 1.609 +} 1.610 + 1.611 +function todo_check_true(condition, stack) { 1.612 + if (!stack) 1.613 + stack = Components.stack.caller; 1.614 + 1.615 + todo_check_eq(condition, true, stack); 1.616 +} 1.617 + 1.618 +function do_check_false(condition, stack) { 1.619 + if (!stack) 1.620 + stack = Components.stack.caller; 1.621 + 1.622 + do_check_eq(condition, false, stack); 1.623 +} 1.624 + 1.625 +function todo_check_false(condition, stack) { 1.626 + if (!stack) 1.627 + stack = Components.stack.caller; 1.628 + 1.629 + todo_check_eq(condition, false, stack); 1.630 +} 1.631 + 1.632 +function do_check_null(condition, stack=Components.stack.caller) { 1.633 + do_check_eq(condition, null, stack); 1.634 +} 1.635 + 1.636 +function todo_check_null(condition, stack=Components.stack.caller) { 1.637 + todo_check_eq(condition, null, stack); 1.638 +} 1.639 + 1.640 +/** 1.641 + * Check that |value| matches |pattern|. 1.642 + * 1.643 + * A |value| matches a pattern |pattern| if any one of the following is true: 1.644 + * 1.645 + * - |value| and |pattern| are both objects; |pattern|'s enumerable 1.646 + * properties' values are valid patterns; and for each enumerable 1.647 + * property |p| of |pattern|, plus 'length' if present at all, |value| 1.648 + * has a property |p| whose value matches |pattern.p|. Note that if |j| 1.649 + * has other properties not present in |p|, |j| may still match |p|. 1.650 + * 1.651 + * - |value| and |pattern| are equal string, numeric, or boolean literals 1.652 + * 1.653 + * - |pattern| is |undefined| (this is a wildcard pattern) 1.654 + * 1.655 + * - typeof |pattern| == "function", and |pattern(value)| is true. 1.656 + * 1.657 + * For example: 1.658 + * 1.659 + * do_check_matches({x:1}, {x:1}) // pass 1.660 + * do_check_matches({x:1}, {}) // fail: all pattern props required 1.661 + * do_check_matches({x:1}, {x:2}) // fail: values must match 1.662 + * do_check_matches({x:1}, {x:1, y:2}) // pass: extra props tolerated 1.663 + * 1.664 + * // Property order is irrelevant. 1.665 + * do_check_matches({x:"foo", y:"bar"}, {y:"bar", x:"foo"}) // pass 1.666 + * 1.667 + * do_check_matches({x:undefined}, {x:1}) // pass: 'undefined' is wildcard 1.668 + * do_check_matches({x:undefined}, {x:2}) 1.669 + * do_check_matches({x:undefined}, {y:2}) // fail: 'x' must still be there 1.670 + * 1.671 + * // Patterns nest. 1.672 + * do_check_matches({a:1, b:{c:2,d:undefined}}, {a:1, b:{c:2,d:3}}) 1.673 + * 1.674 + * // 'length' property counts, even if non-enumerable. 1.675 + * do_check_matches([3,4,5], [3,4,5]) // pass 1.676 + * do_check_matches([3,4,5], [3,5,5]) // fail; value doesn't match 1.677 + * do_check_matches([3,4,5], [3,4,5,6]) // fail; length doesn't match 1.678 + * 1.679 + * // functions in patterns get applied. 1.680 + * do_check_matches({foo:function (v) v.length == 2}, {foo:"hi"}) // pass 1.681 + * do_check_matches({foo:function (v) v.length == 2}, {bar:"hi"}) // fail 1.682 + * do_check_matches({foo:function (v) v.length == 2}, {foo:"hello"}) // fail 1.683 + * 1.684 + * // We don't check constructors, prototypes, or classes. However, if 1.685 + * // pattern has a 'length' property, we require values to match that as 1.686 + * // well, even if 'length' is non-enumerable in the pattern. So arrays 1.687 + * // are useful as patterns. 1.688 + * do_check_matches({0:0, 1:1, length:2}, [0,1]) // pass 1.689 + * do_check_matches({0:1}, [1,2]) // pass 1.690 + * do_check_matches([0], {0:0, length:1}) // pass 1.691 + * 1.692 + * Notes: 1.693 + * 1.694 + * The 'length' hack gives us reasonably intuitive handling of arrays. 1.695 + * 1.696 + * This is not a tight pattern-matcher; it's only good for checking data 1.697 + * from well-behaved sources. For example: 1.698 + * - By default, we don't mind values having extra properties. 1.699 + * - We don't check for proxies or getters. 1.700 + * - We don't check the prototype chain. 1.701 + * However, if you know the values are, say, JSON, which is pretty 1.702 + * well-behaved, and if you want to tolerate additional properties 1.703 + * appearing on the JSON for backward-compatibility, then do_check_matches 1.704 + * is ideal. If you do want to be more careful, you can use function 1.705 + * patterns to implement more stringent checks. 1.706 + */ 1.707 +function do_check_matches(pattern, value, stack=Components.stack.caller, todo=false) { 1.708 + var matcher = pattern_matcher(pattern); 1.709 + var text = "VALUE: " + uneval(value) + "\nPATTERN: " + uneval(pattern) + "\n"; 1.710 + var diagnosis = [] 1.711 + if (matcher(value, diagnosis)) { 1.712 + do_report_result(true, "value matches pattern:\n" + text, stack, todo); 1.713 + } else { 1.714 + text = ("value doesn't match pattern:\n" + 1.715 + text + 1.716 + "DIAGNOSIS: " + 1.717 + format_pattern_match_failure(diagnosis[0]) + "\n"); 1.718 + do_report_result(false, text, stack, todo); 1.719 + } 1.720 +} 1.721 + 1.722 +function todo_check_matches(pattern, value, stack=Components.stack.caller) { 1.723 + do_check_matches(pattern, value, stack, true); 1.724 +} 1.725 + 1.726 +// Return a pattern-matching function of one argument, |value|, that 1.727 +// returns true if |value| matches |pattern|. 1.728 +// 1.729 +// If the pattern doesn't match, and the pattern-matching function was 1.730 +// passed its optional |diagnosis| argument, the pattern-matching function 1.731 +// sets |diagnosis|'s '0' property to a JSON-ish description of the portion 1.732 +// of the pattern that didn't match, which can be formatted legibly by 1.733 +// format_pattern_match_failure. 1.734 +function pattern_matcher(pattern) { 1.735 + function explain(diagnosis, reason) { 1.736 + if (diagnosis) { 1.737 + diagnosis[0] = reason; 1.738 + } 1.739 + return false; 1.740 + } 1.741 + if (typeof pattern == "function") { 1.742 + return pattern; 1.743 + } else if (typeof pattern == "object" && pattern) { 1.744 + var matchers = [[p, pattern_matcher(pattern[p])] for (p in pattern)]; 1.745 + // Kludge: include 'length', if not enumerable. (If it is enumerable, 1.746 + // we picked it up in the array comprehension, above. 1.747 + ld = Object.getOwnPropertyDescriptor(pattern, 'length'); 1.748 + if (ld && !ld.enumerable) { 1.749 + matchers.push(['length', pattern_matcher(pattern.length)]) 1.750 + } 1.751 + return function (value, diagnosis) { 1.752 + if (!(value && typeof value == "object")) { 1.753 + return explain(diagnosis, "value not object"); 1.754 + } 1.755 + for (let [p, m] of matchers) { 1.756 + var element_diagnosis = []; 1.757 + if (!(p in value && m(value[p], element_diagnosis))) { 1.758 + return explain(diagnosis, { property:p, 1.759 + diagnosis:element_diagnosis[0] }); 1.760 + } 1.761 + } 1.762 + return true; 1.763 + }; 1.764 + } else if (pattern === undefined) { 1.765 + return function(value) { return true; }; 1.766 + } else { 1.767 + return function (value, diagnosis) { 1.768 + if (value !== pattern) { 1.769 + return explain(diagnosis, "pattern " + uneval(pattern) + " not === to value " + uneval(value)); 1.770 + } 1.771 + return true; 1.772 + }; 1.773 + } 1.774 +} 1.775 + 1.776 +// Format an explanation for a pattern match failure, as stored in the 1.777 +// second argument to a matching function. 1.778 +function format_pattern_match_failure(diagnosis, indent="") { 1.779 + var a; 1.780 + if (!diagnosis) { 1.781 + a = "Matcher did not explain reason for mismatch."; 1.782 + } else if (typeof diagnosis == "string") { 1.783 + a = diagnosis; 1.784 + } else if (diagnosis.property) { 1.785 + a = "Property " + uneval(diagnosis.property) + " of object didn't match:\n"; 1.786 + a += format_pattern_match_failure(diagnosis.diagnosis, indent + " "); 1.787 + } 1.788 + return indent + a; 1.789 +} 1.790 + 1.791 +function do_test_pending() { 1.792 + ++_tests_pending; 1.793 + 1.794 + _dump("TEST-INFO | (xpcshell/head.js) | test " + _tests_pending + 1.795 + " pending\n"); 1.796 +} 1.797 + 1.798 +function do_test_finished() { 1.799 + _dump("TEST-INFO | (xpcshell/head.js) | test " + _tests_pending + 1.800 + " finished\n"); 1.801 + 1.802 + if (--_tests_pending == 0) 1.803 + _do_quit(); 1.804 +} 1.805 + 1.806 +function do_get_file(path, allowNonexistent) { 1.807 + try { 1.808 + let lf = Components.classes["@mozilla.org/file/directory_service;1"] 1.809 + .getService(Components.interfaces.nsIProperties) 1.810 + .get("CurWorkD", Components.interfaces.nsILocalFile); 1.811 + 1.812 + let bits = path.split("/"); 1.813 + for (let i = 0; i < bits.length; i++) { 1.814 + if (bits[i]) { 1.815 + if (bits[i] == "..") 1.816 + lf = lf.parent; 1.817 + else 1.818 + lf.append(bits[i]); 1.819 + } 1.820 + } 1.821 + 1.822 + if (!allowNonexistent && !lf.exists()) { 1.823 + // Not using do_throw(): caller will continue. 1.824 + _passed = false; 1.825 + var stack = Components.stack.caller; 1.826 + _dump("TEST-UNEXPECTED-FAIL | " + stack.filename + " | [" + 1.827 + stack.name + " : " + stack.lineNumber + "] " + lf.path + 1.828 + " does not exist\n"); 1.829 + } 1.830 + 1.831 + return lf; 1.832 + } 1.833 + catch (ex) { 1.834 + do_throw(ex.toString(), Components.stack.caller); 1.835 + } 1.836 + 1.837 + return null; 1.838 +} 1.839 + 1.840 +// do_get_cwd() isn't exactly self-explanatory, so provide a helper 1.841 +function do_get_cwd() { 1.842 + return do_get_file(""); 1.843 +} 1.844 + 1.845 +function do_load_manifest(path) { 1.846 + var lf = do_get_file(path); 1.847 + const nsIComponentRegistrar = Components.interfaces.nsIComponentRegistrar; 1.848 + do_check_true(Components.manager instanceof nsIComponentRegistrar); 1.849 + // Previous do_check_true() is not a test check. 1.850 + ++_falsePassedChecks; 1.851 + Components.manager.autoRegister(lf); 1.852 +} 1.853 + 1.854 +/** 1.855 + * Parse a DOM document. 1.856 + * 1.857 + * @param aPath File path to the document. 1.858 + * @param aType Content type to use in DOMParser. 1.859 + * 1.860 + * @return nsIDOMDocument from the file. 1.861 + */ 1.862 +function do_parse_document(aPath, aType) { 1.863 + switch (aType) { 1.864 + case "application/xhtml+xml": 1.865 + case "application/xml": 1.866 + case "text/xml": 1.867 + break; 1.868 + 1.869 + default: 1.870 + do_throw("type: expected application/xhtml+xml, application/xml or text/xml," + 1.871 + " got '" + aType + "'", 1.872 + Components.stack.caller); 1.873 + } 1.874 + 1.875 + var lf = do_get_file(aPath); 1.876 + const C_i = Components.interfaces; 1.877 + const parserClass = "@mozilla.org/xmlextras/domparser;1"; 1.878 + const streamClass = "@mozilla.org/network/file-input-stream;1"; 1.879 + var stream = Components.classes[streamClass] 1.880 + .createInstance(C_i.nsIFileInputStream); 1.881 + stream.init(lf, -1, -1, C_i.nsIFileInputStream.CLOSE_ON_EOF); 1.882 + var parser = Components.classes[parserClass] 1.883 + .createInstance(C_i.nsIDOMParser); 1.884 + var doc = parser.parseFromStream(stream, null, lf.fileSize, aType); 1.885 + parser = null; 1.886 + stream = null; 1.887 + lf = null; 1.888 + return doc; 1.889 +} 1.890 + 1.891 +/** 1.892 + * Registers a function that will run when the test harness is done running all 1.893 + * tests. 1.894 + * 1.895 + * @param aFunction 1.896 + * The function to be called when the test harness has finished running. 1.897 + */ 1.898 +function do_register_cleanup(aFunction) 1.899 +{ 1.900 + _cleanupFunctions.push(aFunction); 1.901 +} 1.902 + 1.903 +/** 1.904 + * Registers a directory with the profile service, 1.905 + * and return the directory as an nsILocalFile. 1.906 + * 1.907 + * @return nsILocalFile of the profile directory. 1.908 + */ 1.909 +function do_get_profile() { 1.910 + if (!runningInParent) { 1.911 + _dump("TEST-INFO | (xpcshell/head.js) | Ignoring profile creation from child process.\n"); 1.912 + return null; 1.913 + } 1.914 + 1.915 + if (!_profileInitialized) { 1.916 + // Since we have a profile, we will notify profile shutdown topics at 1.917 + // the end of the current test, to ensure correct cleanup on shutdown. 1.918 + do_register_cleanup(function() { 1.919 + let obsSvc = Components.classes["@mozilla.org/observer-service;1"]. 1.920 + getService(Components.interfaces.nsIObserverService); 1.921 + obsSvc.notifyObservers(null, "profile-change-net-teardown", null); 1.922 + obsSvc.notifyObservers(null, "profile-change-teardown", null); 1.923 + obsSvc.notifyObservers(null, "profile-before-change", null); 1.924 + }); 1.925 + } 1.926 + 1.927 + let env = Components.classes["@mozilla.org/process/environment;1"] 1.928 + .getService(Components.interfaces.nsIEnvironment); 1.929 + // the python harness sets this in the environment for us 1.930 + let profd = env.get("XPCSHELL_TEST_PROFILE_DIR"); 1.931 + let file = Components.classes["@mozilla.org/file/local;1"] 1.932 + .createInstance(Components.interfaces.nsILocalFile); 1.933 + file.initWithPath(profd); 1.934 + 1.935 + let dirSvc = Components.classes["@mozilla.org/file/directory_service;1"] 1.936 + .getService(Components.interfaces.nsIProperties); 1.937 + let provider = { 1.938 + getFile: function(prop, persistent) { 1.939 + persistent.value = true; 1.940 + if (prop == "ProfD" || prop == "ProfLD" || prop == "ProfDS" || 1.941 + prop == "ProfLDS" || prop == "TmpD") { 1.942 + return file.clone(); 1.943 + } 1.944 + throw Components.results.NS_ERROR_FAILURE; 1.945 + }, 1.946 + QueryInterface: function(iid) { 1.947 + if (iid.equals(Components.interfaces.nsIDirectoryServiceProvider) || 1.948 + iid.equals(Components.interfaces.nsISupports)) { 1.949 + return this; 1.950 + } 1.951 + throw Components.results.NS_ERROR_NO_INTERFACE; 1.952 + } 1.953 + }; 1.954 + dirSvc.QueryInterface(Components.interfaces.nsIDirectoryService) 1.955 + .registerProvider(provider); 1.956 + 1.957 + let obsSvc = Components.classes["@mozilla.org/observer-service;1"]. 1.958 + getService(Components.interfaces.nsIObserverService); 1.959 + 1.960 + if (!_profileInitialized) { 1.961 + obsSvc.notifyObservers(null, "profile-do-change", "xpcshell-do-get-profile"); 1.962 + _profileInitialized = true; 1.963 + } 1.964 + 1.965 + // The methods of 'provider' will retain this scope so null out everything 1.966 + // to avoid spurious leak reports. 1.967 + env = null; 1.968 + profd = null; 1.969 + dirSvc = null; 1.970 + provider = null; 1.971 + obsSvc = null; 1.972 + return file.clone(); 1.973 +} 1.974 + 1.975 +/** 1.976 + * This function loads head.js (this file) in the child process, so that all 1.977 + * functions defined in this file (do_throw, etc) are available to subsequent 1.978 + * sendCommand calls. It also sets various constants used by these functions. 1.979 + * 1.980 + * (Note that you may use sendCommand without calling this function first; you 1.981 + * simply won't have any of the functions in this file available.) 1.982 + */ 1.983 +function do_load_child_test_harness() 1.984 +{ 1.985 + // Make sure this isn't called from child process 1.986 + if (!runningInParent) { 1.987 + do_throw("run_test_in_child cannot be called from child!"); 1.988 + } 1.989 + 1.990 + // Allow to be called multiple times, but only run once 1.991 + if (typeof do_load_child_test_harness.alreadyRun != "undefined") 1.992 + return; 1.993 + do_load_child_test_harness.alreadyRun = 1; 1.994 + 1.995 + _XPCSHELL_PROCESS = "parent"; 1.996 + 1.997 + let command = 1.998 + "const _HEAD_JS_PATH=" + uneval(_HEAD_JS_PATH) + "; " 1.999 + + "const _HTTPD_JS_PATH=" + uneval(_HTTPD_JS_PATH) + "; " 1.1000 + + "const _HEAD_FILES=" + uneval(_HEAD_FILES) + "; " 1.1001 + + "const _TAIL_FILES=" + uneval(_TAIL_FILES) + "; " 1.1002 + + "const _XPCSHELL_PROCESS='child';"; 1.1003 + 1.1004 + if (this._TESTING_MODULES_DIR) { 1.1005 + command += " const _TESTING_MODULES_DIR=" + uneval(_TESTING_MODULES_DIR) + ";"; 1.1006 + } 1.1007 + 1.1008 + command += " load(_HEAD_JS_PATH);"; 1.1009 + 1.1010 + sendCommand(command); 1.1011 +} 1.1012 + 1.1013 +/** 1.1014 + * Runs an entire xpcshell unit test in a child process (rather than in chrome, 1.1015 + * which is the default). 1.1016 + * 1.1017 + * This function returns immediately, before the test has completed. 1.1018 + * 1.1019 + * @param testFile 1.1020 + * The name of the script to run. Path format same as load(). 1.1021 + * @param optionalCallback. 1.1022 + * Optional function to be called (in parent) when test on child is 1.1023 + * complete. If provided, the function must call do_test_finished(); 1.1024 + */ 1.1025 +function run_test_in_child(testFile, optionalCallback) 1.1026 +{ 1.1027 + var callback = (typeof optionalCallback == 'undefined') ? 1.1028 + do_test_finished : optionalCallback; 1.1029 + 1.1030 + do_load_child_test_harness(); 1.1031 + 1.1032 + var testPath = do_get_file(testFile).path.replace(/\\/g, "/"); 1.1033 + do_test_pending(); 1.1034 + sendCommand("_dump('CHILD-TEST-STARTED'); " 1.1035 + + "const _TEST_FILE=['" + testPath + "']; _execute_test(); " 1.1036 + + "_dump('CHILD-TEST-COMPLETED');", 1.1037 + callback); 1.1038 +} 1.1039 + 1.1040 + 1.1041 +/** 1.1042 + * Add a test function to the list of tests that are to be run asynchronously. 1.1043 + * 1.1044 + * Each test function must call run_next_test() when it's done. Test files 1.1045 + * should call run_next_test() in their run_test function to execute all 1.1046 + * async tests. 1.1047 + * 1.1048 + * @return the test function that was passed in. 1.1049 + */ 1.1050 +let _gTests = []; 1.1051 +function add_test(func) { 1.1052 + _gTests.push([false, func]); 1.1053 + return func; 1.1054 +} 1.1055 + 1.1056 +// We lazy import Task.jsm so we don't incur a run-time penalty for all tests. 1.1057 +let _Task; 1.1058 + 1.1059 +/** 1.1060 + * Add a test function which is a Task function. 1.1061 + * 1.1062 + * Task functions are functions fed into Task.jsm's Task.spawn(). They are 1.1063 + * generators that emit promises. 1.1064 + * 1.1065 + * If an exception is thrown, a do_check_* comparison fails, or if a rejected 1.1066 + * promise is yielded, the test function aborts immediately and the test is 1.1067 + * reported as a failure. 1.1068 + * 1.1069 + * Unlike add_test(), there is no need to call run_next_test(). The next test 1.1070 + * will run automatically as soon the task function is exhausted. To trigger 1.1071 + * premature (but successful) termination of the function, simply return or 1.1072 + * throw a Task.Result instance. 1.1073 + * 1.1074 + * Example usage: 1.1075 + * 1.1076 + * add_task(function test() { 1.1077 + * let result = yield Promise.resolve(true); 1.1078 + * 1.1079 + * do_check_true(result); 1.1080 + * 1.1081 + * let secondary = yield someFunctionThatReturnsAPromise(result); 1.1082 + * do_check_eq(secondary, "expected value"); 1.1083 + * }); 1.1084 + * 1.1085 + * add_task(function test_early_return() { 1.1086 + * let result = yield somethingThatReturnsAPromise(); 1.1087 + * 1.1088 + * if (!result) { 1.1089 + * // Test is ended immediately, with success. 1.1090 + * return; 1.1091 + * } 1.1092 + * 1.1093 + * do_check_eq(result, "foo"); 1.1094 + * }); 1.1095 + */ 1.1096 +function add_task(func) { 1.1097 + if (!_Task) { 1.1098 + let ns = {}; 1.1099 + _Task = Components.utils.import("resource://gre/modules/Task.jsm", ns).Task; 1.1100 + } 1.1101 + 1.1102 + _gTests.push([true, func]); 1.1103 +} 1.1104 + 1.1105 +/** 1.1106 + * Runs the next test function from the list of async tests. 1.1107 + */ 1.1108 +let _gRunningTest = null; 1.1109 +let _gTestIndex = 0; // The index of the currently running test. 1.1110 +function run_next_test() 1.1111 +{ 1.1112 + function _run_next_test() 1.1113 + { 1.1114 + if (_gTestIndex < _gTests.length) { 1.1115 + do_test_pending(); 1.1116 + let _isTask; 1.1117 + [_isTask, _gRunningTest] = _gTests[_gTestIndex++]; 1.1118 + _dump("TEST-INFO | " + _TEST_FILE + " | Starting " + _gRunningTest.name); 1.1119 + 1.1120 + if (_isTask) { 1.1121 + _Task.spawn(_gRunningTest) 1.1122 + .then(run_next_test, do_report_unexpected_exception); 1.1123 + } else { 1.1124 + // Exceptions do not kill asynchronous tests, so they'll time out. 1.1125 + try { 1.1126 + _gRunningTest(); 1.1127 + } catch (e) { 1.1128 + do_throw(e); 1.1129 + } 1.1130 + } 1.1131 + } 1.1132 + } 1.1133 + 1.1134 + // For sane stacks during failures, we execute this code soon, but not now. 1.1135 + // We do this now, before we call do_test_finished(), to ensure the pending 1.1136 + // counter (_tests_pending) never reaches 0 while we still have tests to run 1.1137 + // (do_execute_soon bumps that counter). 1.1138 + do_execute_soon(_run_next_test); 1.1139 + 1.1140 + if (_gRunningTest !== null) { 1.1141 + // Close the previous test do_test_pending call. 1.1142 + do_test_finished(); 1.1143 + } 1.1144 +} 1.1145 + 1.1146 +/** 1.1147 + * End of code adapted from xpcshell head.js 1.1148 + */ 1.1149 + 1.1150 + 1.1151 +/** 1.1152 + * JavaBridge facilitates communication between Java and JS. See 1.1153 + * JavascriptBridge.java for the corresponding JavascriptBridge and docs. 1.1154 + */ 1.1155 + 1.1156 +function JavaBridge(obj) { 1.1157 + 1.1158 + this._EVENT_TYPE = "Robocop:JS"; 1.1159 + this._target = obj; 1.1160 + // The number of replies needed to answer all outstanding sync calls. 1.1161 + this._repliesNeeded = 0; 1.1162 + this._Services.obs.addObserver(this, this._EVENT_TYPE, false); 1.1163 + 1.1164 + this._sendMessage("notify-loaded", []); 1.1165 +}; 1.1166 + 1.1167 +JavaBridge.prototype = { 1.1168 + 1.1169 + _Services: Components.utils.import( 1.1170 + "resource://gre/modules/Services.jsm", {}).Services, 1.1171 + 1.1172 + _sendMessageToJava: Components.utils.import( 1.1173 + "resource://gre/modules/Messaging.jsm", {}).sendMessageToJava, 1.1174 + 1.1175 + _sendMessage: function (innerType, args) { 1.1176 + this._sendMessageToJava({ 1.1177 + type: this._EVENT_TYPE, 1.1178 + innerType: innerType, 1.1179 + method: args[0], 1.1180 + args: Array.prototype.slice.call(args, 1), 1.1181 + }); 1.1182 + }, 1.1183 + 1.1184 + observe: function(subject, topic, data) { 1.1185 + let message = JSON.parse(data); 1.1186 + if (message.innerType === "sync-reply") { 1.1187 + // Reply to our Javascript-to-Java sync call 1.1188 + this._repliesNeeded--; 1.1189 + return; 1.1190 + } 1.1191 + // Call the corresponding method on the target 1.1192 + try { 1.1193 + this._target[message.method].apply(this._target, message.args); 1.1194 + } catch (e) { 1.1195 + do_report_unexpected_exception(e, "Failed to call " + message.method); 1.1196 + } 1.1197 + if (message.innerType === "sync-call") { 1.1198 + // Reply for sync message 1.1199 + this._sendMessage("sync-reply", [message.method]); 1.1200 + } 1.1201 + }, 1.1202 + 1.1203 + /** 1.1204 + * Synchronously call a method in Java, 1.1205 + * given the method name followed by a list of arguments. 1.1206 + */ 1.1207 + syncCall: function (methodName /*, ... */) { 1.1208 + this._sendMessage("sync-call", arguments); 1.1209 + let thread = this._Services.tm.currentThread; 1.1210 + let initialReplies = this._repliesNeeded; 1.1211 + // Need one more reply to answer the current sync call. 1.1212 + this._repliesNeeded++; 1.1213 + // Wait for the reply to arrive. Normally we would not want to 1.1214 + // spin the event loop, but here we're in a test and our API 1.1215 + // specifies a synchronous call, so we spin the loop to wait for 1.1216 + // the call to finish. 1.1217 + while (this._repliesNeeded > initialReplies) { 1.1218 + thread.processNextEvent(true); 1.1219 + } 1.1220 + }, 1.1221 + 1.1222 + /** 1.1223 + * Asynchronously call a method in Java, 1.1224 + * given the method name followed by a list of arguments. 1.1225 + */ 1.1226 + asyncCall: function (methodName /*, ... */) { 1.1227 + this._sendMessage("async-call", arguments); 1.1228 + }, 1.1229 + 1.1230 + /** 1.1231 + * Disconnect with Java. 1.1232 + */ 1.1233 + disconnect: function () { 1.1234 + this._Services.obs.removeObserver(this, this._EVENT_TYPE); 1.1235 + }, 1.1236 +};