Skip to content

Javascript Binding - Store JavascriptObjects per CefBrowser #4475

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

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
4 changes: 2 additions & 2 deletions CefSharp.BrowserSubprocess.Core/BindObjectAsyncHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ namespace CefSharp
{
private:
gcroot<RegisterBoundObjectRegistry^> _callbackRegistry;
gcroot<Dictionary<String^, JavascriptObject^>^> _javascriptObjects;
gcroot<IDictionary<String^, JavascriptObject^>^> _javascriptObjects;
gcroot<CefBrowserWrapper^> _browserWrapper;

public:
BindObjectAsyncHandler(RegisterBoundObjectRegistry^ callbackRegistery, Dictionary<String^, JavascriptObject^>^ javascriptObjects, CefBrowserWrapper^ browserWrapper)
BindObjectAsyncHandler(RegisterBoundObjectRegistry^ callbackRegistery, IDictionary<String^, JavascriptObject^>^ javascriptObjects, CefBrowserWrapper^ browserWrapper)
{
_callbackRegistry = callbackRegistery;
_javascriptObjects = javascriptObjects;
Expand Down
42 changes: 13 additions & 29 deletions CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,20 +82,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 @@ -118,6 +105,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 @@ -141,9 +130,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 @@ -153,13 +144,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 @@ -633,15 +625,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
4 changes: 2 additions & 2 deletions CefSharp.BrowserSubprocess.Core/RegisterBoundObjectHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ namespace CefSharp
private class RegisterBoundObjectHandler : public CefV8Handler
{
private:
gcroot<Dictionary<String^, JavascriptObject^>^> _javascriptObjects;
gcroot<IDictionary<String^, JavascriptObject^>^> _javascriptObjects;

public:
RegisterBoundObjectHandler(Dictionary<String^, JavascriptObject^>^ javascriptObjects)
RegisterBoundObjectHandler(IDictionary<String^, JavascriptObject^>^ javascriptObjects)
{
_javascriptObjects = javascriptObjects;
}
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
118 changes: 118 additions & 0 deletions CefSharp.Test/JavascriptBinding/LegacyJavaScriptObjectCacheTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
using System.Collections.Generic;
using CefSharp.Internals;
using Xunit;

namespace CefSharp.Test.JavascriptBinding
{
public class LegacyJavaScriptObjectCacheTests
{
private const int BrowserId = 1;

[Fact]
public void InsertOrUpdateShouldAddObjectsToCache()
{
var cache = new LegacyJavaScriptObjectCache();
var javascriptObjects = new List<JavascriptObject>
{
new JavascriptObject { Name = "Object1" },
new JavascriptObject { Name = "Object2" }
};

cache.InsertOrUpdate(BrowserId, javascriptObjects);

var cachedValues = cache.GetCacheValues(BrowserId);
Assert.Contains(javascriptObjects[0], cachedValues);
Assert.Contains(javascriptObjects[1], cachedValues);
}

[Fact]
public void GetCacheValuesShouldReturnAllCachedObjects()
{
var cache = new LegacyJavaScriptObjectCache();
var javascriptObjects = new List<JavascriptObject>
{
new JavascriptObject { Name = "Object1" },
new JavascriptObject { Name = "Object2" }
};
cache.InsertOrUpdate(BrowserId, javascriptObjects);

var cachedValues = cache.GetCacheValues(BrowserId);

Assert.Equal(2, cachedValues.Count);
}

[Fact]
public void GetCacheShouldReturnUnderlyingDictionary()
{
var cache = new LegacyJavaScriptObjectCache();
var javascriptObjects = new List<JavascriptObject>
{
new JavascriptObject { Name = "Object1" }
};
cache.InsertOrUpdate(BrowserId, javascriptObjects);

var cachedDictionary = cache.GetCache(BrowserId);

Assert.Single(cachedDictionary);
Assert.True(cachedDictionary.ContainsKey("Object1"));
}

[Fact]
public void InsertOrUpdateShouldReplaceExistingObjects()
{
var cache = new LegacyJavaScriptObjectCache();
var initialObjects = new List<JavascriptObject>
{
new JavascriptObject { Name = "Object1" }
};
var updatedObjects = new List<JavascriptObject>
{
new JavascriptObject { Name = "Object1" }
};
cache.InsertOrUpdate(BrowserId, initialObjects);

cache.InsertOrUpdate(BrowserId, updatedObjects);

var cachedValues = cache.GetCacheValues(BrowserId);
Assert.DoesNotContain(initialObjects[0], cachedValues);
Assert.Contains(updatedObjects[0], cachedValues);
}

[Fact]
public void InsertOrUpdateShouldAppendObjectsWithDifferentNames()
{
var cache = new LegacyJavaScriptObjectCache();
var initialObjects = new List<JavascriptObject>
{
new JavascriptObject { Name = "Object1" }
};
var updatedObjects = new List<JavascriptObject>
{
new JavascriptObject { Name = "Object2" }
};
cache.InsertOrUpdate(BrowserId, initialObjects);

cache.InsertOrUpdate(BrowserId, updatedObjects);

var cachedValues = cache.GetCacheValues(BrowserId);
Assert.Contains(initialObjects[0], cachedValues);
Assert.Contains(updatedObjects[0], cachedValues);
}

[Fact]
public void ClearCacheShouldDoNothing()
{
var cache = new LegacyJavaScriptObjectCache();
var javascriptObjects = new List<JavascriptObject>
{
new JavascriptObject { Name = "Object1" }
};
cache.InsertOrUpdate(BrowserId, javascriptObjects);

cache.ClearCache(BrowserId);

var cachedValues = cache.GetCacheValues(BrowserId);
Assert.Contains(javascriptObjects[0], cachedValues);
}
}
}
Loading