using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using OriginalJsException = JavaScriptEngineSwitcher.ChakraCore.JsRt.JsException;
using JavaScriptEngineSwitcher.Core;
using JavaScriptEngineSwitcher.Core.Utilities;
using CoreStrings = JavaScriptEngineSwitcher.Core.Resources.Strings;
using JavaScriptEngineSwitcher.ChakraCore.Helpers;
using JavaScriptEngineSwitcher.ChakraCore.JsRt;
using JavaScriptEngineSwitcher.ChakraCore.Resources;
namespace JavaScriptEngineSwitcher.ChakraCore
{
///
/// Adapter for the ChakraCore JS engine
///
public sealed class ChakraCoreJsEngine : JsEngineBase
{
///
/// Name of JS engine
///
public const string EngineName = "ChakraCoreJsEngine";
///
/// Version of original JS engine
///
private const string EngineVersion = "1.11.3";
///
/// Instance of JS runtime
///
private JsRuntime _jsRuntime;
///
/// Instance of JS context
///
private JsContext _jsContext;
///
/// JS source context
///
private JsSourceContext _jsSourceContext = JsSourceContext.FromIntPtr(IntPtr.Zero);
///
/// Set of external objects
///
private HashSet _externalObjects = new HashSet();
///
/// Callback for finalization of external object
///
private JsObjectFinalizeCallback _externalObjectFinalizeCallback;
///
/// Callback for continuation of promise
///
private JsPromiseContinuationCallback _promiseContinuationCallback;
///
/// List of native function callbacks
///
private HashSet _nativeFunctions = new HashSet();
///
/// Script dispatcher
///
private ScriptDispatcher _dispatcher;
///
/// Unique document name manager
///
private readonly UniqueDocumentNameManager _documentNameManager =
new UniqueDocumentNameManager(DefaultDocumentName);
#if !NETSTANDARD1_3
///
/// Synchronizer of JS engine initialization
///
private static readonly object _initializationSynchronizer = new object();
///
/// Flag indicating whether the JS engine is initialized
///
private static bool _initialized;
#endif
///
/// Gets a name of JS engine
///
public override string Name
{
get { return EngineName; }
}
///
/// Gets a version of original JS engine
///
public override string Version
{
get { return EngineVersion; }
}
///
/// Gets a value that indicates if the JS engine supports garbage collection
///
public override bool SupportsGarbageCollection
{
get { return true; }
}
///
/// Constructs an instance of adapter for the ChakraCore JS engine
///
public ChakraCoreJsEngine()
: this(new ChakraCoreSettings())
{ }
///
/// Constructs an instance of adapter for the ChakraCore JS engine
///
/// Settings of the ChakraCore JS engine
public ChakraCoreJsEngine(ChakraCoreSettings settings)
{
#if !NETSTANDARD1_3
Initialize();
#endif
ChakraCoreSettings chakraCoreSettings = settings ?? new ChakraCoreSettings();
JsRuntimeAttributes attributes = JsRuntimeAttributes.None;
if (chakraCoreSettings.DisableBackgroundWork)
{
attributes |= JsRuntimeAttributes.DisableBackgroundWork;
}
if (chakraCoreSettings.DisableEval)
{
attributes |= JsRuntimeAttributes.DisableEval;
}
if (chakraCoreSettings.DisableExecutablePageAllocation)
{
attributes |= JsRuntimeAttributes.DisableExecutablePageAllocation;
}
if (chakraCoreSettings.DisableNativeCodeGeneration)
{
attributes |= JsRuntimeAttributes.DisableNativeCodeGeneration;
}
if (chakraCoreSettings.DisableFatalOnOOM)
{
attributes |= JsRuntimeAttributes.DisableFatalOnOOM;
}
if (chakraCoreSettings.EnableExperimentalFeatures)
{
attributes |= JsRuntimeAttributes.EnableExperimentalFeatures;
}
_dispatcher = new ScriptDispatcher();
_externalObjectFinalizeCallback = ExternalObjectFinalizeCallback;
_promiseContinuationCallback = PromiseContinuationCallback;
try
{
_dispatcher.Invoke(() =>
{
_jsRuntime = JsRuntime.Create(attributes, null);
_jsRuntime.MemoryLimit = settings.MemoryLimit;
_jsContext = _jsRuntime.CreateContext();
if (_jsContext.IsValid)
{
_jsContext.AddRef();
}
});
}
catch (Exception e)
{
throw new JsEngineLoadException(
string.Format(CoreStrings.Runtime_JsEngineNotLoaded, EngineName, e.Message),
EngineName, EngineVersion, e);
}
finally
{
if (!_jsContext.IsValid)
{
Dispose();
}
}
}
///
/// Destructs an instance of adapter for the ChakraCore JS engine
///
~ChakraCoreJsEngine()
{
Dispose(false);
}
#if !NETSTANDARD1_3
///
/// Initializes a JS engine
///
private static void Initialize()
{
if (_initialized)
{
return;
}
lock (_initializationSynchronizer)
{
if (_initialized)
{
return;
}
if (Utils.IsWindows())
{
try
{
AssemblyResolver.Initialize();
}
catch (InvalidOperationException e)
{
throw new JsEngineLoadException(
string.Format(CoreStrings.Runtime_JsEngineNotLoaded, EngineName, e.Message),
EngineName, EngineVersion, e);
}
}
_initialized = true;
}
}
#endif
///
/// Adds a reference to the value
///
/// The value
private static void AddReferenceToValue(JsValue value)
{
if (CanHaveReferences(value))
{
value.AddRef();
}
}
///
/// Removes a reference to the value
///
/// The value
private static void RemoveReferenceToValue(JsValue value)
{
if (CanHaveReferences(value))
{
value.Release();
}
}
///
/// Checks whether the value can have references
///
/// The value
/// Result of check (true - may have; false - may not have)
private static bool CanHaveReferences(JsValue value)
{
JsValueType valueType = value.ValueType;
switch (valueType)
{
case JsValueType.Null:
case JsValueType.Undefined:
case JsValueType.Boolean:
return false;
default:
return true;
}
}
///
/// Creates a instance of JS scope
///
/// Instance of JS scope
private JsScope CreateJsScope()
{
var jsScope = new JsScope(_jsContext);
JsRuntime.SetPromiseContinuationCallback(_promiseContinuationCallback, IntPtr.Zero);
return jsScope;
}
///
/// The promise continuation callback
///
/// The task, represented as a JavaScript function
/// The data argument to be passed to the callback
private static void PromiseContinuationCallback(JsValue task, IntPtr callbackState)
{
task.AddRef();
try
{
task.CallFunction(JsValue.GlobalObject);
}
finally
{
task.Release();
}
}
#region Mapping
///
/// Makes a mapping of value from the host type to a script type
///
/// The source value
/// The mapped value
private JsValue MapToScriptType(object value)
{
if (value == null)
{
return JsValue.Null;
}
if (value is Undefined)
{
return JsValue.Undefined;
}
TypeCode typeCode = value.GetType().GetTypeCode();
switch (typeCode)
{
case TypeCode.Boolean:
return (bool)value ? JsValue.True : JsValue.False;
case TypeCode.SByte:
case TypeCode.Byte:
case TypeCode.Int16:
case TypeCode.UInt16:
case TypeCode.Int32:
case TypeCode.UInt32:
case TypeCode.Int64:
case TypeCode.UInt64:
return JsValue.FromInt32(Convert.ToInt32(value));
case TypeCode.Single:
case TypeCode.Double:
case TypeCode.Decimal:
return JsValue.FromDouble(Convert.ToDouble(value));
case TypeCode.Char:
case TypeCode.String:
return JsValue.FromString((string)value);
default:
return FromObject(value);
}
}
///
/// Makes a mapping of array items from the host type to a script type
///
/// The source array
/// The mapped array
private JsValue[] MapToScriptType(object[] args)
{
return args.Select(MapToScriptType).ToArray();
}
///
/// Makes a mapping of value from the script type to a host type
///
/// The source value
/// The mapped value
private object MapToHostType(JsValue value)
{
JsValueType valueType = value.ValueType;
JsValue processedValue;
object result;
switch (valueType)
{
case JsValueType.Null:
result = null;
break;
case JsValueType.Undefined:
result = Undefined.Value;
break;
case JsValueType.Boolean:
processedValue = value.ConvertToBoolean();
result = processedValue.ToBoolean();
break;
case JsValueType.Number:
processedValue = value.ConvertToNumber();
result = NumericHelpers.CastDoubleValueToCorrectType(processedValue.ToDouble());
break;
case JsValueType.String:
processedValue = value.ConvertToString();
result = processedValue.ToString();
break;
case JsValueType.Object:
case JsValueType.Function:
case JsValueType.Error:
case JsValueType.Array:
case JsValueType.Symbol:
case JsValueType.ArrayBuffer:
case JsValueType.TypedArray:
case JsValueType.DataView:
result = ToObject(value);
break;
default:
throw new ArgumentOutOfRangeException();
}
return result;
}
///
/// Makes a mapping of array items from the script type to a host type
///
/// The source array
/// The mapped array
private object[] MapToHostType(JsValue[] args)
{
return args.Select(MapToHostType).ToArray();
}
private JsValue FromObject(object value)
{
var del = value as Delegate;
JsValue objValue = del != null ? CreateFunctionFromDelegate(del) : CreateExternalObjectFromObject(value);
return objValue;
}
private object ToObject(JsValue value)
{
object result = value.HasExternalData ?
GCHandle.FromIntPtr(value.ExternalData).Target : value.ConvertToObject();
return result;
}
private JsValue CreateExternalObjectFromObject(object value)
{
GCHandle handle = GCHandle.Alloc(value);
_externalObjects.Add(value);
JsValue objValue = JsValue.CreateExternalObject(
GCHandle.ToIntPtr(handle), _externalObjectFinalizeCallback);
Type type = value.GetType();
ProjectFields(objValue, type, true);
ProjectProperties(objValue, type, true);
ProjectMethods(objValue, type, true);
FreezeObject(objValue);
return objValue;
}
private void ExternalObjectFinalizeCallback(IntPtr data)
{
if (data == IntPtr.Zero)
{
return;
}
GCHandle handle = GCHandle.FromIntPtr(data);
object obj = handle.Target;
if (obj != null && _externalObjects != null)
{
_externalObjects.Remove(obj);
}
handle.Free();
}
private JsValue CreateObjectFromType(Type type)
{
JsValue typeValue = CreateConstructor(type);
ProjectFields(typeValue, type, false);
ProjectProperties(typeValue, type, false);
ProjectMethods(typeValue, type, false);
FreezeObject(typeValue);
return typeValue;
}
private void FreezeObject(JsValue objValue)
{
JsValue freezeMethodValue = JsValue.GlobalObject
.GetProperty("Object")
.GetProperty("freeze")
;
freezeMethodValue.CallFunction(objValue);
}
private JsValue CreateFunctionFromDelegate(Delegate value)
{
JsNativeFunction nativeFunction = (callee, isConstructCall, args, argCount, callbackData) =>
{
object[] processedArgs = MapToHostType(args.Skip(1).ToArray());
ParameterInfo[] parameters = value.GetMethodInfo().GetParameters();
JsValue undefinedValue = JsValue.Undefined;
ReflectionHelpers.FixArgumentTypes(ref processedArgs, parameters);
object result;
try
{
result = value.DynamicInvoke(processedArgs);
}
catch (Exception e)
{
JsValue errorValue = JsErrorHelpers.CreateError(
string.Format(Strings.Runtime_HostDelegateInvocationFailed, e.Message));
JsErrorHelpers.SetException(errorValue);
return undefinedValue;
}
JsValue resultValue = MapToScriptType(result);
return resultValue;
};
_nativeFunctions.Add(nativeFunction);
JsValue functionValue = JsValue.CreateFunction(nativeFunction);
return functionValue;
}
private JsValue CreateConstructor(Type type)
{
TypeInfo typeInfo = type.GetTypeInfo();
string typeName = type.FullName;
BindingFlags defaultBindingFlags = ReflectionHelpers.GetDefaultBindingFlags(true);
ConstructorInfo[] constructors = type.GetConstructors(defaultBindingFlags);
JsNativeFunction nativeFunction = (callee, isConstructCall, args, argCount, callbackData) =>
{
JsValue resultValue;
JsValue undefinedValue = JsValue.Undefined;
object[] processedArgs = MapToHostType(args.Skip(1).ToArray());
object result;
if (processedArgs.Length == 0 && typeInfo.IsValueType)
{
result = Activator.CreateInstance(type);
resultValue = MapToScriptType(result);
return resultValue;
}
if (constructors.Length == 0)
{
JsValue errorValue = JsErrorHelpers.CreateError(
string.Format(Strings.Runtime_HostTypeConstructorNotFound, typeName));
JsErrorHelpers.SetException(errorValue);
return undefinedValue;
}
var bestFitConstructor = (ConstructorInfo)ReflectionHelpers.GetBestFitMethod(
constructors, processedArgs);
if (bestFitConstructor == null)
{
JsValue errorValue = JsErrorHelpers.CreateReferenceError(
string.Format(Strings.Runtime_SuitableConstructorOfHostTypeNotFound, typeName));
JsErrorHelpers.SetException(errorValue);
return undefinedValue;
}
ReflectionHelpers.FixArgumentTypes(ref processedArgs, bestFitConstructor.GetParameters());
try
{
result = bestFitConstructor.Invoke(processedArgs);
}
catch (Exception e)
{
JsValue errorValue = JsErrorHelpers.CreateError(
string.Format(Strings.Runtime_HostTypeConstructorInvocationFailed, typeName, e.Message));
JsErrorHelpers.SetException(errorValue);
return undefinedValue;
}
resultValue = MapToScriptType(result);
return resultValue;
};
_nativeFunctions.Add(nativeFunction);
JsValue constructorValue = JsValue.CreateFunction(nativeFunction);
return constructorValue;
}
private void ProjectFields(JsValue target, Type type, bool instance)
{
string typeName = type.FullName;
BindingFlags defaultBindingFlags = ReflectionHelpers.GetDefaultBindingFlags(instance);
FieldInfo[] fields = type.GetFields(defaultBindingFlags);
foreach (FieldInfo field in fields)
{
string fieldName = field.Name;
JsValue descriptorValue = JsValue.CreateObject();
descriptorValue.SetProperty("enumerable", JsValue.True, true);
JsNativeFunction nativeGetFunction = (callee, isConstructCall, args, argCount, callbackData) =>
{
JsValue thisValue = args[0];
JsValue undefinedValue = JsValue.Undefined;
object thisObj = null;
if (instance)
{
if (!thisValue.HasExternalData)
{
JsValue errorValue = JsErrorHelpers.CreateTypeError(
string.Format(Strings.Runtime_InvalidThisContextForHostObjectField, fieldName));
JsErrorHelpers.SetException(errorValue);
return undefinedValue;
}
thisObj = MapToHostType(thisValue);
}
object result;
try
{
result = field.GetValue(thisObj);
}
catch (Exception e)
{
string errorMessage = instance ?
string.Format(Strings.Runtime_HostObjectFieldGettingFailed, fieldName, e.Message)
:
string.Format(Strings.Runtime_HostTypeFieldGettingFailed, fieldName, typeName, e.Message)
;
JsValue errorValue = JsErrorHelpers.CreateError(errorMessage);
JsErrorHelpers.SetException(errorValue);
return undefinedValue;
}
JsValue resultValue = MapToScriptType(result);
return resultValue;
};
_nativeFunctions.Add(nativeGetFunction);
JsValue getMethodValue = JsValue.CreateFunction(nativeGetFunction);
descriptorValue.SetProperty("get", getMethodValue, true);
JsNativeFunction nativeSetFunction = (callee, isConstructCall, args, argCount, callbackData) =>
{
JsValue thisValue = args[0];
JsValue undefinedValue = JsValue.Undefined;
object thisObj = null;
if (instance)
{
if (!thisValue.HasExternalData)
{
JsValue errorValue = JsErrorHelpers.CreateTypeError(
string.Format(Strings.Runtime_InvalidThisContextForHostObjectField, fieldName));
JsErrorHelpers.SetException(errorValue);
return undefinedValue;
}
thisObj = MapToHostType(thisValue);
}
object value = MapToHostType(args.Skip(1).First());
ReflectionHelpers.FixFieldValueType(ref value, field);
try
{
field.SetValue(thisObj, value);
}
catch (Exception e)
{
string errorMessage = instance ?
string.Format(Strings.Runtime_HostObjectFieldSettingFailed, fieldName, e.Message)
:
string.Format(Strings.Runtime_HostTypeFieldSettingFailed, fieldName, typeName, e.Message)
;
JsValue errorValue = JsErrorHelpers.CreateError(errorMessage);
JsErrorHelpers.SetException(errorValue);
return undefinedValue;
}
return undefinedValue;
};
_nativeFunctions.Add(nativeSetFunction);
JsValue setMethodValue = JsValue.CreateFunction(nativeSetFunction);
descriptorValue.SetProperty("set", setMethodValue, true);
target.DefineProperty(fieldName, descriptorValue);
}
}
private void ProjectProperties(JsValue target, Type type, bool instance)
{
string typeName = type.FullName;
BindingFlags defaultBindingFlags = ReflectionHelpers.GetDefaultBindingFlags(instance);
PropertyInfo[] properties = type.GetProperties(defaultBindingFlags);
foreach (PropertyInfo property in properties)
{
string propertyName = property.Name;
JsValue descriptorValue = JsValue.CreateObject();
descriptorValue.SetProperty("enumerable", JsValue.True, true);
if (property.GetGetMethod() != null)
{
JsNativeFunction nativeFunction = (callee, isConstructCall, args, argCount, callbackData) =>
{
JsValue thisValue = args[0];
JsValue undefinedValue = JsValue.Undefined;
object thisObj = null;
if (instance)
{
if (!thisValue.HasExternalData)
{
JsValue errorValue = JsErrorHelpers.CreateTypeError(
string.Format(Strings.Runtime_InvalidThisContextForHostObjectProperty, propertyName));
JsErrorHelpers.SetException(errorValue);
return undefinedValue;
}
thisObj = MapToHostType(thisValue);
}
object result;
try
{
result = property.GetValue(thisObj, new object[0]);
}
catch (Exception e)
{
string errorMessage = instance ?
string.Format(
Strings.Runtime_HostObjectPropertyGettingFailed, propertyName, e.Message)
:
string.Format(
Strings.Runtime_HostTypePropertyGettingFailed, propertyName, typeName, e.Message)
;
JsValue errorValue = JsErrorHelpers.CreateError(errorMessage);
JsErrorHelpers.SetException(errorValue);
return undefinedValue;
}
JsValue resultValue = MapToScriptType(result);
return resultValue;
};
_nativeFunctions.Add(nativeFunction);
JsValue getMethodValue = JsValue.CreateFunction(nativeFunction);
descriptorValue.SetProperty("get", getMethodValue, true);
}
if (property.GetSetMethod() != null)
{
JsNativeFunction nativeFunction = (callee, isConstructCall, args, argCount, callbackData) =>
{
JsValue thisValue = args[0];
JsValue undefinedValue = JsValue.Undefined;
object thisObj = null;
if (instance)
{
if (!thisValue.HasExternalData)
{
JsValue errorValue = JsErrorHelpers.CreateTypeError(
string.Format(Strings.Runtime_InvalidThisContextForHostObjectProperty, propertyName));
JsErrorHelpers.SetException(errorValue);
return undefinedValue;
}
thisObj = MapToHostType(thisValue);
}
object value = MapToHostType(args.Skip(1).First());
ReflectionHelpers.FixPropertyValueType(ref value, property);
try
{
property.SetValue(thisObj, value, new object[0]);
}
catch (Exception e)
{
string errorMessage = instance ?
string.Format(
Strings.Runtime_HostObjectPropertySettingFailed, propertyName, e.Message)
:
string.Format(
Strings.Runtime_HostTypePropertySettingFailed, propertyName, typeName, e.Message)
;
JsValue errorValue = JsErrorHelpers.CreateError(errorMessage);
JsErrorHelpers.SetException(errorValue);
return undefinedValue;
}
return undefinedValue;
};
_nativeFunctions.Add(nativeFunction);
JsValue setMethodValue = JsValue.CreateFunction(nativeFunction);
descriptorValue.SetProperty("set", setMethodValue, true);
}
target.DefineProperty(propertyName, descriptorValue);
}
}
private void ProjectMethods(JsValue target, Type type, bool instance)
{
string typeName = type.FullName;
BindingFlags defaultBindingFlags = ReflectionHelpers.GetDefaultBindingFlags(instance);
MethodInfo[] methods = type.GetMethods(defaultBindingFlags);
IEnumerable> methodGroups = methods.GroupBy(m => m.Name);
foreach (IGrouping methodGroup in methodGroups)
{
string methodName = methodGroup.Key;
MethodInfo[] methodCandidates = methodGroup.ToArray();
JsNativeFunction nativeFunction = (callee, isConstructCall, args, argCount, callbackData) =>
{
JsValue thisValue = args[0];
JsValue undefinedValue = JsValue.Undefined;
object thisObj = null;
if (instance)
{
if (!thisValue.HasExternalData)
{
JsValue errorValue = JsErrorHelpers.CreateTypeError(
string.Format(Strings.Runtime_InvalidThisContextForHostObjectMethod, methodName));
JsErrorHelpers.SetException(errorValue);
return undefinedValue;
}
thisObj = MapToHostType(thisValue);
}
object[] processedArgs = MapToHostType(args.Skip(1).ToArray());
var bestFitMethod = (MethodInfo)ReflectionHelpers.GetBestFitMethod(
methodCandidates, processedArgs);
if (bestFitMethod == null)
{
JsValue errorValue = JsErrorHelpers.CreateReferenceError(
string.Format(Strings.Runtime_SuitableMethodOfHostObjectNotFound, methodName));
JsErrorHelpers.SetException(errorValue);
return undefinedValue;
}
ReflectionHelpers.FixArgumentTypes(ref processedArgs, bestFitMethod.GetParameters());
object result;
try
{
result = bestFitMethod.Invoke(thisObj, processedArgs);
}
catch (Exception e)
{
string errorMessage = instance ?
string.Format(
Strings.Runtime_HostObjectMethodInvocationFailed, methodName, e.Message)
:
string.Format(
Strings.Runtime_HostTypeMethodInvocationFailed, methodName, typeName, e.Message)
;
JsValue errorValue = JsErrorHelpers.CreateError(errorMessage);
JsErrorHelpers.SetException(errorValue);
return undefinedValue;
}
JsValue resultValue = MapToScriptType(result);
return resultValue;
};
_nativeFunctions.Add(nativeFunction);
JsValue methodValue = JsValue.CreateFunction(nativeFunction);
target.SetProperty(methodName, methodValue, true);
}
}
private static JsRuntimeException ConvertJsExceptionToJsRuntimeException(
OriginalJsException jsException)
{
string message = jsException.Message;
string category = string.Empty;
int lineNumber = 0;
int columnNumber = 0;
string sourceFragment = string.Empty;
var jsScriptException = jsException as JsScriptException;
if (jsScriptException != null)
{
category = "Script error";
JsValue metadataValue = jsScriptException.Metadata;
if (metadataValue.IsValid)
{
JsValue errorValue = metadataValue.GetProperty("exception");
JsPropertyId stackPropertyId = JsPropertyId.FromString("stack");
if (errorValue.HasProperty(stackPropertyId))
{
JsValue stackPropertyValue = errorValue.GetProperty(stackPropertyId);
message = stackPropertyValue.ConvertToString().ToString();
}
else
{
JsValue messagePropertyValue = errorValue.GetProperty("message");
string scriptMessage = messagePropertyValue.ConvertToString().ToString();
if (!string.IsNullOrWhiteSpace(scriptMessage))
{
message = string.Format("{0}: {1}", message.TrimEnd('.'), scriptMessage);
}
}
JsPropertyId linePropertyId = JsPropertyId.FromString("line");
if (metadataValue.HasProperty(linePropertyId))
{
JsValue linePropertyValue = metadataValue.GetProperty(linePropertyId);
lineNumber = linePropertyValue.ConvertToNumber().ToInt32() + 1;
}
JsPropertyId columnPropertyId = JsPropertyId.FromString("column");
if (metadataValue.HasProperty(columnPropertyId))
{
JsValue columnPropertyValue = metadataValue.GetProperty(columnPropertyId);
columnNumber = columnPropertyValue.ConvertToNumber().ToInt32() + 1;
}
JsPropertyId sourcePropertyId = JsPropertyId.FromString("source");
if (metadataValue.HasProperty(sourcePropertyId))
{
JsValue sourcePropertyValue = metadataValue.GetProperty(sourcePropertyId);
sourceFragment = sourcePropertyValue.ConvertToString().ToString();
}
}
}
else if (jsException is JsUsageException)
{
category = "Usage error";
}
else if (jsException is JsEngineException)
{
category = "Engine error";
}
else if (jsException is JsFatalException)
{
category = "Fatal error";
}
var jsEngineException = new JsRuntimeException(message, EngineName, EngineVersion, jsException)
{
ErrorCode = ((uint)jsException.ErrorCode).ToString(CultureInfo.InvariantCulture),
Category = category,
LineNumber = lineNumber,
ColumnNumber = columnNumber,
SourceFragment = sourceFragment
};
return jsEngineException;
}
#endregion
#region JsEngineBase implementation
protected override object InnerEvaluate(string expression)
{
return InnerEvaluate(expression, null);
}
protected override object InnerEvaluate(string expression, string documentName)
{
string uniqueDocumentName = _documentNameManager.GetUniqueName(documentName);
object result = _dispatcher.Invoke(() =>
{
using (CreateJsScope())
{
try
{
JsValue resultValue = JsContext.RunScript(expression, _jsSourceContext++,
uniqueDocumentName);
return MapToHostType(resultValue);
}
catch (OriginalJsException e)
{
throw ConvertJsExceptionToJsRuntimeException(e);
}
}
});
return result;
}
protected override T InnerEvaluate(string expression)
{
return InnerEvaluate(expression, null);
}
protected override T InnerEvaluate(string expression, string documentName)
{
object result = InnerEvaluate(expression, documentName);
return TypeConverter.ConvertToType(result);
}
protected override void InnerExecute(string code)
{
InnerExecute(code, null);
}
protected override void InnerExecute(string code, string documentName)
{
string uniqueDocumentName = _documentNameManager.GetUniqueName(documentName);
_dispatcher.Invoke(() =>
{
using (CreateJsScope())
{
try
{
JsContext.RunScript(code, _jsSourceContext++, uniqueDocumentName);
}
catch (OriginalJsException e)
{
throw ConvertJsExceptionToJsRuntimeException(e);
}
}
});
}
protected override object InnerCallFunction(string functionName, params object[] args)
{
object result = _dispatcher.Invoke(() =>
{
using (CreateJsScope())
{
try
{
JsValue globalObj = JsValue.GlobalObject;
JsPropertyId functionId = JsPropertyId.FromString(functionName);
bool functionExist = globalObj.HasProperty(functionId);
if (!functionExist)
{
throw new JsRuntimeException(
string.Format(CoreStrings.Runtime_FunctionNotExist, functionName));
}
JsValue resultValue;
JsValue functionValue = globalObj.GetProperty(functionId);
if (args.Length > 0)
{
JsValue[] processedArgs = MapToScriptType(args);
foreach (JsValue processedArg in processedArgs)
{
AddReferenceToValue(processedArg);
}
JsValue[] allProcessedArgs = new[] { globalObj }
.Concat(processedArgs)
.ToArray()
;
try
{
resultValue = functionValue.CallFunction(allProcessedArgs);
}
finally
{
foreach (JsValue processedArg in processedArgs)
{
RemoveReferenceToValue(processedArg);
}
}
}
else
{
resultValue = functionValue.CallFunction(globalObj);
}
return MapToHostType(resultValue);
}
catch (OriginalJsException e)
{
throw ConvertJsExceptionToJsRuntimeException(e);
}
}
});
return result;
}
protected override T InnerCallFunction(string functionName, params object[] args)
{
object result = InnerCallFunction(functionName, args);
return TypeConverter.ConvertToType(result);
}
protected override bool InnerHasVariable(string variableName)
{
bool result = _dispatcher.Invoke(() =>
{
using (CreateJsScope())
{
try
{
JsValue globalObj = JsValue.GlobalObject;
JsPropertyId variableId = JsPropertyId.FromString(variableName);
bool variableExist = globalObj.HasProperty(variableId);
if (variableExist)
{
JsValue variableValue = globalObj.GetProperty(variableId);
variableExist = variableValue.ValueType != JsValueType.Undefined;
}
return variableExist;
}
catch (OriginalJsException e)
{
throw ConvertJsExceptionToJsRuntimeException(e);
}
}
});
return result;
}
protected override object InnerGetVariableValue(string variableName)
{
object result = _dispatcher.Invoke(() =>
{
using (CreateJsScope())
{
try
{
JsValue variableValue = JsValue.GlobalObject.GetProperty(variableName);
return MapToHostType(variableValue);
}
catch (OriginalJsException e)
{
throw ConvertJsExceptionToJsRuntimeException(e);
}
}
});
return result;
}
protected override T InnerGetVariableValue(string variableName)
{
object result = InnerGetVariableValue(variableName);
return TypeConverter.ConvertToType(result);
}
protected override void InnerSetVariableValue(string variableName, object value)
{
_dispatcher.Invoke(() =>
{
using (CreateJsScope())
{
try
{
JsValue inputValue = MapToScriptType(value);
AddReferenceToValue(inputValue);
try
{
JsValue.GlobalObject.SetProperty(variableName, inputValue, true);
}
finally
{
RemoveReferenceToValue(inputValue);
}
}
catch (OriginalJsException e)
{
throw ConvertJsExceptionToJsRuntimeException(e);
}
}
});
}
protected override void InnerRemoveVariable(string variableName)
{
_dispatcher.Invoke(() =>
{
using (CreateJsScope())
{
try
{
JsValue globalObj = JsValue.GlobalObject;
JsPropertyId variableId = JsPropertyId.FromString(variableName);
if (globalObj.HasProperty(variableId))
{
globalObj.SetProperty(variableId, JsValue.Undefined, true);
}
}
catch (OriginalJsException e)
{
throw ConvertJsExceptionToJsRuntimeException(e);
}
}
});
}
protected override void InnerEmbedHostObject(string itemName, object value)
{
_dispatcher.Invoke(() =>
{
using (CreateJsScope())
{
try
{
JsValue processedValue = MapToScriptType(value);
JsValue.GlobalObject.SetProperty(itemName, processedValue, true);
}
catch (OriginalJsException e)
{
throw ConvertJsExceptionToJsRuntimeException(e);
}
}
});
}
protected override void InnerEmbedHostType(string itemName, Type type)
{
_dispatcher.Invoke(() =>
{
using (CreateJsScope())
{
try
{
JsValue typeValue = CreateObjectFromType(type);
JsValue.GlobalObject.SetProperty(itemName, typeValue, true);
}
catch (OriginalJsException e)
{
throw ConvertJsExceptionToJsRuntimeException(e);
}
}
});
}
protected override void InnerCollectGarbage()
{
_jsRuntime.CollectGarbage();
}
#endregion
#region IDisposable implementation
///
/// Destroys object
///
public override void Dispose()
{
Dispose(true /* disposing */);
GC.SuppressFinalize(this);
}
///
/// Destroys object
///
/// Flag, allowing destruction of
/// managed objects contained in fields of class
private void Dispose(bool disposing)
{
if (_disposedFlag.Set())
{
if (disposing)
{
if (_dispatcher != null)
{
_dispatcher.Invoke(DisposeUnmanagedResources);
_dispatcher.Dispose();
_dispatcher = null;
}
if (_externalObjects != null)
{
_externalObjects.Clear();
_externalObjects = null;
}
if (_nativeFunctions != null)
{
_nativeFunctions.Clear();
_nativeFunctions = null;
}
_promiseContinuationCallback = null;
_externalObjectFinalizeCallback = null;
}
else
{
DisposeUnmanagedResources();
}
}
}
private void DisposeUnmanagedResources()
{
if (_jsContext.IsValid)
{
_jsContext.Release();
}
_jsRuntime.Dispose();
}
#endregion
}
}