Bug 909129 - fix leaking imported scripts from marionette, r=dburns

This commit is contained in:
Malini Das 2013-10-08 16:11:45 -04:00
parent 37da9f3917
commit c2476960ed
4 changed files with 198 additions and 12 deletions

View File

@ -1186,6 +1186,14 @@ class Marionette(object):
js = f.read()
return self._send_message('importScript', 'ok', script=js)
def clear_imported_scripts(self):
'''
Clears all imported scripts in this context, ie: calling clear_imported_scripts in chrome
context will clear only scripts you imported in chrome, and will leave the scripts
you imported in content context.
'''
return self._send_message('clearImportedScripts', 'ok')
def add_cookie(self, cookie):
"""
Adds a cookie to your current session.

View File

@ -1,18 +1,105 @@
# 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/.
import os
from marionette_test import MarionetteTestCase
from errors import JavascriptException
class TestImportScript(MarionetteTestCase):
def setUp(self):
MarionetteTestCase.setUp(self)
def clear_other_context(self):
self.marionette.set_context("chrome")
self.marionette.clear_imported_scripts()
self.marionette.set_context("content")
def check_file_exists(self):
return self.marionette.execute_script("""
let FileUtils = SpecialPowers.Cu.import("resource://gre/modules/FileUtils.jsm").FileUtils;
let importedScripts = FileUtils.getFile('TmpD', ['marionetteContentScripts']);
return importedScripts.exists();
""", special_powers=True)
def get_file_size(self):
return self.marionette.execute_script("""
let FileUtils = SpecialPowers.Cu.import("resource://gre/modules/FileUtils.jsm").FileUtils;
let importedScripts = FileUtils.getFile('TmpD', ['marionetteContentScripts']);
return importedScripts.fileSize;
""", special_powers=True)
def test_import_script(self):
js = os.path.abspath(os.path.join(__file__, os.path.pardir, "importscript.js"))
self.marionette.import_script(js)
self.assertEqual("i'm a test function!", self.marionette.execute_script("return testFunc();"))
self.assertEqual("i'm a test function!", self.marionette.execute_async_script("marionetteScriptFinished(testFunc());"))
def test_import_script_twice(self):
js = os.path.abspath(os.path.join(__file__, os.path.pardir, "importscript.js"))
self.marionette.import_script(js)
self.assertEqual("i'm a test function!", self.marionette.execute_script("return testFunc();"))
self.assertEqual("i'm a test function!", self.marionette.execute_async_script("marionetteScriptFinished(testFunc());"))
self.assertTrue(self.check_file_exists())
file_size = self.get_file_size()
self.assertNotEqual(file_size, None)
self.marionette.import_script(js)
file_size = self.get_file_size()
self.assertEqual(file_size, self.get_file_size())
self.assertEqual("i'm a test function!", self.marionette.execute_script("return testFunc();"))
self.assertEqual("i'm a test function!", self.marionette.execute_async_script("marionetteScriptFinished(testFunc());"))
def test_import_two_scripts_twice(self):
js = os.path.abspath(os.path.join(__file__, os.path.pardir, "importscript.js"))
self.marionette.import_script(js)
self.assertEqual("i'm a test function!", self.marionette.execute_script("return testFunc();"))
self.assertEqual("i'm a test function!", self.marionette.execute_async_script("marionetteScriptFinished(testFunc());"))
self.assertTrue(self.check_file_exists())
file_size = self.get_file_size()
self.assertNotEqual(file_size, None)
self.marionette.import_script(js)
# same script should not append to file
self.assertEqual(file_size, self.get_file_size())
self.assertEqual("i'm a test function!", self.marionette.execute_script("return testFunc();"))
self.assertEqual("i'm a test function!", self.marionette.execute_async_script("marionetteScriptFinished(testFunc());"))
js = os.path.abspath(os.path.join(__file__, os.path.pardir, "importanotherscript.js"))
self.marionette.import_script(js)
new_size = self.get_file_size()
# new script should append to file
self.assertNotEqual(file_size, new_size)
file_size = new_size
self.assertEqual("i'm yet another test function!",
self.marionette.execute_script("return testAnotherFunc();"))
self.assertEqual("i'm yet another test function!",
self.marionette.execute_async_script("marionetteScriptFinished(testAnotherFunc());"))
self.marionette.import_script(js)
# same script should not append to file
self.assertEqual(file_size, self.get_file_size())
def test_import_script_and_clear(self):
js = os.path.abspath(os.path.join(__file__, os.path.pardir, "importscript.js"))
self.marionette.import_script(js)
self.assertEqual("i'm a test function!", self.marionette.execute_script("return testFunc();"))
self.assertEqual("i'm a test function!", self.marionette.execute_async_script("marionetteScriptFinished(testFunc());"))
self.marionette.clear_imported_scripts()
self.assertFalse(self.check_file_exists())
self.assertRaises(JavascriptException, self.marionette.execute_script, "return testFunc();")
self.assertRaises(JavascriptException, self.marionette.execute_async_script, "marionetteScriptFinished(testFunc());")
def test_import_script_and_clear_in_chrome(self):
js = os.path.abspath(os.path.join(__file__, os.path.pardir, "importscript.js"))
self.marionette.import_script(js)
self.assertTrue(self.check_file_exists())
file_size = self.get_file_size()
self.assertEqual("i'm a test function!", self.marionette.execute_script("return testFunc();"))
self.assertEqual("i'm a test function!", self.marionette.execute_async_script("marionetteScriptFinished(testFunc());"))
self.clear_other_context()
# clearing other context's script file should not affect ours
self.assertTrue(self.check_file_exists())
self.assertEqual(file_size, self.get_file_size())
self.assertEqual("i'm a test function!", self.marionette.execute_script("return testFunc();"))
self.assertEqual("i'm a test function!", self.marionette.execute_async_script("marionetteScriptFinished(testFunc());"))
def test_importing_another_script_and_check_they_append(self):
firstjs = os.path.abspath(
os.path.join(__file__, os.path.pardir, "importscript.js"))
@ -31,4 +118,41 @@ class TestImportScript(MarionetteTestCase):
class TestImportScriptChrome(TestImportScript):
def setUp(self):
MarionetteTestCase.setUp(self)
self.marionette.set_script_timeout(30000)
self.marionette.set_context("chrome")
def clear_other_context(self):
self.marionette.set_context("content")
self.marionette.clear_imported_scripts()
self.marionette.set_context("chrome")
def check_file_exists(self):
return self.marionette.execute_async_script("""
Components.utils.import("resource://gre/modules/FileUtils.jsm");
let checkTimer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
let f = function() {
if (typeof FileUtils === 'undefined') {
checkTimer.initWithCallback(f, 100, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
return;
}
let importedScripts = FileUtils.getFile('TmpD', ['marionetteChromeScripts']);
marionetteScriptFinished(importedScripts.exists());
};
checkTimer.initWithCallback(f, 100, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
""")
def get_file_size(self):
return self.marionette.execute_async_script("""
Components.utils.import("resource://gre/modules/FileUtils.jsm");
let checkTimer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
let f = function() {
if (typeof FileUtils === 'undefined') {
checkTimer.initWithCallback(f, 100, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
return;
}
let importedScripts = FileUtils.getFile('TmpD', ['marionetteChromeScripts']);
marionetteScriptFinished(importedScripts.fileSize);
};
checkTimer.initWithCallback(f, 100, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
""")

View File

@ -96,7 +96,7 @@ function registerSelf() {
if (register[0]) {
listenerId = register[0].id;
importedScripts = FileUtils.File(register[0].importedScripts);
importedScripts = FileUtils.getFile('TmpD', ['marionetteContentScripts']);
startListeners();
}
}

View File

@ -134,7 +134,8 @@ function MarionetteServerConnection(aPrefix, aTransport, aServer)
this.command_id = null;
this.mainFrame = null; //topmost chrome frame
this.curFrame = null; // chrome iframe that currently has focus
this.importedScripts = FileUtils.getFile('TmpD', ['marionettescriptchrome']);
this.importedScripts = FileUtils.getFile('TmpD', ['marionetteChromeScripts']);
this.importedScriptHashes = {"chrome" : [], "content": []};
this.currentFrameElement = null;
this.testName = null;
this.mozBrowserClose = null;
@ -465,6 +466,16 @@ MarionetteServerConnection.prototype = {
return this.uuidGen.generateUUID().toString();
},
/**
* Given a file name, this will delete the file from the temp directory if it exists
*/
deleteFile: function(filename) {
let file = FileUtils.getFile('TmpD', [filename.toString()]);
if (file.exists()) {
file.remove(true);
}
},
/**
* Marionette API:
*
@ -1933,7 +1944,13 @@ MarionetteServerConnection.prototype = {
// if there is only 1 window left, delete the session
if (numOpenWindows === 1){
this.sessionTearDown();
try {
this.sessionTearDown();
}
catch (e) {
this.sendError("Could not clear session", 500, e.name + ": " + e.message, command_id);
return;
}
this.sendOk(command_id);
return;
}
@ -1990,20 +2007,23 @@ MarionetteServerConnection.prototype = {
if (this.mainFrame) {
this.mainFrame.focus();
}
try {
this.importedScripts.remove(false);
}
catch (e) {
}
this.deleteFile('marionetteChromeScripts');
this.deleteFile('marionetteContentScripts');
},
/**
* Processes the 'deleteSession' request from the client by tearing down
* the session and responding 'ok'.
*/
deleteSession: function MDA_sessionTearDown() {
deleteSession: function MDA_deleteSession() {
let command_id = this.command_id = this.getCommandId();
this.sessionTearDown();
try {
this.sessionTearDown();
}
catch (e) {
this.sendError("Could not delete session", 500, e.name + ": " + e.message, command_id);
return;
}
this.sendOk(command_id);
},
@ -2054,6 +2074,23 @@ MarionetteServerConnection.prototype = {
importScript: function MDA_importScript(aRequest) {
let command_id = this.command_id = this.getCommandId();
let converter =
Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].
createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
let result = {};
let data = converter.convertToByteArray(aRequest.script, result);
let ch = Components.classes["@mozilla.org/security/hash;1"]
.createInstance(Components.interfaces.nsICryptoHash);
ch.init(ch.MD5);
ch.update(data, data.length);
let hash = ch.finish(true);
if (this.importedScriptHashes[this.context].indexOf(hash) > -1) {
//we have already imported this script
this.sendOk(command_id);
return;
}
this.importedScriptHashes[this.context].push(hash);
if (this.context == "chrome") {
let file;
if (this.importedScripts.exists()) {
@ -2079,6 +2116,23 @@ MarionetteServerConnection.prototype = {
}
},
clearImportedScripts: function MDA_clearImportedScripts(aRequest) {
let command_id = this.command_id = this.getCommandId();
try {
if (this.context == "chrome") {
this.deleteFile('marionetteChromeScripts');
}
else {
this.deleteFile('marionetteContentScripts');
}
}
catch (e) {
this.sendError("Could not clear imported scripts", 500, e.name + ": " + e.message, command_id);
return;
}
this.sendOk(command_id);
},
/**
* Takes a screenshot of a DOM node. If there is no node given a screenshot
* of the window will be taken.
@ -2196,7 +2250,6 @@ MarionetteServerConnection.prototype = {
listenerWindow);
}
this.curBrowser.elementManager.seenItems[reg.id] = Cu.getWeakReference(listenerWindow);
reg.importedScripts = this.importedScripts.path;
if (nullPrevious && (this.curBrowser.curFrameId != null)) {
if (!this.sendAsync("newSession",
{ B2G: (appName == "B2G") },
@ -2261,6 +2314,7 @@ MarionetteServerConnection.prototype.requestTypes = {
"deleteSession": MarionetteServerConnection.prototype.deleteSession,
"emulatorCmdResult": MarionetteServerConnection.prototype.emulatorCmdResult,
"importScript": MarionetteServerConnection.prototype.importScript,
"clearImportedScripts": MarionetteServerConnection.prototype.clearImportedScripts,
"getAppCacheStatus": MarionetteServerConnection.prototype.getAppCacheStatus,
"closeWindow": MarionetteServerConnection.prototype.closeWindow,
"setTestName": MarionetteServerConnection.prototype.setTestName,