Beispiel #1
0
        /// <summary>
        /// Creates a <see cref="JavaScriptValue"/> representing the host delegate.
        /// </summary>
        /// <remarks>This call requires an active context.</remarks>
        public JavaScriptValue ToJsFunction(object obj, Type type)
        {
            var invokeInfo = type.GetMethod("Invoke");

            if (null == invokeInfo)
            {
                throw new Exception($"Cannot convert delegate of type: {type} to JavaScript function.");
            }

            JavaScriptNativeFunction func = (v, s, args, argLength, data) =>
            {
                var parameters = invokeInfo.GetParameters();
                var realParams = new object[parameters.Length];
                var argIndex   = 1;
                for (int j = 0; j < parameters.Length; ++j)
                {
                    var arg   = args[argIndex++];
                    var param = parameters[j];

                    realParams[j] = ToHostObject(arg, param.ParameterType);
                }

                var result     = invokeInfo.Invoke(obj, realParams);
                var resultType = invokeInfo.ReturnType;
                if (resultType == typeof(void))
                {
                    return(JavaScriptValue.Invalid);
                }

                return(ToJsObject(result, JsConversions.TypeFor(result, resultType)));
            };

            return(_binder.BindFunction(func));
        }
Beispiel #2
0
        /// <summary>
        /// Binds all the fields for the instance.
        /// </summary>
        private void BindFields(JsBinding binding, IHostType hostType, object instance)
        {
            // TODO: Seems like we can hoist this into JsInterop, which would allow us refactor
            // TODO: out the builder class completely.
            var fields = hostType.FieldNames;

            for (int i = 0; i < fields.Count; ++i)
            {
                var fieldName = fields[i];

                binding.AddProperty(
                    fieldName,
                    (v, s, args, argLength, data) =>
                {
                    var fieldInfo = hostType.FieldFor(fieldName).Field;
                    var result    = fieldInfo.GetValue(instance);

                    var returnType = JsConversions.TypeFor(result, fieldInfo.FieldType);
                    return(_interop.ToJsObject(result, returnType));
                },
                    (v, s, args, argLength, data) =>
                {
                    var fieldInfo = hostType.FieldFor(fieldName).Field;
                    var fieldType = fieldInfo.FieldType;

                    var value = _interop.ToHostObject(args[1], fieldType);
                    fieldInfo.SetValue(instance, value);

                    return(JavaScriptValue.Invalid);
                });
            }
        }
Beispiel #3
0
        /// <summary>
        /// Binds all public properties for the instance.
        /// </summary>
        private void BindProperties(JsBinding binding, IHostType hostType, object instance)
        {
            // TODO: Seems like we can hoist this into JsInterop, which would allow us refactor
            // TODO: out the builder class completely.
            var properties = hostType.PropertyNames;

            for (int i = 0; i < properties.Count; ++i)
            {
                var propertyName = properties[i];

                binding.AddProperty(
                    propertyName,
                    (v, s, args, argLength, data) =>
                {
                    var get    = hostType.PropertyFor(propertyName).Getter;
                    var result = get.Invoke(instance, EmptyParameters);

                    var returnType = JsConversions.TypeFor(result, get.ReturnType);
                    return(_interop.ToJsObject(result, returnType));
                },
                    (v, s, args, argLength, data) =>
                {
                    var hostProperty = hostType.PropertyFor(propertyName);
                    var propType     = hostProperty.PropertyType;
                    var set          = hostProperty.Setter;

                    var value = _interop.ToHostObject(args[1], propType);
                    set.Invoke(instance, new[] { value });
                    return(JavaScriptValue.Invalid);
                });
            }
        }
Beispiel #4
0
        /// <summary>
        /// Converts a host object to a javascript value.
        /// </summary>
        public JavaScriptValue ToJsObject(object obj, Type type)
        {
            if (null == obj)
            {
                return(JavaScriptValue.Null);
            }

            if (JsConversions.IsVoidType(type))
            {
                return(ToJsVoid(obj, type));
            }

            if (JsConversions.IsNumberType(type))
            {
                return(ToJsNumber(obj, type));
            }

            if (JsConversions.IsStringType(type))
            {
                return(ToJsString(obj, type));
            }

            if (JsConversions.IsBoolType(type))
            {
                return(ToJsBoolean(obj, type));
            }

            if (JsConversions.IsFunctionType(type))
            {
                return(ToJsFunction(obj, type));
            }

            if (type.IsArray)
            {
                var underlyingType = type.GetElementType();
                var hostArray      = (Array)obj;
                var newArr         = JavaScriptValue.CreateArray((uint)hostArray.Length);

                for (int i = 0; i < hostArray.Length; ++i)
                {
                    var jsValue = ToJsObject(hostArray.GetValue(i), underlyingType);
                    newArr.SetIndexedProperty(JavaScriptValue.FromInt32(i), jsValue);
                }

                return(newArr);
            }

            // Attempt to bind the object and return it
            return(ToBoundJsObject(obj, type));
        }
Beispiel #5
0
        /// <summary>
        /// This method type checks each parameter passed by javascript to determine if the method is a suitable
        /// match for execution. At this point, we can assume that the provided arguments qualify for the method
        /// signature in terms of number of parameters, accounting for optional parameters and var args.
        /// </summary>
        private bool IsCompatibleMethod(HostMethod method, JavaScriptValue[] args, ushort argLength)
        {
            var parameters = method.Parameters;
            var isVarArg   = method.IsVarArgs;

            // Parameter Index Pointers - JS parameters include the callee at the first index, so we start after that
            var argIndex = 1;
            var pIndex   = 0;

            // The maximum number of iterations we'll have to run to complete type checking each parameter
            // This is typically parameters.Length unless there are optional parameters or var args
            var iterations = Math.Max(parameters.Length, argLength - 1);

            // Loop Max Argument Count
            while (iterations-- > 0)
            {
                // Case 1. We've Type Checked All JS Parameters Against C# Parameters
                if (argIndex >= argLength)
                {
                    // For a Var Arg C# Parameter Method, ensure we've indexed into the var arg index
                    if (isVarArg)
                    {
                        // If we haven't, then we ensure that we have an optional parameter. Otherwise,
                        // we're short JS parameters, so we can't call this method.
                        if (pIndex != method.VarArgIndex)
                        {
                            return(parameters[pIndex].IsOptional);
                        }

                        return(true);
                    }

                    // For methods without var args, we simply ensure we've typed checked against all required
                    // parameters.
                    if (pIndex < parameters.Length)
                    {
                        return(parameters[pIndex].IsOptional);
                    }

                    return(true);
                }

                // Case 2. We reach the end of C# Method Parameters, but still have JS Parameters left to check
                if (pIndex >= parameters.Length)
                {
                    // This case is only possible with var args (since the params []) counts as a single index
                    if (!method.IsVarArgs)
                    {
                        return(false);
                    }

                    // Ensure that pIndex stays at the var arg index for type checking the element type
                    pIndex = method.VarArgIndex;
                }

                // Case 3. TypeCheck JS Argument Against C# Parameter
                var arg       = args[argIndex];
                var parameter = parameters[pIndex];

                // For Var Args, we need to ensure that we use the var arg type to check against the JS arg.
                var paramType = (isVarArg && pIndex >= method.VarArgIndex)
                    ? method.VarArgType
                    : parameter.ParameterType;

                // Increment index pointers
                argIndex++;
                pIndex++;

                // Run type conversion checking, early return on failure
                if (!JsConversions.IsAssignable(arg, _binder, paramType))
                {
                    return(false);
                }
            }
            return(true);
        }
Beispiel #6
0
        /// <summary>
        /// Adds function bindings for all methods in the <see cref="IHostType"/>.
        /// </summary>
        private void BindMethods(JsBinding binding, IHostType hostType, object instance)
        {
            // TODO: Seems like we can hoist this into JsInterop, which would allow us refactor
            // TODO: out the builder class completely.
            var methods = hostType.MethodNames;

            for (int i = 0; i < methods.Count; ++i)
            {
                var methodName = methods[i];

                binding.AddFunction(
                    methodName,
                    (v, s, args, argLength, data) =>
                {
                    var totalParameters = argLength - 1;
                    var hostMethods     = hostType.MethodsFor(methodName, totalParameters);
                    if (hostMethods.Count == 0)
                    {
                        var message = $"Calling host function that does not exist: [Method: {methodName}, Instance: {instance}]";
                        JsErrorHelper.SetJsException(message);
                        return(JavaScriptValue.Invalid);
                    }

                    HostMethod hostMethodInfo;

                    // Only a single method returned. Call this optimistically
                    if (hostMethods.Count == 1)
                    {
                        hostMethodInfo = hostMethods[0];
                    }
                    else
                    {
                        // Get the Parameter Key, and look up cached invocation
                        var invokeKey = JsConversions.ToInvokeKey(methodName, args, argLength);
                        if (!hostType.TryGetInvocation(invokeKey, out hostMethodInfo))
                        {
                            // Otherwise, locate best method for these argument, and cache
                            hostMethodInfo = FindBestMethod(hostMethods, args, argLength);
                            hostType.CacheInvocation(invokeKey, hostMethodInfo);
                        }
                    }

                    if (null == hostMethodInfo)
                    {
                        LogMethodSelectionFailure(hostMethods, args, argLength);
                        JsErrorHelper.SetJsException(
                            $"Calling host function that does not exist: [Method: {methodName}, Instance: {instance}]");
                        return(JavaScriptValue.Invalid);
                    }

                    try
                    {
                        var realParams = ToParameters(hostMethodInfo, args, argLength);

                        var result     = hostMethodInfo.Method.Invoke(instance, realParams);
                        var resultType = hostMethodInfo.ReturnType;
                        if (resultType == typeof(void))
                        {
                            return(JavaScriptValue.Invalid);
                        }

                        resultType = JsConversions.TypeFor(result, resultType);
                        return(_interop.ToJsObject(result, resultType));
                    }
                    catch (Exception e)
                    {
                        LogMethodInvocationInfo(hostMethodInfo, instance);

                        throw;
                    }
                });
            }
        }