using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using System; using System.Collections; using System.Diagnostics; using System.IO; using System.Threading; using UnityEngine; using UnityEngine.Networking; namespace bessw.Unity.WebView.ChromeDevTools { public class Browser { /* singleton */ private static Browser instance; private Process browserProcess; /* browser settings */ public static string browserExecutablePath = "chrome"; public static bool headless = true; private const int debugPort = 9222; /// <summary> /// Json serializer for the devtools connection /// </summary> public static JsonSerializer devtoolsSerializer; /// <summary> /// Json serializer settings for the devtools connection /// </summary> public static JsonSerializerSettings devtoolsSerializerSettings; public static CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); /** * get singleton instance */ public static Browser getInstance() { if (instance == null || instance.browserProcess.HasExited) { instance = new Browser(); instance.launchBrowser(); } return instance; } /** * constructor for the static fields */ static Browser() { // initialize the JsonSerializer devtoolsSerializerSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver(), }; devtoolsSerializer = JsonSerializer.Create(devtoolsSerializerSettings); } /** * Launch headless chrome browser with remote-debugging enabled, if not already running. */ private void launchBrowser() { // allow only one instance of chrome if (browserProcess == null || browserProcess.HasExited) { browserProcess = new Process(); browserProcess.StartInfo.FileName = browserExecutablePath; browserProcess.StartInfo.Arguments = String.Join(" ", new []{ $"--user-data-dir={Path.Join(Application.temporaryCachePath, "BrowserView")}", $"--remote-debugging-port={debugPort}", $"--remote-allow-origins=http://localhost:{debugPort}", "--hide-crash-restore-bubble", "--disable-first-run-ui", "--no-first-run" }); // set headlessBrowser to false to see the browser window if (headless) { browserProcess.StartInfo.Arguments = string.Concat(browserProcess.StartInfo.Arguments, " --headless=new"); } else { browserProcess.StartInfo.WindowStyle = ProcessWindowStyle.Minimized; } // register an error handler browserProcess.ErrorDataReceived += (sender, e) => UnityEngine.Debug.LogError($"Browser Error: {e.Data} ExitCode: {browserProcess.ExitCode}"); browserProcess.Exited += (sender, e) => UnityEngine.Debug.LogError($"Browser Exited, ExitCode: {browserProcess.ExitCode}"); browserProcess.Start(); if (browserProcess.HasExited) { UnityEngine.Debug.LogError("Failed to start browser"); } UnityEngine.Debug.Log($"launched '{browserProcess.StartInfo.FileName} {browserProcess.StartInfo.Arguments}'"); } } /** * send web request to the devTools API */ private IEnumerator DevToolsApiRequest(bool isPUT, string apiAddress, Action<string> callback) { UnityEngine.Debug.Log($"DevTools api Request: {apiAddress}"); UnityWebRequest webRequest; if (isPUT) { webRequest = UnityWebRequest.Put($"http://localhost:{debugPort}{apiAddress}", ""); } else { webRequest = UnityWebRequest.Get($"http://localhost:{debugPort}{apiAddress}"); } yield return webRequest.SendWebRequest(); if (webRequest.result != UnityWebRequest.Result.Success) { UnityEngine.Debug.LogError(webRequest.error); //TODO: handle error } else { UnityEngine.Debug.Log($"DevTools api response (for {apiAddress}):\n{webRequest.downloadHandler.text}"); callback(webRequest.downloadHandler.text); } } public IEnumerator OpenNewTab(string targetUrl, Action<BrowserTab> callback) { yield return DevToolsApiRequest(true, $"/json/new?{targetUrl}", (response) => { PageTargetInfo pageTarget = JsonConvert.DeserializeObject<PageTargetInfo>(response, devtoolsSerializerSettings); callback(new BrowserTab(pageTarget)); }); } /** * Not implemented. */ [Obsolete("Not implemented.", true)] private IEnumerator GetOpenTabs() { yield return DevToolsApiRequest(false, "/json/list", (response) => { UnityEngine.Debug.Log($"Currently open tabs:\n{response}"); }); } /** * Close the browser */ public void Close() { cancellationTokenSource.Cancel(); if (browserProcess != null && !browserProcess.HasExited) { browserProcess.Kill(); } } } }