// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; using System.Collections.Concurrent; using System.IO; using System.Threading.Tasks; using Microsoft.ClearScript.JavaScript; using Microsoft.ClearScript.Util; namespace Microsoft.ClearScript { ///

/// Represents a document access configuration. /// /// /// This class can be extended to accommodate custom document loaders. /// public class DocumentSettings { private DocumentLoader loader; private readonly ConcurrentDictionary, Document> systemDocumentMap = new ConcurrentDictionary, Document>(); // ReSharper disable EmptyConstructor /// /// Initializes a new instance. /// public DocumentSettings() { // the help file builder (SHFB) insists on an empty constructor here } // ReSharper restore EmptyConstructor /// /// Gets or sets a document loader. /// public DocumentLoader Loader { get => loader ?? DocumentLoader.Default; set => loader = value; } /// /// Gets or sets document access options. /// public DocumentAccessFlags AccessFlags { get; set; } /// /// Gets or sets a semicolon-delimited list of directory URLs or paths to search for documents. /// public string SearchPath { get; set; } /// /// Gets or sets a semicolon-delimited list of supported file name extensions. /// public string FileNameExtensions { get; set; } /// /// Gets or set an optional method to be called when a document is loaded. /// public DocumentLoadCallback LoadCallback { get; set; } /// /// Gets or sets an optional document context callback. /// /// /// /// This property is used as an alternative to . /// If specified, the callback is invoked the first time a module attempts to retrieve its /// context information. The properties it returns are made available to the module /// implementation. This mechanism can be used to expose host resources selectively, /// securely, and without polluting the script engine's global namespace. /// /// /// Use /// import.meta /// to access the context information of a JavaScript /// module. In a module, use module.meta. /// /// public DocumentContextCallback ContextCallback { get; set; } /// /// Adds a system document to the configuration. /// /// An identifier for the document. /// A string containing the document's contents. /// /// System documents take precedence over loaded documents. Once this method is invoked, /// document access using this configuration will always map the combination of /// and to the /// specified document, bypassing the configuration's document loader. /// public void AddSystemDocument(string identifier, string contents) { AddSystemDocument(identifier, null, contents); } /// /// Adds a system document with the specified category to the configuration. /// /// An identifier for the document. /// An optional category for the document. /// A string containing the document's contents. /// /// System documents take precedence over loaded documents. Once this method is invoked, /// document access using this configuration will always map the combination of /// and to the specified /// document, bypassing the configuration's document loader. /// public void AddSystemDocument(string identifier, DocumentCategory category, string contents) { AddSystemDocument(identifier, category, contents, null); } /// /// Adds a system document with the specified category and context callback to the configuration. /// /// An identifier for the document. /// An optional category for the document. /// A string containing the document's contents. /// An optional context callback for the document. /// /// System documents take precedence over loaded documents. Once this method is invoked, /// document access using this configuration will always map the combination of /// and to the specified /// document, bypassing the configuration's document loader. /// public void AddSystemDocument(string identifier, DocumentCategory category, string contents, DocumentContextCallback contextCallback) { MiscHelpers.VerifyNonBlankArgument(identifier, nameof(identifier), "Invalid document specifier"); var info = new DocumentInfo(Path.GetFileName(identifier)) { Category = category, ContextCallback = contextCallback }; AddSystemDocument(identifier, new StringDocument(info, contents)); } /// /// Adds the specified document as a system document to the configuration. /// /// An identifier for the document. /// The document to be added as a system document. /// /// System documents take precedence over loaded documents. Once this method is invoked, /// document access using this configuration will always map the combination of /// and the specified document's category to the specified /// document, bypassing the configuration's document loader. /// public void AddSystemDocument(string identifier, Document document) { MiscHelpers.VerifyNonNullArgument(document, nameof(document)); systemDocumentMap[Tuple.Create(identifier, document.Info.Category)] = document; } internal Document LoadDocument(DocumentInfo? sourceInfo, string specifier, DocumentCategory category, DocumentContextCallback contextCallback) { var document = FindSystemDocument(specifier, category); if (document != null) { return document; } document = Loader.LoadDocument(this, sourceInfo, specifier, category, contextCallback); if (document.Info.Category != (category ?? DocumentCategory.Script)) { throw new FileLoadException("Loaded document category mismatch", "specifier"); } return document; } internal async Task LoadDocumentAsync(DocumentInfo? sourceInfo, string specifier, DocumentCategory category, DocumentContextCallback contextCallback) { var document = FindSystemDocument(specifier, category); if (document != null) { return document; } document = await Loader.LoadDocumentAsync(this, sourceInfo, specifier, category, contextCallback).ConfigureAwait(false); if (document.Info.Category != (category ?? DocumentCategory.Script)) { throw new FileLoadException("Loaded document category mismatch", "specifier"); } return document; } private Document FindSystemDocument(string identifier, DocumentCategory category) { if (systemDocumentMap.TryGetValue(Tuple.Create(identifier, category ?? DocumentCategory.Script), out var document)) { return document; } return null; } } }