I've tried to do this to some extent to prevent native code hooked up to the embedded engine from being tied to one implementation, but it doesn't work very well in practice. Speaking for Spidermonkey, there are many, many functions which exist in jsapi.h which will not translate well to some common wrapper but will be very common in embedded use when performance is a concern. There are many flavors of strings and functions to create them / manipulate them because they all have slightly different semantics. Why copy a string when you can pass ownership of it to the engine? Are you supplying UTF-16 or ASCII null-terminated strings? GC issues are another area where engine-specific APIs are needed. You might need to add GC tracing into your class, or manually root values that are stored in the heap, or a number of other things. This could be useful for some very simple situations (such as just creating a shell that can execute JS in either environment with a command line switch).
For example, in the source you use JS_AddValueRoot/JS_RemoveValueRoot, which will bog down performance if the app purposely wants to keep values on the stack to avoid having to call into the engine to manage roots. CAJE::MozValueRef::AsString() always allocates memory and potentially flattens the string and copies it every time the method is called. You might want to simply peek at the bytes without allocating a copy. All of these things will add up over time and you'd basically wind up with disjoint features again and a large subset of what is already in jsapi.h.
Hmm, I understand where you're coming from, but I'd argue that many applications embedding Javascript engines - particularly libraries trying to provide JS bindings - won't need anything more complicated than simple script/function/value manipulation (or at least, nothing that can't be wrapped into something high-level and engine agnostic).
It's true that some of the current APIs are implemented in a clunky way, but I'm quite confident there are better ways to do things while still maintaining support for both engines.
For simple things it might work, but if a library wrapper was written using your wrapper instead of the native API then there is a good chance it will not be as performance optimized if it was simply using the engine API. That could mean that the work put into that binding would have to be forked / duplicated to create a lower-level binding that could take advantages of some of the engine APIs you don't wrap in the common interface.
Understood, but I'm not ready to give up on this just yet :-)
I know you think this is a futile effort, but I'd welcome any improvements from someone who knows more about the engine internals than I do. I'm currently working out what I can do instead of rooting every ValueRef.
For the rooting, you can't know if someone is storing you on the stack unless you get hack-y[1]. So you'd have to assume the opposite, that the caller is storing you on the stack and give them something like .root() to call if they are not.
[1] In the end, the way stack-scanning GC works is that it has OS-specific code (see jsnativestack.cpp[2] in SM) to get the highest stack address (stack grows down in all the archs you care about). Then since you know someone is calling into your constructor, you can declare a variable on the stack and get its address to find out the current stack pointer. If 'this' is between the current stack pointer and the stack base, you are on the stack and do not need to root yourself.[3]
[3] I coded an example up to show you (it uses an OSX-specific pthread API to get the stack base -- see the SM code for other platforms)
http://pastebin.com/dVDGWJP5
True, but this is more of a convenience wrapper than a standard.
It can happily wrap existing contexts, so it means that eg. one set of function bindings can be used with both V8 and TraceMonkey (even without the embedding application using the CAJE API, only the bindings).
For example, in the source you use JS_AddValueRoot/JS_RemoveValueRoot, which will bog down performance if the app purposely wants to keep values on the stack to avoid having to call into the engine to manage roots. CAJE::MozValueRef::AsString() always allocates memory and potentially flattens the string and copies it every time the method is called. You might want to simply peek at the bytes without allocating a copy. All of these things will add up over time and you'd basically wind up with disjoint features again and a large subset of what is already in jsapi.h.