diff --git a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp index a7b2ceb1f3..6204834978 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; @@ -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, 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 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, 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 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 38f8da2881..ffae4d65f6 100644 --- a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h +++ b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h @@ -29,6 +29,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; @@ -69,6 +71,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/CefSharpFixture.cs b/CefSharp.Test/CefSharpFixture.cs index 184ef90f17..8981e51f0f 100644 --- a/CefSharp.Test/CefSharpFixture.cs +++ b/CefSharp.Test/CefSharpFixture.cs @@ -64,6 +64,7 @@ private void CefInitialize() settings.CachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CefSharp\\Tests\\Cache"); settings.RootCachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CefSharp\\Tests"); //settings.CefCommandLineArgs.Add("renderer-startup-dialog"); + //settings.CefCommandLineArgs.Add("disable-features=SpareRendererForSitePerProcess"); //settings.CefCommandLineArgs.Add("disable-site-isolation-trials"); settings.SetOffScreenRenderingBestPerformanceArgs(); 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; + } } }