123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- using System.Globalization;
- using System.Reflection;
- using Jint.Extensions;
- using Jint.Native;
- using Jint.Native.Function;
- namespace Jint.Runtime.Interop
- {
- /// <summary>
- /// Represents a FunctionInstance wrapper around a CLR method. This is used by user to pass
- /// custom methods to the engine.
- /// </summary>
- public sealed class DelegateWrapper : FunctionInstance
- {
- private static readonly JsString _name = new JsString("delegate");
- private readonly Delegate _d;
- private readonly bool _delegateContainsParamsArgument;
- public DelegateWrapper(
- Engine engine, Delegate d)
- : base(engine, engine.Realm, _name, FunctionThisMode.Global)
- {
- _d = d;
- _prototype = engine.Realm.Intrinsics.Function.PrototypeObject;
- var parameterInfos = _d.Method.GetParameters();
- _delegateContainsParamsArgument = false;
- foreach (var p in parameterInfos)
- {
- if (Attribute.IsDefined(p, typeof(ParamArrayAttribute)))
- {
- _delegateContainsParamsArgument = true;
- break;
- }
- }
- }
- protected internal override JsValue Call(JsValue thisObject, JsValue[] arguments)
- {
- var parameterInfos = _d.Method.GetParameters();
- #if NETFRAMEWORK
- if (parameterInfos.Length > 0 && parameterInfos[0].ParameterType == typeof(System.Runtime.CompilerServices.Closure))
- {
- var reducedLength = parameterInfos.Length - 1;
- var reducedParameterInfos = new ParameterInfo[reducedLength];
- Array.Copy(parameterInfos, 1, reducedParameterInfos, 0, reducedLength);
- parameterInfos = reducedParameterInfos;
- }
- #endif
- int delegateArgumentsCount = parameterInfos.Length;
- int delegateNonParamsArgumentsCount = _delegateContainsParamsArgument ? delegateArgumentsCount - 1 : delegateArgumentsCount;
- int jsArgumentsCount = arguments.Length;
- int jsArgumentsWithoutParamsCount = Math.Min(jsArgumentsCount, delegateNonParamsArgumentsCount);
- var clrTypeConverter = Engine.TypeConverter;
- var valueCoercionType = Engine.Options.Interop.ValueCoercion;
- var parameters = new object?[delegateArgumentsCount];
- // convert non params parameter to expected types
- for (var i = 0; i < jsArgumentsWithoutParamsCount; i++)
- {
- var parameterType = parameterInfos[i].ParameterType;
- var value = arguments[i];
- object? converted;
- if (parameterType == typeof(JsValue))
- {
- converted = value;
- }
- else if (!ReflectionExtensions.TryConvertViaTypeCoercion(parameterType, valueCoercionType, value, out converted))
- {
- converted = clrTypeConverter.Convert(
- value.ToObject(),
- parameterType,
- CultureInfo.InvariantCulture);
- }
- parameters[i] = converted;
- }
- // assign null to parameters not provided
- for (var i = jsArgumentsWithoutParamsCount; i < delegateNonParamsArgumentsCount; i++)
- {
- if (parameterInfos[i].ParameterType.IsValueType)
- {
- parameters[i] = Activator.CreateInstance(parameterInfos[i].ParameterType);
- }
- else
- {
- parameters[i] = null;
- }
- }
- // assign params to array and converts each object to expected type
- if (_delegateContainsParamsArgument)
- {
- int paramsArgumentIndex = delegateArgumentsCount - 1;
- int paramsCount = Math.Max(0, jsArgumentsCount - delegateNonParamsArgumentsCount);
- var paramsParameterType = parameterInfos[paramsArgumentIndex].ParameterType.GetElementType();
- var paramsParameter = Array.CreateInstance(paramsParameterType!, paramsCount);
- for (var i = paramsArgumentIndex; i < jsArgumentsCount; i++)
- {
- var paramsIndex = i - paramsArgumentIndex;
- var value = arguments[i];
- object? converted;
- if (paramsParameterType == typeof(JsValue))
- {
- converted = value;
- }
- else if (!ReflectionExtensions.TryConvertViaTypeCoercion(paramsParameterType, valueCoercionType, value, out converted))
- {
- converted = Engine.TypeConverter.Convert(
- value.ToObject(),
- paramsParameterType!,
- CultureInfo.InvariantCulture);
- }
- paramsParameter.SetValue(converted, paramsIndex);
- }
- parameters[paramsArgumentIndex] = paramsParameter;
- }
- try
- {
- var result = _d.DynamicInvoke(parameters);
- if (result is not Task task)
- {
- return FromObject(Engine, result);
- }
- return ConvertTaskToPromise(task);
- }
- catch (TargetInvocationException exception)
- {
- ExceptionHelper.ThrowMeaningfulException(Engine, exception);
- throw;
- }
- }
- private JsValue ConvertTaskToPromise(Task task)
- {
- var (promise, resolve, reject) = Engine.RegisterPromise();
- task = task.ContinueWith(continuationAction =>
- {
- if (continuationAction.IsFaulted)
- {
- reject(FromObject(Engine, continuationAction.Exception));
- }
- else if (continuationAction.IsCanceled)
- {
- reject(FromObject(Engine, new ExecutionCanceledException()));
- }
- else
- {
- // Special case: Marshal `async Task` as undefined, as this is `Task<VoidTaskResult>` at runtime
- // See https://github.com/sebastienros/jint/pull/1567#issuecomment-1681987702
- if (Task.CompletedTask.Equals(continuationAction))
- {
- resolve(FromObject(Engine, JsValue.Undefined));
- return;
- }
- var result = continuationAction.GetType().GetProperty(nameof(Task<object>.Result));
- if (result is not null)
- {
- resolve(FromObject(Engine, result.GetValue(continuationAction)));
- }
- else
- {
- resolve(FromObject(Engine, JsValue.Undefined));
- }
- }
- });
- Engine.AddToEventLoop(() =>
- {
- if (!task.IsCompleted)
- {
- // Task.Wait has the potential of inlining the task's execution on the current thread; avoid this.
- ((IAsyncResult) task).AsyncWaitHandle.WaitOne();
- }
- });
- return promise;
- }
- }
- }
|