/// 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.

