forked from synopse/mORMot
-
Notifications
You must be signed in to change notification settings - Fork 0
/
SynSM.pas
2562 lines (2326 loc) · 94.1 KB
/
SynSM.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/// features JavaScript execution using the SpiderMonkey library
// - this unit is a part of the freeware Synopse framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit SynSM;
{
This file is part of Synopse framework.
Synopse framework. Copyright (c) Arnaud Bouchez
Synopse Informatique - https://synopse.info
Scripting support for mORMot Copyright (c) Pavel Mashlyakovsky
pavel.mash at gmail.com
Some ideas taken from
https://code.google.com/p/delphi-javascript
https://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
https://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)
the Initial Developer. All Rights Reserved.
Contributor(s):
- Arnaud Bouchez
- Vadim Orel
- 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 *****
---------------------------------------------------------------------------
Download the SpiderMonkey library at https://synopse.info/files/synsm.7z !
---------------------------------------------------------------------------
Version 1.18
- initial release. Use SpiderMonkey 24
- add TSMObject.defineNativeMethod
- add JSError procedure for Exception handling inside of JSNative function
- enhanced multi thread process
- add TSMEngine.MaybeGarbageCollect method
- add timeout Framework
}
{$I Synopse.inc} // define HASINLINE CPU32 CPU64 OWNNORMTOUPPER
{$I SynSM.inc} // define SM_DEBUG JS_THREADSAFE CONSIDER_TIME_IN_Z
interface
uses
Windows,
{$ifdef ISDELPHIXE2}System.SysUtils,{$else}SysUtils,{$endif}
Classes,
{$ifndef LVCL}
Contnrs,
{$endif}
Variants,
SynCommons,
SynLog,
SynTests,
SynTable,
SynSMAPI;
const
/// default stack growing size, in bytes
STACK_CHUNK_SIZE: cardinal = 8192;
type
/// generic parent class of all SpiderMonkey-related Exception types
ESMException = class(ESynException);
{$M+}
TSMEngineManager = class;
{$M-}
TSMEngine = class;
/// just a wrapper around jsval API type, to be used with our object wrappers
// - SpiderMonkey jsval type can be directly casted to this type via TSMValue(jsval)
// - note that some methods expect an execution context to be supplied as
// parameter, as soon as it contains a non primitive type (double/integer)
TSMValue = object
protected
FValue: jsval;
public
/// type of the value
// - you should better use this before calling other To*() methods
function ValType(cx: PJSContext): JSType;
{$ifdef HASINLINE}inline;{$endif}
/// direct access to the internal jsval instance
property AsJSVal: jsval read FValue write FValue;
/// set the value as one 32 bit integer
procedure SetInteger(const Value: integer);
{$ifdef HASINLINE}inline;{$endif}
/// read the value as one 32 bit integer
function ToInteger: integer;
{$ifdef WITHASSERT}{$ifdef HASINLINE}inline;{$endif}{$endif}
/// access to the value as integer
property AsInteger: integer read ToInteger write SetInteger;
/// set the value as floating point
procedure SetDouble(const Value: double);
{$ifdef HASINLINE}inline;{$endif}
/// read the value as floating point
function ToDouble: double;
{$ifdef WITHASSERT}{$ifdef HASINLINE}inline;{$endif}{$endif}
/// access to the value as floating point
property AsDouble: double read ToDouble write SetDouble;
/// set the value as boolean
procedure SetBoolean(const Value: boolean);
{$ifdef HASINLINE}inline;{$endif}
/// read the value as boolean
function ToBoolean: boolean;
{$ifdef HASINLINE}inline;{$endif}
/// access to the value as boolean
property AsBoolean: boolean read ToBoolean write SetBoolean;
/// set the value as one 64 bit integer
// - this is a somewhat dirty hack, since SpiderMonkey don't support int64:
// but it is possible to transform int64 to double for ant value < (1 shl 51)
// - sometimes we need int64 to be passed do SpiderMonkey (e.g. for an ID)
procedure SetInt64(const Value: int64);
/// read the value as one 64 bit integer
// - note that SpiderMonkey is not able to store all Int64 values directly
function ToInt64: int64;
/// access to the value as one 64 bit integer
property AsInt64: int64 read ToInt64 write SetInt64;
/// set the value as VOID
procedure SetVoid; {$ifdef HASINLINE}inline;{$endif}
/// set the value as NULL
procedure SetNull; {$ifdef HASINLINE}inline;{$endif}
/// set the value as variant (not implemented yet)
// - will set any custom variant type (e.g. TDocVariant) as a JavaScript
// object value computed from the JSON serialization of the variant
// - in SpiderMonkey non-simple type instances do exist in a given JSContext,
// so we need to know the execution context (using a property is not an option)
procedure SetVariant(cx: PJSContext; const Value: Variant);
/// return the value as variant (not implemented yet)
// - will return any JavaScript string value directly as a RawUTF8
// - will return any JavaScript object value as a TDocVariant document
// - in SpiderMonkey non-simple type instances do exist in a given JSContext,
// so we need to know the execution context (using a property is not an option)
function ToVariant(cx: PJSContext): Variant; overload;
{$ifdef HASINLINE}inline;{$endif}
/// return the value as variant (not implemented yet)
// - will return any JavaScript string value directly as a RawUTF8
// - will return any JavaScript object value as a TDocVariant document
// - in SpiderMonkey non-simple type instances do exist in a given JSContext,
// so we need to know the execution context (using a property is not an option)
procedure ToVariant(cx: PJSContext; var result: Variant); overload;
/// set the value as TVarRec (i.e. an "array of const" open parameter)
// - here any AnsiString parameter is expected to be a RawUTF8 before Delphi
// 2009, or its correct code page will be retrieved since Delphi 2009
// - in SpiderMonkey non-simple type instances do exist in a given JSContext,
// so we need to know the execution context (using a property is not an option)
procedure SetTVarRec(cx: PJSContext; const V: TVarRec);
/// set the value as an UTF-16 encoded buffer
// - in SpiderMonkey non-simple type instances do exist in a given JSContext,
// so we need to know the execution context (using a property is not an option)
// - warning - JSString string is a subject for GC so you must root it or set
// as property of some object or use SetNativeString() method to pass the
// value by reference
procedure SetWideChar(cx: PJSContext; Text: PWideChar; TextLen: integer);
/// set the value as an Ansi encoded buffer (may be UTF-8 or any code page)
// - if CodePage is 0, will use the CurrentAnsiCodePage value
// - in SpiderMonkey non-simple type instances do exist in a given JSContext,
// so we need to know the execution context (using a property is not an option)
// - warning - JSString string is a subject for GC so you must root it or set
// as property of some object or use SetNativeString() method to pass the
// value by reference
procedure SetAnsiChar(cx: PJSContext; Text: PAnsiChar; TextLen, CodePage: integer);
/// set the value as an Unicode String
// - in SpiderMonkey non-simple type instances do exist in a given JSContext,
// so we need to know the execution context (using a property is not an option)
// - warning - JSString string is a subject for GC so you must root it or set
// as property of some object or use SetNativeString() method to pass the
// value by reference
procedure SetSynUnicode(cx: PJSContext; const aStr: SynUnicode);
{$ifdef HASINLINE}inline;{$endif}
/// return the value as an Unicode String
// - in SpiderMonkey non-simple type instances do exist in a given JSContext,
// so we need to know the execution context (using a property is not an option)
function ToSynUnicode(cx: PJSContext): SynUnicode; overload;
{$ifdef HASINLINE}inline;{$endif}
/// return the value as an Unicode String
// - in SpiderMonkey non-simple type instances do exist in a given JSContext,
// so we need to know the execution context (using a property is not an option)
procedure ToSynUnicode(cx: PJSContext; var result: SynUnicode); overload;
{$ifdef HASINLINE}inline;{$endif}
/// set the value as an Unicode WideString
// - in SpiderMonkey non-simple type instances do exist in a given JSContext,
// so we need to know the execution context (using a property is not an option)
// - warning - JSString string is a subject for GC so you must root it or set
// as property of some object or use SetNativeString() method to pass the
// value by reference
procedure SetWideString(cx: PJSContext; const aStr: WideString);
{$ifdef HASINLINE}inline;{$endif}
/// return the value as an Unicode WideString
// - in SpiderMonkey non-simple type instances do exist in a given JSContext,
// so we need to know the execution context (using a property is not an option)
function ToWideString(cx: PJSContext): WideString;
{$ifdef HASINLINE}inline;{$endif}
/// set the value as an UTF-8 String
// - in SpiderMonkey non-simple type instances do exist in a given JSContext,
// so we need to know the execution context (using a property is not an option)
// - warning - JSString string is a subject for GC so you must root it or set
// as property of some object or use SetNativeString() method to pass the
// value by reference
procedure SetUTF8(cx: PJSContext; const aStr: RawUTF8);
{$ifdef HASINLINE}inline;{$endif}
/// return the value as an UTF-8 String
// - in SpiderMonkey non-simple type instances do exist in a given JSContext,
// so we need to know the execution context (using a property is not an option)
function ToUTF8(cx: PJSContext): RawUTF8;
{$ifdef HASINLINE}inline;{$endif}
/// set the value from UTF-8 encoded JSON
// - returns TRUE if aJSON was valid, FALSE in case of an error
function SetJSON(cx: PJSContext; const aJSON: RawUTF8): boolean;
/// return the value as UTF-8 encoded JSON
function ToJSON(cx: PJSContext): RawUTF8;
/// add the value as UTF-8 encoded JSON
procedure AddJSON(cx: PJSContext; W: TTextWriter);
/// set the value as Unicode String by reference
// - this is the fastest way to add a string to SpiderMonley: String is in
// fact not copied to the SpiderMonkey engine, just passed by reference
// - Only SynUnicode string support by now (SpiderMonkey is internally UTF-16 based)
// - WARNING - as a consequence, aStr must be UNCHANGED until SpiderMonkey engine
// points to it (SpiderMonkey will also consider its strings as immutable, so will
// never change its content during execution) - for instance, never pass a
// function result as aStr, nor use a local SynUnicode variable unless you
// trigger the Garbage Collection before the end of the local method
procedure SetNativeString(cx: PJSContext; const aStr: SynUnicode);
/// set the value as a date/time
// - in SpiderMonkey non-simple type instances do exist in a given JSContext,
// so we need to know the execution context (using a property is not an option)
procedure SetDateTime(cx: PJSContext; const Value: TDateTime);
/// return the value as a date/time
// - in SpiderMonkey non-simple type instances do exist in a given JSContext,
// so we need to know the execution context (using a property is not an option)
function ToDateTime(cx: PJSContext): TDateTime;
/// transform a JSValue to its UTF-16 string representation
// - JavaScript equivalent is
// ! variable.toString()
function TransformToSynUnicode(cx: PJSContext): SynUnicode;
/// transform a JSValue to its UTF-8 string representation
// - JavaScript equivalent is
// ! variable.toString()
function TransformToUTF8(cx: PJSContext): RawUTF8;
/// attemps to convert the value into a native function pointer
function ToNativeFunction(cx: PJSContext): PJSFunction;
/// attemps to convert the value into a native function name
function ToNativeFunctionName(cx: PJSContext): RawUTF8;
end;
/// a pointer to a jsval wrapper
PSMValue = ^TSMValue;
/// a jsval wrappers array
TSMValues = array[0..(MaxInt div sizeof(TSMValue))-1] of TSMValue;
/// a pointer to a jsval wrappers array
PSMValues = ^TSMValues;
/// a dynamic array of jsval wrappers
SMValArray = array of TSMValue;
/// just a wrapper around JavaScript Object API type, to be used with other
// values wrappers
// - SpiderMonkey object type can NOT be directly casted to this type via
// TSMObject(jsobject) - use JSObject wrapper instead - since we expects
// an execution context to be specified
// - to create instance of this structure, use TSMEngine.NewObject() or
// MakeObject() overloaded methods
TSMObject = object
private
FDefaultPropertyAttrs: TJSPropertyAttrs;
function GetPrivate: pointer;
procedure SetPrivate(const Value: pointer);
function GetItem(aIndex: integer): variant;
procedure SetItem(aIndex: integer; const Value: variant);
procedure SetDefaultPropertyAttrs(const Value: TJSPropertyAttrs);
protected
fCx: PJSContext;
fObj: PJSObject;
procedure SetPropVariant(const propName: SynUnicode; const Value: variant);
public
/// get the parent object of a given object
function Parent: TSMObject;
{$ifdef HASINLINE}inline;{$endif}
/// get the prototype of a given object
function Prototype: TSMObject;
{$ifdef HASINLINE}inline;{$endif}
/// access the private data field of an object
// - wrapper to JS_GetPrivate()/JS_SetPrivate()
// - only works if the object's JSClass has the JSCLASS_HAS_PRIVATE flag:
// it is safer to use GetPrivateData() method and providing the JSClass
property PrivateData: pointer read GetPrivate write SetPrivate;
/// retrieve the private data associated with an object, if that object
// is an instance of a specified class
// - wrapper to JS_GetInstancePrivate()
function GetPrivateData(expectedClass: PJSClass): pointer;
/// return TRUE if the object is an array
function IsArray: boolean;
/// return the number of elements in this array
function ItemsCount: cardinal;
/// delete an item of this object as array
procedure DeleteItem(aIndex: integer);
/// access to an item of this object as array
property Items[aIndex: integer]: variant read GetItem write SetItem;
/// define an object property with a value, specified as jsvalue
// - this is not a direct JavaScript equivalent of
// ! obj[name] = val
// since any setter will be called
// - to set a property in a global object, call either
// ! SMEngine.Global.property := ... // via late-binding
// ! SMEngine.GlobalObject.DefineProperty() // direct via TSMObject
// equivalent in JavaScript to:
// ! var name = value
// outside a JavaScript function context (i.e. in global scope)
// - if property already exists, it will just replace its value with the
// supplied value
// - this method will use the default properties attributes of this engine
procedure DefineProperty(const name: SynUnicode; const value: TSMValue); overload;
/// define an object property with a value, specified as jsvalue
// - this is not a direct JavaScript equivalent of
// ! obj[name] = val
// since any setter will be called
// - to set a property in a global object, call either
// ! SMEngine.Global.property := ... // via late-binding
// ! SMEngine.GlobalObject.DefineProperty() // direct via TSMObject
// equivalent in JavaScript to:
// ! var name = value
// outside a JavaScript function context (i.e. in global scope)
// - if property already exists, it will just replace its value with the
// supplied value
// - this method will allow to set custom properties attributes of this engine
procedure DefineProperty(const name: SynUnicode; const value: TSMValue;
attrs: TJSPropertyAttrs); overload;
/// define an object property with a value, specified as variant
// - you can also use the property Properties[]
// - this is not a direct JavaScript equivalent of
// ! obj[name] = val
// since any setter will be called
// - to set a property in a global object, call either
// ! SMEngine.Global.property := ... // via late-binding
// ! SMEngine.GlobalObject.DefineProperty() // direct via TSMObject
// equivalent in JavaScript to:
// ! var name = value
// outside a JavaScript function context (i.e. in global scope)
// - if property already exists, it will just replace its value with the
// supplied value
// - this method will use the default properties attributes of this engine
procedure DefineProperty(const name: SynUnicode; const value: variant); overload;
{$ifdef HASINLINE}inline;{$endif}
/// define an object property with a value, specified as variant
// - you can also use the property Properties[]
// - this is not a direct JavaScript equivalent of
// ! obj[name] = val
// since any setter will be called
// - to set a property in a global object, call either
// ! SMEngine.Global.property := ... // via late-binding
// ! SMEngine.GlobalObject.DefineProperty() // direct via TSMObject
// equivalent in JavaScript to:
// ! var name = value
// outside a JavaScript function context (i.e. in global scope)
// - if property already exists, it will just replace its value with the
// supplied value
// - this method will allow to set custom properties attributes of this engine
procedure DefineProperty(const name: SynUnicode; const value: variant;
attrs: TJSPropertyAttrs); overload;
{$ifdef HASINLINE}inline;{$endif}
/// add JSNative compatible function into JS object
// - here the method name is specified as SynUnicode
// - func if reference to function with JSNative signature
// - nargs is function argument count
// - actually this method creates a JSFunction and assing its value to
// ! obj[methodName]
// - to add a global function, define it into the "global" object - i.e. call
// ! TSMEngine.GlobalObject.DefineNativeMethod()
// - this method will use the default properties attributes of this engine
function DefineNativeMethod(const methodName: SynUnicode;
func: JSNative; nargs: uintN): PJSFunction; overload;
/// add JSNative compatible function into JS object
// - here the method name is specified as SynUnicode
// - func if reference to function with JSNative signature
// - nargs is function argument count
// - actually this method creates a JSFunction and assing its value to
// ! obj[methodName]
// - to add a global function, define it into the "global" object - i.e. call
// ! TSMEngine.GlobalObject.DefineNativeMethod()
// - this method will allow to set custom properties attributes of this engine
function DefineNativeMethod(const methodName: SynUnicode;
func: JSNative; nargs: uintN; attrs: TJSPropertyAttrs): PJSFunction; overload;
/// add JSNative compatible function into JS object
// - here the method name is specified as AnsiString
// - func if reference to function with JSNative signature
// - nargs is function argument count
// - this method will use the default properties attributes of this engine
function DefineNativeMethod(const methodName: AnsiString;
func: JSNative; nargs: uintN): PJSFunction; overload;
/// add JSNative compatible function into JS object
// - here the method name is specified as AnsiString
// - func if reference to function with JSNative signature
// - nargs is function argument count
// - this method will allow to set custom properties attributes of this engine
function DefineNativeMethod(const methodName: AnsiString;
func: JSNative; nargs: uintN; attrs: TJSPropertyAttrs): PJSFunction; overload;
/// check object property does exist (including prototype chain lookup)
function HasProperty(const propName: SynUnicode): Boolean;
/// Determine whether a property is physically present on a object
// - JavaScript equivalent of
// ! Object.hasOwnProperty(propName)
function HasOwnProperty(const propName: SynUnicode): Boolean;
/// get object property value (call geter for native)
// - JavaScript equivalent of
// ! obj[name]
// - returns JSVAL_VOID if object does not have such property
function GetPropValue(const propName: SynUnicode): TSMValue;
/// get object property value (call geter for native)
// - you can also use the property Properties[]
// - JavaScript equivalent of
// ! obj[name]
// - returns null if object does not have such property
function GetPropVariant(const propName: SynUnicode): variant;
/// read/write access to the object properties as variant
property Properties[const propName: SynUnicode]: variant
read GetPropVariant write SetPropVariant; default;
/// evaluate JavaScript script in the current object scope
// - if exception raised in script - raise Delphi ESMException
// - on success, returns the last executed expression statement processed
// in the script in low-level result output variable
// - JavaScript Equivalent of
// ! with(obj) eval(script)
// - be careful about execution scope - see JS_ExecuteScript() description
// - usualy you need to evaluate script only in global object scope, so you
// should better always call TSMEngine.Evaluate()
procedure Evaluate(const script: SynUnicode; const scriptName: RawUTF8;
lineNo: Cardinal; out result: TSMValue);
/// executes a JavaScript object method using low-level SMVal arguments
// - returns the function result as a TSMValue
// - JavaScript equivalent of
// ! rval := obj.methodName(argv[0], ....);
procedure RunMethod(const methodName: AnsiString; const argv: SMValArray;
out rval: TSMValue); overload;
/// executes a JavaScript object method using a Delphi array of const
// - returns the function result as a TSMValue
// - JavaScript equivalent of
// ! rval := obj.methodName(argv[0], ....);
// - here any AnsiString parameter is expected to be a RawUTF8 before Delphi
// 2009, or its correct code page will be retrieved since Delphi 2009
procedure RunMethod(const methodName: AnsiString; const argv: array of const;
out rval: TSMValue); overload;
/// executes a JavaScript object method using a Delphi array of variants
// - returns the function result as a variant
// - JavaScript equivalent of
// ! rval := obj.methodName(argv[0], ....);
function Run(const methodName: AnsiString; const argv: array of variant): variant;
/// returns the associated execution context
property cx: PJSContext read fCx;
/// returns the associated jsobject instance
property obj: PJSObject read fObj;
/// returns the associated jsobject instance as a jsvalue
function AsSMValue: TSMValue;
/// protect the object from Garbage Collection
// - if this object is not set as property value of any other object
// or passed as parameter to function, you must protect it
procedure Root;
/// unprotect a previously "rooted" object
// - WARNING!! Object MUST be protected by a previous Root method call,
// otherwise you get an access violation
procedure UnRoot;
/// set properties obj and cx to nil
procedure Clear;
/// returns the associated script engine instance
function Engine: TSMEngine;
{$ifdef HASINLINE}inline;{$endif}
/// access to the default attributes when accessing any properties
property DefaultPropertyAttrs: TJSPropertyAttrs read FDefaultPropertyAttrs write SetDefaultPropertyAttrs;
end;
//// variant-based callback signature used for TSMEngine.RegisterMethod()
// - any Delphi exception raised during this execution will be converted into
// a JavaScript exception by TSMEngine
// - "this" JavaScript calling object is transmitted as a TSMVariant custom
// variant: you can use late-binding over it to access its methods
// or properties, or transtype it using TSMVariantData(Instance)
// and access its low-level API content
// - input arguments (and function result) are simple variant values, or
// TDocVariant custom variant instance for any object as complex document
// - corresponds to meVariant kind of callback method
TSMEngineMethodEventVariant = function(const This: variant;
const Args: array of variant): variant of object;
//// JSON-based callback signature used for TSMEngine.RegisterMethod()
// - any Delphi exception raised during this execution will be converted into
// a JavaScript exception by TSMEngine
// - similar to TServiceMethod.InternalExecute() as defined in mORMot.pas
// (for instance, this callback will be used to execute native Delphi
// interface-based methods from JavaScript code in mORMotSM.pas unit)
// - "this" JavaScript calling object is transmitted as low-level TSMObject
// - will expect as input a JSON array of parameters from Args, e.g.
// ! '[1,2,3]'
// - if the method only expect one result, shall return one JSON value, e.g.
// ! '6'
// - if the method expect more than one result (i.e. several var/out parameters
// in addition to the main function result), it shall return a JSON object,
// with parameter names for all var/out/result values, e.g.
// ! '{"first":1,"second":2,"result":3}'
// - this allows the function result to be consumed by the JavaScript as
// a regular JS value or object
// - corresponds to meJSON kind of callback method
TSMEngineMethodEventJSON = function(const This: TSMObject;
const Args: RawUTF8): RawUTF8 of object;
/// pointer to our wrapper around JavaScript Object
PSMObject = ^TSMObject;
/// kinds of callback methods available for TSMEngine.RegisterMethod()
TSMEngineMethodEventKind = (meVariant, meJSON);
/// used to store one registered method event
TSMEngineMethodEvent = record
Func: PJSFunction;
case EventKind: TSMEngineMethodEventKind of
meVariant: (CallbackVariant: TSMEngineMethodEventVariant);
meJSON: (CallbackJSON: TSMEngineMethodEventJSON);
end;
/// used to store the registered method events
TSMEngineMethodEventDynArray = array of TSMEngineMethodEvent;
/// implements a ThreadSafe JavaScript engine
// - use TSMEngineManager.ThreadSafeEngine to retrieve the Engine instance
// corresponding to the current thread, in multithread application
// - contains JSRuntime + JSContext (to be ready for new SpiderMonkey version where
// context and runtime is the same)
// - contains also one "global" JavaScript object. From script it is
// accessible via "global." (in browser, this is the "window." object)
// - set SpiderMonkey error reporter and store last SpiderMonkey error in
// LastError property
TSMEngine = class
protected
fRt: PJSRuntime;
fCx: PJSContext;
fcomp: PJSCompartment;
fNativeMethod: TSMEngineMethodEventDynArray;
fNativeMethods: TDynArrayHashed;
fNativeMethodCount: integer;
FManager: TSMEngineManager;
FGlobal: variant;
FGlobalObject: TSMObject;
FEngineContentVersion: Cardinal;
FStringFinalizer: JSStringFinalizer;
FThreadID: TThreadID;
FLastErrorMsg: RawUTF8;
FLastErrorFileName: RawUTF8;
FLastErrorLine: integer;
FLastErrorStackTrace: RawUTF8;
FErrorExist: boolean;
function InternalRegisterMethod(obj: PJSObject; const MethodName: SynUnicode;
const Event: TMethod; Kind: TSMEngineMethodEventKind; ArgumentsCount: integer): PJSFunction;
/// called from SpiderMonkey callback. Do not raise exception here
// instead use CheckJSError metod after JSAPI compile/evaluate call
procedure DoProcessJSError(errMsg: PCChar; report: PJSErrorReport); virtual;
/// called from SpiderMonkey callback. It used for interrupt execution of script
// when it executes too long
function DoProcessOperationCallback: JSBool; virtual;
procedure CancelExecution;
private
FDefaultPropertyAttrs: TJSPropertyAttrs;
procedure SetDefaultPropertyAttrs(const Value: TJSPropertyAttrs);
protected
// used by Watchdog thread state. See js.cpp
fTimeOutAborted: Boolean;
fTimedOut: Boolean;
fWatchdogLock: PRLock;
fWatchdogWakeup: PRCondVar;
fWatchdogThread: PRThread;
fWatchdogHasTimeout: Boolean;
fWatchdogTimeout: Int64;
fSleepWakeup: PRCondVar;
fTimeoutInterval: double;
function ScheduleWatchdog(t: Double): Boolean;
procedure KillWatchdog;
function InitWatchdog: boolean;
procedure SetTimeoutValue(const Value: Double);
public
/// create one threadsafe JavaScript Engine instance
// - initialize internal JSRuntime, JSContext, and global objects and
// standard JavaScript classes
// - do not create Engine directly via this constructor, but instead call
// TSMEngineManager.ThreadSafeEngine
constructor Create(aManager: TSMEngineManager); virtual;
/// finalize the JavaScript engine instance
destructor Destroy; override;
/// check if last call to JSAPI compile/eval fucntion was successful
// - raise ESMException if any error occurred
// - put error description to SynSMLog
procedure CheckJSError(res: JSBool); virtual;
/// clear last JavaScript error
// - called before every evaluate() function call
procedure ClearLastError;
/// trigger Garbage Collection
// - all unrooted things (JSString, JSObject, VSVal) will be released
procedure GarbageCollect;
/// Offer the JavaScript engine an opportunity to perform garbage collection if needed
// - Tries to determine whether garbage collection in would free up enough
// memory to be worth the amount of time it would take. If so, it performs
// some garbage collection
// - Frequent calls are safe and will not cause the application to spend a
// lot of time doing redundant garbage collection work
procedure MaybeGarbageCollect;
/// create new ordinary JavaScript object
// - JavaScript equivalent of
// ! {}
// - new object is subject to Garbage Collection, so must be rooted or
// assigned as value for a property to create new object type property,
// as in JavaScript:
// ! var obj = {}
procedure NewObject(out newobj: TSMObject); overload;
/// create new ordinary JavaScript object, stored as TSMVariant custom type
// - JavaScript equivalent of
// ! {}
// - new object is subject to Garbage Collection, so should be
// assigned as value for a property to create new object type property,
// as in JavaScript:
// ! var obj = {}
function NewSMVariant: variant;
{$ifdef HASINLINE}inline;{$endif}
/// create new ordinary JavaScript object, stored as TSMVariant custom type,
// and rooted to avoid garbage collection
// - JavaScript equivalent of
// ! {}
// - new object is subject to Garbage Collection, so is rooted and should
// be explicitly unrooted, e.g. via:
// ! obj: variant;
// ! ...
// ! FManager.ThreadSafeEngine.NewSMVariantRooted(obj);
// ! try
// ! ... work with obj
// ! finally
// ! obj._UnRoot; // pseudo-method
// ! end;
procedure NewSMVariantRooted(out newobj: variant);
/// create new JavaScript object with prototype
// - JavaScript equivalent of
// ! {}.__proto__ := prototype;
procedure NewObject(const prototype: TSMObject; out newobj: TSMObject); overload;
/// create new JavaScript object from its class
procedure NewObjectWithClass(clasp: PJSClass; var newobj: TSMObject); overload;
/// create new JavaScript object from its prototype
procedure NewObjectWithClass(clasp: PJSClass; const prototype: TSMObject; const parent: TSMObject; var newobj: TSMObject); overload;
/// create new JavaScript object from its class and property specifications
procedure InitClass(clasp: PJSClass; ps: PJSPropertySpec; var newobj: TSMObject);
/// converts a JavaScript value into a JavaScript object
procedure MakeObject(const value: TSMValue; out obj: TSMObject); overload;
{$ifdef HASINLINE}inline;{$endif}
/// converts a JavaScript low-level value into a JavaScript object
procedure MakeObject(const value: jsval; out obj: TSMObject); overload;
/// converts a JavaScript low-level object into a JavaScript object
procedure MakeObject(jsobj: PJSObject; out obj: TSMObject); overload;
/// register a native Delphi variant-based method for a given object
// - the supplied function name is case-sensitive
// - the supplied callback will be executed directly by the JavaScript
// engine, supplying all parameters as variant (including TDocVariant for
// any complex object), and returning the function result as variant
// - raise an ESMException if the function could not be registered
function RegisterMethod(obj: PJSObject; const MethodName: SynUnicode;
const Event: TSMEngineMethodEventVariant; ArgumentsCount: integer): PJSFunction; overload;
/// register a native Delphi JSON-based method for a given object
// - the supplied function name is case-sensitive
// - the supplied callback will be executed directly by the JavaScript
// engine, supplying all parameters as JSON array, and returning the
// function result either as a JSON value or a JSON object
// - raise an ESMException if the function could not be registered
function RegisterMethod(obj: PJSObject; const MethodName: SynUnicode;
const Event: TSMEngineMethodEventJSON; ArgumentsCount: integer): PJSFunction; overload;
/// unregister a native Delphi method for a given object
// - raise an ESMException if the function was not previously registered
// - you should not call it usually, but it is available in case
procedure UnRegisterMethod(JSFunction: PJSFunction);
/// evaluate a JavaScript script in the global scope
// - a wrapper to GlobalObject.Evaluate(...)
// - if exception raised in script - raise Delphi ESMException
// - on success returns last executed expression statement processed
// in the script as a variant
// - JavaScript equivalent to
// ! eval(script)
function Evaluate(const script: SynUnicode; const scriptName: RawUTF8='script';
lineNo: Cardinal=1): variant;
/// access to the associated global object as a TSMVariant custom variant
// - allows direct property and method executions in Delphi code, via
// late-binding, for instance:
// ! engine.Global.MyVariable := 1.0594631;
// ! engine.Global.MyFunction(1,'text');
property Global: variant read FGlobal;
/// access to the associated global object as a TSMObject wrapper
// - you can use it to register a method
property GlobalObject: TSMObject read FGlobalObject;
/// access to the associated global object as low-level PJSObject
property GlobalObj: PJSObject read FGlobalObject.fobj;
/// access to the associated execution context
property cx: PJSContext read fCx;
/// access to the associated execution runtime
property rt: PJSRuntime read frt;
/// access to the associated execution compartment
property comp: PJSCompartment read fcomp;
/// internal version number of engine scripts
// - used in TSMEngine.ThreadSafeEngine to determine if context is up to
// date, in order to trigger on-the-fly reload of scripts without the need
// if restarting the application
// - caller must change this parameter value e.g. in case of changes in
// the scripts folder in an HTTP server
property EngineContentVersion: Cardinal read FEngineContentVersion;
/// last error message triggered during JavaScript execution
property LastErrorMsg: RawUTF8 read FLastErrorMsg;
/// last error source code line number triggered during JavaScript execution
property LastErrorLine: integer read FLastErrorLine;
/// last error file name triggered during JavaScript execution
property LastErrorFileName: RawUTF8 read FLastErrorFileName;
/// TRUE if an error was triggered during JavaScript execution
property ErrorExist: boolean read FErrorExist;
/// notifies a WatchDog timeout
property TimeOutAborted: boolean read FTimeOutAborted;
/// define a WatchDog timeout interval
// - is set to -1 by default, i.e. meaning no execution timeout
property TimeOutValue: Double read fTimeoutInterval write SetTimeoutValue;
/// access to the default attributes when accessing any properties
property DefaultPropertyAttrs: TJSPropertyAttrs read FDefaultPropertyAttrs write SetDefaultPropertyAttrs;
end;
/// prototype of SpideMonkey notification callback method
TEngineEvent = procedure(const Engine: TSMEngine) of object;
/// main access point to the SpiderMonkey per-thread scripting engines
// - allow thread-safe access to an internal per-thread TSMEngine instance list
// - contains runtime-level properties shared between thread-safe engines
// - you can create several TSMEngineManager instances, if you need several
// separate scripting instances
// - set OnNewEngine callback to initialize each TSMEngine, when a new thread
// is accessed, and tune per-engine memory allocation via MaxPerEngineMemory
// and MaxRecursionDepth
// - get the current per-thread TSMEngine instance via ThreadSafeEngine method
TSMEngineManager = class
protected
FMaxPerEngineMemory: Cardinal;
FMaxRecursionDepth: Cardinal;
FEnginePool: TObjectList;
FEngineCS: TRTLCriticalSection;
FContentVersion: Cardinal;
FOnNewEngine: TEngineEvent;
procedure SetMaxPerEngineMemory(AMaxMem: Cardinal);
/// returns -1 if none was defined yet
// - this method is not protected via the global FEngineCS mutex/lock
function ThreadEngineIndex(ThreadID: TThreadID): Integer;
/// returns nil if none was defined yet
function CurrentThreadEngine: TSMEngine;
/// create a new SpiderMonkey Engine
// - used by ThreadSafeEngine method to instantiate a new per-thread Engine
function CreateNewEngine: TSMEngine; virtual;
/// called when a new Engine is created
// - this default implementation will run the OnNewEngine callback (if any)
procedure DoOnNewEngine(const Engine: TSMEngine); virtual;
public
/// initialize the SpiderMonkey scripting engine
constructor Create; virtual;
/// finalize the SpiderMonkey scripting engine
destructor Destroy; override;
/// get or create one Engine associated with current running thread
// - in single thread application will return the MainEngine
function ThreadSafeEngine: TSMEngine;
/// method to be called when a thread is about to be finished
// - you can call this method just before a thread is finished to ensure
// that the associated scripting Engine will be released
// - could be used e.g. in a try...finally block inside a TThread.Execute
// overriden method
procedure ReleaseCurrentThreadEngine;
/// internal version of the script files
// - used in TSMEngine.ThreadSafeEngine to determine if context is up to
// date, in order to trigger on-the-fly reload of scripts without the need
// if restarting the application
property ContentVersion: Cardinal read FContentVersion write FContentVersion;
/// lock/mutex used for thread-safe access to the TSMEngine list
property Lock: TRTLCriticalSection read FEngineCS;
published
/// max amount of memory (in bytes) for a single SpiderMonkey instance
// - this parameter will be set only at Engine start, i.e. it must be set
// BEFORE any call to ThreadSafeEngine
// - default is 8 MB
property MaxPerEngineMemory: Cardinal read FMaxPerEngineMemory write SetMaxPerEngineMemory
default 8*1024*1024;
/// maximum expected recursion depth for JavaScript functions
// - to avoid out of memory situation in functions like
// ! function f(){ f() };
// - default is 32, but you can specify some higher value
property MaxRecursionDepth: Cardinal read FMaxRecursionDepth write FMaxRecursionDepth
default 32;
/// event triggered every time a new Engine is created
// - here your code can change the initial state of the Engine
property OnNewEngine: TEngineEvent read FOnNewEngine write FOnNewEngine;
end;
{$M-}
var
/// the internal custom variant type used to register TSMVariant
SMVariantType: TSynInvokeableVariantType = nil;
type
/// pointer to a TSMVariant storage
PSMVariantData = ^TSMVariantData;
/// a custom variant type used to store a SpiderMonkey object in Delphi code
// - via the magic of late binding, it will allow access of any JavaScript
// object property, or execute any of its methods
// - primitive types (i.e. null, string, or numbers) will be stored as
// simple variant instances, but JavaScript objects (i.e. objects, prototypes
// or functions) can be stored as an instance of this TSMVariant custom type
// - you can use the _Root and _UnRoot pseudo-methods, which will protect
// the object instance to avoid unexpected Garbage Collection
TSMVariant = class(TSynInvokeableVariantType)
protected
/// fast getter/setter implementation of object properties
function IntGet(var Dest: TVarData; const Instance: TVarData;
Name: PAnsiChar; NameLen: PtrInt): boolean; override;
function IntSet(const Instance, Value: TVarData;
Name: PAnsiChar; NameLen: PtrInt): boolean; override;
public
/// initialize a variant instance to store a JavaScript object
class procedure New(const aObject: TSMObject; out aValue: variant); overload;
/// initialize a variant instance to store a JavaScript object
class procedure New(cx: PJSContext; obj: PJSObject; out aValue: variant); overload;
/// initialize a variant instance to store a new JavaScript object
class procedure New(engine: TSMEngine; out aValue: variant); overload;
// this implementation will let SpiderMonkey write directly the JSON content
procedure ToJSON(W: TTextWriter; const Value: variant; Escape: TTextWriterKind); override;
/// handle type conversion
// - any TSMVariant will be converted to '<<JavaScript TSMVariant>>' text
procedure Cast(var Dest: TVarData; const Source: TVarData); override;
/// handle type conversion
// - any TSMVariant will be converted to '<<JavaScript TSMVariant>>' text
procedure CastTo(var Dest: TVarData; const Source: TVarData;
const AVarType: TVarType); override;
/// low-level callback to execute any JavaScript object method
// - add the _(Index: integer): variant method to retrieve an item
// if the object is an array
function DoFunction(var Dest: TVarData; const V: TVarData;
const Name: string; const Arguments: TVarDataArray): Boolean; override;
end;
{$A-} { packet object not allowed since Delphi 2009 :( }
/// memory structure used for TSMVariant storage of any JavaScript object
// as Delphi variant
// - primitive types (i.e. null, string, or numbers) will be stored as
// simple variant instances, but JavaScript objects (i.e. objects, prototypes
// or functions) can be stored as an instance of this TSMVariant custom type
// - this variant stores its execution context, so is pretty convenient to
// work with in plain Delphi code, also thanks to late-binding feature
{$ifdef UNICODE}
TSMVariantData = record
private
{$else}
TSMVariantData = object
protected
{$endif}
VType: TVarType;
{$IFDEF FPC} {$PUSH} {$ENDIF} {$HINTS OFF}
// does not complain if Filler is declared but never used
Filler: array[1..SizeOf(TVarData)-SizeOf(TVarType)-SizeOf(TSMObject)] of byte;
{$IFDEF FPC} {$POP} {$ELSE} {$HINTS ON} {$ENDIF}
VObject: TSMObject;
public
/// initialize a TSMVariant structure to store a specified JavaScript object
procedure Init(const aObject: TSMObject); overload;
{$ifdef HASINLINE}inline;{$endif}
/// initialize a TSMVariant structure to store a specified JavaScript object
procedure Init(aCx: PJSContext; aObj: PJSObject); overload;
/// initialize a TSMVariant structure to store a new JavaScript object
procedure InitNew(engine: TSMEngine);
/// retrieve the global object of this execution context
// - you can use this from a native function, e.g.:
//!function TMyClass.MyFunction(const This: variant; const Args: array of variant): variant;
//!var global: variant;
//!begin
//! TSMVariantData(This).GetGlobal(global);
//! global.anotherFunction(Args[0],Args[1],'test');
//! // same as:
//! global := TSMVariantData(This).SMObject.Engine.Global;
//! global.anotherFunction(Args[0],Args[1],'test');
//! // but you may also write directly:
//! with TSMVariantData(This).SMObject.Engine do
//! Global.anotherFunction(Args[0],Args[1],'test');
//! result := AnyTextFileToSynUnicode(Args[0]);
//!end;
procedure GetGlobal(out global: variant);
/// return the custom variant type identifier, i.e. SMVariantType.VarType
property VarType: word read VType;
/// returns the associated TSMObject instance
property SMObject: TSMObject read VObject;
/// returns the associated execution context
property cx: PJSContext read VObject.fcx;
/// returns the associated jsobject instance
property obj: PJSObject read VObject.fobj;
end;
{$A+}
/// to be used to catch Delphi exceptions inside JSNative function implementation
// - usage example:
// ! try
// ! doSomething()
// ! Result := JS_TRUE;
// ! except
// ! on E: Exception do begin
// ! JS_SET_RVAL(cx, vp, JSVAL_VOID);
// ! JSError(cx, E);
// ! Result := JS_FALSE;
// ! end;
procedure JSError(cx: PJSContext; aException: Exception; const aContext: RawByteString='');
/// convert a variant to a Java Script value
function VariantToJSVal(cx: PJSContext; const Value: Variant): jsval;
var
/// define the TSynLog class used for logging for all our SynSM related units
// - you may override it with TSQLLog, if available from mORMot.pas
// - since not all exceptions are handled specificaly by this unit, you
// may better use a common TSynLog class for the whole application or module
SynSMLog: TSynLogClass=TSynLog;
implementation
uses
Math;
const