JavaScript and Plug-in Interface

Prior to the PDK, developers had the option to use the webOS application framework--the Mojo (JavaScript) application framework--to create applications for Palm devices. The Mojo Framework is based on HTML5, CSS, and JavaScript (JS) standards. Now, with the PDK, JavaScript developers have the option to combine their applications with Plug-in applications written in C or C++. JavaScript applications can share device screen space and utilize two-way communication with Plug-in applications.

In this section:

Notes:

Support for combined JavaScript and Plug-in apps is currently in beta. You can develop these apps in webOS 1.4.5, but you cannot currently enter them into the Palm App Catalog without special clearance. Please contact HP Palm Developer Support for more information.

You cannot use SDL_cinema calls in hybrid apps. To play video from the JavaScript side of your app, consult our video documentation.

Initializing a Combined JavaScript/Plug-in App

For a JavaScript application to be combined with a Plug-in application, both need to do some initialization.

To initialize a combined JavaScript and Plug-in app:

  1. In your JavaScript application, declare the Plug-in application as an embedded object.

    For example:

    <object type="application/x-palm-remote" id="Plugin1" height=320 width=320
      x-palm-pass-event="true">
    <param name="appid" value="com.palm.app.shapespin">
    <param name="exe" value="shapespin_plugin">
    <param name="Param1" value="0">
    <param name="Param2" value="1">
    </object>
    
    

    You can use the param fields to send data to the Plug-in app's main function. shapespin_plugin is the name of the Plug-in executable.

  2. In the Plug-in application, code a handler function.

    Handler functions serve as interfaces between the JavaScript and Plug-in applications.

    PDL_bool MyJSHandlerFunc(PDL_JSParameters *parms) { ... }            
    
    

    Note that the function signature must be declared exactly as it is here -- only the function name can be different. PDL_JSParameters is a hidden type used to provide context and pass data. The developer does not need to know about this type's internals.

    Note also that this document is going to show you how to code this function. What is important right now is to note the signature.

  3. In the Plug-in application, register the handler function.

    Call PDL_RegisterJSHandler to register your handler function. Registering a function will allow a JavaScript app to invoke it.

    Syntax:

    PDL_Err PDL_RegisterJSHandler(const char *functionName,
      PDL_JSHandlerFunc function);     
    
    

    For example:

    PDL_Err err = PDL_RegisterJSHandler("foo", MyJSHandlerFunc);     
    
    

    Here, foo is the name the JavaScript app uses to reference the handler function. The second parameter is the name of your function.

    Note: You must call SDL_Init with, at least, SDL_INIT_VIDEO before you register any functions. It is advised that you register functions early, so a good place to do this would be after SDL_Init is called.

  4. Code and register any additional handler functions.

  5. In the Plug-in application, call PDL_JSRegistrationComplete.

    This function takes no parameters. Calling this function is critical -- no more functions can be registered after making this call. In addition, handler functions cannot be referenced until this call is made.

Note about threads:

Handler functions are called in a thread that is separate from the rest of your application. You need to use thread-safe variable access when sharing data with the main application thread that is used to draw to the screen and handle SDL events.

Communicating Between JS and a Plug-in

A JavaScript application and a Plug-in application can utilize two-way communication. Once a Plug-in application registers a handler function, the JavaScript application can use the functionName parameter (passed during registration) to reference it.

For example (in JavaScript app):

var element = document.getElementById("Plugin1");
element.foo();
// Note that you could also use this Mojo shortcut for the above two lines:
// $('Plugin1').foo();

Sending data to the Plug-in from JavaScript

To send data to the Plug-in application, the JavaScript application could call a registered handler function with any number of elements.

For example:

element.foo("John", "Doe", 50);            

Note that you can pass strings or numbers, but the Plug-in app always receives them as strings. Parsing functions are provided on the Plug-in side to convert strings back to numbers (see below).

On the Plug-in side, the handler function can parse these parameters using these calls:

  • int PDL_GetNumJSParams(PDL_JSParameters *parms);
  • const char * PDL_GetJSParamString(PDL_JSParameters *parms, int paramNum);
  • int PDL_GetJSParamInt(PDL_JSParameters *parms, int paramNum);
  • double PDL_GetJSParamDouble(PDL_JSParameters *parms, int paramNum);

For example:

PDL_bool MyJSHandlerFunc(PDL_JSParameters *parms) {
  //...
  int num =  PDL_GetNumJSParams(parms);
  if (num==2) {
      const char * firstName = PDL_GetJSParamString(parms, 0);
      const char * lastName = PDL_GetJSParamString(parms, 1);
  }
  //...
} 

Note that the handler function is expecting two strings, representing a person's first and last names. If it were expecting an int or double, it could use one of the other parsing functions.

Note:

PDL_GetJSParamString returns a pointer to a string, but that string is internally allocated -- callers should not try to free its memory.

Returning data to JS from the Plug-in

To get data from the Plug-in application, the JavaScript application can use the handler function's return value.

For example:

var plugInAppData = element.foo("John", "Doe");            

The handler function sends the data back with this call:

PDL_Err PDL_JSReply(PDL_JSParameters *parms, const char *reply);            

Note that all return values are strings.

For example:

PDL_bool MyJSHandlerFunc(PDL_JSParameters *parms) {
  int num =  PDL_GetNumJSParams(parms);
  if (num==2) {
      const char * firstName = PDL_GetJSParamString(parms, 0);
      const char * lastName = PDL_GetJSParamString(parms, 1);
      PDL_JSReply(parms, "Husband of Jane Doe");
      return PDL_TRUE;
  }
} 

There is no limit on the amount of string data that can be returned.

Calling a JavaScript function from a Plug-in

Plug-in applications have the option to call a JavaScript function and pass it parameters with the PDL_CallJS API. The call is made asynchronously and nothing is returned.

For example:

Plug-in code

const char *params[2];
params[0] = "foo";
params[1] = "bar";
PDL_Err mjErr = PDL_CallJS("testFunc", params, 2);
if ( mjErr != PDL_NOERROR ) {
  printf("error: %s\n", PDL_GetError());
}                    

JavaScript code

// In the setup function of the assistant class
// 'Plugin1' = Object for Plug-in App
// Without binding below, call into JS cannot be made
$('Plugin1').testFunc = this.testFunc.bind(this);
// A member function of the assistant class
testFunc: function(a, b) {
  $('outputId').innerHTML = String(a) + "-" + String(b);
};

Cross-calling Limitations

Plug-in handler functions can not call JavaScript functions. Conversely, JavaScript functions called from the plug-in can not call handler functions. On either side, to work around this, you can set internal flags and dispatch the call later in the main loop. On the JavaScript side, you could also use setTimeout.

Throwing Exceptions

From the handler function, you can throw an exception back to the JavaScript with the PDL_JSException function. This throws an exception with the string value returned.

For example:

PDL_bool MyJSHandlerFunc(PDL_JSParameters *parms)                                      {                                          int num =  PDL_GetNumJSParams(parms);                                         if (num==2)            {                const char * firstName = PDL_GetJSParamString(parms, 0);               const char * lastName = PDL_GetJSParamString(parms, 1);              PDL_JSReply(parms, "Husband of Jane Doe");                return PDL_TRUE;          }          else          {                 PDL_JSException(parms, "Invalid Input Parameters");               return PDL_FALSE;           }        } 
{
  int num =  PDL_GetNumJSParams(parms);
  if (num==2)
  {
      const char * firstName = PDL_GetJSParamString(parms, 0);
      const char * lastName = PDL_GetJSParamString(parms, 1);
      PDL_JSReply(parms, "Husband of Jane Doe");
      return PDL_TRUE;
  }
  else
  {
      PDL_JSException(parms, "Invalid Input Parameters");
      return PDL_FALSE;
  }
}

The JavaScript to handle this would look similar to this:

try {
  var plugInAppData = element.foo("John", "Doe", "50");
} catch(e) {
  // error handling
}

Building a Combined JS and Plug-in Application

In addition to declaring the Plug-in application as an embedded object in your JavaScript code as previously documented, bundling a JS app with a Plug-in app requires two extra files and some additional configurations. Given the following sample JS application file directory structure:

image

Two files would need to be added before packaging and installing on the device:

  1. The Plug-in executable.

  2. A <plug-in executable name>_plugin_appinfo.json file. The <plug-in executable name> must be the complete file name, including the file extension if it has one.

    This JSON file has only two fields:

    {
      "type": "hybrid",
      "requiredMemory": <required memory in megabytes>
    }
    
    

    For example:

    {
      "type": "hybrid",
      "requiredMemory": 20
    }
    
    

    These two files have to be in the same directory, but that directory can be nested within the root JS application directory.

    In addition, the JS application's appinfo.json file needs to have the following configuration added:

    "plug-ins": true
    
    

Troubleshooting Your Hybrid App

There are a number of reasons why a hybrid app might not work correctly. This section addresses the most common ones.

  1. Plug-in does not launch

    To confirm your native plug-in is a valid application, launch it directly from novaterm or a shell and see if any of the following occurs:

    • Missing .so file message -- Your app is not packaged with an .so file it needs, or is referencing one Palm no longer installs on the device.

    • unexpected word (expecting")") message -- The plug-in is intended for different hardware, i.e., you are running an emulator build on a device or vice-versa.

    • "Segmentation fault" or "Killed" message -- Your plug-in is crashing on its own.

    Make sure your permissions are set correctly. Your main app's "appinfo.json" needs to have the following line to launch a plug-in:

    "plug-ins": true 
    
    
  2. Plug-in crashes after launch

    If your hybrid app crashes or stops working after running correctly for a while, then it probably has an internal bug.

    To see if your plug-in is running, enter the following command:

    ps -axu
    
    

    Look near the bottom of the list. Your native plug-in should be listed in approximately this area. If it is not, or if it is there and listed as "defunct", then it means your plug-in crashed or deliberately terminated. This indicates the problem is internal to your plug-in.

  3. The Jailer is causing a problem

    The Jailer can potentially cause problems with hybrid communication. Disable it and see if the problem goes away, or changes.

    To disable the Jailer:

    1. Create an /etc/nojail file.

    2. In this file, type pardon as the first 6 characters.

      After you save the file, the Jailer should be disabled.

    If the Jailer is the problem, then contact HP Palm support.

  4. Using instrumentation to debug

    Starting with webOS 2.1, you can turn on instrumentation to see what the various PDK components are doing.

    To turn on instrumentation:

    • Create a /media/internal/tconfig.txt file.

    • Add the following text:

      ra*
      pdl*
      sdl*
      napp* 
      
      
    • Make the following calls:

      stop LunaSysMgr
      start LunaSysMgr 
      
      

    Once LunaSysMgr returns, launch your hybrid as normal, then terminate it.

    There is now a /media/internal/tracking.txt file for logged instrumentation output. This file can get quite large, so you may want to use less or vi to look at it.

    The system never deletes tracking.txt, so, if you do multiple runs, additional log output is appended to the end. It is recommended you delete this file between runs.

    The tracking category is the first word on a line (e.g.,"ra_1"). If there are no lines with categories from napp, pdl, or sdl, your app is probably either not being launched, or crashing before it can call PDL_Init or SDL_Init.

    Look in the log for a line like this:

    ra_trace: [RemoteAdapterBase] launchNativeProcess <path to your native plugin> success 
    
    

    This indicates the app was launched. If it says "returned <a number>" instead of success at the end, it indicates your native plug-in could not be launched, usually because its name or path is wrong.