diff --git a/src/CEFSubprocess/CEF/NirnLabSubprocessCefApp.cpp b/src/CEFSubprocess/CEF/NirnLabSubprocessCefApp.cpp index 31c04bf..438be2b 100644 --- a/src/CEFSubprocess/CEF/NirnLabSubprocessCefApp.cpp +++ b/src/CEFSubprocess/CEF/NirnLabSubprocessCefApp.cpp @@ -53,9 +53,7 @@ namespace NL::CEF continue; } - ++addedFuncCount; - - if (!objectName.empty() || objectName != IPC_JS_WINDOW_OBJECT_NAME) + if (!objectName.empty()) { auto objectValue = currentObjectValue->GetValue(objectName); if (objectValue == nullptr || objectValue->IsNull() || objectValue->IsUndefined()) @@ -69,6 +67,7 @@ namespace NL::CEF CefRefPtr funcHandler = new NL::JS::CEFFunctionHandler(a_browser, objectName); CefRefPtr funcValue = CefV8Value::CreateFunction(funcName, funcHandler); currentObjectValue->SetValue(funcName, funcValue, V8_PROPERTY_ATTRIBUTE_NONE); + ++addedFuncCount; } } } @@ -77,6 +76,60 @@ namespace NL::CEF return addedFuncCount; } + size_t NirnLabSubprocessCefApp::RemoveFunctionHandlers(CefRefPtr a_browser, + CefRefPtr a_frame, + CefProcessId a_sourceProcess, + CefRefPtr a_funcDict) + { + size_t removedFuncCount = 0; + const auto v8Context = a_frame->GetV8Context(); + if (!v8Context->Enter()) + { + spdlog::error("{}[{}]: can't enter v8 context", NameOf(NirnLabSubprocessCefApp::RemoveFunctionHandlers), ::GetCurrentProcessId()); + return removedFuncCount; + } + + CefDictionaryValue::KeyList keyList; + if (!a_funcDict->GetKeys(keyList)) + { + spdlog::error("{}[{}]: can't get keys from function dictionary", NameOf(NirnLabSubprocessCefApp::RemoveFunctionHandlers), ::GetCurrentProcessId()); + } + else + { + for (const auto& objectName : keyList) + { + auto currentObjectValue = v8Context->GetGlobal(); + const auto funcList = a_funcDict->GetList(objectName); + for (auto i = 0; i < funcList->GetSize(); ++i) + { + const auto& funcName = funcList->GetString(i); + if (funcName.empty()) + { + continue; + } + + if (!objectName.empty()) + { + auto objectValue = currentObjectValue->GetValue(objectName); + if (objectValue != nullptr && objectValue->IsObject()) + { + currentObjectValue = objectValue; + } + } + + // Note: the window object does not allow deleting a custom function for some reason. Use different object. + if (currentObjectValue->DeleteValue(funcName)) + { + ++removedFuncCount; + } + } + } + } + + v8Context->Exit(); + return removedFuncCount; + } + void NirnLabSubprocessCefApp::OnBeforeCommandLineProcessing(CefString const& process_type, CefRefPtr command_line) { @@ -154,6 +207,17 @@ namespace NL::CEF const auto addedFuncCount = AddFunctionHandlers(browser, frame, source_process, funcDict); spdlog::info("{}[{}]: registered {} functions for the browser with id {}", NameOf(NirnLabSubprocessCefApp::OnProcessMessageReceived), ::GetCurrentProcessId(), addedFuncCount, browser->GetIdentifier()); } + else if (message->GetName() == IPC_JS_FUNCTION_REMOVE_EVENT) + { + const auto funcDict = message->GetArgumentList()->GetDictionary(0); + if (funcDict == nullptr) + { + return true; + } + + const auto addedFuncCount = RemoveFunctionHandlers(browser, frame, source_process, funcDict); + spdlog::info("{}[{}]: removed {} functions for the browser with id {}", NameOf(NirnLabSubprocessCefApp::OnProcessMessageReceived), ::GetCurrentProcessId(), addedFuncCount, browser->GetIdentifier()); + } return false; } diff --git a/src/CEFSubprocess/CEF/NirnLabSubprocessCefApp.h b/src/CEFSubprocess/CEF/NirnLabSubprocessCefApp.h index 905605c..0d4effa 100644 --- a/src/CEFSubprocess/CEF/NirnLabSubprocessCefApp.h +++ b/src/CEFSubprocess/CEF/NirnLabSubprocessCefApp.h @@ -26,6 +26,10 @@ namespace NL::CEF CefRefPtr a_frame, CefProcessId a_sourceProcess, CefRefPtr a_funcDict); + size_t RemoveFunctionHandlers(CefRefPtr a_browser, + CefRefPtr a_frame, + CefProcessId a_sourceProcess, + CefRefPtr a_funcDict); // CefApp void OnBeforeCommandLineProcessing(CefString const& process_type, CefRefPtr command_line) override; diff --git a/src/CEFSubprocess/IPC.h b/src/CEFSubprocess/IPC.h index 3ef575b..ef1a2f5 100644 --- a/src/CEFSubprocess/IPC.h +++ b/src/CEFSubprocess/IPC.h @@ -7,3 +7,4 @@ #define IPC_JS_CONTEXT_CREATED "js-context-created" #define IPC_JS_FUNCTION_CALL_EVENT "js-function-call" #define IPC_JS_FUNCION_ADD_EVENT "js-function-add" +#define IPC_JS_FUNCTION_REMOVE_EVENT "js-function-remove" diff --git a/src/UIPlatform/CEF/DefaultBrowser.cpp b/src/UIPlatform/CEF/DefaultBrowser.cpp index 7df01cc..967f6df 100644 --- a/src/UIPlatform/CEF/DefaultBrowser.cpp +++ b/src/UIPlatform/CEF/DefaultBrowser.cpp @@ -57,8 +57,8 @@ namespace NL::CEF m_onMainFrameLoadStart_Connection = m_cefClient->onMainFrameLoadStart.connect([&]() { std::lock_guard locker(m_urlMutex); m_isPageLoaded = true; - - // JS funcs callback + + // Add js func callbacks if (m_clearJSFunctions) { m_jsFuncStorage->ClearFunctionCallback(); @@ -80,6 +80,13 @@ namespace NL::CEF } m_jsFuncCallbackInfoCache.clear(); + // Remove js func callbacks + for (auto& funcInfo : m_jsFuncRemoveCache) + { + RemoveFunctionCallbackAndSendMessage(std::get<0>(funcInfo).c_str(), std::get<1>(funcInfo).c_str()); + } + m_jsFuncRemoveCache.clear(); + // JS exec scripts if (!m_jsExecCache.empty()) { @@ -233,8 +240,25 @@ namespace NL::CEF auto dictValue = CefDictionaryValue::Create(); auto listValue = CefListValue::Create(); listValue->SetSize(1); - listValue->SetString(0, CefString(a_callbackInfo.funcName)); - dictValue->SetList(CefString(a_callbackInfo.objectName), listValue); + listValue->SetString(0, a_callbackInfo.funcName); + dictValue->SetList(a_callbackInfo.objectName, listValue); + cefMessage->GetArgumentList()->SetDictionary(0, dictValue); + browser->GetMainFrame()->SendProcessMessage(CefProcessId::PID_RENDERER, cefMessage); + } + } + + void DefaultBrowser::RemoveFunctionCallbackAndSendMessage(const char* a_objectName, const char* a_funcName) + { + m_jsFuncStorage->RemoveFunctionCallback(a_objectName, a_funcName); + const auto browser = m_cefClient->GetBrowser(); + if (browser != nullptr) + { + auto cefMessage = CefProcessMessage::Create(IPC_JS_FUNCTION_REMOVE_EVENT); + auto dictValue = CefDictionaryValue::Create(); + auto listValue = CefListValue::Create(); + listValue->SetSize(1); + listValue->SetString(0, a_objectName); + dictValue->SetList(a_funcName, listValue); cefMessage->GetArgumentList()->SetDictionary(0, dictValue); browser->GetMainFrame()->SendProcessMessage(CefProcessId::PID_RENDERER, cefMessage); } @@ -369,6 +393,14 @@ namespace NL::CEF std::lock_guard locker(m_urlMutex); if (!IsPageLoaded()) { + for (auto it = m_jsFuncRemoveCache.begin(); it != m_jsFuncRemoveCache.end(); ++it) + { + if (std::get<0>(*it) == a_callbackInfo.objectName && std::get<1>(*it) == a_callbackInfo.funcName) + { + m_jsFuncRemoveCache.erase(it); + } + } + m_jsFuncCallbackInfoCache.push_back(a_callbackInfo); return; } @@ -376,6 +408,34 @@ namespace NL::CEF AddFunctionCallbackAndSendMessage(a_callbackInfo); } + void __cdecl DefaultBrowser::RemoveFunctionCallback(const char* a_objectName, const char* a_funcName) + { + std::lock_guard locker(m_urlMutex); + if (!IsPageLoaded()) + { + for (auto it = m_jsFuncCallbackInfoCache.begin(); it != m_jsFuncCallbackInfoCache.end(); ++it) + { + if (it->objectNameString == a_objectName && it->funcNameString == a_funcName) + { + m_jsFuncCallbackInfoCache.erase(it); + } + } + + if (!m_jsFuncStorage->RemoveFunctionCallback(a_objectName, a_funcName)) + { + m_jsFuncRemoveCache.push_back({a_objectName, a_funcName}); + } + return; + } + + RemoveFunctionCallbackAndSendMessage(a_objectName, a_funcName); + } + + void __cdecl DefaultBrowser::RemoveFunctionCallback(const NL::JS::JSFuncInfo& a_callbackInfo) + { + RemoveFunctionCallback(a_callbackInfo.objectName, a_callbackInfo.funcName); + } + #pragma endregion #pragma region RE::MenuEventHandler diff --git a/src/UIPlatform/CEF/DefaultBrowser.h b/src/UIPlatform/CEF/DefaultBrowser.h index de43204..e4868e9 100644 --- a/src/UIPlatform/CEF/DefaultBrowser.h +++ b/src/UIPlatform/CEF/DefaultBrowser.h @@ -35,7 +35,8 @@ namespace NL::CEF std::vector> m_jsExecCache; // JS function callback - std::vector m_jsFuncCallbackInfoCache; + std::list m_jsFuncCallbackInfoCache; + std::list> m_jsFuncRemoveCache; RE::CursorMenu* m_cursorMenu = nullptr; float& m_currentMousePosX = RE::MenuCursor::GetSingleton()->cursorPosX; @@ -77,6 +78,7 @@ namespace NL::CEF bool IsReadyAndLog(); void AddFunctionCallbackAndSendMessage(const NL::JS::JSFuncInfo& a_callbackInfo); + void RemoveFunctionCallbackAndSendMessage(const char* a_objectName, const char* a_funcName); // IBrowser bool __cdecl IsBrowserReady() override; @@ -93,6 +95,8 @@ namespace NL::CEF void __cdecl LoadBrowserURL(const char* a_url, bool a_clearJSFunctions = true) override; void __cdecl ExecuteJavaScript(const char* a_script, const char* a_scriptUrl = JS_EXECUTE_SCRIPT_URL) override; void __cdecl AddFunctionCallback(const NL::JS::JSFuncInfo& a_callbackInfo) override; + void __cdecl RemoveFunctionCallback(const char* a_objectName, const char* a_funcName) override; + void __cdecl RemoveFunctionCallback(const NL::JS::JSFuncInfo& a_callbackInfo) override; // RE::MenuEventHandler bool CanProcess(RE::InputEvent* a_event) override; diff --git a/src/UIPlatform/NirnLabUIPlatformAPI/IBrowser.h b/src/UIPlatform/NirnLabUIPlatformAPI/IBrowser.h index 39850e9..28a8187 100644 --- a/src/UIPlatform/NirnLabUIPlatformAPI/IBrowser.h +++ b/src/UIPlatform/NirnLabUIPlatformAPI/IBrowser.h @@ -53,5 +53,7 @@ namespace NL::CEF virtual void __cdecl LoadBrowserURL(const char* a_url, bool a_clearJSFunctions = true) = 0; virtual void __cdecl ExecuteJavaScript(const char* a_script, const char* a_scriptUrl = JS_EXECUTE_SCRIPT_URL) = 0; virtual void __cdecl AddFunctionCallback(const NL::JS::JSFuncInfo& a_callbackInfo) = 0; + virtual void __cdecl RemoveFunctionCallback(const char* a_objectName, const char* a_funcName) = 0; + virtual void __cdecl RemoveFunctionCallback(const NL::JS::JSFuncInfo& a_callbackInfo) = 0; }; } diff --git a/src/UIPlatformTest/NirnLabUIPlatformAPI/IBrowser.h b/src/UIPlatformTest/NirnLabUIPlatformAPI/IBrowser.h index 39850e9..28a8187 100644 --- a/src/UIPlatformTest/NirnLabUIPlatformAPI/IBrowser.h +++ b/src/UIPlatformTest/NirnLabUIPlatformAPI/IBrowser.h @@ -53,5 +53,7 @@ namespace NL::CEF virtual void __cdecl LoadBrowserURL(const char* a_url, bool a_clearJSFunctions = true) = 0; virtual void __cdecl ExecuteJavaScript(const char* a_script, const char* a_scriptUrl = JS_EXECUTE_SCRIPT_URL) = 0; virtual void __cdecl AddFunctionCallback(const NL::JS::JSFuncInfo& a_callbackInfo) = 0; + virtual void __cdecl RemoveFunctionCallback(const char* a_objectName, const char* a_funcName) = 0; + virtual void __cdecl RemoveFunctionCallback(const NL::JS::JSFuncInfo& a_callbackInfo) = 0; }; } diff --git a/src/UIPlatformTest/TestCases/LocalTestPage.cpp b/src/UIPlatformTest/TestCases/LocalTestPage.cpp index 05aea88..a083b98 100644 --- a/src/UIPlatformTest/TestCases/LocalTestPage.cpp +++ b/src/UIPlatformTest/TestCases/LocalTestPage.cpp @@ -6,7 +6,7 @@ namespace NL::UI::TestCase { // func1 auto func1 = new JS::JSFuncInfo(); - func1->objectName = "window"; + func1->objectName = "NL"; func1->funcName = "func1"; func1->callbackData.executeInGameThread = false; func1->callbackData.callback = [](const char** a_args, int a_argsCount) { @@ -21,7 +21,7 @@ namespace NL::UI::TestCase // func2 auto func2 = new JS::JSFuncInfo(); - func2->objectName = "window"; + func2->objectName = "NL"; func2->funcName = "func2"; func2->callbackData.executeInGameThread = true; func2->callbackData.callback = [](const char** a_args, int a_argsCount) { @@ -58,10 +58,10 @@ namespace NL::UI::TestCase m_pingThread = std::make_shared([=]() { std::this_thread::sleep_for(12s); - m_browser->LoadBrowserURL("file:///_testLocalPage.html", false); + m_browser->LoadBrowserURL("file:///_testLocalPage.html", true); // func1 - JS::JSFuncInfoString strFunInfo("window", "func1"); + JS::JSFuncInfoString strFunInfo("NL", "func1"); strFunInfo.callbackData.executeInGameThread = false; strFunInfo.callbackData.callback = [](const char** a_args, int a_argsCount) { std::string argsStr = ""; @@ -73,17 +73,18 @@ namespace NL::UI::TestCase spdlog::info("func1__ callback. params: {}", argsStr); }; m_browser->AddFunctionCallback(strFunInfo); - + while (!m_browser->IsPageLoaded()) { std::this_thread::sleep_for(100ms); } + //m_browser->RemoveFunctionCallback(strFunInfo.objectName, strFunInfo.funcName); int i = 0; while (i < 10) { std::this_thread::sleep_for(1s); - m_browser->ExecuteJavaScript(fmt::format("window.func1({})", std::to_string(++i).c_str()).c_str()); + m_browser->ExecuteJavaScript(fmt::format("NL.func1({})", std::to_string(++i).c_str()).c_str()); } a_api->ReleaseBrowserHandle(m_browserHandle);