// %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;
}
}
}