private BaristaModuleRecord CreateBaristaModuleRecordInternal(BaristaContext context, string moduleName, JavaScriptValueSafeHandle specifier, BaristaModuleRecord parentModule = null, bool setAsHost = false, IBaristaModuleLoader moduleLoader = null) { JavaScriptModuleRecord moduleRecord = null; if (m_specifierModuleLookup.ContainsKey(specifier)) { moduleRecord = m_specifierModuleLookup[specifier]; } if (moduleRecord == null || moduleRecord.IsClosed || moduleRecord.IsInvalid) { if (m_specifierModuleLookup.ContainsKey(specifier)) { m_specifierModuleLookup.Remove(specifier); } moduleRecord = m_engine.JsInitializeModuleRecord(parentModule == null ? JavaScriptModuleRecord.Invalid : parentModule.Handle, specifier); } return(m_moduleReferencePool.GetOrAdd(moduleRecord, () => { //If a module loader hasn't be specified in the parameters, obtain one from DI. if (moduleLoader == null) { moduleLoader = m_serviceProvider.GetRequiredService <IBaristaModuleLoader>(); } var module = new BaristaModuleRecord(moduleName.ToString(), specifier, parentModule, m_engine, context, this, moduleLoader, moduleRecord); m_specifierModuleLookup.Add(specifier, moduleRecord); //If specified, indicate as host module. if (setAsHost == true) { m_engine.JsSetModuleHostInfo(moduleRecord, JavaScriptModuleHostInfoKind.HostDefined, specifier.DangerousGetHandle()); } void beforeCollect(object sender, BaristaObjectBeforeCollectEventArgs args) { context.BeforeCollect -= beforeCollect; m_moduleReferencePool.RemoveHandle(new JavaScriptModuleRecord(args.Handle)); if (sender is BaristaModuleRecord baristaModuleRecord) { m_specifierModuleLookup.Remove(baristaModuleRecord.Specifier); } } module.BeforeCollect += beforeCollect; return module; })); }
public void JsModuleCanBeImportedAndExecuted() { var mainModuleName = ""; var mainModuleSource = @" import cube from 'foo'; const global = (new Function('return this;'))(); global.$EXPORTS = cube(3); "; var fooModuleSource = @" export default function cube(x) { return x * x * x; } "; var mainModuleReady = false; JavaScriptModuleRecord childModuleHandle = JavaScriptModuleRecord.Invalid; JavaScriptFetchImportedModuleCallback fetchCallback = (IntPtr referencingModule, IntPtr specifier, out IntPtr dependentModuleRecord) => { var moduleName = Engine.GetStringUtf8(new JavaScriptValueSafeHandle(specifier)); if (string.IsNullOrWhiteSpace(moduleName)) { dependentModuleRecord = referencingModule; return(false); } Assert.True(moduleName == "foo"); var moduleRecord = Engine.JsInitializeModuleRecord(new JavaScriptModuleRecord(referencingModule), new JavaScriptValueSafeHandle(specifier)); dependentModuleRecord = moduleRecord.DangerousGetHandle(); childModuleHandle = moduleRecord; return(false); }; JavaScriptFetchImportedModuleFromScriptCallback fetchFromScriptCallback = (IntPtr referencingModule, IntPtr specifier, out IntPtr dependentModuleRecord) => { var moduleName = Engine.GetStringUtf8(new JavaScriptValueSafeHandle(specifier)); if (string.IsNullOrWhiteSpace(moduleName)) { dependentModuleRecord = referencingModule; return(false); } Assert.True(moduleName == "foo"); var moduleRecord = Engine.JsInitializeModuleRecord(new JavaScriptModuleRecord(referencingModule), new JavaScriptValueSafeHandle((IntPtr)specifier)); dependentModuleRecord = moduleRecord.DangerousGetHandle(); childModuleHandle = moduleRecord; return(false); }; JavaScriptNotifyModuleReadyCallback notifyCallback = (IntPtr referencingModule, IntPtr exceptionVar) => { Assert.Equal(IntPtr.Zero, exceptionVar); mainModuleReady = true; return(false); }; using (var runtimeHandle = Engine.JsCreateRuntime(JavaScriptRuntimeAttributes.None, null)) { using (var contextHandle = Engine.JsCreateContext(runtimeHandle)) { Engine.JsSetCurrentContext(contextHandle); //Initialize the "main" module (Empty-string specifier). var moduleNameHandle = Engine.JsCreateString(mainModuleName, (ulong)mainModuleName.Length); var mainModuleHandle = Engine.JsInitializeModuleRecord(JavaScriptModuleRecord.Invalid, moduleNameHandle); Assert.True(mainModuleHandle != JavaScriptModuleRecord.Invalid); //Set the fetch callback. var fetchCallbackDelegateHandle = GCHandle.Alloc(fetchCallback); IntPtr fetchCallbackPtr = Marshal.GetFunctionPointerForDelegate(fetchCallback); Engine.JsSetModuleHostInfo(mainModuleHandle, JavaScriptModuleHostInfoKind.FetchImportedModuleCallback, fetchCallbackPtr); //Ensure the callback was set properly. var moduleHostPtr = Engine.JsGetModuleHostInfo(mainModuleHandle, JavaScriptModuleHostInfoKind.FetchImportedModuleCallback); Assert.Equal(fetchCallbackPtr, moduleHostPtr); //Set the fetchScript callback var fetchFromScriptCallbackDelegateHandle = GCHandle.Alloc(fetchCallback); IntPtr fetchFromScriptCallbackPtr = Marshal.GetFunctionPointerForDelegate(fetchFromScriptCallback); Engine.JsSetModuleHostInfo(mainModuleHandle, JavaScriptModuleHostInfoKind.FetchImportedModuleFromScriptCallback, fetchFromScriptCallbackPtr); //Ensure the callback was set properly. moduleHostPtr = Engine.JsGetModuleHostInfo(mainModuleHandle, JavaScriptModuleHostInfoKind.FetchImportedModuleFromScriptCallback); Assert.Equal(fetchFromScriptCallbackPtr, moduleHostPtr); //Set the notify callback var notifyCallbackDelegateHandle = GCHandle.Alloc(fetchCallback); IntPtr notifyCallbackPtr = Marshal.GetFunctionPointerForDelegate(notifyCallback); Engine.JsSetModuleHostInfo(mainModuleHandle, JavaScriptModuleHostInfoKind.NotifyModuleReadyCallback, notifyCallbackPtr); //Ensure the callback was set properly. moduleHostPtr = Engine.JsGetModuleHostInfo(mainModuleHandle, JavaScriptModuleHostInfoKind.NotifyModuleReadyCallback); Assert.Equal(notifyCallbackPtr, moduleHostPtr); //Indicate the host-defined, main module. Engine.JsSetModuleHostInfo(mainModuleHandle, JavaScriptModuleHostInfoKind.HostDefined, moduleNameHandle.DangerousGetHandle()); //Ensure the callback was set properly. moduleHostPtr = Engine.JsGetModuleHostInfo(mainModuleHandle, JavaScriptModuleHostInfoKind.HostDefined); Assert.Equal(moduleNameHandle.DangerousGetHandle(), moduleHostPtr); // ParseModuleSource is sync, while additional fetch & evaluation are async. var scriptBuffer = Encoding.UTF8.GetBytes(mainModuleSource); var errorHandle = Engine.JsParseModuleSource(mainModuleHandle, JavaScriptSourceContext.GetNextSourceContext(), scriptBuffer, (uint)mainModuleSource.Length, JavaScriptParseModuleSourceFlags.DataIsUTF8); Assert.True(errorHandle == JavaScriptValueSafeHandle.Invalid); Assert.True(childModuleHandle != JavaScriptModuleRecord.Invalid); Assert.False(mainModuleReady); //Parse the foo now. scriptBuffer = Encoding.UTF8.GetBytes(fooModuleSource); errorHandle = Engine.JsParseModuleSource(childModuleHandle, JavaScriptSourceContext.GetNextSourceContext(), scriptBuffer, (uint)fooModuleSource.Length, JavaScriptParseModuleSourceFlags.DataIsUTF8); Assert.True(errorHandle == JavaScriptValueSafeHandle.Invalid); Assert.True(mainModuleReady); //Now we're ready, evaluate the main module. var evalResultHandle = Engine.JsModuleEvaluation(mainModuleHandle); Assert.True(evalResultHandle != JavaScriptValueSafeHandle.Invalid); //Result type of a module is always undefined per spec. var evalResultType = Engine.JsGetValueType(evalResultHandle); Assert.True(evalResultType == JsValueType.Undefined); var resultHandle = Engine.GetGlobalVariable("$EXPORTS"); var handleType = Engine.JsGetValueType(resultHandle); Assert.True(handleType == JsValueType.Number); var result = Engine.JsNumberToInt(resultHandle); Assert.Equal(27, result); //Cleanup Engine.JsSetModuleHostInfo(mainModuleHandle, JavaScriptModuleHostInfoKind.FetchImportedModuleCallback, IntPtr.Zero); fetchCallbackDelegateHandle.Free(); Engine.JsSetModuleHostInfo(mainModuleHandle, JavaScriptModuleHostInfoKind.FetchImportedModuleFromScriptCallback, IntPtr.Zero); fetchFromScriptCallbackDelegateHandle.Free(); Engine.JsSetModuleHostInfo(mainModuleHandle, JavaScriptModuleHostInfoKind.NotifyModuleReadyCallback, IntPtr.Zero); notifyCallbackDelegateHandle.Free(); } } }