private bool TryConvertFromNonPrimitiveObject(BaristaContext context, object obj, out JsValue value) { if (m_typeConversionStrategy == null) { //TODO: think about cheating with a JsonConversion value = null; return(false); } Type typeToConvert = obj.GetType(); if (m_typeConversionStrategy.TryCreatePrototypeFunction(context, typeToConvert, out JsFunction fnCtor)) { var exObj = context.CreateExternalObject(obj); var resultValue = fnCtor.Construct(null, exObj); value = resultValue; return(true); } value = null; return(false); }
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); }
public bool TryCreatePrototypeFunction(BaristaContext context, Type typeToConvert, out JsFunction ctor) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (typeToConvert == null) { throw new ArgumentNullException(nameof(typeToConvert)); } if (m_prototypes.ContainsKey(typeToConvert)) { ctor = m_prototypes[typeToConvert]; return(true); } var reflector = new ObjectReflector(typeToConvert); JsFunction superCtor = null; var baseType = reflector.GetBaseType(); if (baseType != null && !baseType.IsSameOrSubclass(typeof(JsValue)) && TryCreatePrototypeFunction(context, baseType, out JsFunction fnSuper)) { superCtor = fnSuper; } var objectName = BaristaObjectAttribute.GetBaristaObjectNameFromType(typeToConvert); //Get all the property descriptors for the specified type. var staticPropertyDescriptors = context.CreateObject(); var instancePropertyDescriptors = context.CreateObject(); //Get static and instance properties. ProjectProperties(context, staticPropertyDescriptors, reflector.GetProperties(false)); ProjectProperties(context, instancePropertyDescriptors, reflector.GetProperties(true)); //Get static and instance indexer properties. ProjectIndexerProperties(context, staticPropertyDescriptors, reflector.GetIndexerProperties(false)); ProjectIndexerProperties(context, instancePropertyDescriptors, reflector.GetIndexerProperties(true)); //Get static and instance methods. ProjectMethods(context, staticPropertyDescriptors, reflector, reflector.GetUniqueMethodsByName(false)); ProjectMethods(context, instancePropertyDescriptors, reflector, reflector.GetUniqueMethodsByName(true)); //Get static and instance events. ProjectEvents(context, staticPropertyDescriptors, reflector, reflector.GetEventTable(false)); ProjectEvents(context, instancePropertyDescriptors, reflector, reflector.GetEventTable(true)); //Get the [[iterator]] property. ProjectIEnumerable(context, instancePropertyDescriptors, reflector); JsFunction fnCtor; var publicConstructors = reflector.GetConstructors(); if (publicConstructors.Any()) { fnCtor = context.CreateFunction(new BaristaFunctionDelegate((calleeObj, isConstructCall, thisObj, args) => { if (thisObj == null) { var ex = context.CreateTypeError($"Failed to construct '{objectName}': 'this' must be specified."); context.CurrentScope.SetException(ex); return(context.Undefined); } if (superCtor != null) { superCtor.Call(thisObj); } context.Object.DefineProperties(thisObj, instancePropertyDescriptors); //If this isn't a construct call, don't attempt to set the bean if (!isConstructCall) { return(thisObj); } //Set our native object. JsExternalObject externalObject = null; //!!Special condition -- if there's exactly one argument, and if it matches the enclosing type, //don't invoke the type's constructor, rather, just wrap the object with the JsObject. if (args.Length == 1 && args[0].GetType() == typeToConvert) { externalObject = context.CreateExternalObject(args[0]); } else { try { var bestConstructor = reflector.GetConstructorBestMatch(args); if (bestConstructor == null) { var ex = context.CreateTypeError($"Failed to construct '{objectName}': Could not find a matching constructor for the provided arguments."); context.CurrentScope.SetException(ex); return(context.Undefined); } //Convert the args into the native args of the constructor. var constructorParams = bestConstructor.GetParameters(); var convertedArgs = ConvertArgsToParamTypes(context, args, constructorParams); var newObj = bestConstructor.Invoke(convertedArgs); externalObject = context.CreateExternalObject(newObj); } catch (Exception ex) { context.CurrentScope.SetException(context.CreateError(ex.Message)); return(context.Undefined); } } thisObj.SetBean(externalObject); return(thisObj); }), objectName); } else { fnCtor = context.CreateFunction(new BaristaFunctionDelegate((calleeObj, isConstructCall, thisObj, args) => { var ex = context.CreateTypeError($"Failed to construct '{objectName}': This object cannot be constructed."); context.CurrentScope.SetException(ex); return(context.Undefined); }), objectName); } //We've got everything we need. fnCtor.Prototype = context.Object.Create(superCtor == null ? context.Object.Prototype : superCtor.Prototype); context.Object.DefineProperties(fnCtor, staticPropertyDescriptors); m_prototypes.Add(typeToConvert, fnCtor); ctor = fnCtor; return(true); }