Bug 943357 - Name argument of exportFunction should be optional. r=bholley

This commit is contained in:
Gabor Krizsanits 2013-12-10 09:56:19 +01:00
parent 90193750e8
commit a4dc9c6eea
5 changed files with 82 additions and 40 deletions

View File

@ -120,7 +120,7 @@ interface ScheduledGCCallback : nsISupports
/**
* interface of Components.utils
*/
[scriptable, uuid(ef621cac-c818-464a-9fb1-9a35731a7f32)]
[scriptable, uuid(8dd4680f-4f06-4760-a147-292cb307662f)]
interface nsIXPCComponents_Utils : nsISupports
{
@ -338,12 +338,13 @@ interface nsIXPCComponents_Utils : nsISupports
* 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 3rd argument is an optional options object:
* - defineAs: the name of the property that will
* be set on the target scope, with
* the forwarder function as the value.
*/
[implicit_jscontext]
jsval exportFunction(in jsval vfunction, in jsval vscope, in jsval vname);
jsval exportFunction(in jsval vfunction, in jsval vscope, [optional] in jsval voptions);
/*
* To be called from JS only.

View File

@ -243,17 +243,20 @@ IsProxy(JSContext *cx, unsigned argc, jsval *vp)
namespace xpc {
bool
ExportFunction(JSContext *cx, HandleValue vfunction, HandleValue vscope, HandleValue vname,
ExportFunction(JSContext *cx, HandleValue vfunction, HandleValue vscope, HandleValue voptions,
MutableHandleValue rval)
{
if (!vscope.isObject() || !vfunction.isObject() || !vname.isString()) {
bool hasOptions = !voptions.isUndefined();
if (!vscope.isObject() || !vfunction.isObject() || (hasOptions && !voptions.isObject())) {
JS_ReportError(cx, "Invalid argument");
return false;
}
RootedObject funObj(cx, &vfunction.toObject());
RootedObject targetScope(cx, &vscope.toObject());
RootedString funName(cx, vname.toString());
ExportOptions options(cx, hasOptions ? &voptions.toObject() : nullptr);
if (hasOptions && !options.Parse())
return false;
// We can only export functions to scopes those are transparent for us,
// so if there is a security wrapper around targetScope we must throw.
@ -268,11 +271,6 @@ ExportFunction(JSContext *cx, HandleValue vfunction, HandleValue vscope, HandleV
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.
@ -285,16 +283,28 @@ ExportFunction(JSContext *cx, HandleValue vfunction, HandleValue vscope, HandleV
return false;
}
RootedId id(cx, options.defineAs);
if (JSID_IS_VOID(id)) {
// If there wasn't any function name specified,
// copy the name from the function being imported.
JSFunction *fun = JS_GetObjectFunction(funObj);
RootedString funName(cx, JS_GetFunctionId(fun));
if (!funName)
funName = JS_InternString(cx, "");
RootedValue vname(cx);
vname.setString(funName);
if (!JS_ValueToId(cx, vname, id.address()))
return false;
}
MOZ_ASSERT(JSID_IS_STRING(id));
// 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))
return false;
RootedId id(cx);
if (!JS_ValueToId(cx, vname, 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, rval)) {
@ -302,12 +312,16 @@ ExportFunction(JSContext *cx, HandleValue vfunction, HandleValue vscope, HandleV
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, rval,
JS_PropertyStub, JS_StrictPropertyStub,
JSPROP_ENUMERATE))
return false;
// We have the forwarder function in the target compartment. If
// defineAs was set, we also need to define it as a property on
// the target.
if (!JSID_IS_VOID(options.defineAs)) {
if (!JS_DefinePropertyById(cx, targetScope, id, rval,
JS_PropertyStub, JS_StrictPropertyStub,
JSPROP_ENUMERATE)) {
return false;
}
}
}
// Finally we have to re-wrap the exported function back to the caller compartment.
@ -321,19 +335,19 @@ ExportFunction(JSContext *cx, HandleValue vfunction, HandleValue vscope, HandleV
* Expected type of the arguments and the return value:
* function exportFunction(function funToExport,
* object targetScope,
* string name)
* [optional] object options)
*/
static bool
ExportFunction(JSContext *cx, unsigned argc, jsval *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() < 3) {
JS_ReportError(cx, "Function requires at least 3 arguments");
if (args.length() < 2) {
JS_ReportError(cx, "Function requires at least 2 arguments");
return false;
}
return ExportFunction(cx, args[0], args[1],
args[2], args.rval());
RootedValue options(cx, args.length() > 2 ? args[2] : UndefinedValue());
return ExportFunction(cx, args[0], args[1], options, args.rval());
}
} /* namespace xpc */

View File

@ -3094,13 +3094,13 @@ nsXPCComponents_Utils::EvalInWindow(const nsAString &source, const Value &window
/* jsval exportFunction(in jsval vfunction, in jsval vscope, in jsval vname); */
NS_IMETHODIMP
nsXPCComponents_Utils::ExportFunction(const Value &vfunction, const Value &vscope,
const Value &vname, JSContext *cx, Value *rval)
const Value &voptions, JSContext *cx, Value *rval)
{
RootedValue rfunction(cx, vfunction);
RootedValue rscope(cx, vscope);
RootedValue rname(cx, vname);
RootedValue roptions(cx, voptions);
RootedValue res(cx);
if (!xpc::ExportFunction(cx, rfunction, rscope, rname, &res))
if (!xpc::ExportFunction(cx, rfunction, rscope, roptions, &res))
return NS_ERROR_FAILURE;
*rval = res;
return NS_OK;

View File

@ -3461,6 +3461,19 @@ public:
JS::RootedId defineAs;
};
class MOZ_STACK_CLASS ExportOptions : public OptionsBase {
public:
ExportOptions(JSContext *cx = xpc_GetSafeJSContext(),
JSObject* options = nullptr)
: OptionsBase(cx, options)
, defineAs(cx, JSID_VOID)
{ }
virtual bool Parse() { return ParseId("defineAs", &defineAs); };
JS::RootedId defineAs;
};
JSObject *
CreateGlobalObject(JSContext *cx, const JSClass *clasp, nsIPrincipal *principal,
JS::CompartmentOptions& aOptions);

View File

@ -1,9 +1,9 @@
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", { wantGlobalProperties: ["XMLHttpRequest"] });
subsb2 = new Cu.Sandbox("http://example.com", { wantGlobalProperties: ["XMLHttpRequest"] });
xorigsb = new Cu.Sandbox("http://test.com");
var subsb = new Cu.Sandbox("http://example.com", { wantGlobalProperties: ["XMLHttpRequest"] });
var subsb2 = new Cu.Sandbox("http://example.com", { wantGlobalProperties: ["XMLHttpRequest"] });
var xorigsb = new Cu.Sandbox("http://test.com");
epsb.subsb = subsb;
epsb.xorigsb = xorigsb;
@ -32,7 +32,7 @@ function run_test() {
do_check_true(wasCalled);
wasCalled = false;
}
exportFunction(funToExport, subsb, "imported");
exportFunction(funToExport, subsb, { defineAs: "imported" });
}.toSource() + ")()", epsb);
subsb.xrayed = Cu.evalInSandbox("(" + function () {
@ -64,7 +64,7 @@ function run_test() {
// not subsume the principal of the target.
Cu.evalInSandbox("(" + function() {
try{
exportFunction(function(){}, this.xorigsb, "denied");
exportFunction(function() {}, this.xorigsb, { defineAs: "denied" });
do_check_true(false);
} catch (e) {
do_check_true(e.toString().indexOf('Permission denied') > -1);
@ -74,8 +74,8 @@ function run_test() {
// Let's create an object in the target scope and add privileged
// function to it as a property.
Cu.evalInSandbox("(" + function() {
var newContentObject = createObjectIn(subsb, {defineAs:"importedObject"});
exportFunction(funToExport, newContentObject, "privMethod");
var newContentObject = createObjectIn(subsb, { defineAs: "importedObject" });
exportFunction(funToExport, newContentObject, { defineAs: "privMethod" });
}.toSource() + ")()", epsb);
Cu.evalInSandbox("(" + function () {
@ -87,13 +87,27 @@ function run_test() {
}.toSource() + ")()", epsb);
// exportFunction and createObjectIn should be available from Cu too.
var newContentObject = Cu.createObjectIn(subsb, {defineAs:"importedObject2"});
var newContentObject = Cu.createObjectIn(subsb, { defineAs: "importedObject2" });
var wasCalled = false;
Cu.exportFunction(function(arg){wasCalled = arg.wasCalled;}, newContentObject, "privMethod");
Cu.exportFunction(function(arg) { wasCalled = arg.wasCalled; },
newContentObject, { defineAs: "privMethod" });
Cu.evalInSandbox("(" + function () {
importedObject2.privMethod({wasCalled: true});
}.toSource() + ")()", subsb);
// 3rd argument of exportFunction should be optional.
Cu.evalInSandbox("(" + function() {
subsb.imported2 = exportFunction(funToExport, subsb);
}.toSource() + ")()", epsb);
Cu.evalInSandbox("(" + function () {
imported2(42, tobecloned, native, mixed);
}.toSource() + ")()", subsb);
Cu.evalInSandbox("(" + function() {
checkIfCalled();
}.toSource() + ")()", epsb);
do_check_true(wasCalled, true);
}