From 06d9a5188cc0b5fb8a4ba58512b2862391e5f7cc Mon Sep 17 00:00:00 2001 From: Tom van Enckevort Date: Mon, 25 Mar 2024 13:54:01 +0000 Subject: [PATCH 1/2] Added Image constructor support --- src/AngleSharp.Js.Tests/ScriptEvalTests.cs | 7 ++++ .../DomConstructorFunctionAttribute.cs | 28 +++++++++++++++ src/AngleSharp.Js/Cache/CreatorCache.cs | 35 +++++++++++++++++++ src/AngleSharp.Js/Dom/WindowExtensions.cs | 27 ++++++++++++++ src/AngleSharp.Js/EngineInstance.cs | 1 + .../Extensions/EngineExtensions.cs | 14 ++++++++ .../Extensions/JsValueExtensions.cs | 11 ++++++ .../Proxies/DomConstructorFunctionInstance.cs | 31 ++++++++++++++++ 8 files changed, 154 insertions(+) create mode 100644 src/AngleSharp.Js/Attributes/DomConstructorFunctionAttribute.cs create mode 100644 src/AngleSharp.Js/Proxies/DomConstructorFunctionInstance.cs diff --git a/src/AngleSharp.Js.Tests/ScriptEvalTests.cs b/src/AngleSharp.Js.Tests/ScriptEvalTests.cs index 28e60af..82575fe 100644 --- a/src/AngleSharp.Js.Tests/ScriptEvalTests.cs +++ b/src/AngleSharp.Js.Tests/ScriptEvalTests.cs @@ -75,6 +75,13 @@ public async Task CreateXmlHttpRequestShouldWork() Assert.AreEqual("1", result); } + [Test] + public async Task CreateImageShouldWork() + { + var result = await EvaluateComplexScriptAsync("var img = new Image(400, 200); img.src = '/image.jpg';", SetResult("img.width")); + Assert.AreEqual("400", result); + } + [Test] public async Task PerformXmlHttpRequestSynchronousToDataUrlShouldWork() { diff --git a/src/AngleSharp.Js/Attributes/DomConstructorFunctionAttribute.cs b/src/AngleSharp.Js/Attributes/DomConstructorFunctionAttribute.cs new file mode 100644 index 0000000..6e6b44b --- /dev/null +++ b/src/AngleSharp.Js/Attributes/DomConstructorFunctionAttribute.cs @@ -0,0 +1,28 @@ +namespace AngleSharp.Js.Attributes +{ + using System; + + /// + /// This attribute is used to mark a method to be uses as a + /// constructor function from scripts. + /// + [AttributeUsage(AttributeTargets.Method, Inherited = false)] + public sealed class DomConstructorFunctionAttribute : Attribute + { + /// + /// Creates a new DomConstructorFunctionAttribute. + /// + /// + /// The official name of the decorated method. + /// + public DomConstructorFunctionAttribute(String officialName) + { + OfficialName = officialName; + } + + /// + /// Gets the official name of the given class. + /// + public String OfficialName { get; } + } +} diff --git a/src/AngleSharp.Js/Cache/CreatorCache.cs b/src/AngleSharp.Js/Cache/CreatorCache.cs index fe26650..5003d93 100644 --- a/src/AngleSharp.Js/Cache/CreatorCache.cs +++ b/src/AngleSharp.Js/Cache/CreatorCache.cs @@ -1,4 +1,6 @@ using AngleSharp.Attributes; +using AngleSharp.Js.Attributes; +using AngleSharp.Js.Proxies; using Jint.Native.Object; using Jint.Runtime.Descriptors; using System; @@ -40,6 +42,39 @@ public static Action GetConstructorAction(this T return action; } + private static readonly Dictionary> _constructorFunctionActions = new Dictionary>(); + + public static Action GetConstructorFunctionAction(this Type type) + { + if (!_constructorFunctionActions.TryGetValue(type, out var action)) + { + var constructorFunctions = type.GetTypeInfo().GetMethods().Where(m => m.GetCustomAttributes().Any()); + + if (constructorFunctions.Any()) + { + action = (engine, obj) => + { + foreach (var constructorFunction in constructorFunctions) + { + var attribute = constructorFunction.GetCustomAttribute(); + + var constructorFunctionInstance = new DomConstructorFunctionInstance(engine, constructorFunction, attribute.OfficialName); + + obj.FastSetProperty(attribute.OfficialName, new PropertyDescriptor(constructorFunctionInstance, false, true, false)); + } + }; + } + else + { + action = (e, o) => { }; + } + + _constructorFunctionActions.Add(type, action); + } + + return action; + } + private static readonly Dictionary> _instanceActions = new Dictionary>(); public static Action GetInstanceAction(this Type type) diff --git a/src/AngleSharp.Js/Dom/WindowExtensions.cs b/src/AngleSharp.Js/Dom/WindowExtensions.cs index 5fec386..27835b9 100644 --- a/src/AngleSharp.Js/Dom/WindowExtensions.cs +++ b/src/AngleSharp.Js/Dom/WindowExtensions.cs @@ -4,6 +4,8 @@ namespace AngleSharp.Js.Dom using AngleSharp.Browser; using AngleSharp.Dom; using AngleSharp.Dom.Events; + using AngleSharp.Html.Dom; + using AngleSharp.Js.Attributes; using System; /// @@ -42,5 +44,30 @@ public static Console Console(this IWindow window) { return new Console(window); } + + /// + /// Creates a new IHtmlImageElement instance. + /// + /// + /// + /// + /// + [DomConstructorFunction("Image")] + public static IHtmlImageElement Image(this IWindow window, int? width = null, int? height = null) + { + var imageElement = window.Document.CreateElement(TagNames.Img) as IHtmlImageElement; + + if (width.HasValue) + { + imageElement.DisplayWidth = width.Value; + } + + if (height.HasValue) + { + imageElement.DisplayHeight = height.Value; + } + + return imageElement; + } } } diff --git a/src/AngleSharp.Js/EngineInstance.cs b/src/AngleSharp.Js/EngineInstance.cs index 3848ebb..fe3d678 100644 --- a/src/AngleSharp.Js/EngineInstance.cs +++ b/src/AngleSharp.Js/EngineInstance.cs @@ -47,6 +47,7 @@ public EngineInstance(IWindow window, IDictionary assignments, I foreach (var lib in libs) { this.AddConstructors(_window, lib); + this.AddConstructorFunctions(_window, lib); this.AddInstances(_window, lib); } diff --git a/src/AngleSharp.Js/Extensions/EngineExtensions.cs b/src/AngleSharp.Js/Extensions/EngineExtensions.cs index 12cf37e..a28f7c9 100644 --- a/src/AngleSharp.Js/Extensions/EngineExtensions.cs +++ b/src/AngleSharp.Js/Extensions/EngineExtensions.cs @@ -178,6 +178,14 @@ public static void AddConstructors(this EngineInstance engine, ObjectInstance ct } } + public static void AddConstructorFunctions(this EngineInstance engine, ObjectInstance ctx, Assembly assembly) + { + foreach (var exportedType in assembly.ExportedTypes) + { + engine.AddConstructorFunction(ctx, exportedType); + } + } + public static void AddInstances(this EngineInstance engine, ObjectInstance obj, Assembly assembly) { foreach (var exportedType in assembly.ExportedTypes) @@ -192,6 +200,12 @@ public static void AddConstructor(this EngineInstance engine, ObjectInstance obj apply.Invoke(engine, obj); } + public static void AddConstructorFunction(this EngineInstance engine, ObjectInstance obj, Type type) + { + var apply = type.GetConstructorFunctionAction(); + apply.Invoke(engine, obj); + } + public static void AddInstance(this EngineInstance engine, ObjectInstance obj, Type type) { var apply = type.GetInstanceAction(); diff --git a/src/AngleSharp.Js/Extensions/JsValueExtensions.cs b/src/AngleSharp.Js/Extensions/JsValueExtensions.cs index 70e4c04..26779df 100644 --- a/src/AngleSharp.Js/Extensions/JsValueExtensions.cs +++ b/src/AngleSharp.Js/Extensions/JsValueExtensions.cs @@ -73,6 +73,17 @@ public static Object As(this JsValue value, Type targetType, EngineInstance engi { return TypeConverter.ToInt32(value); } + else if (targetType == typeof(Nullable)) + { + if (value.IsUndefined()) + { + return null; + } + else + { + return TypeConverter.ToInt32(value); + } + } else if (targetType == typeof(Double)) { return TypeConverter.ToNumber(value); diff --git a/src/AngleSharp.Js/Proxies/DomConstructorFunctionInstance.cs b/src/AngleSharp.Js/Proxies/DomConstructorFunctionInstance.cs new file mode 100644 index 0000000..0285cd9 --- /dev/null +++ b/src/AngleSharp.Js/Proxies/DomConstructorFunctionInstance.cs @@ -0,0 +1,31 @@ +namespace AngleSharp.Js.Proxies +{ + using Jint.Native; + using Jint.Native.Object; + using Jint.Runtime; + using System.Reflection; + + sealed class DomConstructorFunctionInstance : Constructor + { + private readonly EngineInstance _instance; + private readonly MethodInfo _constructorFunction; + + public DomConstructorFunctionInstance(EngineInstance instance, MethodInfo constructorFunction, string name) : base(instance.Jint, name) + { + _instance = instance; + _constructorFunction = constructorFunction; + } + + public override ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) + { + try + { + return _instance.Call(_constructorFunction, _instance.Window, arguments) as DomNodeInstance; + } + catch + { + throw new JavaScriptException(_instance.Jint.Intrinsics.Error); + } + } + } +} From f019708a2cdaba57bcac0fbe9b3571b18ff02320 Mon Sep 17 00:00:00 2001 From: Tom van Enckevort Date: Mon, 25 Mar 2024 14:05:45 +0000 Subject: [PATCH 2/2] Use more generic type-cast --- src/AngleSharp.Js/Proxies/DomConstructorFunctionInstance.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AngleSharp.Js/Proxies/DomConstructorFunctionInstance.cs b/src/AngleSharp.Js/Proxies/DomConstructorFunctionInstance.cs index 0285cd9..348ab1a 100644 --- a/src/AngleSharp.Js/Proxies/DomConstructorFunctionInstance.cs +++ b/src/AngleSharp.Js/Proxies/DomConstructorFunctionInstance.cs @@ -20,7 +20,7 @@ public override ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) { try { - return _instance.Call(_constructorFunction, _instance.Window, arguments) as DomNodeInstance; + return _instance.Call(_constructorFunction, _instance.Window, arguments) as ObjectInstance; } catch {