Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/AngleSharp.Js.Tests/DomTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,24 @@ public async Task NumericIndexerOfHtmlCollectionOutOfRangeIsUndefined()
Assert.AreEqual("undefined", result);
}

// Two closed forms of IHtmlCollection<T> in one document - each resolves its own
// indexer, and the explicit IReadOnlyList<T> re-implementation behind them is the
// one accessor that cannot be invoked as declared.
[Test]
public async Task NumericIndexerWorksForHtmlCollectionsOfDifferentItemTypes()
{
var result = await "(function () { var d = new DOMParser().parseFromString(`<img id=x>`, 'text/html'); return d.getElementsByTagName('img')[0].id + ',' + d.images[0].id; })()".EvalScriptAsync();
Assert.AreEqual("x,x", result);
}

// col and colgroup are separate classes sharing the HTMLTableColElement prototype.
[Test]
public async Task NumericIndexerWorksForElementsSharingAPrototype()
{
var result = await "(function () { var d = new DOMParser().parseFromString(`<table><colgroup class='a b'><col class='c d'></colgroup></table>`, 'text/html'); var g = d.getElementsByTagName('colgroup')[0], c = d.getElementsByTagName('col')[0]; return g.classList[1] + ',' + c.classList[1]; })()".EvalScriptAsync();
Assert.AreEqual("b,d", result);
}

[Test]
public async Task NumericIndexerOfNodeListYieldsTheNode()
{
Expand Down
196 changes: 196 additions & 0 deletions src/AngleSharp.Js.Tests/InstanceOfTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
namespace AngleSharp.Js.Tests
{
using NUnit.Framework;
using System.Threading.Tasks;

[TestFixture]
public class InstanceOfTests
{
// The two cases reported in https://github.com/AngleSharp/AngleSharp.Js/issues/103

[Test]
public async Task WindowIsAnInstanceOfWindow()
{
var result = await "window instanceof Window".EvalScriptAsync();
Assert.AreEqual("True", result);
}

[Test]
public async Task CreatedElementIsAnInstanceOfElement()
{
var result = await "document.createElement('div') instanceof Element".EvalScriptAsync();
Assert.AreEqual("True", result);
}

// Every level of the chain on its own - the leaf passing says nothing about the rest.

[Test]
public async Task CreatedElementIsAnInstanceOfItsOwnType()
{
var result = await "document.createElement('div') instanceof HTMLDivElement".EvalScriptAsync();
Assert.AreEqual("True", result);
}

[Test]
public async Task CreatedElementIsAnInstanceOfHtmlElement()
{
var result = await "document.createElement('div') instanceof HTMLElement".EvalScriptAsync();
Assert.AreEqual("True", result);
}

[Test]
public async Task CreatedElementIsAnInstanceOfNode()
{
var result = await "document.createElement('div') instanceof Node".EvalScriptAsync();
Assert.AreEqual("True", result);
}

[Test]
public async Task CreatedElementIsAnInstanceOfEventTarget()
{
var result = await "document.createElement('div') instanceof EventTarget".EvalScriptAsync();
Assert.AreEqual("True", result);
}

[Test]
public async Task CreatedElementIsNotAnInstanceOfAnUnrelatedType()
{
var result = await "document.createElement('div') instanceof HTMLAnchorElement".EvalScriptAsync();
Assert.AreEqual("False", result);
}

[Test]
public async Task DocumentIsAnInstanceOfHtmlDocument()
{
var result = await "document instanceof HTMLDocument".EvalScriptAsync();
Assert.AreEqual("True", result);
}

[Test]
public async Task DocumentIsAnInstanceOfDocument()
{
var result = await "document instanceof Document".EvalScriptAsync();
Assert.AreEqual("True", result);
}

// An anchor reaches HTMLElement through the URLUtils mixin, which sits in the chain
// because AngleSharp implements it as an abstract class of its own.

[Test]
public async Task AnchorIsAnInstanceOfHtmlElement()
{
var result = await "document.createElement('a') instanceof HTMLElement".EvalScriptAsync();
Assert.AreEqual("True", result);
}

// An element class carrying no DOM name of its own - a "b" is an HTMLElement in a
// browser, and now here as well.

[Test]
public async Task UnnamedElementIsAnInstanceOfHtmlElement()
{
var result = await "document.createElement('b') instanceof HTMLElement".EvalScriptAsync();
Assert.AreEqual("True", result);
}

[Test]
public async Task UnnamedElementUsesThePrototypeOfHtmlElement()
{
var result = await "Object.getPrototypeOf(document.createElement('b')) === HTMLElement.prototype".EvalScriptAsync();
Assert.AreEqual("True", result);
}

// Asserted without "instanceof" on purpose: a Symbol.hasInstance answer would hide a
// broken chain from every test above, but not from these.

[Test]
public async Task PrototypeOfElementIsThePrototypeOfItsConstructor()
{
var result = await "Object.getPrototypeOf(document.createElement('div')) === HTMLDivElement.prototype".EvalScriptAsync();
Assert.AreEqual("True", result);
}

[Test]
public async Task PrototypeOfConstructorIsChainedToItsBase()
{
var result = await "Object.getPrototypeOf(HTMLDivElement.prototype) === HTMLElement.prototype".EvalScriptAsync();
Assert.AreEqual("True", result);
}

[Test]
public async Task PrototypeOfWindowIsThePrototypeOfItsConstructor()
{
var result = await "Object.getPrototypeOf(window) === Window.prototype".EvalScriptAsync();
Assert.AreEqual("True", result);
}

[Test]
public async Task ConstructorOfAnElementIsTheExposedConstructor()
{
var result = await "document.createElement('div').constructor === HTMLDivElement".EvalScriptAsync();
Assert.AreEqual("True", result);
}

// Types reached only through a property, never constructed from script.

[Test]
public async Task TokenListIsAnInstanceOfDomTokenList()
{
var result = await "document.createElement('div').classList instanceof DOMTokenList".EvalScriptAsync();
Assert.AreEqual("True", result);
}

[Test]
public async Task CollectionIsAnInstanceOfHtmlCollection()
{
var result = await "document.getElementsByTagName('div') instanceof HTMLCollection".EvalScriptAsync();
Assert.AreEqual("True", result);
}

// A type constructed from script keeps working - these already did.

[Test]
public async Task ConstructedEventIsAnInstanceOfEvent()
{
var result = await "new Event('foo') instanceof Event".EvalScriptAsync();
Assert.AreEqual("True", result);
}

[Test]
public async Task ConstructedCustomEventIsAnInstanceOfEvent()
{
var result = await "new CustomEvent('foo') instanceof Event".EvalScriptAsync();
Assert.AreEqual("True", result);
}

[Test]
public async Task ImageIsAnInstanceOfHtmlImageElement()
{
var result = await "new Image() instanceof HTMLImageElement".EvalScriptAsync();
Assert.AreEqual("True", result);
}

// A DOM constructor is a function, so it inherits from Function.prototype.

[Test]
public async Task DomConstructorIsAFunction()
{
var result = await "typeof HTMLDivElement".EvalScriptAsync();
Assert.AreEqual("function", result);
}

[Test]
public async Task DomConstructorIsAnInstanceOfFunction()
{
var result = await "Window instanceof Function".EvalScriptAsync();
Assert.AreEqual("True", result);
}

[Test]
public async Task DomConstructorInheritsCallFromFunctionPrototype()
{
var result = await "typeof HTMLDivElement.call".EvalScriptAsync();
Assert.AreEqual("function", result);
}
}
}
18 changes: 14 additions & 4 deletions src/AngleSharp.Js/Cache/PrototypeCache.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
namespace AngleSharp.Js
{
using AngleSharp.Js.Cache;
using Jint;
using Jint.Native.Object;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection;

sealed class PrototypeCache
{
private readonly ConcurrentDictionary<Type, ObjectInstance> _prototypes;
private readonly Engine _engine;
private readonly ConcurrentDictionary<Type, Type> _canonicalTypes;
private readonly IEnumerable<Assembly> _libs;

public PrototypeCache(Engine engine)
public PrototypeCache(Engine engine, IEnumerable<Assembly> libs)
{
_prototypes = new ConcurrentDictionary<Type, ObjectInstance>
{
[typeof(Object)] = engine.Intrinsics.Object.PrototypeObject,
};
_engine = engine;
_canonicalTypes = new ConcurrentDictionary<Type, Type>();
_libs = libs;
}

public ObjectInstance GetOrCreate(Type type, Func<Type, ObjectInstance> creator) =>
_prototypes.GetOrAdd(type, creator.Invoke);
_prototypes.GetOrAdd(Canonicalize(type), creator.Invoke);

// Memoized per engine rather than globally: the set of libraries a document uses is what
// decides the outcome, and that is fixed for an engine but not for the process.
private Type Canonicalize(Type type) =>
_canonicalTypes.GetOrAdd(type, m => m.GetDomPrototypeType(_libs));
}
}
Loading
Loading