Skip to content
Open
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
1 change: 0 additions & 1 deletion Jint.Tests/Jint.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks>
<TargetFrameworks Condition="'$(OS)' == 'Windows_NT'">$(TargetFrameworks);net461</TargetFrameworks>
<AssemblyOriginatorKeyFile>..\Jint\Jint.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>
<IsPackable>false</IsPackable>
Expand Down
96 changes: 96 additions & 0 deletions Jint.Tests/Runtime/SchedulingTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using System;
using System.Threading.Tasks;
using Xunit;

namespace Jint.Tests.Runtime
{
public class SchedulingTests
{
class Context
{
public string Result { get; set; }
}

[Fact]
public async Task CanRunAsyncCode()
{
var engine = new Engine();

var ctx = new Context();

engine.SetValue("ctx", ctx);
engine.SetValue("setTimeout", new Action<Action, int>(async (callback, delay) =>
{
using (var task = engine.CreateTask())
{
await Task.Delay(delay);

task.Invoke(callback);
}
}));

await engine.ExecuteAsync(@"
setTimeout(function () {
ctx.Result = 'Hello World';
}, 100);
");

Assert.Equal("Hello World", ctx.Result);
}
[Fact]
public async Task CanRunNestedAsyncCode()
{
var engine = new Engine();

var ctx = new Context();

engine.SetValue("ctx", ctx);
engine.SetValue("setTimeout", new Action<Action, int>(async (callback, delay) =>
{
using (var task = engine.CreateTask())
{
await Task.Delay(delay);

task.Invoke(callback);
}
}));

await engine.ExecuteAsync(@"
setTimeout(function () {
setTimeout(function () {
setTimeout(function () {
ctx.Result = 'Hello World';
}, 100);
}, 100);
}, 100);
");

Assert.Equal("Hello World", ctx.Result);
}

[Fact]
public async Task CanRunSyncCode()
{
var engine = new Engine();

var ctx = new Context();

engine.SetValue("ctx", ctx);
engine.SetValue("setTimeout", new Action<Action, int>((callback, delay) =>
{
using (var task = engine.CreateTask())
{
task.Invoke(callback);
}
}));

await engine.ExecuteAsync(@"
setTimeout(function () {
ctx.Result = 'Hello World';
}, 100);
");

Assert.Equal("Hello World", ctx.Result);
}
}
}
69 changes: 66 additions & 3 deletions Jint/Engine.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Esprima;
using Esprima.Ast;
using Jint.Native;
Expand All @@ -20,6 +21,7 @@
using Jint.Runtime.Interpreter;
using Jint.Runtime.Interpreter.Expressions;
using Jint.Runtime.References;
using Jint.Scheduling;

namespace Jint
{
Expand All @@ -36,6 +38,7 @@ public partial class Engine
internal EvaluationContext _activeEvaluationContext;

private readonly EventLoop _eventLoop = new();
private readonly Stack<Scheduler> _schedulers = new Stack<Scheduler>();

// lazy properties
private DebugHandler _debugHandler;
Expand All @@ -51,6 +54,7 @@ public partial class Engine
internal readonly JsValueArrayPool _jsValueArrayPool;
internal readonly ExtensionMethodCache _extensionMethods;


public ITypeConverter ClrTypeConverter { get; internal set; }

// cache of types used when resolving CLR type names
Expand Down Expand Up @@ -253,7 +257,53 @@ public Engine Execute(string source)
public Engine Execute(string source, ParserOptions parserOptions)
=> Execute(new JavaScriptParser(source, parserOptions).ParseScript());

public Task<Engine> ExecuteAsync(string source)
=> ExecuteAsync(source, DefaultParserOptions);

public Task<Engine> ExecuteAsync(string source, ParserOptions parserOptions)
=> ExecuteAsync(new JavaScriptParser(source, parserOptions).ParseScript());

public Engine Execute(Script script)
{
using (var scheduler = new Scheduler(false))
{
try
{
_schedulers.Push(scheduler);

ExecuteWithScheduler(scheduler, script);
}
finally
{
_schedulers.Pop();
}
}

return this;
}

public async Task<Engine> ExecuteAsync(Script script)
{
using (var scheduler = new Scheduler(true))
{
try
{
_schedulers.Push(scheduler);

ExecuteWithScheduler(scheduler, script);

await scheduler.Completion;
}
finally
{
_schedulers.Pop();
}
}

return this;
}

private void ExecuteWithScheduler(Scheduler scheduler, Script script)
{
Engine DoInvoke()
{
Expand Down Expand Up @@ -290,10 +340,23 @@ Engine DoInvoke()
return this;
}

var strict = _isStrict || script.Strict;
ExecuteWithConstraints(strict, DoInvoke);
var task = scheduler.CreateTask();

return this;
task.Invoke(() =>
{
var strict = _isStrict || script.Strict;
ExecuteWithConstraints(strict, DoInvoke);
});
}

public IDeferredTask CreateTask()
{
if (_schedulers.Count == 0)
{
throw new InvalidOperationException("Not within a script.");
}

return _schedulers.Peek().CreateTask();
}

/// <summary>
Expand Down
3 changes: 2 additions & 1 deletion Jint/Jint.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<NeutralLanguage>en-US</NeutralLanguage>
<TargetFrameworks>net461;netstandard2.0;netstandard2.1</TargetFrameworks>
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
<AssemblyOriginatorKeyFile>Jint.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>
<LangVersion>latest</LangVersion>
Expand All @@ -11,5 +11,6 @@
<PackageReference Include="Esprima" Version="2.1.2" />
<PackageReference Include="IsExternalInit" Version="1.0.1" PrivateAssets="all" />
<PackageReference Include="Nullable" Version="1.3.0" PrivateAssets="all" />
<PackageReference Include="System.Threading.Tasks" Version="4.3.0" />
</ItemGroup>
</Project>
44 changes: 44 additions & 0 deletions Jint/Scheduling/DeferredTask.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System;

namespace Jint.Scheduling
{
internal class DeferredTask : IDeferredTask
{
private readonly Scheduler _scheduler;
private bool _isCompleted;

public DeferredTask(Scheduler scheduler)
{
_scheduler = scheduler;
}

public void Dispose()
{
Cancel();
}

public void Cancel()
{
if (_isCompleted)
{
return;
}

_isCompleted = true;

_scheduler.Cancel(this);
}

public void Invoke(Action action)
{
if (_isCompleted)
{
return;
}

_isCompleted = true;

_scheduler.Invoke(this, action);
}
}
}
11 changes: 11 additions & 0 deletions Jint/Scheduling/IDeferredTask.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;

namespace Jint.Scheduling
{
public interface IDeferredTask : IDisposable
{
void Invoke(Action action);

void Cancel();
}
}
Loading