From 63ee0f9a5ab0f4d6c4badf476c4ebcdb505e1fbe Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Tue, 13 Mar 2018 19:11:10 -0700 Subject: [PATCH] Bug 1445551: Part 1a - Add uses-unsafe-cpows annotation to mochitest harness. r=mconley This allows us to specifically whitelist browser mochitests which still rely on unsafe CPOWs, and run them in a separate Sandbox global with permissive CPOWs enabled. The test harness and most of the in-tree tests will run with permissive CPOWs disabled, like the rest of the browser. MozReview-Commit-ID: CxIkuxr5PXJ --HG-- extra : rebase_source : 897c951e5ea84db58e92c8b627679f029ebf4a42 --- testing/mochitest/browser-harness.xul | 1 + testing/mochitest/browser-test.js | 79 +++++++++++++++---- testing/mochitest/manifestLibrary.js | 4 +- testing/mochitest/runtests.py | 2 + .../mochitest/tests/SimpleTest/EventUtils.js | 12 +++ 5 files changed, 79 insertions(+), 19 deletions(-) diff --git a/testing/mochitest/browser-harness.xul b/testing/mochitest/browser-harness.xul index 77390327d833..f968bdd1f9e3 100644 --- a/testing/mochitest/browser-harness.xul +++ b/testing/mochitest/browser-harness.xul @@ -110,6 +110,7 @@ function browserTest(aTestFile) { this.path = aTestFile['url']; this.expected = aTestFile['expected']; + this.usesUnsafeCPOWs = aTestFile['uses-unsafe-cpows'] || false; this.dumper = gDumper; this.results = []; this.scope = null; diff --git a/testing/mochitest/browser-test.js b/testing/mochitest/browser-test.js index 5ecf1b8729dc..cd1e78d1acad 100644 --- a/testing/mochitest/browser-test.js +++ b/testing/mochitest/browser-test.js @@ -379,6 +379,16 @@ function Tester(aTests, structuredLogger, aCallback) { this._scriptLoader = Services.scriptloader; this.EventUtils = {}; this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", this.EventUtils); + + // In order to allow existing tests to continue using unsafe CPOWs + // with EventUtils, we need to load a separate copy into a sandbox + // which has unsafe CPOW usage whitelisted. + this.cpowSandbox = Cu.Sandbox(window, {sandboxPrototype: window}); + Cu.permitCPOWsInScope(this.cpowSandbox); + + this.cpowEventUtils = new this.cpowSandbox.Object(); + this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", this.cpowEventUtils); + var simpleTestScope = {}; this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/specialpowersAPI.js", simpleTestScope); this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/SpecialPowersObserverAPI.js", simpleTestScope); @@ -970,16 +980,17 @@ Tester.prototype = { let currentTest = this.currentTest; // Import utils in the test scope. - this.currentTest.scope.EventUtils = this.EventUtils; - this.currentTest.scope.SimpleTest = this.SimpleTest; - this.currentTest.scope.gTestPath = this.currentTest.path; - this.currentTest.scope.Task = this.Task; - this.currentTest.scope.ContentTask = this.ContentTask; - this.currentTest.scope.BrowserTestUtils = this.BrowserTestUtils; - this.currentTest.scope.TestUtils = this.TestUtils; - this.currentTest.scope.ExtensionTestUtils = this.ExtensionTestUtils; + let {scope} = this.currentTest; + scope.EventUtils = this.currentTest.usesUnsafeCPOWs ? this.cpowEventUtils : this.EventUtils; + scope.SimpleTest = this.SimpleTest; + scope.gTestPath = this.currentTest.path; + scope.Task = this.Task; + scope.ContentTask = this.ContentTask; + scope.BrowserTestUtils = this.BrowserTestUtils; + scope.TestUtils = this.TestUtils; + scope.ExtensionTestUtils = this.ExtensionTestUtils; // Pass a custom report function for mochitest style reporting. - this.currentTest.scope.Assert = new this.Assert(function(err, message, stack) { + scope.Assert = new this.Assert(function(err, message, stack) { currentTest.addResult(new testResult(err ? { name: err.message, ex: err.stack, @@ -996,7 +1007,7 @@ Tester.prototype = { this.ContentTask.setTestScope(currentScope); // Allow Assert.jsm methods to be tacked to the current scope. - this.currentTest.scope.export_assertions = function() { + scope.export_assertions = function() { for (let func in this.Assert) { this[func] = this.Assert[func].bind(this.Assert); } @@ -1005,11 +1016,11 @@ Tester.prototype = { // Override SimpleTest methods with ours. SIMPLETEST_OVERRIDES.forEach(function(m) { this.SimpleTest[m] = this[m]; - }, this.currentTest.scope); + }, scope); //load the tools to work with chrome .jar and remote try { - this._scriptLoader.loadSubScript("chrome://mochikit/content/chrome-harness.js", this.currentTest.scope); + this._scriptLoader.loadSubScript("chrome://mochikit/content/chrome-harness.js", scope); } catch (ex) { /* no chrome-harness tools */ } // Import head.js script if it exists. @@ -1017,7 +1028,7 @@ Tester.prototype = { this.currentTest.path.substr(0, this.currentTest.path.lastIndexOf("/")); var headPath = currentTestDirPath + "/head.js"; try { - this._scriptLoader.loadSubScript(headPath, this.currentTest.scope); + this._scriptLoader.loadSubScript(headPath, scope); } catch (ex) { // Ignore if no head.js exists, but report all other errors. Note this // will also ignore an existing head.js attempting to import a missing @@ -1032,8 +1043,7 @@ Tester.prototype = { // Import the test script. try { - this._scriptLoader.loadSubScript(this.currentTest.path, - this.currentTest.scope); + this._scriptLoader.loadSubScript(this.currentTest.path, scope); // Run the test this.lastStartTime = Date.now(); if (this.currentTest.scope.__tasks) { @@ -1091,8 +1101,8 @@ Tester.prototype = { } this.finish(); }.bind(currentScope)); - } else if (typeof this.currentTest.scope.test == "function") { - this.currentTest.scope.test(); + } else if (typeof scope.test == "function") { + scope.test(); } else { throw "This test didn't call add_task, nor did it define a generatorTest() function, nor did it define a test() function, so we don't know how to run it."; } @@ -1372,6 +1382,18 @@ function testScope(aTester, aTest, expected) { self.__tester.structuredLogger.activateBuffering(); }) }; + + // If we're running a test that requires unsafe CPOWs, create a + // separate sandbox scope, with CPOWS whitelisted, for that test, and + // mirror all of our properties onto it. Test files will be loaded + // into this sandbox. + // + // Otherwise, load test files directly into the testScope instance. + if (aTest.usesUnsafeCPOWs) { + let sandbox = this._createSandbox(); + Cu.permitCPOWsInScope(sandbox); + return sandbox; + } } function decorateTaskFn(fn) { @@ -1400,6 +1422,29 @@ testScope.prototype = { ExtensionTestUtils: null, Assert: null, + _createSandbox() { + let sandbox = Cu.Sandbox(window, {sandboxPrototype: window}); + + for (let prop in this) { + if (typeof this[prop] == "function") { + sandbox[prop] = this[prop].bind(this); + } else { + Object.defineProperty(sandbox, prop, { + configurable: true, + enumerable: true, + get: () => { + return this[prop]; + }, + set: (value) => { + this[prop] = value; + } + }); + } + } + + return sandbox; + }, + /** * Add a test function which is a Task function. * diff --git a/testing/mochitest/manifestLibrary.js b/testing/mochitest/manifestLibrary.js index 8611a9e22d8f..c2df2d669e81 100644 --- a/testing/mochitest/manifestLibrary.js +++ b/testing/mochitest/manifestLibrary.js @@ -26,10 +26,10 @@ function parseTestManifest(testManifest, params, callback) { } if (params.testRoot != 'tests' && params.testRoot !== undefined) { name = params.baseurl + '/' + params.testRoot + '/' + path; - links[name] = {'test': {'url': name, 'expected': obj['expected']}}; + links[name] = {'test': {'url': name, 'expected': obj['expected'], 'uses-unsafe-cpows': obj['uses-unsafe-cpows']}}; } else { name = params.testPrefix + path; - paths.push({'test': {'url': name, 'expected': obj['expected']}}); + paths.push({'test': {'url': name, 'expected': obj['expected'], 'uses-unsafe-cpows': obj['uses-unsafe-cpows']}}); } } if (paths.length > 0) { diff --git a/testing/mochitest/runtests.py b/testing/mochitest/runtests.py index 66066b00c1de..4f81a3a77c1c 100644 --- a/testing/mochitest/runtests.py +++ b/testing/mochitest/runtests.py @@ -1524,6 +1524,8 @@ toolbar#nav-bar { testob['disabled'] = test['disabled'] if 'expected' in test: testob['expected'] = test['expected'] + if 'uses-unsafe-cpows' in test: + testob['uses-unsafe-cpows'] = test['uses-unsafe-cpows'] == 'true' if 'scheme' in test: testob['scheme'] = test['scheme'] if options.failure_pattern_file: diff --git a/testing/mochitest/tests/SimpleTest/EventUtils.js b/testing/mochitest/tests/SimpleTest/EventUtils.js index 8d6fb0005e9d..1772edb7bdf7 100644 --- a/testing/mochitest/tests/SimpleTest/EventUtils.js +++ b/testing/mochitest/tests/SimpleTest/EventUtils.js @@ -176,6 +176,10 @@ function sendMouseEvent(aEvent, aTarget, aWindow) { ctrlKeyArg, altKeyArg, shiftKeyArg, metaKeyArg, buttonArg, relatedTargetArg); + // If documentURIObject exists or `window` is a stub object, we're in + // a chrome scope, so don't bother trying to go through SpecialPowers. + if (!window.document || window.document.documentURIObject) + return aTarget.dispatchEvent(event); return SpecialPowers.dispatchEvent(aWindow, aTarget, event); } @@ -1240,6 +1244,14 @@ function _getDOMWindowUtils(aWindow = window) aWindow = window; } + // If documentURIObject exists or `window` is a stub object, we're in + // a chrome scope, so don't bother trying to go through SpecialPowers. + if (!window.document || window.document.documentURIObject) { + return aWindow + .QueryInterface(_EU_Ci.nsIInterfaceRequestor) + .getInterface(_EU_Ci.nsIDOMWindowUtils); + } + // we need parent.SpecialPowers for: // layout/base/tests/test_reftests_with_caret.html // chrome: toolkit/content/tests/chrome/test_findbar.xul