using Jint.Native; using Jint.Native.Global; using Jint.Native.Object; using Jint.Native.Promise; using Jint.Runtime.Descriptors; using Jint.Runtime.Environments; using Jint.Runtime.Interop; using Jint.Runtime.Modules; using Module = Jint.Runtime.Modules.Module; namespace Jint.Runtime; public class Host { private Engine? _engine; private readonly List _supportedImportAttributes = ["type"]; protected Engine Engine { get { if (_engine is null) { Throw.InvalidOperationException("Initialize has not been called"); } return _engine!; } private set => _engine = value; } /// /// Initializes the host. /// public void Initialize(Engine engine) { Engine = engine; InitializeHostDefinedRealm(); PostInitialize(); } protected virtual void PostInitialize() { } /// /// https://tc39.es/ecma262/#sec-initializehostdefinedrealm /// protected virtual void InitializeHostDefinedRealm() { var realm = CreateRealm(); var newContext = new ExecutionContext( scriptOrModule: null, lexicalEnvironment: realm.GlobalEnv, variableEnvironment: realm.GlobalEnv, privateEnvironment: null, realm: realm, function: null); Engine.EnterExecutionContext(newContext); } internal virtual GlobalEnvironment CreateGlobalEnvironment(ObjectInstance globalObject) { return JintEnvironment.NewGlobalEnvironment(Engine, globalObject, globalObject); } protected virtual ObjectInstance CreateGlobalObject(Realm realm) { var globalObject = new GlobalObject(Engine, realm); // Because the properties might need some of the built-in object // their configuration is delayed to a later step // trigger initialization globalObject.EnsureInitialized(); return globalObject; } /// /// https://tc39.es/ecma262/#sec-createrealm /// protected internal virtual Realm CreateRealm() { var realmRec = new Realm(); Engine._realmInConstruction = realmRec; CreateIntrinsics(realmRec); var globalObject = CreateGlobalObject(realmRec); var globalEnv = CreateGlobalEnvironment(globalObject); realmRec.GlobalEnv = globalEnv; realmRec.GlobalObject = globalObject; Engine._realmInConstruction = null!; return realmRec; } /// /// https://tc39.es/ecma262/#sec-createintrinsics /// protected virtual void CreateIntrinsics(Realm realmRec) { var intrinsics = new Intrinsics(Engine, realmRec); realmRec.Intrinsics = intrinsics; } /// /// https://tc39.es/ecma262/#sec-hostensurecancompilestrings /// public virtual void EnsureCanCompileStrings(Realm callerRealm, Realm evalRealm) { if (!Engine.Options.Host.StringCompilationAllowed) { Throw.JavaScriptException(callerRealm.Intrinsics.TypeError, "String compilation has been disabled in engine options"); } } /// /// https://tc39.es/ecma262/#sec-GetImportedModule /// internal virtual Module GetImportedModule(IScriptOrModule? referrer, ModuleRequest request) { return Engine.Modules.Load(referrer?.Location, request); } /// /// https://tc39.es/ecma262/#sec-HostLoadImportedModule /// internal virtual void LoadImportedModule(IScriptOrModule? referrer, ModuleRequest moduleRequest, PromiseCapability payload) { var promise = Engine.RegisterPromise(); try { // This should instead return the PromiseInstance returned by ModuleRecord.Evaluate (currently done in Engine.EvaluateModule), but until we have await this will do. Engine.Modules.Import(moduleRequest, referrer?.Location); promise.Resolve(JsValue.Undefined); } catch (JavaScriptException ex) { promise.Reject(ex.Error); } FinishLoadingImportedModule(referrer, moduleRequest, payload, (JsPromise) promise.Promise); } /// /// https://tc39.es/ecma262/#sec-FinishLoadingImportedModule /// internal virtual void FinishLoadingImportedModule(IScriptOrModule? referrer, ModuleRequest moduleRequest, PromiseCapability payload, JsPromise result) { var onFulfilled = new ClrFunction(Engine, "", (thisObj, args) => { var moduleRecord = GetImportedModule(referrer, moduleRequest); try { var ns = Module.GetModuleNamespace(moduleRecord); payload.Resolve.Call(JsValue.Undefined, ns); } catch (JavaScriptException ex) { payload.Reject.Call(JsValue.Undefined, ex.Error); } return JsValue.Undefined; }, 0, PropertyFlag.Configurable); var onRejected = new ClrFunction(Engine, "", (thisObj, args) => { var error = args.At(0); payload.Reject.Call(JsValue.Undefined, error); return JsValue.Undefined; }, 0, PropertyFlag.Configurable); PromiseOperations.PerformPromiseThen(Engine, result, onFulfilled, onRejected, payload); } /// /// https://tc39.es/ecma262/#sec-hostgetimportmetaproperties /// public virtual List> GetImportMetaProperties(Module moduleRecord) { return new List>(); } /// /// https://tc39.es/ecma262/#sec-hostfinalizeimportmeta /// public virtual void FinalizeImportMeta(ObjectInstance importMeta, Module moduleRecord) { } /// /// https://tc39.es/proposal-shadowrealm/#sec-host-initialize-shadow-shadowrealm /// public virtual void InitializeShadowRealm(Realm realm) { } /// /// https://tc39.es/ecma262/#sec-hostmakejobcallback /// internal virtual JobCallback MakeJobCallBack(ICallable cleanupCallback) { return new JobCallback(cleanupCallback, null); } /// /// https://tc39.es/ecma262/#sec-hostenqueuepromisejob /// internal void HostEnqueuePromiseJob(Action job, Realm realm) { Engine.AddToEventLoop(job); } internal virtual List GetSupportedImportAttributes() { return _supportedImportAttributes; } } internal sealed record JobCallback(ICallable Callback, object? HostDefined);