// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.ClearScript.JavaScript;
using Microsoft.ClearScript.Util;
namespace Microsoft.ClearScript.V8
{
// ReSharper disable once PartialTypeWithSinglePart
///
/// Represents an instance of the V8 JavaScript engine.
///
///
/// Unlike WindowsScriptEngine instances, V8ScriptEngine instances do not have
/// thread affinity. The underlying script engine is not thread-safe, however, so this class
/// uses internal locks to automatically serialize all script code execution for a given
/// instance. Script delegates and event handlers are invoked on the calling thread without
/// marshaling.
///
public sealed partial class V8ScriptEngine : ScriptEngine, IJavaScriptEngine
{
#region data
private static readonly DocumentInfo initScriptInfo = new DocumentInfo(MiscHelpers.FormatInvariant("{0} [internal]", nameof(V8ScriptEngine)));
private readonly V8Runtime runtime;
private readonly bool usingPrivateRuntime;
private readonly V8ScriptEngineFlags engineFlags;
private readonly V8ContextProxy proxy;
private readonly V8ScriptItem script;
private readonly InterlockedOneWayFlag disposedFlag = new InterlockedOneWayFlag();
private const int continuationInterval = 2000;
private bool inContinuationTimerScope;
private bool awaitDebuggerAndPause;
private List documentNames;
private bool suppressInstanceMethodEnumeration;
private bool suppressExtensionMethodEnumeration;
private CommonJSManager commonJSManager;
#endregion
#region constructors
///
/// Initializes a new V8 script engine instance.
///
///
/// A separate V8 runtime is created for the new script engine instance.
///
public V8ScriptEngine()
: this(null, null)
{
}
///
/// Initializes a new V8 script engine instance with the specified name.
///
/// A name to associate with the instance. Currently this name is used only as a label in presentation contexts such as debugger user interfaces.
///
/// A separate V8 runtime is created for the new script engine instance.
///
public V8ScriptEngine(string name)
: this(name, null)
{
}
///
/// Initializes a new V8 script engine instance with the specified resource constraints.
///
/// Resource constraints for the V8 runtime (see remarks).
///
/// A separate V8 runtime is created for the new script engine instance.
///
public V8ScriptEngine(V8RuntimeConstraints constraints)
: this(null, constraints)
{
}
///
/// Initializes a new V8 script engine instance with the specified name and resource constraints.
///
/// A name to associate with the instance. Currently this name is used only as a label in presentation contexts such as debugger user interfaces.
/// Resource constraints for the V8 runtime (see remarks).
///
/// A separate V8 runtime is created for the new script engine instance.
///
public V8ScriptEngine(string name, V8RuntimeConstraints constraints)
: this(name, constraints, V8ScriptEngineFlags.None)
{
}
///
/// Initializes a new V8 script engine instance with the specified options.
///
/// A value that selects options for the operation.
///
/// A separate V8 runtime is created for the new script engine instance.
///
public V8ScriptEngine(V8ScriptEngineFlags flags)
: this(flags, 0)
{
}
///
/// Initializes a new V8 script engine instance with the specified options and debug port.
///
/// A value that selects options for the operation.
/// A TCP port on which to listen for a debugger connection.
///
/// A separate V8 runtime is created for the new script engine instance.
///
public V8ScriptEngine(V8ScriptEngineFlags flags, int debugPort)
: this(null, null, flags, debugPort)
{
}
///
/// Initializes a new V8 script engine instance with the specified name and options.
///
/// A name to associate with the instance. Currently this name is used only as a label in presentation contexts such as debugger user interfaces.
/// A value that selects options for the operation.
///
/// A separate V8 runtime is created for the new script engine instance.
///
public V8ScriptEngine(string name, V8ScriptEngineFlags flags)
: this(name, flags, 0)
{
}
///
/// Initializes a new V8 script engine instance with the specified name, options, and debug port.
///
/// A name to associate with the instance. Currently this name is used only as a label in presentation contexts such as debugger user interfaces.
/// A value that selects options for the operation.
/// A TCP port on which to listen for a debugger connection.
///
/// A separate V8 runtime is created for the new script engine instance.
///
public V8ScriptEngine(string name, V8ScriptEngineFlags flags, int debugPort)
: this(name, null, flags, debugPort)
{
}
///
/// Initializes a new V8 script engine instance with the specified resource constraints and options.
///
/// Resource constraints for the V8 runtime (see remarks).
/// A value that selects options for the operation.
///
/// A separate V8 runtime is created for the new script engine instance.
///
public V8ScriptEngine(V8RuntimeConstraints constraints, V8ScriptEngineFlags flags)
: this(constraints, flags, 0)
{
}
///
/// Initializes a new V8 script engine instance with the specified resource constraints, options, and debug port.
///
/// Resource constraints for the V8 runtime (see remarks).
/// A value that selects options for the operation.
/// A TCP port on which to listen for a debugger connection.
///
/// A separate V8 runtime is created for the new script engine instance.
///
public V8ScriptEngine(V8RuntimeConstraints constraints, V8ScriptEngineFlags flags, int debugPort)
: this(null, constraints, flags, debugPort)
{
}
///
/// Initializes a new V8 script engine instance with the specified name, resource constraints, and options.
///
/// A name to associate with the instance. Currently this name is used only as a label in presentation contexts such as debugger user interfaces.
/// Resource constraints for the V8 runtime (see remarks).
/// A value that selects options for the operation.
///
/// A separate V8 runtime is created for the new script engine instance.
///
public V8ScriptEngine(string name, V8RuntimeConstraints constraints, V8ScriptEngineFlags flags)
: this(name, constraints, flags, 0)
{
}
///
/// Initializes a new V8 script engine instance with the specified name, resource constraints, options, and debug port.
///
/// A name to associate with the instance. Currently this name is used only as a label in presentation contexts such as debugger user interfaces.
/// Resource constraints for the V8 runtime (see remarks).
/// A value that selects options for the operation.
/// A TCP port on which to listen for a debugger connection.
///
/// A separate V8 runtime is created for the new script engine instance.
///
public V8ScriptEngine(string name, V8RuntimeConstraints constraints, V8ScriptEngineFlags flags, int debugPort)
: this(null, name, constraints, flags, debugPort)
{
}
internal V8ScriptEngine(V8Runtime runtime, string name, V8RuntimeConstraints constraints, V8ScriptEngineFlags flags, int debugPort)
: base((runtime != null) ? runtime.Name + ":" + name : name, "js")
{
if (runtime != null)
{
this.runtime = runtime;
}
else
{
this.runtime = runtime = new V8Runtime(name, constraints);
usingPrivateRuntime = true;
}
DocumentNameManager = runtime.DocumentNameManager;
HostItemCollateral = runtime.HostItemCollateral;
engineFlags = flags;
proxy = V8ContextProxy.Create(runtime.IsolateProxy, Name, flags, debugPort);
script = (V8ScriptItem)GetRootItem();
Execute(initScriptInfo,
@"
Object.defineProperty(this, 'EngineInternal', { value: (function () {
function convertArgs(args) {
let result = [];
let count = args.Length;
for (let i = 0; i < count; i++) {
result.push(args[i]);
}
return result;
}
function construct() {
return new this(...arguments);
}
const isHostObjectKey = this.isHostObjectKey;
delete this.isHostObjectKey;
const savedPromise = Promise;
const checkpointSymbol = Symbol();
return Object.freeze({
commandHolder: {
},
getCommandResult: function (value) {
if (value == null) {
return value;
}
if (typeof(value.hasOwnProperty) != 'function') {
if (value[Symbol.toStringTag] == 'Module') {
return '[module]';
}
return '[external]';
}
if (value[isHostObjectKey] === true) {
return value;
}
if (typeof(value.toString) != 'function') {
return '[' + typeof(value) + ']';
}
return value.toString();
},
invokeConstructor: function (constructor, args) {
if (typeof(constructor) != 'function') {
throw new Error('Function expected');
}
return construct.apply(constructor, convertArgs(args));
},
invokeMethod: function (target, method, args) {
if (typeof(method) != 'function') {
throw new Error('Function expected');
}
return method.apply(target, convertArgs(args));
},
createPromise: function () {
return new savedPromise(...arguments);
},
isPromise: function (value) {
return value instanceof savedPromise;
},
completePromiseWithResult: function (getResult, resolve, reject) {
try {
resolve(getResult());
}
catch (exception) {
reject(exception);
}
return undefined;
},
completePromise: function (wait, resolve, reject) {
try {
wait();
resolve();
}
catch (exception) {
reject(exception);
}
return undefined;
},
throwValue: function (value) {
throw value;
},
getStackTrace: function () {
try {
throw new Error('[stack trace]');
}
catch (exception) {
return exception.stack;
}
return '';
},
toIterator: function* (enumerator) {
try {
while (enumerator.MoveNext()) {
yield enumerator.Current;
}
}
finally {
enumerator.Dispose();
}
},
toAsyncIterator: async function* (asyncEnumerator) {
try {
while (await asyncEnumerator.MoveNextPromise()) {
yield asyncEnumerator.Current;
}
}
finally {
await asyncEnumerator.DisposePromise();
}
},
checkpoint: function () {
const value = globalThis[checkpointSymbol];
if (value) {
throw value;
}
}
});
})() });
"
);
if (flags.HasFlag(V8ScriptEngineFlags.EnableDebugging | V8ScriptEngineFlags.AwaitDebuggerAndPauseOnStart))
{
awaitDebuggerAndPause = true;
}
}
#endregion
#region public members
///
/// Resumes script execution if the script engine is waiting for a debugger connection.
///
///
/// This method can be called safely from any thread.
///
public void CancelAwaitDebugger()
{
VerifyNotDisposed();
proxy.CancelAwaitDebugger();
}
///
/// Gets or sets a soft limit for the size of the V8 runtime's heap.
///
///
///
/// This property is specified in bytes. When it is set to the default value, heap size
/// monitoring is disabled, and scripts with memory leaks or excessive memory usage
/// can cause unrecoverable errors and process termination.
///
///
/// A V8 runtime unconditionally terminates the process when it exceeds its resource
/// constraints (see ). This property enables external
/// heap size monitoring that can prevent termination in some scenarios. To be effective,
/// it should be set to a value that is significantly lower than
/// . Note that enabling heap size
/// monitoring results in slower script execution.
///
///
/// Exceeding this limit causes the V8 runtime to behave in accordance with
/// .
///
///
/// Note that
/// ArrayBuffer
/// memory is allocated outside the runtime's heap and is therefore not tracked by heap
/// size monitoring. See for
/// additional information.
///
///
public UIntPtr MaxRuntimeHeapSize
{
get
{
VerifyNotDisposed();
return proxy.MaxIsolateHeapSize;
}
set
{
VerifyNotDisposed();
proxy.MaxIsolateHeapSize = value;
}
}
///
/// Gets or sets the minimum time interval between consecutive heap size samples.
///
///
/// This property is effective only when heap size monitoring is enabled (see
/// ).
///
public TimeSpan RuntimeHeapSizeSampleInterval
{
get
{
VerifyNotDisposed();
return proxy.IsolateHeapSizeSampleInterval;
}
set
{
VerifyNotDisposed();
proxy.IsolateHeapSizeSampleInterval = value;
}
}
///
/// Gets or sets the maximum amount by which the V8 runtime is permitted to grow the stack during script execution.
///
///
///
/// This property is specified in bytes. When it is set to the default value, no stack
/// usage limit is enforced, and scripts with unchecked recursion or other excessive stack
/// usage can cause unrecoverable errors and process termination.
///
///
/// Note that the V8 runtime does not monitor stack usage while a host call is in progress.
/// Monitoring is resumed when control returns to the runtime.
///
///
public UIntPtr MaxRuntimeStackUsage
{
get
{
VerifyNotDisposed();
return proxy.MaxIsolateStackUsage;
}
set
{
VerifyNotDisposed();
proxy.MaxIsolateStackUsage = value;
}
}
///
/// Enables or disables instance method enumeration.
///
///
/// By default, a host object's instance methods are exposed as enumerable properties.
/// Setting this property to true causes instance methods to be excluded from
/// property enumeration. This affects all host objects exposed in the current script
/// engine. Note that instance methods remain both retrievable and invocable regardless of
/// this property's value.
///
public bool SuppressInstanceMethodEnumeration
{
get => suppressInstanceMethodEnumeration;
set
{
suppressInstanceMethodEnumeration = value;
OnEnumerationSettingsChanged();
}
}
///
/// Enables or disables extension method enumeration.
///
///
///
/// By default, all exposed extension methods appear as enumerable properties of all host
/// objects, regardless of type. Setting this property to true causes extension
/// methods to be excluded from property enumeration. This affects all host objects exposed
/// in the current script engine. Note that extension methods remain both retrievable and
/// invocable regardless of this property's value.
///
///
/// This property has no effect if is set
/// to true.
///
///
public bool SuppressExtensionMethodEnumeration
{
get => suppressExtensionMethodEnumeration;
set
{
suppressExtensionMethodEnumeration = value;
RebuildExtensionMethodSummary();
}
}
///
/// Enables or disables interrupt propagation in the V8 runtime.
///
///
/// By default, when nested script execution is interrupted via , an
/// instance of , if not handled by the host, is
/// wrapped and delivered to the parent script frame as a normal exception that JavaScript
/// code can catch. Setting this property to true causes the V8 runtime to remain in
/// the interrupted state until its outermost script frame has been processed.
///
public bool EnableRuntimeInterruptPropagation
{
get
{
VerifyNotDisposed();
return proxy.EnableIsolateInterruptPropagation;
}
set
{
VerifyNotDisposed();
proxy.EnableIsolateInterruptPropagation = value;
}
}
///
/// Gets or sets the V8 runtime's behavior in response to a violation of the maximum heap size.
///
public V8RuntimeViolationPolicy RuntimeHeapSizeViolationPolicy
{
get
{
VerifyNotDisposed();
return proxy.DisableIsolateHeapSizeViolationInterrupt ? V8RuntimeViolationPolicy.Exception : V8RuntimeViolationPolicy.Interrupt;
}
set
{
VerifyNotDisposed();
switch (value)
{
case V8RuntimeViolationPolicy.Interrupt:
proxy.DisableIsolateHeapSizeViolationInterrupt = false;
return;
case V8RuntimeViolationPolicy.Exception:
proxy.DisableIsolateHeapSizeViolationInterrupt = true;
return;
default:
throw new ArgumentException(MiscHelpers.FormatInvariant("Invalid {0} value", nameof(V8RuntimeViolationPolicy)), nameof(value));
}
}
}
///
/// Creates a compiled script.
///
/// The script code to compile.
/// A compiled script that can be executed multiple times without recompilation.
public V8Script Compile(string code)
{
return Compile(null, code);
}
///
/// Creates a compiled script with an associated document name.
///
/// A document name for the compiled script. Currently this name is used only as a label in presentation contexts such as debugger user interfaces.
/// The script code to compile.
/// A compiled script that can be executed multiple times without recompilation.
public V8Script Compile(string documentName, string code)
{
return Compile(new DocumentInfo(documentName), code);
}
///
/// Creates a compiled script with the specified document meta-information.
///
/// A structure containing meta-information for the script document.
/// The script code to compile.
/// A compiled script that can be executed multiple times without recompilation.
public V8Script Compile(DocumentInfo documentInfo, string code)
{
VerifyNotDisposed();
return ScriptInvoke(() => CompileInternal(documentInfo.MakeUnique(this), code));
}
///
/// Creates a compiled script, generating cache data for accelerated recompilation.
///
/// The script code to compile.
/// The kind of cache data to be generated.
/// Cache data for accelerated recompilation.
/// A compiled script that can be executed multiple times without recompilation.
///
/// The generated cache data can be stored externally and is usable in other V8 script
/// engines and application processes.
///
///
public V8Script Compile(string code, V8CacheKind cacheKind, out byte[] cacheBytes)
{
return Compile(null, code, cacheKind, out cacheBytes);
}
///
/// Creates a compiled script with an associated document name, generating cache data for accelerated recompilation.
///
/// A document name for the compiled script. Currently this name is used only as a label in presentation contexts such as debugger user interfaces.
/// The script code to compile.
/// The kind of cache data to be generated.
/// Cache data for accelerated recompilation.
/// A compiled script that can be executed multiple times without recompilation.
///
/// The generated cache data can be stored externally and is usable in other V8 script
/// engines and application processes.
///
///
public V8Script Compile(string documentName, string code, V8CacheKind cacheKind, out byte[] cacheBytes)
{
return Compile(new DocumentInfo(documentName), code, cacheKind, out cacheBytes);
}
///
/// Creates a compiled script with the specified document meta-information, generating cache data for accelerated recompilation.
///
/// A structure containing meta-information for the script document.
/// The script code to compile.
/// The kind of cache data to be generated.
/// Cache data for accelerated recompilation.
/// A compiled script that can be executed multiple times without recompilation.
///
/// The generated cache data can be stored externally and is usable in other V8 script
/// engines and application processes.
///
///
public V8Script Compile(DocumentInfo documentInfo, string code, V8CacheKind cacheKind, out byte[] cacheBytes)
{
VerifyNotDisposed();
V8Script tempScript = null;
cacheBytes = ScriptInvoke(() =>
{
tempScript = CompileInternal(documentInfo.MakeUnique(this), code, cacheKind, out var tempCacheBytes);
return tempCacheBytes;
});
return tempScript;
}
///
/// Creates a compiled script, consuming previously generated cache data.
///
/// The script code to compile.
/// The kind of cache data to be consumed.
/// Cache data for accelerated compilation.
/// True if was accepted, false otherwise.
/// A compiled script that can be executed multiple times without recompilation.
///
/// To be accepted, the cache data must have been generated for identical script code by
/// the same V8 build.
///
///
public V8Script Compile(string code, V8CacheKind cacheKind, byte[] cacheBytes, out bool cacheAccepted)
{
return Compile(null, code, cacheKind, cacheBytes, out cacheAccepted);
}
///
/// Creates a compiled script with an associated document name, consuming previously generated cache data.
///
/// A document name for the compiled script. Currently this name is used only as a label in presentation contexts such as debugger user interfaces.
/// The script code to compile.
/// The kind of cache data to be consumed.
/// Cache data for accelerated compilation.
/// True if was accepted, false otherwise.
/// A compiled script that can be executed multiple times without recompilation.
///
/// To be accepted, the cache data must have been generated for identical script code by
/// the same V8 build.
///
///
public V8Script Compile(string documentName, string code, V8CacheKind cacheKind, byte[] cacheBytes, out bool cacheAccepted)
{
return Compile(new DocumentInfo(documentName), code, cacheKind, cacheBytes, out cacheAccepted);
}
///
/// Creates a compiled script with an associated document name, consuming previously generated cache data.
///
/// A structure containing meta-information for the script document.
/// The script code to compile.
/// The kind of cache data to be consumed.
/// Cache data for accelerated compilation.
/// True if was accepted, false otherwise.
/// A compiled script that can be executed multiple times without recompilation.
///
/// To be accepted, the cache data must have been generated for identical script code by
/// the same V8 build.
///
///
public V8Script Compile(DocumentInfo documentInfo, string code, V8CacheKind cacheKind, byte[] cacheBytes, out bool cacheAccepted)
{
VerifyNotDisposed();
V8Script tempScript = null;
cacheAccepted = ScriptInvoke(() =>
{
tempScript = CompileInternal(documentInfo.MakeUnique(this), code, cacheKind, cacheBytes, out var tempCacheAccepted);
return tempCacheAccepted;
});
return tempScript;
}
///
/// Loads and compiles a script document.
///
/// A string specifying the document to be loaded and compiled.
/// A compiled script that can be executed by multiple V8 script engine instances.
public V8Script CompileDocument(string specifier)
{
return CompileDocument(specifier, null);
}
///
/// Loads and compiles a document with the specified category.
///
/// A string specifying the document to be loaded and compiled.
/// An optional category for the requested document.
/// A compiled script that can be executed by multiple V8 script engine instances.
public V8Script CompileDocument(string specifier, DocumentCategory category)
{
return CompileDocument(specifier, category, null);
}
///
/// Loads and compiles a document with the specified category and context callback.
///
/// A string specifying the document to be loaded and compiled.
/// An optional category for the requested document.
/// An optional context callback for the requested document.
/// A compiled script that can be executed by multiple V8 script engine instances.
public V8Script CompileDocument(string specifier, DocumentCategory category, DocumentContextCallback contextCallback)
{
MiscHelpers.VerifyNonBlankArgument(specifier, nameof(specifier), "Invalid document specifier");
var document = DocumentSettings.LoadDocument(null, specifier, category, contextCallback);
return Compile(document.Info, document.GetTextContents());
}
///
/// Loads and compiles a script document, generating cache data for accelerated recompilation.
///
/// A string specifying the document to be loaded and compiled.
/// The kind of cache data to be generated.
/// Cache data for accelerated recompilation.
/// A compiled script that can be executed by multiple V8 script engine instances.
///
/// The generated cache data can be stored externally and is usable in other V8 runtimes
/// and application processes.
///
public V8Script CompileDocument(string specifier, V8CacheKind cacheKind, out byte[] cacheBytes)
{
return CompileDocument(specifier, null, cacheKind, out cacheBytes);
}
///
/// Loads and compiles a document with the specified category, generating cache data for accelerated recompilation.
///
/// A string specifying the document to be loaded and compiled.
/// An optional category for the requested document.
/// The kind of cache data to be generated.
/// Cache data for accelerated recompilation.
/// A compiled script that can be executed by multiple V8 script engine instances.
///
/// The generated cache data can be stored externally and is usable in other V8 runtimes
/// and application processes.
///
public V8Script CompileDocument(string specifier, DocumentCategory category, V8CacheKind cacheKind, out byte[] cacheBytes)
{
return CompileDocument(specifier, category, null, cacheKind, out cacheBytes);
}
///
/// Loads and compiles a document with the specified category and context callback, generating cache data for accelerated recompilation.
///
/// A string specifying the document to be loaded and compiled.
/// An optional category for the requested document.
/// An optional context callback for the requested document.
/// The kind of cache data to be generated.
/// Cache data for accelerated recompilation.
/// A compiled script that can be executed by multiple V8 script engine instances.
///
/// The generated cache data can be stored externally and is usable in other V8 runtimes
/// and application processes.
///
public V8Script CompileDocument(string specifier, DocumentCategory category, DocumentContextCallback contextCallback, V8CacheKind cacheKind, out byte[] cacheBytes)
{
MiscHelpers.VerifyNonBlankArgument(specifier, nameof(specifier), "Invalid document specifier");
var document = DocumentSettings.LoadDocument(null, specifier, category, contextCallback);
return Compile(document.Info, document.GetTextContents(), cacheKind, out cacheBytes);
}
///
/// Loads and compiles a script document, consuming previously generated cache data.
///
/// A string specifying the document to be loaded and compiled.
/// The kind of cache data to be consumed.
/// Cache data for accelerated compilation.
/// True if was accepted, false otherwise.
/// A compiled script that can be executed by multiple V8 script engine instances.
///
/// To be accepted, the cache data must have been generated for identical script code by
/// the same V8 build.
///
public V8Script CompileDocument(string specifier, V8CacheKind cacheKind, byte[] cacheBytes, out bool cacheAccepted)
{
return CompileDocument(specifier, null, cacheKind, cacheBytes, out cacheAccepted);
}
///
/// Loads and compiles a document with the specified category, consuming previously generated cache data.
///
/// A string specifying the document to be loaded and compiled.
/// An optional category for the requested document.
/// The kind of cache data to be consumed.
/// Cache data for accelerated compilation.
/// True if was accepted, false otherwise.
/// A compiled script that can be executed by multiple V8 script engine instances.
///
/// To be accepted, the cache data must have been generated for identical script code by
/// the same V8 build.
///
public V8Script CompileDocument(string specifier, DocumentCategory category, V8CacheKind cacheKind, byte[] cacheBytes, out bool cacheAccepted)
{
return CompileDocument(specifier, category, null, cacheKind, cacheBytes, out cacheAccepted);
}
///
/// Loads and compiles a document with the specified category and context callback, consuming previously generated cache data.
///
/// A string specifying the document to be loaded and compiled.
/// An optional category for the requested document.
/// An optional context callback for the requested document.
/// The kind of cache data to be consumed.
/// Cache data for accelerated compilation.
/// True if was accepted, false otherwise.
/// A compiled script that can be executed by multiple V8 script engine instances.
///
/// To be accepted, the cache data must have been generated for identical script code by
/// the same V8 build.
///
public V8Script CompileDocument(string specifier, DocumentCategory category, DocumentContextCallback contextCallback, V8CacheKind cacheKind, byte[] cacheBytes, out bool cacheAccepted)
{
MiscHelpers.VerifyNonBlankArgument(specifier, nameof(specifier), "Invalid document specifier");
var document = DocumentSettings.LoadDocument(null, specifier, category, contextCallback);
return Compile(document.Info, document.GetTextContents(), cacheKind, cacheBytes, out cacheAccepted);
}
// ReSharper disable ParameterHidesMember
///
/// Evaluates a compiled script.
///
/// The compiled script to evaluate.
/// The result value.
///
/// For information about the types of result values that script code can return, see
/// .
///
public object Evaluate(V8Script script)
{
return Execute(script, true);
}
///
/// Executes a compiled script.
///
/// The compiled script to execute.
///
/// This method is similar to with the exception that it
/// does not marshal a result value to the host. It can provide a performance advantage
/// when the result value is not needed.
///
public void Execute(V8Script script)
{
Execute(script, false);
}
// ReSharper restore ParameterHidesMember
///
/// Cancels any pending request to interrupt script execution.
///
///
/// This method can be called safely from any thread.
///
///
public void CancelInterrupt()
{
VerifyNotDisposed();
proxy.CancelInterrupt();
}
///
/// Returns memory usage information for the V8 runtime.
///
/// A object containing memory usage information for the V8 runtime.
public V8RuntimeHeapInfo GetRuntimeHeapInfo()
{
VerifyNotDisposed();
return proxy.GetIsolateHeapInfo();
}
///
/// Begins collecting a new CPU profile.
///
/// A name for the profile.
/// True if the profile was created successfully, false otherwise.
///
/// A V8 script engine can collect multiple CPU profiles simultaneously.
///
public bool BeginCpuProfile(string name)
{
return BeginCpuProfile(name, V8CpuProfileFlags.None);
}
///
/// Begins collecting a new CPU profile with the specified options.
///
/// A name for the profile.
/// Options for creating the profile.
/// True if the profile was created successfully, false otherwise.
///
/// A V8 script engine can collect multiple CPU profiles simultaneously.
///
public bool BeginCpuProfile(string name, V8CpuProfileFlags flags)
{
VerifyNotDisposed();
return proxy.BeginCpuProfile(Name + ':' + name, flags);
}
///
/// Completes and returns a CPU profile.
///
/// The name of the profile.
/// The profile if it was found and completed successfully, null otherwise.
///
/// An empty argument selects the most recently created CPU profile.
///
public V8CpuProfile EndCpuProfile(string name)
{
VerifyNotDisposed();
return proxy.EndCpuProfile(Name + ':' + name);
}
///
/// Collects a sample in all CPU profiles active in the V8 runtime.
///
public void CollectCpuProfileSample()
{
VerifyNotDisposed();
proxy.CollectCpuProfileSample();
}
///
/// Gets or sets the time interval between automatic CPU profile samples, in microseconds.
///
///
/// Assigning this property has no effect on CPU profiles already active in the V8 runtime.
/// The default value is 1000.
///
public uint CpuProfileSampleInterval
{
get
{
VerifyNotDisposed();
return proxy.CpuProfileSampleInterval;
}
set
{
VerifyNotDisposed();
proxy.CpuProfileSampleInterval = value;
}
}
///
/// Writes a snapshot of the V8 runtime's heap to the given stream.
///
/// The stream to which to write the heap snapshot.
///
/// This method generates a heap snapshot in JSON format with ASCII encoding.
///
public void WriteRuntimeHeapSnapshot(Stream stream)
{
MiscHelpers.VerifyNonNullArgument(stream, nameof(stream));
VerifyNotDisposed();
ScriptInvoke(() => proxy.WriteIsolateHeapSnapshot(stream));
}
#endregion
#region internal members
internal V8Runtime.Statistics GetRuntimeStatistics()
{
VerifyNotDisposed();
return proxy.GetIsolateStatistics();
}
internal Statistics GetStatistics()
{
VerifyNotDisposed();
return ScriptInvoke(() =>
{
var statistics = proxy.GetStatistics();
if (commonJSManager != null)
{
statistics.CommonJSModuleCacheSize = CommonJSManager.ModuleCacheSize;
}
return statistics;
});
}
private CommonJSManager CommonJSManager => commonJSManager ?? (commonJSManager = new CommonJSManager(this));
private object GetRootItem()
{
return MarshalToHost(ScriptInvoke(() => proxy.GetRootItem()), false);
}
private void VerifyNotDisposed()
{
if (disposedFlag.IsSet)
{
throw new ObjectDisposedException(ToString());
}
}
// ReSharper disable ParameterHidesMember
private object Execute(V8Script script, bool evaluate)
{
MiscHelpers.VerifyNonNullArgument(script, nameof(script));
VerifyNotDisposed();
return MarshalToHost(ScriptInvoke(() =>
{
if (inContinuationTimerScope || (ContinuationCallback == null))
{
if (MiscHelpers.Exchange(ref awaitDebuggerAndPause, false))
{
proxy.AwaitDebuggerAndPause();
}
return ExecuteInternal(script, evaluate);
}
var state = new Timer[] { null };
using (state[0] = new Timer(unused => OnContinuationTimer(state[0]), null, Timeout.Infinite, Timeout.Infinite))
{
inContinuationTimerScope = true;
try
{
state[0].Change(continuationInterval, Timeout.Infinite);
if (MiscHelpers.Exchange(ref awaitDebuggerAndPause, false))
{
proxy.AwaitDebuggerAndPause();
}
return ExecuteInternal(script, evaluate);
}
finally
{
inContinuationTimerScope = false;
}
}
}), false);
}
// ReSharper restore ParameterHidesMember
private V8Script CompileInternal(UniqueDocumentInfo documentInfo, string code)
{
if (FormatCode)
{
code = MiscHelpers.FormatCode(code);
}
CommonJSManager.Module module = null;
if (documentInfo.Category == ModuleCategory.CommonJS)
{
module = CommonJSManager.GetOrCreateModule(documentInfo, code);
code = CommonJSManager.Module.GetAugmentedCode(code);
}
// ReSharper disable once LocalVariableHidesMember
var script = proxy.Compile(documentInfo, code);
if (module != null)
{
module.Evaluator = () => proxy.Execute(script, true);
}
return script;
}
private V8Script CompileInternal(UniqueDocumentInfo documentInfo, string code, V8CacheKind cacheKind, out byte[] cacheBytes)
{
if (FormatCode)
{
code = MiscHelpers.FormatCode(code);
}
CommonJSManager.Module module = null;
if (documentInfo.Category == ModuleCategory.CommonJS)
{
module = CommonJSManager.GetOrCreateModule(documentInfo, code);
code = CommonJSManager.Module.GetAugmentedCode(code);
}
// ReSharper disable once LocalVariableHidesMember
var script = proxy.Compile(documentInfo, code, cacheKind, out cacheBytes);
if (module != null)
{
module.Evaluator = () => proxy.Execute(script, true);
}
return script;
}
private V8Script CompileInternal(UniqueDocumentInfo documentInfo, string code, V8CacheKind cacheKind, byte[] cacheBytes, out bool cacheAccepted)
{
if (FormatCode)
{
code = MiscHelpers.FormatCode(code);
}
CommonJSManager.Module module = null;
if (documentInfo.Category == ModuleCategory.CommonJS)
{
module = CommonJSManager.GetOrCreateModule(documentInfo, code);
code = CommonJSManager.Module.GetAugmentedCode(code);
}
// ReSharper disable once LocalVariableHidesMember
var script = proxy.Compile(documentInfo, code, cacheKind, cacheBytes, out cacheAccepted);
if (module != null)
{
module.Evaluator = () => proxy.Execute(script, true);
}
return script;
}
private object ExecuteInternal(UniqueDocumentInfo documentInfo, string code, bool evaluate)
{
if (FormatCode)
{
code = MiscHelpers.FormatCode(code);
}
if (documentInfo.Category == ModuleCategory.CommonJS)
{
var module = CommonJSManager.GetOrCreateModule(documentInfo, code);
return module.Process();
}
return ExecuteRaw(documentInfo, code, evaluate);
}
// ReSharper disable ParameterHidesMember
private object ExecuteInternal(V8Script script, bool evaluate)
{
if (script.UniqueDocumentInfo.Category == ModuleCategory.CommonJS)
{
var module = CommonJSManager.GetOrCreateModule(script.UniqueDocumentInfo, script.CodeDigest, () => proxy.Execute(script, evaluate));
return module.Process();
}
return proxy.Execute(script, evaluate);
}
// ReSharper restore ParameterHidesMember
private void OnContinuationTimer(Timer timer)
{
try
{
var callback = ContinuationCallback;
if ((callback != null) && !callback())
{
Interrupt();
}
else
{
timer.Change(continuationInterval, Timeout.Infinite);
}
}
catch (ObjectDisposedException)
{
}
}
private object CreatePromise(Action