init lserp cs 5.0
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
namespace Cef3.Wrapper
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
// Maps an arbitrary TId to an arbitrary TObject on a per-browser basis.
|
||||
internal sealed class CefBrowserInfoMap<TKey, TValue>
|
||||
{
|
||||
public delegate bool Visitor(int browserId, TKey key, TValue value, ref bool remove);
|
||||
|
||||
private Dictionary<int, Dictionary<TKey, TValue>> _map = new Dictionary<int, Dictionary<TKey, TValue>>();
|
||||
|
||||
public CefBrowserInfoMap()
|
||||
{
|
||||
}
|
||||
|
||||
public bool IsEmpty
|
||||
{
|
||||
get
|
||||
{
|
||||
return Count() == 0;
|
||||
}
|
||||
}
|
||||
|
||||
public int Count()
|
||||
{
|
||||
var result = 0;
|
||||
foreach (var v in _map.Values)
|
||||
{
|
||||
result += v.Count;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public int Count(int browserId)
|
||||
{
|
||||
Dictionary<TKey, TValue> v;
|
||||
if (_map.TryGetValue(browserId, out v)) return v.Count;
|
||||
else return 0;
|
||||
}
|
||||
|
||||
|
||||
public void Add(int browserId, TKey key, TValue value)
|
||||
{
|
||||
Dictionary<TKey, TValue> v;
|
||||
if (!_map.TryGetValue(browserId, out v))
|
||||
{
|
||||
v = new Dictionary<TKey, TValue>();
|
||||
_map.Add(browserId, v);
|
||||
}
|
||||
|
||||
v.Add(key, value);
|
||||
}
|
||||
|
||||
public TValue Find(int browserId, TKey key, Visitor visitor)
|
||||
{
|
||||
Dictionary<TKey, TValue> v;
|
||||
if (!_map.TryGetValue(browserId, out v))
|
||||
{
|
||||
return default(TValue);
|
||||
}
|
||||
|
||||
TValue x;
|
||||
if (v.TryGetValue(key, out x))
|
||||
{
|
||||
bool remove = false;
|
||||
visitor(browserId, key, x, ref remove);
|
||||
if (remove)
|
||||
{
|
||||
v.Remove(key);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
else return default(TValue);
|
||||
}
|
||||
|
||||
public void FindAll(int browserId, Visitor visitor)
|
||||
{
|
||||
Dictionary<TKey, TValue> v;
|
||||
if (!_map.TryGetValue(browserId, out v)) return;
|
||||
|
||||
bool hasRemoveKey = false;
|
||||
TKey removeKey = default(TKey);
|
||||
List<TKey> removeList = null;
|
||||
foreach (var kv in v)
|
||||
{
|
||||
bool remove = false;
|
||||
visitor(browserId, kv.Key, kv.Value, ref remove);
|
||||
if (remove)
|
||||
{
|
||||
if (!hasRemoveKey)
|
||||
{
|
||||
removeKey = kv.Key;
|
||||
hasRemoveKey = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (removeList == null) removeList = new List<TKey>();
|
||||
removeList.Add(kv.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasRemoveKey) v.Remove(removeKey);
|
||||
if (removeList != null)
|
||||
{
|
||||
foreach (var k in removeList) v.Remove(k);
|
||||
}
|
||||
}
|
||||
|
||||
public void FindAll(Visitor visitor)
|
||||
{
|
||||
foreach (var k in _map.Keys)
|
||||
{
|
||||
FindAll(k, visitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
namespace Cef3.Wrapper
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
internal static class CefMessageRouter
|
||||
{
|
||||
// ID value reserved for internal use.
|
||||
public const int ReservedId = 0;
|
||||
|
||||
// Appended to the JS function name for related IPC messages.
|
||||
public const string MessageSuffix = "Msg";
|
||||
|
||||
// JS object member argument names for cefQuery.
|
||||
public const string MemberRequest = "request";
|
||||
public const string MemberOnSuccess = "onSuccess";
|
||||
public const string MemberOnFailure = "onFailure";
|
||||
public const string MemberPersistent = "persistent";
|
||||
|
||||
// Default error information when a query is canceled.
|
||||
public const int CanceledErrorCode = -1;
|
||||
public const string CanceledErrorMessage = "The query has been canceled";
|
||||
|
||||
|
||||
public sealed class IdGeneratorInt32
|
||||
{
|
||||
private int _next_id;
|
||||
|
||||
public int GetNextId()
|
||||
{
|
||||
var id = ++_next_id;
|
||||
if (id == CefMessageRouter.ReservedId)
|
||||
id = ++_next_id;
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class IdGeneratorInt64
|
||||
{
|
||||
private long _next_id;
|
||||
|
||||
public long GetNextId()
|
||||
{
|
||||
var id = ++_next_id;
|
||||
if (id == CefMessageRouter.ReservedId)
|
||||
id = ++_next_id;
|
||||
return id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,629 @@
|
||||
namespace Cef3.Wrapper
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
using BrowserInfoMap = CefBrowserInfoMap<long, Cef3.Wrapper.CefMessageRouterBrowserSide.QueryInfo>;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the browser side of query routing. The methods of this class may
|
||||
/// be called on any browser process thread unless otherwise indicated.
|
||||
/// </summary>
|
||||
public sealed class CefMessageRouterBrowserSide
|
||||
{
|
||||
#region Callback
|
||||
|
||||
/// <summary>
|
||||
/// Callback associated with a single pending asynchronous query. Execute the
|
||||
/// Success or Failure method to send an asynchronous response to the
|
||||
/// associated JavaScript handler. It is a runtime error to destroy a Callback
|
||||
/// object associated with an uncanceled query without first executing one of
|
||||
/// the callback methods. The methods of this class may be called on any
|
||||
/// browser process thread.
|
||||
/// </summary>
|
||||
public sealed class Callback
|
||||
{
|
||||
private CefMessageRouterBrowserSide _router;
|
||||
private readonly int _browserId;
|
||||
private readonly long _queryId;
|
||||
private readonly bool _persistent;
|
||||
|
||||
internal Callback(CefMessageRouterBrowserSide router, int browserId, long queryId, bool persistent)
|
||||
{
|
||||
_router = router;
|
||||
_browserId = browserId;
|
||||
_queryId = queryId;
|
||||
_persistent = persistent;
|
||||
}
|
||||
|
||||
~Callback()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
internal void Dispose()
|
||||
{
|
||||
// Hitting this DCHECK means that you didn't call Success or Failure
|
||||
// on the Callback after returning true from Handler::OnQuery. You must
|
||||
// call Failure to terminate persistent queries.
|
||||
Debug.Assert(_router == null);
|
||||
// if (_router != null) throw new InvalidOperationException("You didn't call Success or Failure on the Callback after returning true from Handler::OnQuery.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notify the associated JavaScript onSuccess callback that the query has
|
||||
/// completed successfully with the specified |response|.
|
||||
/// </summary>
|
||||
public void Success(string response)
|
||||
{
|
||||
if (!CefRuntime.CurrentlyOn(CefThreadId.UI))
|
||||
{
|
||||
Helpers.PostTask(CefThreadId.UI,
|
||||
Helpers.Apply(this.Success, response)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_router != null)
|
||||
{
|
||||
Helpers.PostTaskUncertainty(CefThreadId.UI,
|
||||
Helpers.Apply(_router.OnCallbackSuccess, _browserId, _queryId, response)
|
||||
);
|
||||
|
||||
if (!_persistent)
|
||||
{
|
||||
// Non-persistent callbacks are only good for a single use.
|
||||
_router = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notify the associated JavaScript onFailure callback that the query has
|
||||
/// failed with the specified |error_code| and |error_message|.
|
||||
/// </summary>
|
||||
public void Failure(int errorCode, string errorMessage)
|
||||
{
|
||||
if (!CefRuntime.CurrentlyOn(CefThreadId.UI))
|
||||
{
|
||||
// Must execute on the UI thread to access member variables.
|
||||
Helpers.PostTask(CefThreadId.UI,
|
||||
Helpers.Apply(this.Failure, errorCode, errorMessage)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_router != null)
|
||||
{
|
||||
Helpers.PostTaskUncertainty(CefThreadId.UI,
|
||||
Helpers.Apply(_router.OnCallbackFailure, _browserId, _queryId, errorCode, errorMessage)
|
||||
);
|
||||
|
||||
// Failure always invalidates the callback.
|
||||
_router = null;
|
||||
}
|
||||
}
|
||||
|
||||
internal void Detach()
|
||||
{
|
||||
Helpers.RequireUIThread();
|
||||
_router = null;
|
||||
}
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
#region Handler
|
||||
|
||||
/// <summary>
|
||||
/// Implement this interface to handle queries. All methods will be executed on
|
||||
/// the browser process UI thread.
|
||||
/// </summary>
|
||||
public class Handler
|
||||
{
|
||||
/// <summary>
|
||||
/// Executed when a new query is received. |query_id| uniquely identifies the
|
||||
/// query for the life span of the router. Return true to handle the query
|
||||
/// or false to propagate the query to other registered handlers, if any. If
|
||||
/// no handlers return true from this method then the query will be
|
||||
/// automatically canceled with an error code of -1 delivered to the
|
||||
/// JavaScript onFailure callback. If this method returns true then a
|
||||
/// Callback method must be executed either in this method or asynchronously
|
||||
/// to complete the query.
|
||||
/// </summary>
|
||||
public virtual bool OnQuery(CefBrowser browser, CefFrame frame, long queryId, string request, bool persistent, Callback callback)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executed when a query has been canceled either explicitly using the
|
||||
/// JavaScript cancel function or implicitly due to browser destruction,
|
||||
/// navigation or renderer process termination. It will only be called for
|
||||
/// the single handler that returned true from OnQuery for the same
|
||||
/// |query_id|. No references to the associated Callback object should be
|
||||
/// kept after this method is called, nor should any Callback methods be
|
||||
/// executed.
|
||||
/// </summary>
|
||||
public virtual void OnQueryCanceled(CefBrowser browser, CefFrame frame, long queryId)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
private readonly CefMessageRouterConfig _config;
|
||||
private readonly string _queryMessageName;
|
||||
private readonly string _cancelMessageName;
|
||||
|
||||
private readonly List<Handler> _handlerSet = new List<Handler>(4); // TODO: use a HashSet, for .NET 3.5+
|
||||
|
||||
private readonly BrowserInfoMap _browserQueryInfoMap = new BrowserInfoMap();
|
||||
|
||||
private readonly CefMessageRouter.IdGeneratorInt64 _queryIdGenerator = new CefMessageRouter.IdGeneratorInt64();
|
||||
|
||||
/// <summary>
|
||||
/// Create a new router with the specified configuration.
|
||||
/// </summary>
|
||||
public CefMessageRouterBrowserSide(CefMessageRouterConfig config)
|
||||
{
|
||||
if (!config.Validate()) throw new ArgumentException("Invalid configuration.");
|
||||
|
||||
_config = config;
|
||||
_queryMessageName = config.JSQueryFunction + CefMessageRouter.MessageSuffix;
|
||||
_cancelMessageName = config.JSCancelFunction + CefMessageRouter.MessageSuffix;
|
||||
}
|
||||
|
||||
~CefMessageRouterBrowserSide()
|
||||
{
|
||||
// There should be no pending queries when the router is deleted.
|
||||
Debug.Assert(_browserQueryInfoMap.IsEmpty);
|
||||
}
|
||||
|
||||
// TODO: Dispose method ?
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create a new router with the specified configuration.
|
||||
/// </summary>
|
||||
public static CefMessageRouterBrowserSide Create(CefMessageRouterConfig config)
|
||||
{
|
||||
return new CefMessageRouterBrowserSide(config);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a new query handler. If |first| is true it will be added as the first
|
||||
/// handler, otherwise it will be added as the last handler. Returns true if
|
||||
/// the handler is added successfully or false if the handler has already been
|
||||
/// added. Must be called on the browser process UI thread. The Handler object
|
||||
/// must either outlive the router or be removed before deletion.
|
||||
/// </summary>
|
||||
public bool AddHandler(Handler handler, bool first = false)
|
||||
{
|
||||
if (handler == null) throw new ArgumentNullException("handler");
|
||||
|
||||
Helpers.RequireUIThread();
|
||||
|
||||
if (_handlerSet.Contains(handler)) return false;
|
||||
|
||||
if (first) { _handlerSet.Insert(0, handler); }
|
||||
else { _handlerSet.Add(handler); }
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove an existing query handler. Any pending queries associated with the
|
||||
/// handler will be canceled. Handler::OnQueryCanceled will be called and the
|
||||
/// associated JavaScript onFailure callback will be executed with an error
|
||||
/// code of -1. Returns true if the handler is removed successfully or false
|
||||
/// if the handler is not found. Must be called on the browser process UI
|
||||
/// thread.
|
||||
/// </summary>
|
||||
public bool RemoveHandler(Handler handler)
|
||||
{
|
||||
if (handler == null) throw new ArgumentNullException("handler");
|
||||
|
||||
Helpers.RequireUIThread();
|
||||
|
||||
if (_handlerSet.Remove(handler))
|
||||
{
|
||||
CancelPendingFor(null, handler, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel all pending queries associated with either |browser| or |handler|.
|
||||
/// If both |browser| and |handler| are NULL all pending queries will be
|
||||
/// canceled. Handler::OnQueryCanceled will be called and the associated
|
||||
/// JavaScript onFailure callback will be executed in all cases with an error
|
||||
/// code of -1.
|
||||
/// </summary>
|
||||
public void CancelPending(CefBrowser browser = null, Handler handler = null)
|
||||
{
|
||||
CancelPendingFor(browser, handler, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of queries currently pending for the specified |browser|
|
||||
/// and/or |handler|. Either or both values may be empty. Must be called on the
|
||||
/// browser process UI thread.
|
||||
/// </summary>
|
||||
public int GetPendingCount(CefBrowser browser = null, Handler handler = null)
|
||||
{
|
||||
Helpers.RequireUIThread();
|
||||
|
||||
if (_browserQueryInfoMap.IsEmpty) return 0;
|
||||
|
||||
if (handler != null)
|
||||
{
|
||||
int count = 0;
|
||||
BrowserInfoMap.Visitor visitor = (int browserId, long key, QueryInfo value, ref bool remove) =>
|
||||
{
|
||||
if (value.Handler == handler) count++;
|
||||
return true;
|
||||
};
|
||||
|
||||
if (browser != null)
|
||||
{
|
||||
// Count queries associated with the specified browser.
|
||||
_browserQueryInfoMap.FindAll(browser.Identifier, visitor);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Count all queries for all browsers.
|
||||
_browserQueryInfoMap.FindAll(visitor);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
else if (browser != null)
|
||||
{
|
||||
return _browserQueryInfoMap.Count(browser.Identifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
return _browserQueryInfoMap.Count();
|
||||
}
|
||||
}
|
||||
|
||||
#region The below methods should be called from other CEF handlers. They must be called exactly as documented for the router to function correctly.
|
||||
|
||||
/// <summary>
|
||||
/// Call from CefLifeSpanHandler::OnBeforeClose. Any pending queries associated
|
||||
/// with |browser| will be canceled and Handler::OnQueryCanceled will be called.
|
||||
/// No JavaScript callbacks will be executed since this indicates destruction
|
||||
/// of the browser.
|
||||
/// </summary>
|
||||
public void OnBeforeClose(CefBrowser browser)
|
||||
{
|
||||
CancelPendingFor(browser, null, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call from CefRequestHandler::OnRenderProcessTerminated. Any pending queries
|
||||
/// associated with |browser| will be canceled and Handler::OnQueryCanceled
|
||||
/// will be called. No JavaScript callbacks will be executed since this
|
||||
/// indicates destruction of the context.
|
||||
/// </summary>
|
||||
public void OnRenderProcessTerminated(CefBrowser browser)
|
||||
{
|
||||
CancelPendingFor(browser, null, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call from CefRequestHandler::OnBeforeBrowse only if the navigation is
|
||||
/// allowed to proceed. If |frame| is the main frame then any pending queries
|
||||
/// associated with |browser| will be canceled and Handler::OnQueryCanceled
|
||||
/// will be called. No JavaScript callbacks will be executed since this
|
||||
/// indicates destruction of the context.
|
||||
/// </summary>
|
||||
public void OnBeforeBrowse(CefBrowser browser, CefFrame frame)
|
||||
{
|
||||
if (frame.IsMain) CancelPendingFor(browser, null, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call from CefClient::OnProcessMessageReceived. Returns true if the message
|
||||
/// is handled by this router or false otherwise.
|
||||
/// </summary>
|
||||
public bool OnProcessMessageReceived(CefBrowser browser, CefProcessId sourceProcess, CefProcessMessage message)
|
||||
{
|
||||
Helpers.RequireUIThread();
|
||||
|
||||
var messageName = message.Name;
|
||||
if (messageName == _queryMessageName)
|
||||
{
|
||||
var args = message.Arguments;
|
||||
Debug.Assert(args.Count == 6);
|
||||
|
||||
var frameId = Helpers.Int64Set(args.GetInt(0), args.GetInt(1));
|
||||
var contextId = args.GetInt(2);
|
||||
var requestId = args.GetInt(3);
|
||||
var request = args.GetString(4);
|
||||
var persistent = args.GetBool(5);
|
||||
|
||||
if (_handlerSet.Count == 0)
|
||||
{
|
||||
// No handlers so cancel the query.
|
||||
CancelUnhandledQuery(browser, contextId, requestId);
|
||||
return true;
|
||||
}
|
||||
|
||||
var browserId = browser.Identifier;
|
||||
var queryId = _queryIdGenerator.GetNextId();
|
||||
|
||||
var frame = browser.GetFrame(frameId);
|
||||
var callback = new Callback(this, browserId, queryId, persistent);
|
||||
|
||||
// Make a copy of the handler list in case the user adds or removes a
|
||||
// handler while we're iterating.
|
||||
var handlers = _handlerSet.ToArray();
|
||||
|
||||
var handled = false;
|
||||
Handler handler = null;
|
||||
foreach (var x in handlers)
|
||||
{
|
||||
handled = x.OnQuery(browser, frame, queryId, request, persistent, callback);
|
||||
if (handled)
|
||||
{
|
||||
handler = x;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If the query isn't handled nothing should be keeping a reference to
|
||||
// the callback.
|
||||
// DCHECK(handled || callback->GetRefCt() == 1);
|
||||
// Debug.Assert(handled && handler != null);
|
||||
// We don't need this assertion, in GC environment.
|
||||
// There is client responsibility to do not reference callback, if request is not handled.
|
||||
|
||||
if (handled)
|
||||
{
|
||||
// Persist the query information until the callback executes.
|
||||
// It's safe to do this here because the callback will execute
|
||||
// asynchronously.
|
||||
var info = new QueryInfo
|
||||
{
|
||||
Browser = browser,
|
||||
FrameId = frameId,
|
||||
ContextId = contextId,
|
||||
RequestId = requestId,
|
||||
Persistent = persistent,
|
||||
Callback = callback,
|
||||
Handler = handler,
|
||||
};
|
||||
_browserQueryInfoMap.Add(browserId, queryId, info);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Invalidate the callback.
|
||||
callback.Detach();
|
||||
|
||||
// No one chose to handle the query so cancel it.
|
||||
CancelUnhandledQuery(browser, contextId, requestId);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (messageName == _cancelMessageName)
|
||||
{
|
||||
var args = message.Arguments;
|
||||
Debug.Assert(args.Count == 2);
|
||||
|
||||
var browserId = browser.Identifier;
|
||||
var contextId = args.GetInt(0);
|
||||
var requestId = args.GetInt(1);
|
||||
|
||||
CancelPendingRequest(browserId, contextId, requestId);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
internal sealed class QueryInfo
|
||||
{
|
||||
// Browser and frame originated the query.
|
||||
public CefBrowser Browser;
|
||||
public long FrameId;
|
||||
|
||||
// IDs that uniquely identify the query in the renderer process. These
|
||||
// values are opaque to the browser process but must be returned with the
|
||||
// response.
|
||||
public int ContextId;
|
||||
public int RequestId;
|
||||
|
||||
// True if the query is persistent.
|
||||
public bool Persistent;
|
||||
|
||||
// Callback associated with the query that must be detached when the query
|
||||
// is canceled.
|
||||
public Callback Callback;
|
||||
|
||||
// Handler that should be notified if the query is automatically canceled.
|
||||
public Handler Handler;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Browser = null;
|
||||
//if (Browser != null)
|
||||
//{
|
||||
// Browser.Dispose();
|
||||
// Browser = null;
|
||||
//}
|
||||
}
|
||||
};
|
||||
|
||||
// Retrieve a QueryInfo object from the map based on the browser-side query
|
||||
// ID. If |always_remove| is true then the QueryInfo object will always be
|
||||
// removed from the map. Othewise, the QueryInfo object will only be removed
|
||||
// if the query is non-persistent. If |removed| is true the caller is
|
||||
// responsible for deleting the returned QueryInfo object.
|
||||
private QueryInfo GetQueryInfo(int browserId, long queryId, bool alwaysRemove, ref bool removed)
|
||||
{
|
||||
bool removedTemp = false;
|
||||
BrowserInfoMap.Visitor visitor = (int browserId_, long key, QueryInfo value, ref bool remove) =>
|
||||
{
|
||||
remove = removedTemp = alwaysRemove || !value.Persistent;
|
||||
return true;
|
||||
};
|
||||
var info = _browserQueryInfoMap.Find(browserId, queryId, visitor);
|
||||
if (info != null) removed = removedTemp;
|
||||
return info;
|
||||
}
|
||||
|
||||
// Called by CallbackImpl on success.
|
||||
private void OnCallbackSuccess(int browserId, long queryId, string response)
|
||||
{
|
||||
Helpers.RequireUIThread();
|
||||
|
||||
bool removed = false;
|
||||
var info = GetQueryInfo(browserId, queryId, false, ref removed);
|
||||
if (info != null)
|
||||
{
|
||||
SendQuerySuccess(info, response);
|
||||
if (removed) info.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// Called by CallbackImpl on failure.
|
||||
private void OnCallbackFailure(int browserId, long queryId, int errorCode, string errorMessage)
|
||||
{
|
||||
Helpers.RequireUIThread();
|
||||
|
||||
bool removed = false;
|
||||
var info = GetQueryInfo(browserId, queryId, true, ref removed);
|
||||
if (info != null)
|
||||
{
|
||||
SendQueryFailure(info, errorCode, errorMessage);
|
||||
Debug.Assert(removed);
|
||||
info.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void SendQuerySuccess(QueryInfo info, string response)
|
||||
{
|
||||
SendQuerySuccess(info.Browser, info.ContextId, info.RequestId, response);
|
||||
}
|
||||
|
||||
private void SendQuerySuccess(CefBrowser browser, int contextId, int requestId, string response)
|
||||
{
|
||||
var message = CefProcessMessage.Create(_queryMessageName);
|
||||
var args = message.Arguments;
|
||||
args.SetInt(0, contextId);
|
||||
args.SetInt(1, requestId);
|
||||
args.SetBool(2, true); // Indicates a success result.
|
||||
args.SetString(3, response);
|
||||
browser.SendProcessMessage(CefProcessId.Renderer, message);
|
||||
args.Dispose();
|
||||
message.Dispose();
|
||||
}
|
||||
|
||||
private void SendQueryFailure(QueryInfo info, int errorCode, string errorMessage)
|
||||
{
|
||||
SendQueryFailure(info.Browser, info.ContextId, info.RequestId, errorCode, errorMessage);
|
||||
}
|
||||
|
||||
private void SendQueryFailure(CefBrowser browser, int contextId, int requestId, int errorCode, string errorMessage)
|
||||
{
|
||||
var message = CefProcessMessage.Create(_queryMessageName);
|
||||
var args = message.Arguments;
|
||||
args.SetInt(0, contextId);
|
||||
args.SetInt(1, requestId);
|
||||
args.SetBool(2, false); // Indicates a failure result.
|
||||
args.SetInt(3, errorCode);
|
||||
args.SetString(4, errorMessage);
|
||||
browser.SendProcessMessage(CefProcessId.Renderer, message);
|
||||
args.Dispose();
|
||||
message.Dispose();
|
||||
}
|
||||
|
||||
// Cancel a query that has not been sent to a handler.
|
||||
private void CancelUnhandledQuery(CefBrowser browser, int contextId, int requestId)
|
||||
{
|
||||
SendQueryFailure(browser, contextId, requestId, CefMessageRouter.CanceledErrorCode, CefMessageRouter.CanceledErrorMessage);
|
||||
}
|
||||
|
||||
// Cancel a query that has already been sent to a handler.
|
||||
private void CancelQuery(long queryId, QueryInfo info, bool notifyRenderer)
|
||||
{
|
||||
if (notifyRenderer)
|
||||
SendQueryFailure(info, CefMessageRouter.CanceledErrorCode, CefMessageRouter.CanceledErrorMessage);
|
||||
|
||||
var frame = info.Browser.GetFrame(info.FrameId);
|
||||
info.Handler.OnQueryCanceled(info.Browser, frame, queryId);
|
||||
|
||||
// Invalidate the callback.
|
||||
info.Callback.Detach();
|
||||
}
|
||||
|
||||
// Cancel all pending queries associated with either |browser| or |handler|.
|
||||
// If both |browser| and |handler| are NULL all pending queries will be
|
||||
// canceled. Set |notify_renderer| to true if the renderer should be notified.
|
||||
private void CancelPendingFor(CefBrowser browser, Handler handler, bool notifyRenderer)
|
||||
{
|
||||
if (!CefRuntime.CurrentlyOn(CefThreadId.UI))
|
||||
{
|
||||
// Must execute on the UI thread.
|
||||
Helpers.PostTask(CefThreadId.UI,
|
||||
Helpers.Apply(this.CancelPendingFor, browser, handler, notifyRenderer)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_browserQueryInfoMap.IsEmpty) return;
|
||||
|
||||
BrowserInfoMap.Visitor visitor = (int browserId, long queryId, QueryInfo info, ref bool remove) =>
|
||||
{
|
||||
if (handler == null || info.Handler == handler)
|
||||
{
|
||||
remove = true;
|
||||
CancelQuery(queryId, info, notifyRenderer);
|
||||
info.Dispose();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if (browser != null)
|
||||
{
|
||||
// Cancel all queries associated with the specified browser.
|
||||
_browserQueryInfoMap.FindAll(browser.Identifier, visitor);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Cancel all queries for all browsers.
|
||||
_browserQueryInfoMap.FindAll(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
// Cancel a query based on the renderer-side IDs. If |request_id| is
|
||||
// kReservedId all requests associated with |context_id| will be canceled.
|
||||
private void CancelPendingRequest(int browserId, int contextId, int requestId)
|
||||
{
|
||||
BrowserInfoMap.Visitor visitor = (int vBrowserId, long queryId, QueryInfo info, ref bool remove) =>
|
||||
{
|
||||
if (info.ContextId == contextId
|
||||
&& (requestId == CefMessageRouter.ReservedId || info.RequestId == requestId))
|
||||
{
|
||||
remove = true;
|
||||
CancelQuery(queryId, info, false);
|
||||
info.Dispose();
|
||||
|
||||
// Stop iterating if only canceling a single request.
|
||||
return requestId == CefMessageRouter.ReservedId;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
_browserQueryInfoMap.FindAll(browserId, visitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
namespace Cef3.Wrapper
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// Used to configure the query router. The same values must be passed to both
|
||||
/// CefMessageRouterBrowserSide and CefMessageRouterRendererSide. If using multiple
|
||||
/// router pairs make sure to choose values that do not conflict.
|
||||
/// </summary>
|
||||
public sealed class CefMessageRouterConfig
|
||||
{
|
||||
public CefMessageRouterConfig()
|
||||
{
|
||||
JSQueryFunction = "cefQuery";
|
||||
JSCancelFunction = "cefQueryCancel";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Name of the JavaScript function that will be added to the 'window' object
|
||||
/// for sending a query. The default value is "cefQuery".
|
||||
/// </summary>
|
||||
public string JSQueryFunction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Name of the JavaScript function that will be added to the 'window' object
|
||||
/// for canceling a pending query. The default value is "cefQueryCancel".
|
||||
/// </summary>
|
||||
public string JSCancelFunction { get; set; }
|
||||
|
||||
// Validate configuration settings.
|
||||
internal bool Validate()
|
||||
{
|
||||
// Must specify function names.
|
||||
if (string.IsNullOrEmpty(JSQueryFunction) || string.IsNullOrEmpty(JSCancelFunction))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,535 @@
|
||||
namespace Cef3.Wrapper
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
using BrowserRequestInfoMap = CefBrowserInfoMap<System.Collections.Generic.KeyValuePair<int, int>, Cef3.Wrapper.CefMessageRouterRendererSide.RequestInfo>;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the renderer side of query routing. The methods of this class must
|
||||
/// be called on the render process main thread.
|
||||
/// </summary>
|
||||
public sealed class CefMessageRouterRendererSide
|
||||
{
|
||||
#region V8HandlerImpl
|
||||
|
||||
private sealed class V8HandlerImpl : CefV8Handler
|
||||
{
|
||||
private readonly CefMessageRouterRendererSide _router;
|
||||
private readonly CefMessageRouterConfig _config;
|
||||
private int _contextId;
|
||||
|
||||
public V8HandlerImpl(CefMessageRouterRendererSide router, CefMessageRouterConfig config)
|
||||
{
|
||||
_router = router;
|
||||
_config = config;
|
||||
_contextId = CefMessageRouter.ReservedId;
|
||||
}
|
||||
|
||||
protected override bool Execute(string name, CefV8Value obj, CefV8Value[] arguments, out CefV8Value returnValue, out string exception)
|
||||
{
|
||||
if (name == _config.JSQueryFunction)
|
||||
{
|
||||
if (arguments.Length != 1 || !arguments[0].IsObject)
|
||||
{
|
||||
returnValue = null;
|
||||
exception = "Invalid arguments; expecting a single object";
|
||||
return true;
|
||||
}
|
||||
|
||||
var arg = arguments[0];
|
||||
|
||||
var requestVal = arg.GetValue(CefMessageRouter.MemberRequest);
|
||||
if (requestVal == null || !requestVal.IsString)
|
||||
{
|
||||
returnValue = null;
|
||||
exception = "Invalid arguments; object member '" +
|
||||
CefMessageRouter.MemberRequest + "' is required and must " +
|
||||
"have type string";
|
||||
return true;
|
||||
}
|
||||
|
||||
CefV8Value successVal = null;
|
||||
if (arg.HasValue(CefMessageRouter.MemberOnSuccess))
|
||||
{
|
||||
successVal = arg.GetValue(CefMessageRouter.MemberOnSuccess);
|
||||
if (!successVal.IsFunction)
|
||||
{
|
||||
returnValue = null;
|
||||
exception = "Invalid arguments; object member '" +
|
||||
CefMessageRouter.MemberOnSuccess + "' must have type " +
|
||||
"function";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
CefV8Value failureVal = null;
|
||||
if (arg.HasValue(CefMessageRouter.MemberOnFailure))
|
||||
{
|
||||
failureVal = arg.GetValue(CefMessageRouter.MemberOnFailure);
|
||||
if (!failureVal.IsFunction)
|
||||
{
|
||||
returnValue = null;
|
||||
exception = "Invalid arguments; object member '" +
|
||||
CefMessageRouter.MemberOnFailure + "' must have type " +
|
||||
"function";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
CefV8Value persistentVal = null;
|
||||
if (arg.HasValue(CefMessageRouter.MemberPersistent))
|
||||
{
|
||||
persistentVal = arg.GetValue(CefMessageRouter.MemberPersistent);
|
||||
if (!persistentVal.IsBool)
|
||||
{
|
||||
returnValue = null;
|
||||
exception = "Invalid arguments; object member '" +
|
||||
CefMessageRouter.MemberPersistent + "' must have type " +
|
||||
"boolean";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
var context = CefV8Context.GetCurrentContext();
|
||||
var contextId = GetIDForContext(context);
|
||||
var frameId = context.GetFrame().Identifier;
|
||||
var persistent = (persistentVal != null && persistentVal.GetBoolValue());
|
||||
|
||||
var requestId = _router.SendQuery(context.GetBrowser(), frameId, contextId,
|
||||
requestVal.GetStringValue(), persistent, successVal, failureVal);
|
||||
returnValue = CefV8Value.CreateInt(requestId);
|
||||
exception = null;
|
||||
return true;
|
||||
}
|
||||
else if (name == _config.JSCancelFunction)
|
||||
{
|
||||
if (arguments.Length != 1 || !arguments[0].IsInt)
|
||||
{
|
||||
returnValue = null;
|
||||
exception = "Invalid arguments; expecting a single integer";
|
||||
return true;
|
||||
}
|
||||
|
||||
var result = false;
|
||||
var requestId = arguments[0].GetIntValue();
|
||||
if (requestId != CefMessageRouter.ReservedId)
|
||||
{
|
||||
CefV8Context context = CefV8Context.GetCurrentContext();
|
||||
var contextId = GetIDForContext(context);
|
||||
var frameId = context.GetFrame().Identifier;
|
||||
|
||||
result = _router.SendCancel(context.GetBrowser(), frameId, contextId, requestId);
|
||||
}
|
||||
returnValue = CefV8Value.CreateBool(result);
|
||||
exception = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
returnValue = null;
|
||||
exception = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't create the context ID until it's actually needed.
|
||||
private int GetIDForContext(CefV8Context context)
|
||||
{
|
||||
if (_contextId == CefMessageRouter.ReservedId)
|
||||
_contextId = _router.CreateIDForContext(context);
|
||||
return _contextId;
|
||||
}
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
private readonly CefMessageRouterConfig _config;
|
||||
|
||||
private readonly string _queryMessageName;
|
||||
private readonly string _cancelMessageName;
|
||||
|
||||
private readonly CefMessageRouter.IdGeneratorInt32 _contextIdGenerator = new CefMessageRouter.IdGeneratorInt32();
|
||||
private readonly CefMessageRouter.IdGeneratorInt32 _requestIdGenerator = new CefMessageRouter.IdGeneratorInt32();
|
||||
|
||||
// Map of (request ID, context ID) to RequestInfo for pending queries. An
|
||||
// entry is added when a request is initiated via the bound function and
|
||||
// removed when either the request completes, is canceled via the bound
|
||||
// function, or the associated context is released.
|
||||
private readonly BrowserRequestInfoMap _browserRequestInfoMap = new BrowserRequestInfoMap();
|
||||
|
||||
// Map of context ID to CefV8Context for existing contexts. An entry is added
|
||||
// when a bound function is executed for the first time in the context and
|
||||
// removed when the context is released.
|
||||
private readonly Dictionary<int, CefV8Context> _contextMap = new Dictionary<int, CefV8Context>();
|
||||
|
||||
/// <summary>
|
||||
/// Create a new router with the specified configuration.
|
||||
/// </summary>
|
||||
public CefMessageRouterRendererSide(CefMessageRouterConfig config)
|
||||
{
|
||||
if (!config.Validate()) throw new ArgumentException("Invalid configuration.");
|
||||
|
||||
_config = config;
|
||||
_queryMessageName = config.JSQueryFunction + CefMessageRouter.MessageSuffix;
|
||||
_cancelMessageName = config.JSCancelFunction + CefMessageRouter.MessageSuffix;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new router with the specified configuration.
|
||||
/// </summary>
|
||||
public static CefMessageRouterRendererSide Create(CefMessageRouterConfig config)
|
||||
{
|
||||
return new CefMessageRouterRendererSide(config);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of queries currently pending for the specified |browser|
|
||||
/// and/or |context|. Either or both values may be empty.
|
||||
/// </summary>
|
||||
public int GetPendingCount(CefBrowser browser = null, CefV8Context context = null)
|
||||
{
|
||||
Helpers.RequireRendererThread();
|
||||
|
||||
if (_browserRequestInfoMap.IsEmpty) return 0;
|
||||
|
||||
if (context != null)
|
||||
{
|
||||
var contextId = GetIDForContext(context, false);
|
||||
if (contextId == CefMessageRouter.ReservedId)
|
||||
return 0; // Nothing associated with the specified context.
|
||||
|
||||
int count = 0;
|
||||
BrowserRequestInfoMap.Visitor visitor = (int browserId, KeyValuePair<int, int> infoId, RequestInfo info, ref bool remove) =>
|
||||
{
|
||||
if (infoId.Key == contextId) count++;
|
||||
return true;
|
||||
};
|
||||
|
||||
if (browser != null)
|
||||
{
|
||||
// Count requests associated with the specified browser.
|
||||
_browserRequestInfoMap.FindAll(browser.Identifier, visitor);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Count all requests for all browsers.
|
||||
_browserRequestInfoMap.FindAll(visitor);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
else if (browser != null)
|
||||
{
|
||||
return _browserRequestInfoMap.Count(browser.Identifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
return _browserRequestInfoMap.Count();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#region The below methods should be called from other CEF handlers. They must be called exactly as documented for the router to function correctly.
|
||||
|
||||
/// <summary>
|
||||
/// Call from CefRenderProcessHandler::OnContextCreated. Registers the
|
||||
/// JavaScripts functions with the new context.
|
||||
/// </summary>
|
||||
public void OnContextCreated(CefBrowser browser, CefFrame frame, CefV8Context context)
|
||||
{
|
||||
Helpers.RequireRendererThread();
|
||||
|
||||
// Register function handlers with the 'window' object.
|
||||
var window = context.GetGlobal();
|
||||
|
||||
var handler = new V8HandlerImpl(this, _config);
|
||||
CefV8PropertyAttribute attributes = CefV8PropertyAttribute.ReadOnly | CefV8PropertyAttribute.DontEnum | CefV8PropertyAttribute.DontDelete;
|
||||
|
||||
// Add the query function.
|
||||
var queryFunc = CefV8Value.CreateFunction(_config.JSQueryFunction, handler);
|
||||
window.SetValue(_config.JSQueryFunction, queryFunc, attributes);
|
||||
|
||||
// Add the cancel function.
|
||||
var cancelFunc = CefV8Value.CreateFunction(_config.JSCancelFunction, handler);
|
||||
window.SetValue(_config.JSCancelFunction, cancelFunc, attributes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call from CefRenderProcessHandler::OnContextReleased. Any pending queries
|
||||
/// associated with the released context will be canceled and
|
||||
/// Handler::OnQueryCanceled will be called in the browser process.
|
||||
/// </summary>
|
||||
public void OnContextReleased(CefBrowser browser, CefFrame frame, CefV8Context context)
|
||||
{
|
||||
Helpers.RequireRendererThread();
|
||||
|
||||
// Get the context ID and remove the context from the map.
|
||||
var contextId = GetIDForContext(context, true);
|
||||
if (contextId != CefMessageRouter.ReservedId)
|
||||
{
|
||||
// Cancel all pending requests for the context.
|
||||
SendCancel(browser, frame.Identifier, contextId, CefMessageRouter.ReservedId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call from CefRenderProcessHandler::OnProcessMessageReceived. Returns true
|
||||
/// if the message is handled by this router or false otherwise.
|
||||
/// </summary>
|
||||
public bool OnProcessMessageReceived(CefBrowser browser, CefProcessId sourceProcess, CefProcessMessage message)
|
||||
{
|
||||
Helpers.RequireRendererThread();
|
||||
|
||||
var messageName = message.Name;
|
||||
if (messageName == _queryMessageName)
|
||||
{
|
||||
var args = message.Arguments;
|
||||
Debug.Assert(args.Count > 3);
|
||||
|
||||
var contextId = args.GetInt(0);
|
||||
var requestId = args.GetInt(1);
|
||||
var isSuccess = args.GetBool(2);
|
||||
|
||||
if (isSuccess)
|
||||
{
|
||||
Debug.Assert(args.Count == 4);
|
||||
string response = args.GetString(3);
|
||||
Helpers.PostTaskUncertainty(CefThreadId.Renderer,
|
||||
Helpers.Apply(this.ExecuteSuccessCallback, browser.Identifier, contextId, requestId, response)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(args.Count == 5);
|
||||
var errorCode = args.GetInt(3);
|
||||
string errorMessage = args.GetString(4);
|
||||
Helpers.PostTaskUncertainty(CefThreadId.Renderer,
|
||||
Helpers.Apply(this.ExecuteFailureCallback, browser.Identifier, contextId, requestId, errorCode, errorMessage)
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// Structure representing a pending request.
|
||||
internal class RequestInfo
|
||||
{
|
||||
// True if the request is persistent.
|
||||
public bool Persistent;
|
||||
|
||||
// Success callback function. May be NULL.
|
||||
public CefV8Value SuccessCallback;
|
||||
|
||||
// Failure callback function. May be NULL.
|
||||
public CefV8Value FailureCallback;
|
||||
|
||||
internal void Dispose()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
// Retrieve a RequestInfo object from the map based on the renderer-side
|
||||
// IDs. If |always_remove| is true then the RequestInfo object will always be
|
||||
// removed from the map. Othewise, the RequestInfo object will only be removed
|
||||
// if the query is non-persistent. If |removed| is true the caller is
|
||||
// responsible for deleting the returned QueryInfo object.
|
||||
private RequestInfo GetRequestInfo(int browserId, int requestId, int contextId, bool alwaysRemove, ref bool removed)
|
||||
{
|
||||
var removedTemp = false;
|
||||
BrowserRequestInfoMap.Visitor visitor = (int vBrowserId, KeyValuePair<int, int> vInfoId, RequestInfo vInfo, ref bool vRemove) =>
|
||||
{
|
||||
vRemove = removedTemp = (alwaysRemove || !vInfo.Persistent);
|
||||
return true;
|
||||
};
|
||||
|
||||
var info = _browserRequestInfoMap.Find(browserId, new KeyValuePair<int, int>(requestId, contextId), visitor);
|
||||
if (info != null) removed = removedTemp;
|
||||
return info;
|
||||
}
|
||||
|
||||
// Returns the new request ID.
|
||||
private int SendQuery(CefBrowser browser, long frameId, int contextId, string request, bool persistent, CefV8Value successCallback, CefV8Value failureCallback)
|
||||
{
|
||||
Helpers.RequireRendererThread();
|
||||
|
||||
var requestId = _requestIdGenerator.GetNextId();
|
||||
|
||||
var info = new RequestInfo
|
||||
{
|
||||
Persistent = persistent,
|
||||
SuccessCallback = successCallback,
|
||||
FailureCallback = failureCallback,
|
||||
};
|
||||
_browserRequestInfoMap.Add(browser.Identifier, new KeyValuePair<int, int>(contextId, requestId), info);
|
||||
|
||||
var message = CefProcessMessage.Create(_queryMessageName);
|
||||
var args = message.Arguments;
|
||||
args.SetInt(0, Helpers.Int64GetLow(frameId));
|
||||
args.SetInt(1, Helpers.Int64GetHigh(frameId));
|
||||
args.SetInt(2, contextId);
|
||||
args.SetInt(3, requestId);
|
||||
args.SetString(4, request);
|
||||
args.SetBool(5, persistent);
|
||||
|
||||
browser.SendProcessMessage(CefProcessId.Browser, message);
|
||||
|
||||
args.Dispose();
|
||||
message.Dispose();
|
||||
|
||||
return requestId;
|
||||
}
|
||||
|
||||
// If |requestId| is kReservedId all requests associated with |contextId|
|
||||
// will be canceled, otherwise only the specified |requestId| will be
|
||||
// canceled. Returns true if any request was canceled.
|
||||
private bool SendCancel(CefBrowser browser, long frameId, int contextId, int requestId)
|
||||
{
|
||||
Helpers.RequireRendererThread();
|
||||
|
||||
var browserId = browser.Identifier;
|
||||
|
||||
int cancelCount = 0;
|
||||
if (requestId != CefMessageRouter.ReservedId)
|
||||
{
|
||||
// Cancel a single request.
|
||||
bool removed = false;
|
||||
var info = GetRequestInfo(browserId, contextId, requestId, true, ref removed);
|
||||
if (info != null)
|
||||
{
|
||||
Debug.Assert(removed);
|
||||
info.Dispose();
|
||||
cancelCount = 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Cancel all requests with the specified context ID.
|
||||
BrowserRequestInfoMap.Visitor visitor = (int vBrowserId, KeyValuePair<int, int> vInfoId, RequestInfo vInfo, ref bool vRemove) =>
|
||||
{
|
||||
if (vInfoId.Key == contextId)
|
||||
{
|
||||
vRemove = true;
|
||||
vInfo.Dispose();
|
||||
cancelCount++;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
_browserRequestInfoMap.FindAll(browserId, visitor);
|
||||
}
|
||||
|
||||
if (cancelCount > 0)
|
||||
{
|
||||
var message = CefProcessMessage.Create(_cancelMessageName);
|
||||
|
||||
var args = message.Arguments;
|
||||
args.SetInt(0, contextId);
|
||||
args.SetInt(1, requestId);
|
||||
|
||||
browser.SendProcessMessage(CefProcessId.Browser, message);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Execute the onSuccess JavaScript callback.
|
||||
private void ExecuteSuccessCallback(int browserId, int contextId, int requestId, string response)
|
||||
{
|
||||
Helpers.RequireRendererThread();
|
||||
|
||||
bool removed = false;
|
||||
var info = GetRequestInfo(browserId, contextId, requestId, false, ref removed);
|
||||
if (info == null) return;
|
||||
|
||||
var context = GetContextByID(contextId);
|
||||
if (context != null && info.SuccessCallback != null)
|
||||
{
|
||||
var args = new[] { CefV8Value.CreateString(response) };
|
||||
info.SuccessCallback.ExecuteFunctionWithContext(context, null, args);
|
||||
}
|
||||
|
||||
if (removed) info.Dispose();
|
||||
}
|
||||
|
||||
// Execute the onFailure JavaScript callback.
|
||||
void ExecuteFailureCallback(int browserId, int contextId, int requestId,
|
||||
int errorCode, string errorMessage)
|
||||
{
|
||||
Helpers.RequireRendererThread();
|
||||
|
||||
bool removed = false;
|
||||
var info = GetRequestInfo(browserId, contextId, requestId, true, ref removed);
|
||||
if (info == null) return;
|
||||
|
||||
var context = GetContextByID(contextId);
|
||||
if (context != null && info.FailureCallback != null)
|
||||
{
|
||||
var args = new[] {
|
||||
CefV8Value.CreateInt(errorCode),
|
||||
CefV8Value.CreateString(errorMessage)
|
||||
};
|
||||
info.FailureCallback.ExecuteFunctionWithContext(context, null, args);
|
||||
}
|
||||
|
||||
Debug.Assert(removed);
|
||||
info.Dispose();
|
||||
}
|
||||
|
||||
private int CreateIDForContext(CefV8Context context)
|
||||
{
|
||||
Helpers.RequireRendererThread();
|
||||
|
||||
// The context should not already have an associated ID.
|
||||
Debug.Assert(GetIDForContext(context, false) == CefMessageRouter.ReservedId);
|
||||
|
||||
var contextId = _contextIdGenerator.GetNextId();
|
||||
_contextMap.Add(contextId, context);
|
||||
return contextId;
|
||||
}
|
||||
|
||||
// Retrieves the existing ID value associated with the specified |context|.
|
||||
// If |remove| is true the context will also be removed from the map.
|
||||
private int GetIDForContext(CefV8Context context, bool remove)
|
||||
{
|
||||
Helpers.RequireRendererThread();
|
||||
|
||||
int contextId = CefMessageRouter.ReservedId;
|
||||
int? removeContextId = null;
|
||||
foreach (var kv in _contextMap)
|
||||
{
|
||||
if (kv.Value.IsSame(context))
|
||||
{
|
||||
contextId = kv.Key;
|
||||
if (remove)
|
||||
{
|
||||
removeContextId = contextId;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (removeContextId.HasValue) _contextMap.Remove(removeContextId.Value);
|
||||
|
||||
return contextId;
|
||||
}
|
||||
|
||||
private CefV8Context GetContextByID(int contextId)
|
||||
{
|
||||
Helpers.RequireRendererThread();
|
||||
|
||||
CefV8Context context;
|
||||
if (_contextMap.TryGetValue(contextId, out context)) return context;
|
||||
else return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
namespace Cef3.Wrapper
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
internal static class Helpers
|
||||
{
|
||||
public static void PostTask(CefThreadId threadId, Action action)
|
||||
{
|
||||
CefRuntime.PostTask(threadId, new ActionTask(action));
|
||||
}
|
||||
|
||||
public static void PostTaskUncertainty(CefThreadId threadId, Action action)
|
||||
{
|
||||
CefRuntime.PostTask(threadId, new ActionTask(action));
|
||||
}
|
||||
|
||||
internal sealed class ActionTask : CefTask
|
||||
{
|
||||
public Action _action;
|
||||
|
||||
public ActionTask(Action action)
|
||||
{
|
||||
_action = action;
|
||||
}
|
||||
|
||||
protected override void Execute()
|
||||
{
|
||||
_action();
|
||||
_action = null;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void RequireUIThread()
|
||||
{
|
||||
if (!CefRuntime.CurrentlyOn(CefThreadId.UI))
|
||||
throw new InvalidOperationException("This method should be called on CEF UI thread.");
|
||||
}
|
||||
|
||||
internal static void RequireRendererThread()
|
||||
{
|
||||
if (!CefRuntime.CurrentlyOn(CefThreadId.Renderer))
|
||||
throw new InvalidOperationException("This method should be called on CEF renderer thread.");
|
||||
}
|
||||
|
||||
|
||||
|
||||
internal delegate void Action();
|
||||
internal delegate void Action<T1, T2>(T1 arg1, T2 arg2);
|
||||
internal delegate void Action<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3);
|
||||
internal delegate void Action<T1, T2, T3, T4>(T1 arg1, T2 arg2, T3 arg3, T4 arg4);
|
||||
internal delegate void Action<T1, T2, T3, T4, T5>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5);
|
||||
|
||||
//internal delegate TResult Func<TResult>();
|
||||
|
||||
//internal delegate TResult Func<T, TResult>(T arg);
|
||||
|
||||
//internal delegate TResult Func<T1, T2, TResult>(T1 arg1, T2 arg2);
|
||||
|
||||
public static Action Apply<T>(Action<T> action, T arg)
|
||||
{
|
||||
return () => action(arg);
|
||||
}
|
||||
|
||||
public static Action Apply<T1, T2>(Action<T1, T2> action, T1 arg1, T2 arg2)
|
||||
{
|
||||
return () => action(arg1, arg2);
|
||||
}
|
||||
|
||||
public static Action Apply<T1, T2, T3>(Action<T1, T2, T3> action, T1 arg1, T2 arg2, T3 arg3)
|
||||
{
|
||||
return () => action(arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
public static Action Apply<T1, T2, T3, T4>(Action<T1, T2, T3, T4> action, T1 arg1, T2 arg2, T3 arg3, T4 arg4)
|
||||
{
|
||||
return () => action(arg1, arg2, arg3, arg4);
|
||||
}
|
||||
|
||||
public static Action Apply<T1, T2, T3, T4, T5>(Action<T1, T2, T3, T4, T5> action, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5)
|
||||
{
|
||||
return () => action(arg1, arg2, arg3, arg4, arg5);
|
||||
}
|
||||
|
||||
public static long Int64Set(int low, int high)
|
||||
{
|
||||
return (uint)low | (long)high << 32;
|
||||
}
|
||||
|
||||
public static int Int64GetLow(long value)
|
||||
{
|
||||
return (int)value;
|
||||
}
|
||||
|
||||
public static int Int64GetHigh(long value)
|
||||
{
|
||||
return (int)(value >> 32);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
// The below classes implement support for routing aynchronous messages between
|
||||
// JavaScript running in the renderer process and C++ running in the browser
|
||||
// process. An application interacts with the router by passing it data from
|
||||
// standard CEF C++ callbacks (OnBeforeBrowse, OnProcessMessageRecieved,
|
||||
// OnContextCreated, etc). The renderer-side router supports generic JavaScript
|
||||
// callback registration and execution while the browser-side router supports
|
||||
// application-specific logic via one or more application-provided Handler
|
||||
// instances.
|
||||
//
|
||||
// The renderer-side router implementation exposes a query function and a cancel
|
||||
// function via the JavaScript 'window' object:
|
||||
//
|
||||
// // Create and send a new query.
|
||||
// var request_id = window.cefQuery({
|
||||
// request: 'my_request',
|
||||
// persistent: false,
|
||||
// onSuccess: function(response) {},
|
||||
// onFailure: function(error_code, error_message) {}
|
||||
// });
|
||||
//
|
||||
// // Optionally cancel the query.
|
||||
// window.cefQueryCancel(request_id);
|
||||
//
|
||||
// When |window.cefQuery| is executed the request is sent asynchronously to one
|
||||
// or more C++ Handler objects registered in the browser process. Each C++
|
||||
// Handler can choose to either handle or ignore the query in the
|
||||
// Handler::OnQuery callback. If a Handler chooses to handle the query then it
|
||||
// should execute Callback::Success when a response is available or
|
||||
// Callback::Failure if an error occurs. This will result in asynchronous
|
||||
// execution of the associated JavaScript callback in the renderer process. Any
|
||||
// queries unhandled by C++ code in the browser process will be automatically
|
||||
// canceled and the associated JavaScript onFailure callback will be executed
|
||||
// with an error code of -1.
|
||||
//
|
||||
// Queries can be either persistent or non-persistent. If the query is
|
||||
// persistent than the callbacks will remain registered until one of the
|
||||
// following conditions are met:
|
||||
//
|
||||
// A. The query is canceled in JavaScript using the |window.cefQueryCancel|
|
||||
// function.
|
||||
// B. The query is canceled in C++ code using the Callback::Failure function.
|
||||
// C. The context associated with the query is released due to browser
|
||||
// destruction, navigation or renderer process termination.
|
||||
//
|
||||
// If the query is non-persistent then the registration will be removed after
|
||||
// the JavaScript callback is executed a single time. If a query is canceled for
|
||||
// a reason other than Callback::Failure being executed then the associated
|
||||
// Handler's OnQueryCanceled method will be called.
|
||||
//
|
||||
// Some possible usage patterns include:
|
||||
//
|
||||
// One-time Request. Use a non-persistent query to send a JavaScript request.
|
||||
// The Handler evaluates the request and returns the response. The query is
|
||||
// then discarded.
|
||||
//
|
||||
// Broadcast. Use a persistent query to register as a JavaScript broadcast
|
||||
// receiver. The Handler keeps track of all registered Callbacks and executes
|
||||
// them sequentially to deliver the broadcast message.
|
||||
//
|
||||
// Subscription. Use a persistent query to register as a JavaScript subscription
|
||||
// receiver. The Handler initiates the subscription feed on the first request
|
||||
// and delivers responses to all registered subscribers as they become
|
||||
// available. The Handler cancels the subscription feed when there are no
|
||||
// longer any registered JavaScript receivers.
|
||||
//
|
||||
// Message routing occurs on a per-browser and per-context basis. Consequently,
|
||||
// additional application logic can be applied by restricting which browser or
|
||||
// context instances are passed into the router. If you choose to use this
|
||||
// approach do so cautiously. In order for the router to function correctly any
|
||||
// browser or context instance passed into a single router callback must then
|
||||
// be passed into all router callbacks.
|
||||
//
|
||||
// There is generally no need to have multiple renderer-side routers unless you
|
||||
// wish to have multiple bindings with different JavaScript function names. It
|
||||
// can be useful to have multiple browser-side routers with different client-
|
||||
// provided Handler instances when implementing different behaviors on a per-
|
||||
// browser basis.
|
||||
//
|
||||
// This implementation places no formatting restrictions on payload content.
|
||||
// An application may choose to exchange anything from simple formatted
|
||||
// strings to serialized XML or JSON data.
|
||||
//
|
||||
//
|
||||
// EXAMPLE USAGE
|
||||
//
|
||||
// 1. Define the router configuration. You can optionally specify settings
|
||||
// like the JavaScript function names. The configuration must be the same in
|
||||
// both the browser and renderer processes. If using multiple routers in the
|
||||
// same application make sure to specify unique function names for each
|
||||
// router configuration.
|
||||
//
|
||||
// // Example config object showing the default values.
|
||||
// CefMessageRouterConfig config;
|
||||
// config.js_query_function = "cefQuery";
|
||||
// config.js_cancel_function = "cefQueryCancel";
|
||||
//
|
||||
// 2. Create an instance of CefMessageRouterBrowserSide in the browser process.
|
||||
// You might choose to make it a member of your CefClient implementation,
|
||||
// for example.
|
||||
//
|
||||
// browser_side_router_ = CefMessageRouterBrowserSide::Create(config);
|
||||
//
|
||||
// 3. Register one or more Handlers. The Handler instances must either outlive
|
||||
// the router or be removed from the router before they're deleted.
|
||||
//
|
||||
// browser_side_router_->AddHandler(my_handler);
|
||||
//
|
||||
// 4. Call all required CefMessageRouterBrowserSide methods from other callbacks
|
||||
// in your CefClient implementation (OnBeforeClose, etc). See the
|
||||
// CefMessageRouterBrowserSide class documentation for the complete list of
|
||||
// methods.
|
||||
//
|
||||
// 5. Create an instance of CefMessageRouterRendererSide in the renderer process.
|
||||
// You might choose to make it a member of your CefApp implementation, for
|
||||
// example.
|
||||
//
|
||||
// renderer_side_router_ = CefMessageRouterRendererSide::Create(config);
|
||||
//
|
||||
// 6. Call all required CefMessageRouterRendererSide methods from other
|
||||
// callbacks in your CefRenderProcessHandler implementation
|
||||
// (OnContextCreated, etc). See the CefMessageRouterRendererSide class
|
||||
// documentation for the complete list of methods.
|
||||
//
|
||||
// 7. Execute the query function from JavaScript code.
|
||||
//
|
||||
// window.cefQuery({request: 'my_request',
|
||||
// persistent: false,
|
||||
// onSuccess: function(response) { print(response); },
|
||||
// onFailure: function(error_code, error_message) {} });
|
||||
//
|
||||
// 8. Handle the query in your Handler::OnQuery implementation and execute the
|
||||
// appropriate callback either immediately or asynchronously.
|
||||
//
|
||||
// void MyHandler::OnQuery(int64 query_id,
|
||||
// CefRefPtr<CefBrowser> browser,
|
||||
// CefRefPtr<CefFrame> frame,
|
||||
// const CefString& request,
|
||||
// bool persistent,
|
||||
// CefRefPtr<Callback> callback) {
|
||||
// if (request == "my_request") {
|
||||
// callback->Continue("my_response");
|
||||
// return true;
|
||||
// }
|
||||
// return false; // Not handled.
|
||||
// }
|
||||
//
|
||||
// 9. Notice that the onSuccess callback is executed in JavaScript.
|
||||
Reference in New Issue
Block a user