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(); } }
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); }
/// <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); }
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(); }
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(); }
/// <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; }