From 0d045ee1065194f35d328b8ccdac18d9fe9f80cb Mon Sep 17 00:00:00 2001 From: Dave Townsend Date: Wed, 15 Jun 2011 11:08:43 -0700 Subject: [PATCH] Bug 481603: Add a way to unload JS modules loaded with Components.utils.import. r=mrbkap --- js/src/xpconnect/idl/xpcIJSModuleLoader.idl | 14 +++- js/src/xpconnect/idl/xpccomponents.idl | 12 +++- .../xpconnect/loader/mozJSComponentLoader.cpp | 70 +++++++++++++++++++ js/src/xpconnect/src/xpccomponents.cpp | 12 ++++ js/src/xpconnect/tests/unit/test_unload.js | 65 +++++++++++++++++ js/src/xpconnect/tests/unit/xpcshell.ini | 1 + 6 files changed, 170 insertions(+), 4 deletions(-) create mode 100644 js/src/xpconnect/tests/unit/test_unload.js diff --git a/js/src/xpconnect/idl/xpcIJSModuleLoader.idl b/js/src/xpconnect/idl/xpcIJSModuleLoader.idl index 926f36afaeea..0fddc471edb8 100644 --- a/js/src/xpconnect/idl/xpcIJSModuleLoader.idl +++ b/js/src/xpconnect/idl/xpcIJSModuleLoader.idl @@ -48,14 +48,14 @@ struct JSObject; [ptr] native JSObjectPtr(JSObject); -[scriptable, uuid(89da3673-e699-4f26-9ed7-11a528011434)] +[scriptable, uuid(3f945a8e-58ca-47ba-a789-82d022e837fd)] interface xpcIJSModuleLoader : nsISupports { /** * To be called from JavaScript only. * * Synchronously loads and evaluates the js file located at - * 'registryLocation' with a new, fully privileged global object. + * aResourceURI with a new, fully privileged global object. * * If 'targetObj' is specified and equal to null, returns the * module's global object. Otherwise (if 'targetObj' is not @@ -85,11 +85,19 @@ interface xpcIJSModuleLoader : nsISupports /* , [optional] in JSObject targetObj */); /** - * Imports the JS module at 'registryLocation' to the JS object + * Imports the JS module at aResourceURI to the JS object * 'targetObj' (if != null) as described for importModule() and * returns the module's global object. */ [noscript] JSObjectPtr importInto(in AUTF8String aResourceURI, in JSObjectPtr targetObj, in nsAXPCNativeCallContextPtr cc); + + /** + * Unloads the JS module at aResourceURI. Existing references to the module + * will continue to work but any subsequent import of the module will + * reload it and give new reference. If the JS module hasn't yet been imported + * then this method will do nothing. + */ + void unload(in AUTF8String aResourceURI); }; diff --git a/js/src/xpconnect/idl/xpccomponents.idl b/js/src/xpconnect/idl/xpccomponents.idl index 9de5574d810b..2e0ad7b2a0a4 100644 --- a/js/src/xpconnect/idl/xpccomponents.idl +++ b/js/src/xpconnect/idl/xpccomponents.idl @@ -123,7 +123,7 @@ interface nsIXPCComponents_utils_Sandbox : nsISupports /** * interface of Components.utils */ -[scriptable, uuid(c1d616fa-1875-49ba-b7b8-5861dab31a42)] +[scriptable, uuid(5f0acf45-135a-48d1-976c-082ce3b24ead)] interface nsIXPCComponents_Utils : nsISupports { @@ -204,6 +204,16 @@ interface nsIXPCComponents_Utils : nsISupports void /* JSObject */ import(in AUTF8String registryLocation /*, [optional] in JSObject targetObj */); + /* + * Unloads the JS module at 'registryLocation'. Existing references to the + * module will continue to work but any subsequent import of the module will + * reload it and give new reference. If the JS module hasn't yet been + * imported then this method will do nothing. + * + * @param resourceURI A resource:// URI string to unload the module from. + */ + void unload(in AUTF8String registryLocation); + /* * To be called from JS only. * diff --git a/js/src/xpconnect/loader/mozJSComponentLoader.cpp b/js/src/xpconnect/loader/mozJSComponentLoader.cpp index 88932fda25c6..a0838d631c02 100644 --- a/js/src/xpconnect/loader/mozJSComponentLoader.cpp +++ b/js/src/xpconnect/loader/mozJSComponentLoader.cpp @@ -1601,6 +1601,76 @@ mozJSComponentLoader::ImportInto(const nsACString & aLocation, return NS_OK; } +NS_IMETHODIMP +mozJSComponentLoader::Unload(const nsACString & aLocation) +{ + nsresult rv; + + if (!mInitialized) { + return NS_OK; + } + + nsCOMPtr ioService = do_GetIOService(&rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Get the URI. + nsCOMPtr resURI; + rv = ioService->NewURI(aLocation, nsnull, nsnull, getter_AddRefs(resURI)); + NS_ENSURE_SUCCESS(rv, rv); + + // figure out the resolved URI + nsCOMPtr scriptChannel; + rv = ioService->NewChannelFromURI(resURI, getter_AddRefs(scriptChannel)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG); + + nsCOMPtr resolvedURI; + rv = scriptChannel->GetURI(getter_AddRefs(resolvedURI)); + NS_ENSURE_SUCCESS(rv, rv); + + // get the JAR if there is one + nsCOMPtr jarURI; + jarURI = do_QueryInterface(resolvedURI, &rv); + nsCOMPtr baseFileURL; + nsCAutoString jarEntry; + if (NS_SUCCEEDED(rv)) { + nsCOMPtr baseURI; + rv = jarURI->GetJARFile(getter_AddRefs(baseURI)); + NS_ENSURE_SUCCESS(rv, rv); + + baseFileURL = do_QueryInterface(baseURI, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + jarURI->GetJAREntry(jarEntry); + NS_ENSURE_SUCCESS(rv, rv); + } else { + baseFileURL = do_QueryInterface(resolvedURI, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr sourceFile; + rv = baseFileURL->GetFile(getter_AddRefs(sourceFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr sourceLocalFile; + sourceLocalFile = do_QueryInterface(sourceFile, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString key; + if (jarEntry.IsEmpty()) { + rv = FileKey(sourceLocalFile, key); + } else { + rv = JarKey(sourceLocalFile, jarEntry, key); + } + NS_ENSURE_SUCCESS(rv, rv); + + ModuleEntry* mod; + if (mImports.Get(key, &mod)) { + mImports.Remove(key); + } + + return NS_OK; +} + NS_IMETHODIMP mozJSComponentLoader::Observe(nsISupports *subject, const char *topic, const PRUnichar *data) diff --git a/js/src/xpconnect/src/xpccomponents.cpp b/js/src/xpconnect/src/xpccomponents.cpp index 2886d2cf7141..0e8cb62b95ad 100644 --- a/js/src/xpconnect/src/xpccomponents.cpp +++ b/js/src/xpconnect/src/xpccomponents.cpp @@ -3725,6 +3725,18 @@ nsXPCComponents_Utils::Import(const nsACString & registryLocation) return moduleloader->Import(registryLocation); } +/* unload (in AUTF8String registryLocation); + */ +NS_IMETHODIMP +nsXPCComponents_Utils::Unload(const nsACString & registryLocation) +{ + nsCOMPtr moduleloader = + do_GetService(MOZJSCOMPONENTLOADER_CONTRACTID); + if (!moduleloader) + return NS_ERROR_FAILURE; + return moduleloader->Unload(registryLocation); +} + /* xpcIJSWeakReference getWeakReference (); */ NS_IMETHODIMP nsXPCComponents_Utils::GetWeakReference(xpcIJSWeakReference **_retval) diff --git a/js/src/xpconnect/tests/unit/test_unload.js b/js/src/xpconnect/tests/unit/test_unload.js new file mode 100644 index 000000000000..5a6762af6683 --- /dev/null +++ b/js/src/xpconnect/tests/unit/test_unload.js @@ -0,0 +1,65 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla code. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Dave Townsend (original author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +function run_test() { + var scope1 = {}; + var global1 = Components.utils.import("resource://gre/modules/NetUtil.jsm", scope1); + + var scope2 = {}; + var global2 = Components.utils.import("resource://gre/modules/NetUtil.jsm", scope2); + + do_check_true(global1 === global2); + do_check_true(scope1.NetUtil === scope2.NetUtil); + + Components.utils.unload("resource://gre/modules/NetUtil.jsm"); + + var scope3 = {}; + var global3 = Components.utils.import("resource://gre/modules/NetUtil.jsm", scope3); + + do_check_false(global1 === global3); + do_check_false(scope1.NetUtil === scope3.NetUtil); + + // Both instances should work + uri1 = scope1.NetUtil.newURI("http://www.example.com"); + do_check_true(uri1 instanceof Components.interfaces.nsIURL); + + var uri3 = scope3.NetUtil.newURI("http://www.example.com"); + do_check_true(uri3 instanceof Components.interfaces.nsIURL); + + do_check_true(uri1.equals(uri3)); +} diff --git a/js/src/xpconnect/tests/unit/xpcshell.ini b/js/src/xpconnect/tests/unit/xpcshell.ini index 24d2ea431beb..9dc2bb9a1168 100644 --- a/js/src/xpconnect/tests/unit/xpcshell.ini +++ b/js/src/xpconnect/tests/unit/xpcshell.ini @@ -15,3 +15,4 @@ tail = [test_localeCompare.js] [test_recursive_import.js] [test_xpcomutils.js] +[test_unload.js]