/// <summary>
        /// Trigger an event callback in a remote process.
        /// Can be called on any thread as the CEF API involved (<see cref="CefBrowser.SendProcessMessage"/>) can be called on any thread when in the browser process.
        /// </summary>
        /// <param name="listener">
        /// The message that requested to listen to the triggered event.
        /// </param>
        /// <param name="result">
        /// The data payload of the triggered event.
        /// </param>
        public void SendEvent(BrowserCallInfo listener, ResultData result)
        {
            if (listener == null)
            {
                throw new ArgumentNullException("listener");
            }

            if (result == null)
            {
                throw new ArgumentNullException("result");
            }

            var eventMessage = new PluginMessage
            {
                MessageId   = listener.RequestMessage.MessageId,
                MessageType = PluginMessageType.EventFired,
                PluginId    = string.Empty,
                MemberName  = string.Empty,
                Data        = ParagonJsonSerializer.Serialize(result),
                BrowserId   = listener.RequestMessage.BrowserId,
                ContextId   = listener.RequestMessage.ContextId
            };

            SendMessage(listener.Browser, eventMessage);
        }
        private void OnInvokeFunction(CefBrowser browser, PluginMessage pluginMessage, JavaScriptPlugin handler)
        {
            var methodDescriptor = handler.Descriptor.Methods.Find(descriptor => descriptor.MethodName == pluginMessage.MemberName);
            IJavaScriptPluginCallback returnCallback    = null;
            BrowserCallInfo           parameterCallback = null;

            if (pluginMessage.V8CallbackId != Guid.Empty)
            {
                if (methodDescriptor.HasCallbackParameter)
                {
                    // Create a second stored callback info which represents the V8 callback function itself
                    // rather than the method that is being invoked now. This allows the callback function
                    // to be passed to and invoked by multiple native methods that accept a callback parameter.
                    parameterCallback = _pendingCallbacks.Get(pluginMessage.V8CallbackId);
                    if (parameterCallback == null)
                    {
                        var parameterCallbackMessage = new PluginMessage
                        {
                            MessageId    = pluginMessage.V8CallbackId,
                            MessageType  = PluginMessageType.ParameterCallback,
                            PluginId     = string.Empty,
                            MemberName   = string.Empty,
                            BrowserId    = pluginMessage.BrowserId,
                            ContextId    = pluginMessage.ContextId,
                            FrameId      = pluginMessage.FrameId,
                            V8CallbackId = Guid.Empty
                        };

                        parameterCallback = CreateAndAddCall(browser.Clone(), parameterCallbackMessage, null, null);
                    }
                }

                var returnCallInfo = CreateAndAddCall(browser, pluginMessage, handler, parameterCallback);
                if (!handler.IsValid)
                {
                    RemoveAndCancelCall(returnCallInfo);
                    return;
                }

                returnCallback = returnCallInfo;
            }

            JArray callArgs = null;

            if (!string.IsNullOrEmpty(pluginMessage.Data))
            {
                callArgs = JArray.Parse(pluginMessage.Data);
            }

            handler.InvokeFunction(
                _pluginManager,
                pluginMessage.BrowserId,
                pluginMessage.FrameId,
                pluginMessage.ContextId,
                pluginMessage.MemberName,
                new JArrayJavaScriptParameters(callArgs),
                returnCallback);
        }
        private BrowserCallInfo CreateAndAddCall(CefBrowser browser, PluginMessage pluginMessage,
                                                 JavaScriptPlugin handler, IJavaScriptParameterCallback parameterCallback)
        {
            var pluginId = handler != null ? handler.Descriptor.PluginId : null;
            var info     = new BrowserCallInfo(this, browser, pluginMessage, pluginId, parameterCallback);

            _pendingCallbacks.Add(info);
            return(info);
        }
        private void RemoveAndCancelCall(BrowserCallInfo info)
        {
            _pendingCallbacks.Remove(info);

            if (info.RequestMessage.MessageType == PluginMessageType.FunctionCallback)
            {
                CancelUnhandledQuery(info.Browser, info.RequestMessage);
            }

            info.Dispose();
        }
        /// <summary>
        /// Send a response to a function call.
        /// Can be called on any thread as the CEF API involved (<see cref="CefBrowser.SendProcessMessage"/>) can be called on any thread when in the browser process.
        /// </summary>
        /// <param name="info">
        /// The callback that was created when the function was invoked.
        /// </param>
        /// <param name="result">
        /// The result of the function (result, errorCode, error).
        /// </param>
        public void SendFunctionResponse(BrowserCallInfo info, ResultData result)
        {
            if (info == null)
            {
                throw new ArgumentNullException("info");
            }

            if (result == null)
            {
                throw new ArgumentNullException("result");
            }

            if (!info.IsRetained)
            {
                _pendingCallbacks.Remove(info);
            }

            SendFunctionResponse(info.Browser, info.RequestMessage, result);

            if (!info.IsRetained)
            {
                info.Dispose();
            }
        }