AdvancedWebViewDemo.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. // Copyright (c) 2022 Vuplex Inc. All rights reserved.
  2. //
  3. // Licensed under the Vuplex Commercial Software Library License, you may
  4. // not use this file except in compliance with the License. You may obtain
  5. // a copy of the License at
  6. //
  7. // https://vuplex.com/commercial-library-license
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. using System;
  15. using System.Threading.Tasks;
  16. using System.Timers;
  17. using UnityEngine;
  18. namespace Vuplex.WebView.Demos {
  19. /// <summary>
  20. /// Sets up the AdvancedWebViewDemo scene, which displays web content in a main
  21. /// world-space WebViewPrefab and then renders a UI in a second webview to display the current URL
  22. /// and provide back / forward navigation controls.<br/><br/>
  23. ///
  24. /// <b>Note:</b> The address bar currently only displays the current URL and is not an input.
  25. /// I plan to add a dedicated browser prefab in the future that will include
  26. /// a functional address bar. In the meantime, you can edit the CONTROLS_HTML field
  27. /// below to implement a URL input.
  28. /// </summary>
  29. /// <remarks>
  30. /// This scene demonstrates the following: <br/>
  31. /// - Programmatically instantiating WebViewPrefabs at runtime <br/>
  32. /// - Creating and hooking up an on-screen keyboard <br/>
  33. /// - Using IWebView methods like LoadUrl, LoadHtml, GoBack, and GoForward <br/>
  34. /// - Attaching handlers to the IWebView.UrlChanged and MessageEmitted events <br/>
  35. /// - Message passing from C#-to-JavaScript and vice versa <br/>
  36. /// - Creating a transparent webview using the transparent meta tag <br/><br/>
  37. ///
  38. /// Links: <br/>
  39. /// - WebViewPrefab docs: https://developer.vuplex.com/webview/WebViewPrefab <br/>
  40. /// - How clicking works: https://support.vuplex.com/articles/clicking <br/>
  41. /// - Other examples: https://developer.vuplex.com/webview/overview#examples <br/>
  42. /// </remarks>
  43. class AdvancedWebViewDemo : MonoBehaviour {
  44. Timer _buttonRefreshTimer = new Timer();
  45. WebViewPrefab _controlsWebViewPrefab;
  46. HardwareKeyboardListener _hardwareKeyboardListener;
  47. WebViewPrefab _mainWebViewPrefab;
  48. async void Start() {
  49. 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.");
  50. // Use a desktop User-Agent to request the desktop versions of websites.
  51. // https://developer.vuplex.com/webview/Web#SetUserAgent
  52. Web.SetUserAgent(false);
  53. // Create a 0.6 x 0.3 webview for the main web content.
  54. _mainWebViewPrefab = WebViewPrefab.Instantiate(0.6f, 0.3f);
  55. _mainWebViewPrefab.PixelDensity = 2;
  56. _mainWebViewPrefab.transform.parent = transform;
  57. _mainWebViewPrefab.transform.localPosition = new Vector3(0, -0.05f, 0.4f);
  58. _mainWebViewPrefab.transform.localEulerAngles = new Vector3(0, 180, 0);
  59. // Create a second webview above the first to show a UI that
  60. // displays the current URL and provides back / forward navigation buttons.
  61. _controlsWebViewPrefab = WebViewPrefab.Instantiate(0.6f, 0.05f);
  62. _controlsWebViewPrefab.transform.parent = _mainWebViewPrefab.transform;
  63. _controlsWebViewPrefab.transform.localPosition = new Vector3(0, 0.06f, 0);
  64. _controlsWebViewPrefab.transform.localEulerAngles = Vector3.zero;
  65. // Set up a timer to allow the state of the back / forward buttons to be
  66. // refreshed one second after a URL change occurs.
  67. _buttonRefreshTimer.AutoReset = false;
  68. _buttonRefreshTimer.Interval = 1000;
  69. _buttonRefreshTimer.Elapsed += ButtonRefreshTimer_Elapsed;
  70. _setUpKeyboards();
  71. // Wait for both WebViewPrefabs to initialize, because the
  72. // WebViewPrefab.WebView property is null until the prefabs have initialized.
  73. await Task.WhenAll(new Task[] {
  74. _mainWebViewPrefab.WaitUntilInitialized(),
  75. _controlsWebViewPrefab.WaitUntilInitialized()
  76. });
  77. // Now that the WebViewPrefabs are initialized, we can use the WebViewPrefab.WebView property.
  78. _mainWebViewPrefab.WebView.UrlChanged += MainWebView_UrlChanged;
  79. _mainWebViewPrefab.WebView.LoadUrl("https://www.google.com");
  80. _controlsWebViewPrefab.WebView.MessageEmitted += Controls_MessageEmitted;
  81. _controlsWebViewPrefab.WebView.LoadHtml(CONTROLS_HTML);
  82. // Android Gecko and UWP w/ XR enabled don't support transparent webviews, so set the cutout
  83. // rect to the entire view so that the shader makes its black background pixels transparent.
  84. var pluginType = _controlsWebViewPrefab.WebView.PluginType;
  85. if (pluginType == WebPluginType.AndroidGecko || pluginType == WebPluginType.UniversalWindowsPlatform) {
  86. _controlsWebViewPrefab.SetCutoutRect(new Rect(0, 0, 1, 1));
  87. }
  88. }
  89. void ButtonRefreshTimer_Elapsed(object sender, ElapsedEventArgs eventArgs) {
  90. // Get the main webview's back / forward state and then post a message
  91. // to the controls UI to update its buttons' state.
  92. Vuplex.WebView.Internal.Dispatcher.RunOnMainThread(async () => {
  93. var canGoBack = await _mainWebViewPrefab.WebView.CanGoBack();
  94. var canGoForward = await _mainWebViewPrefab.WebView.CanGoForward();
  95. var serializedMessage = $"{{ \"type\": \"SET_BUTTONS\", \"canGoBack\": {canGoBack.ToString().ToLowerInvariant()}, \"canGoForward\": {canGoForward.ToString().ToLowerInvariant()} }}";
  96. _controlsWebViewPrefab.WebView.PostMessage(serializedMessage);
  97. });
  98. }
  99. void Controls_MessageEmitted(object sender, EventArgs<string> eventArgs) {
  100. if (eventArgs.Value == "CONTROLS_INITIALIZED") {
  101. // The controls UI won't be initialized in time to receive the first UrlChanged event,
  102. // so explicitly set the initial URL after the controls UI indicates it's ready.
  103. _setDisplayedUrl(_mainWebViewPrefab.WebView.Url);
  104. return;
  105. }
  106. var message = eventArgs.Value;
  107. if (message == "GO_BACK") {
  108. _mainWebViewPrefab.WebView.GoBack();
  109. } else if (message == "GO_FORWARD") {
  110. _mainWebViewPrefab.WebView.GoForward();
  111. }
  112. }
  113. void MainWebView_UrlChanged(object sender, UrlChangedEventArgs eventArgs) {
  114. _setDisplayedUrl(eventArgs.Url);
  115. _buttonRefreshTimer.Start();
  116. }
  117. void _setDisplayedUrl(string url) {
  118. if (_controlsWebViewPrefab.WebView != null) {
  119. var serializedMessage = $"{{ \"type\": \"SET_URL\", \"url\": \"{url}\" }}";
  120. _controlsWebViewPrefab.WebView.PostMessage(serializedMessage);
  121. }
  122. }
  123. async void _setUpKeyboards() {
  124. await _mainWebViewPrefab.WaitUntilInitialized();
  125. // Send keys from the hardware (USB or Bluetooth) keyboard to the webview.
  126. // Use separate KeyDown() and KeyUp() methods if the webview supports
  127. // it, otherwise just use IWebView.SendKey().
  128. // https://developer.vuplex.com/webview/IWithKeyDownAndUp
  129. var webViewWithKeyDownAndUp = _mainWebViewPrefab.WebView as IWithKeyDownAndUp;
  130. _hardwareKeyboardListener = HardwareKeyboardListener.Instantiate();
  131. _hardwareKeyboardListener.KeyDownReceived += (sender, eventArgs) => {
  132. if (webViewWithKeyDownAndUp != null) {
  133. webViewWithKeyDownAndUp.KeyDown(eventArgs.Value, eventArgs.Modifiers);
  134. } else {
  135. _mainWebViewPrefab.WebView.SendKey(eventArgs.Value);
  136. }
  137. };
  138. _hardwareKeyboardListener.KeyUpReceived += (sender, eventArgs) => {
  139. webViewWithKeyDownAndUp?.KeyUp(eventArgs.Value, eventArgs.Modifiers);
  140. };
  141. // Also add an on-screen keyboard under the main webview.
  142. var keyboard = Keyboard.Instantiate();
  143. keyboard.transform.SetParent(_mainWebViewPrefab.transform, false);
  144. keyboard.transform.localPosition = new Vector3(0, -0.31f, 0);
  145. keyboard.transform.localEulerAngles = Vector3.zero;
  146. keyboard.InputReceived += (sender, eventArgs) => {
  147. _mainWebViewPrefab.WebView.SendKey(eventArgs.Value);
  148. };
  149. }
  150. const string CONTROLS_HTML = @"
  151. <!DOCTYPE html>
  152. <html>
  153. <head>
  154. <!-- This transparent meta tag instructs 3D WebView to allow the page to be transparent. -->
  155. <meta name='transparent' content='true'>
  156. <meta charset='UTF-8'>
  157. <style>
  158. body {
  159. font-family: Helvetica, Arial, Sans-Serif;
  160. margin: 0;
  161. height: 100vh;
  162. color: white;
  163. }
  164. .controls {
  165. display: flex;
  166. justify-content: space-between;
  167. align-items: center;
  168. height: 100%;
  169. }
  170. .controls > div {
  171. background-color: #283237;
  172. border-radius: 8px;
  173. height: 100%;
  174. }
  175. .url-display {
  176. flex: 0 0 75%;
  177. width: 75%;
  178. display: flex;
  179. align-items: center;
  180. overflow: hidden;
  181. cursor: default;
  182. }
  183. #url {
  184. width: 100%;
  185. white-space: nowrap;
  186. overflow: hidden;
  187. text-overflow: ellipsis;
  188. padding: 0 15px;
  189. font-size: 18px;
  190. }
  191. .buttons {
  192. flex: 0 0 20%;
  193. width: 20%;
  194. display: flex;
  195. justify-content: space-around;
  196. align-items: center;
  197. }
  198. .buttons > button {
  199. font-size: 40px;
  200. background: none;
  201. border: none;
  202. outline: none;
  203. color: white;
  204. margin: 0;
  205. padding: 0;
  206. }
  207. .buttons > button:disabled {
  208. color: rgba(255, 255, 255, 0.3);
  209. }
  210. .buttons > button:last-child {
  211. transform: scaleX(-1);
  212. }
  213. /* For Gecko only, set the background color
  214. to black so that the shader's cutout rect
  215. can translate the black pixels to transparent.*/
  216. @supports (-moz-appearance:none) {
  217. body {
  218. background-color: black;
  219. }
  220. }
  221. </style>
  222. </head>
  223. <body>
  224. <div class='controls'>
  225. <div class='url-display'>
  226. <div id='url'></div>
  227. </div>
  228. <div class='buttons'>
  229. <button id='back-button' disabled='true' onclick='vuplex.postMessage(""GO_BACK"")'>←</button>
  230. <button id='forward-button' disabled='true' onclick='vuplex.postMessage(""GO_FORWARD"")'>←</button>
  231. </div>
  232. </div>
  233. <script>
  234. // Handle messages sent from C#
  235. function handleMessage(message) {
  236. var data = JSON.parse(message.data);
  237. if (data.type === 'SET_URL') {
  238. document.getElementById('url').innerText = data.url;
  239. } else if (data.type === 'SET_BUTTONS') {
  240. document.getElementById('back-button').disabled = !data.canGoBack;
  241. document.getElementById('forward-button').disabled = !data.canGoForward;
  242. }
  243. }
  244. function attachMessageListener() {
  245. window.vuplex.addEventListener('message', handleMessage);
  246. window.vuplex.postMessage('CONTROLS_INITIALIZED');
  247. }
  248. if (window.vuplex) {
  249. attachMessageListener();
  250. } else {
  251. window.addEventListener('vuplexready', attachMessageListener);
  252. }
  253. </script>
  254. </body>
  255. </html>
  256. ";
  257. }
  258. }