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..348ab1a
--- /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 ObjectInstance;
+ }
+ catch
+ {
+ throw new JavaScriptException(_instance.Jint.Intrinsics.Error);
+ }
+ }
+ }
+}