using System; using System.Collections.Generic; using System.Runtime.Serialization; using AOT; using UnityEngine; namespace ZenFulcrum.EmbeddedBrowser { /// /// Callbacks used by Browser.cs /// Man, that file's getting big. (Breaking it up would likely involve backwards-incompatible API changes, so /// let it be for now.) /// public partial class Browser { /// /// Map of browserId => Browser fro looking up C# objects when we get a native callback. /// /// Note that this reflects the native state, that is, we include Browsers that havne't fully initialized yet, /// but instead list what we need to lookup callbacks coming from the native side. /// /// (We used to rely on closures and generated trampolines for this data, but il2cpp doens't support that.) /// internal static Dictionary allBrowsers = new Dictionary(); private static Browser GetBrowser(int browserId) { lock (allBrowsers) { Browser ret; if (allBrowsers.TryGetValue(browserId, out ret)) return ret; } Debug.LogWarning("Got a callback for brower id " + browserId + " which doesn't exist."); return null; } [MonoPInvokeCallback(typeof(BrowserNative.ForwardJSCallFunc))] private static void CB_ForwardJSCallFunc(int browserId, int callbackId, string data, int size) { var browser = GetBrowser(browserId); if (!browser) return; lock (browser.thingsToDo) browser.thingsToDo.Add(() => { JSResultFunc cb; if (!browser.registeredCallbacks.TryGetValue(callbackId, out cb)) { Debug.LogWarning("Got a JS callback for event " + callbackId + ", but no such event is registered."); return; } var isError = false; if (data.StartsWith("fail-")) { isError = true; data = data.Substring(5); } JSONNode node; try { node = JSONNode.Parse(data); } catch (SerializationException) { Debug.LogWarning("Invalid JSON sent from browser: " + data); return; } try { cb(node, isError); } catch (Exception ex) { //user's function died, log it and carry on Debug.LogException(ex); return; } }); } [MonoPInvokeCallback(typeof(BrowserNative.ChangeFunc))] private static void CB_ChangeFunc(int browserId, BrowserNative.ChangeType changeType, string arg1) { //Note: we may have been Object.Destroy'd at this point, so guard against that. //That means we can't trust that `browser == null` means we don't have a browser, we may have an object that //is destroyed, but not actually null. Browser browser; lock (allBrowsers) { if (!allBrowsers.TryGetValue(browserId, out browser)) return; } if (changeType == BrowserNative.ChangeType.CHT_BROWSER_CLOSE) { //We can't continue if the browser is closed, so goodbye. //At this point, we may or may not be destroyed, but if not, become destroyed. //Debug.Log("Got close notification for " + unsafeBrowserId); if (browser) { //Need to be destroyed. lock (browser.thingsToDo) browser.thingsToDo.Add(() => { Destroy(browser.gameObject); }); } else { //If we are (Unity) destroyed, we won't get another update, so we can't rely on thingsToDo //That said, there's not anything else for us to do but step out of allThingsToRemember. } //The native side has acknowledged it's done, now we can finally let the native trampolines be GC'd lock (allThingsToRemember) allThingsToRemember.Remove(browser.unsafeBrowserId); //Now that we know we can allow ourselves to be GC'd and destroyed (without leaking to allThingsToRemember) //go ahead and allow it. (And we won't get any more native callbacks at this point.) lock (allBrowsers) allBrowsers.Remove(browserId); //Just in case someone tries to call something, make sure CheckSanity and such fail. browser.browserId = 0; } else if (browser) { lock (browser.thingsToDo) browser.thingsToDo.Add(() => browser.OnItemChange(changeType, arg1)); } } [MonoPInvokeCallback(typeof(BrowserNative.DisplayDialogFunc))] private static void CB_DisplayDialogFunc( int browserId, BrowserNative.DialogType dialogType, IntPtr textPtr, IntPtr promptTextPtr, IntPtr sourceURL ) { var browser = GetBrowser(browserId); if (!browser) return; var text = Util.PtrToStringUTF8(textPtr); var promptText = Util.PtrToStringUTF8(promptTextPtr); //var url = Util.PtrToStringUTF8(urlPtr); lock (browser.thingsToDo) browser.thingsToDo.Add(() => { browser.CreateDialogHandler(); browser.dialogHandler.HandleDialog(dialogType, text, promptText); }); } [MonoPInvokeCallback(typeof(BrowserNative.ShowContextMenuFunc))] private static void CB_ShowContextMenuFunc( int browserId, string json, int x, int y, BrowserNative.ContextMenuOrigin origin ) { var browser = GetBrowser(browserId); if (!browser) return; if (json != null && (browser.allowContextMenuOn & origin) == 0) { //ignore this BrowserNative.zfb_sendContextMenuResults(browserId, -1); return; } lock (browser.thingsToDo) browser.thingsToDo.Add(() => { if (json != null) browser.CreateDialogHandler(); if (browser.dialogHandler != null) browser.dialogHandler.HandleContextMenu(json, x, y); }); } [MonoPInvokeCallback(typeof(BrowserNative.ConsoleFunc))] private static void CB_ConsoleFunc(int browserId, string message, string source, int line) { var browser = GetBrowser(browserId); if (!browser) return; lock (browser.thingsToDo) browser.thingsToDo.Add(() => { browser.onConsoleMessage(message, source + ":" + line); }); } [MonoPInvokeCallback(typeof(BrowserNative.ReadyFunc))] private static void CB_ReadyFunc(int browserId) { var browser = GetBrowser(browserId); if (!browser) return; //We could be on any thread at this time, so schedule the callbacks to fire during the next InputUpdate lock (browser.thingsToDo) browser.thingsToDo.Add(() => { browser.browserId = browserId; // ReSharper disable once PossibleNullReferenceException browser.onNativeReady(browserId); }); } [MonoPInvokeCallback(typeof(BrowserNative.NavStateFunc))] private static void CB_NavStateFunc(int browserId, bool canGoBack, bool canGoForward, bool lodaing, IntPtr urlRaw) { var browser = GetBrowser(browserId); if (!browser) return; var url = Util.PtrToStringUTF8(urlRaw); lock (browser.thingsToDo) browser.thingsToDo.Add(() => { browser.navState.canGoBack = canGoBack; browser.navState.canGoForward = canGoForward; browser.navState.loading = lodaing; browser.navState.url = url; browser._url = url;//update the inspector browser.onNavStateChange(); }); } [MonoPInvokeCallback(typeof(BrowserNative.NewWindowFunc))] private static void CB_NewWindowFunc(int creatorBrowserId, int newBrowserId, IntPtr urlPtr) { var browser = GetBrowser(creatorBrowserId); if (!browser) return; #if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX var url = Util.PtrToStringUTF8(urlPtr); if (url == "about:inspector" || browser.newWindowAction == NewWindowAction.NewWindow) lock (browser.thingsToDo) { browser.thingsToDo.Add(() => { PopUpBrowser.Create(newBrowserId); }); return; } #endif lock (browser.thingsToDo) { browser.thingsToDo.Add(() => { var newBrowser = browser.newWindowHandler.CreateBrowser(browser); newBrowser.RequestNativeBrowser(newBrowserId); }); } } } }