Esempio n. 1
0
        public void Run(string source)
        {
            JavaScriptModuleRecord root = JavaScriptModuleRecord.Initialize(
                null,
                rootSpecifier
                );

            root.HostUrl = "<root>";

            root.NotifyModuleReadyCallback   = NotifyModuleReadyCallback;
            root.FetchImportedModuleCallBack = FetchImportedModuleCallback;

            try {
                root.ParseSource(source);
            } catch {
                // Exception gets handled by NotifyModuleReadyCallback
            }

            while (moduleParseQueue.Count > 0)
            {
                Loader.Debug("Parsing module from queue...");
                moduleParseQueue.Dequeue() ();
            }

            if (executeRoot != null)
            {
                executeRoot();
                executeRoot = null;
            }
        }
Esempio n. 2
0
        JavaScriptErrorCode FetchImportedModuleCallback(
            JavaScriptModuleRecord referencingModule,
            JavaScriptValue specifierAsValue,
            out JavaScriptModuleRecord outDependentModuleRecord
            )
        {
            string specifier = specifierAsValue.ToString();

            Loader.Debug($"Javascript.Module.RecordDelegate FetchImportedModuleCallback for specifier: '{specifier}'");

            string normalizedSpecifier = resolver.Normalize(referencingModule.HostUrl, specifier);

            JavaScriptModuleRecord dependentModuleRecord;

            if (normalizedSpecifier == null)
            {
                dependentModuleRecord           = JavaScriptModuleRecord.Initialize(null, "<invalid record>");
                dependentModuleRecord.Exception = JavaScriptValue.CreateError($"Could not find module '{specifier}' from '{referencingModule.HostUrl}'");
            }
            else if (cache.Has(normalizedSpecifier))
            {
                dependentModuleRecord = cache.GetOrInvalid(normalizedSpecifier);
            }
            else
            {
                dependentModuleRecord         = JavaScriptModuleRecord.Initialize(referencingModule, normalizedSpecifier);
                dependentModuleRecord.HostUrl = normalizedSpecifier;
                cache.Set(normalizedSpecifier, dependentModuleRecord);

                moduleParseQueue.Enqueue(() => {
                    Loader.Debug($"Javascript.Module.RecordDelegate parsing source for '{specifier}'");
                    string source = "";
                    try {
                        source = resolver.Read(normalizedSpecifier);
                    } catch (Exception loadException) {
                        dependentModuleRecord.Exception = JavaScriptValue.CreateError($"Couldn't read file '{normalizedSpecifier}': " + loadException);
                    }

                    try {
                        // if we couldn't read the file for whatever reason, we must still call ParseSource
                        // so that the internals will move on to the next step and show us the Exception set above
                        dependentModuleRecord.ParseSource(source); // can throw syntax errors
                    } catch {
                        // will set Exception for us!
                    }
                });
            }

            dependencies.Add(dependentModuleRecord);

            outDependentModuleRecord = dependentModuleRecord;
            return(JavaScriptErrorCode.NoError); // Error case gets handled by Exception being set on the module record
        }
        public string[] ValidateTemplate(string code)
        {
            var context = JavaScriptContext.CreateContext(_runtime);

            context.AddRef();

            try
            {
                return(_dispatcher.Invoke(() =>
                {
                    using (context.GetScope())
                    {
                        Native.ThrowIfError(Native.JsSetPromiseContinuationCallback(_promiseContinuationCallback, IntPtr.Zero));

                        // Load polyfill's
                        JavaScriptContext.RunScript("const globalThis = this;");
                        JavaScriptContext.RunScript(PolyfillScripts.Get("ImageData"));

                        try
                        {
                            var module = JavaScriptModuleRecord.Initialize();
                            module.FetchImportedModuleCallBack = (JavaScriptModuleRecord referencingModule, JavaScriptValue specifier, out JavaScriptModuleRecord dependentModuleRecord) =>
                            {
                                dependentModuleRecord = JavaScriptModuleRecord.Invalid;
                                return JavaScriptErrorCode.NoError;
                            };
                            module.NotifyModuleReadyCallback = (JavaScriptModuleRecord referencingModule, JavaScriptValue exceptionVar) => JavaScriptErrorCode.NoError;
                            module.ParseSource(code);

                            return Array.Empty <string>();
                        }
                        catch (JavaScriptException e) when(e.ErrorCode == JavaScriptErrorCode.ScriptCompile)
                        {
                            return new[] { e.Message };
                        }
                    }
                }));
            }
            finally
            {
                context.Release();
            }
        }
Esempio n. 4
0
        public void AddPreload(string specifier, string code)
        {
            if (!RootModule.IsValid)
            {
                throw new InvalidOperationException("No root module set");
            }

            var module = JavaScriptModuleRecord.Initialize(RootModule, specifier);

            module.HostUrl = specifier; // Only for debugging
            _moduleLeases.Add(specifier, new ModuleLease(module));

            _dispatcher.Invoke(() =>
            {
                using (_context.GetScope())
                {
                    module.ParseSource(code);
                }
            });
        }
Esempio n. 5
0
        /// <summary>
        ///     User implemented callback to fetch additional imported modules in ES modules.
        /// </summary>
        /// <remarks>
        ///     The callback is invoked on the current runtime execution thread, therefore execution is blocked until
        ///     the callback completes. Notify the host to fetch the dependent module. This is the "import" part
        ///     before HostResolveImportedModule in ES6 spec. This notifies the host that the referencing module has
        ///     the specified module dependency, and the host needs to retrieve the module back.
        ///
        ///     Callback should:
        ///     1. Check if the requested module has been requested before - if yes return the existing
        ///         module record
        ///     2. If no create and initialize a new module record with JsInitializeModuleRecord to
        ///         return and schedule a call to JsParseModuleSource for the new record.
        /// </remarks>
        /// <param name="referencingModule">The referencing module that is requesting the dependent module.</param>
        /// <param name="specifier">The specifier coming from the module source code.</param>
        /// <param name="dependentModuleRecord">The ModuleRecord of the dependent module. If the module was requested
        ///                                     before from other source, return the existing ModuleRecord, otherwise
        ///                                     return a newly created ModuleRecord.</param>
        /// <returns>
        ///     Returns a <c>JsNoError</c> if the operation succeeded an error code otherwise.
        /// </returns>
        /// <see cref="JavaScriptFetchImportedModuleCallBack"/>
        private JavaScriptErrorCode LoadModuleImpl(JavaScriptModuleRecord referencingModule, JavaScriptValue specifier, out JavaScriptModuleRecord dependentModuleRecord)
        {
            var specifierString = specifier.ToString();

            if (_moduleLeases.TryGetValue(specifierString, out var lease))
            {
                dependentModuleRecord = lease.Module;
                return(JavaScriptErrorCode.NoError);
            }

            var alias = ResourceModuleUtils.GetResourceAlias(specifierString);

            if (alias is null)
            {
                dependentModuleRecord           = JavaScriptModuleRecord.Initialize(referencingModule, specifier);
                dependentModuleRecord.HostUrl   = specifierString; // Only for debugging
                dependentModuleRecord.Exception = JavaScriptValue.CreateTypeError($"Failed to resolve module for specifier '{specifierString}'");
                _moduleLeases.Add(specifierString, new ModuleLease(dependentModuleRecord));
                dependentModuleRecord = _moduleLeases[specifierString].Module;
                return(JavaScriptErrorCode.NoError);
            }

            dependentModuleRecord         = JavaScriptModuleRecord.Initialize(referencingModule, specifier);
            dependentModuleRecord.HostUrl = specifierString; // Only for debugging
            _moduleLeases.Add(specifierString, new ModuleLease(dependentModuleRecord));

            // Fire off a task in the threadpool
            Task.Run(async() =>
            {
                var module = _moduleLeases[specifierString].Module;
                try
                {
                    var resource = await _resourceManager.GetResourceAsync(alias);

                    if (resource is object)
                    {
                        var script = _resourceScriptFactory.CreateFromExtension(resource, Path.GetExtension(specifierString));

                        _dispatcher.Invoke(() =>
                        {
                            using (_context.GetScope())
                            {
                                module.ParseSource(script);
                            }
                        });
                    }
                    else
                    {
                        _dispatcher.Invoke(() =>
                        {
                            using (_context.GetScope())
                            {
                                ThrowModuleException(module, JavaScriptValue.CreateError($"Could not find the resource '{specifierString}'"));
                            }
                        });
                    }
                }
                catch (Exception e)
                {
                    _dispatcher.Invoke(() =>
                    {
                        using (_context.GetScope())
                        {
                            ThrowModuleException(module, JavaScriptValue.CreateError(e.Message));
                        }
                    });
                }

                void ThrowModuleException(JavaScriptModuleRecord module, JavaScriptValue error)
                {
                    error.AddRef();
                    module.Exception = error;

                    if (!JavaScriptContext.HasException)
                    {
                        JavaScriptContext.SetException(error);
                    }

                    try
                    {
                        Native.ThrowIfError(JavaScriptErrorCode.ScriptException);
                    }
                    catch (Exception e)
                    {
                        OnResourceLoadError?.Invoke(e);
                    }
                    finally
                    {
                        error.Release();
                    }
                }
            });

            return(JavaScriptErrorCode.NoError);
        }
        public async Task <GenerateResult> GenerateDocumentAsync(string code, object model, IResourceManager resourceManager = null, CancellationToken cancellationToken = default)
        {
            // Multiple contexts can share a runtime, but only one thread can access the runtime at a time
            var context = JavaScriptContext.CreateContext(_runtime);

            context.AddRef();
            ModuleLease rootModuleLease = null;

            try
            {
                using var loader = new CustomLoader(context, _dispatcher, _resourceScriptFactory, resourceManager);
                var tcs = new TaskCompletionSource <JavaScriptValue>(TaskCreationOptions.RunContinuationsAsynchronously);

                _dispatcher.Invoke(() =>
                {
                    using (context.GetScope())
                    {
                        Native.ThrowIfError(Native.JsSetPromiseContinuationCallback(_promiseContinuationCallback, IntPtr.Zero));

                        // Load polyfill's
                        JavaScriptContext.RunScript("const globalThis = this;");
                        JavaScriptContext.RunScript(PolyfillScripts.Get("ImageData"));

                        var rootModule = JavaScriptModuleRecord.Initialize();
                        rootModule.AddRef();
                        rootModule.HostUrl = "<host>"; // Only for debugging

                        loader.RootModule = rootModule;
                        rootModule.FetchImportedModuleCallBack = loader.LoadModule;
                        rootModule.NotifyModuleReadyCallback   = loader.ModuleLoaded;
                        rootModuleLease = new ModuleLease(rootModule);

                        loader.AddPreload("main", code);
                    }
                });

                // Add callback that will be run when the root module has been evaluated,
                // at which time its rootModule.Namespace becomes valid.
                loader.OnRootModuleEvaluated = () =>
                {
                    using (context.GetScope())
                    {
                        var build = rootModuleLease.Module.Namespace.GetProperty("build");
                        build.AddRef();

                        var modelJson = JavaScriptValue.FromString(JsonSerializer.Serialize(model));
                        modelJson.AddRef();

                        var resultPromise = build.CallFunction(JavaScriptValue.GlobalObject, modelJson);
                        resultPromise.AddRef();

                        FunctionLease resolve = default;
                        FunctionLease reject  = default;
                        FunctionLease always  = default;

                        resolve = new FunctionLease((callee, isConstructCall, arguments, argumentCount, callbackState) =>
                        {
                            var result = arguments[1];

                            result.AddRef();
                            tcs.SetResult(result);

                            return(callee);
                        });

                        reject = new FunctionLease((callee, isConstructCall, arguments, argumentCount, callbackState) =>
                        {
                            JavaScriptException exception;

                            if (arguments.Length >= 2)
                            {
                                var reason = arguments[1];
                                if (reason.ValueType == JavaScriptValueType.Error)
                                {
                                    exception = new JavaScriptException(JavaScriptErrorCode.ScriptException, reason.GetProperty("message").ToString());
                                }
                                else
                                {
                                    exception = new JavaScriptException(JavaScriptErrorCode.ScriptException, reason.ConvertToString().ToString());
                                }
                            }
                            else
                            {
                                exception = new JavaScriptException(JavaScriptErrorCode.ScriptException);
                            }

                            tcs.SetException(exception);

                            return(callee);
                        });

                        always = new FunctionLease((callee, isConstructCall, arguments, argumentCount, callbackState) =>
                        {
                            build.Release();
                            modelJson.Release();
                            resultPromise.Release();

                            resolve.Dispose();
                            reject.Dispose();
                            always.Dispose();

                            return(callee);
                        });

                        resultPromise
                        .GetProperty("then").CallFunction(resultPromise, resolve.Function)
                        .GetProperty("catch").CallFunction(resultPromise, reject.Function)
                        .GetProperty("finally").CallFunction(resultPromise, always.Function);
                    }
                };
                loader.OnResourceLoadError = e => tcs.SetException(e);

                _dispatcher.Invoke(() =>
                {
                    using (context.GetScope())
                    {
                        rootModuleLease.Module.ParseSource(@"
import Builder from 'main';

const builder = new Builder();
const build = async (modelJson) => {
const model = JSON.parse(modelJson);
const content = await Promise.resolve(builder.build(model));
try {
const { contentType } = await import('main');
return { content, contentType };
} catch (error) {
return { content };
}
};

export { build };
");
                    }
                });

                var result = await tcs.Task;

                return(_dispatcher.Invoke(() =>
                {
                    using (context.GetScope())
                    {
                        _runtime.CollectGarbage();

                        var content = result.GetProperty("content");
                        var contentTypeValue = result.GetProperty("contentType");
                        var contentType = contentTypeValue.ValueType == JavaScriptValueType.String ? contentTypeValue.ToString() : null;

                        switch (content.ValueType)
                        {
                        case JavaScriptValueType.String: return new GenerateResult()
                            {
                                Content = Encoding.UTF8.GetBytes(content.ToString()), ContentType = contentType ?? "text/plain"
                            };

                        case JavaScriptValueType.TypedArray:
                            {
                                content.GetTypedArrayInfo(out var arrayType, out var buffer, out var byteOffset, out var byteLength);

                                if (arrayType != JavaScriptTypedArrayType.Uint8)
                                {
                                    throw new NotSupportedException("Typed array must be Uint8Array");
                                }

                                var bufferPointer = buffer.GetArrayBufferStorage(out var bufferLength);
                                var array = new byte[byteLength];
                                Marshal.Copy(IntPtr.Add(bufferPointer, (int)byteOffset), array, 0, (int)byteLength);

                                return new GenerateResult()
                                {
                                    Content = array, ContentType = contentType ?? "application/octet-stream"
                                };
                            }

                        case JavaScriptValueType.Array:
                            {
                                var list = content.ToList();
                                var array = new byte[list.Count];
                                for (var i = 0; i < list.Count; i++)
                                {
                                    array[i] = (byte)list[i].ToInt32();
                                }

                                return new GenerateResult()
                                {
                                    Content = array, ContentType = contentType ?? "application/octet-stream"
                                };
                            }

                        default: throw new NotSupportedException("Build did not produce a supported result");
                        }
                    }
                }));
            }
            finally
            {
                _dispatcher.Invoke(() =>
                {
                    using (context.GetScope())
                    {
                        rootModuleLease?.Dispose();
                    }
                });
                context.Release();
            }
        }