Files
lserp_cs_5.0/插件库/CommonLib/Cef3.24/Wrapper/MessageRouter/CefMessageRouterRendererSide.cs
T
2026-07-10 15:25:05 +08:00

536 lines
21 KiB
C#

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;
}
}
}