Page tree
Skip to end of metadata
Go to start of metadata

What is a JSAPI Method?

A JavaScript API Method is exactly what it sounds like -- a method on your JSAPI object that can be called from JavaScript.

A JSAPI Method definition consists of two parts:

  • A method on your JSAPI class
  • A registration for that method in the constructor of your JSAPI class

Creating an empty JSAPI object

Let's create a really really basic JSAPI class. We're going to use FB::JSAPIAuto as our base class, and we strongly recommend that you do the same!

// MyPluginAPI.h
#include "JSAPIAuto.h"

class MyPluginAPI : public FB::JSAPIAuto
{
public:
    MyPluginAPI();
    ~MyPluginAPI() {};
};
// MyPluginAPI.cpp
#include "MyPluginAPI.h"

MyPluginAPI::MyPluginAPI() : FB::JSAPIAuto("MyPluginAPI object") {
}

There! That was easy, wasn't it? If you pass an instance of this class back to the page, it will have two members: valid and ToString. These members are defined and registered by FB::JSAPIAuto, the base class.

Adding a New Method

To add a new method to our MyPluginAPI JSAPI interface, first we need to add the method to our class. Let's add some basic math functions, shall we?

// MyPluginAPI.h
class MyPluginAPI : public FB::JSAPIAuto
{
public:
    MyPluginAPI();
    ~MyPluginAPI() {};

    int add(int a, int b);
    int subtract(int a, int b);
    int multiply(int a, int b);
    int divide(int a, int b);
};
// MyPluginAPI.cpp

int MyPluginAPI::add(int a, int b) {
    return a + b;
}

int MyPluginAPI::subtract(int a, int b) {
    return a - b;
}

int MyPluginAPI::multiply(int a, int b) {
    return a * b;
}

int MyPluginAPI::divide(int a, int b) {
    return a / b;
}

Great! Now we have four methods in our JSAPI class, but we still can't use them from JavaScript. Remember part two of any JSAPI method: Registration. This determines what the name will be from javascript; as a convention, we name our method the same name we want to use to call it from JavaScript. To register the method, we add a call for each method in the constructor:

// MyPluginAPI.cpp

MyPluginAPI::MyPluginAPI() : FB::JSAPIAuto("MyPluginAPI object") {
    registerMethod("add",  make_method(this, &MyPluginAPI::add));
    registerMethod("subtract",  make_method(this, &MyPluginAPI::subtract));
    registerMethod("multiply",  make_method(this, &MyPluginAPI::multiply));
    registerMethod("divide",  make_method(this, &MyPluginAPI::divide));
}

That's all there is to it! This object now has four new functions for doing math! If this were your Root JSAPI object, we could then access it from JavaScript like this:

// Note that using a function here isn't neccesary; you could just have plugin be the document element,
// but it seems to confuse people because in the test page we have plugin be a function; therefore some
// get confused. You do *not* make a function call if it's the actual element, but if plugin is a function
// that returns the element then plugin() would be used.   
var plugin = function() { return document.getElementById("plugin"); }
  
plugin().add(3,5); // 8
plugin().multiply(3,5); // 15
plugin().divide(15,3); // 5
plugin().subtract(3,5); // -2

Strong dynamic typing

What would happen with the above example if we called it using strings instead of ints? Remember that javascript is a dynamically typed language, but C++ is not! So let's say we called it like this:

plugin.add("15", 5);

What do you think the result will be? If you're a C++ programmer, it may surprise you to know that this call will actually return the integer value 20, just like it would if you'd passed it two integer values. Why? JSAPIAuto "Auto"matically converts the input values into the type expected by your method. Since your method expects two int arguments, JSAPIAuto will convert those arguments to int from whatever type they are – if possible.

plugin.divide("30", "five");

If you were to run this for this function, it will throw a javascript exception; JSAPIAuto is pretty smart, but it can't convert "five" into 5.

By the same logic, though, if you accept parameters of type const std::string&, JSAPIAuto will convert any numeric types to their string equivalent and pass that value into your function. Whatever your return type, JSAPIAuto will pass that back to JavaScript in the closest representation that it can.

Note that JSAPIAuto does not automatically accept all types, but only certain "recognized" types; other types can be forced through by using the FB::variant class as a return type, but it usually isn't a good idea since the browser won't know what to do with it.

It is often a good idea to use const parameters and accept complex objects (such as std::string or FB::JSObjectPtr) by reference.

Optional parameters

Starting in FireBreath 1.4, you can specify optional parameters using boost::optional. For example:

void MyPluginAPI::doSomething(int a, boost::optional<int> b, boost::optional<bool> c) {
...
}

you can then call it any of the following ways:

plugin.doSomething(5); // b and c not specified
plugin.doSomething(5, 3); // c not specified
plugin.doSomething(3, 5, true); // all specified
plugin.doSomething(5, null, 3); // b not specified

However, if you were to omit the first parameter, an exception would be thrown.

JavaScript methods

If you want to pass in a JavaScript method as a callback, you can do that as well. The datatype you need is FB::JSObjectPtr. Note that all JSAPI-derived objects in FireBreath must be used in a boost::shared_ptr; as a convenience, typedefs are provided for each commonly used type with the suffix Ptr appended:

#include "variant_list.h"

void MyPluginAPI::doCallback(const std::string& a, const FB::JSObjectPtr& callback) {
    callback->InvokeAsync("", FB::variant_list_of(a)("Param2")(3));
    // Note that if callback is an actual JS function, "" should be used as the method name
    // when calling Invoke or InvokeAsync
}

As always, the registerMethod call in the constructor is exactly the same. The most common use-case for this feature is to perform a task that takes a long time on another thread and then call the callback when finished; in that case you would want to save the callback so you have it later when you need it.

JavaScript value objects

If you need to pass a "dictionary" object from Javascript, you can do that by using a std::map (or any other STL dictionary type, such as hash_map, multimap, etc) as your parameter type. The key should be a std::string, but the value can be any type supported by JSAPIAuto.

void MyPluginAPI::setup(const std::map<std::string,std::string>& settings) {
    // Read settings, do something useful
}

Note that this is a pass-by-value only situation; if you need to pass the object by reference then you should accept a FB::JSObjectPtr and use Invoke, GetProperty, and SetProperty to manipulate it.

JavaScript arrays

If you need to pass an array from Javascript, you can do that by using a std::vector (or any other STL list type, such as list, set, etc) as your parameter type. The template type of the container can be any supported JSAPI type

void MyPluginAPI::setFlags(const std::list<std::bool>& flags) {
    // Read flags, do something useful
}

Note that this is a pass-by-value only situation; if you need to pass the object by reference then you should accept a FB::JSObjectPtr and use Invoke, GetProperty, and SetProperty to manipulate it -- remember that arrays are just objects in JavaScript.

More types

For a complete list of supported types, see Supported JSAPI types

  • No labels

1 Comment

  1. Unknown User (pnd1305)

    超强的动态类型转换

    例子当中"5"在function中就是5用来计算。 Why? 由于JSAPIAuto 对象的"自动"机制的作用

    能转换的则转换的原则,不是所有的,不能转换都用 FB::variant类作为返回值, 但这对浏览器是无意义的,谁认识谁啊?.

    用 const 参数来接收复杂的对象类型 ( std::string or FB::JSObjectPtr) 的引用是好注意.

    函数接受可选的参数,用 boost::optional关键字,见例。.

    传递JS函数(一般用于callback),用关键字FB::JSObjectPtr.

      注that all JSAPI-derived objects in FireBreath must be used in a boost::shared_ptr; as a convenience, typedefs are provided for each commonly used type with the suffix Ptr appended:??

    传递JS数据参数,关键字 std::map

    传递JS数据数组,关键字 std::list