// %BANNER_BEGIN% // --------------------------------------------------------------------- // %COPYRIGHT_BEGIN% // Copyright (c) (2018-2022) Magic Leap, Inc. All Rights Reserved. // Use of this file is governed by the Software License Agreement, located here: https://www.magicleap.com/software-license-agreement-ml2 // Terms and conditions applicable to third-party materials accompanying this distribution may also be found in the top-level NOTICE file appearing herein. // %COPYRIGHT_END% // --------------------------------------------------------------------- // %BANNER_END% // Disabling deprecated warning for the internal project #pragma warning disable 618 namespace UnityEngine.XR.MagicLeap { using System; using System.Runtime.InteropServices; using UnityEngine.XR.MagicLeap.Native; /// /// API for MLWebView that allows an application to instantiate a hardware /// accelerated WebView and interact with it(via "mouse" and "keyboard" events). /// public partial class MLWebView { /// /// GC Handle. /// private GCHandle gcHandle; private MLWebView() { gcHandle = GCHandle.Alloc(this, GCHandleType.Weak); Handle = Native.MagicLeapNativeBindings.InvalidHandle; } ~MLWebView() { gcHandle.Free(); } /// /// Create a MLWebView. /// The MLWebView will be ready to use once this function returns with MLResult_OK. /// /// Width of the WebView in pixels. /// Height of the WebView in pixels. /// handle to the WebView tab created. /// MLResult.Code.Ok if the MLWebView is ready for use. /// MLResult.Code.UnspecifiedFailure if Unable to create the MLWebView. /// MLResult.Code.InvalidParam if the parameter was null pointer. /// MLResult.Code.PermissionDenied its missing the permission(s). private MLResult.Code CreateInternal(uint width, uint height, bool isPopup, ulong popupID) { Handle = MagicLeapNativeBindings.InvalidHandle; var resultCode = MLPermissions.CheckPermission(MLPermission.WebView).Result; if (!MLResult.DidNativeCallSucceed(resultCode, nameof(CreateInternal))) { MLPluginLog.Error($"{nameof(MLWebView)} requires missing permission {MLPermission.Internet}"); return MLResult.Code.PermissionDenied; } var settings = NativeBindings.Settings.Create(gcHandle, width, height, isPopup, popupID); MLResult.Code result = NativeBindings.MLWebViewCreate(out Handle, ref settings); MLResult.DidNativeCallSucceed(result, nameof(NativeBindings.MLWebViewCreate)); return result; } /// /// Destroy a MLWebView. The MLWebView will be terminated by this function call and the handle shall no longer be valid. /// /// handle to the WebView tab to destroy. /// MLResult.Code.Ok if was destroyed successfully. /// MLResult.Code.UnspecifiedFailure if an error occurred destroying the MLWebView. /// MLResult.Code.InvalidParam if its unable to find the specified MLWebView handle. private MLResult.Code DestroyInternal() { if (!MagicLeapNativeBindings.MLHandleIsValid(Handle)) { MLPluginLog.Error("invalid WebView handle"); return MLResult.Code.InvalidParam; } MLResult.Code result = NativeBindings.MLWebViewDestroy(Handle); MLResult.DidNativeCallSucceed(result, nameof(NativeBindings.MLWebViewDestroy)); Handle = MagicLeapNativeBindings.InvalidHandle; return result; } /// /// Go to a URL with the specified MLWebView. Note that success with this call only indicates that a load will be /// attempted. Caller can be notified about issues loading the URL via the event handler on_load_error. /// /// handle to the WebView tab to issue the URL request to. /// URL that will be loaded. /// MLResult.Code.Ok if WebView is attempting to load the specified URL. /// MLResult.Code.InvalidParam if its unable to find the specified MLWebView handle. private MLResult.Code GoToInternal(string url) { if (!MagicLeapNativeBindings.MLHandleIsValid(Handle)) { MLPluginLog.Error("invalid WebView handle"); return MLResult.Code.InvalidParam; } var result = NativeBindings.MLWebViewGoTo(Handle, url); MLResult.DidNativeCallSucceed(result, nameof(NativeBindings.MLWebViewGoTo)); return result; } /// /// Trigger a "Back" action in the MLWebView. Query #MLWebViewCanGoBack before calling this method. If there is no valid /// page to go back to, this method will be no-op. /// /// handle to the WebView tab to issue the go back request to. /// MLResult.Code.Ok if WebView Back action was initiated or cannot go back any further. /// MLResult.Code.InvalidParam if its unable to find the specified MLWebView handle. private MLResult.Code GoBackInternal() { if (!MagicLeapNativeBindings.MLHandleIsValid(Handle)) { MLPluginLog.Error("invalid WebView handle"); return MLResult.Code.InvalidParam; } var result = NativeBindings.MLWebViewGoBack(Handle); MLResult.DidNativeCallSucceed(result, nameof(NativeBindings.MLWebViewGoBack)); return result; } /// /// Trigger a "Forward" action in the MLWebView. Query MLWebViewCanGoForward before calling this method. If there is no /// valid page to go forward to, this method will be no-op. /// /// handle to the WebView tab to issue the go forward request to. /// MLResult.Code.Ok if WebView Forward action was initiated or cannot go forward any further. /// MLResult.Code.InvalidParam if its unable to find the specified MLWebView handle. private MLResult.Code GoForwardInternal() { if (!MagicLeapNativeBindings.MLHandleIsValid(Handle)) { MLPluginLog.Error("invalid WebView handle"); return MLResult.Code.InvalidParam; } var result = NativeBindings.MLWebViewGoForward(Handle); MLResult.DidNativeCallSucceed(result, nameof(NativeBindings.MLWebViewGoForward)); return result; } /// /// Trigger a "Reload" action in the MLWebView. /// /// handle to the WebView tab to reload. /// MLResult.Code.Ok if WebView Reload action was initiated. /// MLResult.Code.InvalidParam if its unable to find the specified MLWebView handle. private MLResult.Code ReloadInternal() { if (!MagicLeapNativeBindings.MLHandleIsValid(Handle)) { MLPluginLog.Error("invalid WebView handle"); return MLResult.Code.InvalidParam; } var result = NativeBindings.MLWebViewReload(Handle); MLResult.DidNativeCallSucceed(result, nameof(NativeBindings.MLWebViewReload)); return result; } /// /// Get the current URL. /// /// handle to the WebView tab to get the current URL address of. /// Current URL. private string GetURLInternal() { string url = string.Empty; if (!MagicLeapNativeBindings.MLHandleIsValid(Handle)) { MLPluginLog.Error("invalid WebView handle"); return url; } MLResult.Code result = NativeBindings.MLWebViewGetUrl(Handle, out uint length, IntPtr.Zero); if (MLResult.DidNativeCallSucceed(result, nameof(NativeBindings.MLWebViewGetUrl))) { IntPtr stringPtr = Marshal.AllocHGlobal((int)length); try { result = NativeBindings.MLWebViewGetUrl(Handle, out length, stringPtr); MLResult.DidNativeCallSucceed(result, nameof(NativeBindings.MLWebViewGetUrl)); url = Marshal.PtrToStringAnsi(stringPtr); } finally { Marshal.FreeHGlobal(stringPtr); } } return url; } /// /// Checks if the "Back" action is currently valid. /// /// handle to the WebView tab to check go back status of. /// True if can use the "Back" action. private bool CanGoBackInternal() { bool canGoBack = false; if (!MagicLeapNativeBindings.MLHandleIsValid(Handle)) { MLPluginLog.Error("invalid WebView handle"); return canGoBack; } MLResult.Code result = NativeBindings.MLWebViewCanGoBack(Handle, out canGoBack); MLResult.DidNativeCallSucceed(result, nameof(NativeBindings.MLWebViewCanGoBack), IllegalStatePredicate); return canGoBack; } /// /// Checks if the "Forward" action is currently valid. /// /// handle to the WebView tab to check go forward status of. /// True if can use the "Forward" action. private bool CanGoForwardInternal() { bool canGoForward = false; if (!MagicLeapNativeBindings.MLHandleIsValid(Handle)) { MLPluginLog.Error("invalid WebView handle"); return canGoForward; } MLResult.Code result = NativeBindings.MLWebViewCanGoForward(Handle, out canGoForward); MLResult.DidNativeCallSucceed(result, nameof(NativeBindings.MLWebViewCanGoForward), IllegalStatePredicate); return canGoForward; } /// /// Illegal state just means that webview is paused, use this to not log an error for appropriate functions. /// private bool IllegalStatePredicate(MLResult.Code code) => code == MLResult.Code.Ok || code == MLResult.Code.IllegalState; /// /// Moves the WebView mouse. /// /// handle to the WebView tab to issue mouse move to. /// Horizontal position of the cursor. /// Vertical position of the cursor. /// Should be one or combination of EventFlags. /// MLResult.Code.Ok if internal mouse was moved. /// MLResult.Code.InvalidParam if its unable to find the specified MLWebView handle. private MLResult.Code InjectMouseMoveInternal(uint xPosition, uint yPosition, EventFlags modifiers) { if (!MagicLeapNativeBindings.MLHandleIsValid(Handle)) { MLPluginLog.Error("invalid WebView handle"); return MLResult.Code.InvalidParam; } NativeBindings.CursorState cursorState = NativeBindings.CursorState.Create(xPosition, yPosition, modifiers); var result = NativeBindings.MLWebViewInjectMouseMove(Handle, ref cursorState); MLResult.DidNativeCallSucceed(result, nameof(NativeBindings.MLWebViewInjectMouseMove)); return result; } /// /// Sends a mouse button down/pressed event on a specific location on screen. /// /// handle to the WebView tab to issue mouse down to. /// Horizontal position of the cursor. /// Vertical position of the cursor. /// Should be one or combination of EventFlags. /// MLResult.Code.Ok if successful. /// MLResult.Code.InvalidParam if its unable to find the specified MLWebView handle. private MLResult.Code InjectMouseButtonDownInternal(uint xPosition, uint yPosition, EventFlags modifiers) { if (!MagicLeapNativeBindings.MLHandleIsValid(Handle)) { MLPluginLog.Error("invalid WebView handle"); return MLResult.Code.InvalidParam; } NativeBindings.CursorState cursorState = NativeBindings.CursorState.Create(xPosition, yPosition, modifiers); var result = NativeBindings.MLWebViewInjectMouseButtonDown(Handle, ref cursorState); MLResult.DidNativeCallSucceed(result, nameof(NativeBindings.MLWebViewInjectMouseButtonDown)); return result; } /// /// Sends a mouse button up/released event on a specific location on screen. /// /// handle to the WebView tab to issue mouse up to. /// Horizontal position of the cursor. /// Vertical position of the cursor. /// Should be one or combination of EventFlags. /// MLResult.Code.Ok if successful. /// MLResult.Code.InvalidParam if its unable to find the specified MLWebView handle. private MLResult.Code InjectMouseButtonUpInternal(uint xPosition, uint yPosition, EventFlags modifiers) { if (!MagicLeapNativeBindings.MLHandleIsValid(Handle)) { MLPluginLog.Error("invalid WebView handle"); return MLResult.Code.InvalidParam; } NativeBindings.CursorState cursorState = NativeBindings.CursorState.Create(xPosition, yPosition, modifiers); var result = NativeBindings.MLWebViewInjectMouseButtonUp(Handle, ref cursorState); MLResult.DidNativeCallSucceed(result, nameof(NativeBindings.MLWebViewInjectMouseButtonUp)); return result; } /// /// Sends a printable char keyboard event to MLWebView. /// /// handle to the WebView tab to issue char utf code to. /// printable char utf code /// MLResult.Code.Ok if key event was injected. /// MLResult.Code.InvalidParam if its unable to find the specified MLWebView handle. private MLResult.Code InjectCharInternal(char charUtf32) { if (!MagicLeapNativeBindings.MLHandleIsValid(Handle)) { MLPluginLog.Error("invalid WebView handle"); return MLResult.Code.InvalidParam; } var result = NativeBindings.MLWebViewInjectChar(Handle, Convert.ToUInt32(charUtf32)); MLResult.DidNativeCallSucceed(result, nameof(NativeBindings.MLWebViewInjectChar)); return result; } /// /// Sends a key down/pressed event to MLWebView. /// /// handle to the WebView tab to issue key down to. /// MLWebView.KeyCode. /// Should be one or combination of MLWebView.EventFlags. /// MLResult.Code.Ok if key event was injected. /// MLResult.Code.InvalidParam if its unable to find the specified MLWebView handle. private MLResult.Code InjectKeyDownInternal(KeyCode keyCode, uint modifierMask) { if (!MagicLeapNativeBindings.MLHandleIsValid(Handle)) { MLPluginLog.Error("invalid WebView handle"); return MLResult.Code.InvalidParam; } var result = NativeBindings.MLWebViewInjectKeyDown(Handle, keyCode, modifierMask); MLResult.DidNativeCallSucceed(result, nameof(NativeBindings.MLWebViewInjectKeyDown)); return result; } /// /// Sends a key up/release event to MLWebView. /// /// handle to the WebView tab to issue key up to. /// MLWebView.KeyCode. /// Should be one or combination of MLWebView.EventFlags. /// MLResult.Code.Ok if key event was injected. /// MLResult.Code.InvalidParam if its unable to find the specified MLWebView handle. private MLResult.Code InjectKeyUpInternal(KeyCode keyCode, uint modifierMask) { if (!MagicLeapNativeBindings.MLHandleIsValid(Handle)) { MLPluginLog.Error("invalid WebView handle"); return MLResult.Code.InvalidParam; } var result = NativeBindings.MLWebViewInjectKeyUp(Handle, keyCode, modifierMask); MLResult.DidNativeCallSucceed(result, nameof(NativeBindings.MLWebViewInjectKeyUp)); return result; } /// /// Triggers a mouse "Scroll" event. /// /// handle to the WebView tab to issue scroll to. /// The number of pixels to scroll on the x axis. /// The number of pixels to scroll on the y axis. /// MLResult.Code.Ok if MLWebView was scrolled. /// MLResult.Code.InvalidParam if its unable to find the specified MLWebView handle. private MLResult.Code ScrollByInternal(uint xPixels, uint yPixels) { if (!MagicLeapNativeBindings.MLHandleIsValid(Handle)) { MLPluginLog.Error("invalid WebView handle"); return MLResult.Code.InvalidParam; } var result = NativeBindings.MLWebViewScrollBy(Handle, xPixels, yPixels); MLResult.DidNativeCallSucceed(result, nameof(NativeBindings.MLWebViewScrollBy)); return result; } /// /// Get the entire scrollable size of the webview. /// This should be typically called afer HandleOnLoadEnd to determine the scollable size /// of the main frame of the loaded page.Some pages might dynamically resize and this should be called /// before each frame draw to correctly determine the scrollable size of the webview. /// /// handle to the WebView tab to request scroll size from. /// Vector2Int representing the entire width and height of the webview, in pixels. /// TODO: To be removed due to deprecation. private Vector2Int GetScrollSizeInternal() { int width = 0; int height = 0; if (!MagicLeapNativeBindings.MLHandleIsValid(Handle)) { MLPluginLog.Error("invalid WebView handle"); return new Vector2Int(width, height); } MLResult.Code result = NativeBindings.MLWebViewGetScrollSize(Handle, out width, out height); MLResult.DidNativeCallSucceed(result, nameof(NativeBindings.MLWebViewGetScrollSize)); return new Vector2Int(width, height); } /// /// Get the scroll offset of the webview. /// /// handle to the WebView tab request scroll offset from. /// Vector2Int representing the horizontal and vertical offset of the webview, in pixels. /// TODO: To be removed due to deprecation. private Vector2Int GetScrollOffsetInternal() { int x = 0; int y = 0; if (!MagicLeapNativeBindings.MLHandleIsValid(Handle)) { MLPluginLog.Error("invalid WebView handle"); return new Vector2Int(x, y); } MLResult.Code result = NativeBindings.MLWebViewGetScrollOffset(Handle, out x, out y); MLResult.DidNativeCallSucceed(result, nameof(NativeBindings.MLWebViewGetScrollOffset)); return new Vector2Int(x, y); } /// /// Reset zoom level to 1.0. /// /// handle to the WebView tab to reset zoom of. /// MLResult.Code.Ok if MLWebView zoom was reset. /// MLResult.Code.InvalidParam if its unable to find the specified MLWebView handle. /// MLResult.Code.UnspecifiedFailure if it failed to reset zoom due to an internal error. private MLResult.Code ResetZoomInternal() { if (!MagicLeapNativeBindings.MLHandleIsValid(Handle)) { MLPluginLog.Error("invalid WebView handle"); return MLResult.Code.InvalidParam; } var result = NativeBindings.MLWebViewResetZoom(Handle); MLResult.DidNativeCallSucceed(result, nameof(NativeBindings.MLWebViewResetZoom)); return result; } /// /// Zoom in one level. /// /// handle to the WebView tab to issue zoom in to. /// MLResult.Code.Ok if MLWebView zoomed in. /// MLResult.Code.InvalidParam if its unable to find the specified MLWebView handle. /// MLResult.Code.WebViewResultZoomLimitReached if cannot zoom in any further. /// MLResult.Code.UnspecifiedFailure if it failed to reset zoom due to an internal error. private MLResult.Code ZoomInInternal() { if (!MagicLeapNativeBindings.MLHandleIsValid(Handle)) { MLPluginLog.Error("invalid WebView handle"); return MLResult.Code.InvalidParam; } var result = NativeBindings.MLWebViewZoomIn(Handle); MLResult.DidNativeCallSucceed(result, nameof(NativeBindings.MLWebViewZoomIn)); return result; } /// /// Zoom out one level. /// /// handle to the WebView tab to issue zoom out to. /// MLResult.Code.Ok if MLWebView zoomed out. /// MLResult.Code.InvalidParam if its unable to find the specified MLWebView handle. /// MLResult.Code.WebViewResultZoomLimitReached if cannot zoom out any further. /// MLResult.Code.UnspecifiedFailure if it failed to reset zoom due to an internal error. private MLResult.Code ZoomOutInternal() { if (!MagicLeapNativeBindings.MLHandleIsValid(Handle)) { MLPluginLog.Error("invalid WebView handle"); return MLResult.Code.InvalidParam; } var result = NativeBindings.MLWebViewZoomOut(Handle); MLResult.DidNativeCallSucceed(result, nameof(NativeBindings.MLWebViewZoomOut)); return result; } /// /// Get the current zoom factor. The default zoom factor is 1.0. /// /// handle to the WebView tab to request zoom factor from. /// Current numeric value for zoom factor. private double GetZoomFactorInternal() { double zoomFactor = 1; if (!MagicLeapNativeBindings.MLHandleIsValid(Handle)) { MLPluginLog.Error("invalid WebView handle"); return zoomFactor; } MLResult.Code result = NativeBindings.MLWebViewGetZoomFactor(Handle, out zoomFactor); MLResult.DidNativeCallSucceed(result, nameof(NativeBindings.MLWebViewGetZoomFactor), IllegalStatePredicate); return zoomFactor; } /// /// Remove all webview cookies. /// /// handle to the WebView tab to clear all cookies of. /// MLResult.Code.Ok if all cookies removed successfully. /// MLResult.Code.InvalidParam if its unable to find the specified MLWebView handle. /// MLResult.Code.UnspecifiedFailure if removing all cookies failed due to an internal error. private MLResult.Code RemoveAllCookiesInternal() { if (!MagicLeapNativeBindings.MLHandleIsValid(Handle)) { MLPluginLog.Error("invalid WebView handle"); return MLResult.Code.InvalidParam; } var result = NativeBindings.MLWebViewRemoveAllCookies(Handle); MLResult.DidNativeCallSucceed(result, nameof(NativeBindings.MLWebViewRemoveAllCookies)); return result; } /// /// Clear the webview cache. /// /// handle to the WebView tab to clear cache of. /// MLResult.Code.Ok if cache cleared successfully /// MLResult.Code.InvalidParam if its unable to find the specified MLWebView handle. /// MLResult.Code.UnspecifiedFailure if clearing cache failed due to an internal error. private MLResult.Code ClearCacheInternal() { if (!MagicLeapNativeBindings.MLHandleIsValid(Handle)) { MLPluginLog.Error("invalid WebView handle"); return MLResult.Code.InvalidParam; } var result = NativeBindings.MLWebViewClearCache(Handle); MLResult.DidNativeCallSucceed(result, nameof(NativeBindings.MLWebViewClearCache)); return result; } /// /// Pause the webview. Call MLWebViewResume to resume. /// This method provides a multiple pause types to the webview. /// /// The type of pause to be used. /// MLResult.Code.Ok if paused successfully. /// MLResult.Code.InvalidParam if its unable to find the specified MLWebView handle or PauseType value. /// MLResult.Code.UnspecifiedFailure if failed due to an internal error. /// MLResult.Code.Pending if the MLWebView handle is not ready to use. See an asynchronous mode of MLWebViewCreate. private MLResult.Code PauseInternal(PauseType pauseType) { MLResult.Code result = NativeBindings.MLWebViewPause(Handle, pauseType); MLResult.DidNativeCallSucceed(result, nameof(NativeBindings.MLWebViewPause)); return result; } /// /// Resumes a webview after a previous call of the MLWebViewPause. /// Resume webview to the normal operation for all webview pause types. /// /// MLResult.Code.Ok if resumed successfully. /// MLResult.Code.IllegalState if WebView was paused. See MLWebViewPause. /// MLResult.Code.InvalidParam if its unable to find the specified MLWebView handle. /// MLResult.Code.UnspecifiedFailure if failed due to an internal error. /// MLResult.Code.Pending if the MLWebView handle is not ready to use. See an asynchronous mode of MLWebViewCreate. private MLResult.Code ResumeInternal() { MLResult.Code result = NativeBindings.MLWebViewResume(Handle); MLResult.DidNativeCallSucceed(result, nameof(NativeBindings.MLWebViewResume)); return result; } } }