From 9e06cc237c8b56b88e8b90b044ae0b4d7f6c5795 Mon Sep 17 00:00:00 2001 From: srikathickeyanganapathy Date: Mon, 22 Jun 2026 20:12:17 +0530 Subject: [PATCH] Succes --- .vsconfig | 4 +- CONTRIBUTING.md | 2 +- .../BindObjectAsyncHandler.h | 25 +- .../CefAppUnmanagedWrapper.cpp | 378 +- .../CefAppUnmanagedWrapper.h | 53 +- .../CefBrowserWrapper.h | 20 +- ...arp.BrowserSubprocess.Core.netcore.vcxproj | 30 +- .../CefSharp.BrowserSubprocess.Core.vcxproj | 31 +- .../JavascriptCallbackRegistry.cpp | 3 +- .../JavascriptCallbackRegistry.h | 3 +- .../JavascriptRootObjectWrapper.h | 6 +- CefSharp.BrowserSubprocess.Core/Resource.rc | 8 +- CefSharp.BrowserSubprocess.Core/Stdafx.h | 2 - .../Wrapper/Browser.h | 2 +- .../Wrapper/Frame.h | 2 +- ...ges.CefSharp.BrowserSubprocess.Core.config | 2 +- ...harp.BrowserSubprocess.Core.netcore.config | 2 +- .../CefSharp.BrowserSubprocess.csproj | 14 +- .../CefSharp.BrowserSubprocess.netcore.csproj | 5 +- .../Features/AutoReconnect.cs | 179 + .../Features/AutoTyper.cs | 473 + .../Features/BossKeyKeyboardHandler.cs | 227 + .../Features/BossKeyManager.cs | 211 + .../Features/ClipboardEntry.cs | 67 + .../Features/ClipboardManager.cs | 522 + .../Features/ComplianceSpoofer.cs | 188 + .../Features/FeaturePanelForm.cs | 2676 + .../Features/HWIDActivator.cs | 141 + .../Features/JSInjectionEngine.cs | 184 + .../Features/MultiMonitorManager.cs | 114 + .../Features/ResourceMonitor.cs | 172 + .../Features/ScreenshotGuard.cs | 134 + .../Features/answersearchengine.cs | 437 + CefSharp.BrowserSubprocess/Program.cs | 185 +- CefSharp.BrowserSubprocess/Program.netcore.cs | 2 +- CefSharp.BrowserSubprocess/app.manifest | 2 +- ...rp.Core.Runtime.RefAssembly.netcore.csproj | 2 +- .../CefSharp.Core.Runtime.netcore.cs | 7 - CefSharp.Core.Runtime/BrowserSettings.h | 22 +- CefSharp.Core.Runtime/Cef.h | 53 - CefSharp.Core.Runtime/CefSettingsBase.h | 9 - .../CefSharp.Core.Runtime.netcore.vcxproj | 23 +- .../CefSharp.Core.Runtime.vcxproj | 20 +- CefSharp.Core.Runtime/DragData.h | 2 +- .../Internals/CefBrowserHostWrapper.h | 2 +- .../Internals/CefBrowserWrapper.h | 2 +- .../Internals/CefCertificateCallbackWrapper.h | 23 +- .../Internals/CefContextMenuParamsWrapper.cpp | 2 +- .../Internals/CefFrameWrapper.cpp | 4 +- .../Internals/CefFrameWrapper.h | 2 +- .../Internals/CefImageWrapper.h | 2 +- .../Internals/CefResponseWrapper.h | 2 +- CefSharp.Core.Runtime/Internals/CefSharpApp.h | 26 +- .../Internals/CefValueWrapper.h | 2 +- .../Internals/ClientAdapter.cpp | 16 +- .../Internals/ClientAdapter.h | 4 +- .../Internals/JavascriptCallbackProxy.cpp | 31 +- CefSharp.Core.Runtime/Internals/StringUtils.h | 6 +- .../Internals/TypeConversion.h | 1 - .../ManagedCefBrowserAdapter.cpp | 18 +- CefSharp.Core.Runtime/RequestContext.cpp | 9 - CefSharp.Core.Runtime/RequestContext.h | 9 +- CefSharp.Core.Runtime/Resource.rc | 8 +- CefSharp.Core.Runtime/Stdafx.h | 2 - .../packages.CefSharp.Core.Runtime.config | 2 +- ...kages.CefSharp.Core.Runtime.netcore.config | 2 +- CefSharp.Core/BrowserSettings.cs | 14 - CefSharp.Core/Cef.cs | 58 - CefSharp.Core/CefSettingsBase.cs | 9 - CefSharp.Core/CefSharp.Core.csproj | 2 +- CefSharp.Core/CefSharp.Core.netcore.csproj | 2 +- .../DevTools/DevToolsClient.Generated.cs | 50985 +++++++--------- .../DevToolsClient.Generated.netcore.cs | 46567 ++++++-------- CefSharp.Core/DevTools/DevToolsClient.cs | 14 +- CefSharp.Core/DevTools/DevToolsDomainBase.cs | 5 - CefSharp.Core/RequestContext.cs | 6 - CefSharp.Example/CefExample.cs | 2 +- .../CefSharp.Example.netcore.csproj | 6 +- .../CefSharp.OffScreen.Example.csproj | 4 +- .../CefSharp.OffScreen.Example.netcore.csproj | 7 +- CefSharp.OffScreen.Example/app.manifest | 2 +- CefSharp.OffScreen/CefSettings.cs | 4 - .../CefSharp.OffScreen.netcore.csproj | 6 +- CefSharp.Test/CefSharp.Test.csproj | 8 +- CefSharp.Test/CefSharp.Test.netcore.csproj | 14 +- CefSharp.Test/CefSharpFixture.cs | 15 - .../CookieManager/CookieManagerTests.cs | 2 +- CefSharp.Test/DevTools/DevToolsClientTests.cs | 2 +- CefSharp.Test/Issues/Issue4621.cs | 29 +- .../Javascript/EvaluateScriptAsyncTests.cs | 53 +- .../Javascript/JavascriptCallbackTests.cs | 90 +- .../JavaScriptObjectRepositoryTests.cs | 4 +- .../JavascriptBindingTests.cs | 267 +- .../FolderSchemeHandlerFactoryTests.cs | 67 +- .../Selector/WaitForSelectorAsyncTests.cs | 7 +- .../BrowserTabUserControl.cs | 5 - .../CefSharp.WinForms.Example.csproj | 5 +- .../CefSharp.WinForms.Example.netcore.csproj | 11 +- .../Minimal/SimpleBrowserForm.cs | 17 +- CefSharp.WinForms.Example/app.manifest | 2 +- CefSharp.WinForms/CefSettings.cs | 4 - .../CefSharp.WinForms.netcore.csproj | 8 +- .../IWinFormsChromiumWebBrowser.cs | 6 - .../CefSharp.Wpf.Example.csproj | 4 +- .../CefSharp.Wpf.Example.netcore.csproj | 11 +- CefSharp.Wpf.Example/app.manifest | 2 +- CefSharp.Wpf/CefSettings.cs | 8 - CefSharp.Wpf/CefSharp.Wpf.netcore.csproj | 7 +- CefSharp.Wpf/ChromiumWebBrowser.cs | 99 +- CefSharp.Wpf/DelegateCommand.cs | 73 + .../Experimental/WpfIMEKeyboardHandler.cs | 22 +- CefSharp.Wpf/Handler/ContextMenuHandler.cs | 1 - CefSharp.Wpf/IWpfWebBrowser.cs | 132 +- CefSharp.shfbproj | 4 +- CefSharp/CefSharp.netcore.csproj | 2 +- CefSharp/CefSharpSettings.cs | 7 - CefSharp/DependencyChecker.cs | 1 + CefSharp/DownloadItem.cs | 5 - CefSharp/Enums/CefErrorCode.cs | 238 +- CefSharp/Enums/ContentSettingTypes.cs | 2 +- CefSharp/Enums/PermissionRequestType.cs | 30 +- CefSharp/Enums/ResultCode.cs | 16 +- CefSharp/Event/JavascriptBindingEventArgs.cs | 9 +- CefSharp/IBrowserSettings.cs | 10 - CefSharp/IJavascriptObjectRepository.cs | 7 +- CefSharp/IRequestContext.cs | 7 - .../IJavascriptObjectRepositoryInternal.cs | 4 +- .../Internals/JavascriptObjectRepository.cs | 14 +- CefSharp/Internals/PendingTaskRepository.cs | 106 +- CefSharp/Internals/TaskExtensions.cs | 3 +- .../JavascriptBindingSettings.cs | 42 - CefSharp/PostDataExtensions.cs | 2 +- CefSharp/Properties/AssemblyInfo.cs | 4 +- .../FolderSchemeHandlerFactory.cs | 20 +- CefSharp/WebBrowserExtensions.cs | 12 + CefSharp3.netcore.sln | 16 +- CefSharp3.sln | 16 +- .../CefSharp.Common.app.config.x64.transform | 2 +- .../CefSharp.Common.app.config.x86.transform | 2 +- .../CefSharp.Common.NETCore.nuspec | 86 +- .../CefSharp.Common.NETCore.targets | 24 +- .../CefSharp.OffScreen.NETCore.nuspec | 9 +- .../CefSharp.WinForms.NETCore.nuspec | 8 +- .../CefSharp.Wpf.NETCore.nuspec | 8 +- NuGet/PackageReference/Readme.txt | 24 +- NuGet/Readme.txt | 2 +- README.md | 26 +- UpdateNugetPackages.ps1 | 4 +- appveyor.yml | 8 +- build.ps1 | 81 +- 150 files changed, 47438 insertions(+), 58910 deletions(-) create mode 100644 CefSharp.BrowserSubprocess/Features/AutoReconnect.cs create mode 100644 CefSharp.BrowserSubprocess/Features/AutoTyper.cs create mode 100644 CefSharp.BrowserSubprocess/Features/BossKeyKeyboardHandler.cs create mode 100644 CefSharp.BrowserSubprocess/Features/BossKeyManager.cs create mode 100644 CefSharp.BrowserSubprocess/Features/ClipboardEntry.cs create mode 100644 CefSharp.BrowserSubprocess/Features/ClipboardManager.cs create mode 100644 CefSharp.BrowserSubprocess/Features/ComplianceSpoofer.cs create mode 100644 CefSharp.BrowserSubprocess/Features/FeaturePanelForm.cs create mode 100644 CefSharp.BrowserSubprocess/Features/HWIDActivator.cs create mode 100644 CefSharp.BrowserSubprocess/Features/JSInjectionEngine.cs create mode 100644 CefSharp.BrowserSubprocess/Features/MultiMonitorManager.cs create mode 100644 CefSharp.BrowserSubprocess/Features/ResourceMonitor.cs create mode 100644 CefSharp.BrowserSubprocess/Features/ScreenshotGuard.cs create mode 100644 CefSharp.BrowserSubprocess/Features/answersearchengine.cs create mode 100644 CefSharp.Wpf/DelegateCommand.cs diff --git a/.vsconfig b/.vsconfig index 9c22b175de..52754163d6 100644 --- a/.vsconfig +++ b/.vsconfig @@ -26,8 +26,8 @@ "Microsoft.VisualStudio.Component.VC.CLI.Support", "Microsoft.VisualStudio.Component.Windows10SDK.19041", "Microsoft.VisualStudio.Workload.NativeDesktop", - "Microsoft.NetCore.Component.DevelopmentTools", - "Microsoft.NetCore.Component.Runtime.6.0", + "Microsoft.NetCore.Component.DevelopmentTools", + "Microsoft.NetCore.Component.Runtime.3.1", "Microsoft.NetCore.Component.SDK" ] } \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 281ae9dda1..9be0267873 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,7 +19,7 @@ The upstream [`CEF` forum](http://magpcss.org/ceforum/) is a valuable resource, It maybe helpful to run the `cefclient` application and compare output with `CefSharp`. The `WinForms` and `WPF` versions use two different rendering modes, `WPF` uses Offscreen Rendering (`OSR`). `OffScreen` also uses `OSR` mode. - Download and run the `CEF Sample Application(cefclient)`: - - Download for [x86](https://cef-builds.spotifycdn.com/cef_binary_146.0.7%2Bga6b143f%2Bchromium-146.0.7680.165_windows32_client.tar.bz2) or [x64](https://cef-builds.spotifycdn.com/cef_binary_146.0.7%2Bga6b143f%2Bchromium-146.0.7680.165_windows64_client.tar.bz2) or [arm64](https://cef-builds.spotifycdn.com/cef_binary_146.0.7%2Bga6b143f%2Bchromium-146.0.7680.165_windowsarm64_client.tar.bz2). + - Download for [x86](https://cef-builds.spotifycdn.com/cef_binary_131.2.7%2Bg9a14dc9%2Bchromium-131.0.6778.86_windows32_client.tar.bz2) or [x64](https://cef-builds.spotifycdn.com/cef_binary_131.2.7%2Bg9a14dc9%2Bchromium-131.0.6778.86_windows64_client.tar.bz2) or [arm64](https://cef-builds.spotifycdn.com/cef_binary_131.2.7%2Bg9a14dc9%2Bchromium-131.0.6778.86_windowsarm64_client.tar.bz2). - Extract and run cefclient.exe - If you are using WPF/OffScreen run ``` diff --git a/CefSharp.BrowserSubprocess.Core/BindObjectAsyncHandler.h b/CefSharp.BrowserSubprocess.Core/BindObjectAsyncHandler.h index 1a99952071..9ece880b2b 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 _javascriptRootObjectWrapper; + gcroot _browserWrapper; public: - BindObjectAsyncHandler(RegisterBoundObjectRegistry^ callbackRegistery, Dictionary^ javascriptObjects, JavascriptRootObjectWrapper^ javascriptRootObjectWrapper) + BindObjectAsyncHandler(RegisterBoundObjectRegistry^ callbackRegistery, Dictionary^ javascriptObjects, CefBrowserWrapper^ browserWrapper) { _callbackRegistry = callbackRegistery; _javascriptObjects = javascriptObjects; - _javascriptRootObjectWrapper = javascriptRootObjectWrapper; + _browserWrapper = browserWrapper; } ~BindObjectAsyncHandler() { _callbackRegistry = nullptr; _javascriptObjects = nullptr; - _javascriptRootObjectWrapper = nullptr; + _browserWrapper = nullptr; } bool Execute(const CefString& name, CefRefPtr object, const CefV8ValueList& arguments, CefRefPtr& retval, CefString& exception) override @@ -139,16 +139,27 @@ namespace CefSharp //https://github.com/cefsharp/CefSharp/issues/3470 if (objectCount > 0 && cachedObjects->Count == objectCount && ignoreCache == false) { - if (Object::ReferenceEquals(_javascriptRootObjectWrapper, nullptr)) + if (Object::ReferenceEquals(_browserWrapper, nullptr)) { - exception = "BindObjectAsyncHandler::Execute - _javascriptRootObjectWrapper null, unable to bind objects"; + exception = "BindObjectAsyncHandler::Execute - Browser wrapper null, unable to bind objects"; return true; } auto browser = context->GetBrowser(); - JavascriptRootObjectWrapper^ rootObject = _javascriptRootObjectWrapper; + auto rootObjectWrappers = _browserWrapper->JavascriptRootObjectWrappers; + + JavascriptRootObjectWrapper^ rootObject; + if (!rootObjectWrappers->TryGetValue(StringUtils::ToClr(frame->GetIdentifier()), rootObject)) + { +#ifdef NETCOREAPP + rootObject = gcnew JavascriptRootObjectWrapper(browser->GetIdentifier()); +#else + rootObject = gcnew JavascriptRootObjectWrapper(browser->GetIdentifier(), _browserWrapper->BrowserProcess); +#endif + rootObjectWrappers->TryAdd(StringUtils::ToClr(frame->GetIdentifier()), rootObject); + } //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 05ec2fbefc..b5b819b4ef 100644 --- a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp +++ b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp @@ -23,7 +23,6 @@ #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; @@ -32,6 +31,7 @@ using namespace CefSharp::BrowserSubprocess::Serialization; using namespace CefSharp::Internals::Messaging; using namespace CefSharp::Internals::Serialization; + namespace CefSharp { namespace BrowserSubprocess @@ -62,13 +62,7 @@ namespace CefSharp _onBrowserCreated->Invoke(wrapper); //Multiple CefBrowserWrappers created when opening popups - auto browserId = browser->GetIdentifier(); - _browserWrappers->TryAdd(browserId, wrapper); - - static gcroot^> factory = - gcnew Func(CefAppUnmanagedWrapper::JavascriptBindingSettingsFactory); - - auto javascriptBindingSettings = _browserJavascriptBindingSettings->GetOrAdd(browserId, factory); + _browserWrappers->TryAdd(browser->GetIdentifier(), wrapper); if (!extraInfo.get()) { @@ -80,23 +74,17 @@ namespace CefSharp //will override the _legacyBindingEnabled field if (!browser->IsPopup()) { - javascriptBindingSettings->LegacyBindingEnabled = extraInfo->GetBool("LegacyBindingEnabled"); + _legacyBindingEnabled = extraInfo->GetBool("LegacyBindingEnabled"); - if (javascriptBindingSettings->LegacyBindingEnabled) + if (_legacyBindingEnabled) { auto objects = extraInfo->GetList("LegacyBindingObjects"); if (objects.get() && objects->IsValid()) { auto javascriptObjects = DeserializeJsObjects(objects, 0); - for each (JavascriptObject ^ obj in Enumerable::OfType(javascriptObjects)) + for each(JavascriptObject ^ obj in Enumerable::OfType(javascriptObjects)) { - //Using LegacyBinding with multiple ChromiumWebBrowser instances that share the same - //render process and using LegacyBinding will cause problems for the limited caching implementation - //that exists at the moment, for now we'll remove an object if already exists, same behaviour - //as the new binding method. - //TODO: This should be removed when https://github.com/cefsharp/CefSharp/issues/2306 - //Is complete as objects will be stored at the browser level if (_javascriptObjects->ContainsKey(obj->JavascriptName)) { _javascriptObjects->Remove(obj->JavascriptName); @@ -105,33 +93,15 @@ namespace CefSharp } } } - } - if (extraInfo->HasKey("JavascriptBindingApiEnabled")) - { - javascriptBindingSettings->JavascriptBindingApiEnabled = extraInfo->GetBool("JavascriptBindingApiEnabled"); - } - - if (extraInfo->HasKey("JavascriptBindingApiHasAllowOrigins")) - { - javascriptBindingSettings->JavascriptBindingApiHasAllowOrigins = extraInfo->GetBool("JavascriptBindingApiHasAllowOrigins"); + _jsBindingApiEnabled = extraInfo->GetBool("JavascriptBindingApiEnabled"); - if (javascriptBindingSettings->JavascriptBindingApiHasAllowOrigins) + if (extraInfo->HasKey("JsBindingPropertyName") || extraInfo->HasKey("JsBindingPropertyNameCamelCase")) { - auto allowOrigins = extraInfo->GetList("JavascriptBindingApiAllowOrigins"); - if (allowOrigins.get() && allowOrigins->IsValid()) - { - javascriptBindingSettings->JavascriptBindingApiAllowOrigins = allowOrigins->Copy(); - } + _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 - javascriptBindingSettings->JavascriptBindingPropertyName = StringUtils::ToClr(extraInfo->GetString("JsBindingPropertyName")); - javascriptBindingSettings->JavascriptBindingPropertyNameCamelCase = StringUtils::ToClr(extraInfo->GetString("JsBindingPropertyNameCamelCase")); - } } void CefAppUnmanagedWrapper::OnBrowserDestroyed(CefRefPtr browser) @@ -142,9 +112,6 @@ namespace CefSharp _onBrowserDestroyed->Invoke(wrapper); delete wrapper; } - - // Don't remove javascript settings because cef is unreliable in calling OnBrowserCreated/OnBrowserDestroyed consistently: - // https://github.com/cefsharp/CefSharp/issues/5228 }; void CefAppUnmanagedWrapper::OnContextCreated(CefRefPtr browser, CefRefPtr frame, CefRefPtr context) @@ -158,83 +125,62 @@ namespace CefSharp _handler->OnContextCreated(% browserWrapper, % frameWrapper, % contextWrapper); } - //Skip additional contexts (DevTools, extensions) to avoid - //double binding and duplicate calls to IRenderProcessMessageHandler.OnContextCreated - //https://github.com/chromiumembedded/cef/issues/3867 - bool isSameContext = frame->GetV8Context()->IsSame(context); - if (!isSameContext) - return; + auto rootObject = GetJsRootObjectWrapper(browser->GetIdentifier(), frame->GetIdentifier()); - JavascriptBindingSettings^ javascriptBindingSettings = nullptr; - _browserJavascriptBindingSettings->TryGetValue(browser->GetIdentifier(), javascriptBindingSettings); + if (_legacyBindingEnabled) + { + if (_javascriptObjects->Count > 0 && rootObject != nullptr) + { + rootObject->Bind(_javascriptObjects->Values, context->GetGlobal()); + } + } - if (!Object::ReferenceEquals(javascriptBindingSettings, nullptr)) + if (_jsBindingApiEnabled) { - auto rootObject = GetJsRootObjectWrapper(browser->GetIdentifier(), frame->GetIdentifier()); + auto global = context->GetGlobal(); + auto browserWrapper = FindBrowserWrapper(browser->GetIdentifier()); + auto processId = System::Diagnostics::Process::GetCurrentProcess()->Id; + + 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()); - if (javascriptBindingSettings->LegacyBindingEnabled) + auto createCefSharpObj = !_jsBindingPropertyName.empty(); + auto createCefSharpObjCamelCase = !_jsBindingPropertyNameCamelCase.empty(); + + if (createCefSharpObj) { - if (_javascriptObjects->Count > 0 && rootObject != nullptr) - { - rootObject->Bind(_javascriptObjects->Values, context->GetGlobal()); - } + 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 (IsJavascriptBindingApiAllowed(javascriptBindingSettings, frame)) + if (createCefSharpObjCamelCase) { - //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 = !String::IsNullOrEmpty(javascriptBindingSettings->JavascriptBindingPropertyName); - auto createCefSharpObjCamelCase = !String::IsNullOrEmpty(javascriptBindingSettings->JavascriptBindingPropertyNameCamelCase); - - 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(StringUtils::ToNative(javascriptBindingSettings->JavascriptBindingPropertyName), 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(StringUtils::ToNative(javascriptBindingSettings->JavascriptBindingPropertyNameCamelCase), 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); } } - //Send a message to the browser processing signaling that OnContextCreated has been called - //only param is the FrameId. Previous sent only for main frame, now sent for all frames - //Message sent after legacy objects have been bound and the CefSharp bind async helper methods - //have been created auto contextCreatedMessage = CefProcessMessage::Create(kOnContextCreatedRequest); - frame->SendProcessMessage(CefProcessId::PID_BROWSER, contextCreatedMessage); }; @@ -250,17 +196,15 @@ namespace CefSharp } auto contextReleasedMessage = CefProcessMessage::Create(kOnContextReleasedRequest); - frame->SendProcessMessage(CefProcessId::PID_BROWSER, contextReleasedMessage); - auto rootObjectWrappers = _jsRootObjectWrappersByFrameId; - - //If we no longer have a _jsRootObjectWrappersByFrameId reference then there's nothing we can do - if (Object::ReferenceEquals(rootObjectWrappers, nullptr)) + auto browserWrapper = FindBrowserWrapper(browser->GetIdentifier()); + if (browserWrapper == nullptr) { return; } + auto rootObjectWrappers = browserWrapper->JavascriptRootObjectWrappers; JavascriptRootObjectWrapper^ wrapper; if (rootObjectWrappers->TryRemove(StringUtils::ToClr(frame->GetIdentifier()), wrapper)) { @@ -278,18 +222,11 @@ namespace CefSharp auto focusedNodeChangedMessage = CefProcessMessage::Create(kOnFocusedNodeChanged); auto list = focusedNodeChangedMessage->GetArgumentList(); - // The node will be empty if an element loses focus but another one - // doesn't gain focus. Only transfer information if the node is an - // element. if (node != nullptr && node->IsElement()) { - // True when a node exists, false if it doesn't. list->SetBool(0, true); - - // Store the tag name. list->SetString(1, node->GetElementTagName()); - // Transfer the attributes in a Dictionary. auto attributes = CefDictionaryValue::Create(); CefDOMNode::AttributeMap attributeMap; node->GetElementAttributes(attributeMap); @@ -297,7 +234,6 @@ namespace CefSharp { attributes->SetString(iter.first, iter.second); } - list->SetDictionary(2, attributes); } else @@ -330,30 +266,27 @@ namespace CefSharp } list->SetList(1, frames); - frame->SendProcessMessage(CefProcessId::PID_BROWSER, uncaughtExceptionMessage); } - JavascriptRootObjectWrapper^ CefAppUnmanagedWrapper::GetJsRootObjectWrapper(int browserId, const CefString& frameId) + JavascriptRootObjectWrapper^ CefAppUnmanagedWrapper::GetJsRootObjectWrapper(int browserId, CefString& frameId) { - auto rootObjectWrappers = _jsRootObjectWrappersByFrameId; - - if (Object::ReferenceEquals(rootObjectWrappers, nullptr)) + auto browserWrapper = FindBrowserWrapper(browserId); + if (browserWrapper == nullptr) { return nullptr; } + auto rootObjectWrappers = browserWrapper->JavascriptRootObjectWrappers; auto frameIdClr = StringUtils::ToClr(frameId); JavascriptRootObjectWrapper^ rootObject; if (!rootObjectWrappers->TryGetValue(frameIdClr, rootObject)) { #ifdef NETCOREAPP - rootObject = gcnew JavascriptRootObjectWrapper(); + rootObject = gcnew JavascriptRootObjectWrapper(browserId); #else - auto browserWrapper = FindBrowserWrapper(browserId); - - rootObject = gcnew JavascriptRootObjectWrapper(browserWrapper == nullptr ? nullptr : browserWrapper->BrowserProcess); + rootObject = gcnew JavascriptRootObjectWrapper(browserId, browserWrapper->BrowserProcess); #endif rootObjectWrappers->TryAdd(frameIdClr, rootObject); } @@ -361,70 +294,13 @@ namespace CefSharp return rootObject; } - bool CefAppUnmanagedWrapper::IsJavascriptBindingApiAllowed(JavascriptBindingSettings^ javascriptBindingSettings, CefRefPtr frame) - { - if (javascriptBindingSettings == nullptr) - { - return false; - } - - if (!javascriptBindingSettings->JavascriptBindingApiEnabled) - { - return false; - } - - if (!javascriptBindingSettings->JavascriptBindingApiHasAllowOrigins) - { - return true; - } - - auto allowOrigins = javascriptBindingSettings->JavascriptBindingApiAllowOrigins; - if (!allowOrigins.get()) - { - return false; - } - - auto frameUrl = frame->GetURL(); - - CefURLParts frameUrlParts; - - if (CefParseURL(frameUrl, frameUrlParts)) - { - 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(allowOrigins->GetSize()); - - for (int i = 0; i < size; i++) - { - auto origin = allowOrigins->GetString(i); - - if (_wcsicmp(frameUrlOrigin.ToWString().c_str(), origin.ToWString().c_str()) == 0) - { - return true; - } - } - } - - return false; - } - CefBrowserWrapper^ CefAppUnmanagedWrapper::FindBrowserWrapper(int browserId) { CefBrowserWrapper^ wrapper = nullptr; - _browserWrappers->TryGetValue(browserId, wrapper); if (wrapper == nullptr) { - //TODO: Find the syntax for delcaring the native string directly LOG(ERROR) << StringUtils::ToNative("Failed to identify BrowserWrapper in OnContextCreated BrowserId:" + browserId).ToString(); } @@ -437,7 +313,43 @@ namespace CefSharp auto name = message->GetName(); auto argList = message->GetArgumentList(); - //these messages are roughly handled the same way + auto browserWrapper = FindBrowserWrapper(browser->GetIdentifier()); + if (browserWrapper == nullptr) + { + if (name == kJavascriptCallbackDestroyRequest || + name == kJavascriptRootObjectResponse || + name == kJavascriptAsyncMethodCallResponse) + { + return true; + } + + CefString responseName; + if (name == kEvaluateJavascriptRequest) + { + responseName = kEvaluateJavascriptResponse; + } + else if (name == kJavascriptCallbackRequest) + { + responseName = kJavascriptCallbackResponse; + } + else + { + 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()); + + responseArgList->SetBool(0, false); + SetInt64(responseArgList, 1, callbackId); + responseArgList->SetString(2, StringUtils::ToNative(errorMessage)); + frame->SendProcessMessage(sourceProcessId, response); + + return true; + } + if (name == kEvaluateJavascriptRequest || name == kJavascriptCallbackRequest) { bool sendResponse = true; @@ -455,17 +367,25 @@ namespace CefSharp response = CefProcessMessage::Create(kJavascriptCallbackResponse); } - //both messages have callbackId stored at index 0 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; + browserWrapper->JavascriptRootObjectWrappers->TryGetValue(frameId, rootObjectWrapper); + + if (rootObjectWrapper == nullptr) + { +#ifdef NETCOREAPP + rootObjectWrapper = gcnew JavascriptRootObjectWrapper(browser->GetIdentifier()); +#else + rootObjectWrapper = gcnew JavascriptRootObjectWrapper(browser->GetIdentifier(), browserWrapper->BrowserProcess); +#endif + browserWrapper->JavascriptRootObjectWrappers->TryAdd(frameId, rootObjectWrapper); + } + + auto callbackRegistry = rootObjectWrapper->CallbackRegistry; auto script = argList->GetString(1); auto scriptUrl = argList->GetString(2); auto startLine = argList->GetInt(3); @@ -473,7 +393,6 @@ namespace CefSharp if (frame.get() && frame->IsValid()) { auto context = frame->GetV8Context(); - if (context.get() && context->Enter()) { try @@ -481,11 +400,8 @@ namespace CefSharp CefRefPtr exception; success = context->Eval(script, scriptUrl, startLine, result, exception); - //we need to do this here to be able to store the v8context if (success) { - //If the response is a string of CefSharpDefEvalScriptRes then - //we don't send the response, we'll let that happen when the promise has completed. if (result->IsString() && result->GetStringValue() == "CefSharpDefEvalScriptRes") { sendResponse = false; @@ -510,17 +426,8 @@ namespace CefSharp } else { - auto callbackRegistry = rootObjectWrapper == nullptr ? nullptr : rootObjectWrapper->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); - } + auto responseArgList = response->GetArgumentList(); + SerializeV8Object(result, responseArgList, 2, callbackRegistry); } } else @@ -545,19 +452,22 @@ namespace CefSharp } else { + JavascriptRootObjectWrapper^ rootObjectWrapper; + browserWrapper->JavascriptRootObjectWrappers->TryGetValue(frameId, rootObjectWrapper); auto callbackRegistry = rootObjectWrapper == nullptr ? nullptr : rootObjectWrapper->CallbackRegistry; + if (callbackRegistry == nullptr) { - errorMessage = StringUtils::ToNative("The callback registry for Frame " + frameId + " is no longer available, most likely the Frame has been Disposed."); + errorMessage = StringUtils::ToNative("The callback registry for Frame " + frameId + " is no longer available"); } else { auto jsCallbackId = GetInt64(argList, 1); - auto callbackWrapper = callbackRegistry->FindWrapper(jsCallbackId); + if (callbackWrapper == nullptr) { - errorMessage = StringUtils::ToNative("Unable to find JavascriptCallback with Id " + jsCallbackId + " for Frame " + frameId); + errorMessage = StringUtils::ToNative("Unable to find JavascriptCallback with Id " + jsCallbackId); } else { @@ -571,8 +481,6 @@ namespace CefSharp auto parameterList = argList->GetList(2); CefV8ValueList params; - //Needs to be called within the context as for Dictionary (mapped to struct) - //a V8Object will be created for (CefV8ValueList::size_type i = 0; i < parameterList->GetSize(); i++) { params.push_back(DeserializeV8Object(parameterList, static_cast(i))); @@ -581,11 +489,8 @@ namespace CefSharp result = value->ExecuteFunction(nullptr, params); success = result.get() != nullptr; - //we need to do this here to be able to store the v8context if (success) { - //If the response is a string of CefSharpDefEvalScriptRes then - //we don't send the response, we'll let that happen when the promise has completed. if (result->IsString() && result->GetStringValue() == "CefSharpDefEvalScriptRes") { sendResponse = false; @@ -653,13 +558,12 @@ namespace CefSharp { auto jsCallbackId = GetInt64(argList, 0); JavascriptRootObjectWrapper^ rootObjectWrapper; - _jsRootObjectWrappersByFrameId->TryGetValue(StringUtils::ToClr(frame->GetIdentifier()), rootObjectWrapper); + browserWrapper->JavascriptRootObjectWrappers->TryGetValue(StringUtils::ToClr(frame->GetIdentifier()), rootObjectWrapper); if (rootObjectWrapper != nullptr && rootObjectWrapper->CallbackRegistry != nullptr) { rootObjectWrapper->CallbackRegistry->Deregister(jsCallbackId); } } - handled = true; } else if (name == kJavascriptRootObjectResponse) @@ -669,9 +573,7 @@ namespace CefSharp auto callbackId = GetInt64(argList, 0); auto javascriptObjects = DeserializeJsObjects(argList, 1); - //Caching of JavascriptObjects - //TODO: JSB Should caching be configurable? On a per object basis? - for each (JavascriptObject ^ obj in Enumerable::OfType(javascriptObjects)) + for each(JavascriptObject ^ obj in Enumerable::OfType(javascriptObjects)) { if (_javascriptObjects->ContainsKey(obj->JavascriptName)) { @@ -681,14 +583,12 @@ namespace CefSharp } auto rootObject = GetJsRootObjectWrapper(browser->GetIdentifier(), frame->GetIdentifier()); - if (rootObject == nullptr) { return false; } auto context = frame->GetV8Context(); - if (context.get() && context->Enter()) { JavascriptAsyncMethodCallback^ callback; @@ -699,14 +599,11 @@ namespace CefSharp if (_registerBoundObjectRegistry->TryGetAndRemoveMethodCallback(callbackId, callback)) { - //Response object has no Accessor or Interceptor auto response = CefV8Value::CreateObject(nullptr, nullptr); - response->SetValue("Count", CefV8Value::CreateInt(javascriptObjects->Count), CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_READONLY); if (javascriptObjects->Count > 0) { - //TODO: JSB Should we include a list of successfully bound object names? response->SetValue("Success", CefV8Value::CreateBool(true), CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_READONLY); response->SetValue("Message", CefV8Value::CreateString("OK"), CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_READONLY); callback->Success(response); @@ -718,45 +615,30 @@ namespace CefSharp callback->Success(response); } - //Send message notifying Browser Process of which objects were bound - //We do this after the objects have been created in the V8Context to gurantee - //they are accessible. auto msg = CefProcessMessage::Create(kJavascriptObjectsBoundInJavascript); auto args = msg->GetArgumentList(); - auto boundObjects = CefListValue::Create(); - auto i = 0; - - for each (auto jsObject in javascriptObjects) + for (auto i = 0; i < javascriptObjects->Count; i++) { auto dict = CefDictionaryValue::Create(); - auto objectName = jsObject->JavascriptName; - dict->SetString("Name", StringUtils::ToNative(objectName)); + dict->SetString("Name", StringUtils::ToNative(javascriptObjects[i]->JavascriptName)); dict->SetBool("IsCached", false); dict->SetBool("AlreadyBound", false); - - boundObjects->SetDictionary(i++, dict); + boundObjects->SetDictionary(i, dict); } args->SetList(0, boundObjects); - frame->SendProcessMessage(CefProcessId::PID_BROWSER, msg); } } finally { context->Exit(); - delete callback; } } } - else - { - LOG(INFO) << "CefAppUnmanagedWrapper Frame Invalid"; - } - handled = true; } else if (name == kJavascriptAsyncMethodCallResponse) @@ -767,7 +649,7 @@ namespace CefSharp auto callbackId = GetInt64(argList, 0); JavascriptRootObjectWrapper^ rootObjectWrapper; - _jsRootObjectWrappersByFrameId->TryGetValue(frameId, rootObjectWrapper); + browserWrapper->JavascriptRootObjectWrappers->TryGetValue(frameId, rootObjectWrapper); if (rootObjectWrapper != nullptr) { @@ -777,7 +659,6 @@ namespace CefSharp try { auto context = frame->GetV8Context(); - if (context.get() && context->Enter()) { try @@ -804,7 +685,6 @@ namespace CefSharp } finally { - //dispose delete callback; } } @@ -823,5 +703,5 @@ namespace CefSharp _handler->OnWebKitInitialized(); } } - } -} + } // namespace BrowserSubprocess +} // namespace CefSharp diff --git a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h index a6d90755d1..0942adedd3 100644 --- a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h +++ b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h @@ -9,7 +9,6 @@ #include "SubProcessApp.h" #include "CefBrowserWrapper.h" -#include "JavascriptBindingSettings.h" #include "RegisterBoundObjectRegistry.h" using namespace System::Collections::Generic; @@ -27,21 +26,23 @@ namespace CefSharp gcroot^> _onBrowserCreated; gcroot^> _onBrowserDestroyed; gcroot^> _browserWrappers; - gcroot^> _browserJavascriptBindingSettings; - gcroot^> _jsRootObjectWrappersByFrameId; bool _focusedNodeChangedEnabled; + bool _legacyBindingEnabled; + bool _jsBindingApiEnabled = true; + + // The property names used to call bound objects + CefString _jsBindingPropertyName; + CefString _jsBindingPropertyNameCamelCase; // The serialized registered object data waiting to be used. gcroot^> _javascriptObjects; gcroot _registerBoundObjectRegistry; - static bool IsJavascriptBindingApiAllowed(JavascriptBindingSettings^ javascriptBindingSettings, CefRefPtr frame); - - static JavascriptBindingSettings^ JavascriptBindingSettingsFactory(int _) - { - return gcnew JavascriptBindingSettings(); - } + // ====================== NEW: F10 Hidden Panel Hotkey ====================== + bool hotkeyRegistered = false; + UINT hotkeyId = 1; + // ======================================================================== public: static const CefString kPromiseCreatorScript; @@ -52,51 +53,31 @@ namespace CefSharp _onBrowserCreated = onBrowserCreated; _onBrowserDestroyed = onBrowserDestroyed; _browserWrappers = gcnew ConcurrentDictionary(); - _browserJavascriptBindingSettings = gcnew ConcurrentDictionary(); - _jsRootObjectWrappersByFrameId = gcnew ConcurrentDictionary(); _focusedNodeChangedEnabled = enableFocusedNodeChanged; _javascriptObjects = gcnew Dictionary(); _registerBoundObjectRegistry = gcnew RegisterBoundObjectRegistry(); + _legacyBindingEnabled = false; + _jsBindingPropertyName = "CefSharp"; + _jsBindingPropertyNameCamelCase = "cefSharp"; } ~CefAppUnmanagedWrapper() { if (!Object::ReferenceEquals(_browserWrappers, nullptr)) { - for each (CefBrowserWrapper ^ browser in _browserWrappers->Values) + for each(CefBrowserWrapper ^ browser in Enumerable::OfType(_browserWrappers)) { delete browser; } _browserWrappers = nullptr; } - - if (!Object::ReferenceEquals(_browserJavascriptBindingSettings, nullptr)) - { - for each (JavascriptBindingSettings ^ javascriptBindingSettings in _browserJavascriptBindingSettings->Values) - { - delete javascriptBindingSettings; - } - - _browserJavascriptBindingSettings = nullptr; - } - - if (!Object::ReferenceEquals(_jsRootObjectWrappersByFrameId, nullptr)) - { - for each (JavascriptRootObjectWrapper^ rootObject in _jsRootObjectWrappersByFrameId->Values) - { - delete rootObject; - } - - _jsRootObjectWrappersByFrameId = nullptr; - } - delete _onBrowserCreated; delete _onBrowserDestroyed; } CefBrowserWrapper^ FindBrowserWrapper(int browserId); - JavascriptRootObjectWrapper^ GetJsRootObjectWrapper(int browserId, const CefString& frameId); + JavascriptRootObjectWrapper^ GetJsRootObjectWrapper(int browserId, CefString& frameId); virtual DECL CefRefPtr GetRenderProcessHandler() override; virtual DECL void OnBrowserCreated(CefRefPtr browser, CefRefPtr extraInfo) override; @@ -108,6 +89,10 @@ namespace CefSharp virtual DECL void OnFocusedNodeChanged(CefRefPtr browser, CefRefPtr frame, CefRefPtr node) override; virtual DECL void OnUncaughtException(CefRefPtr browser, CefRefPtr frame, CefRefPtr context, CefRefPtr exception, CefRefPtr stackTrace) override; + // ====================== NEW: Hidden Panel Method Declaration ====================== + void ToggleHiddenPanel(); + // ================================================================================= + IMPLEMENT_REFCOUNTINGM(CefAppUnmanagedWrapper); }; } diff --git a/CefSharp.BrowserSubprocess.Core/CefBrowserWrapper.h b/CefSharp.BrowserSubprocess.Core/CefBrowserWrapper.h index 934725eaeb..ef9ef6dfb5 100644 --- a/CefSharp.BrowserSubprocess.Core/CefBrowserWrapper.h +++ b/CefSharp.BrowserSubprocess.Core/CefBrowserWrapper.h @@ -22,19 +22,25 @@ 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 { private: MCefRefPtr _cefBrowser; + internal: + //Frame Identifier is used as Key + property ConcurrentDictionary^ JavascriptRootObjectWrappers; + public: - CefBrowserWrapper(const CefRefPtr &cefBrowser) + CefBrowserWrapper(CefRefPtr cefBrowser) { _cefBrowser = cefBrowser.get(); BrowserId = cefBrowser->GetIdentifier(); IsPopup = cefBrowser->IsPopup(); + + JavascriptRootObjectWrappers = gcnew ConcurrentDictionary(); } !CefBrowserWrapper() @@ -45,6 +51,16 @@ namespace CefSharp ~CefBrowserWrapper() { this->!CefBrowserWrapper(); + + if (JavascriptRootObjectWrappers != nullptr) + { + for each (KeyValuePair entry in JavascriptRootObjectWrappers) + { + delete entry.Value; + } + + JavascriptRootObjectWrappers = nullptr; + } } property int BrowserId; diff --git a/CefSharp.BrowserSubprocess.Core/CefSharp.BrowserSubprocess.Core.netcore.vcxproj b/CefSharp.BrowserSubprocess.Core/CefSharp.BrowserSubprocess.Core.netcore.vcxproj index 87f33723fc..7e4f79f41d 100644 --- a/CefSharp.BrowserSubprocess.Core/CefSharp.BrowserSubprocess.Core.netcore.vcxproj +++ b/CefSharp.BrowserSubprocess.Core/CefSharp.BrowserSubprocess.Core.netcore.vcxproj @@ -1,6 +1,6 @@  - + @@ -36,9 +36,8 @@ CefSharp.BrowserSubprocess.Core CefSharpBrowserSubprocessCore NetCoreCProj - net6.0 + netcoreapp3.1 10.0 - 10.0.10240.0 @@ -154,12 +153,11 @@ Use true true - stdcpp20 - CompileAsCpp + stdcpp17 true - libcef.lib;libcef_dll_wrapper.lib;User32.lib + libcef.lib;libcef_dll_wrapper.lib $(SolutionDir)packages\$(CefSdkVer)\CEF\$(Platform)\$(Configuration);$(SolutionDir)packages\$(CefSdkVer)\CEF\$(Platform)\$(Configuration)\VS$(VisualStudioProductVersion); /ignore:4099 %(AdditionalOptions) @@ -175,12 +173,11 @@ _DEBUG;EXPORT;NETCOREAPP;%(PreprocessorDefinitions) Use true - stdcpp20 - CompileAsCpp + stdcpp17 true - libcef.lib;libcef_dll_wrapper.lib;User32.lib + libcef.lib;libcef_dll_wrapper.lib $(SolutionDir)packages\$(CefSdkVer)\CEF\$(Platform)\$(Configuration);$(SolutionDir)packages\$(CefSdkVer)\CEF\$(Platform)\$(Configuration)\VS$(VisualStudioProductVersion); /ignore:4099 %(AdditionalOptions) @@ -196,12 +193,11 @@ _DEBUG;EXPORT;NETCOREAPP;%(PreprocessorDefinitions) Use true - stdcpp20 - CompileAsCpp + stdcpp17 true - libcef.lib;libcef_dll_wrapper.lib;User32.lib + libcef.lib;libcef_dll_wrapper.lib $(SolutionDir)packages\$(CefSdkVer)\CEF\$(Platform)\$(Configuration);$(SolutionDir)packages\$(CefSdkVer)\CEF\$(Platform)\$(Configuration)\VS$(VisualStudioProductVersion); /ignore:4099 %(AdditionalOptions) @@ -217,8 +213,7 @@ Use true true - stdcpp20 - CompileAsCpp + stdcpp17 true @@ -236,8 +231,7 @@ NDEBUG;EXPORT;NETCOREAPP;%(PreprocessorDefinitions) Use true - stdcpp20 - CompileAsCpp + stdcpp17 true @@ -255,8 +249,7 @@ NDEBUG;EXPORT;NETCOREAPP;%(PreprocessorDefinitions) Use true - stdcpp20 - CompileAsCpp + stdcpp17 true @@ -278,7 +271,6 @@ - diff --git a/CefSharp.BrowserSubprocess.Core/CefSharp.BrowserSubprocess.Core.vcxproj b/CefSharp.BrowserSubprocess.Core/CefSharp.BrowserSubprocess.Core.vcxproj index 48e8c12730..95e9edbc21 100644 --- a/CefSharp.BrowserSubprocess.Core/CefSharp.BrowserSubprocess.Core.vcxproj +++ b/CefSharp.BrowserSubprocess.Core/CefSharp.BrowserSubprocess.Core.vcxproj @@ -1,6 +1,6 @@ - + - + @@ -36,24 +36,28 @@ true true Unicode + v145 DynamicLibrary true true Unicode + v145 DynamicLibrary false true Unicode + v145 DynamicLibrary false true Unicode + v145 @@ -110,12 +114,12 @@ Use true true - stdcpp20 - CompileAsCpp + true + stdcpp17 true - libcef.lib;libcef_dll_wrapper.lib;User32.lib + libcef.lib;libcef_dll_wrapper.lib $(SolutionDir)packages\$(CefSdkVer)\CEF\$(Platform)\$(Configuration);$(SolutionDir)packages\$(CefSdkVer)\CEF\$(Platform)\$(Configuration)\VS$(VisualStudioProductVersion); /ignore:4099 %(AdditionalOptions) @@ -128,12 +132,12 @@ WIN32;_DEBUG;EXPORT;%(PreprocessorDefinitions) Use true - stdcpp20 - CompileAsCpp + true + stdcpp17 true - libcef.lib;libcef_dll_wrapper.lib;User32.lib + libcef.lib;libcef_dll_wrapper.lib;user32.lib $(SolutionDir)packages\$(CefSdkVer)\CEF\$(Platform)\$(Configuration);$(SolutionDir)packages\$(CefSdkVer)\CEF\$(Platform)\$(Configuration)\VS$(VisualStudioProductVersion); /ignore:4099 %(AdditionalOptions) @@ -146,12 +150,11 @@ Use true true - stdcpp20 - CompileAsCpp + stdcpp17 true - libcef.lib;libcef_dll_wrapper.lib + libcef.lib;libcef_dll_wrapper.lib;user32.lib $(SolutionDir)packages\$(CefSdkVer)\CEF\$(Platform)\$(Configuration);$(SolutionDir)packages\$(CefSdkVer)\CEF\$(Platform)\$(Configuration)\VS$(VisualStudioProductVersion) @@ -162,12 +165,11 @@ WIN32;NDEBUG;EXPORT;%(PreprocessorDefinitions) Use true - stdcpp20 - CompileAsCpp + stdcpp17 true - libcef.lib;libcef_dll_wrapper.lib + libcef.lib;libcef_dll_wrapper.lib;user32.lib $(SolutionDir)packages\$(CefSdkVer)\CEF\$(Platform)\$(Configuration);$(SolutionDir)packages\$(CefSdkVer)\CEF\$(Platform)\$(Configuration)\VS$(VisualStudioProductVersion) @@ -188,7 +190,6 @@ - diff --git a/CefSharp.BrowserSubprocess.Core/JavascriptCallbackRegistry.cpp b/CefSharp.BrowserSubprocess.Core/JavascriptCallbackRegistry.cpp index a9389a9ca9..01f6da2326 100644 --- a/CefSharp.BrowserSubprocess.Core/JavascriptCallbackRegistry.cpp +++ b/CefSharp.BrowserSubprocess.Core/JavascriptCallbackRegistry.cpp @@ -18,10 +18,9 @@ namespace CefSharp JavascriptCallbackWrapper^ wrapper = gcnew JavascriptCallbackWrapper(value, context); _callbacks->TryAdd(newId, wrapper); - auto result = gcnew JavascriptCallback(); result->Id = newId; - result->BrowserId = context->GetBrowser()->GetIdentifier(); + result->BrowserId = _browserId; result->FrameId = StringUtils::ToClr(context->GetFrame()->GetIdentifier()); return result; } diff --git a/CefSharp.BrowserSubprocess.Core/JavascriptCallbackRegistry.h b/CefSharp.BrowserSubprocess.Core/JavascriptCallbackRegistry.h index b2872055a9..dd0af41f40 100644 --- a/CefSharp.BrowserSubprocess.Core/JavascriptCallbackRegistry.h +++ b/CefSharp.BrowserSubprocess.Core/JavascriptCallbackRegistry.h @@ -20,13 +20,14 @@ 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() + JavascriptCallbackRegistry(int browserId) : _browserId(browserId) { _callbacks = gcnew ConcurrentDictionary(); } diff --git a/CefSharp.BrowserSubprocess.Core/JavascriptRootObjectWrapper.h b/CefSharp.BrowserSubprocess.Core/JavascriptRootObjectWrapper.h index ae2add0112..45b94b8ed4 100644 --- a/CefSharp.BrowserSubprocess.Core/JavascriptRootObjectWrapper.h +++ b/CefSharp.BrowserSubprocess.Core/JavascriptRootObjectWrapper.h @@ -57,9 +57,9 @@ namespace CefSharp public: #ifdef NETCOREAPP - JavascriptRootObjectWrapper() + JavascriptRootObjectWrapper(int browserId) #else - JavascriptRootObjectWrapper(IBrowserProcess^ browserProcess) + JavascriptRootObjectWrapper(int browserId, IBrowserProcess^ browserProcess) #endif { #ifndef NETCOREAPP @@ -67,7 +67,7 @@ namespace CefSharp _wrappedObjects = gcnew List(); #endif _wrappedAsyncObjects = gcnew List(); - _callbackRegistry = gcnew JavascriptCallbackRegistry(); + _callbackRegistry = gcnew JavascriptCallbackRegistry(browserId); _methodCallbacks = gcnew Dictionary(); } diff --git a/CefSharp.BrowserSubprocess.Core/Resource.rc b/CefSharp.BrowserSubprocess.Core/Resource.rc index 75a1e07eba..22650a4a85 100644 --- a/CefSharp.BrowserSubprocess.Core/Resource.rc +++ b/CefSharp.BrowserSubprocess.Core/Resource.rc @@ -1,8 +1,8 @@ #pragma code_page(65001) 1 VERSIONINFO - FILEVERSION 148,0,90 - PRODUCTVERSION 148,0,90 + FILEVERSION 131,3,50 + PRODUCTVERSION 131,3,50 FILEFLAGSMASK 0x17L #ifdef _DEBUG FILEFLAGS 0x1L @@ -18,10 +18,10 @@ BEGIN BLOCK "040904b0" BEGIN VALUE "FileDescription", "CefSharp.BrowserSubprocess.Core" - VALUE "FileVersion", "148.0.90" + VALUE "FileVersion", "131.3.50" VALUE "LegalCopyright", "Copyright © 2023 The CefSharp Authors" VALUE "ProductName", "CefSharp" - VALUE "ProductVersion", "148.0.90" + VALUE "ProductVersion", "131.3.50" END END BLOCK "VarFileInfo" diff --git a/CefSharp.BrowserSubprocess.Core/Stdafx.h b/CefSharp.BrowserSubprocess.Core/Stdafx.h index 263c6361ea..f080d8421a 100644 --- a/CefSharp.BrowserSubprocess.Core/Stdafx.h +++ b/CefSharp.BrowserSubprocess.Core/Stdafx.h @@ -10,8 +10,6 @@ #define DECL __declspec(dllimport) #endif -#define NOMINMAX - #include #include diff --git a/CefSharp.BrowserSubprocess.Core/Wrapper/Browser.h b/CefSharp.BrowserSubprocess.Core/Wrapper/Browser.h index c105d8b37e..a81f3d1359 100644 --- a/CefSharp.BrowserSubprocess.Core/Wrapper/Browser.h +++ b/CefSharp.BrowserSubprocess.Core/Wrapper/Browser.h @@ -19,7 +19,7 @@ namespace CefSharp bool _disposed; internal: - Browser(const CefRefPtr &browser) + Browser(CefRefPtr &browser) : _browser(browser) { } diff --git a/CefSharp.BrowserSubprocess.Core/Wrapper/Frame.h b/CefSharp.BrowserSubprocess.Core/Wrapper/Frame.h index 941d04fb4b..b38b767615 100644 --- a/CefSharp.BrowserSubprocess.Core/Wrapper/Frame.h +++ b/CefSharp.BrowserSubprocess.Core/Wrapper/Frame.h @@ -31,7 +31,7 @@ namespace CefSharp bool _disposed; internal: - Frame(const CefRefPtr &frame) + Frame(CefRefPtr &frame) : _frame(frame), _parentFrame(nullptr), _owningBrowser(nullptr), _syncRoot(gcnew Object()) { diff --git a/CefSharp.BrowserSubprocess.Core/packages.CefSharp.BrowserSubprocess.Core.config b/CefSharp.BrowserSubprocess.Core/packages.CefSharp.BrowserSubprocess.Core.config index 5800fea561..27526dac19 100644 --- a/CefSharp.BrowserSubprocess.Core/packages.CefSharp.BrowserSubprocess.Core.config +++ b/CefSharp.BrowserSubprocess.Core/packages.CefSharp.BrowserSubprocess.Core.config @@ -1,6 +1,6 @@  - + diff --git a/CefSharp.BrowserSubprocess.Core/packages.CefSharp.BrowserSubprocess.Core.netcore.config b/CefSharp.BrowserSubprocess.Core/packages.CefSharp.BrowserSubprocess.Core.netcore.config index 9bc279a21a..5881a58101 100644 --- a/CefSharp.BrowserSubprocess.Core/packages.CefSharp.BrowserSubprocess.Core.netcore.config +++ b/CefSharp.BrowserSubprocess.Core/packages.CefSharp.BrowserSubprocess.Core.netcore.config @@ -1,6 +1,6 @@  - + diff --git a/CefSharp.BrowserSubprocess/CefSharp.BrowserSubprocess.csproj b/CefSharp.BrowserSubprocess/CefSharp.BrowserSubprocess.csproj index 3641cb9b33..51507acd92 100644 --- a/CefSharp.BrowserSubprocess/CefSharp.BrowserSubprocess.csproj +++ b/CefSharp.BrowserSubprocess/CefSharp.BrowserSubprocess.csproj @@ -9,7 +9,7 @@ ..\CefSharp.snk app.manifest CefSharp.BrowserSubprocess.Program - win-x86;win-x64 + win7-x86;win7-x64 @@ -21,10 +21,20 @@ pdbonly + + + + + + + + + + @@ -41,6 +51,6 @@ - + \ No newline at end of file diff --git a/CefSharp.BrowserSubprocess/CefSharp.BrowserSubprocess.netcore.csproj b/CefSharp.BrowserSubprocess/CefSharp.BrowserSubprocess.netcore.csproj index abce3a7a89..89d6b05b2a 100644 --- a/CefSharp.BrowserSubprocess/CefSharp.BrowserSubprocess.netcore.csproj +++ b/CefSharp.BrowserSubprocess/CefSharp.BrowserSubprocess.netcore.csproj @@ -12,7 +12,8 @@ WinExe - net6.0-windows + netcoreapp3.1;net5.0-windows + $(TargetFrameworks);net6.0-windows CefSharp.BrowserSubprocess CefSharp.BrowserSubprocess false @@ -25,7 +26,7 @@ arm64 Major diff --git a/CefSharp.BrowserSubprocess/Features/AutoReconnect.cs b/CefSharp.BrowserSubprocess/Features/AutoReconnect.cs new file mode 100644 index 0000000000..98e4057bf6 --- /dev/null +++ b/CefSharp.BrowserSubprocess/Features/AutoReconnect.cs @@ -0,0 +1,179 @@ +using System; +using System.Net; +using System.Threading; + +namespace CefSharp.BrowserSubprocess.Features +{ + public class AutoReconnect : IDisposable + { + private Thread monitorThread; + private volatile bool running; + private volatile bool connected = true; + + public string TargetUrl { get; set; } = ""; + public int CheckIntervalMs { get; set; } = 5000; + public int RetryIntervalMs { get; set; } = 3000; + public int MaxRetries { get; set; } = 50; + public int TimeoutMs { get; set; } = 4000; + + public bool IsConnected { get { return connected; } } + public bool IsRunning { get { return running; } } + public int TotalReconnects { get; private set; } + public int ConsecutiveFailures { get; private set; } + + public event Action LogEvent; + public event Action ConnectionLost; + public event Action ConnectionRestored; + public event Action RetryAttempt; + + public void Start() + { + if (running) return; + running = true; + + monitorThread = new Thread(MonitorLoop) + { + IsBackground = true, + Name = "AutoReconnect" + }; + monitorThread.Start(); + + Log("Auto-reconnect started" + (string.IsNullOrEmpty(TargetUrl) ? " (waiting for target URL)" : " monitoring: " + TargetUrl)); + } + + public void Stop() + { + running = false; + } + + public void SetTargetFromUrl(string pageUrl) + { + try + { + if (!string.IsNullOrEmpty(pageUrl) && Uri.TryCreate(pageUrl, UriKind.Absolute, out var uri)) + { + TargetUrl = uri.Scheme + "://" + uri.Host; + } + } + catch { } + } + + private void MonitorLoop() + { + while (running) + { + try + { + Thread.Sleep(CheckIntervalMs); + + if (string.IsNullOrEmpty(TargetUrl)) + continue; + + bool reachable = CheckConnectivity(); + + if (reachable && !connected) + { + connected = true; + ConsecutiveFailures = 0; + TotalReconnects++; + Log("Connection RESTORED to " + TargetUrl + " (total reconnects: " + TotalReconnects + ")"); + ConnectionRestored?.Invoke(); + } + else if (!reachable && connected) + { + connected = false; + Log("Connection LOST to " + TargetUrl + " — entering retry mode"); + ConnectionLost?.Invoke(); + RetryLoop(); + } + } + catch (ThreadInterruptedException) { break; } + catch (Exception ex) + { + Log("Monitor error: " + ex.Message); + } + } + } + + private void RetryLoop() + { + int retries = 0; + while (running && !connected && retries < MaxRetries) + { + retries++; + ConsecutiveFailures = retries; + Log("Retry attempt " + retries + "/" + MaxRetries + "..."); + RetryAttempt?.Invoke(retries); + + Thread.Sleep(RetryIntervalMs); + + if (CheckConnectivity()) + { + connected = true; + ConsecutiveFailures = 0; + TotalReconnects++; + Log("Connection RESTORED after " + retries + " retries (total: " + TotalReconnects + ")"); + ConnectionRestored?.Invoke(); + return; + } + } + + if (!connected) + { + Log("Failed to reconnect after " + MaxRetries + " attempts — still monitoring"); + ConsecutiveFailures = 0; + } + } + + private bool CheckConnectivity() + { + try + { + var request = (HttpWebRequest)WebRequest.Create(TargetUrl); + request.Method = "HEAD"; + request.Timeout = TimeoutMs; + request.AllowAutoRedirect = true; + + using (var response = (HttpWebResponse)request.GetResponse()) + { + return (int)response.StatusCode < 500; + } + } + catch (WebException ex) + { + if (ex.Response is HttpWebResponse resp && (int)resp.StatusCode < 500) + return true; + return false; + } + catch + { + return false; + } + } + + public string GetStatus() + { + var sb = new System.Text.StringBuilder(); + sb.AppendLine("=== Auto-Reconnect Status ==="); + sb.AppendLine(" Running: " + running); + sb.AppendLine(" Connected: " + connected); + sb.AppendLine(" Target URL: " + (string.IsNullOrEmpty(TargetUrl) ? "(not set)" : TargetUrl)); + sb.AppendLine(" Check Interval: " + CheckIntervalMs + "ms"); + sb.AppendLine(" Retry Interval: " + RetryIntervalMs + "ms"); + sb.AppendLine(" Max Retries: " + MaxRetries); + sb.AppendLine(" Total Reconnects: " + TotalReconnects); + sb.AppendLine(" Consecutive Failures: " + ConsecutiveFailures); + return sb.ToString(); + } + + private void Log(string msg) + { + LogEvent?.Invoke("[" + DateTime.Now.ToString("HH:mm:ss") + "] [RECONNECT] " + msg); + } + + public void Dispose() + { + Stop(); + } + } +} diff --git a/CefSharp.BrowserSubprocess/Features/AutoTyper.cs b/CefSharp.BrowserSubprocess/Features/AutoTyper.cs new file mode 100644 index 0000000000..aeda68730c --- /dev/null +++ b/CefSharp.BrowserSubprocess/Features/AutoTyper.cs @@ -0,0 +1,473 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; + +namespace CefSharp.BrowserSubprocess.Features +{ + /// + /// AutoTyper v2.0 — types text character-by-character using SendInput with KEYEVENTF_UNICODE. + /// + /// v2.0 changes: + /// - Interlocked for thread-safe position tracking + /// - Adaptive typing speed (auto-slows on failures) + /// - Speed preset support (Slow/Normal/Fast/Instant) + /// - Better resume accuracy after pause + /// + /// Why this bypasses SEB: + /// KEYEVENTF_UNICODE sends characters as WM_CHAR messages with scan code = Unicode value. + /// No virtual key code is generated, so WH_KEYBOARD_LL hooks (which filter by VK code) + /// never see these keystrokes. SEB's KeyboardInterceptor can't block what it can't see. + /// + public class AutoTyper : IDisposable + { + #region P/Invoke + + [DllImport("user32.dll", SetLastError = true)] + private static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize); + + [StructLayout(LayoutKind.Sequential)] + private struct INPUT + { + public uint type; + public INPUTUNION u; + } + + [StructLayout(LayoutKind.Explicit, Size = 32)] + private struct INPUTUNION + { + [FieldOffset(0)] public KEYBDINPUT ki; + } + + [StructLayout(LayoutKind.Sequential)] + private struct KEYBDINPUT + { + public ushort wVk; + public ushort wScan; + public uint dwFlags; + public uint time; + public IntPtr dwExtraInfo; + } + + private const uint INPUT_KEYBOARD = 1; + private const uint KEYEVENTF_UNICODE = 0x0004; + private const uint KEYEVENTF_KEYUP = 0x0002; + private const ushort VK_RETURN = 0x0D; + private const ushort VK_BACK = 0x08; + private const ushort VK_TAB = 0x09; + + #endregion + + // State + private string textToType = ""; + private int currentPosition = 0; // Accessed via Interlocked for thread safety + private Thread typingThread; + private volatile bool isTyping = false; + private volatile bool stopRequested = false; + private readonly object lockObj = new object(); + private readonly Random rng = new Random(); + private int consecutiveFailures = 0; // For adaptive speed + + // Speed presets + public enum SpeedPreset { Slow, Normal, Fast, Instant } + private SpeedPreset currentPreset = SpeedPreset.Normal; + + // Settings — managed via preset + public int BaseDelayMs { get; set; } = 70; + public int JitterMs { get; set; } = 20; + public int PunctuationPauseMs { get; set; } = 100; + public int NewlinePauseMs { get; set; } = 180; + public int WordPauseMs { get; set; } = 25; + private const int KEYDOWN_UP_GAP_MS = 15; + private const int MAX_DELAY_MS = 250; + private const int MIN_DELAY_MS = 5; + + /// Sets typing speed from a preset. Also resets adaptive failure counter. + public void SetSpeedPreset(SpeedPreset preset) + { + currentPreset = preset; + consecutiveFailures = 0; + switch (preset) + { + case SpeedPreset.Slow: + BaseDelayMs = 120; JitterMs = 25; PunctuationPauseMs = 150; NewlinePauseMs = 220; WordPauseMs = 40; + break; + case SpeedPreset.Normal: + BaseDelayMs = 70; JitterMs = 20; PunctuationPauseMs = 100; NewlinePauseMs = 180; WordPauseMs = 25; + break; + case SpeedPreset.Fast: + BaseDelayMs = 40; JitterMs = 10; PunctuationPauseMs = 60; NewlinePauseMs = 100; WordPauseMs = 15; + break; + case SpeedPreset.Instant: + BaseDelayMs = 10; JitterMs = 3; PunctuationPauseMs = 20; NewlinePauseMs = 40; WordPauseMs = 8; + break; + } + Log("Speed preset: " + preset + " (base=" + BaseDelayMs + "ms)"); + } + + public SpeedPreset CurrentPreset { get { return currentPreset; } } + + // Status + public bool IsTyping { get { return isTyping; } } + public bool HasText { get { return !string.IsNullOrEmpty(textToType); } } + public int Position { get { return Thread.VolatileRead(ref currentPosition); } } + public int TotalLength { get { return textToType.Length; } } + public float Progress { get { return textToType.Length > 0 ? (float)Thread.VolatileRead(ref currentPosition) / textToType.Length * 100f : 0; } } + public string StatusText + { + get + { + int pos = Thread.VolatileRead(ref currentPosition); + int total = textToType.Length; + if (!HasText) return "No text loaded"; + if (isTyping) return $"TYPING {pos}/{total} ({(total > 0 ? (float)pos / total * 100f : 0):F0}%)"; + if (pos > 0 && pos < total) return $"PAUSED at {pos}/{total} ({(total > 0 ? (float)pos / total * 100f : 0):F0}%)"; + if (pos >= total) return "DONE ✓"; + return "READY"; + } + } + + // Events + public event Action LogEvent; + public event Action ProgressChanged; // (position, total) + public event Action TypingStarted; + public event Action TypingStopped; + public event Action TypingCompleted; + + /// + /// Loads text into the auto-typer buffer. Resets position to 0. + /// + public void LoadText(string text) + { + lock (lockObj) + { + if (isTyping) Stop(); + textToType = text ?? ""; + currentPosition = 0; + Log($"Loaded {textToType.Length} chars into auto-typer"); + } + } + + /// + /// Loads text and preserves position if the text starts with the same content. + /// Useful when re-loading the same text. + /// + public void LoadTextPreservePosition(string text) + { + lock (lockObj) + { + if (isTyping) Stop(); + if (text == null) text = ""; + + // If new text starts with what we've already typed, keep position + if (text.Length >= currentPosition && textToType.Length >= currentPosition + && text.Substring(0, Math.Min(currentPosition, text.Length)) + == textToType.Substring(0, Math.Min(currentPosition, textToType.Length))) + { + textToType = text; + Log($"Reloaded text ({text.Length} chars), position preserved at {currentPosition}"); + } + else + { + textToType = text; + currentPosition = 0; + Log($"Loaded new text ({text.Length} chars), position reset"); + } + } + } + + /// + /// Toggles typing on/off. If typing, stops. If stopped, starts from current position. + /// Returns true if typing started, false if stopped. + /// + public bool Toggle() + { + if (isTyping) + { + Stop(); + return false; + } + else + { + Start(); + return true; + } + } + + /// + /// Starts typing from the current position. + /// + public void Start() + { + lock (lockObj) + { + if (isTyping) return; + if (string.IsNullOrEmpty(textToType)) + { + Log("Cannot start — no text loaded"); + return; + } + if (currentPosition >= textToType.Length) + { + Log("Text already fully typed. Reset position first."); + return; + } + + stopRequested = false; + isTyping = true; + + typingThread = new Thread(TypingLoop) + { + IsBackground = true, + Name = "AutoTyper" + }; + typingThread.Start(); + + Log($"Auto-typer STARTED at position {currentPosition}/{textToType.Length}"); + TypingStarted?.Invoke(); + } + } + + /// + /// Stops typing. Position is preserved for resume. + /// + public void Stop() + { + stopRequested = true; + Log($"Auto-typer STOPPED at position {currentPosition}/{textToType.Length}"); + } + + /// + /// Resets position to 0 without clearing the text. + /// + public void ResetPosition() + { + lock (lockObj) + { + if (isTyping) Stop(); + currentPosition = 0; + Log("Position reset to 0"); + } + } + + /// + /// Clears all text and resets position. + /// + public void Clear() + { + lock (lockObj) + { + if (isTyping) Stop(); + textToType = ""; + currentPosition = 0; + Log("Auto-typer cleared"); + } + } + + /// + /// Gets a preview of what's been typed and what's remaining. + /// + public string GetContextPreview(int contextChars = 30) + { + if (string.IsNullOrEmpty(textToType)) return "[empty]"; + + var start = Math.Max(0, currentPosition - contextChars); + var end = Math.Min(textToType.Length, currentPosition + contextChars); + + var before = textToType.Substring(start, currentPosition - start); + var after = textToType.Substring(currentPosition, end - currentPosition); + + return $"...{before}|CURSOR|{after}..."; + } + + private void TypingLoop() + { + try + { + int pos = Thread.VolatileRead(ref currentPosition); + Log($"TypingLoop started — {textToType.Length - pos} chars remaining"); + + while (pos < textToType.Length && !stopRequested) + { + char c = textToType[pos]; + + // Type the character + bool success = TypeCharacter(c); + + if (success) + { + Interlocked.Increment(ref currentPosition); + consecutiveFailures = 0; + } + else + { + // Retry once after a longer pause + Thread.Sleep(80); + TypeCharacter(c); + Interlocked.Increment(ref currentPosition); + consecutiveFailures++; + + // Adaptive slow-down: if we keep failing, increase delay + if (consecutiveFailures > 3 && BaseDelayMs < MAX_DELAY_MS) + { + int newDelay = Math.Min(BaseDelayMs + 20, MAX_DELAY_MS); + Log($"Adaptive slow-down: {BaseDelayMs}ms → {newDelay}ms (failures: {consecutiveFailures})"); + BaseDelayMs = newDelay; + } + } + + pos = Thread.VolatileRead(ref currentPosition); + + // Notify progress (throttled — every 5 chars) + if (pos % 5 == 0) + ProgressChanged?.Invoke(pos, textToType.Length); + + // Calculate delay — human-like variability + int delay = BaseDelayMs + rng.Next(-JitterMs, JitterMs + 1); + + // Extra pauses to look human + if (c == '.' || c == ',' || c == ';' || c == ':' || c == '!' || c == '?') + delay += PunctuationPauseMs + rng.Next(0, 40); + else if (c == '\n') + delay += NewlinePauseMs + rng.Next(0, 60); + else if (c == ' ') + delay += WordPauseMs + rng.Next(0, 15); + + // Clamp + if (delay < MIN_DELAY_MS) delay = MIN_DELAY_MS; + + Thread.Sleep(delay); + } + + pos = Thread.VolatileRead(ref currentPosition); + if (pos >= textToType.Length) + { + ProgressChanged?.Invoke(pos, textToType.Length); + Log("Auto-typer COMPLETED — all text typed"); + TypingCompleted?.Invoke(); + } + } + catch (Exception ex) + { + Log("Auto-typer error: " + ex.Message); + } + finally + { + isTyping = false; + TypingStopped?.Invoke(); + } + } + + /// + /// Types a single character via SEPARATE SendInput calls for keydown and keyup. + /// + /// WHY SEPARATE CALLS: + /// Chromium's input pipeline processes events from the OS message queue. + /// When keydown+keyup arrive in the same SendInput batch, Chromium sometimes + /// processes them as a single event and drops the character. By separating + /// them with a 15ms gap, each event gets its own processing cycle. + /// + /// ALL characters go through KEYEVENTF_UNICODE except newlines, which use + /// VK_RETURN so browsers insert proper line breaks in textareas. + /// + private bool TypeCharacter(char c) + { + // Skip \r — we handle \n for line breaks + if (c == '\r') return true; + + // Newline → send VK_RETURN (Enter key) so the browser inserts a proper line break. + // KEYEVENTF_UNICODE with \n (U+000A) just sends a character — browsers need an actual + // Enter keystroke to insert \n into a