author | Gabor Krizsanits <gkrizsanits@mozilla.com> |
Mon, 19 Aug 2013 11:46:36 +0200 | |
changeset 143051 | 9b4c4e56f4bb76d04bd2119bd08d05cd03316823 |
parent 143050 | ad0a766f2f6bb0593477784ce9035ca2b826894b |
child 143052 | d608711909f43b57154ce4ba11a45c656c95b826 |
child 143105 | 5e29096a0d1243482ec16d8c1fbf3d7afe97218c |
child 143129 | 04149d2dace694bf8c9a0013bd21567ffe495be9 |
child 155715 | 156f07313f85232b60d559b088e93246544bf1ca |
push id | 32609 |
push user | gkrizsanits@mozilla.com |
push date | Mon, 19 Aug 2013 09:49:40 +0000 |
treeherder | mozilla-inbound@9b4c4e56f4bb [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | bholley |
bugs | 877673 |
milestone | 26.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/js/xpconnect/src/XPCComponents.cpp +++ b/js/xpconnect/src/XPCComponents.cpp @@ -32,16 +32,18 @@ #include "nsPrincipal.h" #include "mozilla/Attributes.h" #include "nsIScriptContext.h" #include "nsJSEnvironment.h" #include "nsXMLHttpRequest.h" #include "mozilla/Telemetry.h" #include "mozilla/XPTInterfaceInfoManager.h" #include "nsDOMClassInfoID.h" +#include "nsGlobalWindow.h" + using namespace mozilla; using namespace js; using namespace xpc; using mozilla::dom::DestroyProtoAndIfaceCache; /***************************************************************************/ @@ -2930,16 +2932,363 @@ CreateXMLHttpRequest(JSContext *cx, unsi rv = nsContentUtils::WrapNative(cx, global, xhr, vp); if (NS_FAILED(rv)) return false; return true; } +bool +NewFunctionForwarder(JSContext *cx, HandleId id, HandleObject callable, + bool doclone, MutableHandleValue vp); + +/* + * Instead of simply wrapping a function into another compartment, + * this helper function creates a native function in the target + * compartment and forwards the call to the original function. + * That call will be different than a regular JS function call in + * that, the |this| is left unbound, and all the non-native JS + * object arguments will be cloned using the structured clone + * algorithm. + * The return value is the new forwarder function, wrapped into + * the caller's compartment. + * The 3rd argument is the name of the property that will + * be set on the target scope, with the forwarder function as + * the value. + * The principal of the caller must subsume that of the target. + * + * Expected type of the arguments and the return value: + * function exportFunction(function funToExport, + * object targetScope, + * string name) + */ +static bool +ExportFunction(JSContext *cx, unsigned argc, jsval *vp) +{ + MOZ_ASSERT(cx); + if (argc < 3) { + JS_ReportError(cx, "Function requires at least 3 arguments"); + return false; + } + + CallArgs args = CallArgsFromVp(argc, vp); + if (!args[0].isObject() || !args[1].isObject() || !args[2].isString()) { + JS_ReportError(cx, "Invalid argument"); + return false; + } + + RootedObject funObj(cx, &args[0].toObject()); + RootedObject targetScope(cx, &args[1].toObject()); + RootedString funName(cx, args[2].toString()); + + // We can only export functions to scopes those are transparent for us, + // so if there is a security wrapper around targetScope we must throw. + targetScope = CheckedUnwrap(targetScope); + if (!targetScope) { + JS_ReportError(cx, "Permission denied to export function into scope"); + return false; + } + + if (JS_GetStringLength(funName) == 0) { + JS_ReportError(cx, "3rd argument should be a non-empty string"); + return false; + } + + { + // We need to operate in the target scope from here on, let's enter + // its compartment. + JSAutoCompartment ac(cx, targetScope); + + // Unwrapping to see if we have a callable. + funObj = UncheckedUnwrap(funObj); + if (!JS_ObjectIsCallable(cx, funObj)) { + JS_ReportError(cx, "First argument must be a function"); + return false; + } + + // The function forwarder will live in the target compartment. Since + // this function will be referenced from its private slot, to avoid a + // GC hazard, we must wrap it to the same compartment. + if (!JS_WrapObject(cx, funObj.address())) + return false; + + RootedId id(cx); + if (!JS_ValueToId(cx, args[2], id.address())) + return false; + + // And now, let's create the forwarder function in the target compartment + // for the function the be exported. + if (!NewFunctionForwarder(cx, id, funObj, /* doclone = */ true, args.rval())) { + JS_ReportError(cx, "Exporting function failed"); + return false; + } + + // We have the forwarder function in the target compartment, now + // we have to add it to the target scope as a property. + if (!JS_DefinePropertyById(cx, targetScope, id, args.rval(), + JS_PropertyStub, JS_StrictPropertyStub, + JSPROP_ENUMERATE)) + return false; + } + + // Finally we have to re-wrap the exported function back to the caller compartment. + if (!JS_WrapValue(cx, args.rval().address())) + return false; + + return true; +} + +static bool +GetFilenameAndLineNumber(JSContext *cx, nsACString &filename, unsigned &lineno) +{ + JSScript *script; + if (JS_DescribeScriptedCaller(cx, &script, &lineno)) { + if (const char *cfilename = JS_GetScriptFilename(cx, script)) { + filename.Assign(nsDependentCString(cfilename)); + return true; + } + } + return false; +} + +namespace xpc { +bool +IsReflector(JSObject *obj) +{ + return IS_WN_REFLECTOR(obj) || dom::IsDOMObject(obj); +} +} /* namespace xpc */ + +enum ForwarderCloneTags { + SCTAG_BASE = JS_SCTAG_USER_MIN, + SCTAG_REFLECTOR +}; + +static JSObject * +CloneNonReflectorsRead(JSContext *cx, JSStructuredCloneReader *reader, uint32_t tag, + uint32_t data, void *closure) +{ + MOZ_ASSERT(closure, "Null pointer!"); + AutoObjectVector *reflectors = static_cast<AutoObjectVector *>(closure); + if (tag == SCTAG_REFLECTOR) { + MOZ_ASSERT(!data); + + size_t idx; + if (JS_ReadBytes(reader, &idx, sizeof(size_t))) { + RootedObject reflector(cx, reflectors->handleAt(idx)); + MOZ_ASSERT(reflector, "No object pointer?"); + MOZ_ASSERT(IsReflector(reflector), "Object pointer must be a reflector!"); + + JS_WrapObject(cx, reflector.address()); + JS_ASSERT(WrapperFactory::IsXrayWrapper(reflector) || + IsReflector(reflector)); + + return reflector; + } + } + + JS_ReportError(cx, "CloneNonReflectorsRead error"); + return nullptr; +} + +static bool +CloneNonReflectorsWrite(JSContext *cx, JSStructuredCloneWriter *writer, + Handle<JSObject *> obj, void *closure) +{ + MOZ_ASSERT(closure, "Null pointer!"); + + // We need to maintain a list of reflectors to make sure all these objects + // are properly rooter. Only their indices will be serialized. + AutoObjectVector *reflectors = static_cast<AutoObjectVector *>(closure); + if (IsReflector(obj)) { + if (!reflectors->append(obj)) + return false; + + size_t idx = reflectors->length()-1; + if (JS_WriteUint32Pair(writer, SCTAG_REFLECTOR, 0) && + JS_WriteBytes(writer, &idx, sizeof(size_t))) { + return true; + } + } + + JS_ReportError(cx, "CloneNonReflectorsWrite error"); + return false; +} + +JSStructuredCloneCallbacks gForwarderStructuredCloneCallbacks = { + CloneNonReflectorsRead, + CloneNonReflectorsWrite, + nullptr +}; + +/* + * This is a special structured cloning, that clones only non-reflectors. + * The function assumes the cx is already entered the compartment we want + * to clone to, and that if val is an object is from the compartment we + * clone from. + */ +bool +CloneNonReflectors(JSContext *cx, MutableHandleValue val) +{ + JSAutoStructuredCloneBuffer buffer; + AutoObjectVector rootedReflectors(cx); + { + // For parsing val we have to enter its compartment. + // (unless it's a primitive) + Maybe<JSAutoCompartment> ac; + if (val.isObject()) { + ac.construct(cx, &val.toObject()); + } + + if (!buffer.write(cx, val, + &gForwarderStructuredCloneCallbacks, + &rootedReflectors)) + { + return false; + } + } + + // Now recreate the clones in the target compartment. + RootedValue rval(cx); + if (!buffer.read(cx, val.address(), + &gForwarderStructuredCloneCallbacks, + &rootedReflectors)) + { + return false; + } + + return true; +} + +/* + * Similar to evalInSandbox except this one is used to eval a script in the + * scope of a window. Also note, that the return value and the possible exceptions + * in the script are structured cloned, unless they are natives (then they are just + * wrapped). + * Principal of the caller must subsume the target's. + * + * Expected type of the arguments: + * value evalInWindow(string script, + * object window) + */ +static bool +EvalInWindow(JSContext *cx, unsigned argc, jsval *vp) +{ + MOZ_ASSERT(cx); + if (argc < 2) { + JS_ReportError(cx, "Function requires two arguments"); + return false; + } + + CallArgs args = CallArgsFromVp(argc, vp); + if (!args[0].isString() || !args[1].isObject()) { + JS_ReportError(cx, "Invalid arguments"); + return false; + } + + RootedString srcString(cx, args[0].toString()); + RootedObject targetScope(cx, &args[1].toObject()); + + // If we cannot unwrap we must not eval in it. + targetScope = CheckedUnwrap(targetScope); + if (!targetScope) { + JS_ReportError(cx, "Permission denied to eval in target scope"); + return false; + } + + // Make sure that we have a window object. + RootedObject inner(cx, CheckedUnwrap(targetScope, /* stopAtOuter = */ false)); + nsCOMPtr<nsIGlobalObject> global; + nsCOMPtr<nsPIDOMWindow> window; + if (!JS_IsGlobalObject(inner) || + !(global = GetNativeForGlobal(inner)) || + !(window = do_QueryInterface(global))) + { + JS_ReportError(cx, "Second argument must be a window"); + return false; + } + + nsCOMPtr<nsIScriptContext> context = + (static_cast<nsGlobalWindow*>(window.get()))->GetScriptContext(); + if (!context) { + JS_ReportError(cx, "Script context needed"); + return false; + } + + if (!context->GetScriptsEnabled()) { + JS_ReportError(cx, "Scripts are disabled in this window"); + return false; + } + + nsCString filename; + unsigned lineNo; + if (!GetFilenameAndLineNumber(cx, filename, lineNo)) { + // Default values for non-scripted callers. + filename.Assign("Unknown"); + lineNo = 0; + } + + nsDependentJSString srcDepString; + srcDepString.init(cx, srcString); + + { + // CompileOptions must be created from the context + // we will execute this script in. + JSContext *wndCx = context->GetNativeContext(); + AutoCxPusher pusher(wndCx); + JS::CompileOptions compileOptions(wndCx); + compileOptions.setFileAndLine(filename.get(), lineNo); + + // We don't want the JS engine to automatically report + // uncaught exceptions. + nsJSUtils::EvaluateOptions evaluateOptions; + evaluateOptions.setReportUncaught(false); + + nsresult rv = nsJSUtils::EvaluateString(wndCx, + srcDepString, + targetScope, + compileOptions, + evaluateOptions, + args.rval().address()); + + if (NS_FAILED(rv)) { + // If there was an exception we get it as a return value, if + // the evaluation failed for some other reason, then a default + // exception is raised. + MOZ_ASSERT(!JS_IsExceptionPending(wndCx), + "Exception should be delivered as return value."); + if (args.rval().isUndefined()) { + MOZ_ASSERT(rv == NS_ERROR_OUT_OF_MEMORY); + return false; + } + + // If there was an exception thrown we should set it + // on the calling context. + RootedValue exn(wndCx, args.rval()); + // First we should reset the return value. + args.rval().set(UndefinedValue()); + + // Then clone the exception. + if (CloneNonReflectors(cx, &exn)) + JS_SetPendingException(cx, exn); + + return false; + } + } + + // Let's clone the return value back to the callers compartment. + if (!CloneNonReflectors(cx, args.rval())) { + args.rval().set(UndefinedValue()); + return false; + } + + return true; +} + static bool sandbox_enumerate(JSContext *cx, HandleObject obj) { return JS_EnumerateStandardClasses(cx, obj); } static bool sandbox_resolve(JSContext *cx, HandleObject obj, HandleId id) @@ -3345,16 +3694,22 @@ xpc_CreateSandboxObject(JSContext *cx, j return NS_ERROR_XPC_UNEXPECTED; if (!JS_DefineFunctions(cx, sandbox, SandboxFunctions)) return NS_ERROR_XPC_UNEXPECTED; if (options.wantXHRConstructor && !JS_DefineFunction(cx, sandbox, "XMLHttpRequest", CreateXMLHttpRequest, 0, JSFUN_CONSTRUCTOR)) return NS_ERROR_XPC_UNEXPECTED; + + if (options.wantExportHelpers && + (!JS_DefineFunction(cx, sandbox, "exportFunction", ExportFunction, 3, 0) || + !JS_DefineFunction(cx, sandbox, "evalInWindow", EvalInWindow, 2, 0))) + return NS_ERROR_XPC_UNEXPECTED; + } if (vp) { // We have this crazy behavior where wantXrays=false also implies that the // returned sandbox is implicitly waived. We've stopped advertising it, but // keep supporting it for now. *vp = OBJECT_TO_JSVAL(sandbox); if (options.wantXrays && !JS_WrapValue(cx, vp)) @@ -3615,16 +3970,20 @@ ParseOptionsObject(JSContext *cx, jsval rv = GetBoolPropFromOptions(cx, optionsObject, "wantComponents", &options.wantComponents); NS_ENSURE_SUCCESS(rv, rv); rv = GetBoolPropFromOptions(cx, optionsObject, "wantXHRConstructor", &options.wantXHRConstructor); NS_ENSURE_SUCCESS(rv, rv); + rv = GetBoolPropFromOptions(cx, optionsObject, + "wantExportHelpers", &options.wantExportHelpers); + NS_ENSURE_SUCCESS(rv, rv); + rv = GetStringPropFromOptions(cx, optionsObject, "sandboxName", options.sandboxName); NS_ENSURE_SUCCESS(rv, rv); rv = GetObjPropFromOptions(cx, optionsObject, "sameZoneAs", options.sameZoneAs.address()); NS_ENSURE_SUCCESS(rv, rv); @@ -4200,41 +4559,78 @@ nsXPCComponents_Utils::CreateDateIn(cons if (!JS_WrapObject(cx, obj.address())) return NS_ERROR_FAILURE; *rval = ObjectValue(*obj); return NS_OK; } bool -FunctionWrapper(JSContext *cx, unsigned argc, Value *vp) +NonCloningFunctionForwarder(JSContext *cx, unsigned argc, Value *vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedValue v(cx, js::GetFunctionNativeReserved(&args.callee(), 0)); MOZ_ASSERT(v.isObject(), "weird function"); JSObject *obj = JS_THIS_OBJECT(cx, vp); if (!obj) { return false; } return JS_CallFunctionValue(cx, obj, v, args.length(), args.array(), vp); } +/* + * Forwards the call to the exported function. Clones all the non reflectors, ignores + * the |this| argument. + */ bool -WrapCallable(JSContext *cx, HandleObject obj, HandleId id, HandleObject propobj, - MutableHandleValue vp) -{ - JSFunction *fun = js::NewFunctionByIdWithReserved(cx, FunctionWrapper, 0, 0, - JS_GetGlobalForObject(cx, obj), id); +CloningFunctionForwarder(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + RootedValue v(cx, js::GetFunctionNativeReserved(&args.callee(), 0)); + NS_ASSERTION(v.isObject(), "weird function"); + RootedObject origFunObj(cx, UncheckedUnwrap(&v.toObject())); + { + JSAutoCompartment ac(cx, origFunObj); + // Note: only the arguments are cloned not the |this| or the |callee|. + // Function forwarder does not use those. + for (unsigned i = 0; i < args.length(); i++) { + if (!CloneNonReflectors(cx, args[i])) { + return false; + } + } + + // JS API does not support any JSObject to JSFunction conversion, + // so let's use JS_CallFunctionValue instead. + RootedValue functionVal(cx); + functionVal.setObject(*origFunObj); + + if (!JS_CallFunctionValue(cx, nullptr, functionVal, args.length(), args.array(), vp)) + return false; + } + + // Return value must be wrapped. + return JS_WrapValue(cx, vp); +} + +bool +NewFunctionForwarder(JSContext *cx, HandleId id, HandleObject callable, bool doclone, + MutableHandleValue vp) +{ + JSFunction *fun = js::NewFunctionByIdWithReserved(cx, doclone ? CloningFunctionForwarder : + NonCloningFunctionForwarder, + 0,0, JS::CurrentGlobalOrNull(cx), id); + if (!fun) return false; JSObject *funobj = JS_GetFunctionObject(fun); - js::SetFunctionNativeReserved(funobj, 0, ObjectValue(*propobj)); + js::SetFunctionNativeReserved(funobj, 0, ObjectValue(*callable)); vp.setObject(*funobj); return true; } /* void makeObjectPropsNormal(jsval vobj); */ NS_IMETHODIMP nsXPCComponents_Utils::MakeObjectPropsNormal(const Value &vobj, JSContext *cx) { @@ -4262,17 +4658,17 @@ nsXPCComponents_Utils::MakeObjectPropsNo if (v.isPrimitive()) continue; RootedObject propobj(cx, &v.toObject()); // TODO Deal with non-functions. if (!js::IsWrapper(propobj) || !JS_ObjectIsCallable(cx, propobj)) continue; - if (!WrapCallable(cx, obj, id, propobj, &v) || + if (!NewFunctionForwarder(cx, id, propobj, /* doclone = */ false, &v) || !JS_SetPropertyById(cx, obj, id, v)) return NS_ERROR_FAILURE; } return NS_OK; } NS_IMETHODIMP
--- a/js/xpconnect/src/xpcprivate.h +++ b/js/xpconnect/src/xpcprivate.h @@ -3695,23 +3695,25 @@ xpc_GetSafeJSContext() } namespace xpc { struct SandboxOptions { SandboxOptions(JSContext *cx) : wantXrays(true) , wantComponents(true) , wantXHRConstructor(false) + , wantExportHelpers(false) , proto(xpc_GetSafeJSContext()) , sameZoneAs(xpc_GetSafeJSContext()) { } bool wantXrays; bool wantComponents; bool wantXHRConstructor; + bool wantExportHelpers; JS::RootedObject proto; nsCString sandboxName; JS::RootedObject sameZoneAs; }; JSObject * CreateGlobalObject(JSContext *cx, JSClass *clasp, nsIPrincipal *principal, JS::CompartmentOptions& aOptions);
--- a/js/xpconnect/src/xpcpublic.h +++ b/js/xpconnect/src/xpcpublic.h @@ -61,16 +61,18 @@ GetXBLScope(JSContext *cx, JSObject *con // Returns whether XBL scopes have been explicitly disabled for code running // in this compartment. See the comment around mAllowXBLScope. bool AllowXBLScope(JSCompartment *c); bool IsSandboxPrototypeProxy(JSObject *obj); +bool +IsReflector(JSObject *obj); } /* namespace xpc */ namespace JS { struct RuntimeStats; }
--- a/js/xpconnect/tests/chrome/Makefile.in +++ b/js/xpconnect/tests/chrome/Makefile.in @@ -56,16 +56,17 @@ MOCHITEST_CHROME_FILES = \ outoflinexulscript.js \ subscript.js \ utf8_subscript.js \ test_cows.xul \ test_documentdomain.xul \ test_doublewrappedcompartments.xul \ test_evalInSandbox.xul \ file_evalInSandbox.html \ + test_evalInWindow.xul \ test_exnstack.xul \ test_expandosharing.xul \ file_expandosharing.jsm \ test_exposeInDerived.xul \ test_getweakmapkeys.xul \ test_mozMatchesSelector.xul \ test_nodelists.xul \ test_precisegc.xul \
new file mode 100644 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_evalInWindow.xul @@ -0,0 +1,75 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=877673 +--> +<window title="Mozilla Bug 877673" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + SimpleTest.waitForExplicitFinish(); + const Cu = Components.utils; + var sb = new Cu.Sandbox("http://example.org", {wantExportHelpers: true}); + sb.ok = ok; + + function executeIn(frame, script, exceptionCb) { + sb.frame = frame; + sb.exceptionCb = exceptionCb; + if (exceptionCb) { + return Cu.evalInSandbox("try {evalInWindow('" + script + "',frame); ok(false, 'Exception should have been thrown.')} catch(e) {exceptionCb(e)}", sb); + } + + return Cu.evalInSandbox("evalInWindow('" + script + "',frame)", sb); + } + + function testSameOrigin(frame) { + frame.contentWindow.document.wrappedJSObject.str = "foobar"; + is(executeIn(frame.contentWindow, "document.str"), "foobar", + "Same origin string property access."); + + executeIn(frame.contentWindow, 'document.obj = {prop: "foobar"}'); + is((executeIn(frame.contentWindow, "document.obj")).prop, "foobar", + "Same origin object property access (cloning)."); + isnot(executeIn(frame.contentWindow, "document.obj"), frame.contentWindow.document.wrappedJSObject.obj, + "Ensure cloning for js objects."); + is(executeIn(frame.contentWindow, "document"), frame.contentWindow.document, + "Xrayables should just pass without cloning."); + is( executeIn(frame.contentWindow, "({a:{doc: document}})").a.doc, frame.contentWindow.document, + "Deep cloning works."); + + executeIn(frame.contentWindow, "throw 42", function(e){is(e, 42, + "Exception was thrown from script.")}); + + executeIn(frame.contentDocument, "var a = 42;", function(e){ok(e.toString().indexOf("Second argument must be a window") > -1, + "Passing non-window to evalInWindow should throw.");}); + testDone(); + } + + function testCrossOrigin(frame) { + executeIn(frame.contentWindow, "var a = 42;", function(e){ok(e.toString().indexOf("Permission denied") > -1, + "Executing script in a window from cross origin should throw.");}); + testDone(); + } + + var testsRun = 0; + function testDone() { + if (++testsRun == 2) + SimpleTest.finish(); + } + ]]></script> + <iframe src="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html" + onload="testSameOrigin(this)"> + </iframe> + <iframe src="http://mochi.test:8888/tests/js/xpconnect/tests/mochitest/file_empty.html" + onload="testCrossOrigin(this)"> + </iframe> +</window> \ No newline at end of file
new file mode 100644 --- /dev/null +++ b/js/xpconnect/tests/unit/test_exportFunction.js @@ -0,0 +1,73 @@ +function run_test() { + var Cu = Components.utils; + var epsb = new Cu.Sandbox(["http://example.com", "http://example.org"], { wantExportHelpers: true }); + subsb = new Cu.Sandbox("http://example.com", { wantXHRConstructor: true }); + subsb2 = new Cu.Sandbox("http://example.com", { wantXHRConstructor: true }); + xorigsb = new Cu.Sandbox("http://test.com"); + + epsb.subsb = subsb; + epsb.xorigsb = xorigsb; + epsb.do_check_true = do_check_true; + epsb.do_check_eq = do_check_eq; + epsb.do_check_neq = do_check_neq; + + // Exporting should work if prinicipal of the source sandbox + // subsumes the principal of the target sandbox. + Cu.evalInSandbox("(" + function() { + Object.prototype.protoProp = "common"; + var wasCalled = false; + var _this = this; + var funToExport = function(a, obj, native, mixed) { + do_check_eq(a, 42); + do_check_neq(obj, subsb.tobecloned); + do_check_eq(obj.cloned, "cloned"); + do_check_eq(obj.protoProp, "common"); + do_check_eq(native, subsb.native); + do_check_eq(_this, this); + do_check_eq(mixed.xrayed, subsb.xrayed); + do_check_eq(mixed.xrayed2, subsb.xrayed2); + wasCalled = true; + }; + this.checkIfCalled = function() { + do_check_true(wasCalled); + wasCalled = false; + } + exportFunction(funToExport, subsb, "imported"); + }.toSource() + ")()", epsb); + + subsb.xrayed = Cu.evalInSandbox("(" + function () { + return new XMLHttpRequest(); + }.toSource() + ")()", subsb2); + + // Exported function should be able to be call from the + // target sandbox. Native arguments should be just wrapped + // every other argument should be cloned. + Cu.evalInSandbox("(" + function () { + native = new XMLHttpRequest(); + xrayed2 = XPCNativeWrapper(new XMLHttpRequest()); + mixed = { xrayed: xrayed, xrayed2: xrayed2 }; + tobecloned = { cloned: "cloned" }; + imported(42,tobecloned, native, mixed); + }.toSource() + ")()", subsb); + + // Apply should work but the |this| argument should not be + // possible to be changed. + Cu.evalInSandbox("(" + function() { + imported.apply("something", [42, tobecloned, native, mixed]); + }.toSource() + ")()", subsb); + + Cu.evalInSandbox("(" + function() { + checkIfCalled(); + }.toSource() + ")()", epsb); + + // Exporting should throw if princpal of the source sandbox does + // not subsume the principal of the target. + Cu.evalInSandbox("(" + function() { + try{ + exportFunction(function(){}, this.xorigsb, "denied"); + do_check_true(false); + } catch (e) { + do_check_true(e.toString().indexOf('Permission denied') > -1); + } + }.toSource() + ")()", epsb); +}
--- a/js/xpconnect/tests/unit/xpcshell.ini +++ b/js/xpconnect/tests/unit/xpcshell.ini @@ -42,9 +42,10 @@ fail-if = os == "android" [test_attributes.js] [test_params.js] [test_tearoffs.js] [test_want_components.js] [test_components.js] [test_allowedDomains.js] [test_allowedDomainsXHR.js] [test_nuke_sandbox.js] +[test_exportFunction.js] [test_watchdog.js]