Beispiel #1
0
        private void HandleKeyInput()
        {
            List <Event> keyEvents = browser.UIHandler.KeyEvents;

            if (keyEvents.Count == 0)
            {
                return;
            }
            foreach (Event item in keyEvents)
            {
                int windowsKeyCode = KeyMappings.GetWindowsKeyCode(item);
                if (item.character == '\n')
                {
                    item.character = '\r';
                }
                if (item.character != 0 && item.type == EventType.KeyDown)
                {
                    BrowserNative.zfb_characterEvent(browser.browserId, item.character, windowsKeyCode);
                }
                else
                {
                    BrowserNative.zfb_keyEvent(browser.browserId, item.type == EventType.KeyDown, windowsKeyCode);
                }
            }
        }
Beispiel #2
0
        /**
         * Returns a list of all cookies in the browser across all domains.
         *
         * Note that cookies are shared between browser instances.
         *
         * If the browser is not ready yet (browser.IsReady or WhenReady()) this will return an empty list.
         */
        public IPromise <List <Cookie> > GetCookies()
        {
            Cookie.Init();

            var ret = new List <Cookie>();

            if (!browser.IsReady || !browser.enabled)
            {
                return(Promise <List <Cookie> > .Resolved(ret));
            }
            var promise = new Promise <List <Cookie> >();

            BrowserNative.GetCookieFunc cookieFunc = cookie => {
                try {
                    if (cookie == null)
                    {
                        browser.RunOnMainThread(() => promise.Resolve(ret));
                        cookieFuncs.Remove(promise);
                        return;
                    }

                    ret.Add(new Cookie(this, cookie));
                } catch (Exception ex) {
                    Debug.LogException(ex);
                }
            };

            BrowserNative.zfb_getCookies(browser.browserId, cookieFunc);

            cookieFuncs[promise] = cookieFunc;

            return(promise);
        }
Beispiel #3
0
        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);
                    }
                });
        }
Beispiel #4
0
        /**
         * Returns a user agent that, hopefully, tricks legacy, stupid, non-feature-detection websites
         * into giving us their actual content.
         *
         * If you change this, the Editor will usually need to be restarted for changes to take effect.
         */
        public static string GetUserAgent()
        {
            if (agentOverride != null)
            {
                return(agentOverride);
            }

            var chromeVersion = Marshal.PtrToStringAnsi(BrowserNative.zfb_getVersion());

            //(Note: I don't care what version of the OS you have, we're telling the website you have this
            //version so you get a good site.)
            string osStr = "Windows NT 6.1";

#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
            osStr = "Macintosh; Intel Mac OS X 10_7_0";
#elif UNITY_EDITOR_LINUX || UNITY_STANDALONE_LINUX
            osStr = "X11; Linux x86_64";
#endif

            var userAgent =
                "Mozilla/5.0 " +
                "(" + osStr + "; Unity 3D; ZFBrowser 2.0.0; " + Application.productName + " " + Application.version + ") " +
                "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + chromeVersion + " Safari/537.36"
            ;

            //Chromium has issues with non-ASCII user agents.
            userAgent = Regex.Replace(userAgent, @"[^\u0020-\u007E]", "?");

            return(userAgent);
        }
        private static void CheckForEditorExit()
        {
            //Read off the last bit of log to see if we are going to reload or exit.
            //NB: Doing Debug.Log in this function before the read will result in a read of that log instead.
            const string exitString = "Cleanup mono";
            const int    readBack   = 500;
            var          buffer     = new byte[readBack];

            try {
                using (var file = File.Open(logLocation, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)) {
                    file.Seek(-readBack, SeekOrigin.End);

                    var len = file.Read(buffer, 0, readBack);

                    var readStr = System.Text.Encoding.UTF8.GetString(buffer, 0, len);
//				Debug.Log("len " + len + " readstr " + readStr);

                    if (readStr.Contains(exitString))
                    {
                        Debug.Log("Editor shutting down, also stopping ZFBrowser");


                        BrowserNative.UnloadNative();
                    }
                }
            } catch (Exception ex) {
                Debug.LogError("Failed to check for shutdown: " + ex);
            }
        }
Beispiel #6
0
        public void HandleInput()
        {
            browser.UIHandler.InputUpdate();

            if (browser.UIHandler.MouseHasFocus)
            {
                HandleMouseInput();
            }

            if (browser.UIHandler.KeyboardHasFocus)
            {
                if (!wasFocused)
                {
                    BrowserNative.zfb_setFocused(browser.browserId, wasFocused = true);
                }
                HandleKeyInput();
            }
            else
            {
                if (wasFocused)
                {
                    BrowserNative.zfb_setFocused(browser.browserId, wasFocused = false);
                }
            }
        }
        /// <summary>
        /// Feeds scroll events to the browser.
        /// In particular, it will clump together scrolling "floods" into fewer larger scrolls
        /// to prevent the backend from getting choked up and taking forever to execute the requests.
        /// </summary>
        /// <param name="mouseScroll"></param>
        private void FeedScrolling(Vector2 mouseScroll, float scrollSpeed)
        {
            accumulatedScroll += mouseScroll * scrollSpeed;

            if (accumulatedScroll.sqrMagnitude != 0 && Time.realtimeSinceStartup > lastScrollEvent + maxScrollEventRate)
            {
                //Debug.Log("Do scroll: " + accumulatedScroll);

                //The backend seems to have trouble coping with horizontal AND vertical scroll. So only do one at a time.
                //(And if we do both at once, vertical appears to get priority and horizontal gets ignored.)

                if (Mathf.Abs(accumulatedScroll.x) > Mathf.Abs(accumulatedScroll.y))
                {
                    BrowserNative.zfb_mouseScroll(browser.browserId, (int)accumulatedScroll.x, 0);
                    accumulatedScroll.x = 0;
                    accumulatedScroll.y = Mathf.Round(accumulatedScroll.y * .5f);            //reduce the thing we weren't doing so it's less likely to accumulate strange
                }
                else
                {
                    BrowserNative.zfb_mouseScroll(browser.browserId, 0, (int)accumulatedScroll.y);
                    accumulatedScroll.x = Mathf.Round(accumulatedScroll.x * .5f);
                    accumulatedScroll.y = 0;
                }

                lastScrollEvent = Time.realtimeSinceStartup;
            }
        }
Beispiel #8
0
        protected void LateUpdate()
        {
            //Note: we use LateUpdate here in hopes that commands issued during (anybody's) Update()
            //will have a better chance of being completed before we push the render

            if (lastUpdateFrame != Time.frameCount)
            {
                UnityEngine.Profiling.Profiler.BeginSample("Browser.NativeTick");
                BrowserNative.zfb_tick();
                UnityEngine.Profiling.Profiler.EndSample();
                lastUpdateFrame = Time.frameCount;
            }

            ProcessCallbacks();

            if (pageReplacer != null)
            {
                pageReplacer();
                pageReplacer = null;
            }

            if (browserId == 0)
            {
                return;                    //not ready yet or not loaded
            }
            if (EnableRendering)
            {
                Render();
            }
        }
Beispiel #9
0
        /// <summary>
        /// Sends headers, status code, content-type, etc. for a request.
        /// </summary>
        /// <param name="id"></param>
        /// <param name="pre"></param>
        protected void SendPreamble(int id, ResponsePreamble pre)
        {
            var headers = new JSONNode(JSONNode.NodeType.Object);

            if (pre.headers != null)
            {
                foreach (var kvp in pre.headers)
                {
                    headers[kvp.Key] = kvp.Value;
                }
            }

            if (pre.statusText == null)
            {
                if (!statusTexts.TryGetValue(pre.statusCode, out pre.statusText))
                {
                    pre.statusText = statusTexts[-1];
                }
            }
            headers[":status:"]     = pre.statusCode.ToString();
            headers[":statusText:"] = pre.statusText;
            headers["Content-Type"] = pre.mimeType;

            //Debug.Log("response headers " + headers.AsJSON);

            lock (BrowserNative.symbolsLock) {
                BrowserNative.zfb_sendRequestHeaders(id, pre.length, headers.AsJSON);
            }
        }
Beispiel #10
0
        /**
         * Adds the given browser as an overlay of this browser.
         *
         * The overlaid browser will appear transparently over the top of us on our texture.
         * {overlayBrowser} must not have an overlay and must be sized exactly the same as {this}.
         * Additionally, overlayBrowser.EnableRendering must be false. You still need to
         * do something to handle getting input to the right places. Overlays take a notable performance
         * hit on rendering (CPU alpha compositing).
         *
         * Overlays are used internally to implement context menus and pop-up dialogs (alert, onbeforeunload).
         * If the page causes any type of dialog, the overlay will be replaced.
         *
         * Overlays will be resized onto our texture when we are resized. The sizes must always match exactly.
         *
         * Remove the overlay (SetOverlay(null)) before closing either browser.
         *
         * (Note: though you can't set B as an overlay to A when B has an overlay, you can set
         * an overlay on B /while/ it is the overlay for A. For an example of this, try
         * right-clicking on the text area inside a prompt() popup. The context menu that
         * appears is an overlay to the overlay to the actual browser.)
         */
        public void SetOverlay(Browser overlayBrowser)
        {
            if (DeferUnready(() => SetOverlay(overlayBrowser)))
            {
                return;
            }
            if (overlayBrowser && overlayBrowser.DeferUnready(() => SetOverlay(overlayBrowser)))
            {
                return;
            }

            BrowserNative.zfb_setOverlay(browserId, overlayBrowser ? overlayBrowser.browserId : 0);
            overlay = overlayBrowser;

            if (!overlay)
            {
                return;
            }

            if (
                !overlay.Texture ||
                (overlay.Texture.width != Texture.width || overlay.Texture.height != Texture.height)
                )
            {
                overlay.Resize(Texture);
            }
        }
Beispiel #11
0
        /**
         * Returns a list of all cookies in the browser across all domains.
         *
         * Note that cookies are shared between browser instances.
         *
         * If the browser is not ready yet (browser.IsReady or WhenReady()) this will return an empty list.
         *
         * This method is not reentrant! You must wait for the returned promise to resolve before calling it again,
         * even on a differnet object.
         */
        public IPromise <List <Cookie> > GetCookies()
        {
            if (currentFetch != null)
            {
                //This method Wait for the previous promise to resolve, then make your call.
                //If this limitation actually affects you, let me know.
                throw new InvalidOperationException("GetCookies is not reentrant");
            }

            Cookie.Init();

            var result = new List <Cookie>();

            if (!browser.IsReady || !browser.enabled)
            {
                return(Promise <List <Cookie> > .Resolved(result));
            }
            var promise = new Promise <List <Cookie> >();

            BrowserNative.GetCookieFunc cookieFunc = CB_GetCookieFunc;
            BrowserNative.zfb_getCookies(browser.browserId, cookieFunc);

            currentFetch = new CookieFetch {
                promise  = promise,
                nativeCB = cookieFunc,
                manager  = this,
                result   = result,
            };

            return(promise);
        }
Beispiel #12
0
        protected void OnApplicationQuit()
        {
            //According to http://docs.unity3d.com/Manual/ExecutionOrder.html,
            //OnDisable will be called before this. Experience shows this to be not so much the case.
            //Therefore, we will forcefully call OnDestroy()
            OnDestroy();

            if (BrowserNative.zfb_numBrowsers() == 0)
            {
                //last one out, turn off the lights

                //beforeunload windows won't fully disappear without ticking the message loop
                //Ideally, we'd just keep ticking it, but we are stopping.
                for (int i = 0; i < 10; ++i)
                {
                    BrowserNative.zfb_tick();
                    System.Threading.Thread.Sleep(10);
                }


                        #if UNITY_EDITOR
                //You can't re-init CEF, so if we are the editor, never shut it down.
                        #else
                BrowserNative.UnloadNative();
                        #endif
            }
        }
Beispiel #13
0
 public void ClearAll()
 {
     if (!browser.DeferUnready(ClearAll))
     {
         BrowserNative.zfb_clearCookies(browser.browserId);
     }
 }
Beispiel #14
0
        /**
         * Loads the given HTML string as if it were the given URL.
         * Use http://-like porotocols or else things may not work right.
         *
         * Note that, instead of using this, you can also load "data:" URIs into this.Url.
         * This allows pretty much any type of content to be loaded as the whole page.
         */
        public void LoadHTML(string html, string url = null)
        {
            if (DeferUnready(() => LoadHTML(html, url)))
            {
                return;
            }

            //Debug.Log("Load HTML " + html);

            loadPending = true;

            if (string.IsNullOrEmpty(url))
            {
                url = LocalUrlPrefix + "custom";
            }

            if (string.IsNullOrEmpty(this.Url))
            {
                //Nothing will happen if we don't have an initial page, so cause one.
                this.Url     = "about:blank";
                skipNextLoad = true;
            }

            BrowserNative.zfb_goToHTML(browserId, html, url);
        }
Beispiel #15
0
 public void Delete()
 {
     if (original != null)
     {
         BrowserNative.zfb_editCookie(cookies.browser.browserId, original, BrowserNative.CookieAction.Delete);
         original = null;
     }
 }
Beispiel #16
0
 public void OnDestroy()
 {
     if (!BrowserNative.SymbolsLoaded)
     {
         return;
     }
     BrowserNative.zfb_windowClose(windowId);
 }
Beispiel #17
0
 public void Update()
 {
     if (!BrowserNative.SymbolsLoaded)
     {
         return;
     }
     BrowserNative.zfb_windowRender(windowId, browserId);
 }
Beispiel #18
0
        private void HandleMouseInput()
        {
            var handler  = browser.UIHandler;
            var mousePos = handler.MousePosition;

            // ReSharper disable CompareOfFloatsByEqualityOperator
            var currentButtons = handler.MouseButtons;
            var mouseScroll    = handler.MouseScroll;

            if (mousePos != prevPos)
            {
                BrowserNative.zfb_mouseMove(browser.browserId, mousePos.x, 1 - mousePos.y);
            }
            if (mouseScroll.sqrMagnitude != 0)
            {
                BrowserNative.zfb_mouseScroll(
                    browser.browserId,
                    (int)mouseScroll.x * handler.InputSettings.scrollSpeed, (int)mouseScroll.y * handler.InputSettings.scrollSpeed
                    );
            }
            // ReSharper restore CompareOfFloatsByEqualityOperator

            var leftChange   = (prevButtons & MouseButton.Left) != (currentButtons & MouseButton.Left);
            var leftDown     = (currentButtons & MouseButton.Left) == MouseButton.Left;
            var middleChange = (prevButtons & MouseButton.Middle) != (currentButtons & MouseButton.Middle);
            var middleDown   = (currentButtons & MouseButton.Middle) == MouseButton.Middle;
            var rightChange  = (prevButtons & MouseButton.Right) != (currentButtons & MouseButton.Right);
            var rightDown    = (currentButtons & MouseButton.Right) == MouseButton.Right;

            if (leftChange)
            {
                if (leftDown)
                {
                    leftClickHistory.ButtonPress(mousePos, handler, browser.Size);
                }
                BrowserNative.zfb_mouseButton(
                    browser.browserId, BrowserNative.MouseButton.MBT_LEFT, leftDown,
                    leftDown ? leftClickHistory.repeatCount : 0
                    );
            }
            if (middleChange)
            {
                //no double-clicks, to be consistent with other browsers
                BrowserNative.zfb_mouseButton(
                    browser.browserId, BrowserNative.MouseButton.MBT_MIDDLE, middleDown, 1
                    );
            }
            if (rightChange)
            {
                //no double-clicks, to be consistent with other browsers
                BrowserNative.zfb_mouseButton(
                    browser.browserId, BrowserNative.MouseButton.MBT_RIGHT, rightDown, 1
                    );
            }

            prevPos     = mousePos;
            prevButtons = currentButtons;
        }
Beispiel #19
0
        /**
         * Show the development tools for the current page.
         *
         * If {show} is false the dev tools will be hidden, if possible.
         */
        public void ShowDevTools(bool show = true)
        {
            if (DeferUnready(() => ShowDevTools(show)))
            {
                return;
            }

            BrowserNative.zfb_showDevTools(browserId, show);
        }
Beispiel #20
0
 public void Stop()
 {
     if (!IsReady)
     {
         return;
     }
     CheckSanity();
     BrowserNative.zfb_changeLoading(browserId, BrowserNative.LoadChange.LC_STOP);
 }
Beispiel #21
0
 public void GoBack()
 {
     if (!IsReady)
     {
         return;
     }
     CheckSanity();
     BrowserNative.zfb_doNav(browserId, -1);
 }
Beispiel #22
0
 public void GoForward()
 {
     if (!IsReady)
     {
         return;
     }
     CheckSanity();
     BrowserNative.zfb_doNav(browserId, 1);
 }
Beispiel #23
0
        /**
         * Sends a command such as "select all", "undo", or "copy"
         * to the currently focused frame in th browser.
         */
        public void SendFrameCommand(BrowserNative.FrameCommand command)
        {
            if (DeferUnready(() => SendFrameCommand(command)))
            {
                return;
            }

            BrowserNative.zfb_sendCommandToFocusedFrame(browserId, command);
        }
Beispiel #24
0
 public void Update()
 {
     if (original != null)
     {
         Delete();
     }
     original = new BrowserNative.NativeCookie();
     Copy(this, original);
     BrowserNative.zfb_editCookie(cookies.browser.browserId, original, BrowserNative.CookieAction.Create);
 }
Beispiel #25
0
        protected void Render()
        {
            CheckSanity();

            BrowserNative.RenderData renderData;

            UnityEngine.Profiling.Profiler.BeginSample("Browser.UpdateTexture.zfb_getImage", this);
            {
                renderData      = BrowserNative.zfb_getImage(browserId, forceNextRender);
                forceNextRender = false;

                if (renderData.pixels == IntPtr.Zero)
                {
                    return;                                          //no changes
                }
                if (renderData.w != texture.width || renderData.h != texture.height)
                {
                    //Mismatch, can happen, for example, when we resize and ask for a new image before the IPC layer gets back to us.
                    return;
                }

                if (pixelData == null || pixelData.Length != renderData.w * renderData.h)
                {
                    pixelData = new Color32[renderData.w * renderData.h];
                }
            }
            UnityEngine.Profiling.Profiler.EndSample();

            /*
             * Getting the frame data from CEF to the GPU is the biggest framerate bottleneck.
             *
             * This memcpy is unfortunate, and with more work we could just upload directly to the GPU texture from C++.
             * That said, the profiler tells us:
             *      - With mipmaps on, GPUUpload takes an order of magnitude more time than DataCopy
             *      - With mipmaps off, GPUUpload takes 1-2x the time DataCopy does.
             *      - On my machine with a quite large 2048x2048 texture constantly updating, both weigh in at about ~10ms/frame
             *        without mipmap generation.
             */

            UnityEngine.Profiling.Profiler.BeginSample("Browser.UpdateTexture.DataCopy", this);
            {
                GCHandle handle = GCHandle.Alloc(pixelData, GCHandleType.Pinned);
                BrowserNative.zfb_memcpy(handle.AddrOfPinnedObject(), renderData.pixels, pixelData.Length * 4);
                handle.Free();
            }
            UnityEngine.Profiling.Profiler.EndSample();

            UnityEngine.Profiling.Profiler.BeginSample("Browser.UpdateTexture.GPUUpload", this);
            {
                texture.SetPixels32(pixelData);
                texture.Apply(true);
            }
            UnityEngine.Profiling.Profiler.EndSample();
        }
Beispiel #26
0
        public static string GetUserAgent()
        {
            if (agentOverride != null)
            {
                return(agentOverride);
            }
            string text  = Marshal.PtrToStringAnsi(BrowserNative.zfb_getVersion());
            string text2 = "Windows NT 6.1";
            string input = "Mozilla/5.0 (" + text2 + "; Unity 3D; ZFBrowser 1.1.0; " + Application.productName + " " + Application.version + ") AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + text + " Safari/537.36";

            return(Regex.Replace(input, "[^\\u0020-\\u007E]", "?"));
        }
        private void HandleMouseInput()
        {
            var handler  = browser.UIHandler;
            var mousePos = handler.MousePosition;

            var currentButtons = handler.MouseButtons;
            var mouseScroll    = handler.MouseScroll;

            if (mousePos != prevPos)
            {
                BrowserNative.zfb_mouseMove(browser.browserId, mousePos.x, 1 - mousePos.y);
            }

            FeedScrolling(mouseScroll, handler.InputSettings.scrollSpeed);

            var leftChange   = (prevButtons & MouseButton.Left) != (currentButtons & MouseButton.Left);
            var leftDown     = (currentButtons & MouseButton.Left) == MouseButton.Left;
            var middleChange = (prevButtons & MouseButton.Middle) != (currentButtons & MouseButton.Middle);
            var middleDown   = (currentButtons & MouseButton.Middle) == MouseButton.Middle;
            var rightChange  = (prevButtons & MouseButton.Right) != (currentButtons & MouseButton.Right);
            var rightDown    = (currentButtons & MouseButton.Right) == MouseButton.Right;

            if (leftChange)
            {
                if (leftDown)
                {
                    leftClickHistory.ButtonPress(mousePos, handler, browser.Size);
                }
                BrowserNative.zfb_mouseButton(
                    browser.browserId, BrowserNative.MouseButton.MBT_LEFT, leftDown,
                    leftDown ? leftClickHistory.repeatCount : 0
                    );
            }
            if (middleChange)
            {
                //no double-clicks, to be consistent with other browsers
                BrowserNative.zfb_mouseButton(
                    browser.browserId, BrowserNative.MouseButton.MBT_MIDDLE, middleDown, 1
                    );
            }
            if (rightChange)
            {
                //no double-clicks, to be consistent with other browsers
                BrowserNative.zfb_mouseButton(
                    browser.browserId, BrowserNative.MouseButton.MBT_RIGHT, rightDown, 1
                    );
            }

            prevPos     = mousePos;
            prevButtons = currentButtons;
        }
Beispiel #28
0
        private IEnumerator FixFocus()
        {
            //OS X: Magically, new browser windows that are focused don't think they are focused, even though we told them so.
            //Hack workaround.
            yield return(null);

            BrowserNative.zfb_setFocused(browserId, false);
            yield return(null);

            BrowserNative.zfb_setFocused(browserId, true);
            yield return(null);

            BrowserNative.zfb_setFocused(browserId, KeyboardHasFocus);
        }
        private void HandleKeyInput(List <Event> keyEvents)
        {
#if ZF_OSX
            ReconstructInputs(keyEvents);
#endif

            foreach (var ev in keyEvents)
            {
                var keyCode = KeyMappings.GetWindowsKeyCode(ev);
                if (ev.character == '\n')
                {
                    ev.character = '\r';                              //'cuz that's what Chromium expects
                }
                if (ev.character == 0)
                {
                    if (ev.type == EventType.KeyDown)
                    {
                        keysToReleaseOnFocusLoss.Add(ev.keyCode);
                    }
                    else
                    {
                        keysToReleaseOnFocusLoss.Remove(ev.keyCode);
                    }
                }

//			if (false) {
//				if (ev.character != 0) Debug.Log("k >>> " + ev.character);
//				else if (ev.type == EventType.KeyUp) Debug.Log("k ^^^ " + ev.keyCode);
//				else if (ev.type == EventType.KeyDown) Debug.Log("k vvv " + ev.keyCode);
//			}

                FireCommands(ev);

                if (ev.character != 0 && ev.type == EventType.KeyDown)
                {
#if ZF_LINUX
                    //It seems, on Linux, we don't get keydown, keypress, keyup, we just get a keypress, keyup.
                    //So, fire the keydown just before the keypress.
                    BrowserNative.zfb_keyEvent(browser.browserId, true, keyCode);
                    //Thanks for being consistent, Unity.
#endif

                    BrowserNative.zfb_characterEvent(browser.browserId, ev.character, keyCode);
                }
                else
                {
                    BrowserNative.zfb_keyEvent(browser.browserId, ev.type == EventType.KeyDown, keyCode);
                }
            }
        }
        public void HandleFocusLoss()
        {
            foreach (var keyCode in keysToReleaseOnFocusLoss)
            {
                //Debug.Log("Key " + keyCode + " is held, release");
                var wCode = KeyMappings.GetWindowsKeyCode(new Event()
                {
                    keyCode = keyCode
                });
                BrowserNative.zfb_keyEvent(browser.browserId, false, wCode);
            }

            keysToReleaseOnFocusLoss.Clear();
        }