Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Javascript Binding - Store JavascriptObjects per CefBrowser #4475

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 13 additions & 29 deletions CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,20 +77,7 @@ namespace CefSharp
{
auto javascriptObjects = DeserializeJsObjects(objects, 0);

for each (JavascriptObject ^ obj in Enumerable::OfType<JavascriptObject^>(javascriptObjects))
{
//Using LegacyBinding with multiple ChromiumWebBrowser instances that share the same
//render process and using LegacyBinding will cause problems for the limited caching implementation
//that exists at the moment, for now we'll remove an object if already exists, same behaviour
//as the new binding method.
//TODO: This should be removed when https://github.com/cefsharp/CefSharp/issues/2306
//Is complete as objects will be stored at the browser level
if (_javascriptObjects->ContainsKey(obj->JavascriptName))
{
_javascriptObjects->Remove(obj->JavascriptName);
}
_javascriptObjects->Add(obj->JavascriptName, obj);
}
_javascriptObjectCache->InsertOrUpdate(browser->GetIdentifier(), javascriptObjects);
}
}

Expand All @@ -113,6 +100,8 @@ namespace CefSharp
_onBrowserDestroyed->Invoke(wrapper);
delete wrapper;
}

_javascriptObjectCache->ClearCache(browser->GetIdentifier());
};

void CefAppUnmanagedWrapper::OnContextCreated(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefV8Context> context)
Expand All @@ -130,9 +119,11 @@ namespace CefSharp

if (_legacyBindingEnabled)
{
if (_javascriptObjects->Count > 0 && rootObject != nullptr)
auto values = _javascriptObjectCache->GetCacheValues(browser->GetIdentifier());

if (values->Count > 0 && rootObject != nullptr)
{
rootObject->Bind(_javascriptObjects->Values, context->GetGlobal());
rootObject->Bind(values, context->GetGlobal());
}
}

Expand All @@ -142,13 +133,14 @@ namespace CefSharp
auto global = context->GetGlobal();
auto browserWrapper = FindBrowserWrapper(browser->GetIdentifier());
auto processId = System::Diagnostics::Process::GetCurrentProcess()->Id;
auto objectCache = _javascriptObjectCache->GetCache(browser->GetIdentifier());

//TODO: JSB: Split functions into their own classes
//Browser wrapper is only used for BindObjectAsync
auto bindObjAsyncFunction = CefV8Value::CreateFunction(kBindObjectAsync, new BindObjectAsyncHandler(_registerBoundObjectRegistry, _javascriptObjects, browserWrapper));
auto unBindObjFunction = CefV8Value::CreateFunction(kDeleteBoundObject, new RegisterBoundObjectHandler(_javascriptObjects));
auto removeObjectFromCacheFunction = CefV8Value::CreateFunction(kRemoveObjectFromCache, new RegisterBoundObjectHandler(_javascriptObjects));
auto isObjectCachedFunction = CefV8Value::CreateFunction(kIsObjectCached, new RegisterBoundObjectHandler(_javascriptObjects));
auto bindObjAsyncFunction = CefV8Value::CreateFunction(kBindObjectAsync, new BindObjectAsyncHandler(_registerBoundObjectRegistry, objectCache, browserWrapper));
auto unBindObjFunction = CefV8Value::CreateFunction(kDeleteBoundObject, new RegisterBoundObjectHandler(objectCache));
auto removeObjectFromCacheFunction = CefV8Value::CreateFunction(kRemoveObjectFromCache, new RegisterBoundObjectHandler(objectCache));
auto isObjectCachedFunction = CefV8Value::CreateFunction(kIsObjectCached, new RegisterBoundObjectHandler(objectCache));
auto postMessageFunction = CefV8Value::CreateFunction(kPostMessage, new JavascriptPostMessageHandler(rootObject == nullptr ? nullptr : rootObject->CallbackRegistry));
auto promiseHandlerFunction = CefV8Value::CreateFunction(kSendEvalScriptResponse, new JavascriptPromiseHandler());

Expand Down Expand Up @@ -621,15 +613,7 @@ namespace CefSharp
auto javascriptObjects = DeserializeJsObjects(argList, 1);

//Caching of JavascriptObjects
//TODO: JSB Should caching be configurable? On a per object basis?
for each (JavascriptObject ^ obj in Enumerable::OfType<JavascriptObject^>(javascriptObjects))
{
if (_javascriptObjects->ContainsKey(obj->JavascriptName))
{
_javascriptObjects->Remove(obj->JavascriptName);
}
_javascriptObjects->Add(obj->JavascriptName, obj);
}
_javascriptObjectCache->InsertOrUpdate(browser->GetIdentifier(), javascriptObjects);

auto rootObject = GetJsRootObjectWrapper(browser->GetIdentifier(), frame->GetIdentifier());

Expand Down
14 changes: 11 additions & 3 deletions CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,25 +35,33 @@ namespace CefSharp
CefString _jsBindingPropertyNameCamelCase;

// The serialized registered object data waiting to be used.
gcroot<Dictionary<String^, JavascriptObject^>^> _javascriptObjects;
gcroot<IJavaScriptObjectCache^> _javascriptObjectCache;

gcroot<RegisterBoundObjectRegistry^> _registerBoundObjectRegistry;

public:
static const CefString kPromiseCreatorScript;

CefAppUnmanagedWrapper(IRenderProcessHandler^ handler, List<CefCustomScheme^>^ schemes, bool enableFocusedNodeChanged, Action<CefBrowserWrapper^>^ onBrowserCreated, Action<CefBrowserWrapper^>^ onBrowserDestroyed) : SubProcessApp(schemes)
CefAppUnmanagedWrapper(IRenderProcessHandler^ handler, List<CefCustomScheme^>^ schemes, bool jsbCachePerBrowser, bool enableFocusedNodeChanged, Action<CefBrowserWrapper^>^ onBrowserCreated, Action<CefBrowserWrapper^>^ onBrowserDestroyed) : SubProcessApp(schemes)
{
_handler = handler;
_onBrowserCreated = onBrowserCreated;
_onBrowserDestroyed = onBrowserDestroyed;
_browserWrappers = gcnew ConcurrentDictionary<int, CefBrowserWrapper^>();
_focusedNodeChangedEnabled = enableFocusedNodeChanged;
_javascriptObjects = gcnew Dictionary<String^, JavascriptObject^>();
_registerBoundObjectRegistry = gcnew RegisterBoundObjectRegistry();
_legacyBindingEnabled = false;
_jsBindingPropertyName = "CefSharp";
_jsBindingPropertyNameCamelCase = "cefSharp";

if (jsbCachePerBrowser)
{
_javascriptObjectCache = gcnew PerBrowserJavaScriptObjectCache();
}
else
{
_javascriptObjectCache = gcnew LegacyJavaScriptObjectCache();
}
}

~CefAppUnmanagedWrapper()
Expand Down
3 changes: 2 additions & 1 deletion CefSharp.BrowserSubprocess.Core/SubProcess.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@ namespace CefSharp
auto onBrowserCreated = gcnew Action<CefBrowserWrapper^>(this, &SubProcess::OnBrowserCreated);
auto onBrowserDestroyed = gcnew Action<CefBrowserWrapper^>(this, &SubProcess::OnBrowserDestroyed);
auto schemes = CefCustomScheme::ParseCommandLineArguments(args);
auto jsbCachePerBrowser = CommandLineArgsParser::HasArgument(args, CefSharpArguments::PerBrowserJavaScriptObjectCache);
auto enableFocusedNodeChanged = CommandLineArgsParser::HasArgument(args, CefSharpArguments::FocusedNodeChangedEnabledArgument);

_cefApp = new CefAppUnmanagedWrapper(handler, schemes, enableFocusedNodeChanged, onBrowserCreated, onBrowserDestroyed);
_cefApp = new CefAppUnmanagedWrapper(handler, schemes, jsbCachePerBrowser, enableFocusedNodeChanged, onBrowserCreated, onBrowserDestroyed);
}

!SubProcess()
Expand Down
1 change: 1 addition & 0 deletions CefSharp/Internals/CefSharpArguments.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public static class CefSharpArguments
public const string HostProcessIdArgument = "--host-process-id";
public const string CustomSchemeArgument = "--custom-scheme";
public const string FocusedNodeChangedEnabledArgument = "--focused-node-enabled";
public const string PerBrowserJavaScriptObjectCache = "--jsb-cache-perbrowser";
public const string SubProcessTypeArgument = "--type";
public const string ExitIfParentProcessClosed = "--cefsharpexitsub";
}
Expand Down
42 changes: 42 additions & 0 deletions CefSharp/Internals/IJavaScriptObjectCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright © 2023 The CefSharp Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

using System.Collections.Generic;

namespace CefSharp.Internals
{
/// <summary>
/// Render Process JavaScript Binding (JSB) object cache
/// </summary>
public interface IJavaScriptObjectCache
{
/// <summary>
/// Remove the Browser specific Cache
/// </summary>
/// <param name="browserId">browser Id</param>
void ClearCache(int browserId);
/// <summary>
/// Gets the browser specific cache (dictionary) based on it's Id
/// </summary>
/// <param name="browserId">browser Id</param>
/// <returns>Dictionary of cache <see cref="JavascriptObject"/>'s.</returns>
/// <exception cref="InvalidOperationException"></exception>
Dictionary<string, JavascriptObject> GetCache(int browserId);
/// <summary>
/// Gets a collection of <see cref="JavascriptObject"/>s
/// for the given <paramref name="browserId"/>
/// </summary>
/// <param name="browserId">browser Id</param>
/// <returns>Collection of current bound objects for the browser</returns>
/// <exception cref="InvalidOperationException"></exception>
ICollection<JavascriptObject> GetCacheValues(int browserId);
/// <summary>
/// Insert or Update the <paramref name="javascriptObject"/> within the Cache
/// </summary>
/// <param name="browserId">browser id</param>
/// <param name="javascriptObject">JavaScript object</param>
/// <exception cref="InvalidOperationException"></exception>
void InsertOrUpdate(int browserId, IList<JavascriptObject> javascriptObjects);
}
}
45 changes: 45 additions & 0 deletions CefSharp/Internals/LegacyJavaScriptObjectCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright © 2023 The CefSharp Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

using System.Collections.Generic;

namespace CefSharp.Internals
{
/// <summary>
/// Render Process JavaScript Binding (JSB) object cache
/// Legacy Behaviour, objects are cache per process.
/// </summary>
public class LegacyJavaScriptObjectCache : IJavaScriptObjectCache
{
private readonly Dictionary<string, JavascriptObject> cache
= new Dictionary<string, JavascriptObject>();

/// <inheritdoc/>
public void ClearCache(int browserId)
{
// NO OP
}

/// <inheritdoc/>
public void InsertOrUpdate(int browserId, IList<JavascriptObject> javascriptObjects)
{
foreach (var obj in javascriptObjects)
{
cache[obj.Name] = obj;
}
}

/// <inheritdoc/>
public ICollection<JavascriptObject> GetCacheValues(int browserId)
{
return cache.Values;
}

/// <inheritdoc/>
public Dictionary<string, JavascriptObject> GetCache(int browserId)
{
return cache;
}
}
}
69 changes: 69 additions & 0 deletions CefSharp/Internals/PerBrowserJavaScriptObjectCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright © 2023 The CefSharp Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

using System;
using System.Collections.Generic;

namespace CefSharp.Internals
{
/// <summary>
/// Render Process JavaScript Binding (JSB) object cache
/// Stores bound objects per CefBrowser.
/// </summary>
public class PerBrowserJavaScriptObjectCache : IJavaScriptObjectCache
{
private readonly Dictionary<int, Dictionary<string, JavascriptObject>> cache
= new Dictionary<int, Dictionary<string, JavascriptObject>>();

/// <inheritdoc/>
public void ClearCache(int browserId)
{
cache.Remove(browserId);
}

/// <inheritdoc/>
public void InsertOrUpdate(int browserId, IList<JavascriptObject> javascriptObjects)
{
var dict = GetCacheInternal(browserId);

foreach (var obj in javascriptObjects)
{
dict[obj.Name] = obj;
}
}

/// <inheritdoc/>
public ICollection<JavascriptObject> GetCacheValues(int browserId)
{
if (cache.TryGetValue(browserId, out var dict))
{
return dict.Values;
}

return new List<JavascriptObject>();
}

/// <inheritdoc/>
public Dictionary<string, JavascriptObject> GetCache(int browserId)
{
var dict = GetCacheInternal(browserId);

return dict;
}

private Dictionary<string, JavascriptObject> GetCacheInternal(int browserId)
{
Dictionary<string, JavascriptObject> dict;

if (!cache.TryGetValue(browserId, out dict))
{
dict = new Dictionary<string, JavascriptObject>();

cache.Add(browserId, dict);
}

return dict;
}
}
}