From 3d8246f702d8f6309d9a7003ad6e2f74227b47a4 Mon Sep 17 00:00:00 2001 From: Andrey Taritsyn Date: Fri, 26 Feb 2016 18:50:22 +0300 Subject: [PATCH] Added the `EmbedHostType` method (embeds a host type to script code) --- .../ActiveScript/ActiveScriptJsEngineBase.cs | 456 +++++++++++++--- .../ActiveScript/ActiveScriptSiteWrapper.cs | 494 ------------------ .../Constants/SpecialMemberName.cs | 10 + src/MsieJavaScriptEngine/HostItemBase.cs | 184 +++++++ src/MsieJavaScriptEngine/HostObject.cs | 156 +----- src/MsieJavaScriptEngine/HostType.cs | 54 ++ src/MsieJavaScriptEngine/IInnerJsEngine.cs | 11 + .../JsRt/ChakraJsRtJsEngineBase.cs | 4 + .../JsRt/Edge/ChakraEdgeJsRtJsEngine.cs | 11 + .../JsRt/Ie/ChakraIeJsRtJsEngine.cs | 11 + .../MsieJavaScriptEngine.csproj | 4 +- src/MsieJavaScriptEngine/MsieJsEngine.cs | 44 +- .../Resources/Strings.Designer.cs | 9 + .../Resources/Strings.resx | 3 + .../Resources/Strings.ru-ru.resx | 3 + .../Interop/Base64Encoder.cs | 16 + .../Interop/BundleTable.cs | 19 + .../Interop/Date.cs | 13 + .../Interop/Person.cs | 6 + .../Interop/Point3D.cs | 25 + .../Interop/PredefinedStrings.cs | 9 + .../Interop/SimpleSingleton.cs | 17 + .../InteropTestsBase.cs | 481 +++++++++++++++++ .../MsieJavaScriptEngine.Test.Common.csproj | 5 + 24 files changed, 1356 insertions(+), 689 deletions(-) delete mode 100644 src/MsieJavaScriptEngine/ActiveScript/ActiveScriptSiteWrapper.cs create mode 100644 src/MsieJavaScriptEngine/Constants/SpecialMemberName.cs create mode 100644 src/MsieJavaScriptEngine/HostItemBase.cs create mode 100644 src/MsieJavaScriptEngine/HostType.cs create mode 100644 test/MsieJavaScriptEngine.Test.Common/Interop/Base64Encoder.cs create mode 100644 test/MsieJavaScriptEngine.Test.Common/Interop/BundleTable.cs create mode 100644 test/MsieJavaScriptEngine.Test.Common/Interop/Point3D.cs create mode 100644 test/MsieJavaScriptEngine.Test.Common/Interop/PredefinedStrings.cs create mode 100644 test/MsieJavaScriptEngine.Test.Common/Interop/SimpleSingleton.cs diff --git a/src/MsieJavaScriptEngine/ActiveScript/ActiveScriptJsEngineBase.cs b/src/MsieJavaScriptEngine/ActiveScript/ActiveScriptJsEngineBase.cs index 5cf3ec6..ee02f2e 100644 --- a/src/MsieJavaScriptEngine/ActiveScript/ActiveScriptJsEngineBase.cs +++ b/src/MsieJavaScriptEngine/ActiveScript/ActiveScriptJsEngineBase.cs @@ -1,11 +1,15 @@ namespace MsieJavaScriptEngine.ActiveScript { using System; + using System.Collections.Generic; using System.Globalization; using System.Reflection; using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Expando; using System.Windows.Threading; + using EXCEPINFO = System.Runtime.InteropServices.ComTypes.EXCEPINFO; + using Constants; using Helpers; using Resources; @@ -14,7 +18,7 @@ /// /// Base class of the ActiveScript JavaScript engine /// - internal abstract class ActiveScriptJsEngineBase : IInnerJsEngine + internal abstract class ActiveScriptJsEngineBase : IInnerJsEngine, IActiveScriptSite { /// /// Name of resource, which contains a ECMAScript 5 Polyfill @@ -37,9 +41,29 @@ internal abstract class ActiveScriptJsEngineBase : IInnerJsEngine private IActiveScript _activeScript; /// - /// Instance of site for the ActiveScript engine + /// Instance of ActiveScriptParseWrapper + /// + private IActiveScriptParseWrapper _activeScriptParse; + + /// + /// Instance of script dispatch + /// + private IExpando _dispatch; + + /// + /// List of host items /// - private ActiveScriptSiteWrapper _activeScriptSite; + private Dictionary _hostItems = new Dictionary(); + + /// + /// Host-defined document version string + /// + private readonly string _documentVersion; + + /// + /// Last ActiveScript exception + /// + private ActiveScriptException _lastException; /// /// JavaScript engine mode @@ -63,7 +87,7 @@ internal abstract class ActiveScriptJsEngineBase : IInnerJsEngine /// - /// Constructs instance of the ActiveScript JavaScript engine + /// Constructs an instance of the ActiveScript JavaScript engine /// /// CLSID of JavaScript engine /// JavaScript engine mode @@ -95,10 +119,10 @@ protected ActiveScriptJsEngineBase(string clsid, JsEngineMode engineMode, string var activeScriptProperty = _activeScript as IActiveScriptProperty; if (activeScriptProperty != null) { - object scriptLanguageVersion = (int) languageVersion; - uint result = activeScriptProperty.SetProperty((uint) ScriptProperty.InvokeVersioning, + object scriptLanguageVersion = (int)languageVersion; + uint result = activeScriptProperty.SetProperty((uint)ScriptProperty.InvokeVersioning, IntPtr.Zero, ref scriptLanguageVersion); - if (result != (uint) ScriptHResult.Ok) + if (result != (uint)ScriptHResult.Ok) { throw new JsEngineLoadException( string.Format(Strings.Runtime_ActiveScriptLanguageVersionSelectionFailed, languageVersion)); @@ -106,7 +130,14 @@ protected ActiveScriptJsEngineBase(string clsid, JsEngineMode engineMode, string } } - _activeScriptSite = new ActiveScriptSiteWrapper(_pActiveScript, _activeScript); + _activeScriptParse = new ActiveScriptParseWrapper(_pActiveScript, _activeScript); + _activeScriptParse.InitNew(); + + _activeScript.SetScriptSite(this); + _activeScript.SetScriptState(ScriptState.Started); + + InitScriptDispatch(); + _documentVersion = DateTime.UtcNow.ToString("o"); LoadResources(useEcmaScript5Polyfill, useJson2Library); } @@ -203,7 +234,7 @@ private object MapToHostType(object value) } /// - /// Makes a mapping of array itemp from the script type to a host type + /// Makes a mapping of array items from the script type to a host type /// /// The source array /// The mapped array @@ -229,6 +260,49 @@ private JsRuntimeException ConvertActiveScriptExceptionToJsRuntimeException( return jsEngineException; } + /// + /// Initializes a script dispatch + /// + private void InitScriptDispatch() + { + IExpando dispatch = null; + object obj; + + _activeScript.GetScriptDispatch(null, out obj); + + if (obj != null && obj.GetType().IsCOMObject) + { + dispatch = obj as IExpando; + } + + if (dispatch == null) + { + throw new InvalidOperationException(Strings.Runtime_ActiveScriptDispatcherNotInitialized); + } + + _dispatch = dispatch; + } + + /// + /// Gets and resets a last exception. Returns null for none. + /// + private ActiveScriptException GetAndResetLastException() + { + ActiveScriptException temp = _lastException; + _lastException = null; + + return temp; + } + + private void ThrowError() + { + ActiveScriptException last = GetAndResetLastException(); + if (last != null) + { + throw last; + } + } + private void InvokeScript(Action action) { try @@ -273,6 +347,139 @@ private T InvokeScript(Func func) } } + /// + /// Executes a script text + /// + /// Script text + /// Flag that script text needs to run as an expression + /// Result of the execution + private object InnerExecute(string code, bool isExpression) + { + object result; + + try + { + result = _activeScriptParse.ParseScriptText(code, null, null, null, IntPtr.Zero, + 0, isExpression ? ScriptTextFlags.IsExpression : ScriptTextFlags.IsVisible); + } + catch + { + ThrowError(); + throw; + } + + // Check for parse error + ThrowError(); + + return result; + } + + /// + /// Calls a function + /// + /// Function name + /// Function arguments + /// Result of the function execution + private object InnerCallFunction(string functionName, params object[] args) + { + object result; + + try + { + result = _dispatch.InvokeMember(functionName, BindingFlags.InvokeMethod, + null, _dispatch, args, null, CultureInfo.InvariantCulture, null); + } + catch + { + ThrowError(); + throw; + } + + return result; + } + + /// + /// Gets a value of variable + /// + /// Name of variable + /// Value of variable + private object InnerGetVariableValue(string variableName) + { + object variableValue; + + try + { + variableValue = _dispatch.InvokeMember(variableName, BindingFlags.GetProperty, + null, _dispatch, new object[0], null, + CultureInfo.InvariantCulture, null); + } + catch + { + ThrowError(); + throw; + } + + return variableValue; + } + + /// + /// Sets a value to variable + /// + /// Name of variable + /// Value of variable + private void InnerSetVariableValue(string variableName, object value) + { + object[] args = { value }; + + try + { + _dispatch.InvokeMember(variableName, BindingFlags.SetProperty, null, _dispatch, + args, null, CultureInfo.InvariantCulture, null); + } + catch (MissingMemberException) + { + _dispatch.AddProperty(variableName); + _dispatch.InvokeMember(variableName, BindingFlags.SetProperty, null, _dispatch, + args, null, CultureInfo.InvariantCulture, null); + } + catch + { + ThrowError(); + throw; + } + } + + private void EmbedHostItem(string itemName, object value) + { + InvokeScript(() => + { + object oldValue = null; + if (_hostItems.ContainsKey(itemName)) + { + oldValue = _hostItems[itemName]; + } + _hostItems[itemName] = value; + + try + { + _activeScript.AddNamedItem(itemName, ScriptItemFlags.IsVisible | ScriptItemFlags.GlobalMembers); + } + catch (Exception) + { + if (oldValue != null) + { + _hostItems[itemName] = oldValue; + } + else + { + _hostItems.Remove(itemName); + } + + throw; + } + }); + } + /// /// Loads a resources /// @@ -323,31 +530,156 @@ private void ExecuteResource(string resourceName, Type type) /// managed objects contained in fields of class private void Dispose(bool disposing) { - _dispatcher.Invoke(DispatcherPriority.Input, (Action)InnerDispose); + _dispatcher.Invoke(DispatcherPriority.Input, (Action)(() => + { + if (!_disposed) + { + _disposed = true; + + if (_dispatch != null) + { + ComHelpers.ReleaseComObject(ref _dispatch, !disposing); + _dispatch = null; + } + + if (_activeScriptParse != null) + { + _activeScriptParse.Dispose(); + _activeScriptParse = null; + } + + if (_activeScript != null) + { + _activeScript.Close(); + _activeScript = null; + } + + ComHelpers.ReleaseAndEmpty(ref _pActiveScript); + + if (_hostItems != null) + { + _hostItems.Clear(); + _hostItems = null; + } + + _lastException = null; + } + })); + } + + #region IActiveScriptSite implementation + + /// + /// Retrieves the locale identifier associated with the host's user interface. The scripting + /// engine uses the identifier to ensure that error strings and other user-interface elements + /// generated by the engine appear in the appropriate language. + /// + /// A variable that receives the locale identifier for user-interface + /// elements displayed by the scripting engine + void IActiveScriptSite.GetLcid(out int lcid) + { + lcid = CultureInfo.CurrentCulture.LCID; } - private void InnerDispose() + /// + /// Allows the scripting engine to obtain information about an item added with the + /// IActiveScript.AddNamedItem method + /// + /// The name associated with the item, as specified in the + /// IActiveScript.AddNamedItem method + /// A bit mask specifying what information about the item should be + /// returned. The scripting engine should request the minimum amount of information possible + /// because some of the return parameters (for example, ITypeInfo) can take considerable + /// time to load or generate + /// A variable that receives a pointer to the IUnknown interface associated + /// with the given item. The scripting engine can use the IUnknown.QueryInterface method to + /// obtain the IDispatch interface for the item. This parameter receives null if mask + /// does not include the ScriptInfo.IUnknown value. Also, it receives null if there is no + /// object associated with the item name; this mechanism is used to create a simple class when + /// the named item was added with the ScriptItem.CodeOnly flag set in the + /// IActiveScript.AddNamedItem method. + /// A variable that receives a pointer to the ITypeInfo interface + /// associated with the item. This parameter receives null if mask does not include the + /// ScriptInfo.ITypeInfo value, or if type information is not available for this item. If type + /// information is not available, the object cannot source events, and name binding must be + /// realized with the IDispatch.GetIDsOfNames method. Note that the ITypeInfo interface + /// retrieved describes the item's coclass (TKIND_COCLASS) because the object may support + /// multiple interfaces and event interfaces. If the item supports the IProvideMultipleTypeInfo + /// interface, the ITypeInfo interface retrieved is the same as the index zero ITypeInfo that + /// would be obtained using the IProvideMultipleTypeInfo.GetInfoOfIndex method. + void IActiveScriptSite.GetItemInfo(string name, ScriptInfoFlags mask, ref IntPtr pUnkItem, ref IntPtr pTypeInfo) { - if (!_disposed) + object item = _hostItems[name]; + if (item == null) { - _disposed = true; - - if (_activeScriptSite != null) - { - _activeScriptSite.Dispose(); - _activeScriptSite = null; - } + throw new COMException( + string.Format(Strings.Runtime_ItemNotFound, name), ComErrorCode.ElementNotFound); + } - if (_activeScript != null) - { - _activeScript.Close(); - _activeScript = null; - } + if (mask.HasFlag(ScriptInfoFlags.IUnknown)) + { + pUnkItem = Marshal.GetIDispatchForObject(item); + } - ComHelpers.ReleaseAndEmpty(ref _pActiveScript); + if (mask.HasFlag(ScriptInfoFlags.ITypeInfo)) + { + pTypeInfo = Marshal.GetITypeInfoForType(item.GetType()); } } + /// + /// Retrieves a host-defined string that uniquely identifies the current document version. If + /// the related document has changed outside the scope of Windows Script (as in the case of an + /// HTML page being edited with Notepad), the scripting engine can save this along with its + /// persisted state, forcing a recompile the next time the script is loaded. + /// + /// The host-defined document version string + void IActiveScriptSite.GetDocVersionString(out string version) + { + version = _documentVersion; + } + + /// + /// Informs the host that the script has completed execution + /// + /// A variable that contains the script result, or null if the script + /// produced no result + /// Contains exception information generated when the script + /// terminated, or null if no exception was generated + void IActiveScriptSite.OnScriptTerminate(object result, EXCEPINFO exceptionInfo) + { } + + /// + /// Informs the host that the scripting engine has changed states + /// + /// Indicates the new script state + void IActiveScriptSite.OnStateChange(ScriptState scriptState) + { } + + /// + /// Informs the host that an execution error occurred while the engine was running the script. + /// + /// A host can use this interface to obtain information about the + /// execution error + void IActiveScriptSite.OnScriptError(IActiveScriptError scriptError) + { + _lastException = ActiveScriptException.Create(scriptError); + } + + /// + /// Informs the host that the scripting engine has begun executing the script code + /// + void IActiveScriptSite.OnEnterScript() + { } + + /// + /// Informs the host that the scripting engine has returned from executing script code + /// + void IActiveScriptSite.OnLeaveScript() + { } + + #endregion + #region IInnerJsEngine implementation public string Mode @@ -357,7 +689,7 @@ public string Mode public object Evaluate(string expression) { - object result = InvokeScript(() => _activeScriptSite.ExecuteScriptText(expression, true)); + object result = InvokeScript(() => InnerExecute(expression, true)); result = MapToHostType(result); return result; @@ -367,19 +699,19 @@ public void Execute(string code) { InvokeScript(() => { - _activeScriptSite.ExecuteScriptText(code, false); + InnerExecute(code, false); }); } public object CallFunction(string functionName, params object[] args) { - var processedArgs = MapToScriptType(args); + object[] processedArgs = MapToScriptType(args); object result = InvokeScript(() => { try { - return _activeScriptSite.CallFunction(functionName, processedArgs); + return InnerCallFunction(functionName, processedArgs); } catch (MissingMemberException) { @@ -395,18 +727,33 @@ public object CallFunction(string functionName, params object[] args) public bool HasVariable(string variableName) { - var variableExist = InvokeScript(() => _activeScriptSite.HasProperty(variableName)); + bool result = InvokeScript(() => + { + bool variableExist; - return variableExist; + try + { + object variableValue = InnerGetVariableValue(variableName); + variableExist = variableValue != null; + } + catch (MissingMemberException) + { + variableExist = false; + } + + return variableExist; + }); + + return result; } public object GetVariableValue(string variableName) { - object variableValue = InvokeScript(() => + object result = InvokeScript(() => { try { - return _activeScriptSite.GetProperty(variableName); + return InnerGetVariableValue(variableName); } catch (MissingMemberException) { @@ -415,7 +762,7 @@ public object GetVariableValue(string variableName) } }); - object result = MapToHostType(variableValue); + result = MapToHostType(result); return result; } @@ -423,43 +770,34 @@ public object GetVariableValue(string variableName) public void SetVariableValue(string variableName, object value) { object processedValue = MapToScriptType(value); - - InvokeScript(() => _activeScriptSite.SetProperty(variableName, processedValue)); + InvokeScript(() => InnerSetVariableValue(variableName, processedValue)); } public void RemoveVariable(string variableName) - { - InvokeScript(() => _activeScriptSite.DeleteProperty(variableName)); - } - - public void EmbedHostObject(string itemName, object value) { InvokeScript(() => { - var processedValue = MapToScriptType(value); - object oldValue = _activeScriptSite.GetItem(itemName); - _activeScriptSite.SetItem(itemName, processedValue); + InnerSetVariableValue(variableName, null); - try + if (_hostItems.ContainsKey(variableName)) { - _activeScript.AddNamedItem(itemName, ScriptItemFlags.IsVisible | ScriptItemFlags.GlobalMembers); - } - catch (Exception) - { - if (oldValue != null) - { - _activeScriptSite.SetItem(itemName, oldValue); - } - else - { - _activeScriptSite.RemoveItem(itemName); - } - - throw; + _hostItems.Remove(variableName); } }); } + public void EmbedHostObject(string itemName, object value) + { + object processedValue = MapToScriptType(value); + EmbedHostItem(itemName, processedValue); + } + + public void EmbedHostType(string itemName, Type type) + { + var typeValue = new HostType(type, _engineMode); + EmbedHostItem(itemName, typeValue); + } + #endregion #region IDisposable implementation diff --git a/src/MsieJavaScriptEngine/ActiveScript/ActiveScriptSiteWrapper.cs b/src/MsieJavaScriptEngine/ActiveScript/ActiveScriptSiteWrapper.cs deleted file mode 100644 index cb2e430..0000000 --- a/src/MsieJavaScriptEngine/ActiveScript/ActiveScriptSiteWrapper.cs +++ /dev/null @@ -1,494 +0,0 @@ -namespace MsieJavaScriptEngine.ActiveScript -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Reflection; - using System.Runtime.InteropServices; - using System.Runtime.InteropServices.Expando; - - using EXCEPINFO = System.Runtime.InteropServices.ComTypes.EXCEPINFO; - - using Constants; - using Helpers; - using Resources; - - internal class ActiveScriptSiteWrapper : IActiveScriptSite, IDisposable - { - /// - /// Instance of native JavaScript engine - /// - private IActiveScript _activeScript; - - /// - /// Instance of ActiveScriptParseWrapper - /// - private IActiveScriptParseWrapper _activeScriptParse; - - /// - /// Instance of script dispatch - /// - private IExpando _dispatch; - - /// - /// List of site items - /// - private Dictionary _siteItems = new Dictionary(); - - /// - /// Synchronizer - /// - private readonly object _synchronizer = new object(); - - /// - /// Last ActiveScript exception - /// - private ActiveScriptException _lastException; - - /// - /// Flag that object is destroyed - /// - private bool _disposed; - - /// - /// Gets or sets a host-defined document version string - /// - public string DocumentVersion - { - get; - protected set; - } - - - /// - /// Constructs instance of - /// - /// Pointer to an instance of native JavaScript engine - /// Instance of native JavaScript engine - public ActiveScriptSiteWrapper(IntPtr pActiveScript, IActiveScript activeScript) - : this(pActiveScript, activeScript, DateTime.UtcNow.ToString("o")) - { } - - /// - /// Constructs instance of - /// - /// Pointer to an instance of native JavaScript engine - /// Instance of native JavaScript engine - /// Host-defined document version string - public ActiveScriptSiteWrapper(IntPtr pActiveScript, IActiveScript activeScript, - string documentVersion) - { - _activeScript = activeScript; - - _activeScriptParse = new ActiveScriptParseWrapper(pActiveScript, _activeScript); - _activeScriptParse.InitNew(); - - _activeScript.SetScriptSite(this); - _activeScript.SetScriptState(ScriptState.Started); - - InitScriptDispatch(); - - DocumentVersion = documentVersion; - } - - /// - /// Destructs instance of - /// - ~ActiveScriptSiteWrapper() - { - Dispose(false); - } - - - /// - /// Initializes a script dispatch - /// - private void InitScriptDispatch() - { - IExpando dispatch = null; - object obj; - - _activeScript.GetScriptDispatch(null, out obj); - - if (obj != null && obj.GetType().IsCOMObject) - { - dispatch = obj as IExpando; - } - - if (dispatch == null) - { - throw new InvalidOperationException(Strings.Runtime_ActiveScriptDispatcherNotInitialized); - } - - _dispatch = dispatch; - } - - /// - /// Allows the scripting engine to obtain information about an item added with the - /// IActiveScript.AddNamedItem method - /// - /// The name associated with the item, as specified in the - /// IActiveScript.AddNamedItem method - public object GetItem(string name) - { - lock (_synchronizer) - { - object result; - - return _siteItems.TryGetValue(name, out result) ? result : null; - } - } - - public void SetItem(string name, object value) - { - lock (_synchronizer) - { - _siteItems[name] = value; - } - } - - public void RemoveItem(string name) - { - lock (_synchronizer) - { - _siteItems.Remove(name); - } - } - - /// - /// Gets and resets a last exception. Returns null for none. - /// - private ActiveScriptException GetAndResetLastException() - { - var temp = _lastException; - _lastException = null; - - return temp; - } - - private void ThrowError() - { - var last = GetAndResetLastException(); - if (last != null) - { - throw last; - } - } - - /// - /// Executes a script text - /// - /// Script text - /// Flag that script text needs to run as an expression - /// Result of the execution - public object ExecuteScriptText(string code, bool isExpression) - { - object result; - - try - { - result = _activeScriptParse.ParseScriptText(code, null, null, null, IntPtr.Zero, - 0, isExpression ? ScriptTextFlags.IsExpression : ScriptTextFlags.IsVisible); - } - catch - { - ThrowError(); - throw; - } - - // Check for parse error - ThrowError(); - - return result; - } - - /// - /// Calls a function - /// - /// Function name - /// Function arguments - /// Result of the function execution - public object CallFunction(string functionName, params object[] args) - { - object result; - try - { - result = _dispatch.InvokeMember(functionName, BindingFlags.InvokeMethod, - null, _dispatch, args, null, CultureInfo.InvariantCulture, null); - } - catch - { - ThrowError(); - throw; - } - - return result; - } - - /// - /// Сhecks for the existence of a global object property - /// - /// Name of property - /// Result of check (true - exists; false - not exists) - public bool HasProperty(string propertyName) - { - bool propertyExist; - - try - { - object propertyValue = GetProperty(propertyName); - propertyExist = (propertyValue != null); - } - catch (MissingMemberException) - { - propertyExist = false; - } - catch - { - ThrowError(); - throw; - } - - return propertyExist; - } - - /// - /// Gets a value of global object property - /// - /// Name of property - /// Value of property - public object GetProperty(string propertyName) - { - object propertyValue; - - try - { - propertyValue = _dispatch.InvokeMember(propertyName, BindingFlags.GetProperty, - null, _dispatch, new object[0], null, - CultureInfo.InvariantCulture, null); - } - catch - { - ThrowError(); - throw; - } - - return propertyValue; - } - - /// - /// Sets a value to global object property - /// - /// Name of property - /// Value of property - public void SetProperty(string propertyName, object value) - { - var marshaledArgs = new[] { value }; - try - { - _dispatch.InvokeMember(propertyName, BindingFlags.SetProperty, null, _dispatch, - marshaledArgs, null, CultureInfo.InvariantCulture, null); - } - catch (MissingMemberException) - { - _dispatch.AddProperty(propertyName); - _dispatch.InvokeMember(propertyName, BindingFlags.SetProperty, null, _dispatch, - marshaledArgs, null, CultureInfo.InvariantCulture, null); - } - catch - { - ThrowError(); - throw; - } - } - - /// - /// Removes a global object property - /// - /// Name of property - public void DeleteProperty(string propertyName) - { - try - { - SetProperty(propertyName, null); - } - catch - { - ThrowError(); - throw; - } - } - - /// - /// Destroys object - /// - /// Flag, allowing destruction of - /// managed objects contained in fields of class - private void Dispose(bool disposing) - { - if (!_disposed) - { - _disposed = true; - - _lastException = null; - - lock (_synchronizer) - { - if (_siteItems != null) - { - _siteItems.Clear(); - _siteItems = null; - } - } - - if (_dispatch != null) - { - ComHelpers.ReleaseComObject(ref _dispatch, !disposing); - _dispatch = null; - } - - if (_activeScriptParse != null) - { - _activeScriptParse.Dispose(); - _activeScriptParse = null; - } - - _activeScript = null; - } - } - - #region IActiveScriptSite implementation - - /// - /// Retrieves the locale identifier associated with the host's user interface. The scripting - /// engine uses the identifier to ensure that error strings and other user-interface elements - /// generated by the engine appear in the appropriate language. - /// - /// A variable that receives the locale identifier for user-interface - /// elements displayed by the scripting engine - public void GetLcid(out int lcid) - { - lcid = CultureInfo.CurrentCulture.LCID; - } - - /// - /// Allows the scripting engine to obtain information about an item added with the - /// IActiveScript.AddNamedItem method - /// - /// The name associated with the item, as specified in the - /// IActiveScript.AddNamedItem method - /// A bit mask specifying what information about the item should be - /// returned. The scripting engine should request the minimum amount of information possible - /// because some of the return parameters (for example, ITypeInfo) can take considerable - /// time to load or generate - /// A variable that receives a pointer to the IUnknown interface associated - /// with the given item. The scripting engine can use the IUnknown.QueryInterface method to - /// obtain the IDispatch interface for the item. This parameter receives null if mask - /// does not include the ScriptInfo.IUnknown value. Also, it receives null if there is no - /// object associated with the item name; this mechanism is used to create a simple class when - /// the named item was added with the ScriptItem.CodeOnly flag set in the - /// IActiveScript.AddNamedItem method. - /// A variable that receives a pointer to the ITypeInfo interface - /// associated with the item. This parameter receives null if mask does not include the - /// ScriptInfo.ITypeInfo value, or if type information is not available for this item. If type - /// information is not available, the object cannot source events, and name binding must be - /// realized with the IDispatch.GetIDsOfNames method. Note that the ITypeInfo interface - /// retrieved describes the item's coclass (TKIND_COCLASS) because the object may support - /// multiple interfaces and event interfaces. If the item supports the IProvideMultipleTypeInfo - /// interface, the ITypeInfo interface retrieved is the same as the index zero ITypeInfo that - /// would be obtained using the IProvideMultipleTypeInfo.GetInfoOfIndex method. - public void GetItemInfo(string name, ScriptInfoFlags mask, ref IntPtr pUnkItem, ref IntPtr pTypeInfo) - { - object item = GetItem(name); - if (item == null) - { - throw new COMException( - string.Format(Strings.Runtime_ItemNotFound, name), ComErrorCode.ElementNotFound); - } - - if (mask.HasFlag(ScriptInfoFlags.IUnknown)) - { - pUnkItem = Marshal.GetIDispatchForObject(item); - } - - if (mask.HasFlag(ScriptInfoFlags.ITypeInfo)) - { - pTypeInfo = Marshal.GetITypeInfoForType(item.GetType()); - } - } - - /// - /// Retrieves a host-defined string that uniquely identifies the current document version. If - /// the related document has changed outside the scope of Windows Script (as in the case of an - /// HTML page being edited with Notepad), the scripting engine can save this along with its - /// persisted state, forcing a recompile the next time the script is loaded. - /// - /// The host-defined document version string - public void GetDocVersionString(out string version) - { - version = DocumentVersion; - } - - /// - /// Informs the host that the script has completed execution - /// - /// A variable that contains the script result, or null if the script - /// produced no result - /// Contains exception information generated when the script - /// terminated, or null if no exception was generated - public virtual void OnScriptTerminate(object result, EXCEPINFO exceptionInfo) - { } - - /// - /// Informs the host that the scripting engine has changed states - /// - /// Indicates the new script state - public virtual void OnStateChange(ScriptState scriptState) - { } - - /// - /// Informs the host that an execution error occurred while the engine was running the script. - /// - /// A host can use this interface to obtain information about the - /// execution error - public void OnScriptError(IActiveScriptError scriptError) - { - _lastException = ActiveScriptException.Create(scriptError); - OnScriptError(_lastException); - } - - /// - /// Informs the host that an execution error occurred while the engine was running the script - /// - /// The exception - protected virtual void OnScriptError(ActiveScriptException exception) - { } - - /// - /// Informs the host that the scripting engine has begun executing the script code - /// - public virtual void OnEnterScript() - { } - - /// - /// Informs the host that the scripting engine has returned from executing script code - /// - public virtual void OnLeaveScript() - { } - - #endregion - - #region IDisposable implementation - - /// - /// Destroys object - /// - public void Dispose() - { - Dispose(true /* disposing */); - GC.SuppressFinalize(this); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/MsieJavaScriptEngine/Constants/SpecialMemberName.cs b/src/MsieJavaScriptEngine/Constants/SpecialMemberName.cs new file mode 100644 index 0000000..5415c70 --- /dev/null +++ b/src/MsieJavaScriptEngine/Constants/SpecialMemberName.cs @@ -0,0 +1,10 @@ +namespace MsieJavaScriptEngine.Constants +{ + /// + /// Special member names + /// + internal static class SpecialMemberName + { + public const string Default = "[DISPID=0]"; + } +} \ No newline at end of file diff --git a/src/MsieJavaScriptEngine/HostItemBase.cs b/src/MsieJavaScriptEngine/HostItemBase.cs new file mode 100644 index 0000000..ad1970f --- /dev/null +++ b/src/MsieJavaScriptEngine/HostItemBase.cs @@ -0,0 +1,184 @@ +namespace MsieJavaScriptEngine +{ + using System; + using System.Globalization; + using System.Linq; + using System.Reflection; + + /// + /// Base class of item, that implements interface + /// + internal abstract class HostItemBase : IReflect + { + /// + /// Target type + /// + protected readonly Type _type; + + /// + /// Target object + /// + protected readonly object _target; + + /// + /// JavaScript engine mode + /// + protected readonly JsEngineMode _engineMode; + + /// + /// List of fields + /// + private readonly FieldInfo[] _fields; + + /// + /// List of properties + /// + private readonly PropertyInfo[] _properties; + + /// + /// List of methods + /// + private readonly MethodInfo[] _methods; + + /// + /// Gets a target object + /// + public object Target + { + get { return _target; } + } + + + /// + /// Constructs an instance of the wrapper for item, that implements interface + /// + /// Target type + /// Target object + /// JavaScript engine mode + /// Flag for whether to allow access to members of the instance + protected HostItemBase(Type type, object target, JsEngineMode engineMode, bool instance) + { + _type = type; + _target = target; + _engineMode = engineMode; + + BindingFlags bindingFlags = BindingFlags.Public; + if (instance) + { + bindingFlags |= BindingFlags.Instance; + } + else + { + bindingFlags |= BindingFlags.Static; + } + + _fields = _type.GetFields(bindingFlags); + _properties = _type.GetProperties(bindingFlags); + _methods = _type.GetMethods(bindingFlags); + } + + + protected abstract object InnerInvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, + object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters); + + protected object InvokeStandardMember(string name, BindingFlags invokeAttr, Binder binder, object target, + object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters) + { + BindingFlags processedInvokeAttr = invokeAttr; + if ((processedInvokeAttr.HasFlag(BindingFlags.GetProperty) + || processedInvokeAttr.HasFlag(BindingFlags.PutDispProperty)) + && !_properties.Any(p => p.Name == name) + && _fields.Any(p => p.Name == name)) + { + if (processedInvokeAttr.HasFlag(BindingFlags.GetProperty)) + { + processedInvokeAttr &= ~BindingFlags.GetProperty; + processedInvokeAttr |= BindingFlags.GetField; + } + else if (processedInvokeAttr.HasFlag(BindingFlags.PutDispProperty)) + { + processedInvokeAttr &= ~BindingFlags.PutDispProperty; + processedInvokeAttr |= BindingFlags.SetField; + } + } + + object result = _type.InvokeMember(name, processedInvokeAttr, binder, target, + args, modifiers, culture, namedParameters); + + return result; + } + + #region IReflect implementation + + Type IReflect.UnderlyingSystemType + { + get { throw new NotImplementedException(); } + } + + + FieldInfo IReflect.GetField(string name, BindingFlags bindingAttr) + { + FieldInfo field = _fields.SingleOrDefault(f => f.Name == name); + + return field; + } + + FieldInfo[] IReflect.GetFields(BindingFlags bindingAttr) + { + return _fields; + } + + MemberInfo[] IReflect.GetMember(string name, BindingFlags bindingAttr) + { + throw new NotImplementedException(); + } + + MemberInfo[] IReflect.GetMembers(BindingFlags bindingAttr) + { + throw new NotImplementedException(); + } + + MethodInfo IReflect.GetMethod(string name, BindingFlags bindingAttr) + { + MethodInfo method = _methods.SingleOrDefault(m => m.Name == name); + + return method; + } + + MethodInfo IReflect.GetMethod(string name, BindingFlags bindingAttr, Binder binder, Type[] types, ParameterModifier[] modifiers) + { + throw new NotImplementedException(); + } + + MethodInfo[] IReflect.GetMethods(BindingFlags bindingAttr) + { + return _methods; + } + + PropertyInfo[] IReflect.GetProperties(BindingFlags bindingAttr) + { + return _properties; + } + + PropertyInfo IReflect.GetProperty(string name, BindingFlags bindingAttr) + { + PropertyInfo property = _properties.SingleOrDefault(p => p.Name == name); + + return property; + } + + PropertyInfo IReflect.GetProperty(string name, BindingFlags bindingAttr, Binder binder, + Type returnType, Type[] types, ParameterModifier[] modifiers) + { + throw new NotImplementedException(); + } + + object IReflect.InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, + object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters) + { + return InnerInvokeMember(name, invokeAttr, binder,target, args, modifiers, culture, namedParameters); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/MsieJavaScriptEngine/HostObject.cs b/src/MsieJavaScriptEngine/HostObject.cs index 44b8688..1c19248 100644 --- a/src/MsieJavaScriptEngine/HostObject.cs +++ b/src/MsieJavaScriptEngine/HostObject.cs @@ -5,172 +5,60 @@ using System.Linq; using System.Reflection; + using Constants; using Helpers; /// /// Wrapper for object, that implements interface /// - internal class HostObject : IReflect + internal sealed class HostObject : HostItemBase { - /// - /// Target object - /// - private readonly object _target; - - /// - /// Target type - /// - private readonly Type _type; - - /// - /// JavaScript engine mode - /// - private readonly JsEngineMode _engineMode; - - /// - /// List of fields - /// - private readonly FieldInfo[] _fields; - - /// - /// List of properties - /// - private readonly PropertyInfo[] _properties; - - /// - /// List of methods - /// - private readonly MethodInfo[] _methods; - - /// - /// Gets a target object - /// - public object Target - { - get { return _target; } - } - - /// /// Constructs an instance of the wrapper for object, that implements interface /// /// Target object /// JavaScript engine mode public HostObject(object target, JsEngineMode engineMode) - { - _target = target; - _type = target.GetType(); - _engineMode = engineMode; - - var defaultBindingFlags = BindingFlags.Instance | BindingFlags.Public; - _fields = _type.GetFields(defaultBindingFlags); - _properties = _type.GetProperties(defaultBindingFlags); - _methods = _type.GetMethods(defaultBindingFlags); - } - - #region IReflect implementation - - Type IReflect.UnderlyingSystemType - { - get { throw new NotImplementedException(); } - } + : base(target.GetType(), target, engineMode, true) + { } - FieldInfo IReflect.GetField(string name, BindingFlags bindingAttr) + private object InvokeDelegate(Delegate del, object[] args) { - FieldInfo field = _fields.SingleOrDefault(f => f.Name == name); - - return field; - } - - FieldInfo[] IReflect.GetFields(BindingFlags bindingAttr) - { - return _fields; - } - - MemberInfo[] IReflect.GetMember(string name, BindingFlags bindingAttr) - { - throw new NotImplementedException(); - } - - MemberInfo[] IReflect.GetMembers(BindingFlags bindingAttr) - { - throw new NotImplementedException(); - } - - MethodInfo IReflect.GetMethod(string name, BindingFlags bindingAttr) - { - MethodInfo method = _methods.SingleOrDefault(m => m.Name == name); - - return method; - } - - MethodInfo IReflect.GetMethod(string name, BindingFlags bindingAttr, Binder binder, Type[] types, ParameterModifier[] modifiers) - { - throw new NotImplementedException(); - } + if (del == null) + { + throw new ArgumentNullException("del"); + } - MethodInfo[] IReflect.GetMethods(BindingFlags bindingAttr) - { - return _methods; - } + object[] processedArgs = args; - PropertyInfo[] IReflect.GetProperties(BindingFlags bindingAttr) - { - return _properties; - } + if (_engineMode == JsEngineMode.Classic && processedArgs.Length > 0) + { + processedArgs = processedArgs.Skip(1).ToArray(); + } - PropertyInfo IReflect.GetProperty(string name, BindingFlags bindingAttr) - { - PropertyInfo property = _properties.SingleOrDefault(p => p.Name == name); + object result = del.DynamicInvoke(processedArgs); - return property; + return result; } - PropertyInfo IReflect.GetProperty(string name, BindingFlags bindingAttr, Binder binder, - Type returnType, Type[] types, ParameterModifier[] modifiers) - { - throw new NotImplementedException(); - } + #region HostItemBase implementation - object IReflect.InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, + protected override object InnerInvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters) { object result; object processedTarget = TypeMappingHelpers.MapToHostType(target); object[] processedArgs = TypeMappingHelpers.MapToHostType(args); - var del = processedTarget as Delegate; - if (del != null) + if (name == SpecialMemberName.Default && processedTarget is Delegate) { - if (_engineMode == JsEngineMode.Classic && processedArgs.Length > 0) - { - processedArgs = processedArgs.Skip(1).ToArray(); - } - - result = del.DynamicInvoke(processedArgs); + var del = (Delegate)processedTarget; + result = InvokeDelegate(del, processedArgs); } else { - BindingFlags processedInvokeAttr = invokeAttr; - if ((processedInvokeAttr.HasFlag(BindingFlags.GetProperty) - || processedInvokeAttr.HasFlag(BindingFlags.PutDispProperty)) - && !_properties.Any(p => p.Name == name) - && _fields.Any(p => p.Name == name)) - { - if (processedInvokeAttr.HasFlag(BindingFlags.GetProperty)) - { - processedInvokeAttr &= ~BindingFlags.GetProperty; - processedInvokeAttr |= BindingFlags.GetField; - } - else if (processedInvokeAttr.HasFlag(BindingFlags.PutDispProperty)) - { - processedInvokeAttr &= ~BindingFlags.PutDispProperty; - processedInvokeAttr |= BindingFlags.SetField; - } - } - - result = _type.InvokeMember(name, processedInvokeAttr, binder, processedTarget, + result = InvokeStandardMember(name, invokeAttr, binder, processedTarget, processedArgs, modifiers, culture, namedParameters); } diff --git a/src/MsieJavaScriptEngine/HostType.cs b/src/MsieJavaScriptEngine/HostType.cs new file mode 100644 index 0000000..4cdbf31 --- /dev/null +++ b/src/MsieJavaScriptEngine/HostType.cs @@ -0,0 +1,54 @@ +namespace MsieJavaScriptEngine +{ + using System; + using System.Globalization; + using System.Linq; + using System.Reflection; + + using Constants; + using Helpers; + + /// + /// Wrapper for type, that implements interface + /// + internal sealed class HostType : HostItemBase + { + /// + /// Constructs an instance of the wrapper for type, that implements interface + /// + /// Target type + /// JavaScript engine mode + public HostType(Type type, JsEngineMode engineMode) + : base(type, null, engineMode, false) + { } + + + #region HostItemBase implementation + + protected override object InnerInvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, + object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters) + { + object[] processedArgs = TypeMappingHelpers.MapToHostType(args); + object result; + + if (name == SpecialMemberName.Default && invokeAttr.HasFlag(BindingFlags.CreateInstance)) + { + if (_engineMode != JsEngineMode.Classic && processedArgs.Length > 0) + { + processedArgs = processedArgs.Skip(1).ToArray(); + } + + result = Activator.CreateInstance(_type, processedArgs); + } + else + { + result = InvokeStandardMember(name, invokeAttr, binder, target, + processedArgs, modifiers, culture, namedParameters); + } + + return TypeMappingHelpers.MapToScriptType(result, _engineMode); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/MsieJavaScriptEngine/IInnerJsEngine.cs b/src/MsieJavaScriptEngine/IInnerJsEngine.cs index 205494d..a733dc1 100644 --- a/src/MsieJavaScriptEngine/IInnerJsEngine.cs +++ b/src/MsieJavaScriptEngine/IInnerJsEngine.cs @@ -68,5 +68,16 @@ internal interface IInnerJsEngine : IDisposable /// The object to expose /// Allows to embed instances of simple classes (or structures) and delegates. void EmbedHostObject(string itemName, object value); + + /// + /// Embeds a host type to script code + /// + /// The name for the new global variable that will represent the type + /// The type to expose + /// + /// Host types are exposed to script code in the form of objects whose properties and + /// methods are bound to the type's static members. + /// + void EmbedHostType(string itemName, Type type); } } \ No newline at end of file diff --git a/src/MsieJavaScriptEngine/JsRt/ChakraJsRtJsEngineBase.cs b/src/MsieJavaScriptEngine/JsRt/ChakraJsRtJsEngineBase.cs index f0c17d9..ca92f4e 100644 --- a/src/MsieJavaScriptEngine/JsRt/ChakraJsRtJsEngineBase.cs +++ b/src/MsieJavaScriptEngine/JsRt/ChakraJsRtJsEngineBase.cs @@ -1,5 +1,7 @@ namespace MsieJavaScriptEngine.JsRt { + using System; + using Helpers; /// @@ -76,6 +78,8 @@ protected void StartDebugging() public abstract void EmbedHostObject(string itemName, object value); + public abstract void EmbedHostType(string itemName, Type type); + #endregion #region IDisposable implementation diff --git a/src/MsieJavaScriptEngine/JsRt/Edge/ChakraEdgeJsRtJsEngine.cs b/src/MsieJavaScriptEngine/JsRt/Edge/ChakraEdgeJsRtJsEngine.cs index f014f31..a63bbb2 100644 --- a/src/MsieJavaScriptEngine/JsRt/Edge/ChakraEdgeJsRtJsEngine.cs +++ b/src/MsieJavaScriptEngine/JsRt/Edge/ChakraEdgeJsRtJsEngine.cs @@ -507,6 +507,17 @@ public override void EmbedHostObject(string itemName, object value) }); } + public override void EmbedHostType(string itemName, Type type) + { + InvokeScript(() => + { + EdgeJsValue typeValue = EdgeJsValue.FromObject(new HostType(type, _engineMode)); + EdgeJsPropertyId itemId = EdgeJsPropertyId.FromString(itemName); + + EdgeJsValue.GlobalObject.SetProperty(itemId, typeValue, true); + }); + } + #endregion #region IDisposable implementation diff --git a/src/MsieJavaScriptEngine/JsRt/Ie/ChakraIeJsRtJsEngine.cs b/src/MsieJavaScriptEngine/JsRt/Ie/ChakraIeJsRtJsEngine.cs index f65ebae..1696b76 100644 --- a/src/MsieJavaScriptEngine/JsRt/Ie/ChakraIeJsRtJsEngine.cs +++ b/src/MsieJavaScriptEngine/JsRt/Ie/ChakraIeJsRtJsEngine.cs @@ -543,6 +543,17 @@ public override void EmbedHostObject(string itemName, object value) }); } + public override void EmbedHostType(string itemName, Type type) + { + InvokeScript(() => + { + IeJsValue typeValue = IeJsValue.FromObject(new HostType(type, _engineMode)); + IeJsPropertyId itemId = IeJsPropertyId.FromString(itemName); + + IeJsValue.GlobalObject.SetProperty(itemId, typeValue, true); + }); + } + #endregion #region IDisposable implementation diff --git a/src/MsieJavaScriptEngine/MsieJavaScriptEngine.csproj b/src/MsieJavaScriptEngine/MsieJavaScriptEngine.csproj index 514cbf7..ad6c1b0 100644 --- a/src/MsieJavaScriptEngine/MsieJavaScriptEngine.csproj +++ b/src/MsieJavaScriptEngine/MsieJavaScriptEngine.csproj @@ -44,7 +44,6 @@ - @@ -67,12 +66,15 @@ + + + diff --git a/src/MsieJavaScriptEngine/MsieJsEngine.cs b/src/MsieJavaScriptEngine/MsieJsEngine.cs index 7102273..db1ce4e 100644 --- a/src/MsieJavaScriptEngine/MsieJsEngine.cs +++ b/src/MsieJavaScriptEngine/MsieJsEngine.cs @@ -658,7 +658,7 @@ public void EmbedHostObject(string itemName, object value) || itemType == typeof (Undefined)) { throw new NotSupportedTypeException( - string.Format(Strings.Runtime_EmbeddedHostObjectTypeNotSupported, itemType.FullName)); + string.Format(Strings.Runtime_EmbeddedHostObjectTypeNotSupported, itemName, itemType.FullName)); } } else @@ -669,6 +669,48 @@ public void EmbedHostObject(string itemName, object value) _jsEngine.EmbedHostObject(itemName, value); } + /// + /// Embeds a host type to script code + /// + /// The name for the new global variable that will represent the type + /// The type to expose + /// + /// Host types are exposed to script code in the form of objects whose properties and + /// methods are bound to the type's static members. + /// + public void EmbedHostType(string itemName, Type type) + { + VerifyNotDisposed(); + + if (string.IsNullOrWhiteSpace(itemName)) + { + throw new ArgumentException( + string.Format(Strings.Common_ArgumentIsEmpty, "itemName"), "itemName"); + } + + if (!ValidationHelpers.CheckNameFormat(itemName)) + { + throw new FormatException( + string.Format(Strings.Runtime_InvalidScriptItemNameFormat, itemName)); + } + + if (type != null) + { + if (ValidationHelpers.IsPrimitiveType(type) + || type == typeof(Undefined)) + { + throw new NotSupportedTypeException( + string.Format(Strings.Runtime_EmbeddedHostTypeNotSupported, type.FullName)); + } + } + else + { + throw new ArgumentNullException("type", string.Format(Strings.Common_ArgumentIsNull, "type")); + } + + _jsEngine.EmbedHostType(itemName, type); + } + #region IDisposable implementation /// diff --git a/src/MsieJavaScriptEngine/Resources/Strings.Designer.cs b/src/MsieJavaScriptEngine/Resources/Strings.Designer.cs index d0b7699..e8a951e 100644 --- a/src/MsieJavaScriptEngine/Resources/Strings.Designer.cs +++ b/src/MsieJavaScriptEngine/Resources/Strings.Designer.cs @@ -234,6 +234,15 @@ internal static string Runtime_EmbeddedHostObjectTypeNotSupported { } } + /// + /// Looks up a localized string similar to The embedded host type `{0}` is not supported.. + /// + internal static string Runtime_EmbeddedHostTypeNotSupported { + get { + return ResourceManager.GetString("Runtime_EmbeddedHostTypeNotSupported", resourceCulture); + } + } + /// /// Looks up a localized string similar to The function name '{0}' is forbidden, as is contained in the list of reserved words of JavaScript language.. /// diff --git a/src/MsieJavaScriptEngine/Resources/Strings.resx b/src/MsieJavaScriptEngine/Resources/Strings.resx index a41d230..a9b9aca 100644 --- a/src/MsieJavaScriptEngine/Resources/Strings.resx +++ b/src/MsieJavaScriptEngine/Resources/Strings.resx @@ -177,6 +177,9 @@ See more details: The embedded host object '{0}' has a type `{1}`, which is not supported. + + The embedded host type `{0}` is not supported. + The function name '{0}' is forbidden, as is contained in the list of reserved words of JavaScript language. diff --git a/src/MsieJavaScriptEngine/Resources/Strings.ru-ru.resx b/src/MsieJavaScriptEngine/Resources/Strings.ru-ru.resx index bde2624..cc53d1f 100644 --- a/src/MsieJavaScriptEngine/Resources/Strings.ru-ru.resx +++ b/src/MsieJavaScriptEngine/Resources/Strings.ru-ru.resx @@ -177,6 +177,9 @@ Встраиваемый объекта хоста "{0}" имеет тип `{1}`, который не поддерживается! + + Встраиваемый тип хоста `{0}` не поддерживается! + Имя функции "{0}" запрещено, т.к. входит в список зарезервированных слов JavaScript! diff --git a/test/MsieJavaScriptEngine.Test.Common/Interop/Base64Encoder.cs b/test/MsieJavaScriptEngine.Test.Common/Interop/Base64Encoder.cs new file mode 100644 index 0000000..bbc5e6f --- /dev/null +++ b/test/MsieJavaScriptEngine.Test.Common/Interop/Base64Encoder.cs @@ -0,0 +1,16 @@ +namespace MsieJavaScriptEngine.Test.Common.Interop +{ + using System; + using System.Text; + + public static class Base64Encoder + { + public const int DATA_URI_MAX = 32768; + + + public static string Encode(string value) + { + return Convert.ToBase64String(Encoding.Default.GetBytes(value)); + } + } +} \ No newline at end of file diff --git a/test/MsieJavaScriptEngine.Test.Common/Interop/BundleTable.cs b/test/MsieJavaScriptEngine.Test.Common/Interop/BundleTable.cs new file mode 100644 index 0000000..2b7d887 --- /dev/null +++ b/test/MsieJavaScriptEngine.Test.Common/Interop/BundleTable.cs @@ -0,0 +1,19 @@ +namespace MsieJavaScriptEngine.Test.Common.Interop +{ + public static class BundleTable + { + private static bool _enableOptimizations = true; + + public static bool EnableOptimizations + { + get + { + return _enableOptimizations; + } + set + { + _enableOptimizations = value; + } + } + } +} \ No newline at end of file diff --git a/test/MsieJavaScriptEngine.Test.Common/Interop/Date.cs b/test/MsieJavaScriptEngine.Test.Common/Interop/Date.cs index 73da9de..c6c4be4 100644 --- a/test/MsieJavaScriptEngine.Test.Common/Interop/Date.cs +++ b/test/MsieJavaScriptEngine.Test.Common/Interop/Date.cs @@ -1,5 +1,7 @@ namespace MsieJavaScriptEngine.Test.Common.Interop { + using System; + public struct Date { private static readonly int[] _cumulativeDays = { 0, 31, 59, 90, 120, 151, 181, @@ -9,6 +11,17 @@ public struct Date public int Month; public int Day; + public static Date Today + { + get + { + DateTime currentDateTime = DateTime.Today; + Date currentDate = new Date(currentDateTime.Year, currentDateTime.Month, currentDateTime.Day); + + return currentDate; + } + } + public Date(int year, int month, int day) { diff --git a/test/MsieJavaScriptEngine.Test.Common/Interop/Person.cs b/test/MsieJavaScriptEngine.Test.Common/Interop/Person.cs index 294a17c..c5d3862 100644 --- a/test/MsieJavaScriptEngine.Test.Common/Interop/Person.cs +++ b/test/MsieJavaScriptEngine.Test.Common/Interop/Person.cs @@ -24,5 +24,11 @@ public Person(string firstName, string lastName) FirstName = firstName; LastName = lastName; } + + + public override string ToString() + { + return string.Format("{{FirstName={0},LastName={1}}}", FirstName, LastName); + } } } \ No newline at end of file diff --git a/test/MsieJavaScriptEngine.Test.Common/Interop/Point3D.cs b/test/MsieJavaScriptEngine.Test.Common/Interop/Point3D.cs new file mode 100644 index 0000000..ee76d89 --- /dev/null +++ b/test/MsieJavaScriptEngine.Test.Common/Interop/Point3D.cs @@ -0,0 +1,25 @@ +namespace MsieJavaScriptEngine.Test.Common.Interop +{ + public struct Point3D + { + public int X; + public int Y; + public int Z; + + public static readonly Point3D Empty = new Point3D(); + + + public Point3D(int x, int y, int z) + { + X = x; + Y = y; + Z = z; + } + + + public override string ToString() + { + return string.Format("{{X={0},Y={1},Z={2}}}", X, Y, Z); + } + } +} \ No newline at end of file diff --git a/test/MsieJavaScriptEngine.Test.Common/Interop/PredefinedStrings.cs b/test/MsieJavaScriptEngine.Test.Common/Interop/PredefinedStrings.cs new file mode 100644 index 0000000..efca32e --- /dev/null +++ b/test/MsieJavaScriptEngine.Test.Common/Interop/PredefinedStrings.cs @@ -0,0 +1,9 @@ +namespace MsieJavaScriptEngine.Test.Common.Interop +{ + public struct PredefinedStrings + { + public const string VeryLongName = "Very Long Name"; + public const string AnotherVeryLongName = "Another Very Long Name"; + public const string TheLastVeryLongName = "The Last Very Long Name"; + } +} \ No newline at end of file diff --git a/test/MsieJavaScriptEngine.Test.Common/Interop/SimpleSingleton.cs b/test/MsieJavaScriptEngine.Test.Common/Interop/SimpleSingleton.cs new file mode 100644 index 0000000..14d1ed4 --- /dev/null +++ b/test/MsieJavaScriptEngine.Test.Common/Interop/SimpleSingleton.cs @@ -0,0 +1,17 @@ +namespace MsieJavaScriptEngine.Test.Common.Interop +{ + public class SimpleSingleton + { + public static readonly SimpleSingleton Instance = new SimpleSingleton(); + + + private SimpleSingleton() + { } + + + public override string ToString() + { + return "[simple singleton]"; + } + } +} \ No newline at end of file diff --git a/test/MsieJavaScriptEngine.Test.Common/InteropTestsBase.cs b/test/MsieJavaScriptEngine.Test.Common/InteropTestsBase.cs index cdbae5c..8f57899 100644 --- a/test/MsieJavaScriptEngine.Test.Common/InteropTestsBase.cs +++ b/test/MsieJavaScriptEngine.Test.Common/InteropTestsBase.cs @@ -486,5 +486,486 @@ public virtual void InteractionOfEmbeddedCustomValueTypeAndDelegateInstancesIsCo #endregion #endregion + + + #region Embedding of types + + #region Creating of instances + + [Test] + public virtual void CreatingAnInstanceOfEmbeddedBuiltinValueTypeIsCorrect() + { + // Arrange + Type pointType = typeof(Point); + + const string input = "(new Point()).ToString()"; + const string targetOutput = "{X=0,Y=0}"; + + // Act + string output; + + using (var jsEngine = CreateJsEngine()) + { + jsEngine.EmbedHostType("Point", pointType); + output = jsEngine.Evaluate(input); + } + + // Assert + Assert.AreEqual(targetOutput, output); + } + + [Test] + public virtual void CreatingAnInstanceOfEmbeddedBuiltinReferenceTypeIsCorrect() + { + // Arrange + Type uriType = typeof(Uri); + + const string input = @"var baseUri = new Uri('https://github.com'), + relativeUri = 'Taritsyn/MsieJavaScriptEngine' + ; + +(new Uri(baseUri, relativeUri)).ToString()"; + const string targetOutput = "https://github.com/Taritsyn/MsieJavaScriptEngine"; + + // Act + string output; + + using (var jsEngine = CreateJsEngine()) + { + jsEngine.EmbedHostType("Uri", uriType); + output = jsEngine.Evaluate(input); + } + + // Assert + Assert.AreEqual(targetOutput, output); + } + + [Test] + public virtual void CreatingAnInstanceOfEmbeddedCustomValueTypeIsCorrect() + { + // Arrange + Type point3DType = typeof(Point3D); + + const string input = "(new Point3D(2, 5, 14)).ToString()"; + const string targetOutput = "{X=2,Y=5,Z=14}"; + + // Act + string output; + + using (var jsEngine = CreateJsEngine()) + { + jsEngine.EmbedHostType("Point3D", point3DType); + output = jsEngine.Evaluate(input); + } + + // Assert + Assert.AreEqual(targetOutput, output); + } + + [Test] + public virtual void CreatingAnInstanceOfEmbeddedCustomReferenceTypeIsCorrect() + { + // Arrange + Type personType = typeof(Person); + + const string input = "(new Person('Vanya', 'Tutkin')).ToString()"; + const string targetOutput = "{FirstName=Vanya,LastName=Tutkin}"; + + // Act + string output; + + using (var jsEngine = CreateJsEngine()) + { + jsEngine.EmbedHostType("Person", personType); + output = jsEngine.Evaluate(input); + } + + // Assert + Assert.AreEqual(targetOutput, output); + } + + #endregion + + #region Types with constants + + [Test] + public virtual void EmbeddingOfBuiltinReferenceTypeWithConstantsIsCorrect() + { + // Arrange + Type mathType = typeof(Math); + + const string input1 = "Math2.PI"; + const double targetOutput1 = 3.1415926535897931d; + + const string input2 = "Math2.E"; + const double targetOutput2 = 2.7182818284590451d; + + // Act + double output1; + double output2; + + using (var jsEngine = CreateJsEngine()) + { + jsEngine.EmbedHostType("Math2", mathType); + + output1 = jsEngine.Evaluate(input1); + output2 = jsEngine.Evaluate(input2); + } + + // Assert + Assert.AreEqual(targetOutput1, output1); + Assert.AreEqual(targetOutput2, output2); + } + + [Test] + public virtual void EmbeddingOfCustomValueTypeWithConstantsIsCorrect() + { + // Arrange + Type predefinedStringsType = typeof(PredefinedStrings); + + const string input1 = "PredefinedStrings.VeryLongName"; + const string targetOutput1 = "Very Long Name"; + + const string input2 = "PredefinedStrings.AnotherVeryLongName"; + const string targetOutput2 = "Another Very Long Name"; + + const string input3 = "PredefinedStrings.TheLastVeryLongName"; + const string targetOutput3 = "The Last Very Long Name"; + + // Act + string output1; + string output2; + string output3; + + using (var jsEngine = CreateJsEngine()) + { + jsEngine.EmbedHostType("PredefinedStrings", predefinedStringsType); + + output1 = jsEngine.Evaluate(input1); + output2 = jsEngine.Evaluate(input2); + output3 = jsEngine.Evaluate(input3); + } + + // Assert + Assert.AreEqual(targetOutput1, output1); + Assert.AreEqual(targetOutput2, output2); + Assert.AreEqual(targetOutput3, output3); + } + + [Test] + public virtual void EmbeddingOfCustomReferenceTypeWithConstantIsCorrect() + { + // Arrange + Type base64EncoderType = typeof(Base64Encoder); + + const string input = "Base64Encoder.DATA_URI_MAX"; + const int targetOutput = 32768; + + // Act + int output; + + using (var jsEngine = CreateJsEngine()) + { + jsEngine.EmbedHostType("Base64Encoder", base64EncoderType); + output = jsEngine.Evaluate(input); + } + + // Assert + Assert.AreEqual(targetOutput, output); + } + + #endregion + + #region Types with fields + + [Test] + public virtual void EmbeddingOfBuiltinValueTypeWithFieldIsCorrect() + { + // Arrange + Type guidType = typeof(Guid); + + const string input = "Guid.Empty.ToString()"; + const string targetOutput = "00000000-0000-0000-0000-000000000000"; + + // Act + string output; + + using (var jsEngine = CreateJsEngine()) + { + jsEngine.EmbedHostType("Guid", guidType); + output = jsEngine.Evaluate(input); + } + + // Assert + Assert.AreEqual(targetOutput, output); + } + + [Test] + public virtual void EmbeddingOfBuiltinReferenceTypeWithFieldIsCorrect() + { + // Arrange + Type bitConverterType = typeof(BitConverter); + + const string input = "BitConverter.IsLittleEndian"; + const bool targetOutput = true; + + // Act + bool output; + + using (var jsEngine = CreateJsEngine()) + { + jsEngine.EmbedHostType("BitConverter", bitConverterType); + output = (bool)jsEngine.Evaluate(input); + } + + // Assert + Assert.AreEqual(targetOutput, output); + } + + [Test] + public virtual void EmbeddingOfCustomValueTypeWithFieldIsCorrect() + { + // Arrange + Type point3DType = typeof(Point3D); + + const string input = "Point3D.Empty.ToString()"; + const string targetOutput = "{X=0,Y=0,Z=0}"; + + // Act + string output; + + using (var jsEngine = CreateJsEngine()) + { + jsEngine.EmbedHostType("Point3D", point3DType); + output = jsEngine.Evaluate(input); + } + + // Assert + Assert.AreEqual(targetOutput, output); + } + + [Test] + public virtual void EmbeddingOfCustomReferenceTypeWithFieldIsCorrect() + { + // Arrange + Type simpleSingletonType = typeof(SimpleSingleton); + + const string input = "SimpleSingleton.Instance.ToString()"; + const string targetOutput = "[simple singleton]"; + + // Act + string output; + + using (var jsEngine = CreateJsEngine()) + { + jsEngine.EmbedHostType("SimpleSingleton", simpleSingletonType); + output = jsEngine.Evaluate(input); + } + + // Assert + Assert.AreEqual(targetOutput, output); + } + + #endregion + + #region Types with properties + + [Test] + public virtual void EmbeddingOfBuiltinValueTypeWithPropertyIsCorrect() + { + // Arrange + Type colorType = typeof(Color); + + const string input = "Color.OrangeRed.ToString()"; + const string targetOutput = "Color [OrangeRed]"; + + // Act + string output; + + using (var jsEngine = CreateJsEngine()) + { + jsEngine.EmbedHostType("Color", colorType); + output = jsEngine.Evaluate(input); + } + + // Assert + Assert.AreEqual(targetOutput, output); + } + + [Test] + public virtual void EmbeddingOfBuiltinReferenceTypeWithPropertyIsCorrect() + { + // Arrange + Type environmentType = typeof(Environment); + + const string input = "Environment.NewLine"; + string[] targetOutput = { "\r", "\r\n", "\n", "\n\r" }; + + // Act + string output; + + using (var jsEngine = CreateJsEngine()) + { + jsEngine.EmbedHostType("Environment", environmentType); + output = jsEngine.Evaluate(input); + } + + // Assert + Assert.IsTrue(targetOutput.Contains(output)); + } + + [Test] + public virtual void EmbeddingOfCustomValueTypeWithPropertyIsCorrect() + { + // Arrange + Type dateType = typeof(Date); + + const string initCode = "var currentDate = Date2.Today;"; + + const string inputYear = "currentDate.Year"; + const string inputMonth = "currentDate.Month"; + const string inputDay = "currentDate.Day"; + + DateTime targetOutput = DateTime.Today; + + // Act + DateTime output; + + using (var jsEngine = CreateJsEngine()) + { + jsEngine.EmbedHostType("Date2", dateType); + jsEngine.Execute(initCode); + + var outputYear = jsEngine.Evaluate(inputYear); + var outputMonth = jsEngine.Evaluate(inputMonth); + var outputDay = jsEngine.Evaluate(inputDay); + + output = new DateTime(outputYear, outputMonth, outputDay); + } + + // Assert + Assert.AreEqual(targetOutput, output); + } + + [Test] + public virtual void EmbeddingOfCustomReferenceTypeWithPropertyIsCorrect() + { + // Arrange + Type bundleTableType = typeof(BundleTable); + const string updateCode = "BundleTable.EnableOptimizations = false;"; + + const string input = "BundleTable.EnableOptimizations"; + const bool targetOutput = false; + + // Act + bool output; + + using (var jsEngine = CreateJsEngine()) + { + jsEngine.EmbedHostType("BundleTable", bundleTableType); + jsEngine.Execute(updateCode); + + output = jsEngine.Evaluate(input); + } + + // Assert + Assert.AreEqual(targetOutput, output); + } + + #endregion + + #region Types with methods + + [Test] + public virtual void EmbeddingOfBuiltinValueTypeWithMethodIsCorrect() + { + // Arrange + Type dateTimeType = typeof(DateTime); + + const string input = "DateTime.DaysInMonth(2016, 2)"; + const int targetOutput = 29; + + // Act + int output; + + using (var jsEngine = CreateJsEngine()) + { + jsEngine.EmbedHostType("DateTime", dateTimeType); + output = jsEngine.Evaluate(input); + } + + // Assert + Assert.AreEqual(targetOutput, output); + } + + [Test] + public virtual void EmbeddingOfBuiltinReferenceTypeWithMethodIsCorrect() + { + // Arrange + Type mathType = typeof(Math); + + const string input = "Math2.Max(5.37, 5.56)"; + const double targetOutput = 5.56; + + // Act + double output; + + using (var jsEngine = CreateJsEngine()) + { + jsEngine.EmbedHostType("Math2", mathType); + output = jsEngine.Evaluate(input); + } + + // Assert + Assert.AreEqual(targetOutput, output); + } + + [Test] + public virtual void EmbeddingOfCustomValueTypeWithMethodIsCorrect() + { + // Arrange + var dateType = typeof(Date); + + const string input = "Date2.IsLeapYear(2016)"; + const bool targetOutput = true; + + // Act + bool output; + + using (var jsEngine = CreateJsEngine()) + { + jsEngine.EmbedHostType("Date2", dateType); + output = jsEngine.Evaluate(input); + } + + // Assert + Assert.AreEqual(targetOutput, output); + } + + [Test] + public virtual void EmbeddingOfCustomReferenceTypeWithMethodIsCorrect() + { + // Arrange + Type base64EncoderType = typeof(Base64Encoder); + + const string input = "Base64Encoder.Encode('https://github.com/Taritsyn/MsieJavaScriptEngine')"; + const string targetOutput = "aHR0cHM6Ly9naXRodWIuY29tL1Rhcml0c3luL01zaWVKYXZhU2NyaXB0RW5naW5l"; + + // Act + string output; + + using (var jsEngine = CreateJsEngine()) + { + jsEngine.EmbedHostType("Base64Encoder", base64EncoderType); + output = jsEngine.Evaluate(input); + } + + // Assert + Assert.AreEqual(targetOutput, output); + } + + #endregion + + #endregion } } \ No newline at end of file diff --git a/test/MsieJavaScriptEngine.Test.Common/MsieJavaScriptEngine.Test.Common.csproj b/test/MsieJavaScriptEngine.Test.Common/MsieJavaScriptEngine.Test.Common.csproj index b506e69..c724905 100644 --- a/test/MsieJavaScriptEngine.Test.Common/MsieJavaScriptEngine.Test.Common.csproj +++ b/test/MsieJavaScriptEngine.Test.Common/MsieJavaScriptEngine.Test.Common.csproj @@ -44,11 +44,16 @@ + + + + +