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