FireBreath Tips: Asynchronous Javascript Calls

December 1, 2010 8 Comments by Richard

Never block a Javascript call!

One cardinal rule of browser plugins is that you should never block the thread when processing a method or property call from Javascript.  In FireBreath, that means that any method or property on your JSAPI must never block, but should return in a timely manner.

The reason for this is that Javascript is effectively not a multi-threaded language. The way that Javascript handles setTimeout and setInterval varies, but from the perspective of your plugin, all JSAPI calls come in on the same thread — the UI thread. If you block on a JSAPI call the whole web browser will lock up until you finish.

In the programming circles that I hang out in, this is considered a Bad Thing.

However, there are often cases when you may need to perform some operation that is inherently blocking — such as searching the filesystem, processing a large file, or making a network request. There are doubtless several ways you could deal with this, but perhaps the easiest is to run the blocking operation on a new thread, and then call back into Javascript when you’re done.

If you think about it, this is why AJAX calls are all asynchronous.

FB::JSObjectPtr to the rescue!

One of the types supported by FireBreath is FB::JSObject. This represents any type of Javascript object that is passed in — so basically anything other than a primitive datatype. This includes arrays, raw objects (key : value stores), and functions as well as other things like DOM objects.

FB:JSObject has a method InvokeAsync that works the same way as Invoke would on a normal JSAPI object except that the call is asynchronous, so it doesn’t have a return value and thus doesn’t wait for the call to complete before returning. If we want, we can even pass back a reference to the current API object in the callback. To call a function (instead of a method on an object), we pass an empty string in for the method name when we call Invoke or InvokeAsync.

// Say we have a FB::JSObjectPtr callback
callback->InvokeAsync("", FB::variant_list_of(shared_from_this()));

Creating a thread

FireBreath relies on the boost thread library; this means that it is quite simple to create a new thread, particularly to run something minor. Keep in mind that my purpose isn’t to talk about proper techniques for multithreaded programming, so you’ll have to worry about things like making sure that your threads have all ended before shutting down and so forth on your own.

With boost thread, basically you just create a function that will be run on the other thread and use boost::bind to provide that method object (with a shared_from_this to the object instance to keep it from going away while the thread is running) to the thread constructor. Edit 1/19/2010: Someone rightly pointed out that using shared_from_this may not be (in fact usually is not) optimal. If you want to stop the thread when the JSAPI object goes away, you should pass “this” instead of shared_from_this. The code below has been updated to reflect that.

    boost::thread t(boost::bind(&MyPluginAPI::doSomethingTimeConsuming_thread,
         this, num, callback));

Putting it together

bool MyPluginAPI::doSomethingTimeConsuming( int num, FB::JSObjectPtr &callback )
{
    boost::thread t(boost::bind(&MyPluginAPI::doSomethingTimeConsuming_thread,
         this, num, callback));
    return true; // the thread is started
}
 
void MyPluginAPI::doSomethingTimeConsuming_thread( int num, FB::JSObjectPtr &callback )
{
    // Do something that takes a long time here
    int result = num * 10;
    callback->InvokeAsync("", FB::variant_list_of(shared_from_this())(result));
}

As you can see, when doSomethingTimeConsuming is called it accepts a callback. It creates a new thread and gives it the callback function.

Synchronous calls

It should also be noted that calls to Invoke on a FB::JSObject will also be threadsafe — they can be called from any thread. Keep in mind that a mutex and signal are being used behind to scenes to block until after the call is made to Javascript on the main thread, so there will be a performance hit for this.