From 8697a25b196a3a4f810e36824dd7ded7dbc5c35b Mon Sep 17 00:00:00 2001 From: Malini Das Date: Tue, 8 Oct 2013 16:11:45 -0400 Subject: [PATCH] Bug 909129 - stop leaking imported scripts and don't import duplicates, r=jgriffin --- .../client/marionette/marionette.py | 8 ++ .../tests/unit/test_import_script.py | 110 +++++++++++++++++- testing/marionette/marionette-listener.js | 2 +- testing/marionette/marionette-server.js | 74 ++++++++++-- 4 files changed, 182 insertions(+), 12 deletions(-) diff --git a/testing/marionette/client/marionette/marionette.py b/testing/marionette/client/marionette/marionette.py index d2dbaafcf98a..3e2e516e92c6 100644 --- a/testing/marionette/client/marionette/marionette.py +++ b/testing/marionette/client/marionette/marionette.py @@ -1191,6 +1191,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. diff --git a/testing/marionette/client/marionette/tests/unit/test_import_script.py b/testing/marionette/client/marionette/tests/unit/test_import_script.py index 8b93d7b3ac54..c47f98f1b529 100644 --- a/testing/marionette/client/marionette/tests/unit/test_import_script.py +++ b/testing/marionette/client/marionette/tests/unit/test_import_script.py @@ -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,25 @@ 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(""" + let FileUtils = Components.utils.import("resource://gre/modules/FileUtils.jsm", {}).FileUtils; + let importedScripts = FileUtils.getFile('TmpD', ['marionetteChromeScripts']); + marionetteScriptFinished(importedScripts.exists()); + """) + + def get_file_size(self): + return self.marionette.execute_async_script(""" + let FileUtils = Components.utils.import("resource://gre/modules/FileUtils.jsm", {}).FileUtils; + let importedScripts = FileUtils.getFile('TmpD', ['marionetteChromeScripts']); + marionetteScriptFinished(importedScripts.fileSize); + """) + diff --git a/testing/marionette/marionette-listener.js b/testing/marionette/marionette-listener.js index 23a86b065c11..9bc9a8560eca 100644 --- a/testing/marionette/marionette-listener.js +++ b/testing/marionette/marionette-listener.js @@ -98,7 +98,7 @@ function registerSelf() { if (register[0]) { listenerId = register[0].id; - importedScripts = FileUtils.File(register[0].importedScripts); + importedScripts = FileUtils.getFile('TmpD', ['marionetteContentScripts']); startListeners(); } } diff --git a/testing/marionette/marionette-server.js b/testing/marionette/marionette-server.js index ef75bb092cb0..5c2c4abebee1 100644 --- a/testing/marionette/marionette-server.js +++ b/testing/marionette/marionette-server.js @@ -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: * @@ -1947,7 +1958,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; } @@ -2004,20 +2021,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); }, @@ -2068,6 +2088,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.parameters.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()) { @@ -2093,6 +2130,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. @@ -2210,7 +2264,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") }, @@ -2276,6 +2329,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,