From 752f8ca157b33848e5e64fb952c6af84d25c086b Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Tue, 21 Apr 2026 11:45:47 +0000 Subject: [PATCH 1/3] Add compressed debug symbols for release 0.22.23 --- debug-symbols/0.22.23/android-debug-symbols.7z | 3 +++ debug-symbols/0.22.23/macos-debug-symbols.7z | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 debug-symbols/0.22.23/android-debug-symbols.7z create mode 100644 debug-symbols/0.22.23/macos-debug-symbols.7z diff --git a/debug-symbols/0.22.23/android-debug-symbols.7z b/debug-symbols/0.22.23/android-debug-symbols.7z new file mode 100644 index 0000000000..9a4088a5a5 --- /dev/null +++ b/debug-symbols/0.22.23/android-debug-symbols.7z @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da536c3698a35b10b664c885d7a8232f06aac26cd4efba2e0e1d82a3621a08ad +size 13009265 diff --git a/debug-symbols/0.22.23/macos-debug-symbols.7z b/debug-symbols/0.22.23/macos-debug-symbols.7z new file mode 100644 index 0000000000..10187fef2e --- /dev/null +++ b/debug-symbols/0.22.23/macos-debug-symbols.7z @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ef05de60f1a6fb9b284ab73a074ef1e6118bd13a1b37ab8673a9b3408fac8b19 +size 99988 From 1eab726daffa845d4b058c94d0a85c9af59e15ed Mon Sep 17 00:00:00 2001 From: "Dalton.m" Date: Tue, 28 Apr 2026 10:19:09 +0800 Subject: [PATCH 2/3] perf(bridge): skip FlushUICommand for DOM-independent modules --- bridge/core/frame/module_manager.cc | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/bridge/core/frame/module_manager.cc b/bridge/core/frame/module_manager.cc index 7b6c84c785..14795b7574 100644 --- a/bridge/core/frame/module_manager.cc +++ b/bridge/core/frame/module_manager.cc @@ -3,6 +3,8 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ #include "module_manager.h" +#include +#include #include "core/executing_context.h" #include "core/frame/window.h" #include "foundation/logging.h" @@ -177,7 +179,15 @@ NativeValue* ModuleManager::__webf_invoke_module__(ExecutingContext* context, return nullptr; } - context->FlushUICommand(context->window(), FlushUICommandReason::kDependentsAll); + // Modules that do not read DOM state can skip FlushUICommand, avoiding a + // PostToDartSync round-trip on every call. Add module names here when the + // module is purely network/storage/device and never inspects the DOM tree. + static const std::unordered_set kNoFlushModules = { + "Fetch", "AsyncStorage", "LocalStorage", "SessionStorage", "Clipboard", "TextCodec", "Navigator", + }; + if (kNoFlushModules.find(module_name.ToStdString(context->ctx())) == kNoFlushModules.end()) { + context->FlushUICommand(context->window(), FlushUICommandReason::kDependentsAll); + } NativeValue* result; auto module_name_string = module_name.ToNativeString(context->ctx()); From 1598941bd63272172c032bc27386e17e44d67dca Mon Sep 17 00:00:00 2001 From: "Dalton.m" Date: Tue, 28 Apr 2026 10:28:57 +0800 Subject: [PATCH 3/3] test(bridge): add unit tests for FlushUICommand skip optimization in ModuleManager --- .../frame/module_manager_no_flush_test.cc | 284 ++++++++++++++++++ bridge/test/test.cmake | 1 + 2 files changed, 285 insertions(+) create mode 100644 bridge/core/frame/module_manager_no_flush_test.cc diff --git a/bridge/core/frame/module_manager_no_flush_test.cc b/bridge/core/frame/module_manager_no_flush_test.cc new file mode 100644 index 0000000000..2682d07662 --- /dev/null +++ b/bridge/core/frame/module_manager_no_flush_test.cc @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +// Tests for the FlushUICommand skip optimization in ModuleManager. +// +// The optimization skips FlushUICommand (a PostToDartSync round-trip) for +// modules that do not read DOM state: Fetch, AsyncStorage, LocalStorage, +// SessionStorage, Clipboard, TextCodec, Navigator. +// +// Two test suites: +// +// 1. ModuleManagerNoFlushBehavior — verifies that whitelisted modules can be +// invoked without errors and return values correctly (correctness). +// +// 2. ModuleManagerNoFlushCount — replaces the flushUICommand mock with a +// counting version to assert that FlushUICommand is skipped for whitelist +// modules and triggered for non-whitelist modules (optimization verified). + +#include +#include "webf_bridge.h" +#include "webf_test_env.h" + +namespace webf { + +// --------------------------------------------------------------------------- +// Suite 1: Correctness — whitelist modules work normally after the optimization +// --------------------------------------------------------------------------- + +TEST(ModuleManagerNoFlushBehavior, FetchModuleInvokesSuccessfully) { + static bool errorCalled = false; + auto env = TEST_init([](double, const char* errmsg) { + errorCalled = true; + WEBF_LOG(VERBOSE) << errmsg; + }); + webf::WebFPage::consoleMessageHandler = [](void*, const std::string&, int) {}; + + auto* context = env->page()->executingContext(); + // webf.invokeModule returns the module name string in the test mock. + std::string code = R"( + let result = webf.invokeModule('Fetch', 'test', null); + console.assert(result === 'Fetch', 'Fetch module should return its name'); + )"; + context->EvaluateJavaScript(code.c_str(), code.size(), "vm://", 0); + + EXPECT_EQ(errorCalled, false); +} + +TEST(ModuleManagerNoFlushBehavior, AsyncStorageModuleInvokesSuccessfully) { + static bool errorCalled = false; + auto env = TEST_init([](double, const char* errmsg) { + errorCalled = true; + WEBF_LOG(VERBOSE) << errmsg; + }); + webf::WebFPage::consoleMessageHandler = [](void*, const std::string&, int) {}; + + auto* context = env->page()->executingContext(); + std::string code = R"( + let result = webf.invokeModule('AsyncStorage', 'getItem', null); + console.assert(result === 'AsyncStorage', 'AsyncStorage module should return its name'); + )"; + context->EvaluateJavaScript(code.c_str(), code.size(), "vm://", 0); + + EXPECT_EQ(errorCalled, false); +} + +TEST(ModuleManagerNoFlushBehavior, LocalStorageModuleInvokesSuccessfully) { + static bool errorCalled = false; + auto env = TEST_init([](double, const char* errmsg) { + errorCalled = true; + WEBF_LOG(VERBOSE) << errmsg; + }); + webf::WebFPage::consoleMessageHandler = [](void*, const std::string&, int) {}; + + auto* context = env->page()->executingContext(); + std::string code = R"( + let result = webf.invokeModule('LocalStorage', 'getItem', null); + console.assert(result === 'LocalStorage', 'LocalStorage module should return its name'); + )"; + context->EvaluateJavaScript(code.c_str(), code.size(), "vm://", 0); + + EXPECT_EQ(errorCalled, false); +} + +TEST(ModuleManagerNoFlushBehavior, SessionStorageModuleInvokesSuccessfully) { + static bool errorCalled = false; + auto env = TEST_init([](double, const char* errmsg) { + errorCalled = true; + WEBF_LOG(VERBOSE) << errmsg; + }); + webf::WebFPage::consoleMessageHandler = [](void*, const std::string&, int) {}; + + auto* context = env->page()->executingContext(); + std::string code = R"( + let result = webf.invokeModule('SessionStorage', 'getItem', null); + console.assert(result === 'SessionStorage', 'SessionStorage module should return its name'); + )"; + context->EvaluateJavaScript(code.c_str(), code.size(), "vm://", 0); + + EXPECT_EQ(errorCalled, false); +} + +TEST(ModuleManagerNoFlushBehavior, ClipboardModuleInvokesSuccessfully) { + static bool errorCalled = false; + auto env = TEST_init([](double, const char* errmsg) { + errorCalled = true; + WEBF_LOG(VERBOSE) << errmsg; + }); + webf::WebFPage::consoleMessageHandler = [](void*, const std::string&, int) {}; + + auto* context = env->page()->executingContext(); + std::string code = R"( + let result = webf.invokeModule('Clipboard', 'readText', null); + console.assert(result === 'Clipboard', 'Clipboard module should return its name'); + )"; + context->EvaluateJavaScript(code.c_str(), code.size(), "vm://", 0); + + EXPECT_EQ(errorCalled, false); +} + +TEST(ModuleManagerNoFlushBehavior, TextCodecModuleInvokesSuccessfully) { + static bool errorCalled = false; + auto env = TEST_init([](double, const char* errmsg) { + errorCalled = true; + WEBF_LOG(VERBOSE) << errmsg; + }); + webf::WebFPage::consoleMessageHandler = [](void*, const std::string&, int) {}; + + auto* context = env->page()->executingContext(); + std::string code = R"( + let result = webf.invokeModule('TextCodec', 'encode', null); + console.assert(result === 'TextCodec', 'TextCodec module should return its name'); + )"; + context->EvaluateJavaScript(code.c_str(), code.size(), "vm://", 0); + + EXPECT_EQ(errorCalled, false); +} + +TEST(ModuleManagerNoFlushBehavior, NavigatorModuleInvokesSuccessfully) { + static bool errorCalled = false; + auto env = TEST_init([](double, const char* errmsg) { + errorCalled = true; + WEBF_LOG(VERBOSE) << errmsg; + }); + webf::WebFPage::consoleMessageHandler = [](void*, const std::string&, int) {}; + + auto* context = env->page()->executingContext(); + std::string code = R"( + let result = webf.invokeModule('Navigator', 'getUserAgent', null); + console.assert(result === 'Navigator', 'Navigator module should return its name'); + )"; + context->EvaluateJavaScript(code.c_str(), code.size(), "vm://", 0); + + EXPECT_EQ(errorCalled, false); +} + +// Verify that non-whitelist modules are also unaffected (regression guard). +TEST(ModuleManagerNoFlushBehavior, NonWhitelistModuleInvokesSuccessfully) { + static bool errorCalled = false; + auto env = TEST_init([](double, const char* errmsg) { + errorCalled = true; + WEBF_LOG(VERBOSE) << errmsg; + }); + webf::WebFPage::consoleMessageHandler = [](void*, const std::string&, int) {}; + + auto* context = env->page()->executingContext(); + std::string code = R"( + let result = webf.invokeModule('WebSocket', 'connect', null); + console.assert(result === 'WebSocket', 'WebSocket module should return its name'); + )"; + context->EvaluateJavaScript(code.c_str(), code.size(), "vm://", 0); + + EXPECT_EQ(errorCalled, false); +} + +// --------------------------------------------------------------------------- +// Suite 2: FlushUICommand call count verification +// +// We replace the flushUICommand slot in the dart methods array with a +// counting function, then verify the count after invoking each module. +// +// flushUICommand is at index 11 in TEST_getMockDartMethods (0-based). +// See webf_test_env.cc for the ordering. +// --------------------------------------------------------------------------- + +static int g_flush_call_count = 0; + +static void TEST_flushUICommand_counting(double /*contextId*/) { + g_flush_call_count++; +} + +// Build a test env with the counting flush function substituted in. +// We use the same initialization sequence as TEST_init but swap index 11. +static std::unique_ptr makeEnvWithCountingFlush(OnJSError onJsError = nullptr) { + // Index of flushUICommand in the dart methods vector produced by + // TEST_getMockDartMethods. Keep in sync with webf_test_env.cc. + constexpr size_t kFlushUICommandIndex = 11; + + auto methods = TEST_getMockDartMethods(onJsError); + methods[kFlushUICommandIndex] = reinterpret_cast(TEST_flushUICommand_counting); + + // Use a unique negative context id range to avoid collisions with TEST_init. + static double sPageContextId = -1000; + sPageContextId -= 1; + + auto* dart_isolate_context = + reinterpret_cast(initDartIsolateContextSync(0, methods.data(), methods.size())); + auto* page = reinterpret_cast( + allocateNewPageSync(sPageContextId, dart_isolate_context, nullptr, 0)); + + // initTestFramework registers the test polyfill (webf.invokeModule etc.). + void* testContext = initTestFramework(page); + TEST_mockTestEnvDartMethods(testContext, onJsError); + + JSThreadState* th = new JSThreadState(); + JS_SetRuntimeOpaque( + reinterpret_cast(testContext)->page()->executingContext()->dartIsolateContext()->runtime(), th); + + return std::make_unique(dart_isolate_context, page); +} + +TEST(ModuleManagerNoFlushCount, WhitelistModulesSkipFlush) { + const std::vector whitelist = { + "Fetch", "AsyncStorage", "LocalStorage", "SessionStorage", + "Clipboard", "TextCodec", "Navigator", + }; + + for (const auto& moduleName : whitelist) { + g_flush_call_count = 0; + auto env = makeEnvWithCountingFlush([](double, const char*) {}); + webf::WebFPage::consoleMessageHandler = [](void*, const std::string&, int) {}; + + auto* context = env->page()->executingContext(); + std::string code = "webf.invokeModule('" + moduleName + "', 'test', null);"; + context->EvaluateJavaScript(code.c_str(), code.size(), "vm://", 0); + + EXPECT_EQ(g_flush_call_count, 0) + << moduleName << " is whitelisted and should NOT trigger FlushUICommand"; + } +} + +TEST(ModuleManagerNoFlushCount, NonWhitelistModuleTriggersFlush) { + g_flush_call_count = 0; + auto env = makeEnvWithCountingFlush([](double, const char*) {}); + webf::WebFPage::consoleMessageHandler = [](void*, const std::string&, int) {}; + + auto* context = env->page()->executingContext(); + // "WebSocket" is not in the whitelist. + std::string code = "webf.invokeModule('WebSocket', 'connect', null);"; + context->EvaluateJavaScript(code.c_str(), code.size(), "vm://", 0); + + EXPECT_GT(g_flush_call_count, 0) + << "WebSocket is not whitelisted and MUST trigger FlushUICommand"; +} + +TEST(ModuleManagerNoFlushCount, CaseSensitivity_LowercaseFetchIsNotWhitelisted) { + g_flush_call_count = 0; + auto env = makeEnvWithCountingFlush([](double, const char*) {}); + webf::WebFPage::consoleMessageHandler = [](void*, const std::string&, int) {}; + + auto* context = env->page()->executingContext(); + // "fetch" (lowercase) must NOT be treated as whitelisted. + std::string code = "webf.invokeModule('fetch', 'test', null);"; + context->EvaluateJavaScript(code.c_str(), code.size(), "vm://", 0); + + EXPECT_GT(g_flush_call_count, 0) + << "Lowercase 'fetch' is not in the whitelist and must trigger FlushUICommand"; +} + +TEST(ModuleManagerNoFlushCount, MethodChannelTriggersFlush) { + g_flush_call_count = 0; + auto env = makeEnvWithCountingFlush([](double, const char*) {}); + webf::WebFPage::consoleMessageHandler = [](void*, const std::string&, int) {}; + + auto* context = env->page()->executingContext(); + std::string code = "webf.methodChannel.invokeMethod('test', 'fn', null);"; + context->EvaluateJavaScript(code.c_str(), code.size(), "vm://", 0); + + EXPECT_GT(g_flush_call_count, 0) + << "MethodChannel is not whitelisted and must trigger FlushUICommand"; +} + +} // namespace webf diff --git a/bridge/test/test.cmake b/bridge/test/test.cmake index a65eb25820..f3bc00e913 100644 --- a/bridge/test/test.cmake +++ b/bridge/test/test.cmake @@ -22,6 +22,7 @@ list(APPEND WEBF_UNIT_TEST_SOURCE ./core/executing_context_test.cc ./core/frame/console_test.cc ./core/frame/module_manager_test.cc + ./core/frame/module_manager_no_flush_test.cc ./core/dom/events/event_target_test.cc ./core/dom/document_test.cc ./core/dom/legacy/element_attribute_test.cc