Skip to content

Instantly share code, notes, and snippets.

@TLabAltoh
Last active January 13, 2025 16:02
Show Gist options
  • Save TLabAltoh/e0512b3367c25d3e1ec28ddbe95da497 to your computer and use it in GitHub Desktop.
Save TLabAltoh/e0512b3367c25d3e1ec28ddbe95da497 to your computer and use it in GitHub Desktop.

html input element's focus in/out interaction

Case 1: Switch virtual keyborad's on/off via html input element's focus event

It is possible to switch keybord's active state via html input's focus event, but it is with some limmitation. Here is an example and this feature has implemented on this sample repositly.


C#

original is here

using UnityEngine;
using UnityEngine.EventSystems;
using TLab.VKeyborad;


namespace TLab.WebView.Sample
{
    public class FocusInOutInteractionSample : MonoBehaviour, IPointerDownHandler
    {
        [SerializeField] private SearchBar m_searchBar;
        [SerializeField] private BaseInputField m_inputField;
        [SerializeField] private BrowserContainer m_container;

        public void OnPageFinish(string url)
        {
            var js = JSUtil.ToVariable("go", gameObject.name) + JSUtil.ToVariable("method", nameof(OnMessage));
            js += Resources.Load<TextAsset>("TLab/WebView/Samples/Scripts/JS/focus-in-out-interaction")?.ToString();

            m_container.browser.EvaluateJS(js);
        }

        public void OnMessage(string message)
        {
            Debug.Log("OnMessage: " + message);

            switch (message)
            {
                case "Focusin":
                    m_inputField.OnFocus(true);
                    break;
                case "Focusout":
                    m_inputField.OnFocus(false);
                    break;
            }
        }

        public void OnPointerDown(PointerEventData eventData) => m_searchBar.OnFocus(false);
    }
}

javascript

original is here

function searchShadowRoot(node, roots) {
    if (node == null) {
        return;
    }

    if (node.shadowRoot != undefined && node.shadowRoot != null) {
        roots.push(node.shadowRoot);
        searchShadowRoot(node.shadowRoot, roots);
    }

    for (var i = 0; i < node.childNodes.length; i++) {
        searchShadowRoot(node.childNodes[i], roots);
    }
}


function getAllRoot() {
        var roots = [document];
        searchShadowRoot(document, roots);
        return roots;
}

var roots = getAllRoot();

function focusin (e) {
    const target = e.target;
    if (target.tagName == 'INPUT' || target.tagName == 'TEXTAREA') {
        window.tlab.unitySendMessage(go, method, 'Focusin');
    }
}


function focusout (e) {
    const target = e.target;
    if (target.tagName == 'INPUT' || target.tagName == 'TEXTAREA') {
        window.tlab.unitySendMessage(go, method, 'Focusout');
    }
}


for (var i = 0; i < roots.length; i++) {
    roots[i].removeEventListener('focusin', focusin);
    roots[i].removeEventListener('focusout', focusout);

    roots[i].addEventListener('focusin', focusin);
    roots[i].addEventListener('focusout', focusout);
}

This approach didn't work on some website because it's impossible to add event listener to iframe's html document if iframe content's server doesn't allow crossorigin access. So above example will work on most of website, but won't work on only some iframe content.

other solution

Oculus Quest can use the system keyboard on unity and also works on the TLabWebView's html input element (this feature is not limited even if iframe). Please see here for how to setup system keyborad on unity.


NativePlugin-LegacyCode

Load html file from local storage

@Override
public WebResourceResponse shouldInterceptRequest (WebView view,
                                                   WebResourceRequest request) {
    Uri uri = request.getUrl();

    if (Objects.equals(uri.getHost(), "assets")) {
        try {
            InputStream stream = UnityPlayer.currentActivity.getAssets().open(uri.toString().substring("https://assets/".length()));
            WebResourceResponse response = new WebResourceResponse(
                    URLConnection.guessContentTypeFromName(uri.getPath()),
                    "utf-8",
                    stream);
            Map<String, String> headers = new Hashtable<>();
            headers.put("Access-Control-Allow-Origin", "*");
            headers.put("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
            response.setResponseHeaders(headers);
            return response;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    return null;
}

XR-Composition-Layers Rendering

Overview

This is a tutorial to render WebView directly to composition layers. Not Graphics API dependent and lighter than approach that renders frame to byte buffer, hardware buffer because low overhead.

Note

There is currently no way to interact with WebView rendered in composition layers as UI. You will need to implement this yourself, but this code may be helpful.

Get started

v0.0.6+~v0.0.15

  1. Make sure you have TLabWebView v0.0.6+~v0.0.15.

  2. Install the composition-layers package and the setup project.

  3. Add this script to the Assets folder.

WebViewToCompositionLayers.cs
using System.Collections;
using System;
using UnityEngine;
using Unity.XR.CompositionLayers;
using UnityEngine.XR.OpenXR.CompositionLayers;
using TLab.Android.WebView;

[RequireComponent(typeof(CompositionLayer))]
[RequireComponent(typeof(TLabWebView))]
public class WebViewToCompositionLayers : MonoBehaviour
{
    private string THIS_NAME => "[" + this.GetType() + "] ";

    private IEnumerator Start()
    {
        yield return StartCoroutine(DisplayWebView());
    }

    private IEnumerator DisplayWebView()
    {
        var webview = gameObject.GetComponent<TLabWebView>();
        if (webview != null)
        {
            webview.Init();
            yield return new WaitUntil(() =>
            {
                return webview.IsInitialized();
            });
            Debug.Log(THIS_NAME + "webview initialized");

            var layer = gameObject.GetComponent<CompositionLayer>();
            var surface = IntPtr.Zero;
            yield return new WaitUntil(() =>
            {
                surface = OpenXRLayerUtility.GetLayerAndroidSurfaceObject(layer.GetInstanceID());
                return (surface != IntPtr.Zero);
            });
            Debug.Log(THIS_NAME + "layer found");

            webview.SetSurface(surface);
            Debug.Log(THIS_NAME + "set surface");
        }
        else
        {
            Debug.LogError(THIS_NAME + "webview is null");
        }
    }

    private IEnumerator HideWebView()
    {
        var webview = gameObject.GetComponent<TLabWebView>();
        if (webview != null)
        {
            yield return new WaitUntil(() =>
            {
                return webview.IsInitialized();
            });
            webview.RemoveSurface();
        }
        else
        {
            Debug.LogError(THIS_NAME + "webview is null");
        }
    }

    void OnApplicationFocus(bool hasFocus)
    {
        if (hasFocus)
            StartCoroutine(DisplayWebView());
        else
            StartCoroutine(HideWebView());
    }
}
  1. Create the game object as shown below

  1. Build !

v1.0.0+

  1. Make sure you have TLabWebView v1.0.0+.

  2. Install the composition-layers package and the setup project.

  3. Add this script to the Assets folder.

WebViewToCompositionLayers.cs
using System.Collections;
using System;
using Unity.XR.CompositionLayers;
using UnityEngine;
using UnityEngine.XR.OpenXR.CompositionLayers;
using TLab.WebView;

[RequireComponent(typeof(CompositionLayer))]
[RequireComponent(typeof(BrowserContainer))]
public class WebViewToCompositionLayers : MonoBehaviour
{
    private string THIS_NAME => "[" + this.GetType() + "] ";

    private IEnumerator Start()
    {
        yield return StartCoroutine(DisplayWebView());
    }

    private IEnumerator DisplayWebView()
    {
        var container = gameObject.GetComponent<BrowserContainer>();
        if (container != null)
        {
            container.browser.Init();
            yield return new WaitUntil(() =>
            {
                return container.browser.IsInitialized();
            });
            Debug.Log(THIS_NAME + "Initialized !");

            var layer = gameObject.GetComponent<CompositionLayer>();
            var surface = IntPtr.Zero;
            yield return new WaitUntil(() =>
            {
                surface = OpenXRLayerUtility.GetLayerAndroidSurfaceObject(layer.GetInstanceID());
                return (surface != IntPtr.Zero);
            });
            Debug.Log(THIS_NAME + "Layer found");

            container.browser.SetSurface(surface, container.browser.viewSize.x, container.browser.viewSize.y);
            Debug.Log(THIS_NAME + $"{nameof(container.browser.SetSurface)}");
        }
        else
        {
            Debug.LogError(THIS_NAME + "Container is null");
        }
    }

    private IEnumerator HideWebView()
    {
        var container = gameObject.GetComponent<Browser>();
        if (container != null)
        {
            yield return new WaitUntil(() =>
            {
                return container.IsInitialized();
            });
            container.RemoveSurface();
        }
        else
        {
            Debug.LogError(THIS_NAME + "Container is null");
        }
    }

    void OnApplicationFocus(bool hasFocus)
    {
        if (hasFocus)
            StartCoroutine(DisplayWebView());
        else
            StartCoroutine(HideWebView());
    }
}
  1. Create the game object as shown below

  1. Build !
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment