From c37047ea23630341d32f9036bd788d181f702cd9 Mon Sep 17 00:00:00 2001 From: Alex Maitland Date: Sun, 9 Apr 2023 21:02:45 +1000 Subject: [PATCH 1/4] JavaScript Binding - Cache objects on a per browser basis Issue #2306 --- .../CefAppUnmanagedWrapper.cpp | 42 +++------ .../CefAppUnmanagedWrapper.h | 4 +- .../RenderProcess/JavaScriptObjectCache.cs | 94 +++++++++++++++++++ 3 files changed, 109 insertions(+), 31 deletions(-) create mode 100644 CefSharp/RenderProcess/JavaScriptObjectCache.cs diff --git a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp index 14e4c66b47..199cf61450 100644 --- a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp +++ b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp @@ -77,20 +77,7 @@ namespace CefSharp { auto javascriptObjects = DeserializeJsObjects(objects, 0); - for each (JavascriptObject ^ obj in Enumerable::OfType(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); } } @@ -113,6 +100,8 @@ namespace CefSharp _onBrowserDestroyed->Invoke(wrapper); delete wrapper; } + + _javascriptObjectCache->ClearCache(browser->GetIdentifier()); }; void CefAppUnmanagedWrapper::OnContextCreated(CefRefPtr browser, CefRefPtr frame, CefRefPtr context) @@ -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()); } } @@ -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()); @@ -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(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()); diff --git a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h index 6ef917ec85..3a790cacb3 100644 --- a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h +++ b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h @@ -35,7 +35,7 @@ namespace CefSharp CefString _jsBindingPropertyNameCamelCase; // The serialized registered object data waiting to be used. - gcroot^> _javascriptObjects; + gcroot _javascriptObjectCache; gcroot _registerBoundObjectRegistry; @@ -49,7 +49,7 @@ namespace CefSharp _onBrowserDestroyed = onBrowserDestroyed; _browserWrappers = gcnew ConcurrentDictionary(); _focusedNodeChangedEnabled = enableFocusedNodeChanged; - _javascriptObjects = gcnew Dictionary(); + _javascriptObjectCache = gcnew JavaScriptObjectCache(); _registerBoundObjectRegistry = gcnew RegisterBoundObjectRegistry(); _legacyBindingEnabled = false; _jsBindingPropertyName = "CefSharp"; diff --git a/CefSharp/RenderProcess/JavaScriptObjectCache.cs b/CefSharp/RenderProcess/JavaScriptObjectCache.cs new file mode 100644 index 0000000000..2e6eae258f --- /dev/null +++ b/CefSharp/RenderProcess/JavaScriptObjectCache.cs @@ -0,0 +1,94 @@ +// 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; +using CefSharp.Internals; + +namespace CefSharp.RenderProcess +{ + /// + /// JavaScriptObjectCache is used in the RenderProcess to cache + /// JavaScript bound objects at a CefBrowser level + /// + public class JavaScriptObjectCache + { + private readonly Dictionary> cache + = new Dictionary>(); + + /// + /// Remove the Browser specific Cache + /// + /// browser Id + public void ClearCache(int browserId) + { + cache.Remove(browserId); + } + + /// + /// Insert or Update the within the Cache + /// + /// browser id + /// JavaScript object + /// + public void InsertOrUpdate(int browserId, IList javascriptObjects) + { + var dict = GetCacheInternal(browserId); + + foreach (var obj in javascriptObjects) + { + if (dict.ContainsKey(obj.Name)) + { + dict.Remove(obj.Name); + } + + dict.Add(obj.Name, obj); + } + } + + /// + /// Gets a collection of s + /// for the given + /// + /// browser Id + /// Collection of current bound objects for the browser + /// + public ICollection GetCacheValues(int browserId) + { + if (cache.TryGetValue(browserId, out var dict)) + { + return dict.Values; + } + + return new List(); + } + + /// + /// Gets the browser specific cache (dictionary) based on it's Id + /// + /// browser Id + /// Dictionary of cache 's. + /// + public Dictionary GetCache(int browserId) + { + var dict = GetCacheInternal(browserId); + + return dict; + } + + private Dictionary GetCacheInternal(int browserId) + { + Dictionary dict; + + if (!cache.TryGetValue(browserId, out dict)) + { + dict = new Dictionary(); + + cache.Add(browserId, dict); + } + + return dict; + } + } +} From 7f0bb2edfe5d11d3f0ecf438ab0432bc499f284c Mon Sep 17 00:00:00 2001 From: Alex Maitland Date: Wed, 12 Apr 2023 13:38:46 +1000 Subject: [PATCH 2/4] Legacy and PerBrowser implementations --- .../CefAppUnmanagedWrapper.h | 4 +- CefSharp/Internals/IJavaScriptObjectCache.cs | 42 ++++++++++++++++ .../Internals/LegacyJavaScriptObjectCache.cs | 50 +++++++++++++++++++ .../PerBrowserJavaScriptObjectCache.cs} | 38 ++++---------- 4 files changed, 103 insertions(+), 31 deletions(-) create mode 100644 CefSharp/Internals/IJavaScriptObjectCache.cs create mode 100644 CefSharp/Internals/LegacyJavaScriptObjectCache.cs rename CefSharp/{RenderProcess/JavaScriptObjectCache.cs => Internals/PerBrowserJavaScriptObjectCache.cs} (53%) diff --git a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h index 3a790cacb3..cbf60d91d4 100644 --- a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h +++ b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h @@ -35,7 +35,7 @@ namespace CefSharp CefString _jsBindingPropertyNameCamelCase; // The serialized registered object data waiting to be used. - gcroot _javascriptObjectCache; + gcroot _javascriptObjectCache; gcroot _registerBoundObjectRegistry; @@ -49,7 +49,7 @@ namespace CefSharp _onBrowserDestroyed = onBrowserDestroyed; _browserWrappers = gcnew ConcurrentDictionary(); _focusedNodeChangedEnabled = enableFocusedNodeChanged; - _javascriptObjectCache = gcnew JavaScriptObjectCache(); + _javascriptObjectCache = gcnew LegacyJavaScriptObjectCache(); _registerBoundObjectRegistry = gcnew RegisterBoundObjectRegistry(); _legacyBindingEnabled = false; _jsBindingPropertyName = "CefSharp"; diff --git a/CefSharp/Internals/IJavaScriptObjectCache.cs b/CefSharp/Internals/IJavaScriptObjectCache.cs new file mode 100644 index 0000000000..16ba921112 --- /dev/null +++ b/CefSharp/Internals/IJavaScriptObjectCache.cs @@ -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 +{ + /// + /// Render Process JavaScript Binding (JSB) object cache + /// + public interface IJavaScriptObjectCache + { + /// + /// Remove the Browser specific Cache + /// + /// browser Id + void ClearCache(int browserId); + /// + /// Gets the browser specific cache (dictionary) based on it's Id + /// + /// browser Id + /// Dictionary of cache 's. + /// + Dictionary GetCache(int browserId); + /// + /// Gets a collection of s + /// for the given + /// + /// browser Id + /// Collection of current bound objects for the browser + /// + ICollection GetCacheValues(int browserId); + /// + /// Insert or Update the within the Cache + /// + /// browser id + /// JavaScript object + /// + void InsertOrUpdate(int browserId, IList javascriptObjects); + } +} diff --git a/CefSharp/Internals/LegacyJavaScriptObjectCache.cs b/CefSharp/Internals/LegacyJavaScriptObjectCache.cs new file mode 100644 index 0000000000..704a730391 --- /dev/null +++ b/CefSharp/Internals/LegacyJavaScriptObjectCache.cs @@ -0,0 +1,50 @@ +// 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 +{ + /// + /// Render Process JavaScript Binding (JSB) object cache + /// Legacy Behaviour, objects are cache per process. + /// + public class LegacyJavaScriptObjectCache : IJavaScriptObjectCache + { + private readonly Dictionary cache + = new Dictionary(); + + /// + public void ClearCache(int browserId) + { + // NO OP + } + + /// + public void InsertOrUpdate(int browserId, IList javascriptObjects) + { + foreach (var obj in javascriptObjects) + { + if (cache.ContainsKey(obj.Name)) + { + cache.Remove(obj.Name); + } + + cache.Add(obj.Name, obj); + } + } + + /// + public ICollection GetCacheValues(int browserId) + { + return cache.Values; + } + + /// + public Dictionary GetCache(int browserId) + { + return cache; + } + } +} diff --git a/CefSharp/RenderProcess/JavaScriptObjectCache.cs b/CefSharp/Internals/PerBrowserJavaScriptObjectCache.cs similarity index 53% rename from CefSharp/RenderProcess/JavaScriptObjectCache.cs rename to CefSharp/Internals/PerBrowserJavaScriptObjectCache.cs index 2e6eae258f..2cb8fd0617 100644 --- a/CefSharp/RenderProcess/JavaScriptObjectCache.cs +++ b/CefSharp/Internals/PerBrowserJavaScriptObjectCache.cs @@ -4,35 +4,26 @@ using System; using System.Collections.Generic; -using CefSharp.Internals; -namespace CefSharp.RenderProcess +namespace CefSharp.Internals { /// - /// JavaScriptObjectCache is used in the RenderProcess to cache - /// JavaScript bound objects at a CefBrowser level + /// Render Process JavaScript Binding (JSB) object cache + /// Stores bound objects per CefBrowser. /// - public class JavaScriptObjectCache + public class PerBrowserJavaScriptObjectCache : IJavaScriptObjectCache { private readonly Dictionary> cache = new Dictionary>(); - /// - /// Remove the Browser specific Cache - /// - /// browser Id + /// public void ClearCache(int browserId) { cache.Remove(browserId); } - /// - /// Insert or Update the within the Cache - /// - /// browser id - /// JavaScript object - /// - public void InsertOrUpdate(int browserId, IList javascriptObjects) + /// + public void InsertOrUpdate(int browserId, IList javascriptObjects) { var dict = GetCacheInternal(browserId); @@ -47,13 +38,7 @@ public void InsertOrUpdate(int browserId, IList javascriptObj } } - /// - /// Gets a collection of s - /// for the given - /// - /// browser Id - /// Collection of current bound objects for the browser - /// + /// public ICollection GetCacheValues(int browserId) { if (cache.TryGetValue(browserId, out var dict)) @@ -64,12 +49,7 @@ public ICollection GetCacheValues(int browserId) return new List(); } - /// - /// Gets the browser specific cache (dictionary) based on it's Id - /// - /// browser Id - /// Dictionary of cache 's. - /// + /// public Dictionary GetCache(int browserId) { var dict = GetCacheInternal(browserId); From 7ca5c550ae5ccefa7cdb74761633bb042e5c60ab Mon Sep 17 00:00:00 2001 From: Alex Maitland Date: Tue, 18 Apr 2023 10:10:03 +1000 Subject: [PATCH 3/4] JSB - Add optional command line cache config --- .../CefAppUnmanagedWrapper.h | 12 ++++++++++-- CefSharp.BrowserSubprocess.Core/SubProcess.h | 3 ++- CefSharp/Internals/CefSharpArguments.cs | 1 + 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h index cbf60d91d4..69d7c34fac 100644 --- a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h +++ b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h @@ -42,18 +42,26 @@ namespace CefSharp public: static const CefString kPromiseCreatorScript; - CefAppUnmanagedWrapper(IRenderProcessHandler^ handler, List^ schemes, bool enableFocusedNodeChanged, Action^ onBrowserCreated, Action^ onBrowserDestroyed) : SubProcessApp(schemes) + CefAppUnmanagedWrapper(IRenderProcessHandler^ handler, List^ schemes, bool jsbCachePerBrowser, bool enableFocusedNodeChanged, Action^ onBrowserCreated, Action^ onBrowserDestroyed) : SubProcessApp(schemes) { _handler = handler; _onBrowserCreated = onBrowserCreated; _onBrowserDestroyed = onBrowserDestroyed; _browserWrappers = gcnew ConcurrentDictionary(); _focusedNodeChangedEnabled = enableFocusedNodeChanged; - _javascriptObjectCache = gcnew LegacyJavaScriptObjectCache(); _registerBoundObjectRegistry = gcnew RegisterBoundObjectRegistry(); _legacyBindingEnabled = false; _jsBindingPropertyName = "CefSharp"; _jsBindingPropertyNameCamelCase = "cefSharp"; + + if (jsbCachePerBrowser) + { + _javascriptObjectCache = gcnew PerBrowserJavaScriptObjectCache(); + } + else + { + _javascriptObjectCache = gcnew LegacyJavaScriptObjectCache(); + } } ~CefAppUnmanagedWrapper() diff --git a/CefSharp.BrowserSubprocess.Core/SubProcess.h b/CefSharp.BrowserSubprocess.Core/SubProcess.h index 8f988f561b..dfcd52838f 100644 --- a/CefSharp.BrowserSubprocess.Core/SubProcess.h +++ b/CefSharp.BrowserSubprocess.Core/SubProcess.h @@ -33,9 +33,10 @@ namespace CefSharp auto onBrowserCreated = gcnew Action(this, &SubProcess::OnBrowserCreated); auto onBrowserDestroyed = gcnew Action(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() diff --git a/CefSharp/Internals/CefSharpArguments.cs b/CefSharp/Internals/CefSharpArguments.cs index 707c817aea..6684e57609 100644 --- a/CefSharp/Internals/CefSharpArguments.cs +++ b/CefSharp/Internals/CefSharpArguments.cs @@ -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"; } From 8eb2376770a78cb221d544e67ab59e8299102b72 Mon Sep 17 00:00:00 2001 From: Alex Maitland Date: Fri, 21 Apr 2023 15:27:57 +1000 Subject: [PATCH 4/4] Apply suggestions from code review Co-authored-by: campersau --- CefSharp/Internals/LegacyJavaScriptObjectCache.cs | 7 +------ CefSharp/Internals/PerBrowserJavaScriptObjectCache.cs | 7 +------ 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/CefSharp/Internals/LegacyJavaScriptObjectCache.cs b/CefSharp/Internals/LegacyJavaScriptObjectCache.cs index 704a730391..4866759379 100644 --- a/CefSharp/Internals/LegacyJavaScriptObjectCache.cs +++ b/CefSharp/Internals/LegacyJavaScriptObjectCache.cs @@ -26,12 +26,7 @@ public void InsertOrUpdate(int browserId, IList javascriptObje { foreach (var obj in javascriptObjects) { - if (cache.ContainsKey(obj.Name)) - { - cache.Remove(obj.Name); - } - - cache.Add(obj.Name, obj); + cache[obj.Name] = obj; } } diff --git a/CefSharp/Internals/PerBrowserJavaScriptObjectCache.cs b/CefSharp/Internals/PerBrowserJavaScriptObjectCache.cs index 2cb8fd0617..a347eab0d3 100644 --- a/CefSharp/Internals/PerBrowserJavaScriptObjectCache.cs +++ b/CefSharp/Internals/PerBrowserJavaScriptObjectCache.cs @@ -29,12 +29,7 @@ public void InsertOrUpdate(int browserId, IList javascriptObje foreach (var obj in javascriptObjects) { - if (dict.ContainsKey(obj.Name)) - { - dict.Remove(obj.Name); - } - - dict.Add(obj.Name, obj); + dict[obj.Name] = obj; } }