diff --git a/Robust.Client.WebView/Cef/WebViewManagerCef.Mime.cs b/Robust.Client.WebView/Cef/WebViewManagerCef.Mime.cs new file mode 100644 index 000000000..a95a41e1a --- /dev/null +++ b/Robust.Client.WebView/Cef/WebViewManagerCef.Mime.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Robust.Shared.Utility; + +namespace Robust.Client.WebView.Cef; + +internal sealed partial class WebViewManagerCef +{ + // Loosely based on: + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types + private readonly Dictionary _resourceMimeTypes = new() + { + { "aac", "audio/aac" }, + { "avif", "image/avif" }, + { "avi", "video/x-msvideo" }, + { "bmp", "image/bmp" }, + { "css", "text/css" }, + { "gif", "image/gif" }, + { "htm", "text/html" }, + { "html", "text/html" }, + { "ico", "image/vnd.microsoft.icon" }, + { "jpeg", "image/jpeg" }, + { "jpg", "image/jpeg" }, + { "js", "text/javascript" }, + { "json", "application/json" }, + { "jsonld", "application/ld+json" }, + { "midi", "audio/midi" }, + { "mid", "audio/midi" }, + { "mjs", "text/javascript" }, + { "mp3", "audio/mpeg" }, + { "mp4", "video/mp4" }, + { "mpeg", "video/mpeg" }, + { "oga", "audio/ogg" }, + { "ogg", "audio/ogg" }, + { "ogv", "video/ogg" }, + { "ogx", "application/ogg" }, + { "opus", "audio/opus" }, + { "otf", "font/otf" }, + { "png", "image/png" }, + { "pdf", "application/pdf" }, + { "svg", "image/svg+xml" }, + { "tiff", "image/tiff" }, + { "tif", "image/tiff" }, + { "ts", "video/mp2t" }, + { "ttf", "font/ttf" }, + { "txt", "text/plain" }, + { "wav", "audio/wav" }, + { "weba", "audio/webm" }, + { "webm", "video/webm" }, + { "webp", "image/webp" }, + { "woff", "font/woff" }, + { "woff2", "font/woff2" }, + { "xhtml", "application/xhtml+xml" }, + { "xml", "application/xml" }, + { "zip", "application/zip" }, + }; + + public void SetResourceMimeType(string extension, string mimeType) + { + DebugTools.Assert(!extension.StartsWith("."), "SetResourceMimeType extension must not include starting dot."); + + lock (_resourceMimeTypes) + { + _resourceMimeTypes[extension] = mimeType; + } + } + + public bool TryGetResourceMimeType(string extension, [NotNullWhen(true)] out string? mimeType) + { + lock (_resourceMimeTypes) + { + return _resourceMimeTypes.TryGetValue(extension, out mimeType); + } + } +} diff --git a/Robust.Client.WebView/Cef/WebViewManagerCef.cs b/Robust.Client.WebView/Cef/WebViewManagerCef.cs index 1b88b8016..83c9fec5e 100644 --- a/Robust.Client.WebView/Cef/WebViewManagerCef.cs +++ b/Robust.Client.WebView/Cef/WebViewManagerCef.cs @@ -2,7 +2,9 @@ using System; using System.IO; using System.Net; using System.Reflection; +using System.Text; using Robust.Client.Console; +using Robust.Shared.Configuration; using Robust.Shared.ContentPack; using Robust.Shared.IoC; using Robust.Shared.Localization; @@ -23,6 +25,7 @@ namespace Robust.Client.WebView.Cef [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IResourceManagerInternal _resourceManager = default!; [Dependency] private readonly IClientConsoleHost _consoleHost = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; public void Initialize() { @@ -76,6 +79,12 @@ namespace Robust.Client.WebView.Cef // TODO CEF: After this point, debugging breaks. No, literally. My client crashes but ONLY with the debugger. // I have tried using the DEBUG and RELEASE versions of libcef.so, stripped or non-stripped... // And nothing seemed to work. Odd. + + if (_cfg.GetCVar(WCVars.WebResProtocol)) + { + var handler = new ResourceSchemeFactoryHandler(this, _resourceManager, Logger.GetSawmill("web.res")); + CefRuntime.RegisterSchemeHandlerFactory("res", "", handler); + } } private static string? LocateCefResources() @@ -119,5 +128,48 @@ namespace Robust.Client.WebView.Cef { CefRuntime.Shutdown(); } + + private sealed class ResourceSchemeFactoryHandler : CefSchemeHandlerFactory + { + private readonly WebViewManagerCef _parent; + private readonly IResourceManager _resourceManager; + private readonly ISawmill _sawmill; + + public ResourceSchemeFactoryHandler( + WebViewManagerCef parent, + IResourceManager resourceManager, + ISawmill sawmill) + { + _parent = parent; + _resourceManager = resourceManager; + _sawmill = sawmill; + } + + protected override CefResourceHandler Create( + CefBrowser browser, + CefFrame frame, + string schemeName, + CefRequest request) + { + var uri = new Uri(request.Url); + + _sawmill.Debug($"HANDLING: {request.Url}"); + + var resourcePath = new ResourcePath(uri.AbsolutePath); + if (_resourceManager.TryContentFileRead(resourcePath, out var stream)) + { + if (!_parent.TryGetResourceMimeType(resourcePath.Extension, out var mime)) + mime = "application/octet-stream"; + + return new RequestResultStream(stream, mime, HttpStatusCode.OK).MakeHandler(); + } + + var notFoundStream = new MemoryStream(); + notFoundStream.Write(Encoding.UTF8.GetBytes("Not found")); + notFoundStream.Position = 0; + + return new RequestResultStream(notFoundStream, "text/plain", HttpStatusCode.NotFound).MakeHandler(); + } + } } } diff --git a/Robust.Client.WebView/Headless/WebViewManagerHeadless.cs b/Robust.Client.WebView/Headless/WebViewManagerHeadless.cs index 6500b9056..286c69416 100644 --- a/Robust.Client.WebView/Headless/WebViewManagerHeadless.cs +++ b/Robust.Client.WebView/Headless/WebViewManagerHeadless.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using Robust.Client.Graphics; using Robust.Client.UserInterface; @@ -11,6 +12,17 @@ namespace Robust.Client.WebView.Headless return new WebViewWindowDummy(); } + public void SetResourceMimeType(string extension, string mimeType) + { + // Nop + } + + public bool TryGetResourceMimeType(string extension, [NotNullWhen(true)] out string? mimeType) + { + mimeType = null; + return false; + } + public IWebViewControlImpl MakeControlImpl(WebViewControl owner) { return new WebViewControlImplDummy(); diff --git a/Robust.Client.WebView/IWebViewManager.cs b/Robust.Client.WebView/IWebViewManager.cs index d0f5fb603..97e7f407b 100644 --- a/Robust.Client.WebView/IWebViewManager.cs +++ b/Robust.Client.WebView/IWebViewManager.cs @@ -1,7 +1,40 @@ -namespace Robust.Client.WebView +using System.Diagnostics.CodeAnalysis; + +namespace Robust.Client.WebView { public interface IWebViewManager { IWebViewWindow CreateBrowserWindow(BrowserWindowCreateParameters createParams); + + /// + /// Overrides file extension -> mime type mappings for the res:// protocol. + /// + /// + /// + /// The built-in res:// protocol needs to guess MIME types to report to CEF when resolving files. + /// A limited set of extensions have pre-set MIME types in the engine. + /// This method allows you to replace or add entries if need be. + /// + /// + /// This method is thread safe. + /// + /// + /// + /// The extension to specify the MIME type for. + /// The argument must not include the starting "." of the file extension. + /// + /// The mime type for this file extension. + /// + void SetResourceMimeType(string extension, string mimeType); + + /// + /// Tries to resolve an entry from the list. + /// + /// + /// + /// This method is thread safe. + /// + /// + bool TryGetResourceMimeType(string extension, [NotNullWhen(true)] out string? mimeType); } } diff --git a/Robust.Client.WebView/WCVars.cs b/Robust.Client.WebView/WCVars.cs new file mode 100644 index 000000000..d26bb2dac --- /dev/null +++ b/Robust.Client.WebView/WCVars.cs @@ -0,0 +1,17 @@ +using Robust.Shared.Configuration; + +namespace Robust.Client.WebView; + +// ReSharper disable once InconsistentNaming +/// +/// CVars for Robust.Client.WebView +/// +[CVarDefs] +public static class WCVars +{ + /// + /// Enable the res:// protocol inside WebView browsers, allowing access to the Robust resources. + /// + public static readonly CVarDef WebResProtocol = + CVarDef.Create("web.res_protocol", true, CVar.CLIENTONLY); +} diff --git a/Robust.Client.WebView/WebViewManager.cs b/Robust.Client.WebView/WebViewManager.cs index 58480dee6..890cac17f 100644 --- a/Robust.Client.WebView/WebViewManager.cs +++ b/Robust.Client.WebView/WebViewManager.cs @@ -1,7 +1,9 @@ -using Robust.Client.WebView; +using System.Diagnostics.CodeAnalysis; +using Robust.Client.WebView; using Robust.Client.WebView.Cef; using Robust.Client.WebView.Headless; using Robust.Client.WebViewHook; +using Robust.Shared.Configuration; using Robust.Shared.IoC; using Robust.Shared.Utility; @@ -17,6 +19,9 @@ namespace Robust.Client.WebView { DebugTools.Assert(_impl == null, "WebViewManager has already been initialized!"); + var cfg = IoCManager.Resolve(); + cfg.LoadCVarsFromAssembly(typeof(WebViewManager).Assembly); + IoCManager.RegisterInstance(this); IoCManager.RegisterInstance(this); @@ -49,6 +54,20 @@ namespace Robust.Client.WebView return _impl!.CreateBrowserWindow(createParams); } + public void SetResourceMimeType(string extension, string mimeType) + { + DebugTools.Assert(_impl != null, "WebViewManager has not yet been initialized!"); + + _impl!.SetResourceMimeType(extension, mimeType); + } + + public bool TryGetResourceMimeType(string extension, [NotNullWhen(true)] out string? mimeType) + { + DebugTools.Assert(_impl != null, "WebViewManager has not yet been initialized!"); + + return _impl!.TryGetResourceMimeType(extension, out mimeType); + } + public IWebViewControlImpl MakeControlImpl(WebViewControl owner) { DebugTools.Assert(_impl != null, "WebViewManager has not yet been initialized!");