// 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;
}
}
}