NPAPI:ArrayDictAndBinary

From MozillaWiki
Jump to: navigation, search

Status

First draft

Contributors

  • Last modified: November 1, 2011
  • Authors: Richard Bateman (FireBreath)
  • Contributors: none

Overview

The use of javascript has dramatically improved and changed over the years since NPRuntime was created, but the data types supported by NPRuntime have remained about the same, supporting only one non-primitive type and without any way to return binary data to the page. In order for a plugin to interact with javascript in a natural way and fully utilize all features of a modern browser there are three datatypes that should be added to the NPVariant structure.

Binary data

With the introduction of the HTML5 Canvas and other related technologies there is now a strong use case for passing binary data into Javascript. While there doesn't seem to be a clear choice for what type of data structure in javascript should handle this, the options seem to all have a fairly similar interface at least for querying the data. If a datatype were added to NPRuntime to support a binary array of data, each browser and javascript engine could implement the specifics using whatever data type is most appropriate in their environment, with the most basic option being a javascript array of integers.

Array and dictionary (key/value) data

An even more pressing and more common need is the need to return javascript arrays or objects that act as a key/value dictionary. Current solutions exist:

  • Build the object using javascript calls
    • Request the NPObject for the DOM window
    • Invoke the "array" or "object" methods on the DOM window NPObject
    • Invoke "push" (for arrays) or SetProperty (for objects) for each array element or property that needs to be set
    • Do this recursively in a depth-first manner until the entire data structure has been created
    • Return the resulting NPObject back to the browser in an NPVariant
  • Build the object using JSON
    • Build a string with valid JSON
    • Request the NPObject for the DOM window
    • Call GetProperty("JSON") on the DOM window
    • Invoke "parse" on the NPObject for "JSON"
      • Alternately, NPN_Evaluate may be used to parse the JSON, but security implications may exist
    • Return the resulting NPObject back to the browser in an NPVariant

The first solution is the "cleanest", with no ugly serialization of complex data structures required; however, in practice it has been observed to be many times slower than the second solution, which is obviously inefficient due to the multiple conversions to and from string. In addition when serializing an object to javascript there is no way to pass anything other than primitive datatypes (e.g. an NPObject cannot be passed as a member of an array).

The datatypes NPArray and NPDictionary will be introduced to provide a performant solution to this problem.

Specification

A new NPVariant enum and struct will be introduced, backwards compatible with the old one:

 typedef enum {
     NPVariantType_Void,
     NPVariantType_Null,
     NPVariantType_Bool,
     NPVariantType_Int32,
     NPVariantType_Double,
     NPVariantType_String,
     NPVariantType_Object,
     NPVariantType_Array,
     NPVariantType_Dictionary,
     NPVariantType_ByteArray
 } NPVariantType;
 typedef struct _NPVariant {
     NPVariantType type;
     union {
         bool boolValue;
         int32_t intValue;
         double doubleValue;
         NPString stringValue;
         NPObject *objectValue;
         NPArray arrayValue;
         NPDictionary dictValue;
         NPByteArray byteArrayValue;
     } value;
 } NPVariant;

In addition, several other new structures will be needed:

 typedef struct _NPArray {
     const NPVariant *arrayItems;
     uint32_t arrayLength;
 } NPArray;
 typedef struct _NPDictionaryItem {
     NPIdentifier name;
     NPVariant value;
 } NPDictionaryItem;
 typedef struct _NPDictionary {
     const NPDictionaryItem* dictItems;
     uint32_t itemCount;
 } NPDictionary;
 typedef uint8_t NPByte;
 typedef struct _NPByteArray {
     const NPByte* data;
     uint32_t dataLength;
 } NPByteArray;

Creating an NPArray

An NPArray consists of an arrayLength n and an array of size n of NPVariant objects which should be allocated with NPN_MemAlloc and freed with NPN_MemFree or NPN_ReleaseVariantValue as needed similar to a NPString.

 // To create a new NPArray, we first allocate the simple data structure and set the length:
 uint32_t arraySize = 512;
 NPVariant myNewArray;
 myNewArray.type = NPVariantType_Array;
 myNewArray.arrayValue.arrayLength = arraySize;
 myNewArray.arrayValue.arrayItems = (NPVariant*)NPN_MemAlloc(sizeof(NPVariant)*arraySize);
 for (int i = 0; i < arraySize; ++i) {
     myNewArray[i].type = NPVariantType_Int32;
     myNewArray[i].intValue = i;
 }
 // ... 
 // After you have used the NPVariant, release it as normal
 NPN_ReleaseVariantValue(&myNewArray);

Note that since an NPArray contains an array of NPVariants, ReleaseVariantValue will need to recursively call itself for each item of the array to make sure all nested content is also released. This also means that an NPArray can be nested inside another NPArray, allowing for the same complex datatypes to be created in a plugin that you could normally create from javascript.

Creating an NPDictionary

An NPDictionary consists of an itemCount n and an array of size n of NPDictionaryItem objects which should be allocated with NPN_MemAlloc and freed with NPN_MemFree or NPN_ReleaseVariantValue as needed similar to a NPString or NPArray.

Each NPDictionaryItem consists of a NPIdentifier name that should be populated using the NPN_GetStringIdentifier or NPN_GetIntIdentifier functions and a NPVariant.

 // To create a new NPDictionary, we first allocate the simple data structure and set the length:
 uint32_t dictSize = 512;
 NPVariant myNewDict;
 myNewDict.type = NPVariantType_Dictionary;
 myNewDict.dictValue.itemCount = dictSize;
 myNewDict.dictValue.dictItems = (NPDictionaryItem*)NPN_MemAlloc(sizeof(NPDictionaryItem)*arraySize);
 for (int i = 0; i < dictSize; ++i) {
     std::stringstream idStr;
     idStr << "item" << i;
     myNewDict[i].name = NPN_GetStringIdentifier(idStr.str().c_str());
     myNewDict[i].value.type = NPVariantType_Int32;
     myNewDict[i].value.intValue = i;
 }
 // ... 
 // After you have used the NPVariant, release it as normal
 NPN_ReleaseVariantValue(&myNewArray);

Note that since an NPDictionary contains an array of NPDictionaryItems which each contain a NPVariant, ReleaseVariantValue will need to recursively call itself for each item of the dictionary to make sure all nested content is also released. This also means that a NPDictionary can be nested inside another NPDictionary or NPArray, allowing for the same complex datatypes to be created in a plugin that you could normally create from javascript.

Creating a NPByteArray

An NPByteArray consists of a dataLength n and a NPByte (presumably a uint8) array of size n which should be allocated with NPN_MemAlloc and freed with NPN_MemFree or NPN_ReleaseVariantValue as needed similar to a NPString.

 // To create a new NPDictionary, we first allocate the simple data structure and set the length:
 uint32_t bufferSize = 512;
 NPByte* buffer = (NPByte*)NPN_MemAlloc(bufferSize);
 memcpy(&buffer, &srcBuffer, bufferSize);
 NPVariant bytes;
 bytes.type = NPVariantType_ByteArray;
 bytes.byteArrayValue.dataLength = bufferSize;
 bytes.byteArrayValue.data = buffer;
// ... 
 // After you have used the NPVariant, release it as normal
 NPN_ReleaseVariantValue(&bytes);

The specific datatype used in Javascript to store the Byte Array may vary from browser to browser depending on the specific javascript implementation.

Querying for Support

The plugin will only try to use the new NPVariant types if the version passed from the browser in the NPNetscapeFuncs structure is greater or equal to the following:

 NPVERS_HAS_NPVARIANT2_SUPPORT = 28

Backwards Compatibility

Because the new structures are the same size as NPString, the size of the NPVariant structure should not change and as long as new plugins don't try to pass the new types to old browsers there should be no problem with backwards compatibility.