diff options
Diffstat (limited to 'addon-sdk/source/test/leak')
-rw-r--r-- | addon-sdk/source/test/leak/jetpack-package.ini | 7 | ||||
-rw-r--r-- | addon-sdk/source/test/leak/leak-utils.js | 80 | ||||
-rw-r--r-- | addon-sdk/source/test/leak/test-leak-event-dom-closed-window.js | 29 | ||||
-rw-r--r-- | addon-sdk/source/test/leak/test-leak-tab-events.js | 46 | ||||
-rw-r--r-- | addon-sdk/source/test/leak/test-leak-window-events.js | 65 |
5 files changed, 227 insertions, 0 deletions
diff --git a/addon-sdk/source/test/leak/jetpack-package.ini b/addon-sdk/source/test/leak/jetpack-package.ini new file mode 100644 index 000000000..0632bdc87 --- /dev/null +++ b/addon-sdk/source/test/leak/jetpack-package.ini @@ -0,0 +1,7 @@ +[DEFAULT] +support-files = + leak-utils.js + +[test-leak-window-events.js] +[test-leak-event-dom-closed-window.js] +[test-leak-tab-events.js] diff --git a/addon-sdk/source/test/leak/leak-utils.js b/addon-sdk/source/test/leak/leak-utils.js new file mode 100644 index 000000000..e01255ec8 --- /dev/null +++ b/addon-sdk/source/test/leak/leak-utils.js @@ -0,0 +1,80 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +const { Cu, Ci } = require("chrome"); +const { Services } = Cu.import("resource://gre/modules/Services.jsm", {}); +const { SelfSupportBackend } = Cu.import("resource:///modules/SelfSupportBackend.jsm", {}); +const Startup = Cu.import("resource://gre/modules/sdk/system/Startup.js", {}).exports; + +// Adapted from the SpecialPowers.exactGC() code. We don't have a +// window to operate on so we cannot use the exact same logic. We +// use 6 GC iterations here as that is what is needed to clean up +// the windows we have tested with. +function gc() { + return new Promise(resolve => { + Cu.forceGC(); + Cu.forceCC(); + let count = 0; + function genGCCallback() { + Cu.forceCC(); + return function() { + if (++count < 5) { + Cu.schedulePreciseGC(genGCCallback()); + } else { + resolve(); + } + } + } + + Cu.schedulePreciseGC(genGCCallback()); + }); +} + +// Execute the given test function and verify that we did not leak windows +// in the process. The test function must return a promise or be a generator. +// If the promise is resolved, or generator completes, with an sdk loader +// object then it will be unloaded after the memory measurements. +exports.asyncWindowLeakTest = function*(assert, asyncTestFunc) { + + // SelfSupportBackend periodically tries to open windows. This can + // mess up our window leak detection below, so turn it off. + SelfSupportBackend.uninit(); + + // Wait for the browser to finish loading. + yield Startup.onceInitialized; + + // Track windows that are opened in an array of weak references. + let weakWindows = []; + function windowObserver(subject, topic) { + let supportsWeak = subject.QueryInterface(Ci.nsISupportsWeakReference); + if (supportsWeak) { + weakWindows.push(Cu.getWeakReference(supportsWeak)); + } + } + Services.obs.addObserver(windowObserver, "domwindowopened", false); + + // Execute the body of the test. + let testLoader = yield asyncTestFunc(assert); + + // Stop tracking new windows and attempt to GC any resources allocated + // by the test body. + Services.obs.removeObserver(windowObserver, "domwindowopened", false); + yield gc(); + + // Check to see if any of the windows we saw survived the GC. We consider + // these leaks. + assert.ok(weakWindows.length > 0, "should see at least one new window"); + for (let i = 0; i < weakWindows.length; ++i) { + assert.equal(weakWindows[i].get(), null, "window " + i + " should be GC'd"); + } + + // Finally, unload the test body's loader if it provided one. We do this + // after our leak detection to avoid free'ing things on unload. Users + // don't tend to unload their addons very often, so we want to find leaks + // that happen while addons are in use. + if (testLoader) { + testLoader.unload(); + } +} diff --git a/addon-sdk/source/test/leak/test-leak-event-dom-closed-window.js b/addon-sdk/source/test/leak/test-leak-event-dom-closed-window.js new file mode 100644 index 000000000..c398462ab --- /dev/null +++ b/addon-sdk/source/test/leak/test-leak-event-dom-closed-window.js @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +'use strict'; + +const { asyncWindowLeakTest } = require("./leak-utils"); +const { Loader } = require('sdk/test/loader'); +const openWindow = require("sdk/window/utils").open; + +exports["test sdk/event/dom does not leak when attached to closed window"] = function*(assert) { + yield asyncWindowLeakTest(assert, _ => { + return new Promise(resolve => { + let loader = Loader(module); + let { open } = loader.require('sdk/event/dom'); + let w = openWindow(); + w.addEventListener("DOMWindowClose", function windowClosed(evt) { + w.removeEventListener("DOMWindowClose", windowClosed); + // The sdk/event/dom module tries to clean itself up when DOMWindowClose + // is fired. Verify that it doesn't leak if its attached to an + // already closed window either. (See bug 1268898.) + open(w.document, "TestEvent1"); + resolve(loader); + }); + w.close(); + }); + }); +} + +require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/leak/test-leak-tab-events.js b/addon-sdk/source/test/leak/test-leak-tab-events.js new file mode 100644 index 000000000..4266c04fc --- /dev/null +++ b/addon-sdk/source/test/leak/test-leak-tab-events.js @@ -0,0 +1,46 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +'use strict'; + +const { asyncWindowLeakTest } = require("./leak-utils"); +const { Loader } = require('sdk/test/loader'); +const openWindow = require("sdk/window/utils").open; + +exports["test sdk/tab/events does not leak new window"] = function*(assert) { + yield asyncWindowLeakTest(assert, _ => { + return new Promise(resolve => { + let loader = Loader(module); + let { events } = loader.require('sdk/tab/events'); + let w = openWindow(); + w.addEventListener("load", function windowLoaded(evt) { + w.removeEventListener("load", windowLoaded); + w.addEventListener("DOMWindowClose", function windowClosed(evt) { + w.removeEventListener("DOMWindowClose", windowClosed); + resolve(loader); + }); + w.close(); + }); + }); + }); +} + +exports["test sdk/tab/events does not leak when attached to existing window"] = function*(assert) { + yield asyncWindowLeakTest(assert, _ => { + return new Promise(resolve => { + let loader = Loader(module); + let w = openWindow(); + w.addEventListener("load", function windowLoaded(evt) { + w.removeEventListener("load", windowLoaded); + let { events } = loader.require('sdk/tab/events'); + w.addEventListener("DOMWindowClose", function windowClosed(evt) { + w.removeEventListener("DOMWindowClose", windowClosed); + resolve(loader); + }); + w.close(); + }); + }); + }); +} + +require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/leak/test-leak-window-events.js b/addon-sdk/source/test/leak/test-leak-window-events.js new file mode 100644 index 000000000..ceb20f475 --- /dev/null +++ b/addon-sdk/source/test/leak/test-leak-window-events.js @@ -0,0 +1,65 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +// Opening new windows in Fennec causes issues +module.metadata = { + engines: { + 'Firefox': '*' + } +}; + +const { asyncWindowLeakTest } = require("./leak-utils.js"); +const { Loader } = require("sdk/test/loader"); +const { open } = require("sdk/window/utils"); + +exports["test window/events for leaks"] = function*(assert) { + yield asyncWindowLeakTest(assert, _ => { + return new Promise((resolve, reject) => { + let loader = Loader(module); + let { events } = loader.require("sdk/window/events"); + let { on, off } = loader.require("sdk/event/core"); + + on(events, "data", function handler(e) { + try { + if (e.type === "load") { + e.target.close(); + } + else if (e.type === "close") { + off(events, "data", handler); + + // Let asyncWindowLeakTest call loader.unload() after the + // leak check. + resolve(loader); + } + } catch (e) { + reject(e); + } + }); + + // Open a window. This will trigger our data events. + open(); + }); + }); +}; + +exports["test window/events for leaks with existing window"] = function*(assert) { + yield asyncWindowLeakTest(assert, _ => { + return new Promise((resolve, reject) => { + let loader = Loader(module); + let w = open(); + w.addEventListener("load", function windowLoaded(evt) { + w.removeEventListener("load", windowLoaded); + let { events } = loader.require("sdk/window/events"); + w.addEventListener("DOMWindowClose", function windowClosed(evt) { + w.removeEventListener("DOMWindowClose", windowClosed); + resolve(loader); + }); + w.close(); + }); + }); + }); +}; + +require("sdk/test").run(exports); |