714 lines
22 KiB
ObjectPascal
714 lines
22 KiB
ObjectPascal
/// SyNodeProto - create a JS prototypes for Delphi classes based on Delphi7 RTTI
|
|
// - this unit is a part of the freeware Synopse framework,
|
|
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
|
|
unit SyNodeProto;
|
|
{
|
|
This file is part of Synopse framework.
|
|
|
|
Synopse framework. Copyright (C) 2022 Arnaud Bouchez
|
|
Synopse Informatique - http://synopse.info
|
|
|
|
SyNode for mORMot Copyright (C) 2022 Pavel Mashlyakovsky & Vadim Orel
|
|
pavel.mash at gmail.com
|
|
|
|
Some ideas taken from
|
|
http://code.google.com/p/delphi-javascript
|
|
http://delphi.mozdev.org/javascript_bridge/
|
|
|
|
*** BEGIN LICENSE BLOCK *****
|
|
Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
|
|
The contents of this file are subject to the Mozilla Public License Version
|
|
1.1 (the "License"); you may not use this file except in compliance with
|
|
the License. You may obtain a copy of the License at
|
|
http://www.mozilla.org/MPL
|
|
|
|
Software distributed under the License is distributed on an "AS IS" basis,
|
|
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
for the specific language governing rights and limitations under the License.
|
|
|
|
The Initial Developer of the Original Code is
|
|
Pavel Mashlyakovsky.
|
|
Portions created by the Initial Developer are Copyright (C) 2014
|
|
the Initial Developer. All Rights Reserved.
|
|
|
|
Contributor(s):
|
|
- Arnaud Bouchez
|
|
- Vadim Orel
|
|
- Pavel Mashlyakovsky
|
|
- win2014
|
|
|
|
Alternatively, the contents of this file may be used under the terms of
|
|
either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
in which case the provisions of the GPL or the LGPL are applicable instead
|
|
of those above. If you wish to allow use of your version of this file only
|
|
under the terms of either the GPL or the LGPL, and not to allow others to
|
|
use your version of this file under the terms of the MPL, indicate your
|
|
decision by deleting the provisions above and replace them with the notice
|
|
and other provisions required by the GPL or the LGPL. If you do not delete
|
|
the provisions above, a recipient may use your version of this file under
|
|
the terms of any one of the MPL, the GPL or the LGPL.
|
|
|
|
***** END LICENSE BLOCK *****
|
|
|
|
|
|
Version 1.18
|
|
- initial release. Use SpiderMonkey 45
|
|
|
|
}
|
|
|
|
interface
|
|
|
|
{$I Synopse.inc} // define HASINLINE
|
|
{$I SyNode.inc}
|
|
|
|
uses
|
|
{$ifdef ISDELPHIXE2}System.SysUtils,{$else}SysUtils,{$endif}
|
|
mORMot,
|
|
SynCommons,
|
|
SynLog,
|
|
SpiderMonkey;
|
|
|
|
type
|
|
|
|
TSMRTTIPropCache = record
|
|
mbr: pointer;//TRttiMember;
|
|
jsName: AnsiString;
|
|
isReadOnly: boolean;
|
|
DeterministicIndex: integer; // if not deterministic then -1
|
|
typeInfo: Pointer;//PTypeInfo;
|
|
end;
|
|
PSMRTTIPropCache = ^TSMRTTIPropCache;
|
|
TPropArray = array of TSMRTTIPropCache;
|
|
|
|
{$ifdef UNICODE}
|
|
TSMMethodRec = record
|
|
{$else}
|
|
TSMMethodRec = object
|
|
{$endif}
|
|
ujsName: SynUnicode;
|
|
method: pointer;
|
|
isNativeCall: boolean;
|
|
call: JSNative;
|
|
nargs: uintN;
|
|
flags: TJSPropertyAttrs;
|
|
end;
|
|
PSMMethodRec = ^TSMMethodRec;
|
|
TSMMethodDynArray = array of TSMMethodRec;
|
|
|
|
TSMObjectType = (otProto, otInstance, otOther);
|
|
|
|
{$ifdef UNICODE}
|
|
TSMObjectRecord = record
|
|
{$else}
|
|
TSMObjectRecord = object
|
|
{$endif}
|
|
Magic: Word;
|
|
DataType: TSMObjectType;
|
|
Data: Pointer;
|
|
procedure init(aDataType: TSMObjectType; aData: Pointer);
|
|
function IsMagicCorrect: boolean;
|
|
end;
|
|
PSMObjectRecord = ^TSMObjectRecord;
|
|
|
|
TSMCustomProtoObject = class;
|
|
TSMCustomProtoObjectClass = class of TSMCustomProtoObject;
|
|
|
|
PSMInstanceRecord = ^TSMInstanceRecord;
|
|
TFreeInstanceRecord = procedure (aInstanceRecord: PSMInstanceRecord);
|
|
{$ifdef UNICODE}
|
|
TSMInstanceRecord = record
|
|
{$else}
|
|
TSMInstanceRecord = object
|
|
{$endif}
|
|
private
|
|
function InternalCreate(cx: PJSContext; AInstance: TObject; AProto: TSMCustomProtoObject): jsval;
|
|
public
|
|
proto: TSMCustomProtoObject;
|
|
instance: TObject;
|
|
OwnInstance: Boolean;
|
|
AddData: pointer;
|
|
onFree: TFreeInstanceRecord;
|
|
procedure freeNative;
|
|
function CreateNew(acx: PJSContext; AProto: TSMCustomProtoObject; argc: uintN; var vp: JSArgRec): jsval;
|
|
function CreateForObj(acx: PJSContext; AInstance: TObject; AProto: TSMCustomProtoObjectClass; aParent: PJSRootedObject): jsval; overload;
|
|
function CreateForObj(acx: PJSContext; AInstance: TObject; AProto: TSMCustomProtoObjectClass; aParentProto: TSMCustomProtoObject): jsval; overload;
|
|
end;
|
|
|
|
TSMCustomProtoObject = class
|
|
private
|
|
fFirstDeterministicSlotIndex: uint32;
|
|
FJSClass: JSClass;
|
|
FJSClassProto: JSClass;
|
|
fSlotIndex: integer;
|
|
function getRTTIPropsCache(index: integer): TSMRTTIPropCache;
|
|
protected
|
|
fCx: PJSContext;
|
|
FjsObjName: AnsiString;
|
|
fRttiCls: TClass;
|
|
FJSProps: TJSPropertySpecDynArray;
|
|
fDeterministicCnt: uint32;
|
|
FRTTIPropsCache: TPropArray;
|
|
|
|
FMethods: TSMMethodDynArray;
|
|
FMethodsDA: TDynArrayHashed;
|
|
function GetJSClass: JSClass; virtual;
|
|
procedure InitObject(aParent: PJSRootedObject); virtual;
|
|
/// Add method to internal FMethods array for future define it into JS prototype
|
|
// to be called only inside InitObject method!
|
|
procedure definePrototypeMethod(const ajsName: SynUnicode; const aCall: JSNative; aNargs: uintN; aFlags: TJSPropertyAttrs);
|
|
property SlotIndex: integer read fSlotIndex;
|
|
public
|
|
property RTTIPropsCache[index: integer]: TSMRTTIPropCache read getRTTIPropsCache;
|
|
property jsObjName: AnsiString read FjsObjName;
|
|
property DeterministicCnt: Cardinal read fDeterministicCnt;
|
|
property FirstDeterministicSlotIndex: Cardinal read fFirstDeterministicSlotIndex;
|
|
function getMethod(const aJSFunction: PJSFunction; var obj: PJSObject): PSMMethodRec; //overload;
|
|
constructor Create(Cx: PJSContext; aRttiCls: TClass; aParent: PJSRootedObject; slotIndex: integer); virtual;
|
|
function NewSMInstance(aCx: PJSContext; argc: uintN; var vp: JSArgRec): TObject; virtual;
|
|
end;
|
|
|
|
TSMFastNativeCall = function(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean of object;
|
|
|
|
function IsInstanceObject(cx: PJSContext; jsobj: PJSObject; var asmInstance: PSMInstanceRecord): boolean; overload;
|
|
function IsInstanceObject(cx: PJSContext; jval: jsval; var asmInstance: PSMInstanceRecord): boolean; overload;
|
|
|
|
function IsProtoObject(cx: PJSContext; jsobj: PJSObject; var asmProto: TSMCustomProtoObject): boolean; overload;
|
|
function IsProtoObject(cx: PJSContext; jval: jsval; var asmProto: TSMCustomProtoObject): boolean; overload;
|
|
|
|
procedure defineEnum(cx: PJSContext; ti: PTypeInfo; aParent: PJSRootedObject);
|
|
function defineClass(cx: PJSContext; AForClass: TClass; AProto: TSMCustomProtoObjectClass; aParent: PJSRootedObject): TSMCustomProtoObject; overload;
|
|
function defineClass(cx: PJSContext; AForClass: TClass; AProto: TSMCustomProtoObjectClass; aParentProto: TSMCustomProtoObject): TSMCustomProtoObject; overload;
|
|
|
|
function camelize(const S: AnsiString): AnsiString;
|
|
|
|
const
|
|
SM_NOT_A_NATIVE_OBJECT = 'Not a native object';
|
|
|
|
function strComparePropGetterSetter(prop_name, jsName: AnsiString; isGetter: boolean): Boolean; {$ifdef HASINLINE}inline;{$endif}
|
|
// for can make strComparePropGetterSetter inlined
|
|
const prefix: array[boolean] of TShort4 = ('set ','get ');
|
|
|
|
// called when the interpreter wants to create an object through a new TMyObject ()
|
|
function SMCustomObjectConstruct(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean; cdecl;
|
|
// called when the interpreter destroys the object
|
|
{$IFDEF SM52}
|
|
procedure SMCustomObjectDestroy(var fop: JSFreeOp; obj: PJSObject); cdecl;
|
|
{$ELSE}
|
|
procedure SMCustomObjectDestroy(var rt: PJSRuntime; obj: PJSObject); cdecl;
|
|
{$ENDIF}
|
|
|
|
implementation
|
|
|
|
function strComparePropGetterSetter(prop_name, jsName: AnsiString; isGetter: boolean): Boolean;
|
|
var jsNameLen: integer;
|
|
ch1, ch2: PAnsiChar;
|
|
begin
|
|
jsNameLen := Length(jsName);
|
|
Result := (jsNameLen > 0) and (Length(prop_name) = jsNameLen + 4) and
|
|
((PInteger(@prop_name[1]))^ = (PInteger(@prefix[isGetter][1]))^);
|
|
if Result then begin
|
|
ch1 := @jsName[1];
|
|
ch2 := @prop_name[5];
|
|
while jsNameLen >= 4 do begin
|
|
if PInteger(ch1)^ <> PInteger(ch2)^ then begin
|
|
result := False;
|
|
exit;
|
|
end;
|
|
inc(ch1, 4);
|
|
inc(ch2, 4);
|
|
Dec(jsNameLen, 4);
|
|
end;
|
|
if jsNameLen >= 2 then begin
|
|
if PWord(ch1)^ <> PWord(ch2)^ then begin
|
|
result := False;
|
|
exit;
|
|
end;
|
|
inc(ch1, 2);
|
|
inc(ch2, 2);
|
|
Dec(jsNameLen, 2);
|
|
end;
|
|
if jsNameLen = 1 then begin
|
|
if ch1^ <> ch2^ then begin
|
|
result := False;
|
|
exit;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
function camelize(const S: AnsiString): AnsiString;
|
|
var
|
|
Ch: AnsiChar;
|
|
begin
|
|
result := '';
|
|
if S='' then
|
|
exit;
|
|
SetString(result, PAnsiChar(S), length(s));
|
|
Ch := PAnsiChar(s)^;
|
|
case Ch of
|
|
'A' .. 'Z':
|
|
Ch := AnsiChar(byte(Ch) or $20);
|
|
end;
|
|
PAnsiChar(Result)^ := Ch;
|
|
end;
|
|
|
|
const
|
|
// Magic constant for TSMObjectRecord
|
|
SMObjectRecordMagic: Word = 43857;
|
|
{$IFDEF SM52}
|
|
jsdef_classOpts: JSClassOps = (
|
|
finalize: SMCustomObjectDestroy; // call then JS object GC}
|
|
construct: SMCustomObjectConstruct
|
|
);
|
|
jsdef_class: JSClass = (name: '';
|
|
flags: uint32(JSCLASS_HAS_PRIVATE);
|
|
cOps: @jsdef_classOpts
|
|
);
|
|
{$ELSE}
|
|
jsdef_class: JSClass = (name: '';
|
|
flags: uint32(JSCLASS_HAS_PRIVATE);
|
|
finalize: SMCustomObjectDestroy; // call then JS object GC}
|
|
construct: SMCustomObjectConstruct
|
|
);
|
|
{$ENDIF}
|
|
// create object var obj = new TMyObject();
|
|
function SMCustomObjectConstruct(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean; cdecl;
|
|
var
|
|
jsobj: PJSObject;
|
|
Proto: TSMCustomProtoObject;
|
|
Inst: PSMInstanceRecord;
|
|
begin
|
|
Result := false;
|
|
try
|
|
if not vp.IsConstructing then
|
|
raise ESMException.Create('Construct: not JS_IS_CONSTRUCTING');
|
|
jsobj := vp.calleObject;
|
|
if not IsProtoObject(cx, jsobj, Proto) then
|
|
raise ESMException.Create('Construct: no private data');
|
|
new(Inst);
|
|
vp.rval := Inst.CreateNew(cx, Proto, argc, vp);
|
|
Result := true;
|
|
except
|
|
on E: Exception do
|
|
JSError(cx, E);
|
|
end;
|
|
end;
|
|
|
|
{$IFDEF SM52}
|
|
procedure SMCustomObjectDestroy(var fop: JSFreeOp; obj: PJSObject); cdecl;
|
|
{$ELSE}
|
|
procedure SMCustomObjectDestroy(var rt: PJSRuntime; obj: PJSObject); cdecl;
|
|
{$ENDIF}
|
|
var
|
|
ObjRec: PSMObjectRecord;
|
|
Inst: PSMInstanceRecord;
|
|
proto: TSMCustomProtoObject;
|
|
begin
|
|
ObjRec := obj.PrivateData;
|
|
if Assigned(ObjRec) and (ObjRec.IsMagicCorrect) then
|
|
begin
|
|
if (ObjRec.DataType=otInstance) and Assigned(ObjRec.Data) then begin
|
|
Inst := ObjRec.Data;
|
|
Inst.freeNative;
|
|
Dispose(Inst);
|
|
end else if (ObjRec.DataType=otProto) and Assigned(ObjRec.Data) then begin
|
|
proto := ObjRec.Data;
|
|
FreeAndNil(proto);
|
|
end else begin
|
|
Dispose(ObjRec.Data); // ObjRec.Data is a PSMIdxPropReader from SyNoideNewProto
|
|
end;
|
|
Dispose(ObjRec);
|
|
obj.PrivateData := nil;
|
|
end;
|
|
end;
|
|
|
|
function IsSMObject(cx: PJSContext; jsobj: PJSObject; var aObj: PSMObjectRecord): boolean; overload;
|
|
var
|
|
P: PSMObjectRecord;
|
|
CLS: PJSClass;
|
|
begin
|
|
Result := false;
|
|
CLS := jsobj.Class_;
|
|
if CLS.flags and JSCLASS_HAS_PRIVATE = 0 then
|
|
Exit;
|
|
|
|
p := jsobj.PrivateData;
|
|
if (p <> nil) then
|
|
if (P.IsMagicCorrect) then begin
|
|
aObj := p;
|
|
Result := true;
|
|
end else
|
|
raise ESMException.Create('Incorrect IsMagicCorrect');
|
|
end;
|
|
|
|
function IsSMObject(cx: PJSContext; jval: jsval; var aObj: PSMObjectRecord): boolean; overload;
|
|
var
|
|
obj: PJSObject;
|
|
begin
|
|
Result := jval.isObject;
|
|
if not Result then exit;
|
|
obj := jval.asObject;
|
|
Result := IsSMObject(cx, obj, aObj);
|
|
end;
|
|
|
|
function IsInstanceObject(cx: PJSContext; jval: jsval; var asmInstance: PSMInstanceRecord): boolean; overload;
|
|
var
|
|
obj: PJSObject;
|
|
begin
|
|
Result := jval.isObject;
|
|
if not Result then exit;
|
|
obj := jval.asObject;
|
|
Result := IsInstanceObject(cx, obj, asmInstance);
|
|
end;
|
|
|
|
function IsInstanceObject(cx: PJSContext; jsobj: PJSObject; var asmInstance: PSMInstanceRecord): boolean;
|
|
var
|
|
aObj: PSMObjectRecord;
|
|
begin
|
|
Result := IsSMObject(cx, jsobj, aObj);
|
|
if Result then
|
|
Result := (aObj.DataType = otInstance);
|
|
if not Result then
|
|
asmInstance := nil
|
|
else
|
|
asmInstance := aObj.Data;
|
|
end;
|
|
|
|
function IsProtoObject(cx: PJSContext; jsobj: PJSObject; var asmProto: TSMCustomProtoObject): boolean;
|
|
var
|
|
aObj: PSMObjectRecord;
|
|
begin
|
|
Result := IsSMObject(cx, jsobj, aObj);
|
|
if Result then
|
|
Result := (aObj.DataType = otProto);
|
|
if not Result then
|
|
asmProto := nil
|
|
else
|
|
asmProto := aObj.Data;
|
|
end;
|
|
|
|
function IsProtoObject(cx: PJSContext; jval: jsval; var asmProto: TSMCustomProtoObject): boolean;
|
|
var
|
|
obj: PJSObject;
|
|
begin
|
|
Result := jval.isObject;
|
|
if not Result then exit;
|
|
obj := jval.asObject;
|
|
Result := Assigned(obj) and IsProtoObject(cx, obj, asmProto);
|
|
end;
|
|
|
|
procedure defineEnum(cx: PJSContext; ti: PTypeInfo; aParent: PJSRootedObject);
|
|
var
|
|
i: integer;
|
|
s: SynUnicode;
|
|
found: Boolean;
|
|
val: jsval;
|
|
obj_: PJSRootedObject;
|
|
begin
|
|
if (ti^.Name = 'Boolean') then
|
|
Exit;
|
|
s := UTF8ToSynUnicode(ShortStringToUTF8(ti^.Name));
|
|
if (aParent.ptr.HasUCProperty(cx, Pointer(s), Length(s), found)) and found then
|
|
exit; //enum already defined
|
|
|
|
obj_ := cx.NewRootedObject(cx.NewObject(nil));
|
|
try
|
|
aParent.ptr.DefineUCProperty(cx, Pointer(s), Length(s), obj_.ptr.ToJSValue, JSPROP_ENUMERATE or JSPROP_PERMANENT, nil, nil);
|
|
with ti^.EnumBaseType^ do begin
|
|
for i := MinValue to MaxValue do begin
|
|
s := UTF8ToSynUnicode(GetEnumNameTrimed(i));
|
|
val.asInteger := i;
|
|
obj_.ptr.DefineUCProperty(cx, Pointer(s), Length(s), val, JSPROP_ENUMERATE or JSPROP_PERMANENT, nil, nil);
|
|
end;
|
|
end;
|
|
finally
|
|
cx.FreeRootedObject(obj_);
|
|
end;
|
|
|
|
//TODO freez (seal) created JS object to not allow it modification
|
|
|
|
end;
|
|
|
|
function defineClass(cx: PJSContext; AForClass: TClass; AProto: TSMCustomProtoObjectClass; aParent: PJSRootedObject): TSMCustomProtoObject;
|
|
var
|
|
global: PJSObject;
|
|
i: integer;
|
|
val: jsval;
|
|
begin
|
|
global := cx.CurrentGlobalOrNull;
|
|
for I := JSCLASS_GLOBAL_SLOT_COUNT to 255 do begin
|
|
val := global.ReservedSlot[i];
|
|
if val.isVoid then begin
|
|
//create new
|
|
result := AProto.Create(Cx, AForClass, aParent, i);
|
|
exit;
|
|
end else if IsProtoObject(cx, val, Result) then begin
|
|
if Result.fRttiCls = AForClass then
|
|
exit; // The class prototype has already created
|
|
end else if val.isString then begin
|
|
if val.asJSString.ToString(cx) = AForClass.ClassName then
|
|
exit; // The class prototype is being created right now
|
|
end else
|
|
raise ESMException.Create('Slot value is not ProtoObject');
|
|
end;
|
|
raise Exception.Create('defineClass Error: many proto' + AForClass.ClassName);
|
|
end;
|
|
|
|
function defineClass(cx: PJSContext; AForClass: TClass; AProto: TSMCustomProtoObjectClass; aParentProto: TSMCustomProtoObject): TSMCustomProtoObject; overload;
|
|
var
|
|
global: PJSObject;
|
|
i: integer;
|
|
val: jsval;
|
|
aParent: PJSRootedObject;
|
|
begin
|
|
global := cx.CurrentGlobalOrNull;
|
|
for I := JSCLASS_GLOBAL_SLOT_COUNT to 255 do begin
|
|
val := global.ReservedSlot[i];
|
|
if val.isVoid then begin
|
|
//create new
|
|
aParent := cx.NewRootedObject(global.ReservedSlot[aParentProto.fSlotIndex].asObject.ReservedSlot[aParentProto.FirstDeterministicSlotIndex].asObject);
|
|
try
|
|
result := AProto.Create(Cx, AForClass, aParent, i);
|
|
finally
|
|
cx.FreeRootedObject(aParent);
|
|
end;
|
|
exit;
|
|
end else begin
|
|
if IsProtoObject(cx, val, Result) then begin
|
|
if Result.fRttiCls = AForClass then
|
|
exit;
|
|
end else
|
|
raise ESMException.Create('Slot value is not ProtoObject');
|
|
end;
|
|
end;
|
|
raise Exception.Create('defineClass Error: many proto' + AForClass.ClassName);
|
|
end;
|
|
|
|
{ TSMObjectRecord }
|
|
|
|
procedure TSMObjectRecord.init(aDataType: TSMObjectType; aData: Pointer);
|
|
begin
|
|
Self.Magic := SMObjectRecordMagic;
|
|
Self.DataType := aDataType;
|
|
Self.Data := aData;
|
|
end;
|
|
|
|
function TSMObjectRecord.IsMagicCorrect: boolean;
|
|
begin
|
|
Result := Magic = SMObjectRecordMagic;
|
|
end;
|
|
|
|
{ TSMInstanceRecord }
|
|
|
|
function TSMInstanceRecord.CreateForObj(acx: PJSContext; AInstance: TObject; AProto: TSMCustomProtoObjectClass; aParent: PJSRootedObject): jsval;
|
|
begin
|
|
Result := InternalCreate(acx, AInstance, defineClass(acx, AInstance.ClassType, AProto, aParent));
|
|
OwnInstance := false;
|
|
end;
|
|
|
|
function TSMInstanceRecord.CreateForObj(acx: PJSContext; AInstance: TObject;
|
|
AProto: TSMCustomProtoObjectClass;
|
|
aParentProto: TSMCustomProtoObject): jsval;
|
|
begin
|
|
Result := InternalCreate(acx, AInstance, defineClass(acx, AInstance.ClassType, AProto, aParentProto));
|
|
OwnInstance := false;
|
|
end;
|
|
|
|
function TSMInstanceRecord.CreateNew(acx: PJSContext; AProto: TSMCustomProtoObject; argc: uintN;
|
|
var vp: JSArgRec): jsval;
|
|
begin
|
|
result := InternalCreate(aCx, AProto.NewSMInstance(acx, argc, vp), AProto );
|
|
OwnInstance := True;
|
|
end;
|
|
|
|
procedure TSMInstanceRecord.freeNative;
|
|
begin
|
|
if Assigned(onFree) then
|
|
onFree(@Self);
|
|
if OwnInstance then begin
|
|
if Assigned(instance) then
|
|
FreeAndNil(instance);
|
|
end;
|
|
Instance := nil; // in case the FInstance is IUnknown
|
|
end;
|
|
|
|
function TSMInstanceRecord.InternalCreate(cx: PJSContext; AInstance: TObject; AProto: TSMCustomProtoObject): jsval;
|
|
var
|
|
ObjRec: PSMObjectRecord;
|
|
jsobj: PJSRootedObject;
|
|
global: PJSRootedObject;
|
|
protoObj: PJSRootedObject;
|
|
begin
|
|
proto := AProto;
|
|
onFree := nil;
|
|
AddData := nil;
|
|
|
|
cx.BeginRequest;
|
|
try
|
|
global := cx.NewRootedObject(cx.CurrentGlobalOrNull);
|
|
protoObj := cx.NewRootedObject(global.ptr.ReservedSlot[proto.fSlotIndex].asObject);
|
|
jsobj := cx.NewRootedObject(cx.NewObjectWithGivenProto(@proto.FJSClass, protoObj.ptr));
|
|
try
|
|
// premature optimization is the root of evil
|
|
// as shown by valgrind profiler better to not redefine props in object
|
|
// but let's JS engine to use it from prototype
|
|
//if Length(AProto.FJSProps)>0 then
|
|
// jsobj.ptr.DefineProperties(cx,@AProto.FJSProps[0]);
|
|
|
|
Instance := AInstance;
|
|
new(ObjRec);
|
|
ObjRec.init(otInstance, @Self);
|
|
jsobj.ptr.PrivateData := ObjRec;
|
|
result := jsobj.ptr.ToJSValue;
|
|
finally
|
|
cx.FreeRootedObject(jsobj);
|
|
cx.FreeRootedObject(protoObj);
|
|
cx.FreeRootedObject(global);
|
|
end;
|
|
|
|
finally
|
|
cx.EndRequest;
|
|
end;
|
|
end;
|
|
|
|
{ TSMCustomProtoObject }
|
|
|
|
constructor TSMCustomProtoObject.Create(Cx: PJSContext; aRttiCls: TClass; aParent: PJSRootedObject; slotIndex: integer);
|
|
var
|
|
i: Cardinal;
|
|
ObjRec: PSMObjectRecord;
|
|
obj: PJSObject;
|
|
global: PJSObject;
|
|
begin
|
|
fRttiCls := aRttiCls;
|
|
fCx := Cx;
|
|
fSlotIndex := slotIndex;
|
|
FjsObjName := StringToAnsi7(fRttiCls.ClassName);
|
|
|
|
global := cx.CurrentGlobalOrNull;
|
|
global.ReservedSlot[fSlotIndex] := cx.NewJSString(fRttiCls.ClassName).ToJSVal;
|
|
|
|
FMethodsDA.Init(TypeInfo(TSMMethodDynArray), FMethods);
|
|
InitObject(aParent);
|
|
FJSClass := GetJSClass;
|
|
|
|
FJSClass.Name := PCChar(FjsObjName);
|
|
fFirstDeterministicSlotIndex := (FJSClass.flags and (JSCLASS_RESERVED_SLOTS_MASK shl JSCLASS_RESERVED_SLOTS_SHIFT))
|
|
shr JSCLASS_RESERVED_SLOTS_SHIFT;
|
|
|
|
if fFirstDeterministicSlotIndex + fDeterministicCnt >255 then
|
|
raise ESMException.Create('Too many properties');
|
|
|
|
if fFirstDeterministicSlotIndex + uint32(FMethodsDA.Count) + 1 >255 then
|
|
raise ESMException.Create('Too many methods');
|
|
|
|
FJSClass.flags := FJSClass.flags and not (JSCLASS_RESERVED_SLOTS_MASK shl JSCLASS_RESERVED_SLOTS_SHIFT) or
|
|
((fFirstDeterministicSlotIndex + fDeterministicCnt) shl JSCLASS_RESERVED_SLOTS_SHIFT);
|
|
FJSClassProto := FJSClass;
|
|
FJSClassProto.flags := FJSClassProto.flags and not (JSCLASS_RESERVED_SLOTS_MASK shl JSCLASS_RESERVED_SLOTS_SHIFT) or
|
|
((fFirstDeterministicSlotIndex + uint32(FMethodsDA.Count) + 1) shl JSCLASS_RESERVED_SLOTS_SHIFT);
|
|
cx.BeginRequest;
|
|
try
|
|
//TODO prototypes chain fjsproto
|
|
if length(FJSProps) = 0 then begin
|
|
obj := aParent.ptr.InitClass(cx ,nullObj , @FJSClassProto, nil, 0, nil , nil, nil, nil);
|
|
end else begin
|
|
SetLength(FJSProps, Length(FJSProps) + 1); // must be null terminate!!
|
|
obj := aParent.ptr.InitClass(cx ,nullObj , @FJSClassProto, nil, 0, @FJSProps[0] , nil, nil, nil);
|
|
end;
|
|
obj.ReservedSlot[fFirstDeterministicSlotIndex] := aParent.ptr.ToJSValue;
|
|
//define JS methods
|
|
if FMethodsDA.Count > 0 then
|
|
for i := 0 to FMethodsDA.Count-1 do with FMethods[i] do begin
|
|
obj.ReservedSlot[fFirstDeterministicSlotIndex + i + 1] :=
|
|
obj.DefineUCFunction(cx, PCChar16(ujsName),
|
|
Length(ujsName), call, nargs, uint32(flags)).ToJSValue;
|
|
end;
|
|
new(ObjRec);
|
|
ObjRec.init(otProto,self);
|
|
obj.PrivateData := ObjRec;
|
|
global.ReservedSlot[fSlotIndex] := obj.ToJSValue;
|
|
finally
|
|
cx.EndRequest;
|
|
end;
|
|
end;
|
|
|
|
function CustomProtoObject_freeNative(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean; cdecl;
|
|
var
|
|
nativeObj: PSMInstanceRecord;
|
|
begin
|
|
Result := false;
|
|
try
|
|
if not IsInstanceObject(cx, vp.this[cx], nativeObj) then
|
|
raise ESMException.Create('Object not Native');
|
|
|
|
nativeObj.FreeNative;
|
|
|
|
Result := true;
|
|
except
|
|
on E: Exception do
|
|
begin
|
|
JSError(cx, E);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TSMCustomProtoObject.definePrototypeMethod(const ajsName: SynUnicode; const aCall: JSNative; aNargs: uintN; aFlags: TJSPropertyAttrs);
|
|
var idx: integer;
|
|
added: boolean;
|
|
begin
|
|
idx := FMethodsDA.FindHashedForAdding(ajsName, added);
|
|
if added then begin
|
|
with FMethods[idx] do begin
|
|
ujsName := ajsName;
|
|
nargs := aNargs;
|
|
call := aCall;
|
|
flags := aFlags;
|
|
end;
|
|
end else
|
|
raise ESMException.CreateUtf8('Duplicated native function %()',[ajsName]);
|
|
end;
|
|
|
|
function TSMCustomProtoObject.getMethod(const aJSFunction: PJSFunction; var obj: PJSObject): PSMMethodRec;
|
|
var
|
|
i: Cardinal;
|
|
begin
|
|
result := nil;
|
|
if FMethodsDA.Count > 0 then
|
|
for i := 0 to FMethodsDA.Count-1 do
|
|
if obj.ReservedSlot[i+1+fFirstDeterministicSlotIndex].asObject = aJSFunction then begin
|
|
Result := @FMethods[i];
|
|
break;
|
|
end;
|
|
end;
|
|
|
|
function TSMCustomProtoObject.NewSMInstance(aCx: PJSContext; argc: uintN;
|
|
var vp: JSArgRec): TObject;
|
|
var
|
|
ItemInstance: TClassInstance;
|
|
begin
|
|
ItemInstance.Init(fRttiCls);
|
|
result := ItemInstance.CreateNew;
|
|
end;
|
|
|
|
function TSMCustomProtoObject.getRTTIPropsCache(index: integer): TSMRTTIPropCache;
|
|
begin
|
|
Result := fRTTIPropsCache[index];
|
|
end;
|
|
|
|
function TSMCustomProtoObject.GetJSClass: JSClass;
|
|
begin
|
|
Result := jsdef_class; // default values
|
|
end;
|
|
|
|
procedure TSMCustomProtoObject.InitObject(aParent: PJSRootedObject);
|
|
begin
|
|
definePrototypeMethod('freeNative', @CustomProtoObject_freeNative, 0, [jspEnumerate, jspPermanent, jspReadOnly]);
|
|
end;
|
|
|
|
end.
|
|
|