From ae50a6f7be501a9bf711b30f9a1938349f53d494 Mon Sep 17 00:00:00 2001 From: amaitland <307872+amaitland@users.noreply.github.com> Date: Sat, 12 Apr 2025 12:10:41 +1000 Subject: [PATCH 01/10] Javascript Binding - Add Ability to limit origins --- .../CefAppUnmanagedWrapper.cpp | 131 ++++++++++++------ .../CefAppUnmanagedWrapper.h | 4 + .../ManagedCefBrowserAdapter.cpp | 16 +++ .../JavascriptBindingTests.cs | 54 ++++++++ .../JavascriptBindingSettings.cs | 32 +++++ 5 files changed, 196 insertions(+), 41 deletions(-) diff --git a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp index ff231909d2..9e7b67a9de 100644 --- a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp +++ b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp @@ -23,6 +23,7 @@ #include "Wrapper\Browser.h" #include "..\CefSharp.Core.Runtime\Internals\Messaging\Messages.h" #include "..\CefSharp.Core.Runtime\Internals\Serialization\Primitives.h" +#include using namespace System; using namespace System::Diagnostics; @@ -87,7 +88,7 @@ namespace CefSharp //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. + //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)) @@ -100,6 +101,16 @@ namespace CefSharp } _jsBindingApiEnabled = extraInfo->GetBool("JavascriptBindingApiEnabled"); + _jsBindingApiHasAllowOrigins = extraInfo->GetBool("JavascriptBindingApiHasAllowOrigins"); + + if (_jsBindingApiHasAllowOrigins) + { + auto allowOrigins = extraInfo->GetList("JavascriptBindingApiAllowOrigins"); + if (allowOrigins.get() && allowOrigins->IsValid()) + { + _jsBindingApiAllowOrigins = allowOrigins->Copy(); + } + } if (extraInfo->HasKey("JsBindingPropertyName") || extraInfo->HasKey("JsBindingPropertyNameCamelCase")) { @@ -149,50 +160,88 @@ namespace CefSharp if (_jsBindingApiEnabled) { - //TODO: Look at adding some sort of javascript mapping layer to reduce the code duplication - auto global = context->GetGlobal(); - auto browserWrapper = FindBrowserWrapper(browser->GetIdentifier()); - auto processId = System::Diagnostics::Process::GetCurrentProcess()->Id; - - //TODO: JSB: Split functions into their own classes - //Browser wrapper is only used for BindObjectAsync - 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)); - auto postMessageFunction = CefV8Value::CreateFunction(kPostMessage, new JavascriptPostMessageHandler(rootObject == nullptr ? nullptr : rootObject->CallbackRegistry)); - auto promiseHandlerFunction = CefV8Value::CreateFunction(kSendEvalScriptResponse, new JavascriptPromiseHandler()); - - //By default We'll support both CefSharp and cefSharp, for those who prefer the JS style - auto createCefSharpObj = !_jsBindingPropertyName.empty(); - auto createCefSharpObjCamelCase = !_jsBindingPropertyNameCamelCase.empty(); - - if (createCefSharpObj) + auto createObjects = true; + + if (_jsBindingApiHasAllowOrigins) { - auto cefSharpObj = CefV8Value::CreateObject(nullptr, nullptr); - cefSharpObj->SetValue(kBindObjectAsync, bindObjAsyncFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); - cefSharpObj->SetValue(kDeleteBoundObject, unBindObjFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); - cefSharpObj->SetValue(kRemoveObjectFromCache, removeObjectFromCacheFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); - cefSharpObj->SetValue(kIsObjectCached, isObjectCachedFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); - cefSharpObj->SetValue(kPostMessage, postMessageFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); - cefSharpObj->SetValue(kSendEvalScriptResponse, promiseHandlerFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); - cefSharpObj->SetValue(kRenderProcessId, CefV8Value::CreateInt(processId), CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); - - global->SetValue(_jsBindingPropertyName, cefSharpObj, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_READONLY); + createObjects = false; + + auto frameUrl = frame->GetURL(); + + CefURLParts frameUrlParts; + + if (CefParseURL(frameUrl, frameUrlParts)) + { + auto frameUrlOrigin = CefString(frameUrlParts.origin.str, frameUrlParts.origin.length); + auto clrframeUrlOrigin = StringUtils::ToClr(frameUrlOrigin); + + auto size = static_cast(_jsBindingApiAllowOrigins->GetSize()); + + for (int i = 0; i < size; i++) + { + auto origin = _jsBindingApiAllowOrigins->GetString(i); + + auto clrOrigin = StringUtils::ToClr(origin); + + auto originEqual = String::Compare(clrframeUrlOrigin, clrOrigin, StringComparison::InvariantCultureIgnoreCase); + + if (originEqual == 0) + { + createObjects = true; + + break; + } + } + } } - if (createCefSharpObjCamelCase) + if (createObjects) { - auto cefSharpObjCamelCase = CefV8Value::CreateObject(nullptr, nullptr); - cefSharpObjCamelCase->SetValue(kBindObjectAsyncCamelCase, bindObjAsyncFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); - cefSharpObjCamelCase->SetValue(kDeleteBoundObjectCamelCase, unBindObjFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); - cefSharpObjCamelCase->SetValue(kRemoveObjectFromCacheCamelCase, removeObjectFromCacheFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); - cefSharpObjCamelCase->SetValue(kIsObjectCachedCamelCase, isObjectCachedFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); - cefSharpObjCamelCase->SetValue(kPostMessageCamelCase, postMessageFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); - cefSharpObjCamelCase->SetValue(kSendEvalScriptResponseCamelCase, promiseHandlerFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); - cefSharpObjCamelCase->SetValue(kRenderProcessIdCamelCase, CefV8Value::CreateInt(processId), CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); - - global->SetValue(_jsBindingPropertyNameCamelCase, cefSharpObjCamelCase, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_READONLY); + //TODO: Look at adding some sort of javascript mapping layer to reduce the code duplication + auto global = context->GetGlobal(); + auto browserWrapper = FindBrowserWrapper(browser->GetIdentifier()); + auto processId = System::Diagnostics::Process::GetCurrentProcess()->Id; + + //TODO: JSB: Split functions into their own classes + //Browser wrapper is only used for BindObjectAsync + 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)); + auto postMessageFunction = CefV8Value::CreateFunction(kPostMessage, new JavascriptPostMessageHandler(rootObject == nullptr ? nullptr : rootObject->CallbackRegistry)); + auto promiseHandlerFunction = CefV8Value::CreateFunction(kSendEvalScriptResponse, new JavascriptPromiseHandler()); + + //By default We'll support both CefSharp and cefSharp, for those who prefer the JS style + auto createCefSharpObj = !_jsBindingPropertyName.empty(); + auto createCefSharpObjCamelCase = !_jsBindingPropertyNameCamelCase.empty(); + + if (createCefSharpObj) + { + auto cefSharpObj = CefV8Value::CreateObject(nullptr, nullptr); + cefSharpObj->SetValue(kBindObjectAsync, bindObjAsyncFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); + cefSharpObj->SetValue(kDeleteBoundObject, unBindObjFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); + cefSharpObj->SetValue(kRemoveObjectFromCache, removeObjectFromCacheFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); + cefSharpObj->SetValue(kIsObjectCached, isObjectCachedFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); + cefSharpObj->SetValue(kPostMessage, postMessageFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); + cefSharpObj->SetValue(kSendEvalScriptResponse, promiseHandlerFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); + cefSharpObj->SetValue(kRenderProcessId, CefV8Value::CreateInt(processId), CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); + + global->SetValue(_jsBindingPropertyName, cefSharpObj, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_READONLY); + } + + if (createCefSharpObjCamelCase) + { + auto cefSharpObjCamelCase = CefV8Value::CreateObject(nullptr, nullptr); + cefSharpObjCamelCase->SetValue(kBindObjectAsyncCamelCase, bindObjAsyncFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); + cefSharpObjCamelCase->SetValue(kDeleteBoundObjectCamelCase, unBindObjFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); + cefSharpObjCamelCase->SetValue(kRemoveObjectFromCacheCamelCase, removeObjectFromCacheFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); + cefSharpObjCamelCase->SetValue(kIsObjectCachedCamelCase, isObjectCachedFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); + cefSharpObjCamelCase->SetValue(kPostMessageCamelCase, postMessageFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); + cefSharpObjCamelCase->SetValue(kSendEvalScriptResponseCamelCase, promiseHandlerFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); + cefSharpObjCamelCase->SetValue(kRenderProcessIdCamelCase, CefV8Value::CreateInt(processId), CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); + + global->SetValue(_jsBindingPropertyNameCamelCase, cefSharpObjCamelCase, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_READONLY); + } } } diff --git a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h index 7915412fba..0e6fc44c22 100644 --- a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h +++ b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h @@ -30,6 +30,8 @@ namespace CefSharp bool _focusedNodeChangedEnabled; bool _legacyBindingEnabled; bool _jsBindingApiEnabled = true; + bool _jsBindingApiHasAllowOrigins = false; + CefRefPtr _jsBindingApiAllowOrigins; // The property names used to call bound objects CefString _jsBindingPropertyName; @@ -82,6 +84,8 @@ namespace CefSharp delete _onBrowserCreated; delete _onBrowserDestroyed; + + _jsBindingApiAllowOrigins = nullptr; } CefBrowserWrapper^ FindBrowserWrapper(int browserId); diff --git a/CefSharp.Core.Runtime/ManagedCefBrowserAdapter.cpp b/CefSharp.Core.Runtime/ManagedCefBrowserAdapter.cpp index 47daea5dd0..7a3a202291 100644 --- a/CefSharp.Core.Runtime/ManagedCefBrowserAdapter.cpp +++ b/CefSharp.Core.Runtime/ManagedCefBrowserAdapter.cpp @@ -84,6 +84,22 @@ namespace CefSharp extraInfo->SetBool("JavascriptBindingApiEnabled", objectRepositorySettings->JavascriptBindingApiEnabled); + auto hasJavascriptBindingApiAllowOrigins = objectRepositorySettings->HasJavascriptBindingApiAllowOrigins(); + + extraInfo->SetBool("JavascriptBindingApiHasAllowOrigins", hasJavascriptBindingApiAllowOrigins); + + if (hasJavascriptBindingApiAllowOrigins) + { + auto allowOriginList = CefListValue::Create(); + + for (int i = 0; i < objectRepositorySettings->JavascriptBindingApiAllowOrigins->Length; i++) + { + allowOriginList->SetString(i, StringUtils::ToNative(objectRepositorySettings->JavascriptBindingApiAllowOrigins[i])); + } + + extraInfo->SetList("JavascriptBindingApiAllowOrigins", allowOriginList); + } + CefRefPtr requestCtx; if (requestContext != nullptr) diff --git a/CefSharp.Test/JavascriptBinding/JavascriptBindingTests.cs b/CefSharp.Test/JavascriptBinding/JavascriptBindingTests.cs index 3bfbdd13da..b2f2f2d9a1 100644 --- a/CefSharp.Test/JavascriptBinding/JavascriptBindingTests.cs +++ b/CefSharp.Test/JavascriptBinding/JavascriptBindingTests.cs @@ -130,6 +130,60 @@ public async Task ShouldDisableJsBindingApi() } } + [Fact] + public async Task ShouldDisableJsBindingApiForOrigin() + { + using (var browser = new ChromiumWebBrowser(CefExample.BindingApiCustomObjectNameTestUrl, automaticallyCreateBrowser: false)) + { + var settings = browser.JavascriptObjectRepository.Settings; + settings.JavascriptBindingApiEnabled = true; + settings.JavascriptBindingApiAllowOrigins = new string[] { "notallowed" }; + + //To modify the settings we need to defer browser creation slightly + browser.CreateBrowser(); + + var loadResponse = await browser.WaitForInitialLoadAsync(); + + Assert.True(loadResponse.Success); + + var response1 = await browser.EvaluateScriptAsync("typeof window.cefSharp === 'undefined'"); + var response2 = await browser.EvaluateScriptAsync("typeof window.CefSharp === 'undefined'"); + + Assert.True(response1.Success); + Assert.True((bool)response1.Result); + + Assert.True(response2.Success); + Assert.True((bool)response2.Result); + } + } + + [Theory] + [InlineData(CefExample.BaseUrl + "/")] + [InlineData("someorigin", CefExample.BaseUrl + "/")] + public async Task ShouldEnableJsBindingApiForOrigin(params string[] origins) + { + using (var browser = new ChromiumWebBrowser(CefExample.BindingApiCustomObjectNameTestUrl, automaticallyCreateBrowser: false)) + { + var settings = browser.JavascriptObjectRepository.Settings; + settings.JavascriptBindingApiEnabled = true; + settings.JavascriptBindingApiAllowOrigins = origins; + + //To modify the settings we need to defer browser creation slightly + browser.CreateBrowser(); + + await browser.WaitForInitialLoadAsync(); + + var response1 = await browser.EvaluateScriptAsync("typeof window.cefSharp === 'undefined'"); + var response2 = await browser.EvaluateScriptAsync("typeof window.CefSharp === 'undefined'"); + + Assert.True(response1.Success); + Assert.False((bool)response1.Result); + + Assert.True(response2.Success); + Assert.False((bool)response2.Result); + } + } + [Fact] public async Task ShouldEnableJsBindingApi() { diff --git a/CefSharp/JavascriptBinding/JavascriptBindingSettings.cs b/CefSharp/JavascriptBinding/JavascriptBindingSettings.cs index 7d89aedd64..cfd6b67422 100644 --- a/CefSharp/JavascriptBinding/JavascriptBindingSettings.cs +++ b/CefSharp/JavascriptBinding/JavascriptBindingSettings.cs @@ -2,6 +2,7 @@ // // Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. +using System.Collections.Generic; using CefSharp.Internals; namespace CefSharp.JavascriptBinding @@ -15,6 +16,7 @@ public class JavascriptBindingSettings : FreezableBase private bool legacyBindingEnabled; private string jsBindingGlobalObjectName; private bool jsBindingApiEnabled = true; + private string[] javascriptBindingApiAllowOrigins; /// /// The Javascript methods that CefSharp provides in relation to JavaScript Binding are @@ -33,6 +35,24 @@ public bool JavascriptBindingApiEnabled } } + /// + /// When is set to true, set a collection + /// of origins to limit which origins CefSharp will create it's global (window) object. + /// + /// + /// If you wish to create the CefSharp object for a limited set of origins then set this property + /// + public string[] JavascriptBindingApiAllowOrigins + { + get { return javascriptBindingApiAllowOrigins; } + set + { + ThrowIfFrozen(); + + javascriptBindingApiAllowOrigins = value; + } + } + /// /// The Javascript methods that CefSharp provides in relation to JavaScript Binding are /// created using a Global (window) Object. Settings this property allows you to customise @@ -95,5 +115,17 @@ public bool AlwaysInterceptAsynchronously alwaysInterceptAsynchronously = value; } } + + /// + /// HasJavascriptBindingApiAllowOrigins + /// + /// bool true if is non empty collection. + public bool HasJavascriptBindingApiAllowOrigins() + { + if (javascriptBindingApiAllowOrigins == null) + return false; + + return javascriptBindingApiAllowOrigins.Length > 0; + } } } From fb03970c9f6c6633f4ca1c257735b2c21a04b5c9 Mon Sep 17 00:00:00 2001 From: Luca Sonntag Date: Thu, 26 Feb 2026 11:58:35 +0100 Subject: [PATCH 02/10] Move JS binding settings to CefBrowserWrapper and optimize origin validation --- .../CefAppUnmanagedWrapper.cpp | 164 +++++++++--------- .../CefAppUnmanagedWrapper.h | 6 +- .../CefBrowserWrapper.h | 11 +- 3 files changed, 92 insertions(+), 89 deletions(-) diff --git a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp index 9e7b67a9de..c33e62556d 100644 --- a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp +++ b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp @@ -100,15 +100,15 @@ namespace CefSharp } } - _jsBindingApiEnabled = extraInfo->GetBool("JavascriptBindingApiEnabled"); - _jsBindingApiHasAllowOrigins = extraInfo->GetBool("JavascriptBindingApiHasAllowOrigins"); + wrapper->JavascriptBindingApiEnabled = extraInfo->GetBool("JavascriptBindingApiEnabled"); + wrapper->JavascriptBindingApiHasAllowOrigins = extraInfo->GetBool("JavascriptBindingApiHasAllowOrigins"); - if (_jsBindingApiHasAllowOrigins) + if (wrapper->JavascriptBindingApiHasAllowOrigins) { auto allowOrigins = extraInfo->GetList("JavascriptBindingApiAllowOrigins"); if (allowOrigins.get() && allowOrigins->IsValid()) { - _jsBindingApiAllowOrigins = allowOrigins->Copy(); + wrapper->JavascriptBindingApiAllowOrigins = allowOrigins->Copy(); } } @@ -158,90 +158,53 @@ namespace CefSharp } } - if (_jsBindingApiEnabled) - { - auto createObjects = true; + auto browserWrapper = FindBrowserWrapper(browser->GetIdentifier()); - if (_jsBindingApiHasAllowOrigins) + if (browserWrapper != nullptr && browserWrapper->JavascriptBindingApiEnabled && IsJavascriptBindingApiAllowed(frame)) + { + //TODO: Look at adding some sort of javascript mapping layer to reduce the code duplication + auto global = context->GetGlobal(); + auto processId = System::Diagnostics::Process::GetCurrentProcess()->Id; + + //TODO: JSB: Split functions into their own classes + //Browser wrapper is only used for BindObjectAsync + 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)); + auto postMessageFunction = CefV8Value::CreateFunction(kPostMessage, new JavascriptPostMessageHandler(rootObject == nullptr ? nullptr : rootObject->CallbackRegistry)); + auto promiseHandlerFunction = CefV8Value::CreateFunction(kSendEvalScriptResponse, new JavascriptPromiseHandler()); + + //By default We'll support both CefSharp and cefSharp, for those who prefer the JS style + auto createCefSharpObj = !_jsBindingPropertyName.empty(); + auto createCefSharpObjCamelCase = !_jsBindingPropertyNameCamelCase.empty(); + + if (createCefSharpObj) { - createObjects = false; - - auto frameUrl = frame->GetURL(); - - CefURLParts frameUrlParts; - - if (CefParseURL(frameUrl, frameUrlParts)) - { - auto frameUrlOrigin = CefString(frameUrlParts.origin.str, frameUrlParts.origin.length); - auto clrframeUrlOrigin = StringUtils::ToClr(frameUrlOrigin); - - auto size = static_cast(_jsBindingApiAllowOrigins->GetSize()); - - for (int i = 0; i < size; i++) - { - auto origin = _jsBindingApiAllowOrigins->GetString(i); - - auto clrOrigin = StringUtils::ToClr(origin); - - auto originEqual = String::Compare(clrframeUrlOrigin, clrOrigin, StringComparison::InvariantCultureIgnoreCase); - - if (originEqual == 0) - { - createObjects = true; - - break; - } - } - } + auto cefSharpObj = CefV8Value::CreateObject(nullptr, nullptr); + cefSharpObj->SetValue(kBindObjectAsync, bindObjAsyncFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); + cefSharpObj->SetValue(kDeleteBoundObject, unBindObjFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); + cefSharpObj->SetValue(kRemoveObjectFromCache, removeObjectFromCacheFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); + cefSharpObj->SetValue(kIsObjectCached, isObjectCachedFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); + cefSharpObj->SetValue(kPostMessage, postMessageFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); + cefSharpObj->SetValue(kSendEvalScriptResponse, promiseHandlerFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); + cefSharpObj->SetValue(kRenderProcessId, CefV8Value::CreateInt(processId), CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); + + global->SetValue(_jsBindingPropertyName, cefSharpObj, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_READONLY); } - if (createObjects) + if (createCefSharpObjCamelCase) { - //TODO: Look at adding some sort of javascript mapping layer to reduce the code duplication - auto global = context->GetGlobal(); - auto browserWrapper = FindBrowserWrapper(browser->GetIdentifier()); - auto processId = System::Diagnostics::Process::GetCurrentProcess()->Id; - - //TODO: JSB: Split functions into their own classes - //Browser wrapper is only used for BindObjectAsync - 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)); - auto postMessageFunction = CefV8Value::CreateFunction(kPostMessage, new JavascriptPostMessageHandler(rootObject == nullptr ? nullptr : rootObject->CallbackRegistry)); - auto promiseHandlerFunction = CefV8Value::CreateFunction(kSendEvalScriptResponse, new JavascriptPromiseHandler()); - - //By default We'll support both CefSharp and cefSharp, for those who prefer the JS style - auto createCefSharpObj = !_jsBindingPropertyName.empty(); - auto createCefSharpObjCamelCase = !_jsBindingPropertyNameCamelCase.empty(); - - if (createCefSharpObj) - { - auto cefSharpObj = CefV8Value::CreateObject(nullptr, nullptr); - cefSharpObj->SetValue(kBindObjectAsync, bindObjAsyncFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); - cefSharpObj->SetValue(kDeleteBoundObject, unBindObjFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); - cefSharpObj->SetValue(kRemoveObjectFromCache, removeObjectFromCacheFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); - cefSharpObj->SetValue(kIsObjectCached, isObjectCachedFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); - cefSharpObj->SetValue(kPostMessage, postMessageFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); - cefSharpObj->SetValue(kSendEvalScriptResponse, promiseHandlerFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); - cefSharpObj->SetValue(kRenderProcessId, CefV8Value::CreateInt(processId), CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); - - global->SetValue(_jsBindingPropertyName, cefSharpObj, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_READONLY); - } - - if (createCefSharpObjCamelCase) - { - auto cefSharpObjCamelCase = CefV8Value::CreateObject(nullptr, nullptr); - cefSharpObjCamelCase->SetValue(kBindObjectAsyncCamelCase, bindObjAsyncFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); - cefSharpObjCamelCase->SetValue(kDeleteBoundObjectCamelCase, unBindObjFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); - cefSharpObjCamelCase->SetValue(kRemoveObjectFromCacheCamelCase, removeObjectFromCacheFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); - cefSharpObjCamelCase->SetValue(kIsObjectCachedCamelCase, isObjectCachedFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); - cefSharpObjCamelCase->SetValue(kPostMessageCamelCase, postMessageFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); - cefSharpObjCamelCase->SetValue(kSendEvalScriptResponseCamelCase, promiseHandlerFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); - cefSharpObjCamelCase->SetValue(kRenderProcessIdCamelCase, CefV8Value::CreateInt(processId), CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); - - global->SetValue(_jsBindingPropertyNameCamelCase, cefSharpObjCamelCase, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_READONLY); - } + auto cefSharpObjCamelCase = CefV8Value::CreateObject(nullptr, nullptr); + cefSharpObjCamelCase->SetValue(kBindObjectAsyncCamelCase, bindObjAsyncFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); + cefSharpObjCamelCase->SetValue(kDeleteBoundObjectCamelCase, unBindObjFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); + cefSharpObjCamelCase->SetValue(kRemoveObjectFromCacheCamelCase, removeObjectFromCacheFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); + cefSharpObjCamelCase->SetValue(kIsObjectCachedCamelCase, isObjectCachedFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); + cefSharpObjCamelCase->SetValue(kPostMessageCamelCase, postMessageFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); + cefSharpObjCamelCase->SetValue(kSendEvalScriptResponseCamelCase, promiseHandlerFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); + cefSharpObjCamelCase->SetValue(kRenderProcessIdCamelCase, CefV8Value::CreateInt(processId), CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE); + + global->SetValue(_jsBindingPropertyNameCamelCase, cefSharpObjCamelCase, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_READONLY); } } @@ -377,6 +340,41 @@ namespace CefSharp return rootObject; } + bool CefAppUnmanagedWrapper::IsJavascriptBindingApiAllowed(CefRefPtr frame) + { + auto browserWrapper = FindBrowserWrapper(frame->GetBrowser()->GetIdentifier()); + + if (browserWrapper == nullptr || !browserWrapper->JavascriptBindingApiHasAllowOrigins) + { + return true; + } + + auto frameUrl = frame->GetURL(); + + CefURLParts frameUrlParts; + + if (CefParseURL(frameUrl, frameUrlParts)) + { + auto frameUrlOrigin = CefString(frameUrlParts.origin.str, frameUrlParts.origin.length); + + auto size = static_cast(browserWrapper->JavascriptBindingApiAllowOrigins->GetSize()); + + for (int i = 0; i < size; i++) + { + auto origin = browserWrapper->JavascriptBindingApiAllowOrigins->GetString(i); + + if (_wcsicmp( + reinterpret_cast(frameUrlOrigin.c_str()), + reinterpret_cast(origin.c_str())) == 0) + { + return true; + } + } + } + + return false; + } + CefBrowserWrapper^ CefAppUnmanagedWrapper::FindBrowserWrapper(int browserId) { CefBrowserWrapper^ wrapper = nullptr; diff --git a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h index 0e6fc44c22..470cfc3b0a 100644 --- a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h +++ b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h @@ -29,9 +29,6 @@ namespace CefSharp gcroot^> _jsRootObjectWrappersByFrameId; bool _focusedNodeChangedEnabled; bool _legacyBindingEnabled; - bool _jsBindingApiEnabled = true; - bool _jsBindingApiHasAllowOrigins = false; - CefRefPtr _jsBindingApiAllowOrigins; // The property names used to call bound objects CefString _jsBindingPropertyName; @@ -41,6 +38,7 @@ namespace CefSharp gcroot^> _javascriptObjects; gcroot _registerBoundObjectRegistry; + bool IsJavascriptBindingApiAllowed(CefRefPtr frame); public: static const CefString kPromiseCreatorScript; @@ -84,8 +82,6 @@ namespace CefSharp delete _onBrowserCreated; delete _onBrowserDestroyed; - - _jsBindingApiAllowOrigins = nullptr; } CefBrowserWrapper^ FindBrowserWrapper(int browserId); diff --git a/CefSharp.BrowserSubprocess.Core/CefBrowserWrapper.h b/CefSharp.BrowserSubprocess.Core/CefBrowserWrapper.h index 37f03c1dd0..6cb5cf0cc5 100644 --- a/CefSharp.BrowserSubprocess.Core/CefBrowserWrapper.h +++ b/CefSharp.BrowserSubprocess.Core/CefBrowserWrapper.h @@ -22,7 +22,7 @@ namespace CefSharp { namespace BrowserSubprocess { - // "Master class" for wrapping everything that the Cef Subprocess needs + // "Master class" for wrapping everything that the Cef Subprocess needs // for ONE CefBrowser. public ref class CefBrowserWrapper { @@ -35,11 +35,17 @@ namespace CefSharp _cefBrowser = cefBrowser.get(); BrowserId = cefBrowser->GetIdentifier(); IsPopup = cefBrowser->IsPopup(); + + JavascriptBindingApiEnabled = true; + JavascriptBindingApiHasAllowOrigins = false; + JavascriptBindingApiAllowOrigins = nullptr; } !CefBrowserWrapper() { _cefBrowser = nullptr; + + JavascriptBindingApiAllowOrigins = nullptr; } ~CefBrowserWrapper() @@ -49,6 +55,9 @@ namespace CefSharp property int BrowserId; property bool IsPopup; + property bool JavascriptBindingApiEnabled; + property bool JavascriptBindingApiHasAllowOrigins; + property CefRefPtr JavascriptBindingApiAllowOrigins; #ifndef NETCOREAPP // This allows us to create the WCF proxies back to our parent process. From be839171a073c6af23f6ef16ee6ec9f8603cfef4 Mon Sep 17 00:00:00 2001 From: Luca Sonntag Date: Thu, 26 Feb 2026 16:06:26 +0100 Subject: [PATCH 03/10] Initialize origins to null --- CefSharp.BrowserSubprocess.Core/CefBrowserWrapper.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CefSharp.BrowserSubprocess.Core/CefBrowserWrapper.h b/CefSharp.BrowserSubprocess.Core/CefBrowserWrapper.h index 6cb5cf0cc5..50b9e304a8 100644 --- a/CefSharp.BrowserSubprocess.Core/CefBrowserWrapper.h +++ b/CefSharp.BrowserSubprocess.Core/CefBrowserWrapper.h @@ -28,6 +28,7 @@ namespace CefSharp { private: MCefRefPtr _cefBrowser; + MCefRefPtr _javascriptBindingApiAllowOrigins; public: CefBrowserWrapper(const CefRefPtr &cefBrowser) @@ -45,7 +46,7 @@ namespace CefSharp { _cefBrowser = nullptr; - JavascriptBindingApiAllowOrigins = nullptr; + _javascriptBindingApiAllowOrigins = nullptr; } ~CefBrowserWrapper() @@ -57,7 +58,11 @@ namespace CefSharp property bool IsPopup; property bool JavascriptBindingApiEnabled; property bool JavascriptBindingApiHasAllowOrigins; - property CefRefPtr JavascriptBindingApiAllowOrigins; + property CefRefPtr JavascriptBindingApiAllowOrigins + { + CefRefPtr get() { return _javascriptBindingApiAllowOrigins.get(); } + void set(CefRefPtr value) { _javascriptBindingApiAllowOrigins = value.get(); } + } #ifndef NETCOREAPP // This allows us to create the WCF proxies back to our parent process. From 4043025befd45b7ef5a3ce99d333d4cebdb7597a Mon Sep 17 00:00:00 2001 From: Luca Sonntag Date: Thu, 26 Feb 2026 13:06:01 +0100 Subject: [PATCH 04/10] Normalize allowed origins --- .../JavascriptBinding/JavascriptBindingSettings.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/CefSharp/JavascriptBinding/JavascriptBindingSettings.cs b/CefSharp/JavascriptBinding/JavascriptBindingSettings.cs index cfd6b67422..50fbb120ae 100644 --- a/CefSharp/JavascriptBinding/JavascriptBindingSettings.cs +++ b/CefSharp/JavascriptBinding/JavascriptBindingSettings.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using CefSharp.Internals; +using System; namespace CefSharp.JavascriptBinding { @@ -48,8 +49,17 @@ public string[] JavascriptBindingApiAllowOrigins set { ThrowIfFrozen(); - - javascriptBindingApiAllowOrigins = value; + if (value != null) + { + javascriptBindingApiAllowOrigins = Array.ConvertAll( + value, + origin => origin.EndsWith("/") ? origin.Substring(0, origin.Length - 1) : origin + ); + } + else + { + javascriptBindingApiAllowOrigins = null; + } } } From 74f672603b684ba248cc132e7c69456973ad4523 Mon Sep 17 00:00:00 2001 From: Luca Sonntag Date: Thu, 26 Feb 2026 16:08:28 +0100 Subject: [PATCH 05/10] Optimize origin validation --- .../CefAppUnmanagedWrapper.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp index c33e62556d..d27f1f57be 100644 --- a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp +++ b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp @@ -355,7 +355,15 @@ namespace CefSharp if (CefParseURL(frameUrl, frameUrlParts)) { - auto frameUrlOrigin = CefString(frameUrlParts.origin.str, frameUrlParts.origin.length); + auto originStr = frameUrlParts.origin.str; + auto originLen = frameUrlParts.origin.length; + + if (originLen > 0 && originStr[originLen - 1] == L'/') + { + originLen--; + } + + auto frameUrlOrigin = CefString(originStr, originLen); auto size = static_cast(browserWrapper->JavascriptBindingApiAllowOrigins->GetSize()); From 4eeaef68e948ff92e1fd65b43f18da2bb2331680 Mon Sep 17 00:00:00 2001 From: Luca Sonntag Date: Thu, 26 Feb 2026 16:16:50 +0100 Subject: [PATCH 06/10] Add null check for origin compare --- .../CefAppUnmanagedWrapper.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp index d27f1f57be..1c8ae4b630 100644 --- a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp +++ b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp @@ -370,12 +370,15 @@ namespace CefSharp for (int i = 0; i < size; i++) { auto origin = browserWrapper->JavascriptBindingApiAllowOrigins->GetString(i); + auto frameOriginPtr = reinterpret_cast(frameUrlOrigin.c_str()); + auto allowedOriginPtr = reinterpret_cast(origin.c_str()); - if (_wcsicmp( - reinterpret_cast(frameUrlOrigin.c_str()), - reinterpret_cast(origin.c_str())) == 0) + if (frameOriginPtr != nullptr && allowedOriginPtr != nullptr) { - return true; + if (_wcsicmp(frameOriginPtr, allowedOriginPtr) == 0) + { + return true; + } } } } From 2eac865024297a33acb17dfc5feb63aafc4aa166 Mon Sep 17 00:00:00 2001 From: Luca Sonntag Date: Mon, 2 Mar 2026 11:23:34 +0100 Subject: [PATCH 07/10] Fix JavaScript Binding API settings for popups Move the JS binding API configuration block out of the !browser->IsPopup() scope in OnBrowserCreated. This ensures that IsJavascriptBindingApiAllowed() enforces configured restrictions for popups as well. --- .../CefAppUnmanagedWrapper.cpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp index 1c8ae4b630..13deffad45 100644 --- a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp +++ b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp @@ -99,8 +99,15 @@ namespace CefSharp } } } + } + if (extraInfo->HasKey("JavascriptBindingApiEnabled")) + { wrapper->JavascriptBindingApiEnabled = extraInfo->GetBool("JavascriptBindingApiEnabled"); + } + + if (extraInfo->HasKey("JavascriptBindingApiHasAllowOrigins")) + { wrapper->JavascriptBindingApiHasAllowOrigins = extraInfo->GetBool("JavascriptBindingApiHasAllowOrigins"); if (wrapper->JavascriptBindingApiHasAllowOrigins) @@ -111,13 +118,13 @@ namespace CefSharp wrapper->JavascriptBindingApiAllowOrigins = allowOrigins->Copy(); } } + } - if (extraInfo->HasKey("JsBindingPropertyName") || extraInfo->HasKey("JsBindingPropertyNameCamelCase")) - { - //TODO: Create constant for these and legacy binding strings above - _jsBindingPropertyName = extraInfo->GetString("JsBindingPropertyName"); - _jsBindingPropertyNameCamelCase = extraInfo->GetString("JsBindingPropertyNameCamelCase"); - } + if (extraInfo->HasKey("JsBindingPropertyName") || extraInfo->HasKey("JsBindingPropertyNameCamelCase")) + { + //TODO: Create constant for these and legacy binding strings above + _jsBindingPropertyName = extraInfo->GetString("JsBindingPropertyName"); + _jsBindingPropertyNameCamelCase = extraInfo->GetString("JsBindingPropertyNameCamelCase"); } } From effcf911ae4a6c3bef8419beee4eedd886959964 Mon Sep 17 00:00:00 2001 From: Luca Sonntag Date: Mon, 2 Mar 2026 11:31:31 +0100 Subject: [PATCH 08/10] Fix possible null deref for JavascriptBindingApiAllowOrigins In IsJavascriptBindingApiAllowed, added a null check for JavascriptBindingApiAllowOrigins to prevent a potential null dereference if JavascriptBindingApiHasAllowOrigins is true but the allowOrigins list itself was null. --- .../CefAppUnmanagedWrapper.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp index 13deffad45..37278db4de 100644 --- a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp +++ b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp @@ -356,6 +356,12 @@ namespace CefSharp return true; } + auto allowOrigins = browserWrapper->JavascriptBindingApiAllowOrigins; + if (!allowOrigins.get()) + { + return false; + } + auto frameUrl = frame->GetURL(); CefURLParts frameUrlParts; @@ -372,11 +378,11 @@ namespace CefSharp auto frameUrlOrigin = CefString(originStr, originLen); - auto size = static_cast(browserWrapper->JavascriptBindingApiAllowOrigins->GetSize()); + auto size = static_cast(allowOrigins->GetSize()); for (int i = 0; i < size; i++) { - auto origin = browserWrapper->JavascriptBindingApiAllowOrigins->GetString(i); + auto origin = allowOrigins->GetString(i); auto frameOriginPtr = reinterpret_cast(frameUrlOrigin.c_str()); auto allowedOriginPtr = reinterpret_cast(origin.c_str()); From f971f4c24a2823a2fd56913983786d6f8a158655 Mon Sep 17 00:00:00 2001 From: Luca Sonntag Date: Mon, 2 Mar 2026 11:40:09 +0100 Subject: [PATCH 09/10] Simplify origin normalization and add null check --- CefSharp/JavascriptBinding/JavascriptBindingSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CefSharp/JavascriptBinding/JavascriptBindingSettings.cs b/CefSharp/JavascriptBinding/JavascriptBindingSettings.cs index 50fbb120ae..1b2d6678e9 100644 --- a/CefSharp/JavascriptBinding/JavascriptBindingSettings.cs +++ b/CefSharp/JavascriptBinding/JavascriptBindingSettings.cs @@ -53,7 +53,7 @@ public string[] JavascriptBindingApiAllowOrigins { javascriptBindingApiAllowOrigins = Array.ConvertAll( value, - origin => origin.EndsWith("/") ? origin.Substring(0, origin.Length - 1) : origin + origin => origin?.TrimEnd('/') ); } else From b54d60fd43cc28be393897c333dcd8217d10aa7a Mon Sep 17 00:00:00 2001 From: Luca Sonntag Date: Tue, 3 Mar 2026 11:24:22 +0100 Subject: [PATCH 10/10] Add more test cases --- .../JavascriptBindingTests.cs | 75 +++++++++++++++++-- 1 file changed, 70 insertions(+), 5 deletions(-) diff --git a/CefSharp.Test/JavascriptBinding/JavascriptBindingTests.cs b/CefSharp.Test/JavascriptBinding/JavascriptBindingTests.cs index b2f2f2d9a1..dfac95edf4 100644 --- a/CefSharp.Test/JavascriptBinding/JavascriptBindingTests.cs +++ b/CefSharp.Test/JavascriptBinding/JavascriptBindingTests.cs @@ -130,14 +130,17 @@ public async Task ShouldDisableJsBindingApi() } } - [Fact] - public async Task ShouldDisableJsBindingApiForOrigin() + [Theory] + [InlineData("notallowed")] + [InlineData("notallowed", "alsonotallowed")] + [InlineData("notallowed", "alsonotallowed", "stillnotallowed")] + public async Task ShouldDisableJsBindingApiForOrigin(params string[] origins) { using (var browser = new ChromiumWebBrowser(CefExample.BindingApiCustomObjectNameTestUrl, automaticallyCreateBrowser: false)) { var settings = browser.JavascriptObjectRepository.Settings; settings.JavascriptBindingApiEnabled = true; - settings.JavascriptBindingApiAllowOrigins = new string[] { "notallowed" }; + settings.JavascriptBindingApiAllowOrigins = origins; //To modify the settings we need to defer browser creation slightly browser.CreateBrowser(); @@ -157,9 +160,67 @@ public async Task ShouldDisableJsBindingApiForOrigin() } } + [Fact] + public async Task ShouldEnableJsBindingApiWhenOriginsListIsEmpty() + { + using (var browser = new ChromiumWebBrowser(CefExample.BindingApiCustomObjectNameTestUrl, automaticallyCreateBrowser: false)) + { + var settings = browser.JavascriptObjectRepository.Settings; + settings.JavascriptBindingApiEnabled = true; + settings.JavascriptBindingApiAllowOrigins = new string[0]; + + //To modify the settings we need to defer browser creation slightly + browser.CreateBrowser(); + + var loadResponse = await browser.WaitForInitialLoadAsync(); + + Assert.True(loadResponse.Success); + + var response1 = await browser.EvaluateScriptAsync("typeof window.cefSharp === 'undefined'"); + var response2 = await browser.EvaluateScriptAsync("typeof window.CefSharp === 'undefined'"); + + Assert.True(response1.Success); + Assert.False((bool)response1.Result); + + Assert.True(response2.Success); + Assert.False((bool)response2.Result); + } + } + + [Theory] + [InlineData(CefExample.BaseUrl)] + [InlineData(CefExample.BaseUrl + "/")] + public async Task ShouldEnableJsBindingApiForOriginWithOrWithoutTrailingSlash(string configuredOrigin) + { + using (var browser = new ChromiumWebBrowser(CefExample.BindingApiCustomObjectNameTestUrl, automaticallyCreateBrowser: false)) + { + var settings = browser.JavascriptObjectRepository.Settings; + settings.JavascriptBindingApiEnabled = true; + settings.JavascriptBindingApiAllowOrigins = new string[] { configuredOrigin }; + + //To modify the settings we need to defer browser creation slightly + browser.CreateBrowser(); + + var loadResponse = await browser.WaitForInitialLoadAsync(); + + Assert.True(loadResponse.Success); + + var response1 = await browser.EvaluateScriptAsync("typeof window.cefSharp === 'undefined'"); + var response2 = await browser.EvaluateScriptAsync("typeof window.CefSharp === 'undefined'"); + + Assert.True(response1.Success); + Assert.False((bool)response1.Result); + + Assert.True(response2.Success); + Assert.False((bool)response2.Result); + } + } [Theory] [InlineData(CefExample.BaseUrl + "/")] [InlineData("someorigin", CefExample.BaseUrl + "/")] + [InlineData(CefExample.BaseUrl + "/", "someorigin")] + [InlineData("firstorigin", "secondorigin", CefExample.BaseUrl + "/")] + [InlineData("firstorigin", CefExample.BaseUrl + "/", "secondorigin")] public async Task ShouldEnableJsBindingApiForOrigin(params string[] origins) { using (var browser = new ChromiumWebBrowser(CefExample.BindingApiCustomObjectNameTestUrl, automaticallyCreateBrowser: false)) @@ -171,7 +232,9 @@ public async Task ShouldEnableJsBindingApiForOrigin(params string[] origins) //To modify the settings we need to defer browser creation slightly browser.CreateBrowser(); - await browser.WaitForInitialLoadAsync(); + var loadResponse = await browser.WaitForInitialLoadAsync(); + + Assert.True(loadResponse.Success); var response1 = await browser.EvaluateScriptAsync("typeof window.cefSharp === 'undefined'"); var response2 = await browser.EvaluateScriptAsync("typeof window.CefSharp === 'undefined'"); @@ -195,7 +258,9 @@ public async Task ShouldEnableJsBindingApi() //To modify the settings we need to defer browser creation slightly browser.CreateBrowser(); - await browser.WaitForInitialLoadAsync(); + var loadResponse = await browser.WaitForInitialLoadAsync(); + + Assert.True(loadResponse.Success); var response1 = await browser.EvaluateScriptAsync("typeof window.cefSharp === 'undefined'"); var response2 = await browser.EvaluateScriptAsync("typeof window.CefSharp === 'undefined'");