June 28, 2007

Calling JavaScript functions from C++ with WebKit (Part 2)

Filed under: Distribute — Ryan Wilcox @ 10:34 pm

I didn’t know there was going to be a part 2 to this little adventure: I thought Part 1 was enough. Well, as long as your program liked constructing JavaScript syntax by hand that is, which would work in a pinch.

Today I followed a rabbit hole and found a much better way to call a JavaScript function from C++/WebKit: one where we don’t have to manually build the calling line ourselves. So instead of having to build myFunction(param1, param2) we can just specify the function name to call, and the params to pass.

Below is the C++ function I wrote for the simplest case (calling a JavaScript function with no parameters).

void ClassListFrame::CallJSFunctionNameWithParams(const std::string& jsFunCall)
{
    //A C++ translation of [ WebScriptObject.mm callWebScriptMethod... ]
    
    WebCore::KJSProxy* js = frame->scriptProxy();
    KJS::ScriptInterpreter* interp = js->interpreter();
    KJS::ExecState* exec = interp->globalExec();
    //TODO: error checking ASSERT(!exec->hadException());
    
    KJS::JSLock lock;
    //look up the function object

    KJS::JSValue* functionName = KJS::jsString( jsFunCall.c_str() );    
    KJS::Identifier identifier( functionName->toString(exec) );
    KJS::JSObject* rootObj = interp->globalObject();    
    KJS::JSValue* functionObj = rootObj->get(exec, identifier);

//now that we've looked it up, make sure we have it (and it's a function) if ( !functionObj || !functionObj->isObject() ) throw RuntimeJSError("Requested Object could not Be Found!"); KJS::JSObject* funcImp = static_cast<KJS::JSObject*>( functionObj ); if ( !funcImp->implementsCall() ) throw RuntimeJSError("Retrieved JavaScript Object wasn't a function");
KJS::List paramLst; //TODO: allow parameters //KJS::JSValue* paramValue = ... (see kjs/value.h for jsString(), jsBoolean(), jsNumber() ) //and also JavaScriptCore/bindings/objc/objc_utility.mm for how Cocoa does the conversions //THEN: paramLst.append( paramValue ) //FINALLY! Call it! KJS::JSObject* thisObj = const_cast<KJS::JSObject*>(rootObj); KJS::JSValue* result = funcImp->call(exec, thisObj, paramLst); if (!result) throw RuntimeJSError("Nothing returned from JavaScript function"); }

(thank you CodeColorizer! Ugly non-CSS code, but hey…)

In the future we need to find a way to pass parameters through to JavaScript. One way would be to write several different versions of this function, each accepting 1 more parameter. The trick is to get the type-strict C++ to not care what types these C++ parameters are, instead only caring: “if we can convert it, it’s OK”. I see two ways of doing that right now: either with subclasses or with templates. I like the templates idea because then the caller doesn’t have to worry about creating “manually” translating their int into the appropriate subclass (a kind of encapsulation from what’s actually going on that I don’t think is doable with a subclass approach). Although beyond ease-of-calling (having my “user of the API” hat on) I don’t think I’ll see many benefits to the writer (having my “creator of the API” hat on) to using templates.

Next up to bat (really this time): Calling C++ functions from JavaScript.

No Comments »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a comment