public void RemoveEventListener(JavaScriptPlugin targetPlugin, string eventName,
                                        IV8Callback callback)
        {
            Logger.Info("RemoveEventListener Local Plugin {0} Event {1}", targetPlugin.Descriptor.PluginId, eventName);

            if (!EnsureOnRendererThread())
            {
                return;
            }

            try
            {
                var info = (LocalRenderCallInfo)_pendingCallbacks.Remove(callback.Identifier);
                info.Dispose();

                targetPlugin.RemoveEventListener(eventName, info);
            }
            catch (Exception exception)
            {
                Logger.Error("RemoveEventListener Failed Local Plugin {0} Event {1}: {2}",
                             targetPlugin.Descriptor.PluginId,
                             eventName,
                             exception);
            }
        }
        private void OnRemoveRetained(CefBrowser browser, PluginMessage pluginMessage, JavaScriptPlugin handler)
        {
            var info = _pendingCallbacks.Remove(pluginMessage.MessageId);

            if (info != null)
            {
                if (info.IsEventListener)
                {
                    handler.RemoveEventListener(pluginMessage.MemberName, info);
                }

                info.Dispose();
            }

            browser.Dispose();
        }
        private void OnPluginRemoved(JavaScriptPlugin handler)
        {
            if (handler == null)
            {
                return;
            }

            Logger.Info("PluginRemoved ID {0}", handler.Descriptor.PluginId);

            // Only remove event listeners and parameter callbacks - locking inside JavaScriptPlugin means any
            // inflight calls will still complete, so leave those to be removed and disposed as per usual
            var pending = _pendingCallbacks.RemoveWhere(info => info.PluginId == handler.Descriptor.PluginId && info.IsRetained);

            foreach (var retainedCall in pending.OfType <LocalRenderCallInfo>())
            {
                if (retainedCall.IsEventListener)
                {
                    handler.RemoveEventListener(retainedCall.RequestMessage.MemberName, retainedCall);
                }
                retainedCall.Dispose();
            }
        }