Accessing the DOM from WebAssembly
Create pascal units, containing ‘proxy’ classes: calling a method on a proxy class will call the corresponding class in JS. The proxy classes can be generated by adapting the existing webidl2pas tool.
Problem: data transfer between JS/WebAssembly
JS/Webassembly interface only supports passing integers & floats, not objects.
solution: Every object is stored in an array with ID ID is used to pass references to object between JS and Webassembly Lifetime is controlled from WebAssembly. By using interfaces, the lifetime of objects can be controlled by compiler. Methods can be called using an invoke mechanism.
all that is needed is to pass the object pointer & method pointer (both integers), plus the ID of the event object. Pointers to methods & instances can be passed between JS and webassembly, this can be used. Alternatively: Using the FPC dispatchstr mechanism, the correct method can be called in Webassembly. To be checked.
Here are some technical notes describing the various architectural decisions.
A tool is created to generate an interface from the .webidl files. These files for example exist in the mozilla firefox repo on github: WebIDL
IElement = interface ['someawfulGUID'] (IJSObject); function childElementCount : Integer; function firstElementChild : IElement; // all other end;
Only the interfaces are exposed in the API to access the DOM.
In implementation, the following kind of code can be found:
// Hand crafted in e.g. JSObject unit TJSObject = class(TInterfacedObject) private FObjectID: NativeInt; public constructor CreateFromID(aID: NativeInt); reintroduce; destructor Destroy; override; property ObjectID: NativeInt read FObjectID; function InvokeJSNativeIntResult(aName: string; Const args: Array of const): NativeInt; function InvokeJSStringResult(aName: string; Const args: Array of const): String; function InvokeJSObjResult(aName: string; aResultClass: TJSObjectClass; Const args: Array of const): TJSObject; end;
That function does the actual call: it uses ObjectID to look for the Self object in an array:
- Negative IDs are special: window, document.
- positive IDs use a wasmObjects['id'] to look for the object.
The Invoke* function decodes the arguments and uses TJSFunction.apply to execute the requested function. The result is put in a memory block, encoded in the same way as incoming arguments.
If the result is an object, an ID is generated (simple counter), the result value is stored in the array wasmObjects['id'] .
The ID is returned to the webassembly, which will use the ID to create a TJSObject descendent.
- The interface (see above for an example)
- An implementation object as below, descendent of TJSObject
// Generated from webIDL in jsweb/jsdom unit. TElementImpl = class(TJSObject,IElement) function childElementCount : Integer; function firstElementChild : IElement; // all other end; function TElementImpl.childElementCount : Integer; begin Result:=InvokeJSStringResult('childElementCount',).AsInteger; end; function TElementImpl.firstElementChild : IElement; begin Result:=InvokeJSObjResult('firstElementChild',TElementImpl,) as IElement; end;