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
21 changes: 21 additions & 0 deletions src/AngleSharp.Js.Tests/DomTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,27 @@ public async Task NumericIndexerOfNamedNodeMapStillReadsBothWays()
Assert.AreEqual("1,title,t", result);
}

// An element whose id happens to be numeric must not surface as an index of the
// collection. The array-like projection owns every array-index key - an index past the
// end is authoritatively absent - so the named entry answers only non-index names,
// which is also how WebIDL resolves the collision. Every answer has to say the same
// thing, the descriptor included.
[Test]
public async Task NamedEntryWithAnIndexShapedNameIsNotAnIndex()
{
var result = await "(function () { var f = document.createElement('form'); f.id = '5'; document.documentElement.appendChild(f); var c = document.forms; return (Object.getOwnPropertyDescriptor(c, '5') === undefined) + ',' + ('5' in c) + ',' + (c['5'] === undefined) + ',' + c.hasOwnProperty('5') + ',' + (c[0] === f); })()".EvalScriptAsync();
Assert.AreEqual("true,false,true,false,true", result);
}

// The guard above must not overreach: a named entry whose name is not an array index
// keeps resolving, descriptor and all.
[Test]
public async Task NamedEntryWithAnOrdinaryNameStillResolves()
{
var result = await "(function () { var f = document.createElement('form'); f.id = 'login'; document.documentElement.appendChild(f); var c = document.forms; return (c.login === f) + ',' + (Object.getOwnPropertyDescriptor(c, 'login') !== undefined); })()".EvalScriptAsync();
Assert.AreEqual("true,true", result);
}

// A symbol cannot be an index, and it must not be turned into one either - the
// well-known symbols are probed on every kind of object by library code.
[Test]
Expand Down
12 changes: 6 additions & 6 deletions src/AngleSharp.Js.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,27 @@
<dependencies>
<group targetFramework="netstandard2.0">
<dependency id="AngleSharp" version="[1.5.0,2.0.0)" />
<dependency id="Jint" version="[4.0.0,5.0.0)" />
<dependency id="Jint" version="[4.15.0,5.0.0)" />
</group>
<group targetFramework="net462">
<dependency id="AngleSharp" version="[1.5.0,2.0.0)" />
<dependency id="Jint" version="[4.0.0,5.0.0)" />
<dependency id="Jint" version="[4.15.0,5.0.0)" />
</group>
<group targetFramework="net472">
<dependency id="AngleSharp" version="[1.5.0,2.0.0)" />
<dependency id="Jint" version="[4.0.0,5.0.0)" />
<dependency id="Jint" version="[4.15.0,5.0.0)" />
</group>
<group targetFramework="net60">
<dependency id="AngleSharp" version="[1.5.0,2.0.0)" />
<dependency id="Jint" version="[4.0.0,5.0.0)" />
<dependency id="Jint" version="[4.15.0,5.0.0)" />
</group>
<group targetFramework="net70">
<dependency id="AngleSharp" version="[1.5.0,2.0.0)" />
<dependency id="Jint" version="[4.0.0,5.0.0)" />
<dependency id="Jint" version="[4.15.0,5.0.0)" />
</group>
<group targetFramework="net80">
<dependency id="AngleSharp" version="[1.5.0,2.0.0)" />
<dependency id="Jint" version="[4.0.0,5.0.0)" />
<dependency id="Jint" version="[4.15.0,5.0.0)" />
</group>
</dependencies>
</metadata>
Expand Down
38 changes: 38 additions & 0 deletions src/AngleSharp.Js/Proxies/DomCollectionInstance.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace AngleSharp.Js
{
using AngleSharp.Js.Cache;
using Jint;
using Jint.Native;
using Jint.Native.Object;
using Jint.Native.Symbol;
Expand Down Expand Up @@ -109,6 +110,16 @@ public override PropertyDescriptor GetOwnProperty(JsValue property)
return descriptor;
}

// The base has answered every array-index key authoritatively - an index past the
// end is a miss its value and existence hooks already committed to - so the string
// indexer must not be asked about one. WebIDL says the same: an object supporting
// indexed properties never serves an array-index name from its named getter. An
// element whose id is "5" is findable through a non-index name, never through 5.
if (IsArrayIndexName(property))
{
return PropertyDescriptor.Undefined;
}

if (Prototype is DomPrototypeInstance prototype &&
prototype.TryGetFromNamedIndex(_value, property, out _))
{
Expand All @@ -118,6 +129,33 @@ public override PropertyDescriptor GetOwnProperty(JsValue property)
return PropertyDescriptor.Undefined;
}

// The test the engine's array-like model applies to a key, mirrored exactly so this
// class and its base always classify a name the same way: a number holding a UInt32
// below the maximum, or its canonical string form - no sign, no space, no leading zero.
private static Boolean IsArrayIndexName(JsValue property)
{
if (property.IsNumber())
{
var value = property.AsNumber();
var index = (UInt32)value;
return value == index && index != UInt32.MaxValue;
}

if (property.IsString())
{
var name = property.ToString();

if (name.Length == 0 || (name.Length > 1 && (name[0] < '1' || name[0] > '9')))
{
return false;
}

return UInt32.TryParse(name, out var index) && index != UInt32.MaxValue;
}

return false;
}

protected override void SetOwnProperty(JsValue property, PropertyDescriptor desc)
{
if (Prototype is DomPrototypeInstance prototype &&
Expand Down
Loading