Example #1
0
        /// <summary>
        /// Projects the [[iterator]] protocol on IEnumerable objects.
        /// </summary>
        /// <param name="context"></param>
        /// <param name="targetObject"></param>
        /// <param name="reflector"></param>
        private void ProjectIEnumerable(BaristaContext context, JsObject targetObject, ObjectReflector reflector)
        {
            if (typeof(IEnumerable).IsAssignableFrom(reflector.Type))
            {
                var fnIterator = context.CreateFunction(new Func <JsObject, JsValue>((thisObj) => {
                    IEnumerable targetObj = null;

                    if (thisObj == null)
                    {
                        context.CurrentScope.SetException(context.CreateTypeError($"Could not retrieve iterator on object {targetObject.ToString()}: Invalid 'this' context."));
                        return(context.Undefined);
                    }

                    if (thisObj.TryGetBean(out JsExternalObject xoObj))
                    {
                        targetObj = xoObj.Target as IEnumerable;
                    }

                    return(context.CreateIterator(targetObj.GetEnumerator()));
                }));

                var iteratorDescriptor = context.CreateObject();
                iteratorDescriptor.SetProperty("value", fnIterator);
                targetObject.SetProperty(context.Symbol.Iterator, iteratorDescriptor);
            }
        }
Example #2
0
        private void ProjectProperties(BaristaContext context, JsObject targetObject, IEnumerable <PropertyInfo> properties)
        {
            foreach (var prop in properties)
            {
                var propertyAttribute  = BaristaPropertyAttribute.GetAttribute(prop);
                var propertyName       = propertyAttribute.Name;
                var propertyDescriptor = context.CreateObject();

                if (propertyAttribute.Configurable)
                {
                    propertyDescriptor.SetProperty("configurable", context.True);
                }
                if (propertyAttribute.Enumerable)
                {
                    propertyDescriptor.SetProperty("enumerable", context.True);
                }

                if (prop.GetMethod != null)
                {
                    var jsGet = context.CreateFunction(new BaristaFunctionDelegate((calleeObj, isConstructCall, thisObj, args) =>
                    {
                        return(GetPropertyValue(context, prop, propertyName, thisObj));
                    }));

                    propertyDescriptor.SetProperty("get", jsGet);
                }

                if (prop.SetMethod != null)
                {
                    var jsSet = context.CreateFunction(new BaristaFunctionDelegate((calleeObj, isConstructCall, thisObj, args) =>
                    {
                        return(SetPropertyValue(context, prop, propertyName, thisObj, args));
                    }));

                    propertyDescriptor.SetProperty("set", jsSet);
                }

                targetObject.SetProperty(context.CreateString(propertyName), propertyDescriptor);
            }
        }
Example #3
0
        private void ProjectEvents(BaristaContext context, JsObject targetObject, ObjectReflector reflector, IDictionary <string, EventInfo> eventsTable)
        {
            if (eventsTable.Count == 0)
            {
                return;
            }

            var fnAddEventListener = context.CreateFunction(new Func <JsObject, string, JsFunction, JsValue>((thisObj, eventName, fnCallback) => {
                if (String.IsNullOrWhiteSpace(eventName))
                {
                    context.CurrentScope.SetException(context.CreateTypeError($"The name of the event listener to register must be specified."));
                    return(context.Undefined);
                }

                object targetObj = null;

                if (thisObj == null)
                {
                    context.CurrentScope.SetException(context.CreateTypeError($"Could not register event listener '{eventName}': Invalid 'this' context."));
                    return(context.Undefined);
                }

                if (thisObj.TryGetBean(out JsExternalObject xoObj))
                {
                    targetObj = xoObj.Target;
                }

                if (!eventsTable.TryGetValue(eventName, out EventInfo targetEvent))
                {
                    return(context.False);
                }

                Action <object[]> invokeListener = (args) =>
                {
                    //TODO: Object conversion.
                    fnCallback.Call(thisObj, null);
                };

                var targetEventMethod     = targetEvent.EventHandlerType.GetMethod("Invoke");
                var targetEventParameters = targetEventMethod.GetParameters().Select(p => Expression.Parameter(p.ParameterType, p.Name)).ToArray();

                var exprInvokeListener = Expression.Lambda(targetEvent.EventHandlerType, Expression.Block(
                                                               Expression.Call(
                                                                   Expression.Constant(invokeListener.Target),
                                                                   invokeListener.Method,
                                                                   Expression.NewArrayInit(typeof(object), targetEventParameters))
                                                               ), targetEventParameters);

                var invokeListenerDelegate = exprInvokeListener.Compile();

                IDictionary <string, IList <Tuple <JsFunction, Delegate> > > eventListeners;
                if (thisObj.HasProperty(BaristaEventListenersPropertyName))
                {
                    var xoListeners = thisObj.GetProperty <JsExternalObject>(BaristaEventListenersPropertyName);
                    eventListeners  = xoListeners.Target as IDictionary <string, IList <Tuple <JsFunction, Delegate> > >;
                }
                else
                {
                    eventListeners = new Dictionary <string, IList <Tuple <JsFunction, Delegate> > >();

                    //Set the listeners as a non-configurable, non-enumerable, non-writable property
                    var xoListeners = context.CreateExternalObject(eventListeners);

                    var baristaEventListenersPropertyDescriptor = context.CreateObject();
                    baristaEventListenersPropertyDescriptor.SetProperty("value", xoListeners);
                    context.Object.DefineProperty(thisObj, context.CreateString(BaristaEventListenersPropertyName), baristaEventListenersPropertyDescriptor);
                }

                if (eventListeners != null)
                {
                    if (eventListeners.ContainsKey(eventName))
                    {
                        eventListeners[eventName].Add(new Tuple <JsFunction, Delegate>(fnCallback, invokeListenerDelegate));
                    }
                    else
                    {
                        eventListeners.Add(eventName, new List <Tuple <JsFunction, Delegate> >()
                        {
                            new Tuple <JsFunction, Delegate>(fnCallback, invokeListenerDelegate)
                        });
                    }
                }

                targetEvent.AddMethod.Invoke(targetObj, new object[] { invokeListenerDelegate });

                return(context.True);
            }), "addEventListener");

            var fnRemoveEventListener = context.CreateFunction(new Func <JsObject, string, JsFunction, JsValue>((thisObj, eventName, eventListener) =>
            {
                if (String.IsNullOrWhiteSpace(eventName))
                {
                    context.CurrentScope.SetException(context.CreateTypeError($"The name of the event listener to remove must be specified."));
                    return(context.Undefined);
                }

                if (eventListener == null)
                {
                    context.CurrentScope.SetException(context.CreateTypeError($"The event listener to remove must be specified."));
                    return(context.Undefined);
                }

                object targetObj = null;

                if (thisObj == null)
                {
                    context.CurrentScope.SetException(context.CreateTypeError($"Could not unregister event listener '{eventName}': Invalid 'this' context."));
                    return(context.Undefined);
                }

                if (thisObj.TryGetBean(out JsExternalObject xoObj))
                {
                    targetObj = xoObj.Target;
                }

                if (!eventsTable.TryGetValue(eventName, out EventInfo targetEvent))
                {
                    return(context.False);
                }

                //Get the event listeners.
                IDictionary <string, IList <Tuple <JsFunction, Delegate> > > eventListeners = null;
                if (thisObj.HasProperty(BaristaEventListenersPropertyName))
                {
                    var xoListeners = thisObj.GetProperty <JsExternalObject>(BaristaEventListenersPropertyName);
                    eventListeners  = xoListeners.Target as IDictionary <string, IList <Tuple <JsFunction, Delegate> > >;
                }

                if (eventListeners == null)
                {
                    return(context.False);
                }

                var hasRemoved = false;
                if (eventListeners.ContainsKey(eventName))
                {
                    var listeners = eventListeners[eventName];
                    var toRemove  = new List <Tuple <JsFunction, Delegate> >();
                    foreach (var listener in listeners)
                    {
                        if (listener.Item1 == eventListener)
                        {
                            targetEvent.RemoveMethod.Invoke(targetObj, new object[] { listener.Item2 });
                            toRemove.Add(listener);
                            hasRemoved = true;
                        }
                    }

                    eventListeners[eventName] = listeners.Where(l => toRemove.Any(tl => tl == l)).ToList();
                }

                return(hasRemoved ? context.True : context.False);
            }), "removeEventListener");

            var fnRemoveAllEventListeners = context.CreateFunction(new Func <JsObject, string, JsValue>((thisObj, eventName) => {
                if (String.IsNullOrWhiteSpace(eventName))
                {
                    context.CurrentScope.SetException(context.CreateTypeError($"The name of the event listener to remove must be specified."));
                    return(context.Undefined);
                }

                object targetObj = null;

                if (thisObj == null)
                {
                    context.CurrentScope.SetException(context.CreateTypeError($"Could not unregister event listener '{eventName}': Invalid 'this' context."));
                    return(context.Undefined);
                }

                if (thisObj.TryGetBean(out JsExternalObject xoObj))
                {
                    targetObj = xoObj.Target;
                }

                if (!eventsTable.TryGetValue(eventName, out EventInfo targetEvent))
                {
                    return(context.False);
                }

                //Get the event listeners.
                IDictionary <string, IList <Tuple <JsFunction, Delegate> > > eventListeners = null;
                if (thisObj.HasProperty(BaristaEventListenersPropertyName))
                {
                    var xoListeners = thisObj.GetProperty <JsExternalObject>(BaristaEventListenersPropertyName);
                    eventListeners  = xoListeners.Target as IDictionary <string, IList <Tuple <JsFunction, Delegate> > >;
                }

                if (eventListeners == null)
                {
                    return(context.False);
                }

                if (eventListeners.ContainsKey(eventName))
                {
                    foreach (var listener in eventListeners[eventName])
                    {
                        targetEvent.RemoveMethod.Invoke(targetObj, new object[] { listener.Item2 });
                    }

                    eventListeners.Remove(eventName);
                }

                return(context.True);
            }), "removeAllEventListeners");

            var addEventListenerFunctionDescriptor = context.CreateObject();

            addEventListenerFunctionDescriptor.SetProperty("enumerable", context.True);
            addEventListenerFunctionDescriptor.SetProperty("value", fnAddEventListener);
            targetObject.SetProperty(context.CreateString("addEventListener"), addEventListenerFunctionDescriptor);

            var removeEventListenerFunctionDescriptor = context.CreateObject();

            removeEventListenerFunctionDescriptor.SetProperty("enumerable", context.True);
            removeEventListenerFunctionDescriptor.SetProperty("value", fnRemoveEventListener);
            targetObject.SetProperty(context.CreateString("removeEventListener"), removeEventListenerFunctionDescriptor);

            var removeAllEventListenersFunctionDescriptor = context.CreateObject();

            removeAllEventListenersFunctionDescriptor.SetProperty("enumerable", context.True);
            removeAllEventListenersFunctionDescriptor.SetProperty("value", fnRemoveAllEventListeners);
            targetObject.SetProperty(context.CreateString("removeAllEventListeners"), removeAllEventListenersFunctionDescriptor);
        }
Example #4
0
        private void ProjectMethods(BaristaContext context, JsObject targetObject, ObjectReflector reflector, IDictionary <string, IList <MethodInfo> > methods)
        {
            foreach (var method in methods)
            {
                var methodName  = method.Key;
                var methodInfos = method.Value;

                var fn = context.CreateFunction(new BaristaFunctionDelegate((calleeObj, isConstructCall, thisObj, args) =>
                {
                    object targetObj = null;

                    if (thisObj == null)
                    {
                        context.CurrentScope.SetException(context.CreateTypeError($"Could not call function '{methodName}': Invalid 'this' context."));
                        return(context.Undefined);
                    }

                    if (thisObj.TryGetBean(out JsExternalObject xoObj))
                    {
                        targetObj = xoObj.Target;
                    }

                    try
                    {
                        var bestMethod = reflector.GetMethodBestMatch(methodInfos, args);
                        if (bestMethod == null)
                        {
                            var ex = context.CreateTypeError($"Failed to call function '{methodName}': Could not find a matching function for the provided arguments.");
                            context.CurrentScope.SetException(ex);
                            return(context.Undefined);
                        }

                        //Convert the args into the native args of the method.
                        var methodParams  = bestMethod.GetParameters();
                        var convertedArgs = ConvertArgsToParamTypes(context, args, methodParams);

                        var result = bestMethod.Invoke(targetObj, convertedArgs);
                        if (context.Converter.TryFromObject(context, result, out JsValue resultValue))
                        {
                            return(resultValue);
                        }
                        else
                        {
                            context.CurrentScope.SetException(context.CreateTypeError($"The call to '{methodName}' was successful, but the result could not be converted into a JavaScript object."));
                            return(context.Undefined);
                        }
                    }
                    catch (Exception ex)
                    {
                        context.CurrentScope.SetException(context.CreateError(ex.Message));
                        return(context.Undefined);
                    }
                }));

                var functionDescriptor = context.CreateObject();

                if (methodInfos.All(mi => BaristaPropertyAttribute.GetAttribute(mi).Configurable))
                {
                    functionDescriptor.SetProperty("configurable", context.True);
                }
                if (methodInfos.All(mi => BaristaPropertyAttribute.GetAttribute(mi).Enumerable))
                {
                    functionDescriptor.SetProperty("enumerable", context.True);
                }
                if (methodInfos.All(mi => BaristaPropertyAttribute.GetAttribute(mi).Writable))
                {
                    functionDescriptor.SetProperty("writable", context.True);
                }

                functionDescriptor.SetProperty("value", fn);

                targetObject.SetProperty(context.CreateString(methodName), functionDescriptor);
            }
        }