/**
   Copyright (C) 1995-2011 Opera Software ASA. All rights reserved.  

   THIS FILE CONTAINS DOCUMENTATION DESCRIBING HOW TO USE THE INTERFACE
   ("API") TO JSPLUGIN, THE MODULAR JAVASCRIPT PLUGIN TECHNOLOGY FOR
   THE OPERA 11 BROWSER LAUNCHED BY OPERA SOFTWARE ASA ("OPERA"). THE
   API TOGETHER WITH DOCUMENTATION AND EXAMPLE CODE IS PUBLICISED BY
   OPERA, AND OPERA IS WILLING TO PERMIT USE OF IT BY YOU ("LICENSEE"),
   ONLY UPON THE CONDITION THAT YOU ACCEPT ALL OF THE TERMS CONTAINED
   IN A SEPARATE API LICENSE AGREEMENT ("AGREEMENT"). PLEASE READ THE
   TERMS AND CONDITIONS OF THIS LICENSE CAREFULLY. BY READING, COPYING
   OR USING THE API IN ANY WAY, YOU ACCEPT THE TERMS AND CONDITIONS OF
   THIS AGREEMENT.  IF YOU ARE NOT WILLING TO BE BOUND, YOU ARE NOT
   ALLOWED TO STUDY THIS API OR MAKE USE OF IT IN ANY WAY.
*/

HOWTO write an Opera Native JavaScript Extension

rikard@opera.com

Introduction

Native JavaScript Extensions are provided as a way to interface with the native environment from from the JavaScript environment. A possible use is for set top box manufacturers to control their hardware (eg tv tuners) from an HTML user interface. Other uses could be to create file objects and other functionality that is not included in JavaScript.

Or to quote the internal design draft:

[Native JavaScript Extensions] provide DOM-like functionality on objects that are not part of DOM or JavaScript; these objects may be system-dependent, unsafe, obscure, or otherwise not desirable to include in Opera in general.

How does it work?

In the current implementation, the directory "jsplugins" under the Opera binary directory is scanned for .dll (W*ndows) files at startup. Under U**X, ~/.opera/jsplugins is scanned for .so files. If the library exports the symbol jsplugin_capabilities (see header file for details), the library is kept loaded, and the plugin is registered.

The plugin can then receive requests to do its stuff. These requests are given via callbacks that have been set during initialization.

Initialization

To the plugin:

From the plugin:

js_plugin_capabilities should return 0 on success and non-zero otherwise.

Calling back

Function and regular objects are created in Opera's ecmascript engine via callbacks. The arguments to these callbacks include:

About function signatures

Function signatures indicate what arguments a javascript function takes. Basically, one can combine "s" (string), "n" (number), "b" (boolean) and "-" (any object) as one needs.

So, "snb" will make the function take (string, number, boolean) as arguments. If more arguments are passed than is specified in the signature, they are treated as the last type. Thus, a signature of "n" makes the function accept (number, number, number ...) The function signatures actually represents the requested argument conversion, so the "-" character represents no conversion and needs to be used where an object argument is expected. This meens it's possible to write a function that takes an argument "-", that could be of any type and the type checking is done in the function implementation.

It's up to the native implementation to check that the number and type of arguments is meaningful. The number of arguments can usually be found as an argc argument, and the type can be checked in the type member of the struct jsplugin_value.

Extending the OBJECT element

Extending the OBJECT element is straightforward, and bears many similarities to creating a custom JavaScript extension. Some notable differences are:

The MIME types array

jsplugin_cap.object_types is the equivalent to jsplugin_cap.global_names, but for OBJECT extensions. Like its colleague, it's a NULL terminated array, but in this case of MIME types that the OBJECT plugin is able to handle.

See the example code for -- examples.

Handling of object requests

During initialization, one also has to set the field jsplugin_cap.handle_object.

When a custom OBJECT is found in the html and the plugin has the right permissions set (see the section Giving permissions to a plugin), this callback will be called.

As parameters, the callback gets a number of parameters:

It should return one of the JSP_OBJECT_* status types.

Giving permissions to a plugin

Plugin permissions are used to determine what server may serve content that instansiates and executes code contained in javascript and OBJECT plugins. The default value is that content from no server may access no javascript or OBJECT plugins whatsoever.

To ease this somewhat boring (but very secure) set of default restrictions, plugin permissions can be declared in the jsplugins.ini ini file. It is looked for in the same directory as the plugins themselves. This roughly means ~/.opera/jsplugins/ under U**X-like platforms, and opera_exe_dir/jsplugins/ under W*NDOWS.

The format

The file should contain a list of plugin permissions on the form plugin_id: protocol, server, port, where:

An example jsplugins.ini could be:

triggerreceiver.so: http, myserver.company.com, 0
videoplayer.so: http, myserver.company.com, 0
videoplayer.so: http, myserver2.company.com, 8080
videoplayer.so: file, localhost, 0
videoplayer.so: http, [ALL].corporation.com, 0
This will allow two of the example plugins via http to be instantiated in content from myserver.company.com on the default port for http. videoplayer.so will also be allowed in content from myserver2.company.com served via http on port 8080, and in content coming via file from localhost.

In addition, the last line indicates that all hosts under the corporation.com domain will be allowed to serve content that instantiates the videoplayer plugin.

Getting more advanced

There is also a more advanced way of handling permissions, and that is that the plugin itself handles the security permissions, which can be necessary for complex security schemes. If the plugin sets the jsplugin_cap::allow_access callback and the keyword CALLBACK is in the protocol field of one of the plugin's lines in jsplugins.ini, the allow_access callback will be called to determine whether the plugin should be allowed to be instantiated or not in a particular document.

videoplayer.so: CALLBACK
will make the videoplayer's security be handled by itself. If there are multiple lines for a certain plugin where one line states CALLBACK, the behaviour is undefined.

The callback gets the protocol, the hostname of the accessing host and the port number as arguments. It should return non-zero to grant access.

Comments, whitespace and strictness

There can be comments in the jsplugins.ini file, but only on lines for themselves. Comment lines start with # .

Eg

# this line is a comment

Whitespace is allowed both before the ";" on comment lines and before and in between fields on the permissions lines.

Lines that do not adhere to this (in some ways rather strict) format will be considered invalid and will be discarded.

The global context

The global context is often given as a parameter to functions in the plugin, and the plugin often has to pass it back into Opera. It can be viewed as a token or a reference to the javascript global context. It enables Opera to create the plugin in the right context, regarding global environment, execution permissions etc.

The plugin cannot do anything with the global context. Just pass it back when required and you'll be fine. ;-)

Plugin private data

jsplugin_obj.plugin_private is a pointer that the plugin can use to store things that it needs to keep in between function calls (its context, basically). The struct jsplugin_obj is passed in as a parameter to most functions in the plugin.

Plugin private data has to be allocated and deallocated by the plugin itself. Good places for allocation are eg in the global getters for jsplugin objects (or whereever needed and there exists a jsplugin_obj) and in handle_object for OBJECT plugins. Deallocation is best performed in the respective plugin instance's destructor.

Initialization and destruction

A plugin gets to know when a document that is allowed to instantiate the plugin gets created and destroyed. These are useful hooks if the plugin needs to prepare something (for example some global data) when it can be initialized and destroy it when the document is destroyed. The plugin will get calls to jsplugin_cap::init and jsplugin_cap::destroy to signal these events.

Getters and setters

Getters and setters occur in a number of places in the javascript plugin api:

Note: The getters/setters are quite commonly also being referred to as getname/putname in this documentation. This is what is used in the EcmaScript standard, for example.

Getters and setters are nothing magical. They are used to look up names and, if the names exist, to get or set their values (or call the functions, should they be such creatures). So, reasonable behaviour for a getter is typically to compare the string that is being looked up to the functions/properties that it knows it wants to support, and then create and return either a function object (that will be called from the javascript interpreter) or a javascript value.

When you return a string via the jsplugin_value.u.string, the string is copied on the Opera side, and it will be garbage collected duly. However, if you have allocated any memory to construct the string within the plugin itself, you have the responsibility to deallocate it yourself.

When a value is successfully found and returned from a getter, you can return either with the code JSP_GET_VALUE or with JSP_GET_VALUE_CACHE. If you use the latter, then the property will not be looked up again for the lifetime of the object, but will be cached on the Opera side. This may be useful for performance reasons.

The best documentation on how to do this is probably the code itself. Please have a look at the code in the examples directory in this distribution.

String handling

Most of the time, the API implemented by the plugin does not expect strings to contain null bytes. In this case, string handling is simple: jsplugin_value.type will be set to JSP_TYPE_STRING, and their value will be stored as a null-terminated string in jsplugin_value.u.string. This is true both for values sent to Opera from the plugin and for values sent from Opera to the plugin.

However, it is possible in javascript to have null bytes in the middle of a string. This is sometimes used to store binary data. This is handled differently for strings sent to Opera and strings sent from Opera.

Strings sent from Opera that may contain null bytes will still have their jsplugin_value.type field set to JSP_TYPE_STRING. The plugin needs to read the jsplugin_value.u.len field, which gives the length of the string in bytes, to know if the string extends beyond the first null byte encountered.

To send a string containing null bytes to Opera, the plugin must set jsplugin_value.type to JSP_TYPE_STRING_WITH_LENGTH, store the string data in jsplugin_value.u.string and store the length of the string, in bytes, in jsplugin_value.u.len. If jsplugin_value.type is set to JSP_TYPE_STRING, Opera will ignore the value ofjsplugin_value.u.len, and will truncate the string at the first null byte.

Asynchronous operations

It is possible to perform asynchronous getters and function calls which will suspend the ES execution allowing the program to continue as normal. Once the return value is ready the plugin can call opera_callbacks->resume() with a reference object and the execution will continue. This results in the getter or function to be called again and the real value can be returned.

Distinguishing a restart from a normal get or call is done by checking the type of the result parameter. This is set to JSP_TYPE_UNDEFINED for normal calls or JSP_TYPE_NULL for restarts. When suspending execution the getter or function can set an object in the result parameter. This object will be available when the getter or function is restarted, in which case result will have the type JSP_TYPE_OBJECT.

Async getters must return JSP_GET_DELAYED while function calls must return JSP_CALL_DELAYED to suspend execution.

Miscellaneous tips

You can write plugins in C++. But remember that the default linkage of a C++ compiled file is C++, so you'll have to put an export "C" in front of the plugin's jsplugin_capabilities function.


$Id$