mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-05 08:35:26 +00:00
Bug 1073816 - Implement ES6 Function.prototype.bind. r=Till
This commit is contained in:
parent
e753794c1e
commit
f367a34417
101
js/src/jsfun.cpp
101
js/src/jsfun.cpp
@ -504,10 +504,10 @@ js::fun_resolve(JSContext *cx, HandleObject obj, HandleId id, bool *resolvedp)
|
||||
if (fun->hasResolvedLength())
|
||||
return true;
|
||||
|
||||
if (fun->isInterpretedLazy() && !fun->getOrCreateScript(cx))
|
||||
uint16_t length;
|
||||
if (!fun->getLength(cx, &length))
|
||||
return false;
|
||||
uint16_t length = fun->hasScript() ? fun->nonLazyScript()->funLength() :
|
||||
fun->nargs() - fun->hasRest();
|
||||
|
||||
v.setInt32(length);
|
||||
} else {
|
||||
if (fun->hasResolvedName())
|
||||
@ -1626,22 +1626,22 @@ fun_isGenerator(JSContext *cx, unsigned argc, Value *vp)
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ES5 15.3.4.5. */
|
||||
// ES6 draft rev32 19.2.3.2
|
||||
bool
|
||||
js::fun_bind(JSContext *cx, unsigned argc, Value *vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
/* Step 1. */
|
||||
// Step 1.
|
||||
RootedValue thisv(cx, args.thisv());
|
||||
|
||||
/* Step 2. */
|
||||
// Step 2.
|
||||
if (!IsCallable(thisv)) {
|
||||
ReportIncompatibleMethod(cx, args, &JSFunction::class_);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Step 3. */
|
||||
// Step 3.
|
||||
Value *boundArgs = nullptr;
|
||||
unsigned argslen = 0;
|
||||
if (args.length() > 1) {
|
||||
@ -1649,14 +1649,14 @@ js::fun_bind(JSContext *cx, unsigned argc, Value *vp)
|
||||
argslen = args.length() - 1;
|
||||
}
|
||||
|
||||
/* Steps 7-9. */
|
||||
// Steps 4-14.
|
||||
RootedValue thisArg(cx, args.length() >= 1 ? args[0] : UndefinedValue());
|
||||
RootedObject target(cx, &thisv.toObject());
|
||||
JSObject *boundFunction = js_fun_bind(cx, target, thisArg, boundArgs, argslen);
|
||||
if (!boundFunction)
|
||||
return false;
|
||||
|
||||
/* Step 22. */
|
||||
// Step 15.
|
||||
args.rval().setObject(*boundFunction);
|
||||
return true;
|
||||
}
|
||||
@ -1665,34 +1665,81 @@ JSObject*
|
||||
js_fun_bind(JSContext *cx, HandleObject target, HandleValue thisArg,
|
||||
Value *boundArgs, unsigned argslen)
|
||||
{
|
||||
/* Steps 15-16. */
|
||||
unsigned length = 0;
|
||||
if (target->is<JSFunction>()) {
|
||||
unsigned nargs = target->as<JSFunction>().nargs();
|
||||
if (nargs > argslen)
|
||||
length = nargs - argslen;
|
||||
double length = 0.0;
|
||||
// Try to avoid invoking the resolve hook.
|
||||
if (target->is<JSFunction>() && !target->as<JSFunction>().hasResolvedLength()) {
|
||||
uint16_t len;
|
||||
if (!target->as<JSFunction>().getLength(cx, &len))
|
||||
return nullptr;
|
||||
length = Max(0.0, double(len) - argslen);
|
||||
} else {
|
||||
// Steps 5-6.
|
||||
RootedId id(cx, NameToId(cx->names().length));
|
||||
bool hasLength;
|
||||
if (!HasOwnProperty(cx, target, id, &hasLength))
|
||||
return nullptr;
|
||||
|
||||
// Step 7-8.
|
||||
if (hasLength) {
|
||||
// a-b.
|
||||
RootedValue targetLen(cx);
|
||||
if (!GetProperty(cx, target, target, id, &targetLen))
|
||||
return nullptr;
|
||||
// d.
|
||||
if (targetLen.isNumber())
|
||||
length = Max(0.0, JS::ToInteger(targetLen.toNumber()) - argslen);
|
||||
}
|
||||
}
|
||||
|
||||
/* Step 4-6, 10-11. */
|
||||
RootedAtom name(cx, target->is<JSFunction>() ? target->as<JSFunction>().atom() : nullptr);
|
||||
RootedString name(cx, cx->names().empty);
|
||||
if (target->is<JSFunction>() && !target->as<JSFunction>().hasResolvedName()) {
|
||||
if (target->as<JSFunction>().atom())
|
||||
name = target->as<JSFunction>().atom();
|
||||
} else {
|
||||
// Steps 11-12.
|
||||
RootedValue targetName(cx);
|
||||
if (!GetProperty(cx, target, target, cx->names().name, &targetName))
|
||||
return nullptr;
|
||||
|
||||
// Step 13.
|
||||
if (targetName.isString())
|
||||
name = targetName.toString();
|
||||
}
|
||||
|
||||
// Step 14. Relevant bits from SetFunctionName.
|
||||
StringBuffer sb(cx);
|
||||
if (!sb.append("bound ") || !sb.append(name))
|
||||
return nullptr;
|
||||
|
||||
RootedAtom nameAtom(cx, sb.finishAtom());
|
||||
if (!nameAtom)
|
||||
return nullptr;
|
||||
|
||||
// Step 4.
|
||||
JSFunction::Flags flags = target->isConstructor() ? JSFunction::NATIVE_CTOR
|
||||
: JSFunction::NATIVE_FUN;
|
||||
RootedObject funobj(cx, NewFunction(cx, js::NullPtr(), CallOrConstructBoundFunction, length,
|
||||
flags, target, name));
|
||||
if (!funobj)
|
||||
RootedFunction fun(cx, NewFunction(cx, js::NullPtr(), CallOrConstructBoundFunction, length,
|
||||
flags, target, nameAtom));
|
||||
if (!fun)
|
||||
return nullptr;
|
||||
|
||||
/* NB: Bound functions abuse |parent| to store their target. */
|
||||
if (!JSObject::setParent(cx, funobj, target))
|
||||
// NB: Bound functions abuse |parent| to store their target.
|
||||
MOZ_ASSERT(fun->getParent() == target);
|
||||
|
||||
if (!fun->initBoundFunction(cx, thisArg, boundArgs, argslen))
|
||||
return nullptr;
|
||||
|
||||
if (!funobj->as<JSFunction>().initBoundFunction(cx, thisArg, boundArgs, argslen))
|
||||
return nullptr;
|
||||
// Steps 9-10. Set length again, because NewFunction sometimes truncates.
|
||||
if (length != fun->nargs()) {
|
||||
RootedValue lengthVal(cx, NumberValue(length));
|
||||
if (!DefineProperty(cx, fun, cx->names().length, lengthVal, nullptr, nullptr,
|
||||
JSPROP_READONLY))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Steps 17, 19-21 are handled by fun_resolve. */
|
||||
/* Step 18 is the default for new functions. */
|
||||
return funobj;
|
||||
return fun;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -324,6 +324,16 @@ class JSFunction : public js::NativeObject
|
||||
return u.i.s.script_;
|
||||
}
|
||||
|
||||
bool getLength(JSContext *cx, uint16_t *length) {
|
||||
JS::RootedFunction self(cx, this);
|
||||
if (self->isInterpretedLazy() && !self->getOrCreateScript(cx))
|
||||
return false;
|
||||
|
||||
*length = self->hasScript() ? self->nonLazyScript()->funLength()
|
||||
: (self->nargs() - self->hasRest());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns non-callsited-clone version of this. Use when return
|
||||
// value can flow to arbitrary JS (see Bug 944975).
|
||||
JSFunction* originalFunction() {
|
||||
|
40
js/src/tests/ecma_6/Function/bound-length-and-name.js
Normal file
40
js/src/tests/ecma_6/Function/bound-length-and-name.js
Normal file
@ -0,0 +1,40 @@
|
||||
var proxy = new Proxy(function() {}, {
|
||||
getOwnPropertyDescriptor(target, name) {
|
||||
assertEq(name, "length");
|
||||
return {value: 3, configurable: true};
|
||||
},
|
||||
|
||||
get(target, name) {
|
||||
if (name == "length")
|
||||
return 3;
|
||||
if (name == "name")
|
||||
return "hello world";
|
||||
assertEq(false, true);
|
||||
}
|
||||
})
|
||||
|
||||
var bound = Function.prototype.bind.call(proxy);
|
||||
assertEq(bound.name, "bound hello world");
|
||||
assertEq(bound.length, 3);
|
||||
|
||||
var fun = function() {};
|
||||
Object.defineProperty(fun, "name", {value: 1337});
|
||||
Object.defineProperty(fun, "length", {value: "15"});
|
||||
bound = fun.bind();
|
||||
assertEq(bound.name, "bound ");
|
||||
assertEq(bound.length, 0);
|
||||
|
||||
Object.defineProperty(fun, "length", {value: Number.MAX_SAFE_INTEGER});
|
||||
bound = fun.bind();
|
||||
assertEq(bound.length, Number.MAX_SAFE_INTEGER);
|
||||
|
||||
Object.defineProperty(fun, "length", {value: -100});
|
||||
bound = fun.bind();
|
||||
assertEq(bound.length, 0);
|
||||
|
||||
fun = function f(a, ...b) { };
|
||||
assertEq(fun.length, 1);
|
||||
bound = fun.bind();
|
||||
assertEq(bound.length, 1);
|
||||
|
||||
reportCompare(0, 0, 'ok');
|
@ -31,10 +31,15 @@ function test() {
|
||||
|
||||
/* Test function proxies. */
|
||||
var proxy = Proxy.createFunction({
|
||||
get: function(obj,name) { return Function.prototype[name]; },
|
||||
fix: function() {
|
||||
return ({});
|
||||
}
|
||||
get: function(obj, name) {
|
||||
return Function.prototype[name];
|
||||
},
|
||||
getOwnPropertyDescriptor: function(obj, name) {
|
||||
return Object.getOwnPropertyDescriptor(Function.prototype, name);
|
||||
},
|
||||
fix: function() {
|
||||
return ({});
|
||||
}
|
||||
}, function() { return "call"; });
|
||||
|
||||
assertEq(proxy(), "call");
|
||||
@ -50,8 +55,15 @@ function test() {
|
||||
|
||||
/* Test function proxies as constructors. */
|
||||
var proxy = Proxy.createFunction({
|
||||
get: function(obj, name) { return Function.prototype[name]; },
|
||||
fix: function() { return ({}); }
|
||||
get: function(obj, name) {
|
||||
return Function.prototype[name];
|
||||
},
|
||||
getOwnPropertyDescriptor: function(obj, name) {
|
||||
return Object.getOwnPropertyDescriptor(Function.prototype, name);
|
||||
},
|
||||
fix: function() {
|
||||
return ({});
|
||||
}
|
||||
},
|
||||
function() { var x = {}; x.origin = "call"; return x; },
|
||||
function() { var x = {}; x.origin = "new"; return x; })
|
||||
|
Loading…
Reference in New Issue
Block a user