// Copyright (c) 2024 Vuplex Inc. All rights reserved. // // Licensed under the Vuplex Commercial Software Library License, you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // https://vuplex.com/commercial-library-license // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #if UNITY_WEBGL && !UNITY_EDITOR using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Threading.Tasks; using UnityEngine; using Vuplex.WebView.Internal; namespace Vuplex.WebView { /// /// WebGLWebView is the IWebView implementation used by 2D WebView for WebGL. /// It also includes additional APIs for WebGL-specific functionality. /// public class WebGLWebView : BaseWebView, IWebView, IWithNative2DMode { /// /// Gets the unique `id` attribute of the webview's <iframe> element. /// /// /// /// await canvasWebViewPrefab.WaitUntilInitialized(); /// #if UNITY_WEBGL && !UNITY_EDITOR /// var webGLWebView = canvasWebViewPrefab.WebView as WebGLWebView; /// Debug.Log("IFrame ID: " + webGLWebView.IFrameElementID); /// #endif /// /// public string IFrameElementID { get; private set; } /// public bool Native2DModeEnabled { get => _native2DModeEnabled; } public WebPluginType PluginType { get; } = WebPluginType.WebGL; /// public Rect Rect { get => _rect; } /// public bool Visible { get => _visible; } /// public void BringToFront() { _assertValidState(); _assertNative2DModeEnabled(); WebView_bringToFront(_nativeWebViewPtr); } /// /// Indicates whether 2D WebView can access the content in the /// webview's iframe. If the iframe's content can't be accessed, /// then most of the IWebView APIs become disabled. For more /// information, please see [this article](https://support.vuplex.com/articles/webgl-limitations#cross-origin-limitation). /// /// /// /// await canvasWebViewPrefab.WaitUntilInitialized(); /// #if UNITY_WEBGL && !UNITY_EDITOR /// var webGLWebView = canvasWebViewPrefab.WebView as WebGLWebView; /// if (webGLWebView.CanAccessIFrameContent()) { /// Debug.Log("The iframe content can be accessed 👍"); /// } /// #endif /// /// public bool CanAccessIFrameContent() { _assertValidState(); return WebView_canAccessIFrameContent(_nativeWebViewPtr); } public override Task CanGoBack() => _canGoBackOrForward("CanGoBack"); public override Task CanGoForward() => _canGoBackOrForward("CanGoForward"); public override Task CaptureScreenshot() { WebGLWarnings.LogCaptureScreenshotError(); return Task.FromResult(new byte[0]); } public override void Click(int xInPixels, int yInPixels, bool preventStealingFocus = false) { if (_verifyIFrameCanBeAccessed("Click")) { base.Click(xInPixels, yInPixels, preventStealingFocus); } } public override void Copy() => WebGLWarnings.LogCopyError(); public override void Cut() => WebGLWarnings.LogCutError(); public override void ExecuteJavaScript(string javaScript, Action callback) { _assertValidState(); if (_verifyIFrameCanBeAccessed("ExecuteJavaScript")) { var result = WebView_executeJavaScriptSync(_nativeWebViewPtr, javaScript); callback?.Invoke(result); } } /// /// Executes the given JavaScript locally in the Unity app's window and returns the result. /// /// /// /// #if UNITY_WEBGL && !UNITY_EDITOR /// // Gets the Unity app's web page title. /// var title = WebGLWebView.ExecuteJavaScriptLocally("document.title"); /// Debug.Log("Title: " + title); /// #endif /// /// public static string ExecuteJavaScriptLocally(string javaScript) => WebView_executeJavaScriptLocally(javaScript); /// /// Focuses the Unity game view, which enables Unity to detect keyboard input. When a webview /// is focused, Unity is unable to detect keyboard input (i.e. its APIs like Input.GetKey() don't work). /// To re-enable Unity's ability to detect keyboard input, the application can use this method to programmatically /// shift focus from the webview to the Unity game view. /// For more details, see this article. /// /// /// /// #if UNITY_WEBGL && !UNITY_EDITOR /// WebGLWebView.FocusUnity(); /// #endif /// /// public static void FocusUnity() => WebView_focusUnity(); public override Task GetRawTextureData() { WebGLWarnings.LogGetRawTextureDataError(); return Task.FromResult(new byte[0]); } public override void GoBack() { if (_verifyIFrameCanBeAccessed("GoBack")) { base.GoBack(); } } public override void GoForward() { if (_verifyIFrameCanBeAccessed("GoForward")) { base.GoForward(); } } public Task Init(int width, int height) { var message = "2D WebView for WebGL only supports 2D, so Native 2D Mode must be enabled." + WebGLWarnings.GetArticleLinkText(false); WebViewLogger.LogError(message); throw new NotImplementedException(message); } /// public async Task InitInNative2DMode(Rect rect) { await _initInNative2DModeBase(rect); // Set IFrameElementID *after* base.Init() because it sets gameObject.name. IFrameElementID = gameObject.name; // Prior to Unity 2019.3, Unity's UI used a pixel density of // 1 instead of using window.devicePixelRatio. #if UNITY_2019_3_OR_NEWER var ignoreDevicePixelRatio = false; #else var ignoreDevicePixelRatio = true; #endif _nativeWebViewPtr = WebView_newInNative2DMode( gameObject.name, (int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height, ignoreDevicePixelRatio ); if (_nativeWebViewPtr == IntPtr.Zero) { throw new TrialExpiredException("Your trial of 2D WebView for WebGL has expired. Please purchase a license to continue using it."); } } public static WebGLWebView Instantiate() => new GameObject().AddComponent(); public override void LoadHtml(string html) { WebGLWarnings.LogLoadHtmlWarning(); base.LoadHtml(html); } public override void LoadUrl(string url, Dictionary additionalHttpHeaders) { WebViewLogger.LogWarning("LoadUrl(url, headers) was called, but 2D WebView for WebGL is unable to send additional headers when loading a URL due to browser limitations. So, the URL will be loaded without additional headers using LoadUrl(url) instead."); LoadUrl(url); } public override void Paste() => WebGLWarnings.LogPasteError(); public override void PostMessage(string message) { _assertValidState(); WebView_postMessage(_nativeWebViewPtr, message); } public override void Scroll(int scrollDeltaXInPixels, int scrollDeltaYInPixels) { if (_verifyIFrameCanBeAccessed("Scroll")) { base.Scroll(scrollDeltaXInPixels, scrollDeltaYInPixels); } } public override void Scroll(Vector2 normalizedScrollDelta, Vector2 normalizedPoint) { if (_verifyIFrameCanBeAccessed("Scroll")) { base.Scroll(normalizedScrollDelta, normalizedPoint); } } public override void SendKey(string key) { if (_verifyIFrameCanBeAccessed("SendKey")) { base.SendKey(key); } } /// /// Overrides the value of `devicePixelRatio` that 2D WebView uses to determine the correct /// size and coordinates of webviews. /// /// /// Starting in Unity 2019.3, Unity scales the UI by default based on the browser's window.devicePixelRatio /// value. However, it's possible for an application to override the `devicePixelRatio` value /// by passing a value for `config.devicePixelRatio` to Unity's `createUnityInstance()` JavaScript function. In some /// versions of Unity, the default index.html template sets `config.devicePixelRatio = 1` on mobile. /// In order for 2D WebView to size and position webviews correctly, it must determine the value of `devicePixelRatio` /// to use. Since there's no API to get a reference to the Unity instance that the application created with `createUnityInstance()`, /// 2D WebView tries to detect if `config.devicePixelRatio` was passed to `createUnityInstance()` by checking for the presence of a /// global `config` JavaScript variable. If there is a global variable named `config` with a `devicePixelRatio` property, then 2D WebView /// uses that value, otherwise it defaults to using `window.devicePixelRatio`. This approach works for Unity's default /// index.html templates, but it may not work if the application uses a custom HTML template or changes the name of the `config` /// variable in the default template. In those cases, the application can use this method to pass the overridden value of `devicePixelRatio` to /// 2D WebView. /// /// /// /// void Awake() { /// #if UNITY_WEBGL && !UNITY_EDITOR /// WebGLWebView.SetDevicePixelRatio(1); /// #endif /// } /// /// public static void SetDevicePixelRatio(float devicePixelRatio) { #if UNITY_2019_3_OR_NEWER WebView_setDevicePixelRatio(devicePixelRatio); #else WebViewLogger.LogWarning("The call to WebGLWebView.SetDevicePixelRatio() will be ignored because a version of Unity older than 2019.3 is being used, which always uses a devicePixelRatio of 1."); #endif } /// /// Sets whether the JavaScript Fullscreen API is enabled for webviews. The default is `false`. /// /// /// /// void Awake() { /// #if UNITY_WEBGL && !UNITY_EDITOR /// WebGLWebView.SetFullscreenEnabled(true); /// #endif /// } /// /// public static void SetFullscreenEnabled(bool enabled) => WebView_setFullscreenEnabled(enabled); /// /// Sets whether the JavaScript Geolocation API is enabled for webviews. The default is `false`. /// /// /// /// void Awake() { /// #if UNITY_WEBGL && !UNITY_EDITOR /// WebGLWebView.SetGeolocationEnabled(true); /// #endif /// } /// /// public static void SetGeolocationEnabled(bool enabled) => WebView_setGeolocationEnabled(enabled); public void SetNativeZoomEnabled(bool enabled) { WebViewLogger.LogWarning("2D WebView for WebGL doesn't support native zooming, so the call to IWithNative2DMode.SetNativeZoomEnabled() will be ignored."); } /// public void SetRect(Rect rect) { _assertValidState(); _assertNative2DModeEnabled(); _rect = rect; WebView_setRect(_nativeWebViewPtr, (int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height); } /// /// Explicitly sets the HTML element that 2D WebView should use as the Unity app container. /// 2D WebView automatically detects the Unity app container element if its ID is set to one of the default values of /// "unityContainer" or "unity-container". However, if your app uses a custom WebGL template that /// uses a different ID for the container element, you must call this method to set the container element ID. /// 2D WebView for WebGL works by adding <iframe> elements to the app container, so it's unable to function correctly /// if it's unable to find the Unity app container element. /// /// /// /// void Awake() { /// #if UNITY_WEBGL && !UNITY_EDITOR /// WebGLWebView.SetUnityContainerElementId("your-custom-id"); /// #endif /// } /// /// public static void SetUnityContainerElementId(string containerId) => WebView_setUnityContainerElementId(containerId); /// /// Sets whether screen sharing is enabled for webviews. The default is `false`. /// /// /// /// void Awake() { /// #if UNITY_WEBGL && !UNITY_EDITOR /// WebGLWebView.SetScreenSharingEnabled(true); /// #endif /// } /// /// public static void SetScreenSharingEnabled(bool enabled) => WebView_setScreenSharingEnabled(enabled); /// public void SetVisible(bool visible) { _assertValidState(); _assertNative2DModeEnabled(); _visible = visible; WebView_setVisible(_nativeWebViewPtr, visible); } public override void ZoomIn() => WebGLWarnings.LogZoomInError(); public override void ZoomOut() => WebGLWarnings.LogZoomOutError(); #region Non-public members readonly WaitForEndOfFrame _waitForEndOfFrame = new WaitForEndOfFrame(); Task _canGoBackOrForward(string methodName) { _assertValidState(); if (_verifyIFrameCanBeAccessed(methodName)) { WebGLWarnings.LogCanGoBackOrForwardWarning(); return Task.FromResult(WebView_canGoBackOrForward(_nativeWebViewPtr)); } return Task.FromResult(false); } bool _verifyIFrameCanBeAccessed(string methodName) { if (CanAccessIFrameContent()) { return true; } // Log an error instead of throwing an exception because // exceptions are disabled by default for WebGL. WebGLWarnings.LogIFrameContentAccessWarning(methodName); return false; } [DllImport(_dllName)] static extern void WebView_bringToFront(IntPtr webViewPtr); [DllImport(_dllName)] static extern bool WebView_canAccessIFrameContent(IntPtr webViewPtr); [DllImport(_dllName)] static extern bool WebView_canGoBackOrForward(IntPtr webViewPtr); [DllImport(_dllName)] static extern string WebView_executeJavaScriptSync(IntPtr webViewPtr, string javaScript); [DllImport(_dllName)] static extern string WebView_executeJavaScriptLocally(string javaScript); [DllImport(_dllName)] static extern string WebView_focusUnity(); [DllImport(_dllName)] static extern IntPtr WebView_newInNative2DMode(string gameObjectName, int x, int y, int width, int height, bool ignoreDevicePixelRatio); [DllImport (_dllName)] static extern void WebView_postMessage(IntPtr webViewPtr, string message); [DllImport (_dllName)] static extern void WebView_setDevicePixelRatio(float devicePixelRatio); [DllImport (_dllName)] static extern void WebView_setFullscreenEnabled(bool enabled); [DllImport (_dllName)] static extern void WebView_setGeolocationEnabled(bool enabled); [DllImport (_dllName)] static extern void WebView_setRect(IntPtr webViewPtr, int x, int y, int width, int height); [DllImport (_dllName)] static extern void WebView_setScreenSharingEnabled(bool enabled); [DllImport (_dllName)] static extern void WebView_setUnityContainerElementId(string containerId); [DllImport (_dllName)] static extern void WebView_setVisible(IntPtr webViewPtr, bool visible); #endregion } } #else namespace Vuplex.WebView { [System.Obsolete("To use the WebGLWebView class, you must use the directive `#if UNITY_WEBGL && !UNITY_EDITOR` like described here: https://support.vuplex.com/articles/how-to-call-platform-specific-apis#webgl . Note: WebGLWebView isn't actually obsolete. This compiler error just reports it's obsolete because 3D WebView generated the error with System.ObsoleteAttribute.", true)] public class WebGLWebView {} } #endif