MockWebView.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. // Copyright (c) 2024 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. #pragma warning disable CS0067
  15. using System;
  16. using System.Collections.Generic;
  17. using System.Threading.Tasks;
  18. using UnityEngine;
  19. using Vuplex.WebView.Internal;
  20. namespace Vuplex.WebView {
  21. /// <summary>
  22. /// Mock IWebView implementation used for running in the Unity editor.
  23. /// </summary>
  24. /// <remarks>
  25. /// MockWebView logs messages to the console to indicate when its methods are
  26. /// called, but it doesn't actually load or render web content.
  27. /// </remarks>
  28. partial class MockWebView : MonoBehaviour, IWebView {
  29. public event EventHandler CloseRequested;
  30. public event EventHandler<ConsoleMessageEventArgs> ConsoleMessageLogged;
  31. public event EventHandler<EventArgs<bool>> FocusChanged;
  32. public event EventHandler<FocusedInputFieldChangedEventArgs> FocusedInputFieldChanged;
  33. public event EventHandler<LoadFailedEventArgs> LoadFailed;
  34. public event EventHandler<ProgressChangedEventArgs> LoadProgressChanged;
  35. public event EventHandler<EventArgs<string>> MessageEmitted;
  36. public event EventHandler<TerminatedEventArgs> Terminated;
  37. public event EventHandler<EventArgs<string>> TitleChanged;
  38. public event EventHandler<UrlChangedEventArgs> UrlChanged;
  39. public bool IsDisposed { get; private set; }
  40. public bool IsInitialized { get; private set; }
  41. public List<string> PageLoadScripts { get; } = new List<string>();
  42. public WebPluginType PluginType { get; } = WebPluginType.Mock;
  43. public Vector2Int Size { get; private set; }
  44. public Texture2D Texture { get; private set; }
  45. public string Title { get; private set; } = "";
  46. public string Url { get; private set; } = "";
  47. public Task<bool> CanGoBack() {
  48. _log("CanGoBack()");
  49. OnCanGoBack();
  50. return Task.FromResult(false);
  51. }
  52. public Task<bool> CanGoForward() {
  53. _log("CanGoForward()");
  54. OnCanGoForward();
  55. return Task.FromResult(false);
  56. }
  57. public Task<byte[]> CaptureScreenshot() {
  58. _log("CaptureScreenshot()");
  59. OnCaptureScreenshot();
  60. return Task.FromResult(new byte[0]);
  61. }
  62. public void Click(int xInPixels, int yInPixels, bool preventStealingFocus = false) {
  63. var pointIsValid = xInPixels >= 0 && xInPixels <= Size.x && yInPixels >= 0 && yInPixels <= Size.y;
  64. if (!pointIsValid) {
  65. throw new ArgumentException($"The point provided ({xInPixels}px, {yInPixels}px) is not within the bounds of the webview (width: {Size.x}px, height: {Size.y}px).");
  66. }
  67. _log($"Click({xInPixels}, {yInPixels}, {preventStealingFocus})");
  68. }
  69. public void Click(Vector2 point, bool preventStealingFocus = false) {
  70. _assertValidNormalizedPoint(point);
  71. _log($"Click({point.ToString("n4")}, {preventStealingFocus})");
  72. if (_moreDetailsClickRect.Contains(point)) {
  73. Application.OpenURL("https://support.vuplex.com/articles/mock-webview");
  74. }
  75. }
  76. public void Copy() {
  77. _log("Copy()");
  78. OnCopy();
  79. }
  80. public Material CreateMaterial() {
  81. #if UNITY_SERVER
  82. return null;
  83. #else
  84. var material = new Material(Resources.Load<Material>("MockViewportMaterial"));
  85. // Create a copy of the texture so that an Exception won't be thrown when the prefab destroys it.
  86. // Also, explicitly use RGBA32 here so that the texture will be converted to RGBA32 if the editor
  87. // imported it as a different format. For example, when Texture Compression is set to ASTC in Android build settings,
  88. // the editor automatically imports new textures as ASTC, even though the Windows editor doesn't support that format.
  89. var texture = new Texture2D(material.mainTexture.width, material.mainTexture.height, TextureFormat.RGBA32, true);
  90. texture.SetPixels((material.mainTexture as Texture2D).GetPixels());
  91. texture.Apply();
  92. material.mainTexture = texture;
  93. return material;
  94. #endif
  95. }
  96. public void Cut() {
  97. _log("Cut()");
  98. OnCut();
  99. }
  100. public static Task<bool> DeleteCookies(string url, string cookieName = null) {
  101. if (url == null) {
  102. throw new ArgumentException("The url cannot be null.");
  103. }
  104. _log($"DeleteCookies(\"{url}\", \"{cookieName}\")");
  105. return Task.FromResult(true);
  106. }
  107. public void Dispose() {
  108. IsDisposed = true;
  109. _log("Dispose()");
  110. if (this != null) {
  111. Destroy(gameObject);
  112. }
  113. }
  114. public Task<string> ExecuteJavaScript(string javaScript) {
  115. var taskSource = new TaskCompletionSource<string>();
  116. ExecuteJavaScript(javaScript, taskSource.SetResult);
  117. return taskSource.Task;
  118. }
  119. public void ExecuteJavaScript(string javaScript, Action<string> callback) {
  120. _log($"ExecuteJavaScript(\"{_truncateIfNeeded(javaScript)}\")");
  121. callback("");
  122. OnExecuteJavaScript();
  123. }
  124. public static Task<Cookie[]> GetCookies(string url, string cookieName = null) {
  125. if (url == null) {
  126. throw new ArgumentException("The url cannot be null.");
  127. }
  128. _log($"GetCookies(\"{url}\", \"{cookieName}\")");
  129. var taskSource = new TaskCompletionSource<Cookie[]>();
  130. return Task.FromResult(new Cookie[0]);
  131. }
  132. public Task<byte[]> GetRawTextureData() {
  133. _log("GetRawTextureData()");
  134. OnGetRawTextureData();
  135. return Task.FromResult(new byte[0]);
  136. }
  137. public void GoBack() {
  138. _log("GoBack()");
  139. OnGoBack();
  140. }
  141. public void GoForward() {
  142. _log("GoForward()");
  143. OnGoForward();
  144. }
  145. public Task Init(int width, int height) {
  146. Texture = new Texture2D(width, height, TextureFormat.RGBA32, false);
  147. Size = new Vector2Int(width, height);
  148. IsInitialized = true;
  149. DontDestroyOnLoad(gameObject);
  150. _log($"Init() width: {width.ToString("n4")}, height: {height.ToString("n4")}");
  151. return Task.FromResult(true);
  152. }
  153. public static MockWebView Instantiate() => new GameObject("MockWebView").AddComponent<MockWebView>();
  154. public virtual void LoadHtml(string html) {
  155. var truncatedHtml = _truncateIfNeeded(html);
  156. Url = truncatedHtml;
  157. _log($"LoadHtml(\"{truncatedHtml}...\")");
  158. OnLoadHtml();
  159. _handlePageLoad(truncatedHtml);
  160. }
  161. public virtual void LoadUrl(string url) => LoadUrl(url, null);
  162. public virtual void LoadUrl(string url, Dictionary<string, string> additionalHttpHeaders) {
  163. Url = url;
  164. _log($"LoadUrl(\"{url}\")");
  165. OnLoadUrl(url);
  166. _handlePageLoad(url);
  167. }
  168. public Vector2Int NormalizedToPoint(Vector2 normalizedPoint) {
  169. return new Vector2Int(
  170. (int)Math.Round(normalizedPoint.x * (float)Size.x),
  171. (int)Math.Round(normalizedPoint.y * (float)Size.y)
  172. );
  173. }
  174. public void Paste() {
  175. _log("Paste()");
  176. OnPaste();
  177. }
  178. public Vector2 PointToNormalized(int xInPixels, int yInPixels) {
  179. return new Vector2((float)xInPixels / Size.x, (float)yInPixels / Size.y);
  180. }
  181. public void PostMessage(string data) => _log($"PostMessage(\"{data}\")");
  182. public void Reload() => _log("Reload()");
  183. public void Resize(int width, int height) {
  184. Size = new Vector2Int(width, height);
  185. _log($"Resize({width.ToString("n4")}, {height.ToString("n4")})");
  186. }
  187. public void Scroll(int scrollDeltaX, int scrollDeltaY) => _log($"Scroll({scrollDeltaX}, {scrollDeltaY})");
  188. public void Scroll(Vector2 delta) => _log($"Scroll({delta.ToString("n4")})");
  189. public void Scroll(Vector2 delta, Vector2 point) {
  190. _assertValidNormalizedPoint(point);
  191. _log($"Scroll({delta.ToString("n4")}, {point.ToString("n4")})");
  192. }
  193. public void SelectAll() => _log("SelectAll()");
  194. public void SendKey(string input) => _log($"SendKey(\"{input}\")");
  195. public static Task<bool> SetCookie(Cookie cookie) {
  196. if (cookie == null) {
  197. throw new ArgumentException("Cookie cannot be null.");
  198. }
  199. if (!cookie.IsValid) {
  200. throw new ArgumentException("Cannot set invalid cookie: " + cookie);
  201. }
  202. _log($"SetCookie({cookie}");
  203. return Task.FromResult(true);
  204. }
  205. public void SetDefaultBackgroundEnabled(bool enabled) => _log($"SetDefaultBackgroundEnabled({enabled})");
  206. public void SetFocused(bool focused) {
  207. _log($"SetFocused({focused})");
  208. FocusChanged?.Invoke(this, new EventArgs<bool>(focused));
  209. }
  210. public void SetRenderingEnabled(bool enabled) => _log($"SetRenderingEnabled({enabled})");
  211. public void StopLoad() => _log("StopLoad()");
  212. public Task WaitForNextPageLoadToFinish() {
  213. if (_pageLoadFinishedTaskSource == null) {
  214. _pageLoadFinishedTaskSource = new TaskCompletionSource<bool>();
  215. }
  216. return _pageLoadFinishedTaskSource.Task;
  217. }
  218. public void ZoomIn() => _log("ZoomIn()");
  219. public void ZoomOut() => _log("ZoomOut()");
  220. Rect _moreDetailsClickRect = new Rect(0.67f, 0.7f, 0.17f, 0.11f);
  221. TaskCompletionSource<bool> _pageLoadFinishedTaskSource;
  222. // Partial methods implemented by other 3D WebView packages
  223. // to provide platform-specific warnings in the editor.
  224. partial void OnCanGoBack();
  225. partial void OnCanGoForward();
  226. partial void OnCaptureScreenshot();
  227. partial void OnCopy();
  228. partial void OnCut();
  229. partial void OnExecuteJavaScript();
  230. partial void OnGetRawTextureData();
  231. partial void OnGoBack();
  232. partial void OnGoForward();
  233. partial void OnLoadHtml();
  234. partial void OnLoadUrl(string url);
  235. partial void OnPaste();
  236. partial void OnZoomIn();
  237. partial void OnZoomOut();
  238. void _assertValidNormalizedPoint(Vector2 normalizedPoint) {
  239. var isValid = normalizedPoint.x >= 0f && normalizedPoint.x <= 1f && normalizedPoint.y >= 0f && normalizedPoint.y <= 1f;
  240. if (!isValid) {
  241. throw new ArgumentException($"The normalized point provided is invalid. The x and y values of normalized points must be in the range of [0, 1], but the value provided was {normalizedPoint.ToString("n4")}. For more info, please see https://support.vuplex.com/articles/normalized-points");
  242. }
  243. }
  244. void _handlePageLoad(string url) {
  245. UrlChanged?.Invoke(this, new UrlChangedEventArgs(url, UrlActionType.Load));
  246. LoadProgressChanged?.Invoke(this, new ProgressChangedEventArgs(ProgressChangeType.Started, 0));
  247. LoadProgressChanged?.Invoke(this, new ProgressChangedEventArgs(ProgressChangeType.Finished, 1));
  248. _pageLoadFinishedTaskSource?.SetResult(true);
  249. _pageLoadFinishedTaskSource = null;
  250. }
  251. static void _log(string message) {
  252. #if !VUPLEX_DISABLE_MOCK_WEBVIEW_LOGGING
  253. WebViewLogger.Log("[MockWebView] " + message);
  254. #endif
  255. }
  256. string _truncateIfNeeded(string str) {
  257. var maxLength = 25;
  258. if (str.Length <= maxLength) {
  259. return str;
  260. }
  261. return str.Substring(0, maxLength) + "...";
  262. }
  263. [Obsolete(ObsoletionMessages.Blur, true)]
  264. public void Blur() {}
  265. [Obsolete(ObsoletionMessages.CanGoBack, true)]
  266. public void CanGoBack(Action<bool> callback) {}
  267. [Obsolete(ObsoletionMessages.CanGoForward, true)]
  268. public void CanGoForward(Action<bool> callback) {}
  269. [Obsolete(ObsoletionMessages.CaptureScreenshot, true)]
  270. public void CaptureScreenshot(Action<byte[]> callback) {}
  271. [Obsolete(ObsoletionMessages.DisableViewUpdates, true)]
  272. public void DisableViewUpdates() {}
  273. [Obsolete(ObsoletionMessages.EnableViewUpdates, true)]
  274. public void EnableViewUpdates() {}
  275. [Obsolete(ObsoletionMessages.Focus, true)]
  276. public void Focus() {}
  277. [Obsolete(ObsoletionMessages.GetRawTextureData, true)]
  278. public void GetRawTextureData(Action<byte[]> callback) {}
  279. [Obsolete(ObsoletionMessages.HandleKeyboardInput)]
  280. public void HandleKeyboardInput(string key) => SendKey(key);
  281. [Obsolete(ObsoletionMessages.Init, true)]
  282. public void Init(Texture2D texture, float width, float height) {}
  283. [Obsolete(ObsoletionMessages.Init2, true)]
  284. public void Init(Texture2D texture, float width, float height, Texture2D videoTexture) {}
  285. [Obsolete(ObsoletionMessages.PageLoadFailed)]
  286. public event EventHandler PageLoadFailed;
  287. [Obsolete(ObsoletionMessages.Resolution, true)]
  288. public float Resolution { get; }
  289. [Obsolete(ObsoletionMessages.SetResolution, true)]
  290. public void SetResolution(float pixelsPerUnityUnit) {}
  291. [Obsolete(ObsoletionMessages.SizeInPixels)]
  292. public Vector2 SizeInPixels { get => (Vector2)Size; }
  293. [Obsolete(ObsoletionMessages.VideoRectChanged, true)]
  294. public event EventHandler<EventArgs<Rect>> VideoRectChanged;
  295. [Obsolete(ObsoletionMessages.VideoTexture, true)]
  296. public Texture2D VideoTexture { get; }
  297. }
  298. }