From c3ea9f3eb3ffb0c4627857dba8cd39841bd6bc25 Mon Sep 17 00:00:00 2001 From: Tooru Fujisawa Date: Tue, 20 Sep 2022 01:46:21 +0000 Subject: [PATCH] Bug 1790761 - Add do_importESModule. r=mccr8 Differential Revision: https://phabricator.services.mozilla.com/D157545 --- js/xpconnect/loader/nsImportModule.cpp | 32 ++++++ js/xpconnect/loader/nsImportModule.h | 106 ++++++++++++++++++ .../tests/components/native/moz.build | 1 + .../native/xpctest_esmreturncode.cpp | 20 ++++ .../components/native/xpctest_module.cpp | 2 + .../tests/components/native/xpctest_private.h | 11 ++ .../tests/idl/xpctest_esmreturncode.idl | 45 ++++++++ js/xpconnect/tests/idl/xpctest_returncode.idl | 2 +- .../tests/unit/ReturnCodeChild.sys.mjs | 49 ++++++++ js/xpconnect/tests/unit/test_returncode.js | 17 +-- js/xpconnect/tests/unit/xpcshell.ini | 1 + 11 files changed, 277 insertions(+), 9 deletions(-) create mode 100644 js/xpconnect/tests/components/native/xpctest_esmreturncode.cpp create mode 100644 js/xpconnect/tests/idl/xpctest_esmreturncode.idl create mode 100644 js/xpconnect/tests/unit/ReturnCodeChild.sys.mjs diff --git a/js/xpconnect/loader/nsImportModule.cpp b/js/xpconnect/loader/nsImportModule.cpp index 3d38ada38401..a313c4438899 100644 --- a/js/xpconnect/loader/nsImportModule.cpp +++ b/js/xpconnect/loader/nsImportModule.cpp @@ -77,5 +77,37 @@ nsresult ImportModule(const char* aURI, const char* aExportName, return nsXPConnect::XPConnect()->WrapJS(cx, exports, aIID, aResult); } +nsresult ImportESModule(const char* aURI, const char* aExportName, + const nsIID& aIID, void** aResult, bool aInfallible) { + AutoJSAPI jsapi; + MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope())); + JSContext* cx = jsapi.cx(); + + JS::RootedObject moduleNamespace(cx); + nsresult rv = mozJSModuleLoader::Get()->ImportESModule( + cx, nsDependentCString(aURI), &moduleNamespace); + if (NS_WARN_IF(NS_FAILED(rv))) { + if (aInfallible) { + AnnotateCrashReportWithJSException(cx, aURI); + + MOZ_CRASH_UNSAFE_PRINTF("Failed to load critical module \"%s\"", aURI); + } + return rv; + } + + if (aExportName) { + JS::RootedValue namedExport(cx); + if (!JS_GetProperty(cx, moduleNamespace, aExportName, &namedExport)) { + return NS_ERROR_FAILURE; + } + if (!namedExport.isObject()) { + return NS_ERROR_XPC_BAD_CONVERT_JS; + } + moduleNamespace.set(&namedExport.toObject()); + } + + return nsXPConnect::XPConnect()->WrapJS(cx, moduleNamespace, aIID, aResult); +} + } // namespace loader } // namespace mozilla diff --git a/js/xpconnect/loader/nsImportModule.h b/js/xpconnect/loader/nsImportModule.h index 558a44761254..31f6f8c7c193 100644 --- a/js/xpconnect/loader/nsImportModule.h +++ b/js/xpconnect/loader/nsImportModule.h @@ -19,6 +19,9 @@ namespace loader { nsresult ImportModule(const char* aURI, const char* aExportName, const nsIID& aIID, void** aResult, bool aInfallible); +nsresult ImportESModule(const char* aURI, const char* aExportName, + const nsIID& aIID, void** aResult, bool aInfallible); + } // namespace loader } // namespace mozilla @@ -131,4 +134,107 @@ inline nsImportModule do_ImportModule(const char (&aURI)[N], return {aURI, aExportName, aRv, /* infallible */ false}; } +class MOZ_STACK_CLASS nsImportESModule final : public nsCOMPtr_helper { + public: + nsImportESModule(const char* aURI, const char* aExportName, + nsresult* aErrorPtr, bool aInfallible) + : mURI(aURI), + mExportName(aExportName), + mErrorPtr(aErrorPtr), + mInfallible(aInfallible) { + MOZ_ASSERT_IF(mErrorPtr, !mInfallible); + } + + virtual nsresult NS_FASTCALL operator()(const nsIID& aIID, + void** aResult) const override { + nsresult rv = ::mozilla::loader::ImportESModule(mURI, mExportName, aIID, + aResult, mInfallible); + if (mErrorPtr) { + *mErrorPtr = rv; + } + return rv; + } + + private: + const char* mURI; + const char* mExportName; + nsresult* mErrorPtr; + bool mInfallible; +}; + +/** + * Usage with exported name: + * + * Foo.sys.mjs: + * + * export function foo(bar) { + * return bar.toString(); + * } + * + * mozIFoo.idl: + * + * interface mozIFoo : nsISupports { + * AString foo(double meh); + * } + * + * Thing.cpp: + * + * nsCOMPtr foo = do_ImportESModule( + * "resource://meh/Foo.sys.mjs"); + * + * MOZ_TRY(foo->Foo(42)); + * + * Usage with a single named object: + * + * Foo.sys.mjs: + * + * export var Foo = { + * function foo(bar) { + * return bar.toString(); + * } + * }; + * + * Thing.cpp: + * + * nsCOMPtr foo = do_ImportESModule( + * "resource:://meh/Foo.sys.mjs", "Foo"); + */ + +template +inline nsImportESModule do_ImportESModule(const char (&aURI)[N]) { + return {aURI, nullptr, nullptr, /* infallible */ true}; +} + +template +inline nsImportESModule do_ImportESModule(const char (&aURI)[N], + const mozilla::fallible_t&) { + return {aURI, nullptr, nullptr, /* infallible */ false}; +} + +template +inline nsImportESModule do_ImportESModule(const char (&aURI)[N], + nsresult* aRv) { + return {aURI, nullptr, aRv, /* infallible */ false}; +} + +template +inline nsImportESModule do_ImportESModule(const char (&aURI)[N], + const char (&aExportName)[N2]) { + return {aURI, aExportName, nullptr, /* infallible */ true}; +} + +template +inline nsImportESModule do_ImportESModule(const char (&aURI)[N], + const char (&aExportName)[N2], + const mozilla::fallible_t&) { + return {aURI, aExportName, nullptr, /* infallible */ false}; +} + +template +inline nsImportESModule do_ImportESModule(const char (&aURI)[N], + const char (&aExportName)[N2], + nsresult* aRv) { + return {aURI, aExportName, aRv, /* infallible */ false}; +} + #endif // defined nsImportModule_h diff --git a/js/xpconnect/tests/components/native/moz.build b/js/xpconnect/tests/components/native/moz.build index 295c30ae1015..ba3d227c5b0e 100644 --- a/js/xpconnect/tests/components/native/moz.build +++ b/js/xpconnect/tests/components/native/moz.build @@ -11,6 +11,7 @@ EXPORTS += [ UNIFIED_SOURCES += [ "xpctest_attributes.cpp", "xpctest_cenums.cpp", + "xpctest_esmreturncode.cpp", "xpctest_module.cpp", "xpctest_params.cpp", "xpctest_returncode.cpp", diff --git a/js/xpconnect/tests/components/native/xpctest_esmreturncode.cpp b/js/xpconnect/tests/components/native/xpctest_esmreturncode.cpp new file mode 100644 index 000000000000..758cab65da93 --- /dev/null +++ b/js/xpconnect/tests/components/native/xpctest_esmreturncode.cpp @@ -0,0 +1,20 @@ +/* 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/. */ + +#include "xpctest_private.h" +#include "nsComponentManagerUtils.h" +#include "nsImportModule.h" + +NS_IMPL_ISUPPORTS(nsXPCTestESMReturnCodeParent, nsIXPCTestReturnCodeParent) + +NS_IMETHODIMP nsXPCTestESMReturnCodeParent::CallChild(int32_t childBehavior, + nsresult* _retval) { + nsresult rv; + nsCOMPtr child(do_ImportESModule( + "resource://test/ReturnCodeChild.sys.mjs", "ReturnCodeChild", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + rv = child->DoIt(childBehavior); + *_retval = rv; + return NS_OK; +} diff --git a/js/xpconnect/tests/components/native/xpctest_module.cpp b/js/xpconnect/tests/components/native/xpctest_module.cpp index b14951a25afc..98ee300e4868 100644 --- a/js/xpconnect/tests/components/native/xpctest_module.cpp +++ b/js/xpconnect/tests/components/native/xpctest_module.cpp @@ -36,6 +36,8 @@ nsresult xpcTestRegisterComponents() { "@mozilla.org/js/xpc/test/native/Params;1")); MOZ_TRY(RegisterFactory( "@mozilla.org/js/xpc/test/native/ReturnCodeParent;1")); + MOZ_TRY(RegisterFactory( + "@mozilla.org/js/xpc/test/native/ESMReturnCodeParent;1")); MOZ_TRY(RegisterFactory( "@mozilla.org/js/xpc/test/native/CEnums;1")); diff --git a/js/xpconnect/tests/components/native/xpctest_private.h b/js/xpconnect/tests/components/native/xpctest_private.h index cf88753b6bbd..905cd26f485d 100644 --- a/js/xpconnect/tests/components/native/xpctest_private.h +++ b/js/xpconnect/tests/components/native/xpctest_private.h @@ -79,6 +79,17 @@ class nsXPCTestReturnCodeParent final : public nsIXPCTestReturnCodeParent { ~nsXPCTestReturnCodeParent() = default; }; +class nsXPCTestESMReturnCodeParent final : public nsIXPCTestReturnCodeParent { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCTESTRETURNCODEPARENT + + nsXPCTestESMReturnCodeParent() = default; + + private: + ~nsXPCTestESMReturnCodeParent() = default; +}; + class xpcTestCEnums final : public nsIXPCTestCEnums { public: NS_DECL_ISUPPORTS diff --git a/js/xpconnect/tests/idl/xpctest_esmreturncode.idl b/js/xpconnect/tests/idl/xpctest_esmreturncode.idl new file mode 100644 index 000000000000..ac17feda3fc2 --- /dev/null +++ b/js/xpconnect/tests/idl/xpctest_esmreturncode.idl @@ -0,0 +1,45 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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/. */ + +/** + * Test the use of Components.returnCode with system ESM + * + * This ("parent") interface defines a method that in-turn calls another + * ("child") interface implemented in JS, and returns the nsresult from that + * child interface. The child interface manages the return code by way of + * Components.returnCode. + */ + +#include "nsISupports.idl" + + +[scriptable, uuid(494f9336-ad06-46ad-bbb4-b0010e27e12d)] +interface nsIXPCTestESMReturnCodeParent : nsISupports { + // Calls the "child" interface with the specified behavior flag. Returns + // the NSRESULT from the child interface. + nsresult callChild(in long childBehavior); +}; + +[scriptable, uuid(dee07408-75d8-4968-a37c-fe0d48ccd1ac)] +interface nsIXPCTestESMReturnCodeChild : nsISupports { + void doIt(in long behavior); + + // Flags to control that the child does. + // child will throw a JS exception + const long CHILD_SHOULD_THROW = 0; + + // child will just return normally + const long CHILD_SHOULD_RETURN_SUCCESS = 1; + + // child will return after setting Components.returnCode to NS_ERROR_FAILURE + const long CHILD_SHOULD_RETURN_RESULTCODE = 2; + + // child will set Components.returnCode to NS_ERROR_UNEXPECTED, then create + // a new component that sets Components.returnCode to NS_ERROR_FAILURE. + // Our caller should see the NS_ERROR_UNEXPECTED we set rather than the + // value set later by the "inner" child. + const long CHILD_SHOULD_NEST_RESULTCODES = 3; +}; diff --git a/js/xpconnect/tests/idl/xpctest_returncode.idl b/js/xpconnect/tests/idl/xpctest_returncode.idl index d6555b9e206a..5ee6c554791b 100644 --- a/js/xpconnect/tests/idl/xpctest_returncode.idl +++ b/js/xpconnect/tests/idl/xpctest_returncode.idl @@ -20,7 +20,7 @@ interface nsIXPCTestReturnCodeParent : nsISupports { // Calls the "child" interface with the specified behavior flag. Returns // the NSRESULT from the child interface. - nsresult callChild(in long childBehavior); + nsresult callChild(in long childBehavior); }; [scriptable, uuid(672cfd34-1fd1-455d-9901-d879fa6fdb95)] diff --git a/js/xpconnect/tests/unit/ReturnCodeChild.sys.mjs b/js/xpconnect/tests/unit/ReturnCodeChild.sys.mjs new file mode 100644 index 000000000000..4d3120da33e8 --- /dev/null +++ b/js/xpconnect/tests/unit/ReturnCodeChild.sys.mjs @@ -0,0 +1,49 @@ +/* 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/. */ + +function xpcWrap(obj, iface) { + let ifacePointer = Cc[ + "@mozilla.org/supports-interface-pointer;1" + ].createInstance(Ci.nsISupportsInterfacePointer); + + ifacePointer.data = obj; + return ifacePointer.data.QueryInterface(iface); +} + +export var ReturnCodeChild = { + QueryInterface: ChromeUtils.generateQI(["nsIXPCTestReturnCodeChild"]), + + doIt(behaviour) { + switch (behaviour) { + case Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_THROW: + throw(new Error("a requested error")); + case Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_RETURN_SUCCESS: + return; + case Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_RETURN_RESULTCODE: + Components.returnCode = Cr.NS_ERROR_FAILURE; + return; + case Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_NEST_RESULTCODES: + // Use xpconnect to create another instance of *this* component and + // call that. This way we have crossed the xpconnect bridge twice. + + // We set *our* return code early - this should be what is returned + // to our caller, even though our "inner" component will set it to + // a different value that we will see (but our caller should not) + Components.returnCode = Cr.NS_ERROR_UNEXPECTED; + // call the child asking it to do the .returnCode set. + let sub = xpcWrap(ReturnCodeChild, Ci.nsIXPCTestReturnCodeChild); + let childResult = Cr.NS_OK; + try { + sub.doIt(Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_RETURN_RESULTCODE); + } catch (ex) { + childResult = ex.result; + } + // write it to the console so the test can check it. + let consoleService = Cc["@mozilla.org/consoleservice;1"] + .getService(Ci.nsIConsoleService); + consoleService.logStringMessage("nested child returned " + childResult); + return; + } + } +}; diff --git a/js/xpconnect/tests/unit/test_returncode.js b/js/xpconnect/tests/unit/test_returncode.js index a036a416ee5c..de4289c0131d 100644 --- a/js/xpconnect/tests/unit/test_returncode.js +++ b/js/xpconnect/tests/unit/test_returncode.js @@ -15,13 +15,15 @@ function run_test() { registerXPCTestComponents(); // and the tests. - test_simple(); - test_nested(); + test_simple("@mozilla.org/js/xpc/test/native/ReturnCodeParent;1"); + test_nested("@mozilla.org/js/xpc/test/native/ReturnCodeParent;1"); + + test_simple("@mozilla.org/js/xpc/test/native/ESMReturnCodeParent;1"); + test_nested("@mozilla.org/js/xpc/test/native/ESMReturnCodeParent;1"); } -function test_simple() { - let parent = Cc["@mozilla.org/js/xpc/test/native/ReturnCodeParent;1"] - .createInstance(Ci.nsIXPCTestReturnCodeParent); +function test_simple(contractID) { + let parent = Cc[contractID].createInstance(Ci.nsIXPCTestReturnCodeParent); let result; // flush existing messages before we start testing. @@ -51,9 +53,8 @@ function test_simple() { Assert.deepEqual(getConsoleMessages(), [], "no messages reported with .returnCode"); } -function test_nested() { - let parent = Cc["@mozilla.org/js/xpc/test/native/ReturnCodeParent;1"] - .createInstance(Ci.nsIXPCTestReturnCodeParent); +function test_nested(contractID) { + let parent = Cc[contractID].createInstance(Ci.nsIXPCTestReturnCodeParent); let result; // flush existing messages before we start testing. diff --git a/js/xpconnect/tests/unit/xpcshell.ini b/js/xpconnect/tests/unit/xpcshell.ini index 2e3cf9453848..6390bf21717e 100644 --- a/js/xpconnect/tests/unit/xpcshell.ini +++ b/js/xpconnect/tests/unit/xpcshell.ini @@ -16,6 +16,7 @@ support-files = recursive_importA.jsm recursive_importB.jsm ReturnCodeChild.jsm + ReturnCodeChild.sys.mjs syntax_error.jsm uninitialized_lexical.jsm es6module.js