diff --git a/js/src/jsapi.c b/js/src/jsapi.c index 85fdae642500..23477f19fa9f 100644 --- a/js/src/jsapi.c +++ b/js/src/jsapi.c @@ -3283,15 +3283,82 @@ JS_ObjectIsFunction(JSContext *cx, JSObject *obj) return OBJ_GET_CLASS(cx, obj) == &js_FunctionClass; } +JS_STATIC_DLL_CALLBACK(JSBool) +js_generic_native_method_dispatcher(JSContext *cx, JSObject *obj, + uintN argc, jsval *argv, jsval *rval) +{ + jsval fsv; + JSFunctionSpec *fs; + JSObject *tmp; + + if (!JS_GetReservedSlot(cx, JSVAL_TO_OBJECT(argv[-2]), 0, &fsv)) + return JS_FALSE; + fs = (JSFunctionSpec *) JSVAL_TO_PRIVATE(fsv); + + if (argc == 0) { + /* + * Follow Function.prototype.apply and .call by using the global + * object as the 'this' param if no args. We know argv[0] is valid + * because JS_DefineFunctions, below, defined us as requiring at + * least one argument (it passes fs->nargs + 1 as the penultimate + * argument to JS_DefineFunction). + */ + while ((tmp = OBJ_GET_PARENT(cx, obj)) != NULL) + obj = tmp; + argv[0] = OBJECT_TO_JSVAL(obj); + argc = 1; + } + + /* + * Copy all actual (argc) and required but missing (fs->nargs + 1 - argc) + * args down over our |this| parameter, argv[-1], which is almost always + * the class constructor object, e.g. Array. Then call the corresponding + * prototype native method with our first argument passed as |this|. + */ + memmove(argv - 1, argv, (fs->nargs + 1) * sizeof(jsval)); + return fs->call(cx, JSVAL_TO_OBJECT(argv[-1]), argc - 1, argv, rval); +} + JS_PUBLIC_API(JSBool) JS_DefineFunctions(JSContext *cx, JSObject *obj, JSFunctionSpec *fs) { + uintN flags; + JSObject *ctor; JSFunction *fun; CHECK_REQUEST(cx); + ctor = NULL; for (; fs->name; fs++) { - fun = JS_DefineFunction(cx, obj, fs->name, fs->call, fs->nargs, - fs->flags); + flags = fs->flags; + + /* + * Define a generic arity N+1 static method for the arity N prototype + * method if flags contains JSFUN_GENERIC_NATIVE. + */ + if (flags & JSFUN_GENERIC_NATIVE) { + if (!ctor) { + ctor = JS_GetConstructor(cx, obj); + if (!ctor) + return JS_FALSE; + } + + flags &= ~JSFUN_GENERIC_NATIVE; + fun = JS_DefineFunction(cx, ctor, fs->name, + js_generic_native_method_dispatcher, + fs->nargs + 1, flags); + if (!fun) + return JS_FALSE; + fun->extra = fs->extra; + + /* + * As jsapi.h notes, fs must point to storage that lives as long + * as fun->object lives. + */ + if (!JS_SetReservedSlot(cx, fun->object, 0, PRIVATE_TO_JSVAL(fs))) + return JS_FALSE; + } + + fun = JS_DefineFunction(cx, obj, fs->name, fs->call, fs->nargs, flags); if (!fun) return JS_FALSE; fun->extra = fs->extra; diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 8101664937b9..3ac206b5de46 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -134,6 +134,19 @@ JS_BEGIN_EXTERN_C #define JSFUN_HEAVYWEIGHT 0x80 /* activation requires a Call object */ #define JSFUN_FLAGS_MASK 0xf8 /* overlay JSFUN_* attributes */ +/* + * Re-use JSFUN_LAMBDA, which applies only to scripted functions, for use in + * JSFunctionSpec arrays that specify generic native prototype methods, i.e., + * methods of a class prototype that are exposed as static methods taking an + * extra leading argument: the generic |this| parameter. + * + * If you set this flag in a JSFunctionSpec struct's flags initializer, then + * that struct must live at least as long as the native static method object + * created due to this flag by JS_DefineFunctions or JS_InitClass. Typically + * JSFunctionSpec structs are allocated in static arrays. + */ +#define JSFUN_GENERIC_NATIVE JSFUN_LAMBDA + /* * Well-known JS values. The extern'd variables are initialized when the * first JSContext is created by JS_NewContext (see below). diff --git a/js/src/jsarray.c b/js/src/jsarray.c index 8997f93ef41e..2caa2e735724 100644 --- a/js/src/jsarray.c +++ b/js/src/jsarray.c @@ -1718,32 +1718,32 @@ static JSFunctionSpec array_methods[] = { /* Perl-ish methods. */ #if JS_HAS_SOME_PERL_FUN - {"join", array_join, 1,0,0}, - {"reverse", array_reverse, 0,0,0}, - {"sort", array_sort, 1,0,0}, + {"join", array_join, 1,JSFUN_GENERIC_NATIVE,0}, + {"reverse", array_reverse, 0,JSFUN_GENERIC_NATIVE,0}, + {"sort", array_sort, 1,JSFUN_GENERIC_NATIVE,0}, #endif #if JS_HAS_MORE_PERL_FUN - {"push", array_push, 1,0,0}, - {"pop", array_pop, 0,0,0}, - {"shift", array_shift, 0,0,0}, - {"unshift", array_unshift, 1,0,0}, - {"splice", array_splice, 2,0,0}, + {"push", array_push, 1,JSFUN_GENERIC_NATIVE,0}, + {"pop", array_pop, 0,JSFUN_GENERIC_NATIVE,0}, + {"shift", array_shift, 0,JSFUN_GENERIC_NATIVE,0}, + {"unshift", array_unshift, 1,JSFUN_GENERIC_NATIVE,0}, + {"splice", array_splice, 2,JSFUN_GENERIC_NATIVE,0}, #endif /* Python-esque sequence methods. */ #if JS_HAS_SEQUENCE_OPS - {"concat", array_concat, 1,0,0}, - {"slice", array_slice, 2,0,0}, + {"concat", array_concat, 1,JSFUN_GENERIC_NATIVE,0}, + {"slice", array_slice, 2,JSFUN_GENERIC_NATIVE,0}, #endif #if JS_HAS_ARRAY_EXTRAS - {"indexOf", array_indexOf, 1,0,0}, - {"lastIndexOf", array_lastIndexOf, 1,0,0}, - {"forEach", array_forEach, 1,0,0}, - {"map", array_map, 1,0,0}, - {"filter", array_filter, 1,0,0}, - {"some", array_some, 1,0,0}, - {"every", array_every, 1,0,0}, + {"indexOf", array_indexOf, 1,JSFUN_GENERIC_NATIVE,0}, + {"lastIndexOf", array_lastIndexOf, 1,JSFUN_GENERIC_NATIVE,0}, + {"forEach", array_forEach, 1,JSFUN_GENERIC_NATIVE,0}, + {"map", array_map, 1,JSFUN_GENERIC_NATIVE,0}, + {"filter", array_filter, 1,JSFUN_GENERIC_NATIVE,0}, + {"some", array_some, 1,JSFUN_GENERIC_NATIVE,0}, + {"every", array_every, 1,JSFUN_GENERIC_NATIVE,0}, #endif {0,0,0,0,0} diff --git a/js/src/jsstr.c b/js/src/jsstr.c index 48522e7fba87..f4d97e78baf4 100644 --- a/js/src/jsstr.c +++ b/js/src/jsstr.c @@ -2288,39 +2288,39 @@ str_sub(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) static JSFunctionSpec string_methods[] = { #if JS_HAS_TOSOURCE - {"quote", str_quote, 0,0,0}, + {"quote", str_quote, 0,JSFUN_GENERIC_NATIVE,0}, {js_toSource_str, str_toSource, 0,0,0}, #endif /* Java-like methods. */ {js_toString_str, str_toString, 0,0,0}, {js_valueOf_str, str_valueOf, 0,0,0}, - {"substring", str_substring, 2,0,0}, - {"toLowerCase", str_toLowerCase, 0,0,0}, - {"toUpperCase", str_toUpperCase, 0,0,0}, - {"charAt", str_charAt, 1,0,0}, - {"charCodeAt", str_charCodeAt, 1,0,0}, - {"indexOf", str_indexOf, 1,0,0}, - {"lastIndexOf", str_lastIndexOf, 1,0,0}, - {"toLocaleLowerCase", str_toLocaleLowerCase, 0,0,0}, - {"toLocaleUpperCase", str_toLocaleUpperCase, 0,0,0}, - {"localeCompare", str_localeCompare, 1,0,0}, + {"substring", str_substring, 2,JSFUN_GENERIC_NATIVE,0}, + {"toLowerCase", str_toLowerCase, 0,JSFUN_GENERIC_NATIVE,0}, + {"toUpperCase", str_toUpperCase, 0,JSFUN_GENERIC_NATIVE,0}, + {"charAt", str_charAt, 1,JSFUN_GENERIC_NATIVE,0}, + {"charCodeAt", str_charCodeAt, 1,JSFUN_GENERIC_NATIVE,0}, + {"indexOf", str_indexOf, 1,JSFUN_GENERIC_NATIVE,0}, + {"lastIndexOf", str_lastIndexOf, 1,JSFUN_GENERIC_NATIVE,0}, + {"toLocaleLowerCase", str_toLocaleLowerCase, 0,JSFUN_GENERIC_NATIVE,0}, + {"toLocaleUpperCase", str_toLocaleUpperCase, 0,JSFUN_GENERIC_NATIVE,0}, + {"localeCompare", str_localeCompare, 1,JSFUN_GENERIC_NATIVE,0}, /* Perl-ish methods (search is actually Python-esque). */ #if JS_HAS_REGEXPS - {"match", str_match, 1,0,2}, - {"search", str_search, 1,0,0}, - {"replace", str_replace, 2,0,0}, - {"split", str_split, 2,0,0}, + {"match", str_match, 1,JSFUN_GENERIC_NATIVE,2}, + {"search", str_search, 1,JSFUN_GENERIC_NATIVE,0}, + {"replace", str_replace, 2,JSFUN_GENERIC_NATIVE,0}, + {"split", str_split, 2,JSFUN_GENERIC_NATIVE,0}, #endif #if JS_HAS_PERL_SUBSTR - {"substr", str_substr, 2,0,0}, + {"substr", str_substr, 2,JSFUN_GENERIC_NATIVE,0}, #endif /* Python-esque sequence methods. */ #if JS_HAS_SEQUENCE_OPS - {"concat", str_concat, 0,0,0}, - {"slice", str_slice, 0,0,0}, + {"concat", str_concat, 0,JSFUN_GENERIC_NATIVE,0}, + {"slice", str_slice, 0,JSFUN_GENERIC_NATIVE,0}, #endif /* HTML string methods. */