FireBreath Tips: Dealing with JSAPI objects

November 9, 2010 4 Comments by Richard

FireBreath Tips and Tricks

I have decided to start writing some tutorial type information for solving specific problems using FireBreath.  I don’t know how often I will post on this topic, but my goal is to do a series of short “tips and tricks” posts whenever I think of something that may not be obvious to someone starting out with the framework.

This post will discuss some best practices for using JSAPI objects. Feel free to request future articles in the comments.

boost::shared_ptr

The FB::JSAPIPtr type in FireBreath is just a convenience alias — mostly because I’m lazy — for boost::shared_ptr<FB::JSAPI>.  This is very important to understand, because if you try to use a normal pointer to your JSAPI derived objects without using shared_ptr, you’re likely to have your object disappear out from under you.

How it works

Basically when you create a boost::shared_ptr object, it allocates a small structure for holding two reference counts.  The first and most important is the use_count.  The second is the weak_count, and we’ll discuss that later.  The important thing to know is that every time you assign your shared_ptr to another, it increments the use_count.  Whenever a shared_ptr gets destroyed / goes out of scope, it decrements it.  Once that count hits 0, the object inside the shared_ptr goes away.

This is critical in FireBreath for JSAPI objects because we don’t know when the Browser will let go of our objects, and we can’t be deleting them while the browser still has a reference.

Creating objects

The best way to create a new object is to put it directly into a JSAPIPtr (or other shared_ptr).  You can (and should) also use boost::make_shared to be explicit. For example:

FB::JSAPIPtr myAPI(boost::make_shared());

This creates a new FBTestPluginAPI object and stuffs it into the myAPI JSAPIPtr.  Now, usually you’ll be passing something into the constructor, so it’ll look more like this:

FB::JSAPIPtr myAPI(boost::make_shared(FB::ptr_cast(shared_ptr()), m_host));

Of course, you may want a shared_ptr that allows you better access to your object.  If so, you can also do this:

boost::shared_ptr myAPI(boost::make_shared(FB::ptr_cast(shared_ptr()), m_host));

You can return this in a function that returns FB::JSAPIPtr and it will cast up-cast it implicitly just like a normal pointer.

There are a few unexpected things here:

  • shared_ptr() – return a boost::shared_ptr<PluginEventSink> object, which is the ultimate base class for all Plugin objects (PluginCore derives from it).
  • FB::ptr_cast<FBTestPlugin>(shared_ptr()) – FB::ptr_cast does a dynamic cast of a shared_ptr. This is basically an alias for boost::dynamic_ptr_cast, which is more of a pain to type.
  • m_host – this is the BrowserHost object, which is useful to have around.  Think of it as a dependency injection container that gives you access to the browser functions and tools.

An important thing to realize here is that we aren’t storing the shared_ptr to the plugin object inside the JSAPI class. Instead, we’ll use a boost::weak_ptr.  A weak_ptr uses the other reference count, and the reference counting structure is not destroyed until both the use_count and weak_count hit 0.  To use the plugin from a weak_ptr, you use the .lock() method on the weak_ptr to get the shared_ptr for that class, which prevents it from being destroyed while you’re holding it. However, when you’re not using the plugin, it can be destroyed when the time comes.

This is important because the Plugin object usually has a reference to the API object, and we really don’t want circular references =]

Best practices state that you should always check to see if .lock() returns a NULL pointer indicating that the plugin has gone away.  We recommend a getPlugin() method on your API object like this:

boost::shared_ptr FBTestPluginAPI::getPlugin()
{
    boost::shared_ptr plugin = m_pluginWeak.lock();
    if (!plugin)
        throw FB::script_error("The plugin object has been destroyed");
    return plugin;
}

When you need to use it, simply call getPlugin() and make sure you let go of the resulting shared_ptr when you’re done.

Storing JSAPI objects

If you want to store an array of JSAPI objects, that’s just fine… but make sure you store them in an array of boost::shared_ptrs or boost::weak_ptrs depending on whether you want storing them to keep them around as long as your list is around.

Casting JSAPI objects

If you have a FB::JSAPIPtr and you want to cast it to your plugin API type, you can use either boost::dynamic_pointer_cast or FB::ptr_cast. For example:

boost::shared_ptr api( FB::ptr_cast(m_api) );

That’s it!

That’s it! The #1 thing to remember is to NEVER EVER EVER store or pass around a JSAPI object as a raw pointer.  That’ll get you in trouble =]

4 Comments

  1. Iain Collins
    6 years ago

    Thanks for writing this up (and for helping with this issue, and being super helpful generally).

    I was incorrectly using raw pointers and it was causing the plugin to crash in browsers other than MSIE when the items seemed to go of scope (so in Firefox this happened when leaving the page, in Safari right away but it’s very possibly you wouldn’t right away if only using MSIE for cursory testing, with existing versions of FB at least).

    In my case I return several different JS objects from my plug-in, so I’m not only wanting to return a pointer plug-in itself, as in the examples in the article.

    To spell it out as a simple example for others doing the same, given a class derived from JSAPI/JSAPIAuto:
    class MyClass : public FB::JSAPIAuto {}

    You can safely create, populate and retrieve objects from a simple array (and expose them to JavaScript returning a FB::JSAPIPtr) thusly:
    boost::shared_ptr myArray[10];
    myArray[0] = boost::make_shared(m_host);
    FB::JSAPIPtr arrayItem = FB::JSAPIPtr(this->networkDevices[0]);

    For those (like me) who are not familiar with boost, you can create a shared pointer to a new object with the following and it will behave identically (just in case you see both forms and wondering):
    myArray[0] = boost::shared_ptr(new MyClass(m_host));

    Note: I declare “FB::BrowserHostPtr m_host” as suggested in the article and pass it to my objects as it’s useful to have it around.

  2. Iain Collins
    6 years ago

    That should of course be:
    FB::JSAPIPtr arrayItem = FB::JSAPIPtr(myArray[0]);

    (i.e. ‘myArray’ and not ‘this->networkDevices’ which just happens to related to what I’m actually using it for :)

  3. taxilian
    6 years ago

    Actually, since your myArray is an array of shared_ptrs and the pointer type is a subclass of JSAPI, you can just use:
    FB::JSAPIPtr arrayItem = myArray[0];

  4. Niranjan
    6 years ago

    Hi,

    First of all a very nice article to elucidate the use of JSPAI.

    I wonder how to use the API class in other custom classes to fire events to javascript.

    Thank you

Post a Comment

Your email is never published or shared. Required fields are marked *