From 8b44739ed7a4f77fb08cb3199c8108fe5e419614 Mon Sep 17 00:00:00 2001 From: amaitland <307872+amaitland@users.noreply.github.com> Date: Sat, 29 Nov 2025 08:15:14 +1000 Subject: [PATCH 1/8] JavascriptCallbackRegistry - Get BrowserId From V8Context --- CefSharp.BrowserSubprocess.Core/BindObjectAsyncHandler.h | 4 ++-- .../CefAppUnmanagedWrapper.cpp | 8 ++++---- .../JavascriptCallbackRegistry.cpp | 3 ++- .../JavascriptCallbackRegistry.h | 3 +-- .../JavascriptRootObjectWrapper.h | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/CefSharp.BrowserSubprocess.Core/BindObjectAsyncHandler.h b/CefSharp.BrowserSubprocess.Core/BindObjectAsyncHandler.h index 9ece880b2b..130629f2b7 100644 --- a/CefSharp.BrowserSubprocess.Core/BindObjectAsyncHandler.h +++ b/CefSharp.BrowserSubprocess.Core/BindObjectAsyncHandler.h @@ -154,9 +154,9 @@ namespace CefSharp if (!rootObjectWrappers->TryGetValue(StringUtils::ToClr(frame->GetIdentifier()), rootObject)) { #ifdef NETCOREAPP - rootObject = gcnew JavascriptRootObjectWrapper(browser->GetIdentifier()); + rootObject = gcnew JavascriptRootObjectWrapper(); #else - rootObject = gcnew JavascriptRootObjectWrapper(browser->GetIdentifier(), _browserWrapper->BrowserProcess); + rootObject = gcnew JavascriptRootObjectWrapper(_browserWrapper->BrowserProcess); #endif rootObjectWrappers->TryAdd(StringUtils::ToClr(frame->GetIdentifier()), rootObject); } diff --git a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp index a7b2ceb1f3..93bea8a773 100644 --- a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp +++ b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp @@ -319,9 +319,9 @@ namespace CefSharp if (!rootObjectWrappers->TryGetValue(frameIdClr, rootObject)) { #ifdef NETCOREAPP - rootObject = gcnew JavascriptRootObjectWrapper(browserId); + rootObject = gcnew JavascriptRootObjectWrapper(); #else - rootObject = gcnew JavascriptRootObjectWrapper(browserId, browserWrapper->BrowserProcess); + rootObject = gcnew JavascriptRootObjectWrapper(browserWrapper->BrowserProcess); #endif rootObjectWrappers->TryAdd(frameIdClr, rootObject); } @@ -426,9 +426,9 @@ namespace CefSharp if (rootObjectWrapper == nullptr) { #ifdef NETCOREAPP - rootObjectWrapper = gcnew JavascriptRootObjectWrapper(browser->GetIdentifier()); + rootObjectWrapper = gcnew JavascriptRootObjectWrapper(); #else - rootObjectWrapper = gcnew JavascriptRootObjectWrapper(browser->GetIdentifier(), browserWrapper->BrowserProcess); + rootObjectWrapper = gcnew JavascriptRootObjectWrapper(browserWrapper->BrowserProcess); #endif browserWrapper->JavascriptRootObjectWrappers->TryAdd(frameId, rootObjectWrapper); diff --git a/CefSharp.BrowserSubprocess.Core/JavascriptCallbackRegistry.cpp b/CefSharp.BrowserSubprocess.Core/JavascriptCallbackRegistry.cpp index 01f6da2326..a9389a9ca9 100644 --- a/CefSharp.BrowserSubprocess.Core/JavascriptCallbackRegistry.cpp +++ b/CefSharp.BrowserSubprocess.Core/JavascriptCallbackRegistry.cpp @@ -18,9 +18,10 @@ namespace CefSharp JavascriptCallbackWrapper^ wrapper = gcnew JavascriptCallbackWrapper(value, context); _callbacks->TryAdd(newId, wrapper); + auto result = gcnew JavascriptCallback(); result->Id = newId; - result->BrowserId = _browserId; + result->BrowserId = context->GetBrowser()->GetIdentifier(); result->FrameId = StringUtils::ToClr(context->GetFrame()->GetIdentifier()); return result; } diff --git a/CefSharp.BrowserSubprocess.Core/JavascriptCallbackRegistry.h b/CefSharp.BrowserSubprocess.Core/JavascriptCallbackRegistry.h index dd0af41f40..b2872055a9 100644 --- a/CefSharp.BrowserSubprocess.Core/JavascriptCallbackRegistry.h +++ b/CefSharp.BrowserSubprocess.Core/JavascriptCallbackRegistry.h @@ -20,14 +20,13 @@ namespace CefSharp //Is static so ids are unique to this process, which is required until #1984 is implemented //and callbacks are disposed of properly between contexts static Int64 _lastId; - int _browserId; ConcurrentDictionary^ _callbacks; internal: JavascriptCallbackWrapper^ FindWrapper(int64_t id); public: - JavascriptCallbackRegistry(int browserId) : _browserId(browserId) + JavascriptCallbackRegistry() { _callbacks = gcnew ConcurrentDictionary(); } diff --git a/CefSharp.BrowserSubprocess.Core/JavascriptRootObjectWrapper.h b/CefSharp.BrowserSubprocess.Core/JavascriptRootObjectWrapper.h index 45b94b8ed4..ae2add0112 100644 --- a/CefSharp.BrowserSubprocess.Core/JavascriptRootObjectWrapper.h +++ b/CefSharp.BrowserSubprocess.Core/JavascriptRootObjectWrapper.h @@ -57,9 +57,9 @@ namespace CefSharp public: #ifdef NETCOREAPP - JavascriptRootObjectWrapper(int browserId) + JavascriptRootObjectWrapper() #else - JavascriptRootObjectWrapper(int browserId, IBrowserProcess^ browserProcess) + JavascriptRootObjectWrapper(IBrowserProcess^ browserProcess) #endif { #ifndef NETCOREAPP @@ -67,7 +67,7 @@ namespace CefSharp _wrappedObjects = gcnew List(); #endif _wrappedAsyncObjects = gcnew List(); - _callbackRegistry = gcnew JavascriptCallbackRegistry(browserId); + _callbackRegistry = gcnew JavascriptCallbackRegistry(); _methodCallbacks = gcnew Dictionary(); } From 13c87c077606eaf1000c521f6a8b1a476720c0d9 Mon Sep 17 00:00:00 2001 From: amaitland <307872+amaitland@users.noreply.github.com> Date: Sun, 30 Nov 2025 10:06:10 +1000 Subject: [PATCH 2/8] Render Process - Rewrite to store root object wrapper by frame instead of per browser - Don't store JavascriptRootObjectWrapper in the browserWrapper as CEF is calling destroyed more than it should. - --- .../BindObjectAsyncHandler.h | 25 ++---- .../CefAppUnmanagedWrapper.cpp | 85 +++++-------------- .../CefAppUnmanagedWrapper.h | 13 +++ .../CefBrowserWrapper.h | 16 ---- 4 files changed, 42 insertions(+), 97 deletions(-) diff --git a/CefSharp.BrowserSubprocess.Core/BindObjectAsyncHandler.h b/CefSharp.BrowserSubprocess.Core/BindObjectAsyncHandler.h index 130629f2b7..1a99952071 100644 --- a/CefSharp.BrowserSubprocess.Core/BindObjectAsyncHandler.h +++ b/CefSharp.BrowserSubprocess.Core/BindObjectAsyncHandler.h @@ -26,21 +26,21 @@ namespace CefSharp private: gcroot _callbackRegistry; gcroot^> _javascriptObjects; - gcroot _browserWrapper; + gcroot _javascriptRootObjectWrapper; public: - BindObjectAsyncHandler(RegisterBoundObjectRegistry^ callbackRegistery, Dictionary^ javascriptObjects, CefBrowserWrapper^ browserWrapper) + BindObjectAsyncHandler(RegisterBoundObjectRegistry^ callbackRegistery, Dictionary^ javascriptObjects, JavascriptRootObjectWrapper^ javascriptRootObjectWrapper) { _callbackRegistry = callbackRegistery; _javascriptObjects = javascriptObjects; - _browserWrapper = browserWrapper; + _javascriptRootObjectWrapper = javascriptRootObjectWrapper; } ~BindObjectAsyncHandler() { _callbackRegistry = nullptr; _javascriptObjects = nullptr; - _browserWrapper = nullptr; + _javascriptRootObjectWrapper = nullptr; } bool Execute(const CefString& name, CefRefPtr object, const CefV8ValueList& arguments, CefRefPtr& retval, CefString& exception) override @@ -139,27 +139,16 @@ namespace CefSharp //https://github.com/cefsharp/CefSharp/issues/3470 if (objectCount > 0 && cachedObjects->Count == objectCount && ignoreCache == false) { - if (Object::ReferenceEquals(_browserWrapper, nullptr)) + if (Object::ReferenceEquals(_javascriptRootObjectWrapper, nullptr)) { - exception = "BindObjectAsyncHandler::Execute - Browser wrapper null, unable to bind objects"; + exception = "BindObjectAsyncHandler::Execute - _javascriptRootObjectWrapper null, unable to bind objects"; return true; } auto browser = context->GetBrowser(); - auto rootObjectWrappers = _browserWrapper->JavascriptRootObjectWrappers; - - JavascriptRootObjectWrapper^ rootObject; - if (!rootObjectWrappers->TryGetValue(StringUtils::ToClr(frame->GetIdentifier()), rootObject)) - { -#ifdef NETCOREAPP - rootObject = gcnew JavascriptRootObjectWrapper(); -#else - rootObject = gcnew JavascriptRootObjectWrapper(_browserWrapper->BrowserProcess); -#endif - rootObjectWrappers->TryAdd(StringUtils::ToClr(frame->GetIdentifier()), rootObject); - } + JavascriptRootObjectWrapper^ rootObject = _javascriptRootObjectWrapper; //Cached objects only contains a list of objects not already bound rootObject->Bind(cachedObjects, context->GetGlobal()); diff --git a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp index 93bea8a773..ee216e015c 100644 --- a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp +++ b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp @@ -156,7 +156,7 @@ namespace CefSharp //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 bindObjAsyncFunction = CefV8Value::CreateFunction(kBindObjectAsync, new BindObjectAsyncHandler(_registerBoundObjectRegistry, _javascriptObjects, rootObject)); auto unBindObjFunction = CefV8Value::CreateFunction(kDeleteBoundObject, new RegisterBoundObjectHandler(_javascriptObjects)); auto removeObjectFromCacheFunction = CefV8Value::CreateFunction(kRemoveObjectFromCache, new RegisterBoundObjectHandler(_javascriptObjects)); auto isObjectCachedFunction = CefV8Value::CreateFunction(kIsObjectCached, new RegisterBoundObjectHandler(_javascriptObjects)); @@ -220,16 +220,14 @@ namespace CefSharp frame->SendProcessMessage(CefProcessId::PID_BROWSER, contextReleasedMessage); - auto browserWrapper = FindBrowserWrapper(browser->GetIdentifier()); + auto rootObjectWrappers = _jsRootObjectWrappersByFrameId; - //If we no longer have a browser wrapper reference then there's nothing we can do - if (browserWrapper == nullptr) + //If we no longer have a _jsRootObjectWrappersByFrameId reference then there's nothing we can do + if (Object::ReferenceEquals(rootObjectWrappers, nullptr)) { return; } - auto rootObjectWrappers = browserWrapper->JavascriptRootObjectWrappers; - JavascriptRootObjectWrapper^ wrapper; if (rootObjectWrappers->TryRemove(StringUtils::ToClr(frame->GetIdentifier()), wrapper)) { @@ -305,14 +303,7 @@ namespace CefSharp JavascriptRootObjectWrapper^ CefAppUnmanagedWrapper::GetJsRootObjectWrapper(int browserId, CefString& frameId) { - auto browserWrapper = FindBrowserWrapper(browserId); - - if (browserWrapper == nullptr) - { - return nullptr; - } - - auto rootObjectWrappers = browserWrapper->JavascriptRootObjectWrappers; + auto rootObjectWrappers = _jsRootObjectWrappersByFrameId; auto frameIdClr = StringUtils::ToClr(frameId); JavascriptRootObjectWrapper^ rootObject; @@ -321,6 +312,13 @@ namespace CefSharp #ifdef NETCOREAPP rootObject = gcnew JavascriptRootObjectWrapper(); #else + auto browserWrapper = FindBrowserWrapper(browserId); + + if (browserWrapper == nullptr) + { + return nullptr; + } + rootObject = gcnew JavascriptRootObjectWrapper(browserWrapper->BrowserProcess); #endif rootObjectWrappers->TryAdd(frameIdClr, rootObject); @@ -350,49 +348,6 @@ namespace CefSharp auto name = message->GetName(); auto argList = message->GetArgumentList(); - auto browserWrapper = FindBrowserWrapper(browser->GetIdentifier()); - //Error handling for missing/closed browser - if (browserWrapper == nullptr) - { - if (name == kJavascriptCallbackDestroyRequest || - name == kJavascriptRootObjectResponse || - name == kJavascriptAsyncMethodCallResponse) - { - //If we can't find the browser wrapper then we'll just - //ignore this as it's likely already been disposed of - return true; - } - - CefString responseName; - if (name == kEvaluateJavascriptRequest) - { - responseName = kEvaluateJavascriptResponse; - } - else if (name == kJavascriptCallbackRequest) - { - responseName = kJavascriptCallbackResponse; - } - else - { - //TODO: Should be throw an exception here? It's likely that only a CefSharp developer would see this - // when they added a new message and haven't yet implemented the render process functionality. - throw gcnew Exception("Unsupported message type"); - } - - auto callbackId = GetInt64(argList, 0); - auto response = CefProcessMessage::Create(responseName); - auto responseArgList = response->GetArgumentList(); - auto errorMessage = String::Format("Request BrowserId : {0} not found it's likely the browser is already closed", browser->GetIdentifier()); - - //success: false - responseArgList->SetBool(0, false); - SetInt64(responseArgList, 1, callbackId); - responseArgList->SetString(2, StringUtils::ToNative(errorMessage)); - frame->SendProcessMessage(sourceProcessId, response); - - return true; - } - //these messages are roughly handled the same way if (name == kEvaluateJavascriptRequest || name == kJavascriptCallbackRequest) { @@ -418,7 +373,7 @@ namespace CefSharp if (name == kEvaluateJavascriptRequest) { JavascriptRootObjectWrapper^ rootObjectWrapper; - browserWrapper->JavascriptRootObjectWrappers->TryGetValue(frameId, rootObjectWrapper); + _jsRootObjectWrappersByFrameId->TryGetValue(frameId, rootObjectWrapper); //NOTE: In the rare case when when OnContextCreated hasn't been called we need to manually create the rootObjectWrapper //It appears that OnContextCreated is only called for pages that have javascript on them, which makes sense @@ -428,10 +383,14 @@ namespace CefSharp #ifdef NETCOREAPP rootObjectWrapper = gcnew JavascriptRootObjectWrapper(); #else - rootObjectWrapper = gcnew JavascriptRootObjectWrapper(browserWrapper->BrowserProcess); + auto browserWrapper = FindBrowserWrapper(browser->GetIdentifier()); + + // If we cannot find the browserWrapper then we'll pass in nullptr which will disable + // the use of sync bound objects (async will still work). + rootObjectWrapper = gcnew JavascriptRootObjectWrapper(browserWrapper == nullptr ? nullptr : browserWrapper->BrowserProcess); #endif - browserWrapper->JavascriptRootObjectWrappers->TryAdd(frameId, rootObjectWrapper); + _jsRootObjectWrappersByFrameId->TryAdd(frameId, rootObjectWrapper); } auto callbackRegistry = rootObjectWrapper->CallbackRegistry; @@ -507,7 +466,7 @@ namespace CefSharp else { JavascriptRootObjectWrapper^ rootObjectWrapper; - browserWrapper->JavascriptRootObjectWrappers->TryGetValue(frameId, rootObjectWrapper); + _jsRootObjectWrappersByFrameId->TryGetValue(frameId, rootObjectWrapper); auto callbackRegistry = rootObjectWrapper == nullptr ? nullptr : rootObjectWrapper->CallbackRegistry; if (callbackRegistry == nullptr) { @@ -616,7 +575,7 @@ namespace CefSharp { auto jsCallbackId = GetInt64(argList, 0); JavascriptRootObjectWrapper^ rootObjectWrapper; - browserWrapper->JavascriptRootObjectWrappers->TryGetValue(StringUtils::ToClr(frame->GetIdentifier()), rootObjectWrapper); + _jsRootObjectWrappersByFrameId->TryGetValue(StringUtils::ToClr(frame->GetIdentifier()), rootObjectWrapper); if (rootObjectWrapper != nullptr && rootObjectWrapper->CallbackRegistry != nullptr) { rootObjectWrapper->CallbackRegistry->Deregister(jsCallbackId); @@ -728,7 +687,7 @@ namespace CefSharp auto callbackId = GetInt64(argList, 0); JavascriptRootObjectWrapper^ rootObjectWrapper; - browserWrapper->JavascriptRootObjectWrappers->TryGetValue(frameId, rootObjectWrapper); + _jsRootObjectWrappersByFrameId->TryGetValue(frameId, rootObjectWrapper); if (rootObjectWrapper != nullptr) { diff --git a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h index 38f8da2881..f91d75572f 100644 --- a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h +++ b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h @@ -26,6 +26,7 @@ namespace CefSharp gcroot^> _onBrowserCreated; gcroot^> _onBrowserDestroyed; gcroot^> _browserWrappers; + gcroot^> _jsRootObjectWrappersByFrameId; bool _focusedNodeChangedEnabled; bool _legacyBindingEnabled; bool _jsBindingApiEnabled = true; @@ -48,6 +49,7 @@ namespace CefSharp _onBrowserCreated = onBrowserCreated; _onBrowserDestroyed = onBrowserDestroyed; _browserWrappers = gcnew ConcurrentDictionary(); + _jsRootObjectWrappersByFrameId = gcnew ConcurrentDictionary(); _focusedNodeChangedEnabled = enableFocusedNodeChanged; _javascriptObjects = gcnew Dictionary(); _registerBoundObjectRegistry = gcnew RegisterBoundObjectRegistry(); @@ -67,6 +69,17 @@ namespace CefSharp _browserWrappers = nullptr; } + + if (!Object::ReferenceEquals(_jsRootObjectWrappersByFrameId, nullptr)) + { + for each (JavascriptRootObjectWrapper^ rootObject in Enumerable::OfType(_browserWrappers)) + { + delete rootObject; + } + + _jsRootObjectWrappersByFrameId = nullptr; + } + delete _onBrowserCreated; delete _onBrowserDestroyed; } diff --git a/CefSharp.BrowserSubprocess.Core/CefBrowserWrapper.h b/CefSharp.BrowserSubprocess.Core/CefBrowserWrapper.h index ef9ef6dfb5..12ce17ba47 100644 --- a/CefSharp.BrowserSubprocess.Core/CefBrowserWrapper.h +++ b/CefSharp.BrowserSubprocess.Core/CefBrowserWrapper.h @@ -29,18 +29,12 @@ namespace CefSharp private: MCefRefPtr _cefBrowser; - internal: - //Frame Identifier is used as Key - property ConcurrentDictionary^ JavascriptRootObjectWrappers; - public: CefBrowserWrapper(CefRefPtr cefBrowser) { _cefBrowser = cefBrowser.get(); BrowserId = cefBrowser->GetIdentifier(); IsPopup = cefBrowser->IsPopup(); - - JavascriptRootObjectWrappers = gcnew ConcurrentDictionary(); } !CefBrowserWrapper() @@ -51,16 +45,6 @@ namespace CefSharp ~CefBrowserWrapper() { this->!CefBrowserWrapper(); - - if (JavascriptRootObjectWrappers != nullptr) - { - for each (KeyValuePair entry in JavascriptRootObjectWrappers) - { - delete entry.Value; - } - - JavascriptRootObjectWrappers = nullptr; - } } property int BrowserId; From da9cbe538fbf82c27e6a5a3eea522ceb38ac6851 Mon Sep 17 00:00:00 2001 From: amaitland <307872+amaitland@users.noreply.github.com> Date: Sun, 30 Nov 2025 10:27:55 +1000 Subject: [PATCH 3/8] Reduce code duplication --- .../CefAppUnmanagedWrapper.cpp | 39 ++++--------------- 1 file changed, 8 insertions(+), 31 deletions(-) diff --git a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp index ee216e015c..5979f58ae4 100644 --- a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp +++ b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp @@ -314,12 +314,7 @@ namespace CefSharp #else auto browserWrapper = FindBrowserWrapper(browserId); - if (browserWrapper == nullptr) - { - return nullptr; - } - - rootObject = gcnew JavascriptRootObjectWrapper(browserWrapper->BrowserProcess); + rootObject = gcnew JavascriptRootObjectWrapper(browserWrapper == nullptr ? nullptr : browserWrapper->BrowserProcess); #endif rootObjectWrappers->TryAdd(frameIdClr, rootObject); } @@ -370,31 +365,13 @@ namespace CefSharp auto frameId = StringUtils::ToClr(frame->GetIdentifier()); int64_t callbackId = GetInt64(argList, 0); + //NOTE: In the rare case when when OnContextCreated hasn't been called we need to manually create the rootObjectWrapper + //It appears that OnContextCreated is only called for pages that have javascript on them, which makes sense + //as without javascript there is no need for a context. + JavascriptRootObjectWrapper^ rootObjectWrapper = GetJsRootObjectWrapper(browser->GetIdentifier(), frame->GetIdentifier()); + if (name == kEvaluateJavascriptRequest) { - JavascriptRootObjectWrapper^ rootObjectWrapper; - _jsRootObjectWrappersByFrameId->TryGetValue(frameId, rootObjectWrapper); - - //NOTE: In the rare case when when OnContextCreated hasn't been called we need to manually create the rootObjectWrapper - //It appears that OnContextCreated is only called for pages that have javascript on them, which makes sense - //as without javascript there is no need for a context. - if (rootObjectWrapper == nullptr) - { -#ifdef NETCOREAPP - rootObjectWrapper = gcnew JavascriptRootObjectWrapper(); -#else - auto browserWrapper = FindBrowserWrapper(browser->GetIdentifier()); - - // If we cannot find the browserWrapper then we'll pass in nullptr which will disable - // the use of sync bound objects (async will still work). - rootObjectWrapper = gcnew JavascriptRootObjectWrapper(browserWrapper == nullptr ? nullptr : browserWrapper->BrowserProcess); -#endif - - _jsRootObjectWrappersByFrameId->TryAdd(frameId, rootObjectWrapper); - } - - auto callbackRegistry = rootObjectWrapper->CallbackRegistry; - auto script = argList->GetString(1); auto scriptUrl = argList->GetString(2); auto startLine = argList->GetInt(3); @@ -439,6 +416,8 @@ namespace CefSharp } else { + auto callbackRegistry = rootObjectWrapper->CallbackRegistry; + auto responseArgList = response->GetArgumentList(); SerializeV8Object(result, responseArgList, 2, callbackRegistry); } @@ -465,8 +444,6 @@ namespace CefSharp } else { - JavascriptRootObjectWrapper^ rootObjectWrapper; - _jsRootObjectWrappersByFrameId->TryGetValue(frameId, rootObjectWrapper); auto callbackRegistry = rootObjectWrapper == nullptr ? nullptr : rootObjectWrapper->CallbackRegistry; if (callbackRegistry == nullptr) { From 7a347f5c287715eeb35430570f723430af945849 Mon Sep 17 00:00:00 2001 From: amaitland <307872+amaitland@users.noreply.github.com> Date: Sun, 30 Nov 2025 13:03:04 +1000 Subject: [PATCH 4/8] Bug fix and review suggestion --- .../CefAppUnmanagedWrapper.cpp | 8 +++++++- CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp index 5979f58ae4..ba18b23e8a 100644 --- a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp +++ b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp @@ -304,6 +304,12 @@ namespace CefSharp JavascriptRootObjectWrapper^ CefAppUnmanagedWrapper::GetJsRootObjectWrapper(int browserId, CefString& frameId) { auto rootObjectWrappers = _jsRootObjectWrappersByFrameId; + + if (Object::ReferenceEquals(rootObjectWrappers, nullptr)) + { + return nullptr; + } + auto frameIdClr = StringUtils::ToClr(frameId); JavascriptRootObjectWrapper^ rootObject; @@ -416,7 +422,7 @@ namespace CefSharp } else { - auto callbackRegistry = rootObjectWrapper->CallbackRegistry; + auto callbackRegistry = rootObjectWrapper == nullptr ? nullptr : rootObjectWrapper->CallbackRegistry; auto responseArgList = response->GetArgumentList(); SerializeV8Object(result, responseArgList, 2, callbackRegistry); diff --git a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h index f91d75572f..b989c3467a 100644 --- a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h +++ b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h @@ -72,7 +72,7 @@ namespace CefSharp if (!Object::ReferenceEquals(_jsRootObjectWrappersByFrameId, nullptr)) { - for each (JavascriptRootObjectWrapper^ rootObject in Enumerable::OfType(_browserWrappers)) + for each (JavascriptRootObjectWrapper^ rootObject in Enumerable::OfType(_jsRootObjectWrappersByFrameId)) { delete rootObject; } From f220c3d9af74d4cbaf49a8fad8058b4e2386a8e8 Mon Sep 17 00:00:00 2001 From: amaitland <307872+amaitland@users.noreply.github.com> Date: Tue, 2 Dec 2025 19:37:11 +1000 Subject: [PATCH 5/8] Modify failing test --- CefSharp.Test/Javascript/JavascriptCallbackTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CefSharp.Test/Javascript/JavascriptCallbackTests.cs b/CefSharp.Test/Javascript/JavascriptCallbackTests.cs index c548fcdb8b..988562bf2c 100644 --- a/CefSharp.Test/Javascript/JavascriptCallbackTests.cs +++ b/CefSharp.Test/Javascript/JavascriptCallbackTests.cs @@ -57,6 +57,7 @@ public async Task ShouldCancelAfterV8ContextChange() Assert.True(callbackExecuteCancelAfterV8ContextResponse.Success); var callbackExecuteCancelAfterV8ContextCallback = (IJavascriptCallback)callbackExecuteCancelAfterV8ContextResponse.Result; var callbackExecuteCancelAfterV8ContextTask = callbackExecuteCancelAfterV8ContextCallback.ExecuteAsync(); + var frameId = browser.GetMainFrame().Identifier; // change V8 context await browser.LoadUrlAsync(CefExample.HelloWorldUrl); @@ -64,7 +65,7 @@ public async Task ShouldCancelAfterV8ContextChange() await Assert.ThrowsAsync(() => callbackExecuteCancelAfterV8ContextTask); var callbackExecuteCancelAfterV8ContextResult = await callbackExecuteCancelAfterV8ContextCallback.ExecuteAsync(); Assert.False(callbackExecuteCancelAfterV8ContextResult.Success); - Assert.StartsWith("Unable to find JavascriptCallback with Id " + callbackExecuteCancelAfterV8ContextCallback.Id, callbackExecuteCancelAfterV8ContextResult.Message); + Assert.StartsWith($"Frame with Id: {frameId}", callbackExecuteCancelAfterV8ContextResult.Message); var callbackExecuteCancelAfterDisposeResponse = await browser.EvaluateScriptAsync("(function() { return new Promise(resolve => setTimeout(resolve, 1000)); })"); Assert.True(callbackExecuteCancelAfterDisposeResponse.Success); From a07b5e149bbcb5e2303146cd1df8c2380153a230 Mon Sep 17 00:00:00 2001 From: amaitland <307872+amaitland@users.noreply.github.com> Date: Tue, 2 Dec 2025 19:37:38 +1000 Subject: [PATCH 6/8] Revert "Tests - Simplify ShouldFailWhenIsObjectCachedCalledWithInvalidObjectName" This reverts commit f8a81acf7abd270680f5c36fa165df948599f2a1. --- CefSharp.Test/JavascriptBinding/JavascriptBindingTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CefSharp.Test/JavascriptBinding/JavascriptBindingTests.cs b/CefSharp.Test/JavascriptBinding/JavascriptBindingTests.cs index c0eb9fea80..3bfbdd13da 100644 --- a/CefSharp.Test/JavascriptBinding/JavascriptBindingTests.cs +++ b/CefSharp.Test/JavascriptBinding/JavascriptBindingTests.cs @@ -94,8 +94,10 @@ public async Task ShouldWorkWhenUsingCustomGlobalObjectName() [InlineData("cefSharp.isObjectCached('doesntexist')")] public async Task ShouldFailWhenIsObjectCachedCalledWithInvalidObjectName(string script) { - AssertInitialLoadComplete(); + var loadResponse = await Browser.LoadUrlAsync(CefExample.BindingApiCustomObjectNameTestUrl); + Assert.True(loadResponse.Success); + var response = await Browser.EvaluateScriptAsync(script); Assert.True(response.Success); From c052defc8749e67f65623413d65d9f92853f92eb Mon Sep 17 00:00:00 2001 From: amaitland <307872+amaitland@users.noreply.github.com> Date: Tue, 2 Dec 2025 19:48:12 +1000 Subject: [PATCH 7/8] Minor typo in test assert --- CefSharp.Test/Javascript/JavascriptCallbackTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CefSharp.Test/Javascript/JavascriptCallbackTests.cs b/CefSharp.Test/Javascript/JavascriptCallbackTests.cs index 988562bf2c..e87e0a9ff4 100644 --- a/CefSharp.Test/Javascript/JavascriptCallbackTests.cs +++ b/CefSharp.Test/Javascript/JavascriptCallbackTests.cs @@ -65,7 +65,7 @@ public async Task ShouldCancelAfterV8ContextChange() await Assert.ThrowsAsync(() => callbackExecuteCancelAfterV8ContextTask); var callbackExecuteCancelAfterV8ContextResult = await callbackExecuteCancelAfterV8ContextCallback.ExecuteAsync(); Assert.False(callbackExecuteCancelAfterV8ContextResult.Success); - Assert.StartsWith($"Frame with Id: {frameId}", callbackExecuteCancelAfterV8ContextResult.Message); + Assert.StartsWith($"Frame with Id:{frameId}", callbackExecuteCancelAfterV8ContextResult.Message); var callbackExecuteCancelAfterDisposeResponse = await browser.EvaluateScriptAsync("(function() { return new Promise(resolve => setTimeout(resolve, 1000)); })"); Assert.True(callbackExecuteCancelAfterDisposeResponse.Success); From 1e21f47e3e80bdf2309cec82678420ac761a3411 Mon Sep 17 00:00:00 2001 From: amaitland <307872+amaitland@users.noreply.github.com> Date: Tue, 2 Dec 2025 19:59:20 +1000 Subject: [PATCH 8/8] Action review comment --- .../CefAppUnmanagedWrapper.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp index ba18b23e8a..6ad271d002 100644 --- a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp +++ b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp @@ -424,8 +424,15 @@ namespace CefSharp { auto callbackRegistry = rootObjectWrapper == nullptr ? nullptr : rootObjectWrapper->CallbackRegistry; - auto responseArgList = response->GetArgumentList(); - SerializeV8Object(result, responseArgList, 2, callbackRegistry); + if (callbackRegistry == nullptr) + { + errorMessage = StringUtils::ToNative("The callback registry for Frame " + frameId + " is no longer available."); + } + else + { + auto responseArgList = response->GetArgumentList(); + SerializeV8Object(result, responseArgList, 2, callbackRegistry); + } } } else