paulbartrum / jurassic Goto Github PK
View Code? Open in Web Editor NEWA .NET library to parse and execute JavaScript code.
License: MIT License
A .NET library to parse and execute JavaScript code.
License: MIT License
If I define a Javascript object in C# code using the pattern shown here: Building an instance class, and I call JSON.stringify on an instance of that object, none of the expected fields are shown in the output. However, I am still able to access those fields in the same Javascript code (i.e I can use their values, etc). If I set an 'invisible' field from Javascript code and then perform the stringify, it appears in the output as I would expect.
C# class
using Jurassic;
using Jurassic.Library;
public class MyJsObjectConstructor : ClrFunction
{
public MyJsObjectConstructor(ScriptEngine js)
: base(js.Function.InstancePrototype, "MyJsObject", new MyJsObjectInstance(js.Object.InstancePrototype))
{
}
[JSConstructorFunction]
public MyJsObjectInstance Construct(int i)
{
return new MyJsObjectInstance(this.InstancePrototype, i);
}
}
public class MyJsObjectInstance : ObjectInstance
{
public MyJsObjectInstance(ObjectInstance prototype)
: base(prototype)
{
this.PopulateFields();
this.PopulateFunctions();
}
public MyJsObjectInstance(ObjectInstance prototype, int i)
: base(prototype)
{
}
[JSField]
public string myString = "This is my string";
}
Javascript
var myJsObject = new MyJsObject(); console.log(JSON.stringify(myJsObject)); myJsObject.myString = "Updated the string"; console.log(JSON.stringify(myJsObject));
Javascript console output
This is my string {} {"myString":"Updated the string"}
Hi,
I noticed the error message that occurs when using the instanceof
operator with an non-function right side operand always displays undefined
as its type.
For example:
var engine = new ScriptEngine();
try
{
engine.Execute("var x = ({} instanceof 123);");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
displays:
TypeError: The instanceof operator expected a function, but found 'undefined' instead
instead of:
TypeError: The instanceof operator expected a function, but found 'number' instead
I think this is due to the following lines in Compiler/Expressions/BinaryExpression.cs (line 716-720):
// Check the right-hand side is a function - if not, throw an exception.
generator.IsInstance(typeof(Library.FunctionInstance)); // (1)
generator.Duplicate(); // (2)
var endOfTypeCheck = generator.CreateLabel();
generator.BranchIfNotNull(endOfTypeCheck);
According to its documentation generator.IsInstance
produces null
when the object on the stack is not an instance of the specified type. The result is duplicated (I think for the Branch
call), and in the error message branch the current stack value (null
) is converted to undefined
.
I think lines (1) and (2) need to be swapped, to retain the correct object on the stack for producing the error message.
Thanks!
new ScriptEngine().Evaluate("new Date(new Date(969, 01, 01, 8, 17, 0)).getTime() == new Date(969, 01, 01, 8, 17, 0).getTime()")
is False
new ScriptEngine().Evaluate("new Date(new Date(970, 01, 01, 8, 17, 0)).getTime() == new Date(970, 01, 01, 8, 17, 0).getTime()")
is True
Hi,
I have made a C# class inheritance for javascript object types :
ObjectInstance
+- MyBaseObject
+- MyRealObject
+- MyRealObject
The [Js*] attributes of MyBaseObject
are not recognized by the PopulateFunctions()
, only the final type.
Is it possible to add a way to allow C# type inheritance for ObjectInstance derivation ?
Thanks.
ClrStaticTypeWrapper.PopulateMembers method is incorrectly including BindingFlags.DeclaredOnly to get the type members. The consequence it is now impossible to use members inherited by the type in JS.
Hi, im using jurassic to let users run scripts, but i need to add a timeout for them so they can't mess up my server with "while(true) ;"
Is there any callback or something to do that?
Or do I have to go the ugly route of Thread.Abort() ?? That'd be pretty sad :(
Could you please sign the assembly.
HI, there.
I am writing a hacking game somewhat based on your marvelous framework right now.
In that game (netbreakers is working title) users run a virtual terminal and can launch programs which are basically javascript files, memory file system is already setup and running and everything seems fine.
However, because of the nature of the game (i am using internal classes to run multiple engines and their instances) I am pretty damn certain that smart-arse (and inexperienced) users will hang the game just by creating while(true) {} (or for(;;) loops which will hang execution if they not contain some sort of break or sleep statement.
So, i have a question. Is there a quick way to force loop statements to pause from software side and not from the user side? say, some automatic analysis which can pass handle to resume execution to a c# code once a loop statement reaches it's looping point?
Hi,
I would like to know if I can replace the actual Global object with the one I have defined ?
I am thinking about the navigators window
object, that is the this
in global scope, and each window
functions are available as global functions.
Thanks.
Firstly, thanks for this project. Great work.
I see in your "Future plans" section you hope to get Mono compatibility working, have you had a look at https://github.com/Ormikon/Jurassic which apparently has made the necessary changes to make it work on Mono.
Changes didn't look to major. Would be great to see this merged.
During executing the following code:
var jsEngine = new ScriptEngine
{
EnableExposedClrTypes = true
};
jsEngine.SetGlobalValue("Math2", typeof(Math));
object result = jsEngine.Evaluate("Math2.PI");
Error occurs:
Unhandled Exception: System.NotSupportedException: Specified method is not supported.
at System.Reflection.MdFieldInfo.get_FieldHandle()
at Jurassic.Compiler.DynamicILGenerator.GetToken(FieldInfo field) in c:\temp\jurassic\Jurassic\Compiler\Emit\ILGenerator\DynamicILGenerator.cs:line 476
at Jurassic.Compiler.DynamicILGenerator.LoadField(FieldInfo field) in c:\temp\jurassic\Jurassic\Compiler\Emit\ILGenerator\DynamicILGenerator.cs:line 1885
at Jurassic.Compiler.FieldGetterBinder.GenerateStub(ILGenerator generator, Int32 argumentCount) in c:\temp\jurassic\Jurassic\Compiler\Binders\FieldBinders.cs:line 91
at Jurassic.Compiler.Binder.CreateDelegateCore(Int32 argumentCount) in c:\temp\jurassic\Jurassic\Compiler\Binders\Binder.cs:line 148
at Jurassic.Compiler.Binder.CreateDelegate(Int32 argumentCount) in c:\temp\jurassic\Jurassic\Compiler\Binders\Binder.cs:line 99
at Jurassic.Compiler.Binder.Call(ScriptEngine engine, Object thisObject, Object[] arguments) in c:\temp\jurassic\Jurassic\Compiler\Binders\Binder.cs:line 74
at Jurassic.Library.ClrFunction.CallLateBound(Object thisObject, Object[] arguments) in c:\temp\jurassic\Jurassic\Library\Function\ClrFunction.cs:line 179
at Jurassic.Library.PropertyAccessorValue.GetValue(ObjectInstance thisObject) in c:\temp\jurassic\Jurassic\Library\Object\PropertyAccessorValue.cs:line 52
at Jurassic.Library.ObjectInstance.InlineGetPropertyValue(String name, Int32& cachedIndex, Object& cacheKey) in c:\temp\jurassic\Jurassic\Library\Object\ObjectInstance.cs:line 298
at eval(ScriptEngine , Scope , Object )
at Jurassic.Compiler.EvalMethodGenerator.Execute() in c:\temp\jurassic\Jurassic\Compiler\MethodGenerator\EvalMethodGenerator.cs:line 119
at Jurassic.ScriptEngine.Evaluate(ScriptSource source) in c:\temp\jurassic\Jurassic\Core\ScriptEngine.cs:line 621
at Jurassic.ScriptEngine.Evaluate(String code) in c:\temp\jurassic\Jurassic\Core\ScriptEngine.cs:line 572
...
I am not sure if it is appropriate to ask questions here, please close if not.
I have a script as such:
namespace ScriptAPI
{
public struct SensorData
{
public float Distance;
public string Tag;
}
public class Sensor : ObjectInstance
{
public readonly string Name;
public SensorData Data { get; set; }
public Sensor(ScriptEngine engine, string name)
: base(engine)
{
PopulateFunctions();
Name = name;
}
[JSFunction(Name = "GetData")]
public SensorData GetData()
{
return Data;
}
}
}
The Sensor instance has a Data property that is set elsewhere in my C# code and is not meant to be accessible via Javascript, but rather the GetData() method should return the data. (I also have a GetDistance and GetTag shortcut method but left those out for simplicity)
This is the code I use to execute the Javascript:
// Prepare script engine
engine = new ScriptEngine()
{
EnableExposedClrTypes = true
};
// Data types for robot parts
engine.SetGlobalValue("SensorData", typeof(SensorData));
// Robot
engine.SetGlobalValue("robot", machine);
engine.Execute(Code);
The code
variable is the Javascript code and the machine
variable is another ObjectInstance that contains arrays of Sensor instances. As soon as I try to run some Javascript that run the GetData() method I get the following exception:
NotImplementedException: Unsupported type: ScriptAPI.SensorData
Jurassic.Compiler.PrimitiveTypeUtilities.ToPrimitiveType (System.Type) <IL 0x00104, 0x00631>
...
I am assuming this is related to the Supported Types section in the wiki, but I thought using engine.SetGlobalValue("SensorData", typeof(SensorData));
would allow me to use the C# type. I've also tried changing SensorData from a struct to a class but there's no difference.
Is there something I'm missing that would allow me to work past this, or do I have to store it as separate variables inside Sensor instead of a container struct?
Thank you in advance!
Jurassic compile Javascript to IL, has anyone tried to convert the generated IL to cSharp? Currently I am evaluating if Jurassic could be used to support the porting of WebGL-based codes to C#.
Step1 involves wrapping non-3D Canvas dependent javascript functions with Jarassic. This would allow testing of 3D canvas dependent codes in C#.
Step2 (it would be great) if it is possible to compile non-3D canvas javascript codes to DLL. and reference the DLLs with C#
Step3 test the feasibility if the DLL could be converted to Csharp using tool like ILSpy. This would complete the complete porting from Javascript to C#.
Has anyone attempted? Could this be done? There are plenty of libraries support porting of Csharp to Javascript. There are hardly any robust Javascript to CSharp converter. Perhaps I am wrong.
Hello,
i'm trying to build jurassic on my MacOS, but I get some errors with the projects that they can't find the jurassic library. And I get this error also:
/Library/Frameworks/Mono.framework/Versions/4.2.1/lib/mono/4.5/Microsoft.Common.targets: Warning: Unable to find framework corresponding to the target framework moniker '.NETFramework,Version=v3.5,Profile=Client'. Framework assembly references will be resolved from the GAC, which might not be the intended behavior. (Jurassic)
and a lot of:
..../workspace/jurassic/Jurassic/CSC: Error CS0518: The predefined type `System.Object' is not defined or imported (CS0518) (Jurassic)
I really would love to contribute in this project. I love this idea and use this framework on my Unity3D projects.
I'm having trouble creating a prototype chain of objects that I have defined on the .NET side. I'm creating my objects using the pattern described under the Building and instance class header.
Regardless of what other ObjectInstances I pass through the constructor and instance function constructors (on the .NET side of the code) I always end up with objects that have an Object prototype rather than the specific object that I wanted the new instance to 'prototype' from.
Is this currently possible to achieve with Jurassic, or am I going about this the wrong way?
Hi,
I have a big JS script which looks like
function executeScript(param1, param2) {
// It's about 1000 lines of code here
}
During the first run I compile the script and put FunctionInstance to a cache to be able to reuse it in future:
var engine = new ScriptEngine
{
EnableExposedClrTypes = true
};
var compiledScript = engine.Compile(new StringScriptSource(script));
compiledScript.Execute();
var executeScriptFunc = (FunctionInstance)engine.GetGlobalValue("executeScript");
cache.Add('SCRIPT_ID', executeScriptFunc);
After that I execute the function like this:
Trace.TraceInformation($"{DateTime.Now:T} => Before");
var result = executeScriptFunc.Call(null, "Some value 1", "Some value 2");
Trace.TraceInformation($"{DateTime.Now:T} => After");
On any local environment, the first run takes about 1.5 sec to start executing the first line of the script. Further runs don't have this lag at all
But if I deploy the same code to Azure cloud service, the first run takes about 5 minutes. It doesn't depend on how powerful Azure cloud service is. It's always about 5 minutes. Second and future runs are immediate.
For second and further runs I use cached FunctionInstance
Don't you guys know why it happens? Where could I look at to fix this?
Hi Paul,
I see few threads around this but still don't find the strong named signed binary at http://nuget.org. I am using this in a project that uses strong name signing and has lot of dependencies on other projects. Can you refresh the nuget package with a signed binary?
Hi, I see from here that I can pass a Jurassic.Library.ObjectInstance
(or a derived type) through the .SetGlobalValue
method to create a Javascript Object. However, using Jurassic 2.1.0 from NuGet I see that the constructor for ObjectInstance
is protected.
Would you be able to give me (or a link to) a quick example of creating an ObjectInstance
with a property? Cheers.
Hi,
sometimes it is needed to abort a long-running script, e.g. because the user accidently wrote an infinite loop in the Javascript. I found issues #41 and #7 which ask how to interrupt a script after a timeout.
AFAIK, Jurassic doesn't have a built-in functionaliy to set a timeout for script execution, e.g. in a way that Jint has with Options.TimeoutInterval(TimeSpan timeoutInterval)
, which will throw a TimeoutException
when executing a script is taking longer than the specified timeout. The timeout will only apply to script code, so e.g. if the script calls back a .NET method and the timeout has passed, the .NET method will complete before the TimeoutException is thrown by Jint.
However, for Jurassic, it should be possible to use Thread.Abort()
to abort script execution. I have written a wiki page that shows how to use a Helper class to set a timeout for script execution, while being able to mark .NET callbacks as "critcal code" in which a ThreadAbortException
should not occur.
See the wiki page here: (EDIT: Removed as the page has been adapted, see the reference link below)
Do you think it is possible to add such a wiki page to the Jurassic wiki? (Feel free to modify it in any way if needed.)
Thank you!
Hi,
I would like to know if the "Promise" object will be implemented soon...
My implementation seams odd, and does not really work like expected in some cases.
I tried to rewrite and add it directly in Jurassic by trying to follow your design pattern, but reading the ECMA spec is not so easy : I wrote the skeleton (with NotImplementedException), and a way to handle promises that resolve after script execution, but some things remains complex...
My attempt is here : https://github.com/MaitreDede/jurassic/commits/promise
Hi,
First I wanted to thank you for the amazing work. Keep the great work going.
Next, I wanted to point you to a solution that I have been using for exchanging data between C# and Javascript.
While using ObjectInstance is theoretically the best practice in many situations, regarding the dynamic nature of the Javascript objects, most of the times the easiest way of exchanging data between C# and Javascript is a simple Json serialization.
While, I usually use Json.NET for doing so, but .net's builtin JavaScriptSerializer class also does an acceptable job.
This is just some idea for making things a little easier. Maybe it comes in handy in some situation.
Thanks for the great work.
Hi,
I would like if is possible to create a build task (that can be added to project with a nuget package) to add some code generation like your project "Attribute Code Generation" does, so javascript C# objects can be written correctly, according to Jurassic coding philosophy.
Thanks.
Hi,
I want to call a function in an existing js library.
I did not find the way to do it
here is the library : http://turfjs.org/
As an example, I would like to call turf.distance from C#
Thank you
Check it.
Current version is not supporting .NET Core 1.0.
Is it Jquery supported?
For example if raw content of https://code.jquery.com/jquery-3.0.0.min.js is loaded and then additional custom functions which are using core Jquery function.
Should these functions work correct or no?
Hi,
when running the following code in Firefox, Chrome or other browsers:
new Map().set(1, 2).set(0, 9).set(1, 8).get(1);
the return value is 8
(as set(1, 8)
should overwrite the existing entry with value 2
).
However, on Jurassic it returns 2
.
MapInstance.Set()
looks like the following:
[JSInternalFunction(Name = "set")]
public MapInstance Set(object key, object value)
{
if (this.store.ContainsKey(key))
return this;
if (key is double && TypeUtilities.IsNegativeZero((double)key))
key = 0;
var node = this.list.AddLast(new KeyValuePair<object, object>(key, value));
this.store.Add(key, node);
return this;
}
So it checks whether the key already exists, but in that case does not set the new value.
ES2015 says at 23.1.3.9:
5. Repeat for each Record {[[key]], [[value]]} p that is an element of entries,
a. If p.[[key]] is not empty and SameValueZero(p.[[key]], key) is true, then
i. Set p.[[value]] to value.
ii. Return M.
So it seems the first if
branch misses the code to set the new value.
Thanks!
Hi,
Can it be possible to have direct access to internal byte[] buffer of TypedArrays in managed code ? So I can pass the buffer elsewhere without copying it.
Thanks
Hi,
I know that Engine is not thread safe, but I would like to know if I can call "Engine.Compile(ScriptSource)" in another thread, and then call "compiledScript.Execute()" on main thread without issues...
Thanks.
Hi,
I have followed the sample on the wiki.
In the C# code, I create a new Constructor, then call Construct()
to get an instance, that I expose with engine.SetGlobalValue("myobj", objInstance)
.
On my instance class, I have defined readonly properties :
[JSProperty(Name = "console", IsConfigurable = false, IsEnumerable = true)]
public ObjectInstance Console { get { return this.m_core.Console; } }
In this sample, the m_core
variable is set in the constructor called by the Construct
method.
When I run my script, when I try to read the property, the property is read from the prototype, not from the instance (the m_core
variable is null).
If I understand well, if I do a for(var prop in obj)
, when you make some checks against the property descriptor, but the downside is that it also retreive the property value, that in the case of the prototype is invalid/not readable.
What is the recommanded behavior : return null
in case of the prototype ? or does it need a ScriptEngine modification ?
Thanks.
Hi,
this is a follow-up issue to #59. The error messages for incorrect in
and new
operands always display undefined
as actual type:
'toString' in 5;
// TypeError: The in operator expected an object, but found 'undefined' instead
new (String('five'));
// TypeError: The new operator requires a function, found a 'undefined' instead
Thanks!
Hi,
first of all, thank you for this great library. It is a good alternative to Jint due to the Javascript code being compiled instead of being interpreted.
It seems Jurassic has a similar bug that Jint had with detecting if a property name is a valid array index - see sebastienros/jint#228.
Consider this Javascript code:
var arr = ['A', 'B'];
console.log(arr['1'] === arr['001']);
arr['002'] = 'C';
console.log(arr.length);
If you run this code in Edge, Firefox, Chrome or Node.js, it will output:
false
2
However, on Jurassic (current master branch), it will output:
true
3
This seems to be caused by ArrayInstance.ParseArrayIndex() not correctly checking if key is a canonical numeric string (a "String representation of a Number that would be produced by ToString").
ECMAScript 2015 says at 6.1.7 (The Object Type):
An integer index is a String-valued property key that is a canonical numeric String (see 7.1.16) and whose numeric value is either +0 or a positive integer ≤ 2^53−1. An array index is an integer index whose numeric value i is in the range +0 ≤ i < 2^32−1.
7.1.16 CanonicalNumericIndexString ( argument ):
The abstract operation CanonicalNumericIndexString returns argument converted to a numeric value if it is a String representation of a Number that would be produced by ToString, or the string "-0".
9.4.2 Array Exotic Objects (similar to ECMAScript 5.1 at 15.4):
NOTE: A String property name P is an array index if and only if ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal to 2^32−1.
This means that the string "002" shouldn't be a valid array index because
ToString(ToUint32('002')) != '002'
.
Thank you!
Hi,
I noticed you have started working on Promises, but did not yet implement handling the onFulfilled and onRejected handlers.
After reading through the Promises/A+ specification, I realized that these handlers are not invoked recursively, but instead postponed until no other script function is currently on the stack (which makes sense to avoid a StackOverflow when converting a loop to use promises).
For example. if you run:
console.log("1. Before resolve");
Promise.resolve(123).then(function() {
console.log("2. Inside onResolved handler");
});
console.log("3. After resolve");
then the program prints:
1. Before resolve
3. After resolve
2. Inside onResolved handler
Do you have a thought of how to implement this so that the onFulfilled and onRejected handler's execution is postponed until no further script function is on the stack?
My thought was to have the ScriptEngine declaring a list of FunctionInstances that are to be invoked later, and a boolean that specifies if a function of this script engine is already on the stack:
internal bool ScriptIsExecuting { get; set; }
internal List<FunctionInstance> ExecutionContextFunctionBuffer { get; } =
new List<FunctionInstance>();
Then, maybe the FunctionInstance's CallLateBound()
method could be changed to be not abstract but instead call an abstract CallLateBoundCore()
function (and subclasses override the latter one instead of CallLateBound()
), and the CallLateBound()
can check if it needs to call the buffered function instances:
public object CallLateBound(object thisObject, params object[] argumentValues)
{
bool firstExecution = !Engine.ScriptIsExecuting;
if (firstExecution)
Engine.ScriptIsExecuting = true;
try
{
var returnValue = CallLateBoundCore(thisObject, argumentValues);
// Check if we are the first script function on the stack from this script engine
// and we need to call buffered functions.
if (firstExecution)
{
while (Engine.ExecutionContextFunctionBuffer.Count > 0)
{
List<FunctionInstance> copy =
new List<FunctionInstance>(Engine.ExecutionContextFunctionBuffer);
Engine.ExecutionContextFunctionBuffer.Clear();
foreach (var func in copy)
func.Call(Null.Value); // TODO
}
}
return returnValue;
}
finally
{
if (firstExecution)
Engine.ScriptIsExecuting = false;
}
}
This way it would be ensured that if functions from multiple ScriptEngines are on the stack, the first function of each ScriptEngine on the stack will call the buffered FunctionInstances (this is currently how I do it in my code with a ES6-Promises Polyfill).
However, one issue is how to handle exceptions when FunctionInstances are buffered. For example, if you run this in Chrome:
Promise.resolve(123).then(function() {
console.log("Inside onResolved handler");
});
throw new Error("My Error");
it will print on the console:
Inside onResolved handler
Uncaught Error: My Error(…)
So it will still call the onFulfilled handler and then throw the exception. This could be implemented by catching exceptions in the CallLateBound e.g. with ExceptionDispatchInfo.Capture(ex)
to preserve the StackTrace when rethrowing it later, then running the buffered FunctionInstances and then rethrowing the exception. (I think the FunctionInstances shouldn't be run in a finally
block as it marks a protected region of code and cannot be aborted with Thread.Abort()
).
Thanks!
Hi,
I think it should be usefull to expose the Type property of TypedArray.
When using the latest nuget package (2.2.0):
`var scriptEngine = new ScriptEngine();
var hpath = Server.MapPath("~/scripts/handlebars-v2.0.0.js");
scriptEngine.ExecuteFile(hpath);
scriptEngine.Execute(@"var precompile = Handlebars.precompile;");
var html = "this is a sample template {{variable}}";
result = scriptEngine.CallGlobalFunction("precompile", html).ToString();`
The above code results in: Common Language Runtime detected an invalid program.
This happens only when running on ASP.NET Version:4.6.1055.0. Worked in previous asp.net versions.
I compiled from the latest source and the problem is now gone.
Maybe its a good idea to publish a new package?
thank you
I'm still diagnosing the issue but Jurrasic from asset store and from github both crash Unity3d.
Somewhere in the IL code generation.
I'm using Unity 5.4.0b20 (64-bit).
It's a pain, but the -unsafe and mono .net 2.0 is very limiting in JS choices.
Thanks.
Hi,
I saw that the target .NET Framework for Jurassic was changed from 3.5 to 4.0.
However, according to Microsoft .NET Framework Support Lifecycle Policy FAQ, Support for .NET 4.0, 4.5 and 4.5.1 has ended at January 12, 2016.
Therefore, I think it makes sence to not only switch to 4.0 but instead to 4.5 (or even 4.5.2).
Thanks!
Hi,
the Wiki page Supported types shows which JS type maps to which .NET type. The table contains an entry for JS type string
which maps to .NET type System.String
.
However, I found that sometimes Jurassic supplies an Jurassic.ConcatenatedString
instead of a regular System.String
when calling a function, if the JS string has been constructed using the +
operator. E.g.:
ScriptEngine engine = new ScriptEngine();
engine.SetGlobalFunction("myFunc", new Action<object>(arg =>
{
Console.WriteLine(arg.GetType() + ": " + arg.ToString());
}));
engine.Execute(@"
myFunc('abc');
myFunc('ab' + 'c');
");
displays:
System.String: abc
Jurassic.ConcatenatedString: abc
I think this could be added to the wiki page to avoid confusion. (When the .NET declares the parameter as string
Jurassic automatically converts it to a string, but not when declaring it as object
which is sometimes needed if the function should allow parameters of different types.)
Should I add this to the Wiki page?
Thanks!
This isn't an issue, so it can be closed as soon as you see this.
I just wasn't sure how else to message you about if there was a social media account to receive updates about Jurassic, or I just need to pop my head over here every once in a while.
Hi, from what I understand Jurassic creates IL code from the JavaScript code and then runs the IL. This creates some overhead in the first calls of functions. Of course, this is necessary if you want to run scripts at runtime from C#. However, I am interested in using some JavaScript libraries from C# and so the Javascript code would remain the same from one run to the next. So I was wondering, is there some way to save the generated IL as a dll to effectively remove the startup overhead? Cheers
Hi,
I think it can be a good idea to add the FileAPI objects : https://www.w3.org/TR/FileAPI/
Maybe in a extension assembly (and nuget package) ?
Hi,
I am using polyfills for some functions, and I wonder if there is a notable difference between a plain C# object and the polyfill.
Thanks.
I understand this can execute simple js scripts. Is it possible to run npm style modules? I have a lib that needs instantiating, and can then be used via numerous methods.. Some include Ajax or websocket usage behind the scenes executing call backs when done etc.
I can't figure out how to use it in Jurassic, i can't even get a reference to the instantiated object.
Reproduction:
var element = arrayList.Item[0];
or
var element = arrayList.get_item(0);
Observed behavior:
First code fails with error "No overload for method 'get_Item' takes 0 arguments".
Second code fails with error "'arrayList.get_Item' is not a function".
Expected behavior:
The first code will obviously not work because there are no indexed properties in JS. The second code should work.
Propoposed solution:
ClrStaticTypeWrapper.PopulateMembers method must be modified to call PropertyInfo.GetIndexParameters method to verify if property is indexed. If property is indexed, it should not be setup as JS property. This is the code I propose:
case MemberTypes.Property:
PropertyInfo property = (PropertyInfo)member;
if ( property.GetIndexParameters().Length > 0 )
{ // Indexed property. Do not setup as property.
continue;
}
...
Hi,
I have a script that I want to run under Mono, but I have a different behavior between Windows and Mono.
Javascript is equivalent to navigators' :
function draw()
{
}
window.requestAnimationFrame(draw);
On Microsoft Framework on Windows, works fine.
On Mono :
System.MissingMethodException: Method 'Array.Empty' not found.
It happens in Binder.cs
, in method public object Call(ScriptEngine engine, object thisObject, params object[] arguments)
when trying to execute return binderDelegate(engine, thisObject, arguments);
Running on Mono : 4.2.3 (Stable 4.2.3.4/832de4b)
Environment.Version : 4.0.30319.17020
Partial stacktrace :
System.MissingMethodException: Method 'Array.Empty' not found.
at (wrapper dynamic-method) Jurassic.Compiler.JSBinder:binder_for_RpiMono.JsObjects.WindowInstance.RequestAnimationFrame (Jurassic.ScriptEngine,object,object[])
at Jurassic.Compiler.Binder.Call (Jurassic.ScriptEngine engine, System.Object thisObject, System.Object[] arguments) <0x6d65ec50 + 0x00043> in <filename unknown>:0
at Jurassic.Library.ClrFunction.CallLateBound (System.Object thisObject, System.Object[] arguments) <0x6d65e8c0 + 0x00133> in <filename unknown>:0
at Jurassic.Library.FunctionInstance.CallWithStackTrace (System.String path, System.String function, Int32 line, System.Object thisObject, System.Object[] argumentValues) <0x6d657a60 + 0x00067> in <filename unknown>:0
at JavaScriptClass375.anonymous (Jurassic.ScriptEngine scriptEngine, Jurassic.Compiler.Scope scope, System.Object thisValue, Jurassic.Library.FunctionInstance , System.Object[] ) <0x6cbcda60 + 0x03b1b> in <filename unknown>:0
at (wrapper delegate-invoke) <Module>:invoke_object_ScriptEngine_Scope_object_FunctionInstance_object[] (Jurassic.ScriptEngine,Jurassic.Compiler.Scope,object,Jurassic.Library.FunctionInstance,object[])
at Jurassic.Library.UserDefinedFunction.CallLateBound (System.Object thisObject, System.Object[] argumentValues) <0x6d657c10 + 0x00057> in <filename unknown>:0
at Jurassic.Library.FunctionInstance.CallWithStackTrace (System.String path, System.String function, Int32 line, System.Object thisObject, System.Object[] argumentValues) <0x6d657a60 + 0x00067> in <filename unknown>:0
at JavaScriptClass370.anonymous (Jurassic.ScriptEngine scriptEngine, Jurassic.Compiler.Scope scope, System.Object thisValue, Jurassic.Library.FunctionInstance , System.Object[] ) <0x6cb06000 + 0x1c72f> in <filename unknown>:0
at (wrapper delegate-invoke) <Module>:invoke_object_ScriptEngine_Scope_object_FunctionInstance_object[] (Jurassic.ScriptEngine,Jurassic.Compiler.Scope,object,Jurassic.Library.FunctionInstance,object[])
at Jurassic.Library.UserDefinedFunction.CallLateBound (System.Object thisObject, System.Object[] argumentValues) <0x6d657c10 + 0x00057> in <filename unknown>:0
at Jurassic.Library.FunctionInstance.CallWithStackTrace (System.String path, System.String function, Int32 line, System.Object thisObject, System.Object[] argumentValues) <0x6d657a60 + 0x00067> in <filename unknown>:0
at JavaScriptClass369.global_sampleCube_js (Jurassic.ScriptEngine scriptEngine, Jurassic.Compiler.Scope scope, System.Object thisValue) <0x6cbca670 + 0x0015b> in <filename unknown>:0
at Jurassic.Compiler.GlobalMethodGenerator.Execute () <0x6d656848 + 0x00083> in <filename unknown>:0
at Jurassic.CompiledScript.Execute () <0x6d6567f8 + 0x0001f> in <filename unknown>:0
at Jurassic.ScriptEngine.Execute (Jurassic.ScriptSource source) <0x6e431228 + 0x0006f> in <filename unknown>:0
SpiderMonkey and V8 run close to native speeds by tracking the flow of variable types throughout the system and essentially being able to directly call a method/ read a field rather than performing a prototypical lookup.
We're currently in the process of implementing this with first looks being very encouraging - the changes are very widespread though so more updates and a repo will be coming shortly for you guys to play with. I'm personally focusing on getting Route9.js to function at a respectable framerate which also requires full typed array support, so I'll likely be finishing off that as well unless someone else gets there first.
The current change set that has occurred in order to make this possible is roughly as follows:
But it did come at the cost of a few things at the moment:
The end result is a totally different beast which I've been nicknaming Nitrassic (a combo of Nitro, our own home-grown .NET JS engine which runs fast but has very limited spec coverage). I wanted to drop this here so we could a) thank you guys, Jurassic rocks! and b) open up a discussion on the type tracking system as there are probably thoughts floating around on the implementation. The current approach that is in the process of being implemented is roughly summarized as follows:
Type introduction into the system.
Types are introduced into the system in a set number of ways. These points essentially provide the 'roots' of the type flow graph:
The premise
We make the assumption that a variables type is always known by the compiler. There are three major unknowns and those are function arguments, new object properties and global variables - everywhere else the types can be known - so we must essentially find a way of tracking their types too.
Function arguments
Fortunately this one's relatively simple - we simply compile a method when it gets called. Based on our assumption, the call site always knows the types of the variables its passing as arguments. Hoisted variables will all be known too. Thus, we can know exactly which types are in use at a given call site and compile the method accordingly. A method can potentially be compiled multiple times if it's being called in a few different ways.
Object properties
We need to know all properties that are being added to a given object prototype. As we're compiling at the function scope level, then we essentially have to watch for new properties being added either in this function scope or any function scope that is being called from. In order to do this, a function tracks the side effects it has on its arguments whilst its being compiled. The call site of that function can then be aware of any new properties that may be getting added and their type too.
Global variables
Globals are awkward because 'anyone' can change them. Whenever a global is set, we check to see if the type being set matches that of the global. If they are different, i.e. a global is being set to some different type, then the global is said to have 'collapsed' and has its type set to typeof(object). However, functions may have been compiled that used it as its previous type. So, each global tracks the functions which use it. If that global collapses, it then simply requests that those functions recompile. When a function attempts to compile with a collapsed global, it emits runtime type checking. Interestingly, this is the only situation where runtime type checking actually occurs - essentially only if someone sets a global to more than one type.
Thoughts/ opinions/ 'you're crazy!'s/ ideas all welcome!
I have a few unique issues while using Jurassic that have prompted me to ask, is there a way to perform some type of Include or reference within the JS source i am feeding in?
We know we could probably duck-tape it and simply append the script in at run-time but we are hoping to avoid skewing line numbers that way.
Is there already such support?
Hi,
as ObjectInstance
s etc. have a reference to the ScriptEngine
to which they belong, I assume one shouldn't take an ObjectInstance originating from one ScriptEngine and set it as property on another ScriptEngine. Is this correct?
In that case, I think the catch clauses for JavaScriptExceptions
need to be adjusted. Currently, it is possible that one ScriptEngine throws a JavaScriptException, and if called recursively by another ScriptEngine, its JavaScript code may catch the exception. If that happens, it probably was unintended that a "foreign" JavaScriptException is catched in JS code.
We use Jurassic for extending an application with JavaScripts, where the transitions on the stack can be Native (.NET) -> JS (event handler or callback which calls a FunctionInstance
), JS->Native (Script calls an API) and again Native -> JS (another callback).
Consider this simplified example:
EventProducer:
class EventProducer
{
public event Action MyEvent;
protected void OnMyEvent() => MyEvent?.Invoke();
public void Run()
{
// Run a script which raises the event.
string script = @"
try {
api.raiseEvent();
} catch (e) {
console.log('e.myGlobalVariable: ' + e.myGlobalVariable);
}
";
ScriptEngine engine = new ScriptEngine();
//engine.Compile
engine.ForceStrictMode = true;
engine.SetGlobalValue("console", new FirebugConsole(engine));
engine.SetGlobalValue("api", new Api(engine, this));
engine.Execute(script);
}
private class Api : ObjectInstance
{
private EventProducer producer;
public Api(ScriptEngine engine, EventProducer producer)
: base(engine)
{
this.producer = producer;
PopulateFunctions();
}
[JSFunction(Name = "raiseEvent")]
public void RaiseEvent()
{
producer.OnMyEvent();
}
}
}
EventConsumer:
class EventConsumer
{
private EventProducer producer;
public EventConsumer(EventProducer producer)
{
this.producer = producer;
}
public void Run()
{
// Run a script which adds an event handler.
string script = @"
var myGlobalVariable = 'Hi, this is a global variable.';
(function (globalObject) {
api.addEventHandler(function () {
// Throw the global object
throw globalObject;
});
})(this);
";
ScriptEngine engine = new ScriptEngine();
engine.ForceStrictMode = true;
engine.SetGlobalValue("console", new FirebugConsole(engine));
engine.SetGlobalValue("api", new Api(engine, this));
engine.Execute(script);
}
private class Api : ObjectInstance
{
private EventConsumer consumer;
public Api(ScriptEngine engine, EventConsumer consumer)
: base(engine)
{
this.consumer = consumer;
PopulateFunctions();
}
[JSFunction(Name = "addEventHandler")]
public void AddEventHandler(FunctionInstance instance)
{
consumer.producer.MyEvent += () => instance.Call(null);
}
}
Main Program:
var producer = new EventProducer();
var consumer = new EventConsumer(producer);
consumer.Run();
producer.Run();
In that example, EventProducer
has a event which is raised by JS code. EventConsumer
runs JS code which adds an event handler to the producer's event, and in that handler, throws a JS object.
Both EventConsumer and EventProducer may not know that each other will also run JS code. However, the JS code in the EventProducer can use a catch clause to catch the JavaScriptException and get access to a foreign ScriptEngine's objects (the global object in that case).
To change this, I think the clauses like catch (JavaScriptException ex)
would need to be adjusted to catch (JavaScriptException ex) when (ex.ScriptEngine == this.ScriptEngine)
or something similar, if this is possible (e.g. in the TryCatchFinallyStatement
class, however I don't know how to do this correctly).
Thank you!
Hi,
Jurassic is a really great library, and in our product at work where we use it to allow the user to program the application with JavaScripts/TypeScripts (using the Monaco Editor), gradually more advanced use cases appear while building the interface between JavaScript and .NET.
The wiki page Exposing a .NET class to JavaScript shows an example how to create a constructor and instance for static and instance functions.
However as I understand it, when the Random
function is called as a constructor using new Random(...)
, its JSConstructorFunction
always creates and returns a new RandomInstance
object. This means the class cannot be subclassed in JavaScript because in that case the constructor function would need to be run with an existing ObjectInstance
that has a prototype of the subclass.
For example, consider the following TypeScript that tries to subclass the existing Random
class that we exposed from .NET, using a new EnhancedRandom
class:
// Declare the existing class
declare class Random {
constructor(seed: number);
nextDouble(): number;
}
class EnhancedRandom extends Random {
constructor(seed: number) {
super(seed);
}
nextInt(maxValue: number) {
return Math.floor(this.nextDouble() * maxValue);
}
}
let myRandom = new Random(1000);
let enhanced = new EnhancedRandom(1000);
console.log(myRandom.nextDouble());
console.log(myRandom.nextDouble());
console.log(enhanced.nextDouble());
console.log(enhanced.nextInt(5000));
When compiling down to ECMAScript 5, the following JS file is produced by TS:
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var EnhancedRandom = (function (_super) {
__extends(EnhancedRandom, _super);
function EnhancedRandom(seed) {
_super.call(this, seed);
}
EnhancedRandom.prototype.nextInt = function (maxValue) {
return Math.floor(this.nextDouble() * maxValue);
};
return EnhancedRandom;
}(Random));
var myRandom = new Random(1000);
var enhanced = new EnhancedRandom(1000);
console.log(myRandom.nextDouble());
console.log(myRandom.nextDouble());
console.log(enhanced.nextDouble());
console.log(enhanced.nextInt(5000));
When subclassing the Random
class, a new function EnhancedRandom
is created and its prototype
property is set to a new object that has a prototype of Random.prototype
. When the script calls new EnhancedRandom(1000)
, a new object is created with a prototype of EnhancedRandom
, and the EnhancedRandom
function is called with the 'this' object being the just created new object.
Now, when running the JS code, the call new EnhancedRandom(1000)
produces an InvalidCastException:
System.InvalidCastException: Unable to cast object of type 'SubclassExample.RandomConstructor' to type '<>c'.
at binder_for_Jurassic.Library.ClrFunction+<>c.<.ctor>b__3_0(ScriptEngine , Object , Object[] )
at Jurassic.Compiler.Binder.Call(ScriptEngine engine, Object thisObject, Object[] arguments) in C:\Users\Name\Desktop\JurassicTest\jurassic\Jurassic\Compiler\Binders\Binder.cs:line 72
at Jurassic.Library.ClrFunction.CallLateBound(Object thisObject, Object[] arguments) in C:\Users\Name\Desktop\JurassicTest\jurassic\Jurassic\Library\Function\ClrFunction.cs:line 178
(...)
(Maybe an JavaScriptException
should be thrown in this case so the JS code has a change to catch the exception.)
Now, in order to make Random
subclassable, AFAIK what needs to be changed is:
RandomConstructor
to not create and return a new object, but instead work with the supplied, existing ObjectInstance
.RandomInstance
class cannot be used as instance class anymore (containing the private Random random;
field as shown in the Wiki page), because we may have to work with an existing ObjectInstance
.To fix 2), one can use a ConditionalWeakTable
to attach an extension object to existing ObjectInstance
s where the private data can be stored.
To fix 1), I think one cannot use the ClrFunction
class because I did not found a way to have a declared .NET function be called with the correct 'this' object (it seems that the 'this' object is always the FunctionInstance
, not the new created object), and it overwrites FunctionInstance.ConstructLateBound()
to do its own logic, whereas I would need the logic of the FunctionInstance
which is the JavaScript logic:
FunctionInstance.InstancePrototype
ObjectInstance
, the value of the new ...
expression is the returned object, otherwise it is the previously created object.The only way I found to do this is to directly extend the FunctionInstance
class in the following way:
using System;
using System.Runtime.CompilerServices;
using Jurassic;
using Jurassic.Library;
namespace SubclassExample
{
public class RandomConstructor : FunctionInstance
{
private const int typicalArgumentsLength = 1;
private ScriptEngine engine;
private RandomInstance instancePrototype;
public RandomConstructor(ScriptEngine engine, string name)
: base(engine.Function.InstancePrototype)
{
this.engine = engine;
this.instancePrototype = new RandomInstance(engine.Object.InstancePrototype);
// Set function properties.
this.DefineProperty("name", new PropertyDescriptor(
name, PropertyAttributes.Configurable), true);
this.DefineProperty("length", new PropertyDescriptor(
typicalArgumentsLength, PropertyAttributes.Configurable), true);
this.DefineProperty("prototype", new PropertyDescriptor(
this.instancePrototype, PropertyAttributes.Writable), true);
this.instancePrototype.DefineProperty("constructor", new PropertyDescriptor(
this, PropertyAttributes.Configurable | PropertyAttributes.Writable), true);
PopulateFunctions();
}
private void CheckPrototype(object obj)
{
// Check if the object's prototype is correct.
ObjectInstance prototype = obj as ObjectInstance;
while (true)
{
prototype = prototype?.Prototype;
if (prototype == null)
throw new JavaScriptException(this.Engine, ErrorType.TypeError,
"Cannot call a class as a function");
if (prototype == this.instancePrototype)
break;
}
}
public override object CallLateBound(
object thisObject, params object[] argumentValues)
{
CheckPrototype(thisObject);
int? seed = null;
if (argumentValues.Length > 0)
{
var seedArg = argumentValues[0];
if (seedArg is int)
seed = (int)seedArg;
else if (seedArg is double)
seed = (int)(double)seedArg;
}
if (!seed.HasValue)
throw new JavaScriptException(this.Engine, ErrorType.TypeError,
"Invalid argument");
ConstructCore(thisObject as ObjectInstance, seed.Value);
// Don't return a value. When the function is called with the "new" keyword, the
// value of the "new ...()" expression will be the previously created object.
return Undefined.Value;
}
/// <summary>
/// Constructs a new <see cref="RandomInstance"/> from .NET code.
/// </summary>
/// <param name="seed"></param>
public ObjectInstance ConstructNew(int seed)
{
// Create a new object with our prototype.
ObjectInstance newObject = ObjectConstructor.Create(
this.engine, this.instancePrototype);
ConstructCore(newObject, seed);
return newObject;
}
/// <summary>
/// Runs the constructor on an existing object.
/// </summary>
/// <param name="thisObject"></param>
/// <param name="seed"></param>
protected virtual void ConstructCore(ObjectInstance thisObject, int seed)
{
// Construct the instance.
RandomInstance.ConstructInstance(thisObject, seed);
}
}
public class RandomInstance : ObjectInstance
{
private static ConditionalWeakTable<ObjectInstance, RandomInstanceExtension>
instanceTable =
new ConditionalWeakTable<ObjectInstance, RandomInstanceExtension>();
/// <summary>
/// Creates the instance prototype for the constructor function.
/// </summary>
/// <param name="prototype"></param>
public RandomInstance(ObjectInstance prototype)
: base(prototype)
{
// Construct the Prototype.
this.PopulateFunctions();
}
public static void ConstructInstance(ObjectInstance obj, int seed)
{
// Run the instance constructor code.
RandomInstanceExtension instance;
if (instanceTable.TryGetValue(obj, out instance))
throw new JavaScriptException(obj.Engine, ErrorType.Error,
"Constructor has already been called");
instance = new RandomInstanceExtension()
{
Random = new Random(seed)
};
instanceTable.Add(obj, instance);
}
private static RandomInstanceExtension GetInstanceExtension(
ObjectInstance thisObj)
{
RandomInstanceExtension instance;
if (!instanceTable.TryGetValue(thisObj, out instance))
throw new JavaScriptException(thisObj.Engine, ErrorType.TypeError,
"Method is not generic");
return instance;
}
[JSFunction(Name = "nextDouble", Flags = JSFunctionFlags.HasThisObject)]
public static double NextDouble(ObjectInstance thisObject)
{
RandomInstanceExtension instance = GetInstanceExtension(thisObject);
return instance.Random.NextDouble();
}
private class RandomInstanceExtension
{
public Random Random { get; set; }
}
}
}
In the above code, .NET code can still construct a new RandomInstance with RandomConstructor.ConstructNew()
.
Running the script then produces the result:
0.15155745910087481
0.2359429496507826
0.15155745910087481
1179
Is this the correct way to expose build native classes that are subclassable?
If yes, I think this could be added to the Wiki page.
Thanks!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.