private LocalRenderCallInfo GetOrCreateParameterCallback(CefV8Context context, MethodDescriptor methodDescriptor, IV8Callback callback)
        {
            LocalRenderCallInfo parameterCallbackInfo = null;

            if (methodDescriptor.HasCallbackParameter && callback != null)
            {
                // 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.
                parameterCallbackInfo = (LocalRenderCallInfo)_pendingCallbacks.Get(callback.Identifier);
                if (parameterCallbackInfo == null)
                {
                    var parameterCallbackMessage = new PluginMessage
                    {
                        MessageId    = callback.Identifier,
                        MessageType  = PluginMessageType.ParameterCallback,
                        PluginId     = string.Empty,
                        MemberName   = string.Empty,
                        BrowserId    = context.GetBrowserId(),
                        ContextId    = GetIdForContext(context, false),
                        FrameId      = context.GetFrame().Identifier,
                        V8CallbackId = Guid.Empty
                    };

                    parameterCallbackInfo = AddLocalCallback(parameterCallbackMessage, callback, null);
                }
            }
            return(parameterCallbackInfo);
        }
        private LocalRenderCallInfo AddLocalCallback(PluginMessage pluginMessage, IV8Callback callback, IJavaScriptParameterCallback parameterCallback)
        {
            var info = new LocalRenderCallInfo(this, pluginMessage, callback, parameterCallback);

            _pendingCallbacks.Add(info);
            return(info);
        }
        /// <summary>
        /// Calls back into V8 in response to either:
        /// - a local function response message
        /// - a local event fired message
        /// - an exception while making a local function call
        /// Will switch to the Renderer thread if needed.
        /// </summary>
        /// <param name="call"></param>
        /// <param name="result"></param>
        /// <param name="errorCode"></param>
        /// <param name="error"></param>
        public void LocalCallbackInvoked(LocalRenderCallInfo call, object result, int errorCode, string error)
        {
            if (!CefRuntime.CurrentlyOn(CefThreadId.Renderer))
            {
                // Call completed received from remote process, so currently are on the IO method. Marshal to the renderer to callback into V8
                CefRuntime.PostTask(CefThreadId.Renderer,
                                    new ActionCallbackTask(() => LocalCallbackInvoked(call, result, errorCode, error)));
                return;
            }

            var msg = call.RequestMessage;

            Logger.Info("LocalCallbackInvoked MsgType {0} Plugin {1} Member {2}",
                        msg.MessageType, msg.PluginId, msg.MemberName);

            // Already on the render thread (e.g. local plugin event triggered by call into function from JS, or function invoke failed
            try
            {
                var context = GetContextById(call.RequestMessage.ContextId);
                if (!call.IsRetained)
                {
                    _pendingCallbacks.Remove(call);
                }
                if (context != null && call.Callback != null)
                {
                    call.Callback.Invoke(this, context, result, errorCode, error);
                }
                if (!call.IsRetained)
                {
                    call.Dispose();
                }
            }
            catch (Exception exception)
            {
                Logger.Error("LocalCallbackInvoked Failed MsgType {0} Plugin {1} Member {2}: {3}",
                             call.RequestMessage.MessageType,
                             call.RequestMessage.PluginId,
                             call.RequestMessage.MemberName,
                             exception);
            }
        }