// // Copyright (c) Microsoft Corporation. All rights reserved. // namespace Microsoft.MixedReality.WebView { using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Threading; using UnityEngine; public class WebViewSystem : MonoBehaviour { /// /// A delegate you can register to WebViewSystem to create custom implementations of IWebView.
/// Register your delegate with WebViewSystem.RegisterCreateWebViewDelegate
/// The delegates registered to WebViewSystem are called in order of registration.
/// If you return null, WebViewSystem will fall through to the next registered delegate.
/// If all registered delegates return null, WebViewSystem will attempt to provide a default implementation.
///
/// /// The GameObject to which system-wide singleton scripts should be attached. /// This GameObject is destroyed when all active IWebView instances are destroyed, /// and recreated when new IWebView instances are created. /// /// The GameObject to which the IWebView implementation will be bound. /// The width (in pixels) of the requested IWebView. /// The height (in pixels) of the requested IWebView. /// The custom IWebView implementation, or null as a fallthrough case. public delegate IWebView CreateWebViewDelegate(GameObject systemSingleton, GameObject webView, int width, int height); private static readonly List createWebViewDelegates = new List(); /// /// A delegate you can register to WebViewSystem to add components to any GameObject hosting an IWebView.
/// Register your delegate with WebViewSystem.RegisterComponentWebViewDelegate.
/// The delegates registered to WebViewSystem are called in order of registration, after the CreateWebViewDelegates run.
///
/// /// The GameObject to which system-wide singleton scripts should be attached. /// This GameObject is destroyed when all active IWebView instances are destroyed, /// and recreated when new IWebView instances are created. /// /// The GameObject that holds the WebView component. public delegate void ComponentWebViewDelegate(GameObject systemSingleton, GameObject webView); private static readonly List componentWebViewDelegates = new List(); internal static Thread AppThread { get; private set; } private static readonly Dictionary viewMap = new Dictionary(); private static GameObject singleton; private static void RegisterUniqueDelegate(List list, T del) { if (del is null) { throw new ArgumentNullException(); } if (!list.Contains(del)) { list.Add(del); } else { throw new ArgumentException("Delegate is already registered to WebViewSystem!"); } } public static void UnregisterUniqueDelegate(List list, T del) { if (del is null) { throw new ArgumentNullException(); } if (list.Contains(del)) { list.Remove(del); } else { throw new ArgumentException("Delegate is not registered to WebViewSystem!"); } } /// /// Registers a ComponentWebViewDelegate for adding plugin components to WebViews, /// so that it will be called by WebViewSystem.CreateWebView. /// /// The delegate that will be called in the order of its registration (FIFO). /// If the delegate is already registered. /// If the delegate is null. public static void RegisterCreateWebViewDelegate(CreateWebViewDelegate del) { RegisterUniqueDelegate(createWebViewDelegates, del); } /// /// Unregisters a CreateWebViewDelegate for creating custom WebViews, /// so that it will no longer get called by WebViewSystem.CreateWebView. /// /// The delegate to unregister from the list. /// If the delegate is already registered. /// If the delegate is null. public static void UnregisterCreateWebViewDelegate(CreateWebViewDelegate del) { UnregisterUniqueDelegate(createWebViewDelegates, del); } /// /// Registers a CreateWebViewDelegate for creating custom WebViews, /// so that it will be called by WebViewSystem.CreateWebView. /// /// The delegate to unregister from the list. /// If the delegate is already registered. /// If the delegate is null. public static void RegisterComponentWebViewDelegate(ComponentWebViewDelegate del) { RegisterUniqueDelegate(componentWebViewDelegates, del); } /// /// Unregisters a ComponentWebViewDelegate for adding plugin components to WebViews, /// so that it will no longer get called by WebViewSystem.CreateWebView. /// /// The delegate to unregister from the list. /// If the delegate is already registered. /// If the delegate is null. public static void UnregisterComponentWebViewDelegate(ComponentWebViewDelegate del) { UnregisterUniqueDelegate(componentWebViewDelegates, del); } internal static IWebView CreateWebView(GameObject gameObject, int width, int height, string parentHWNDHint) { if (singleton == null && gameObject != null) { singleton = new GameObject("WebViewSystem"); singleton.AddComponent(); } IWebView webView = null; // Go through all the delegates in order of registration (FIFO). foreach (var del in createWebViewDelegates) { webView = del(singleton, gameObject, width, height); if (webView != null) { break; } } // If none of the delegates were capable of producing an IWebView, fallback to default. webView ??= CreateWebViewDefault(gameObject, width, height, parentHWNDHint); if (webView == null) { throw new Exception("Could not create an instance of IWebView! Perhaps an implementation is missing?"); } // Run the component plugin delegates. foreach (var del in componentWebViewDelegates) { del(singleton, gameObject); } return webView; } private static IWebView CreateWebViewDefault(GameObject gameObject, int width, int height, string parentHWNDHint) { #if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN || UNITY_WSA WindowsWebView windowsWebView = new WindowsWebView(gameObject, width, height, parentHWNDHint); if (windowsWebView.InstanceId != IntPtr.Zero) { viewMap.Add(windowsWebView.InstanceId, windowsWebView); } else { Debug.LogError("InstanceId is null, indicating failure to create the native WebView."); } return windowsWebView; #elif UNITY_ANDROID AndroidWebView androidWebView = new AndroidWebView(gameObject, width, height); viewMap.Add(androidWebView.InstanceId, androidWebView); return androidWebView; #else return null; #endif } internal static void ViewDestroyed(IntPtr instanceId) { viewMap.Remove(instanceId); if (viewMap.Count() == 0) { DestroySingleton(); } } internal static IWebView FindWebView(IntPtr instanceId) { return viewMap.TryGetValue(instanceId, out var view) ? view : null; } private void Awake() { AppThread = Thread.CurrentThread; } private IEnumerator Start() { yield return StartCoroutine("CallPluginAtEndOfFrames"); } private void OnDestroy() { viewMap.Clear(); DestroySingleton(); } private static void DestroySingleton() { if (singleton != null) { Destroy(singleton); singleton = null; } } private IEnumerator CallPluginAtEndOfFrames() { while (true) { // Wait until all frame rendering is done yield return new WaitForEndOfFrame(); WebViewSystem.RenderFrame(); } } public static void RenderFrame() { // Issue a plugin event with arbitrary integer identifier. // The plugin can distinguish between different // things it needs to do based on this ID. // For our plugin, it does not matter which ID we pass here. GL.IssuePluginEvent(WebViewNative.GetRenderEventFunc(), 1); } } }