// used by InitializeProjectionForType private JavaScriptObject CreateObjectFor(JavaScriptEngine engine, JavaScriptProjection baseTypeProjection) { if (baseTypeProjection != null) { // todo: revisit to see if there is a better way to do this // Can probably get // ((engine.GlobalObject.GetPropertyByName("Object") as JavaScriptObject).GetPropertyByName("create") as JavaScriptFunction).Invoke(baseTypeProjection.Prototype) // but this is so much clearer dynamic global = engine.GlobalObject; JavaScriptObject result = global.Object.create(baseTypeProjection.Prototype); return(result); } else { return(engine.CreateObject()); } }
private void ProjectEvents(string owningTypeName, JavaScriptObject target, JavaScriptEngine engine, IEnumerable<EventInfo> events, JavaScriptProjection baseTypeProjection, bool instance) { var eventsArray = events.ToArray(); var eventsLookup = eventsArray.ToDictionary(ei => ei.Name.ToLower()); // General approach here // if there is a base thing, invoke that // for each event, register a delegate that marshals it back to JavaScript var add = engine.CreateFunction((eng, ctor, thisObj, args) => { bool callBase = instance && (baseTypeProjection?.HasInstanceEvents ?? false); var @this = thisObj as JavaScriptObject; if (@this == null) return eng.UndefinedValue; if (callBase) { var baseObj = baseTypeProjection.Prototype; var baseFn = baseObj.GetPropertyByName("addEventListener") as JavaScriptFunction; if (baseFn != null) { baseFn.Call(@this, args); } } var argsArray = args.ToArray(); if (argsArray.Length < 2) return eng.UndefinedValue; string eventName = argsArray[0].ToString(); JavaScriptFunction callbackFunction = argsArray[1] as JavaScriptFunction; if (callbackFunction == null) return eng.UndefinedValue; EventInfo curEvent; if (!eventsLookup.TryGetValue(eventName, out curEvent)) return eng.UndefinedValue; MethodInfo targetMethod = curEvent.EventHandlerType.GetMethod("Invoke"); var paramsExpr = targetMethod.GetParameters().Select(p => Expression.Parameter(p.ParameterType, p.Name)).ToArray(); int cookie = EventMarshaler.RegisterDelegate(callbackFunction, SynchronizationContext.Current); var marshaler = Expression.Lambda(curEvent.EventHandlerType, Expression.Block( Expression.Call( typeof(EventMarshaler).GetMethod(nameof(EventMarshaler.InvokeJavaScriptCallback)), Expression.Constant(cookie), Expression.NewArrayInit(typeof(string), targetMethod.GetParameters().Select(p => Expression.Constant(p.Name))), Expression.NewArrayInit(typeof(object), paramsExpr)) ), paramsExpr); curEvent.AddMethod.Invoke(@this.ExternalObject, new object[] { marshaler.Compile() }); return eng.UndefinedValue; }, owningTypeName + ".addEventListener"); target.SetPropertyByName("addEventListener", add); }
// used by InitializeProjectionForType private JavaScriptObject CreateObjectFor(JavaScriptEngine engine, JavaScriptProjection baseTypeProjection) { if (baseTypeProjection != null) { // todo: revisit to see if there is a better way to do this // Can probably get // ((engine.GlobalObject.GetPropertyByName("Object") as JavaScriptObject).GetPropertyByName("create") as JavaScriptFunction).Invoke(baseTypeProjection.Prototype) // but this is so much clearer dynamic global = engine.GlobalObject; JavaScriptObject result = global.Object.create(baseTypeProjection.Prototype); return result; } else { return engine.CreateObject(); } }
private void ProjectEvents(string owningTypeName, JavaScriptObject target, JavaScriptEngine engine, IEnumerable <EventInfo> events, JavaScriptProjection baseTypeProjection, bool instance) { var eventsArray = events.ToArray(); var eventsLookup = eventsArray.ToDictionary(ei => ei.Name.ToLower()); // General approach here // if there is a base thing, invoke that // for each event, register a delegate that marshals it back to JavaScript var add = engine.CreateFunction((eng, ctor, thisObj, args) => { bool callBase = instance && (baseTypeProjection?.HasInstanceEvents ?? false); var @this = thisObj as JavaScriptObject; if (@this == null) { return(eng.UndefinedValue); } if (callBase) { var baseObj = baseTypeProjection.Prototype; var baseFn = baseObj.GetPropertyByName("addEventListener") as JavaScriptFunction; if (baseFn != null) { baseFn.Call(@this, args); } } var argsArray = args.ToArray(); if (argsArray.Length < 2) { return(eng.UndefinedValue); } string eventName = argsArray[0].ToString(); JavaScriptFunction callbackFunction = argsArray[1] as JavaScriptFunction; if (callbackFunction == null) { return(eng.UndefinedValue); } EventInfo curEvent; if (!eventsLookup.TryGetValue(eventName, out curEvent)) { return(eng.UndefinedValue); } MethodInfo targetMethod = curEvent.EventHandlerType.GetMethod("Invoke"); var paramsExpr = targetMethod.GetParameters().Select(p => Expression.Parameter(p.ParameterType, p.Name)).ToArray(); int cookie = EventMarshaler.RegisterDelegate(callbackFunction, SynchronizationContext.Current); var marshaler = Expression.Lambda(curEvent.EventHandlerType, Expression.Block( Expression.Call( typeof(EventMarshaler).GetMethod(nameof(EventMarshaler.InvokeJavaScriptCallback)), Expression.Constant(cookie), Expression.NewArrayInit(typeof(string), targetMethod.GetParameters().Select(p => Expression.Constant(p.Name))), Expression.NewArrayInit(typeof(object), paramsExpr)) ), paramsExpr); curEvent.AddMethod.Invoke(@this.ExternalObject, new object[] { marshaler.Compile() }); return(eng.UndefinedValue); }, owningTypeName + ".addEventListener"); target.SetPropertyByName("addEventListener", add); }
private JavaScriptProjection InitializeProjectionForType(Type t) { var eng = GetEngineAndClaimContext(); ObjectReflector reflector = ObjectReflector.Create(t); JavaScriptProjection baseTypeProjection = null; if (reflector.HasBaseType) { Type baseType = reflector.GetBaseType(); if (!projectionTypes_.TryGetValue(baseType, out baseTypeProjection)) { baseTypeProjection = InitializeProjectionForType(baseType); baseTypeProjection.RefCount += 1; projectionTypes_[baseType] = baseTypeProjection; } } var publicConstructors = reflector.GetConstructors(); var publicInstanceProperties = reflector.GetProperties(instance: true); var publicStaticProperties = reflector.GetProperties(instance: false); var publicInstanceMethods = reflector.GetMethods(instance: true); var publicStaticMethods = reflector.GetMethods(instance: false); var publicInstanceEvents = reflector.GetEvents(instance: true); var publicStaticEvents = reflector.GetEvents(instance: false); if (AnyHaveSameArity(out var duplicateName, publicConstructors, publicInstanceMethods, publicStaticMethods, publicInstanceProperties, publicStaticProperties)) { throw new InvalidOperationException("The specified type cannot be marshaled; some publicly accessible members have the same arity. Projected methods can't differentiate only by type (e.g., Add(int, int) and Add(float, float) would cause this error): " + t.FullName + "::" + duplicateName); } JavaScriptFunction ctor; if (publicConstructors.Any()) { // e.g. var MyObject = function() { [native code] }; ctor = eng.CreateFunction((engine, constr, thisObj, args) => { // todo return(FromObject(publicConstructors.First().Invoke(new object[] { }))); }, t.FullName); } else { ctor = eng.CreateFunction((engine, constr, thisObj, args) => { return(eng.UndefinedValue); }, t.FullName); } // MyObject.prototype = Object.create(baseTypeProjection.PrototypeObject); var prototypeObj = CreateObjectFor(eng, baseTypeProjection); ctor.SetPropertyByName("prototype", prototypeObj); // MyObject.prototype.constructor = MyObject; prototypeObj.SetPropertyByName("constructor", ctor); // MyObject.CreateMyObject = function() { [native code] }; ProjectMethods(t.FullName, ctor, eng, publicStaticMethods); // Object.defineProperty(MyObject, 'Foo', { get: function() { [native code] } }); ProjectProperties(t.FullName, ctor, eng, publicStaticProperties); // MyObject.addEventListener = function() { [native code] }; if ((baseTypeProjection?.HasStaticEvents ?? false) || publicStaticEvents.Any()) { ProjectEvents(t.FullName, ctor, eng, publicStaticEvents, baseTypeProjection, instance: false); } // MyObject.prototype.ToString = function() { [native code] }; ProjectMethods(t.FullName + ".prototype", prototypeObj, eng, publicInstanceMethods); // Object.defineProperty(MyObject.prototype, 'baz', { get: function() { [native code] }, set: function() { [native code] } }); ProjectProperties(t.FullName + ".prototype", prototypeObj, eng, publicInstanceProperties); // MyObject.prototype.addEventListener = function() { [native code] }; if ((baseTypeProjection?.HasInstanceEvents ?? false) || publicInstanceEvents.Any()) { ProjectEvents(t.FullName + ".prototype", prototypeObj, eng, publicInstanceEvents, baseTypeProjection, instance: true); } prototypeObj.Freeze(); return(new JavaScriptProjection { RefCount = 0, Constructor = ctor, Prototype = prototypeObj, HasInstanceEvents = (baseTypeProjection?.HasInstanceEvents ?? false) || publicInstanceEvents.Any(), HasStaticEvents = (baseTypeProjection?.HasStaticEvents ?? false) || publicStaticEvents.Any(), }); }