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

            // 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 retainedCallbacksAndListeners = _pendingCallbacks.RemoveWhere(info =>
                                                                              info.PluginId == handler.Descriptor.PluginId && info.IsRetained);

            foreach (var eventListener in retainedCallbacksAndListeners)
            {
                // Tell the remote plugin process that owns the listener or parameter callbacks to dispose of it
                var removeListenerMessage = new PluginMessage
                {
                    MessageId   = eventListener.RequestMessage.MessageId,
                    MessageType = PluginMessageType.RemoveRetained,
                    PluginId    = eventListener.RequestMessage.PluginId,
                    MemberName  = eventListener.RequestMessage.MemberName,
                    Data        = string.Empty,
                    BrowserId   = eventListener.RequestMessage.BrowserId,
                    ContextId   = eventListener.RequestMessage.ContextId
                };

                SendMessage(eventListener.Browser, removeListenerMessage);
                eventListener.Dispose();
            }
        }
Beispiel #2
0
        public bool ProcessCefMessage(CefBrowser browser, CefProcessMessage message)
        {
            var args        = message.Arguments;
            var messageName = message.Name;

            if (messageName == CallInfo.Call && args.Count >= 10)
            {
                var pluginMessage = new PluginMessage
                {
                    MessageId    = new Guid(args.GetString(0)),
                    MessageType  = (PluginMessageType)args.GetInt(1),
                    PluginId     = args.GetString(2),
                    MemberName   = args.GetString(3),
                    Data         = args.GetString(4),
                    BrowserId    = args.GetInt(5),
                    ContextId    = args.GetInt(6),
                    FrameId      = (args.GetInt(8) << 32) | ((long)(args.GetInt(7))),
                    V8CallbackId = new Guid(args.GetString(9))
                };

                ReceiveMessage(browser, pluginMessage);
                return(true);
            }
            return(false);
        }
        /// <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);
        }
        protected override void ReceiveMessage(CefBrowser browser, PluginMessage pluginMessage)
        {
            var handler = PluginManager.GetLocalPlugin(pluginMessage.PluginId);

            if (handler == null)
            {
                // No handlers so cancel the query.
                if (pluginMessage.MessageType == PluginMessageType.FunctionInvoke)
                {
                    CancelUnhandledQuery(browser, pluginMessage);
                }
                browser.Dispose();
                return;
            }

            switch (pluginMessage.MessageType)
            {
            case PluginMessageType.FunctionInvoke:
                OnInvokeFunction(browser, pluginMessage, handler);
                break;

            case PluginMessageType.AddListener:
                OnAddListener(browser, pluginMessage, handler);
                break;

            case PluginMessageType.RemoveRetained:
                OnRemoveRetained(browser, pluginMessage, handler);
                break;
            }
        }
        /// <summary>
        /// Calls back into V8 in response to either:
        /// - a remote function response message (triggered by the framework or by a native plugin calling a delegate it was provided)
        /// - a remote event fired message
        /// - an exception while making a remote function call
        /// Will switch to the Renderer thread if needed.
        /// </summary>
        /// <param name="pluginMessage"></param>
        /// <param name="result"></param>
        private void OnBrowserCallbackInvokeReceived(PluginMessage pluginMessage, ResultData result)
        {
            Logger.Debug("BrowserCallbackInvokeReceived MsgType {0} Plugin {1} Member {2}",
                         pluginMessage.MessageType,
                         pluginMessage.PluginId,
                         pluginMessage.MemberName);

            try
            {
                var context = GetContextById(pluginMessage.ContextId);
                var info    = _pendingCallbacks.Get(pluginMessage.MessageId);
                if (info != null)
                {
                    if (!info.IsRetained)
                    {
                        _pendingCallbacks.Remove(info);
                    }

                    // 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(() => InvokeV8Callback(context, info, result)));
                }
            }
            catch (Exception exception)
            {
                Logger.Error("BrowserCallbackInvokeReceived Failed MsgType {0} Plugin {1} Member {2}: {3}",
                             pluginMessage.MessageType,
                             pluginMessage.PluginId,
                             pluginMessage.MemberName,
                             exception);
            }
        }
        private LocalRenderCallInfo AddLocalCallback(PluginMessage pluginMessage, IV8Callback callback, IJavaScriptParameterCallback parameterCallback)
        {
            var info = new LocalRenderCallInfo(this, pluginMessage, callback, parameterCallback);

            _pendingCallbacks.Add(info);
            return(info);
        }
        private RenderCallInfo AddRemoteCallback(PluginMessage pluginMessage, IV8Callback callback)
        {
            var info = new RenderCallInfo(pluginMessage, callback);

            _pendingCallbacks.Add(info);
            return(info);
        }
        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 void InitializePluginContext(PluginMessage initializeMessage)
        {
            Logger.Info("Initializing Plugin Context");

            try
            {
                if (_pluginContext == null)
                {
                    var pluginContext = new PluginContext(this, initializeMessage.MessageId);
                    var pluginGroup   = JsonConvert.DeserializeObject <PluginGroup>(initializeMessage.Data);
                    if (pluginGroup == null)
                    {
                        throw new SerializationException("no plugin group deserialized");
                    }
                    if (this.OnPluginContextCreated != null)
                    {
                        this.OnPluginContextCreated(pluginContext);
                    }
                    pluginContext.Initialize(pluginGroup);
                    _pluginContext = pluginContext;
                    pluginContext.PluginManager.LocalPluginRemoved += OnPluginRemoved;
                }
            }
            catch (Exception exception)
            {
                Logger.Error("Initializing Plugin Context failed", exception);
            }
        }
        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);
        }
Beispiel #12
0
        /// <summary>
        /// Send a message across the CEF boundary.
        /// </summary>
        /// <param name="browser">The browser instance to send the mesage to.</param>
        /// <param name="pluginMessage">The message to send.</param>
        protected void SendMessage(CefBrowser browser, PluginMessage pluginMessage)
        {
            if (browser == null)
            {
                throw new ArgumentNullException("browser");
            }
            var message = CefProcessMessage.Create(CallInfo.Call);

            SerializeProcessMessage(pluginMessage, message.Arguments);
            browser.SendProcessMessage(_remotePluginProcess, message);
        }
        /// <summary>
        /// Respond to a function invocation with the result that the function wasn't handled.
        /// 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="browser">
        /// The browser to send the cancellation to.
        /// </param>
        /// <param name="pluginMessage">
        /// The function invocation request message.
        /// </param>
        private void CancelUnhandledQuery(CefBrowser browser, PluginMessage pluginMessage)
        {
            var result = new ResultData
            {
                DataType  = ResultDataType.Scalar,
                ErrorCode = CallInfo.ErrorCodeCallCanceled,
                Error     = CallInfo.ErrorCallCanceled
            };

            SendFunctionResponse(browser, pluginMessage, result);
        }
Beispiel #14
0
 public BrowserCallInfo(
     IBrowserCallResponseHandler responseHandler,
     CefBrowser browser,
     PluginMessage pluginMessage,
     string pluginId,
     IJavaScriptParameterCallback parameterCallback)
     : base(pluginMessage, pluginId)
 {
     _responseHandler   = responseHandler;
     _parameterCallback = parameterCallback;
     Browser            = browser;
 }
        /// <summary>
        /// Send a response to a function call, which can include the call being cancelled.
        /// 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="browser">
        /// The browser to send the response to.
        /// </param>
        /// <param name="invocationMessage">
        /// The message that requested the function call.
        /// </param>
        /// <param name="result">
        /// The result of the function (result, errorCode, error).
        /// </param>
        private void SendFunctionResponse(CefBrowser browser, PluginMessage invocationMessage, ResultData result)
        {
            var responseMessage = new PluginMessage
            {
                MessageId   = invocationMessage.MessageId,
                MessageType = PluginMessageType.FunctionCallback,
                PluginId    = invocationMessage.PluginId,
                MemberName  = invocationMessage.MemberName,
                Data        = ParagonJsonSerializer.Serialize(result),
                BrowserId   = invocationMessage.BrowserId,
                ContextId   = invocationMessage.ContextId
            };

            SendMessage(browser, responseMessage);
        }
        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();
        }
Beispiel #17
0
 protected void SerializeProcessMessage(PluginMessage pluginMessage, CefListValue args)
 {
     if (pluginMessage == null)
     {
         throw new ArgumentNullException("pluginMessage");
     }
     args.SetString(0, pluginMessage.MessageId.ToString());
     args.SetInt(1, (int)pluginMessage.MessageType);
     args.SetString(2, pluginMessage.PluginId);
     args.SetString(3, pluginMessage.MemberName);
     args.SetString(4, pluginMessage.Data);
     args.SetInt(5, pluginMessage.BrowserId);
     args.SetInt(6, pluginMessage.ContextId);
     args.SetInt(7, (int)(pluginMessage.FrameId & uint.MaxValue)); // Lower 32 bits
     args.SetInt(8, (int)(pluginMessage.FrameId >> 32));           // Upper 32 bits
     args.SetString(9, pluginMessage.V8CallbackId.ToString());
 }
        private void OnAddListener(CefBrowser browser, PluginMessage pluginMessage, JavaScriptPlugin handler)
        {
            var info = CreateAndAddCall(browser, pluginMessage, handler, null);

            if (!handler.IsValid)
            {
                RemoveAndCancelCall(info);
                return;
            }

            var handled = handler.AddEventListener(pluginMessage.MemberName, info);

            if (!handled)
            {
                RemoveAndCancelCall(info);
            }
        }
        private PluginMessage CreateMessage(
            CefV8Context context,
            PluginDescriptor targetPlugin,
            string memberName,
            IV8Callback callback)
        {
            var message = new PluginMessage
            {
                PluginId     = targetPlugin.PluginId,
                MemberName   = memberName,
                BrowserId    = context.GetBrowserId(),
                ContextId    = GetIdForContext(context, false),
                FrameId      = context.GetFrame().Identifier,
                V8CallbackId = callback != null ? callback.Identifier : Guid.Empty
            };

            return(message);
        }
        public CefListValue CreatePluginInitMessage()
        {
            Logger.Info("Creating browser initialization message");
            var initializeMessage = new PluginMessage
            {
                MessageType = PluginMessageType.InitializePlugins,
                MessageId   = _pluginGroupId,
                BrowserId   = 0
            };

            var pluginGroup = new PluginGroup
            {
                PluginDescriptors = PluginManager.GetAllLocalPlugins().Select(p => p.Descriptor).ToList()
            };

            initializeMessage.Data = JsonConvert.SerializeObject(pluginGroup);
            var retVal = CefListValue.Create();

            SerializeProcessMessage(initializeMessage, retVal);
            return(retVal);
        }
        protected override void ReceiveMessage(CefBrowser browser, PluginMessage pluginMessage)
        {
            switch (pluginMessage.MessageType)
            {
            case PluginMessageType.FunctionCallback:
            case PluginMessageType.EventFired:
                var payload = ParagonJsonSerializer.Deserialize <ResultData>(pluginMessage.Data);
                OnBrowserCallbackInvokeReceived(pluginMessage, payload);
                break;

            case PluginMessageType.RemoveRetained:
                // Remove a listener in the render process (handles browser side dynamic plugin dispose scenario)
                var retainedCallback = _pendingCallbacks.Remove(pluginMessage.MessageId);
                if (retainedCallback != null)
                {
                    retainedCallback.Dispose();
                }
                break;

            default:
                throw new ArgumentOutOfRangeException();
            }
            browser.Dispose();
        }
Beispiel #22
0
 /// <summary>
 /// Receive a plugin message from a CEF or external (MAF) process.
 /// Will be on the CEF main thread for messages received via CEF, will be on thread pool for external plugin processes.
 /// </summary>
 /// <param name="browser">
 /// Only populated for a message received across the CEF boundary. If null it means the message has been received from an external plugin process.
 /// </param>
 /// <param name="pluginMessage">
 /// The message to process.
 /// </param>
 protected abstract void ReceiveMessage(CefBrowser browser, PluginMessage pluginMessage);
 protected CallInfo(PluginMessage requestMessage, string pluginId = null)
 {
     RequestMessage = requestMessage;
     PluginId       = pluginId;
 }
 public LocalRenderCallInfo(IRenderSideMessageRouter router, PluginMessage requestMesage, IV8Callback callback, IJavaScriptParameterCallback parameterCallback)
     : base(requestMesage, callback)
 {
     _router            = router;
     _parameterCallback = parameterCallback;
 }