diff --git a/src/AngleSharp.Js.Tests/InteractionTests.cs b/src/AngleSharp.Js.Tests/InteractionTests.cs index eff1a33..178524a 100644 --- a/src/AngleSharp.Js.Tests/InteractionTests.cs +++ b/src/AngleSharp.Js.Tests/InteractionTests.cs @@ -113,6 +113,30 @@ public async Task RunScriptSnippetDirectlyGetSimpleValueFromCalculation() Assert.AreEqual(3.0, result); } + [Test] + public async Task RunSameScriptSourceInSeveralDocumentsKeepsStateSeparate() + { + var html = "Test"; + var config = Configuration.Default.WithJs(); + var source = "(function () { window.counter = (window.counter || 0) + 1; return window.counter; })()"; + var first = await BrowsingContext.New(config).OpenAsync(m => m.Content(html)); + var second = await BrowsingContext.New(config).OpenAsync(m => m.Content(html)); + + Assert.AreEqual(1.0, first.ExecuteScript(source)); + Assert.AreEqual(1.0, second.ExecuteScript(source)); + Assert.AreEqual(2.0, first.ExecuteScript(source)); + Assert.AreEqual(2.0, second.ExecuteScript(source)); + } + + [Test] + public async Task RunScriptSnippetWithSyntaxErrorThrows() + { + var html = "Test"; + var config = Configuration.Default.WithJs(); + var document = await BrowsingContext.New(config).OpenAsync(m => m.Content(html)); + Assert.Throws(() => document.ExecuteScript("function (")); + } + [Test] public async Task RunScriptAtPressingLink_Issue47() { diff --git a/src/AngleSharp.Js/Cache/ScriptCache.cs b/src/AngleSharp.Js/Cache/ScriptCache.cs new file mode 100644 index 0000000..99faff4 --- /dev/null +++ b/src/AngleSharp.Js/Cache/ScriptCache.cs @@ -0,0 +1,54 @@ +namespace AngleSharp.Js.Cache +{ + using Acornima.Ast; + using Jint; + using Jint.Runtime; + using System; + using System.Collections.Concurrent; + + /// + /// Caches the parsed and analyzed form of scripts. A prepared script is + /// documented by Jint as reusable and thread-safe, so the same one can serve + /// every document that runs the same source - a page loading a library ends up + /// parsing it once instead of once per document. + /// + static class ScriptCache + { + // There is no bound on how many distinct scripts a process may see, so the + // cache does not grow without end. The scripts worth keeping are the ones + // seen first: libraries are referenced early and by many documents. + private const Int32 Capacity = 32; + + private static readonly ConcurrentDictionary> _scripts = + new ConcurrentDictionary>(StringComparer.Ordinal); + + /// + /// Gets the prepared form of the given source. An invalid result means the + /// source could not be prepared and should be handed to the engine as text, + /// so that the engine reports the syntax error itself. + /// + public static Prepared