StandaloneWebView.cs 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092
  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. #if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN || UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX
  15. using System;
  16. using System.Collections.Generic;
  17. using System.IO;
  18. using System.Runtime.InteropServices;
  19. using System.Threading.Tasks;
  20. using UnityEngine;
  21. using Vuplex.WebView.Internal;
  22. namespace Vuplex.WebView {
  23. /// <summary>
  24. /// The base IWebView implementation used by 3D WebView for Windows and macOS.
  25. /// This class also includes extra methods for Standalone-specific functionality.
  26. /// </summary>
  27. public abstract partial class StandaloneWebView : BaseWebView,
  28. IWithCursorType,
  29. IWithDownloads,
  30. IWithFileSelection,
  31. IWithKeyDownAndUp,
  32. IWithMovablePointer,
  33. IWithMutableAudio,
  34. IWithPixelDensity,
  35. IWithPointerDownAndUp,
  36. IWithPopups,
  37. IWithTouch {
  38. /// <summary>
  39. /// Indicates that a server requested [HTTP authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication)
  40. /// to make the browser show its built-in authentication UI.
  41. /// </summary>
  42. /// <remarks>
  43. /// If no handler is attached to this event, then the host's authentication request will be ignored
  44. /// and the page will not be paused. If a handler is attached to this event, then the page will
  45. /// be paused until Continue() or Cancel() is called.
  46. ///
  47. /// You can test basic HTTP auth using [this page](https://jigsaw.w3.org/HTTP/Basic/)
  48. /// with the username "guest" and the password "guest".
  49. /// </remarks>
  50. /// <remarks>
  51. /// This event is not raised for most websites because most sites implement a custom sign-in page
  52. /// instead of using HTTP authentication to show the browser's built-in authentication UI.
  53. /// </remarks>
  54. /// <example>
  55. /// <code>
  56. /// await webViewPrefab.WaitUntilInitialized();
  57. /// #if UNITY_STANDALONE
  58. /// var standaloneWebView = webViewPrefab.WebView as StandaloneWebView;
  59. /// standaloneWebView.AuthRequested += (sender, eventArgs) => {
  60. /// Debug.Log("Auth requested by " + eventArgs.Host);
  61. /// eventArgs.Continue("myUsername", "myPassword");
  62. /// };
  63. /// #endif
  64. /// </code>
  65. /// </example>
  66. public event EventHandler<AuthRequestedEventArgs> AuthRequested {
  67. add {
  68. _assertSingletonEventHandlerUnset(_authRequestedHandler, "AuthRequested");
  69. _authRequestedHandler = value;
  70. WebView_setAuthEnabled(_nativeWebViewPtr, true);
  71. }
  72. remove {
  73. if (_authRequestedHandler == value) {
  74. _authRequestedHandler = null;
  75. WebView_setAuthEnabled(_nativeWebViewPtr, false);
  76. }
  77. }
  78. }
  79. /// <see cref="IWithCursorType"/>
  80. public event EventHandler<EventArgs<string>> CursorTypeChanged {
  81. add {
  82. _cursorTypeChanged += value;
  83. WebView_setCursorTypeEventsEnabled(_nativeWebViewPtr, true);
  84. }
  85. remove {
  86. _cursorTypeChanged -= value;
  87. if (_cursorTypeChanged == value) {
  88. _authRequestedHandler = null;
  89. WebView_setCursorTypeEventsEnabled(_nativeWebViewPtr, false);
  90. }
  91. }
  92. }
  93. /// <see cref="IWithDownloads"/>
  94. public event EventHandler<DownloadChangedEventArgs> DownloadProgressChanged;
  95. /// <see cref="IWithFileSelection"/>
  96. public event EventHandler<FileSelectionEventArgs> FileSelectionRequested {
  97. add {
  98. _assertSingletonEventHandlerUnset(_fileSelectionHandler, "FileSelectionRequested");
  99. _fileSelectionHandler = value;
  100. WebView_setFileSelectionEnabled(_nativeWebViewPtr, true);
  101. }
  102. remove {
  103. if (_fileSelectionHandler == value) {
  104. _fileSelectionHandler = null;
  105. WebView_setFileSelectionEnabled(_nativeWebViewPtr, false);
  106. }
  107. }
  108. }
  109. /// <see cref="IWithPopups"/>
  110. public event EventHandler<PopupRequestedEventArgs> PopupRequested;
  111. /// <see cref="IWithPixelDensity"/>
  112. public float PixelDensity { get; private set; } = 1f;
  113. public override Task<bool> CanGoBack() {
  114. OnCanGoBack();
  115. return base.CanGoBack();
  116. }
  117. public override Task<bool> CanGoForward() {
  118. OnCanGoForward();
  119. return base.CanGoForward();
  120. }
  121. public override Task<byte[]> CaptureScreenshot() {
  122. OnCaptureScreenshot();
  123. return base.CaptureScreenshot();
  124. }
  125. public static void ClearAllData() {
  126. if (WebView_browserProcessIsRunning()) {
  127. _throwAlreadyInitializedException("ClearAllData");
  128. }
  129. var cachePath = _getCachePath();
  130. if (Directory.Exists(cachePath)) {
  131. Directory.Delete(cachePath, true);
  132. }
  133. }
  134. public override void Copy() {
  135. _assertValidState();
  136. WebView_copy(_nativeWebViewPtr);
  137. OnCopy();
  138. }
  139. public override void Cut() {
  140. _assertValidState();
  141. WebView_cut(_nativeWebViewPtr);
  142. OnCut();
  143. }
  144. /// <summary>
  145. /// Deletes all cookies for all URLs and returns a `Task&lt;bool&gt;` indicating whether the deletion succeeded.
  146. /// </summary>
  147. /// <example>
  148. /// <code>
  149. /// #if UNITY_STANDALONE
  150. /// var succeeded = await StandaloneWebView.DeleteAllCookies();
  151. /// #endif
  152. /// </code>
  153. /// </example>
  154. /// <seealso cref="Web.ClearAllData"/>
  155. public static Task<bool> DeleteAllCookies() => _deleteCookies();
  156. public static Task<bool> DeleteCookies(string url, string cookieName = null) {
  157. if (url == null) {
  158. // Enforce this to match the ICookieManager behavior of other platforms.
  159. throw new ArgumentException("The url cannot be null.");
  160. }
  161. return _deleteCookies(url, cookieName);
  162. }
  163. /// <see cref="IWithTouch"/>
  164. public void SendTouchEvent(TouchEvent touchEvent) {
  165. _assertValidState();
  166. var pixelsPoint = _convertNormalizedToPixels(touchEvent.Point);
  167. WebView_sendTouchEvent(
  168. _nativeWebViewPtr,
  169. touchEvent.TouchID,
  170. (int)touchEvent.Type,
  171. pixelsPoint.x,
  172. pixelsPoint.y,
  173. touchEvent.RadiusX,
  174. touchEvent.RadiusY,
  175. touchEvent.RotationAngle,
  176. touchEvent.Pressure
  177. );
  178. }
  179. /// <summary>
  180. /// Like Web.EnableRemoteDebugging(), but starts the DevTools session on the
  181. /// specified port instead of the default port 8080.
  182. /// </summary>
  183. /// <param name="portNumber">Port number in the range 1024 - 65535.</param>
  184. /// <example>
  185. /// <code>
  186. /// void Awake() {
  187. /// StandaloneWebView.EnableRemoteDebugging(9000);
  188. /// }
  189. /// </code>
  190. /// </example>
  191. /// <seealso cref="Web.EnableRemoteDebugging"/>
  192. public static void EnableRemoteDebugging(int portNumber) {
  193. if (!(1024 <= portNumber && portNumber <= 65535)) {
  194. throw new ArgumentException($"The given port number ({portNumber}) is not in the range from 1024 to 65535.");
  195. }
  196. var success = WebView_enableRemoteDebugging(portNumber);
  197. if (!success) {
  198. _throwAlreadyInitializedException("EnableRemoteDebugging");
  199. }
  200. }
  201. public override void ExecuteJavaScript(string javaScript, Action<string> callback) {
  202. base.ExecuteJavaScript(javaScript, callback);
  203. OnExecuteJavaScript();
  204. }
  205. public static Task<Cookie[]> GetCookies(string url, string cookieName = null) {
  206. if (url == null) {
  207. throw new ArgumentException("The url cannot be null.");
  208. }
  209. var taskSource = new TaskCompletionSource<Cookie[]>();
  210. var resultCallbackId = Guid.NewGuid().ToString();
  211. _pendingGetCookiesResultCallbacks[resultCallbackId] = taskSource.SetResult;
  212. WebView_getCookies(url, cookieName, resultCallbackId);
  213. return taskSource.Task;
  214. }
  215. public override Task<byte[]> GetRawTextureData() {
  216. OnGetRawTextureData();
  217. return base.GetRawTextureData();
  218. }
  219. public static void GloballySetUserAgent(bool mobile) {
  220. var success = WebView_globallySetUserAgentToMobile(mobile);
  221. if (!success) {
  222. _throwAlreadyInitializedException("SetUserAgent");
  223. }
  224. }
  225. public static void GloballySetUserAgent(string userAgent) {
  226. var success = WebView_globallySetUserAgent(userAgent);
  227. if (!success) {
  228. _throwAlreadyInitializedException("SetUserAgent");
  229. }
  230. }
  231. public override void GoBack() {
  232. base.GoBack();
  233. OnGoBack();
  234. }
  235. public override void GoForward() {
  236. base.GoForward();
  237. OnGoForward();
  238. }
  239. public async Task Init(int width, int height) {
  240. await _initBase(width, height, asyncInit: true);
  241. _nativeWebViewPtr = WebView_new(gameObject.name, width, height, PixelDensity, null);
  242. if (_nativeWebViewPtr == IntPtr.Zero) {
  243. throw new WebViewUnavailableException("Failed to instantiate a new webview. This could indicate that you're using an expired trial version of 3D WebView.");
  244. }
  245. await _initTaskSource.Task;
  246. }
  247. /// <see cref="IWithKeyDownAndUp"/>
  248. public void KeyDown(string key, KeyModifier modifiers) {
  249. _assertValidState();
  250. WebView_keyDown(_nativeWebViewPtr, key, (int)modifiers);
  251. }
  252. /// <see cref="IWithKeyDownAndUp"/>
  253. public void KeyUp(string key, KeyModifier modifiers) {
  254. _assertValidState();
  255. WebView_keyUp(_nativeWebViewPtr, key, (int)modifiers);
  256. }
  257. public override void LoadHtml(string html) {
  258. base.LoadHtml(html);
  259. OnLoadHtml();
  260. }
  261. public override void LoadUrl(string url) {
  262. base.LoadUrl(url);
  263. OnLoadUrl(url);
  264. }
  265. public override void LoadUrl(string url, Dictionary<string, string> additionalHttpHeaders) {
  266. if (additionalHttpHeaders != null) {
  267. foreach (var headerName in additionalHttpHeaders.Keys) {
  268. if (headerName.Equals("Accept-Language", StringComparison.InvariantCultureIgnoreCase)) {
  269. WebViewLogger.LogError("On Windows and macOS, the Accept-Language request header cannot be set with LoadUrl(url, headers). For more info, please see this article: <em>https://support.vuplex.com/articles/how-to-change-accept-language-header</em>");
  270. }
  271. }
  272. }
  273. base.LoadUrl(url, additionalHttpHeaders);
  274. }
  275. /// <see cref="IWithMovablePointer"/>
  276. public void MovePointer(Vector2 normalizedPoint) {
  277. _assertValidState();
  278. var pixelsPoint = _convertNormalizedToPixels(normalizedPoint);
  279. WebView_movePointer(_nativeWebViewPtr, pixelsPoint.x, pixelsPoint.y);
  280. }
  281. public override void Paste() {
  282. _assertValidState();
  283. WebView_paste(_nativeWebViewPtr);
  284. OnPaste();
  285. }
  286. /// <see cref="IWithPointerDownAndUp"/>
  287. public void PointerDown(Vector2 point) => _pointerDown(point, MouseButton.Left, 1);
  288. /// <see cref="IWithPointerDownAndUp"/>
  289. public void PointerDown(Vector2 point, PointerOptions options) {
  290. if (options == null) {
  291. options = new PointerOptions();
  292. }
  293. _pointerDown(point, options.Button, options.ClickCount);
  294. }
  295. /// <see cref="IWithPointerDownAndUp"/>
  296. public void PointerUp(Vector2 point) => _pointerUp(point, MouseButton.Left, 1);
  297. /// <see cref="IWithPointerDownAndUp"/>
  298. public void PointerUp(Vector2 point, PointerOptions options) {
  299. if (options == null) {
  300. options = new PointerOptions();
  301. }
  302. _pointerUp(point, options.Button, options.ClickCount);
  303. }
  304. public override void SelectAll() {
  305. _assertValidState();
  306. WebView_selectAll(_nativeWebViewPtr);
  307. }
  308. /// <see cref="IWithMutableAudio"/>
  309. public void SetAudioMuted(bool muted) {
  310. _assertValidState();
  311. WebView_setAudioMuted(_nativeWebViewPtr, muted);
  312. }
  313. public static void SetAutoplayEnabled(bool enabled) {
  314. var success = WebView_setAutoplayEnabled(enabled);
  315. if (!success) {
  316. _throwAlreadyInitializedException("SetAutoplayEnabled");
  317. }
  318. }
  319. /// <summary>
  320. /// By default, Chromium's cache is saved at the file path Application.persistentDataPath/Vuplex.WebView/chromium-cache,
  321. /// but you can call this method to specify a custom file path for the cache instead. This is useful, for example, to
  322. /// allow multiple instances of your app to run on the same machine, because multiple instances of Chromium cannot
  323. /// simultaneously share the same cache.
  324. /// </summary>
  325. /// <remarks>
  326. /// This method cannot be executed while the Chromium browser process is running. So, you will likely need to call it from Awake() to ensure that it's executed before Chromium is started. Alternatively, you can manually terminate Chromium prior to calling this method using StandaloneWebView.TerminateBrowserProcess().
  327. /// </remarks>
  328. /// <example>
  329. /// <code>
  330. /// void Awake() {
  331. /// #if UNITY_STANDALONE
  332. /// var customCachePath = Path.Combine(Application.persistentDataPath, "your-chromium-cache");
  333. /// StandaloneWebView.SetCachePath(customCachePath);
  334. /// #endif
  335. /// }
  336. /// </code>
  337. /// </example>
  338. public static void SetCachePath(string absoluteFilePath) {
  339. _cachePathOverride = absoluteFilePath;
  340. _setCachePath(absoluteFilePath, "SetCachePath");
  341. }
  342. public static new void SetCameraAndMicrophoneEnabled(bool enabled) {
  343. var success = WebView_setCameraAndMicrophoneEnabled(enabled);
  344. if (!success) {
  345. _throwAlreadyInitializedException("SetCameraAndMicrophoneEnabled");
  346. }
  347. }
  348. /// <summary>
  349. /// Sets the log level for the Chromium logs. The default is ChromiumLogLevel.Warning. Log locations: <br/>
  350. /// - Windows editor: {project path}\Assets\Vuplex\WebView\Standalone\Windows\Plugins\VuplexWebViewChromium\log-chromium.txt~ <br/>
  351. /// - Windows player: {app path}\{app name}_Data\Plugins\{architecture}\VuplexWebViewChromium\log-chromium.txt <br/>
  352. /// - macOS: ~/Library/Logs/Vuplex/log-chromium.txt
  353. /// </summary>
  354. /// <remarks>
  355. /// This method cannot be executed while the Chromium browser process is running. So, you will likely need to call it from Awake() to ensure that it's executed before Chromium is started. Alternatively, you can manually terminate Chromium prior to calling this method using StandaloneWebView.TerminateBrowserProcess().
  356. /// </remarks>
  357. /// <example>
  358. /// <code>
  359. /// void Awake() {
  360. /// #if UNITY_STANDALONE
  361. /// StandaloneWebView.SetChromiumLogLevel(ChromiumLogLevel.Disabled);
  362. /// #endif
  363. /// }
  364. /// </code>
  365. /// </example>
  366. public static void SetChromiumLogLevel(ChromiumLogLevel level) {
  367. var success = WebView_setChromiumLogLevel((int)level);
  368. if (!success) {
  369. _throwAlreadyInitializedException("SetChromiumLogLevel");
  370. }
  371. }
  372. /// <summary>
  373. /// Sets additional command line arguments to pass to Chromium.
  374. /// For reference, [here's an unofficial list of Chromium command line arguments](https://peter.sh/experiments/chromium-command-line-switches/).
  375. /// </summary>
  376. /// <remarks>
  377. /// This method cannot be executed while the Chromium browser process is running. So, you will likely need to call it from Awake() to ensure that it's executed before Chromium is started. Alternatively, you can manually terminate Chromium prior to calling this method using StandaloneWebView.TerminateBrowserProcess().
  378. /// </remarks>
  379. /// <example>
  380. /// <code>
  381. /// void Awake() {
  382. /// #if UNITY_STANDALONE
  383. /// StandaloneWebView.SetCommandLineArguments("--ignore-certificate-errors --disable-web-security");
  384. /// #endif
  385. /// }
  386. /// </code>
  387. /// </example>
  388. public static void SetCommandLineArguments(string args) {
  389. var success = WebView_setCommandLineArguments(args);
  390. if (!success) {
  391. _throwAlreadyInitializedException("SetCommandLineArguments");
  392. }
  393. }
  394. public static Task<bool> SetCookie(Cookie cookie) {
  395. if (cookie == null) {
  396. throw new ArgumentException("Cookie cannot be null.");
  397. }
  398. if (!cookie.IsValid) {
  399. throw new ArgumentException("Cannot set invalid cookie: " + cookie);
  400. }
  401. var taskSource = new TaskCompletionSource<bool>();
  402. var resultCallbackId = Guid.NewGuid().ToString();
  403. _pendingModifyCookiesResultCallbacks[resultCallbackId] = taskSource.SetResult;
  404. WebView_setCookie(cookie.ToJson(), resultCallbackId);
  405. return taskSource.Task;
  406. }
  407. /// <see cref="IWithDownloads"/>
  408. public void SetDownloadsEnabled(bool enabled) {
  409. _assertValidState();
  410. var downloadsDirectoryPath = enabled ? Path.Combine(Application.temporaryCachePath, Path.Combine("Vuplex.WebView", "downloads")) : "";
  411. WebView_setDownloadsEnabled(_nativeWebViewPtr, downloadsDirectoryPath);
  412. }
  413. public static void SetIgnoreCertificateErrors(bool ignore) {
  414. var success = WebView_setIgnoreCertificateErrors(ignore);
  415. if (!success) {
  416. _throwAlreadyInitializedException("SetIgnoreCertificateErrors");
  417. }
  418. }
  419. /// <summary>
  420. /// The native file picker for file input elements is enabled by default,
  421. /// but it can be disabled with this method.
  422. /// </summary>
  423. /// <example>
  424. /// <code>
  425. /// await webViewPrefab.WaitUntilInitialized();
  426. /// #if UNITY_STANDALONE
  427. /// var standaloneWebView = webViewPrefab.WebView as StandaloneWebView;
  428. /// standaloneWebView.SetNativeFileDialogEnabled(false);
  429. /// #endif
  430. /// </code>
  431. /// </example>
  432. public void SetNativeFileDialogEnabled(bool enabled) {
  433. _assertValidState();
  434. WebView_setNativeFileDialogEnabled(_nativeWebViewPtr, enabled);
  435. }
  436. /// <summary>
  437. /// Native popups triggered by JavaScript APIs like window.alert() are enabled by default,
  438. /// but they can be disabled with this method.
  439. /// </summary>
  440. /// <example>
  441. /// <code>
  442. /// await webViewPrefab.WaitUntilInitialized();
  443. /// #if UNITY_STANDALONE
  444. /// var standaloneWebView = webViewPrefab.WebView as StandaloneWebView;
  445. /// standaloneWebView.SetNativeScriptDialogEnabled(false);
  446. /// #endif
  447. /// </code>
  448. /// </example>
  449. public void SetNativeScriptDialogEnabled(bool enabled) {
  450. _assertValidState();
  451. WebView_setNativeScriptDialogEnabled(_nativeWebViewPtr, enabled);
  452. }
  453. /// <see cref="IWithPixelDensity"/>
  454. public void SetPixelDensity(float pixelDensity) {
  455. if (pixelDensity <= 0f || pixelDensity > 10) {
  456. throw new ArgumentException($"Invalid pixel density: {pixelDensity}. The pixel density must be between 0 and 10 (exclusive).");
  457. }
  458. PixelDensity = pixelDensity;
  459. if (IsInitialized) {
  460. _resize();
  461. }
  462. }
  463. /// <see cref="IWithPopups"/>
  464. public void SetPopupMode(PopupMode popupMode) {
  465. _assertValidState();
  466. WebView_setPopupMode(_nativeWebViewPtr, (int)popupMode);
  467. }
  468. /// <summary>
  469. /// By default, web pages cannot share the device's screen
  470. /// via JavaScript. Invoking `SetScreenSharingEnabled(true)` allows
  471. /// **all web pages** to share the screen.
  472. /// </summary>
  473. /// <remarks>
  474. /// <para>
  475. /// The screen that is shared is the default screen, and there isn't currently
  476. /// support for sharing a different screen or a specific application window.
  477. /// This is a limitation of Chromium Embedded Framework (CEF), which 3D WebView
  478. /// uses to embed Chromium.
  479. /// </para>
  480. /// <para>
  481. /// This method cannot be executed while the Chromium browser process is running. So, you will likely need to call it from Awake() to ensure that it's executed before Chromium is started. Alternatively, you can manually terminate Chromium prior to calling this method using StandaloneWebView.TerminateBrowserProcess().
  482. /// </para>
  483. /// </remarks>
  484. /// <example>
  485. /// <code>
  486. /// void Awake() {
  487. /// #if UNITY_STANDALONE
  488. /// StandaloneWebView.SetScreenSharingEnabled(true);
  489. /// #endif
  490. /// }
  491. /// </code>
  492. /// </example>
  493. public static void SetScreenSharingEnabled(bool enabled) {
  494. var success = WebView_setScreenSharingEnabled(enabled);
  495. if (!success) {
  496. _throwAlreadyInitializedException("SetScreenSharingEnabled");
  497. }
  498. }
  499. public static void SetStorageEnabled(bool enabled) {
  500. var cachePath = enabled ? _getCachePath() : "";
  501. _setCachePath(cachePath, "SetStorageEnabled");
  502. }
  503. /// <summary>
  504. /// Sets the target web frame rate. The default is `60`, which is also the maximum value.
  505. /// Specifying a target frame rate of `0` disables the frame rate limit.
  506. /// </summary>
  507. /// <remarks>
  508. /// This method cannot be executed while the Chromium browser process is running. So, you will likely need to call it from Awake() to ensure that it's executed before Chromium is started. Alternatively, you can manually terminate Chromium prior to calling this method using StandaloneWebView.TerminateBrowserProcess().
  509. /// </remarks>
  510. /// <example>
  511. /// <code>
  512. /// void Awake() {
  513. /// #if UNITY_STANDALONE
  514. /// // Disable the frame rate limit.
  515. /// StandaloneWebView.SetTargetFrameRate(0);
  516. /// #endif
  517. /// }
  518. /// </code>
  519. /// </example>
  520. public static void SetTargetFrameRate(uint targetFrameRate) {
  521. var success = WebView_setTargetFrameRate(targetFrameRate);
  522. if (!success) {
  523. _throwAlreadyInitializedException("SetTargetFrameRate");
  524. }
  525. }
  526. /// <summary>
  527. /// Sets the zoom level to the specified value. Specify `0.0` to reset the zoom level.
  528. /// </summary>
  529. /// <example>
  530. /// <code>
  531. /// // Set the zoom level to 1.75 after the page finishes loading.
  532. /// await webViewPrefab.WaitUntilInitialized();
  533. /// webViewPrefab.WebView.LoadProgressChanged += (sender, eventArgs) => {
  534. /// if (eventArgs.Type == ProgressChangeType.Finished) {
  535. /// #if UNITY_STANDALONE
  536. /// var standaloneWebView = webViewPrefab.WebView as StandaloneWebView;
  537. /// standaloneWebView.SetZoomLevel(1.75f);
  538. /// #endif
  539. /// }
  540. /// };
  541. /// </code>
  542. /// </example>
  543. public void SetZoomLevel(float zoomLevel) {
  544. _assertValidState();
  545. WebView_setZoomLevel(_nativeWebViewPtr, zoomLevel);
  546. }
  547. /// <summary>
  548. /// Terminates the Chromium browser process.
  549. /// </summary>
  550. /// <remarks>
  551. /// When 3D WebView for Windows and macOS initializes the application's first webview, it starts Chromium in a separate process.
  552. /// That Chromium process runs until the application closes, at which point 3D WebView automatically calls this
  553. /// method to terminate the Chromium process. However, in some rare cases, you may want your application to call
  554. /// this method manually to terminate Chromium while the app is still running. For example, some 3D WebView APIs
  555. /// like Web.ClearAllData() cannot be called while Chromium is running, but your application can use this method to terminate
  556. /// Chromium so that it can call those APIs. Prior to calling this method, your application must destroy all of the
  557. /// application's existing webviews (e.g. using WebViewPrefab.Destroy()). After Chromium finishes terminating,
  558. /// you can restart Chromium by creating new webview instances. (e.g. using WebViewPrefab.Instantiate()).
  559. /// </remarks>
  560. /// <example>
  561. /// <code>
  562. /// async void ClearDataAtRuntime() {
  563. /// #if UNITY_STANDALONE || UNITY_EDITOR
  564. /// // 1. Destroy all the webviews in the application.
  565. /// var webViewPrefabs = GameObject.FindObjectsOfType&lt;BaseWebViewPrefab&gt;();
  566. /// foreach (var prefab in webViewPrefabs) {
  567. /// prefab.Destroy();
  568. /// }
  569. ///
  570. /// // 2. Terminate Chromium.
  571. /// await StandaloneWebView.TerminateBrowserProcess();
  572. ///
  573. /// // 3. Call the API that can't be called while Chromium is running.
  574. /// Web.ClearAllData();
  575. ///
  576. /// // 4. TODO: Create new webviews with WebViewPrefab.Instantiate() if needed.
  577. /// #else
  578. /// // On other platforms, Web.ClearAllData() can be called while the browser process is running.
  579. /// Web.ClearAllData();
  580. /// #endif
  581. /// }
  582. /// </code>
  583. /// </example>
  584. public static Task TerminateBrowserProcess() {
  585. if (_terminationTaskSource != null) {
  586. return _terminationTaskSource.Task;
  587. }
  588. var processWasRunning = WebView_terminateBrowserProcess();
  589. if (!processWasRunning) {
  590. return Task.FromResult(true);
  591. }
  592. _terminationTaskSource = new TaskCompletionSource<bool>();
  593. return _terminationTaskSource.Task;
  594. }
  595. #region Non-public members
  596. EventHandler<AuthRequestedEventArgs> _authRequestedHandler;
  597. static string _cachePathOverride;
  598. event EventHandler<EventArgs<string>> _cursorTypeChanged;
  599. EventHandler<FileSelectionEventArgs> _fileSelectionHandler;
  600. static Dictionary<string, Action<Cookie[]>> _pendingGetCookiesResultCallbacks = new Dictionary<string, Action<Cookie[]>>();
  601. static Dictionary<string, Action<bool>> _pendingModifyCookiesResultCallbacks = new Dictionary<string, Action<bool>>();
  602. static TaskCompletionSource<bool> _terminationTaskSource;
  603. static Task<bool> _deleteCookies(string url = null, string cookieName = null) {
  604. var taskSource = new TaskCompletionSource<bool>();
  605. var resultCallbackId = Guid.NewGuid().ToString();
  606. _pendingModifyCookiesResultCallbacks[resultCallbackId] = taskSource.SetResult;
  607. WebView_deleteCookies(url, cookieName, resultCallbackId);
  608. return taskSource.Task;
  609. }
  610. protected static string _getCachePath() {
  611. return _cachePathOverride ?? Path.Combine(Application.persistentDataPath, "Vuplex.WebView", "chromium-cache");
  612. }
  613. // Invoked by the native plugin.
  614. void HandleAuthRequested(string host) {
  615. if (_authRequestedHandler == null) {
  616. // This shouldn't happen.
  617. WebViewLogger.LogWarning("The native webview sent an auth request, but no event handler is attached to AuthRequested.");
  618. WebView_cancelAuth(_nativeWebViewPtr);
  619. return;
  620. }
  621. var eventArgs = new AuthRequestedEventArgs(
  622. host,
  623. (username, password) => WebView_continueAuth(_nativeWebViewPtr, username, password),
  624. () => WebView_cancelAuth(_nativeWebViewPtr)
  625. );
  626. _authRequestedHandler?.Invoke(this, eventArgs);
  627. }
  628. // Invoked by the native plugin.
  629. void HandleCursorTypeChanged(string type) => _cursorTypeChanged?.Invoke(this, new EventArgs<string>(type));
  630. // Invoked by the native plugin.
  631. void HandleDownloadProgressChanged(string serializedMessage) {
  632. DownloadProgressChanged?.Invoke(this, DownloadMessage.FromJson(serializedMessage).ToEventArgs());
  633. }
  634. // Invoked by the native plugin.
  635. void HandleFileSelectionRequested(string serializedMessage) {
  636. var message = FileSelectionMessage.FromJson(serializedMessage);
  637. Action<string[]> continueCallback = (filePaths) => {
  638. var serializedFilePaths = JsonUtility.ToJson(new JsonArrayWrapper<string>(filePaths));
  639. WebView_continueFileSelection(_nativeWebViewPtr, serializedFilePaths);
  640. };
  641. Action cancelCallback = () => WebView_cancelFileSelection(_nativeWebViewPtr);
  642. var eventArgs = new FileSelectionEventArgs(
  643. message.AcceptFilters,
  644. message.MultipleAllowed,
  645. continueCallback,
  646. cancelCallback
  647. );
  648. _fileSelectionHandler(this, eventArgs);
  649. }
  650. [AOT.MonoPInvokeCallback(typeof(Action<string, string>))]
  651. static void _handleGetCookiesResult(string resultCallbackId, string serializedCookies) {
  652. var callback = _pendingGetCookiesResultCallbacks[resultCallbackId];
  653. _pendingGetCookiesResultCallbacks.Remove(resultCallbackId);
  654. var cookies = Cookie.ArrayFromJson(serializedCookies);
  655. callback(cookies);
  656. }
  657. // Invoked by the native plugin.
  658. void HandlePopup(string message) {
  659. if (PopupRequested == null) {
  660. return;
  661. }
  662. var components = message.Split(new char[] { ',' }, 2);
  663. var url = components[0];
  664. var popupBrowserId = components[1];
  665. if (popupBrowserId.Length == 0) {
  666. PopupRequested?.Invoke(this, new PopupRequestedEventArgs(url, null));
  667. return;
  668. }
  669. var popupWebView = _instantiate();
  670. Dispatcher.RunOnMainThread(async () => {
  671. await popupWebView._initPopup(Size.x, Size.y, PixelDensity, popupBrowserId);
  672. PopupRequested?.Invoke(this, new PopupRequestedEventArgs(url, popupWebView as IWebView));
  673. });
  674. }
  675. [AOT.MonoPInvokeCallback(typeof(Action<string, bool>))]
  676. static void _handleModifyCookiesResult(string resultCallbackId, bool success) {
  677. var callback = _pendingModifyCookiesResultCallbacks[resultCallbackId];
  678. _pendingModifyCookiesResultCallbacks.Remove(resultCallbackId);
  679. callback?.Invoke(success);
  680. }
  681. [AOT.MonoPInvokeCallback(typeof(Action))]
  682. static void _handleTerminationFinished() {
  683. if (_terminationTaskSource != null) {
  684. _terminationTaskSource.SetResult(true);
  685. _terminationTaskSource = null;
  686. }
  687. }
  688. async Task _initPopup(int width, int height, float pixelDensity, string popupId) {
  689. await _initBase(width, height, asyncInit: true);
  690. PixelDensity = pixelDensity;
  691. _nativeWebViewPtr = WebView_new(gameObject.name, width, height, PixelDensity, popupId);
  692. await _initTaskSource.Task;
  693. }
  694. [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
  695. static void _initializePlugin() {
  696. WebView_initializePlugin(
  697. Marshal.GetFunctionPointerForDelegate<Action>(_handleTerminationFinished),
  698. Marshal.GetFunctionPointerForDelegate<Action<string>>(_logInfo),
  699. Marshal.GetFunctionPointerForDelegate<Action<string>>(_logWarning),
  700. Marshal.GetFunctionPointerForDelegate<Action<string>>(_logError),
  701. Marshal.GetFunctionPointerForDelegate<Action<string, string, string>>(_unitySendMessage),
  702. Marshal.GetFunctionPointerForDelegate<Action<string, string>>(_handleGetCookiesResult),
  703. Marshal.GetFunctionPointerForDelegate<Action<string, bool>>(_handleModifyCookiesResult)
  704. );
  705. // cache, cookies, and storage are enabled by default
  706. _setCachePath(_getCachePath(), "_initializePlugin");
  707. }
  708. protected abstract StandaloneWebView _instantiate();
  709. [AOT.MonoPInvokeCallback(typeof(Action<string>))]
  710. static void _logInfo(string message) => WebViewLogger.Log(message, false);
  711. [AOT.MonoPInvokeCallback(typeof(Action<string>))]
  712. static void _logWarning(string message) => WebViewLogger.LogWarning(message, false);
  713. [AOT.MonoPInvokeCallback(typeof(Action<string>))]
  714. static void _logError(string message) => WebViewLogger.LogError(message, false);
  715. protected override void _resize() => WebView_resizeWithPixelDensity(_nativeWebViewPtr, Size.x, Size.y, PixelDensity);
  716. static void _throwAlreadyInitializedException(string methodName) {
  717. throw new InvalidOperationException($"Unable to execute {methodName}() because Chromium is already running. On Windows and macOS, {methodName}() can only be called before Chromium is started. The easiest way to resolve this issue is by calling {methodName}() earlier in the application, like by calling it from Awake() instead of from Start(). Alternatively, you can manually terminate Chromium prior calling the method by using StandaloneWebView.TerminateBrowserProcess(): https://developer.vuplex.com/webview/StandaloneWebView#TerminateBrowserProcess");
  718. }
  719. // Partial methods implemented by other 3D WebView packages
  720. // to provide platform-specific warnings in the editor.
  721. partial void OnCanGoBack();
  722. partial void OnCanGoForward();
  723. partial void OnCaptureScreenshot();
  724. partial void OnCopy();
  725. partial void OnCut();
  726. partial void OnExecuteJavaScript();
  727. partial void OnGetRawTextureData();
  728. partial void OnGoBack();
  729. partial void OnGoForward();
  730. partial void OnLoadHtml();
  731. partial void OnLoadUrl(string url);
  732. partial void OnPaste();
  733. void _pointerDown(Vector2 normalizedPoint, MouseButton mouseButton, int clickCount) {
  734. _assertValidState();
  735. var pixelsPoint = _convertNormalizedToPixels(normalizedPoint);
  736. WebView_pointerDown(_nativeWebViewPtr, pixelsPoint.x, pixelsPoint.y, (int)mouseButton, clickCount);
  737. }
  738. void _pointerUp(Vector2 normalizedPoint, MouseButton mouseButton, int clickCount) {
  739. _assertValidState();
  740. var pixelsPoint = _convertNormalizedToPixels(normalizedPoint);
  741. WebView_pointerUp(_nativeWebViewPtr, pixelsPoint.x, pixelsPoint.y, (int)mouseButton, clickCount);
  742. }
  743. static void _setCachePath(string path, string methodName) {
  744. var cachePath = path ?? "";
  745. var success = WebView_setCachePath(cachePath);
  746. if (!success) {
  747. _throwAlreadyInitializedException(methodName);
  748. }
  749. }
  750. [AOT.MonoPInvokeCallback(typeof(Action<string, string, string>))]
  751. static void _unitySendMessage(string gameObjectName, string methodName, string message) {
  752. Dispatcher.RunOnMainThread(() => {
  753. var gameObj = GameObject.Find(gameObjectName);
  754. if (gameObj == null) {
  755. WebViewLogger.LogWarning($"Unable to deliver a message from the native plugin to a webview GameObject because there is no longer a GameObject named '{gameObjectName}'. This can sometimes happen directly after destroying a webview. In that case, it is benign and this message can be ignored.");
  756. return;
  757. }
  758. gameObj.SendMessage(methodName, message);
  759. });
  760. }
  761. [DllImport(_dllName)]
  762. static extern bool WebView_browserProcessIsRunning();
  763. [DllImport(_dllName)]
  764. static extern void WebView_cancelAuth(IntPtr webViewPtr);
  765. [DllImport(_dllName)]
  766. static extern void WebView_cancelFileSelection(IntPtr webViewPtr);
  767. [DllImport(_dllName)]
  768. static extern void WebView_continueAuth(IntPtr webViewPtr, string username, string password);
  769. [DllImport(_dllName)]
  770. static extern void WebView_continueFileSelection(IntPtr webViewPtr, string serializedFilePaths);
  771. [DllImport(_dllName)]
  772. static extern void WebView_copy(IntPtr webViewPtr);
  773. [DllImport(_dllName)]
  774. static extern void WebView_cut(IntPtr webViewPtr);
  775. [DllImport(_dllName)]
  776. static extern void WebView_deleteCookies(string url, string cookieName, string resultCallbackId);
  777. [DllImport(_dllName)]
  778. static extern bool WebView_enableRemoteDebugging(int portNumber);
  779. [DllImport(_dllName)]
  780. static extern void WebView_getCookies(string url, string cookieName, string resultCallbackId);
  781. [DllImport(_dllName)]
  782. static extern bool WebView_globallySetUserAgentToMobile(bool mobile);
  783. [DllImport(_dllName)]
  784. static extern bool WebView_globallySetUserAgent(string userAgent);
  785. [DllImport(_dllName)]
  786. static extern void WebView_initializePlugin(
  787. IntPtr terminationFinishedCallback,
  788. IntPtr logInfoFunction,
  789. IntPtr logWarningFunction,
  790. IntPtr logErrorFunction,
  791. IntPtr unitySendMessageFunction,
  792. IntPtr getCookiesCallback,
  793. IntPtr modifyCookiesCallback
  794. );
  795. [DllImport(_dllName)]
  796. static extern void WebView_keyDown(IntPtr webViewPtr, string key, int modifiers);
  797. [DllImport(_dllName)]
  798. static extern void WebView_keyUp(IntPtr webViewPtr, string key, int modifiers);
  799. [DllImport (_dllName)]
  800. static extern void WebView_movePointer(IntPtr webViewPtr, int x, int y);
  801. [DllImport(_dllName)]
  802. static extern IntPtr WebView_new(string gameObjectName, int width, int height, float pixelDensity, string popupBrowserId);
  803. [DllImport(_dllName)]
  804. static extern void WebView_paste(IntPtr webViewPtr);
  805. [DllImport (_dllName)]
  806. static extern void WebView_pointerDown(IntPtr webViewPtr, int x, int y, int mouseButton, int clickCount);
  807. [DllImport (_dllName)]
  808. static extern void WebView_pointerUp(IntPtr webViewPtr, int x, int y, int mouseButton, int clickCount);
  809. [DllImport(_dllName)]
  810. protected static extern void WebView_resizeWithPixelDensity(IntPtr webViewPtr, int width, int height, float pixelDensity);
  811. [DllImport(_dllName)]
  812. static extern void WebView_selectAll(IntPtr webViewPtr);
  813. [DllImport (_dllName)]
  814. static extern void WebView_sendTouchEvent(
  815. IntPtr webViewPtr,
  816. int touchID,
  817. int type,
  818. float pointX,
  819. float pointY,
  820. float radiusX,
  821. float radiusY,
  822. float rotationAngle,
  823. float pressure
  824. );
  825. [DllImport(_dllName)]
  826. static extern void WebView_setAudioMuted(IntPtr webViewPtr, bool muted);
  827. [DllImport (_dllName)]
  828. static extern void WebView_setAuthEnabled(IntPtr webViewPtr, bool enabled);
  829. [DllImport(_dllName)]
  830. static extern bool WebView_setAutoplayEnabled(bool enabled);
  831. [DllImport(_dllName)]
  832. static extern bool WebView_setCachePath(string cachePath);
  833. [DllImport(_dllName)]
  834. static extern bool WebView_setCameraAndMicrophoneEnabled(bool enabled);
  835. [DllImport(_dllName)]
  836. static extern bool WebView_setChromiumLogLevel(int level);
  837. [DllImport(_dllName)]
  838. static extern bool WebView_setCommandLineArguments(string args);
  839. [DllImport(_dllName)]
  840. static extern void WebView_setCookie(string serializedCookie, string resultCallbackId);
  841. [DllImport (_dllName)]
  842. static extern void WebView_setCursorTypeEventsEnabled(IntPtr webViewPtr, bool enabled);
  843. [DllImport (_dllName)]
  844. static extern void WebView_setDownloadsEnabled(IntPtr webViewPtr, string downloadsDirectoryPath);
  845. [DllImport (_dllName)]
  846. static extern void WebView_setFileSelectionEnabled(IntPtr webViewPtr, bool enabled);
  847. [DllImport(_dllName)]
  848. static extern bool WebView_setIgnoreCertificateErrors(bool ignore);
  849. [DllImport(_dllName)]
  850. static extern void WebView_setNativeFileDialogEnabled(IntPtr webViewPtr, bool enabled);
  851. [DllImport(_dllName)]
  852. static extern void WebView_setNativeScriptDialogEnabled(IntPtr webViewPtr, bool enabled);
  853. [DllImport(_dllName)]
  854. static extern void WebView_setPopupMode(IntPtr webViewPtr, int popupMode);
  855. [DllImport(_dllName)]
  856. static extern bool WebView_setScreenSharingEnabled(bool enabled);
  857. [DllImport(_dllName)]
  858. static extern bool WebView_setTargetFrameRate(uint targetFrameRate);
  859. [DllImport(_dllName)]
  860. static extern void WebView_setZoomLevel(IntPtr webViewPtr, float zoomLevel);
  861. [DllImport(_dllName)]
  862. static extern bool WebView_terminateBrowserProcess();
  863. #endregion
  864. #region Obsolete APIs
  865. // Added in v3.16, deprecated in v4.1.
  866. [Obsolete("StandaloneWebView.DispatchTouchEvent() has been renamed to SendTouchEvent(). Please switch to using IWithTouch.SendTouchEvent(): https://developer.vuplex.com/webview/IWithTouch")]
  867. public void DispatchTouchEvent(TouchEvent touchEvent) => SendTouchEvent(touchEvent);
  868. // Added in v3.9, deprecated in v4.0.
  869. [Obsolete("StandaloneWebView.GetCookie() is now deprecated. Please switch to using Web.CookieManager.GetCookies(): https://developer.vuplex.com/webview/CookieManager#GetCookies")]
  870. public static async void GetCookie(string url, string cookieName, Action<Cookie> callback) {
  871. var cookie = await GetCookie(url, cookieName);
  872. callback(cookie);
  873. }
  874. // Added in v3.9, deprecated in v4.0.
  875. [Obsolete("StandaloneWebView.GetCookie() is now deprecated. Please switch to Web.CookieManager.GetCookies(): https://developer.vuplex.com/webview/CookieManager#GetCookies")]
  876. public static async Task<Cookie> GetCookie(string url, string cookieName) {
  877. var cookies = await GetCookies(url, cookieName);
  878. return cookies.Length > 0 ? cookies[0] : null;
  879. }
  880. // Added in v3.11, deprecated in v4.0.
  881. [Obsolete("StandaloneWebView.GetCookies(url, callback) is now deprecated. Please switch to Web.CookieManager.GetCookies(): https://developer.vuplex.com/webview/CookieManager#GetCookies")]
  882. public static async void GetCookies(string url, Action<Cookie[]> callback) {
  883. var cookies = await GetCookies(url);
  884. callback(cookies);
  885. }
  886. // Added in v3.3, deprecated in v4.0.
  887. [Obsolete("StandaloneWebView.SetAudioAndVideoCaptureEnabled() is now deprecated. Please switch to Web.SetCameraAndMicrophoneEnabled(): https://developer.vuplex.com/webview/Web#SetCameraAndMicrophoneEnabled")]
  888. public static void SetAudioAndVideoCaptureEnabled(bool enabled) => SetCameraAndMicrophoneEnabled(enabled);
  889. // Added in v3.10, deprecated in v4.0.
  890. [Obsolete("StandaloneWebView.SetCookie(cookie, callback) is now deprecated. Please switch to Web.CookieManager.SetCookie(): https://developer.vuplex.com/webview/CookieManager#SetCookie")]
  891. public static async void SetCookie(Cookie cookie, Action<bool> callback) {
  892. var result = await SetCookie(cookie);
  893. callback(result);
  894. }
  895. // Deprecated in v4.2.
  896. [Obsolete("StandaloneWebView.TerminatePlugin() has been replaced with StandaloneWebView.TerminateBrowserProcess(). Please switch to TerminateBrowserProcess().")]
  897. public static void TerminatePlugin() => TerminateBrowserProcess();
  898. #endregion
  899. }
  900. }
  901. #endif