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 { /// <summary> /// Adapter for the ChakraCore JS engine /// </summary> public sealed class ChakraCoreJsEngine : JsEngineBase { /// <summary> /// Name of JS engine /// </summary> public const string EngineName = "ChakraCoreJsEngine"; /// <summary> /// Version of original JS engine /// </summary> private const string EngineVersion = "1.11.3"; /// <summary> /// Instance of JS runtime /// </summary> private JsRuntime _jsRuntime; /// <summary> /// Instance of JS context /// </summary> private JsContext _jsContext; /// <summary> /// JS source context /// </summary> private JsSourceContext _jsSourceContext = JsSourceContext.FromIntPtr(IntPtr.Zero); /// <summary> /// Set of external objects /// </summary> private HashSet<object> _externalObjects = new HashSet<object>(); /// <summary> /// Callback for finalization of external object /// </summary> private JsObjectFinalizeCallback _externalObjectFinalizeCallback; /// <summary> /// Callback for continuation of promise /// </summary> private JsPromiseContinuationCallback _promiseContinuationCallback; /// <summary> /// List of native function callbacks /// </summary> private HashSet<JsNativeFunction> _nativeFunctions = new HashSet<JsNativeFunction>(); /// <summary> /// Script dispatcher /// </summary> private ScriptDispatcher _dispatcher; /// <summary> /// Unique document name manager /// </summary> private readonly UniqueDocumentNameManager _documentNameManager = new UniqueDocumentNameManager(DefaultDocumentName); #if !NETSTANDARD1_3 /// <summary> /// Synchronizer of JS engine initialization /// </summary> private static readonly object _initializationSynchronizer = new object(); /// <summary> /// Flag indicating whether the JS engine is initialized /// </summary> private static bool _initialized; #endif /// <summary> /// Gets a name of JS engine /// </summary> public override string Name { get { return EngineName; } } /// <summary> /// Gets a version of original JS engine /// </summary> public override string Version { get { return EngineVersion; } } /// <summary> /// Gets a value that indicates if the JS engine supports garbage collection /// </summary> public override bool SupportsGarbageCollection { get { return true; } } /// <summary> /// Constructs an instance of adapter for the ChakraCore JS engine /// </summary> public ChakraCoreJsEngine() : this(new ChakraCoreSettings()) { } /// <summary> /// Constructs an instance of adapter for the ChakraCore JS engine /// </summary> /// <param name="settings">Settings of the ChakraCore JS engine</param> 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(); } } } /// <summary> /// Destructs an instance of adapter for the ChakraCore JS engine /// </summary> ~ChakraCoreJsEngine() { Dispose(false); } #if !NETSTANDARD1_3 /// <summary> /// Initializes a JS engine /// </summary> 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 /// <summary> /// Adds a reference to the value /// </summary> /// <param name="value">The value</param> private static void AddReferenceToValue(JsValue value) { if (CanHaveReferences(value)) { value.AddRef(); } } /// <summary> /// Removes a reference to the value /// </summary> /// <param name="value">The value</param> private static void RemoveReferenceToValue(JsValue value) { if (CanHaveReferences(value)) { value.Release(); } } /// <summary> /// Checks whether the value can have references /// </summary> /// <param name="value">The value</param> /// <returns>Result of check (true - may have; false - may not have)</returns> 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; } } /// <summary> /// Creates a instance of JS scope /// </summary> /// <returns>Instance of JS scope</returns> private JsScope CreateJsScope() { var jsScope = new JsScope(_jsContext); JsRuntime.SetPromiseContinuationCallback(_promiseContinuationCallback, IntPtr.Zero); return jsScope; } /// <summary> /// The promise continuation callback /// </summary> /// <param name="task">The task, represented as a JavaScript function</param> /// <param name="callbackState">The data argument to be passed to the callback</param> private static void PromiseContinuationCallback(JsValue task, IntPtr callbackState) { task.AddRef(); try { task.CallFunction(JsValue.GlobalObject); } finally { task.Release(); } } #region Mapping /// <summary> /// Makes a mapping of value from the host type to a script type /// </summary> /// <param name="value">The source value</param> /// <returns>The mapped value</returns> 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); } } /// <summary> /// Makes a mapping of array items from the host type to a script type /// </summary> /// <param name="args">The source array</param> /// <returns>The mapped array</returns> private JsValue[] MapToScriptType(object[] args) { return args.Select(MapToScriptType).ToArray(); } /// <summary> /// Makes a mapping of value from the script type to a host type /// </summary> /// <param name="value">The source value</param> /// <returns>The mapped value</returns> 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; } /// <summary> /// Makes a mapping of array items from the script type to a host type /// </summary> /// <param name="args">The source array</param> /// <returns>The mapped array</returns> 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<IGrouping<string, MethodInfo>> methodGroups = methods.GroupBy(m => m.Name); foreach (IGrouping<string, MethodInfo> 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<T>(string expression) { return InnerEvaluate<T>(expression, null); } protected override T InnerEvaluate<T>(string expression, string documentName) { object result = InnerEvaluate(expression, documentName); return TypeConverter.ConvertToType<T>(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<T>(string functionName, params object[] args) { object result = InnerCallFunction(functionName, args); return TypeConverter.ConvertToType<T>(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<T>(string variableName) { object result = InnerGetVariableValue(variableName); return TypeConverter.ConvertToType<T>(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 /// <summary> /// Destroys object /// </summary> public override void Dispose() { Dispose(true /* disposing */); GC.SuppressFinalize(this); } /// <summary> /// Destroys object /// </summary> /// <param name="disposing">Flag, allowing destruction of /// managed objects contained in fields of class</param> 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 } }