251 lines
11 KiB
C#
251 lines
11 KiB
C#
// <copyright file="WebViewSystem.cs" company="Microsoft Corporation">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
|
|
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
|
|
{
|
|
/// <summary>
|
|
/// A delegate you can register to WebViewSystem to create custom implementations of IWebView.<br />
|
|
/// Register your delegate with <see cref="RegisterCreateWebViewDelegate">WebViewSystem.RegisterCreateWebViewDelegate</see><br />
|
|
/// The delegates registered to WebViewSystem are called in order of registration.<br />
|
|
/// If you return null, WebViewSystem will fall through to the next registered delegate.<br />
|
|
/// If all registered delegates return null, WebViewSystem will attempt to provide a default implementation.<br />
|
|
/// </summary>
|
|
/// <param name="systemSingleton">
|
|
/// 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.
|
|
/// </param>
|
|
/// <param name="webview">The GameObject to which the IWebView implementation will be bound.</param>
|
|
/// <param name="width">The width (in pixels) of the requested IWebView.</param>
|
|
/// <param name="height">The height (in pixels) of the requested IWebView.</param>
|
|
/// <returns>The custom IWebView implementation, or null as a fallthrough case.</returns>
|
|
public delegate IWebView CreateWebViewDelegate(GameObject systemSingleton, GameObject webView, int width, int height);
|
|
private static readonly List<CreateWebViewDelegate> createWebViewDelegates = new List<CreateWebViewDelegate>();
|
|
|
|
/// <summary>
|
|
/// A delegate you can register to WebViewSystem to add components to any GameObject hosting an IWebView.<br />
|
|
/// Register your delegate with <see cref="RegisterComponentWebViewDelegate">WebViewSystem.RegisterComponentWebViewDelegate</see>.<br />
|
|
/// The delegates registered to WebViewSystem are called in order of registration, after the <see cref="CreateWebViewDelegate">CreateWebViewDelegates</see> run.<br />
|
|
/// </summary>
|
|
/// <param name="systemSingleton">
|
|
/// 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.
|
|
/// </param>
|
|
/// <param name="webview">The GameObject that holds the WebView component.</param>
|
|
public delegate void ComponentWebViewDelegate(GameObject systemSingleton, GameObject webView);
|
|
private static readonly List<ComponentWebViewDelegate> componentWebViewDelegates = new List<ComponentWebViewDelegate>();
|
|
|
|
internal static Thread AppThread { get; private set; }
|
|
private static readonly Dictionary<IntPtr, IWebView> viewMap = new Dictionary<IntPtr, IWebView>();
|
|
|
|
private static GameObject singleton;
|
|
|
|
private static void RegisterUniqueDelegate<T>(List<T> 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<T>(List<T> 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!");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers a <see cref="ComponentWebViewDelegate">ComponentWebViewDelegate</see> for adding plugin components to WebViews,
|
|
/// so that it will be called by <see cref="CreateWebView">WebViewSystem.CreateWebView</see>.
|
|
/// </summary>
|
|
/// <param name="del">The delegate that will be called in the order of its registration (FIFO).</param>
|
|
/// <exception cref="ArgumentException">If the delegate is already registered.</exception>
|
|
/// <exception cref="ArgumentNullException">If the delegate is null.</exception>
|
|
public static void RegisterCreateWebViewDelegate(CreateWebViewDelegate del)
|
|
{
|
|
RegisterUniqueDelegate(createWebViewDelegates, del);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unregisters a <see cref="CreateWebViewDelegate">CreateWebViewDelegate</see> for creating custom WebViews,
|
|
/// so that it will no longer get called by <see cref="CreateWebView">WebViewSystem.CreateWebView</see>.
|
|
/// </summary>
|
|
/// <param name="del">The delegate to unregister from the list.</param>
|
|
/// <exception cref="ArgumentException">If the delegate is already registered.</exception>
|
|
/// <exception cref="ArgumentNullException">If the delegate is null.</exception>
|
|
public static void UnregisterCreateWebViewDelegate(CreateWebViewDelegate del)
|
|
{
|
|
UnregisterUniqueDelegate(createWebViewDelegates, del);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers a <see cref="CreateWebViewDelegate">CreateWebViewDelegate</see> for creating custom WebViews,
|
|
/// so that it will be called by <see cref="CreateWebView">WebViewSystem.CreateWebView</see>.
|
|
/// </summary>
|
|
/// <param name="del">The delegate to unregister from the list.</param>
|
|
/// <exception cref="ArgumentException">If the delegate is already registered.</exception>
|
|
/// <exception cref="ArgumentNullException">If the delegate is null.</exception>
|
|
public static void RegisterComponentWebViewDelegate(ComponentWebViewDelegate del)
|
|
{
|
|
RegisterUniqueDelegate(componentWebViewDelegates, del);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unregisters a <see cref="ComponentWebViewDelegate">ComponentWebViewDelegate</see> for adding plugin components to WebViews,
|
|
/// so that it will no longer get called by <see cref="CreateWebView">WebViewSystem.CreateWebView</see>.
|
|
/// </summary>
|
|
/// <param name="del">The delegate to unregister from the list.</param>
|
|
/// <exception cref="ArgumentException">If the delegate is already registered.</exception>
|
|
/// <exception cref="ArgumentNullException">If the delegate is null.</exception>
|
|
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<WebViewSystem>();
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
} |