// Copyright (c) 2023 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. using System; using System.Threading.Tasks; using UnityEngine; using Vuplex.WebView; namespace Vuplex.Demos { /// <summary> /// Sets up the AdvancedWebViewDemo scene, which displays web content in a main /// world-space WebViewPrefab and renders a UI in a second webview to display the current URL /// and provide back / forward navigation controls.<br/><br/> /// /// <b>Note:</b> The address bar currently only displays the current URL and is not an input. /// I plan to add a dedicated browser prefab in the future that will include /// a functional address bar. In the meantime, you can edit the CONTROLS_HTML field /// below to implement a URL input. /// </summary> /// <remarks> /// This scene demonstrates the following: <br/> /// - Programmatically instantiating WebViewPrefabs at runtime <br/> /// - Programmatically instantiating an on-screen Keyboard prefab <br/> /// - Using IWebView methods like LoadUrl(), LoadHtml(), GoBack(), and GoForward() <br/> /// - Attaching handlers to the IWebView.UrlChanged and MessageEmitted events <br/> /// - Sending messages from JavaScript to C# and vice versa <br/> /// - Creating a transparent webview using the transparent meta tag <br/><br/> /// /// Links: <br/> /// - WebViewPrefab docs: https://developer.vuplex.com/webview/WebViewPrefab <br/> /// - Sending messages from JavaScript to C# and vice versa: https://support.vuplex.com/articles/how-to-send-messages-from-javascript-to-c-sharp <br/> /// - How to make a transparent webview: https://support.vuplex.com/articles/how-to-make-a-webview-transparent <br/> /// - How clicking works: https://support.vuplex.com/articles/clicking <br/> /// - Other examples: https://developer.vuplex.com/webview/overview#examples <br/> /// </remarks> class AdvancedWebViewDemo : MonoBehaviour { WebViewPrefab controlsWebViewPrefab; WebViewPrefab mainWebViewPrefab; async void Start() { Debug.Log("[AdvancedWebViewDemo] Just a heads-up: this scene's address bar currently only displays the current URL and is not an input. For more info, please see the comments in AdvancedWebViewDemo.cs."); // Use a desktop User-Agent to request the desktop versions of websites. // https://developer.vuplex.com/webview/Web#SetUserAgent Web.SetUserAgent(false); // Instantiate a 0.6 x 0.3 webview for the main web content. // https://developer.vuplex.com/webview/WebViewPrefab#Instantiate mainWebViewPrefab = WebViewPrefab.Instantiate(0.6f, 0.3f); mainWebViewPrefab.PixelDensity = 2; mainWebViewPrefab.transform.parent = transform; mainWebViewPrefab.transform.localPosition = new Vector3(0, -0.05f, 0.4f); mainWebViewPrefab.transform.localEulerAngles = new Vector3(0, 180, 0); // Instantiate a second webview above the first to show a UI that // displays the current URL and provides back / forward navigation buttons. controlsWebViewPrefab = WebViewPrefab.Instantiate(0.6f, 0.05f); controlsWebViewPrefab.KeyboardEnabled = false; controlsWebViewPrefab.transform.parent = mainWebViewPrefab.transform; controlsWebViewPrefab.transform.localPosition = new Vector3(0, 0.06f, 0); controlsWebViewPrefab.transform.localEulerAngles = Vector3.zero; // Add an on-screen keyboard under the main webview. // https://developer.vuplex.com/webview/Keyboard var keyboard = Keyboard.Instantiate(); keyboard.transform.SetParent(mainWebViewPrefab.transform, false); keyboard.transform.localPosition = new Vector3(0, -0.31f, 0); keyboard.transform.localEulerAngles = Vector3.zero; // Wait for the prefabs to initialize because the WebView property of each is null until then. // https://developer.vuplex.com/webview/WebViewPrefab#WaitUntilInitialized await Task.WhenAll(new Task[] { mainWebViewPrefab.WaitUntilInitialized(), controlsWebViewPrefab.WaitUntilInitialized() }); // Now that the WebViewPrefabs are initialized, we can use the IWebView APIs via its WebView property. // https://developer.vuplex.com/webview/IWebView mainWebViewPrefab.WebView.UrlChanged += (sender, eventArgs) => { _setDisplayedUrl(eventArgs.Url); // Refresh the back / forward button state after 1 second. Invoke("_refreshBackForwardState", 1); }; mainWebViewPrefab.WebView.LoadUrl("https://www.google.com"); controlsWebViewPrefab.WebView.MessageEmitted += Controls_MessageEmitted; controlsWebViewPrefab.WebView.LoadHtml(CONTROLS_HTML); // Android Gecko and UWP w/ XR enabled don't support transparent webviews, so set the cutout // rect to the entire view so that the shader makes its black background pixels transparent. var pluginType = controlsWebViewPrefab.WebView.PluginType; if (pluginType == WebPluginType.AndroidGecko || pluginType == WebPluginType.UniversalWindowsPlatform) { controlsWebViewPrefab.SetCutoutRect(new Rect(0, 0, 1, 1)); } } async void _refreshBackForwardState() { // Get the main webview's back / forward state and then post a message // to the controls UI to update its buttons' state. var canGoBack = await mainWebViewPrefab.WebView.CanGoBack(); var canGoForward = await mainWebViewPrefab.WebView.CanGoForward(); var serializedMessage = $"{{ \"type\": \"SET_BUTTONS\", \"canGoBack\": {canGoBack.ToString().ToLowerInvariant()}, \"canGoForward\": {canGoForward.ToString().ToLowerInvariant()} }}"; controlsWebViewPrefab.WebView.PostMessage(serializedMessage); } void Controls_MessageEmitted(object sender, EventArgs<string> eventArgs) { if (eventArgs.Value == "CONTROLS_INITIALIZED") { // The controls UI won't be initialized in time to receive the first UrlChanged event, // so explicitly set the initial URL after the controls UI indicates it's ready. _setDisplayedUrl(mainWebViewPrefab.WebView.Url); return; } var message = eventArgs.Value; if (message == "GO_BACK") { mainWebViewPrefab.WebView.GoBack(); } else if (message == "GO_FORWARD") { mainWebViewPrefab.WebView.GoForward(); } } void _setDisplayedUrl(string url) { if (controlsWebViewPrefab.WebView != null) { var serializedMessage = $"{{ \"type\": \"SET_URL\", \"url\": \"{url}\" }}"; controlsWebViewPrefab.WebView.PostMessage(serializedMessage); } } const string CONTROLS_HTML = @" <!DOCTYPE html> <html> <head> <!-- This transparent meta tag instructs 3D WebView to allow the page to be transparent. --> <meta name='transparent' content='true'> <meta charset='UTF-8'> <style> body { font-family: Helvetica, Arial, Sans-Serif; margin: 0; height: 100vh; color: white; } .controls { display: flex; justify-content: space-between; align-items: center; height: 100%; } .controls > div { background-color: #283237; border-radius: 8px; height: 100%; } .url-display { flex: 0 0 75%; width: 75%; display: flex; align-items: center; overflow: hidden; cursor: default; } #url { width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding: 0 15px; font-size: 18px; } .buttons { flex: 0 0 20%; width: 20%; display: flex; justify-content: space-around; align-items: center; } .buttons > button { font-size: 40px; background: none; border: none; outline: none; color: white; margin: 0; padding: 0; } .buttons > button:disabled { color: rgba(255, 255, 255, 0.3); } .buttons > button:last-child { transform: scaleX(-1); } /* For Gecko only, set the background color to black so that the shader's cutout rect can translate the black pixels to transparent.*/ @supports (-moz-appearance:none) { body { background-color: black; } } </style> </head> <body> <div class='controls'> <div class='url-display'> <div id='url'></div> </div> <div class='buttons'> <button id='back-button' disabled='true' onclick='vuplex.postMessage(""GO_BACK"")'>←</button> <button id='forward-button' disabled='true' onclick='vuplex.postMessage(""GO_FORWARD"")'>←</button> </div> </div> <script> // Handle messages sent from C# function handleMessage(message) { var data = JSON.parse(message.data); if (data.type === 'SET_URL') { document.getElementById('url').innerText = data.url; } else if (data.type === 'SET_BUTTONS') { document.getElementById('back-button').disabled = !data.canGoBack; document.getElementById('forward-button').disabled = !data.canGoForward; } } function attachMessageListener() { window.vuplex.addEventListener('message', handleMessage); window.vuplex.postMessage('CONTROLS_INITIALIZED'); } if (window.vuplex) { attachMessageListener(); } else { window.addEventListener('vuplexready', attachMessageListener); } </script> </body> </html> "; } }