路由被 TextInputHost、任务栏或后挂起的线程占着时,前台应用切走输入法发来的 终止停用会因为不是路由所有者而被静默丢弃,g_is_ime_active 保持为 true, 悬浮工具栏不隐藏。现在来自前台窗口(或其焦点窗口)所在线程的终止停用会退役 残留路由并推进 epoch,照常投递 ClientDeactivated;后台线程仍被忽略。
158 lines
5.7 KiB
C++
158 lines
5.7 KiB
C++
#include "ipc/active_client_state.h"
|
|
#include "ipc/focus_session_policy.h"
|
|
#include "tests/includes/test_framework.h"
|
|
|
|
TEST_CASE(active_client_stale_deactivation_cannot_clear_new_active_client)
|
|
{
|
|
FanyImeIpc::ActiveClientState state;
|
|
|
|
const auto first_a = state.activate(1001);
|
|
REQUIRE(first_a.changed);
|
|
const auto b = state.activate(2002);
|
|
REQUIRE(b.changed);
|
|
const auto second_a = state.activate(1001);
|
|
REQUIRE(second_a.changed);
|
|
|
|
REQUIRE_EQ(state.deactivate(2002), 0u);
|
|
REQUIRE(state.matches(1001, second_a.epoch));
|
|
}
|
|
|
|
TEST_CASE(active_client_implicit_input_activation_is_idempotent)
|
|
{
|
|
FanyImeIpc::ActiveClientState state;
|
|
|
|
const auto first = state.activate(1001);
|
|
REQUIRE(first.changed);
|
|
const auto repeated = state.activate(1001);
|
|
REQUIRE(!repeated.changed);
|
|
REQUIRE_EQ(repeated.epoch, first.epoch);
|
|
|
|
state.activate(2002);
|
|
const auto recovered = state.activate(1001);
|
|
REQUIRE(recovered.changed);
|
|
REQUIRE(state.matches(1001, recovered.epoch));
|
|
}
|
|
|
|
TEST_CASE(active_client_old_epoch_tasks_are_rejected)
|
|
{
|
|
FanyImeIpc::ActiveClientState state;
|
|
|
|
const auto old_a = state.activate(1001);
|
|
state.activate(2002);
|
|
const auto current_a = state.activate(1001);
|
|
|
|
REQUIRE(!state.matches(1001, old_a.epoch));
|
|
REQUIRE(state.matches(1001, current_a.epoch));
|
|
}
|
|
|
|
TEST_CASE(active_client_same_client_reconnect_advances_epoch)
|
|
{
|
|
FanyImeIpc::ActiveClientState state;
|
|
|
|
const auto old_session = state.activate(1001);
|
|
REQUIRE(state.deactivate(1001) != 0);
|
|
const auto new_session = state.activate(1001);
|
|
|
|
REQUIRE(new_session.changed);
|
|
REQUIRE(old_session.epoch != new_session.epoch);
|
|
REQUIRE(!state.matches(1001, old_session.epoch));
|
|
REQUIRE(state.matches(1001, new_session.epoch));
|
|
}
|
|
|
|
TEST_CASE(active_client_transport_failure_invalidates_only_exact_epoch)
|
|
{
|
|
FanyImeIpc::ActiveClientState state;
|
|
|
|
const auto old_session = state.activate(1001);
|
|
REQUIRE(state.invalidate(1001, old_session.epoch) != 0);
|
|
REQUIRE(!state.is_current(1001));
|
|
|
|
const auto new_session = state.activate(1001);
|
|
REQUIRE_EQ(state.invalidate(1001, old_session.epoch), 0u);
|
|
REQUIRE(state.matches(1001, new_session.epoch));
|
|
}
|
|
|
|
TEST_CASE(active_client_new_focus_session_renews_same_client_epoch)
|
|
{
|
|
FanyImeIpc::ActiveClientState state;
|
|
|
|
const auto first_focus = state.activate(1001);
|
|
const auto second_focus = state.renew(1001);
|
|
|
|
REQUIRE(second_focus.changed);
|
|
REQUIRE(first_focus.epoch != second_focus.epoch);
|
|
REQUIRE(!state.matches(1001, first_focus.epoch));
|
|
REQUIRE(state.matches(1001, second_focus.epoch));
|
|
}
|
|
|
|
TEST_CASE(active_client_terminal_deactivation_reuses_only_current_inactive_epoch)
|
|
{
|
|
FanyImeIpc::ActiveClientState state;
|
|
|
|
state.activate(1001);
|
|
const uint64_t suspended_epoch = state.deactivate(1001);
|
|
REQUIRE(suspended_epoch != 0);
|
|
REQUIRE_EQ(state.terminal_deactivation_epoch(1001, suspended_epoch), suspended_epoch);
|
|
|
|
// A later real deactivation observes an already-suspended route. It must
|
|
// still carry the exact inactive epoch to the UI worker.
|
|
REQUIRE_EQ(state.deactivate(1001), 0u);
|
|
REQUIRE_EQ(state.terminal_deactivation_epoch(1001), suspended_epoch);
|
|
REQUIRE(state.matches(0, suspended_epoch));
|
|
|
|
// Once another activation wins, the old terminal event cannot hide it.
|
|
const auto active = state.activate(2002);
|
|
REQUIRE_EQ(state.terminal_deactivation_epoch(1001), 0u);
|
|
REQUIRE(!state.matches(0, suspended_epoch));
|
|
REQUIRE(state.matches(2002, active.epoch));
|
|
|
|
// Inactive state is not sufficient ownership: after B suspends, a delayed
|
|
// terminal event or disconnect from A must not reuse B's zero-state epoch.
|
|
const uint64_t second_suspended_epoch = state.deactivate(2002);
|
|
REQUIRE(second_suspended_epoch != 0);
|
|
REQUIRE_EQ(state.terminal_deactivation_epoch(1001), 0u);
|
|
REQUIRE_EQ(state.terminal_deactivation_epoch(2002), second_suspended_epoch);
|
|
}
|
|
|
|
TEST_CASE(active_client_foreground_terminal_deactivation_retires_foreign_route)
|
|
{
|
|
FanyImeIpc::ActiveClientState state;
|
|
|
|
// The foreground app suspended, then another client (e.g. TextInputHost)
|
|
// took routing and never gave it back. The app's terminal event is not
|
|
// routed to it, so ownership alone rejects it.
|
|
state.activate(1001);
|
|
state.deactivate(1001);
|
|
const auto foreign = state.activate(3003);
|
|
REQUIRE_EQ(state.deactivate(1001), 0u);
|
|
REQUIRE_EQ(state.terminal_deactivation_epoch(1001), 0u);
|
|
|
|
// Once the caller proved 1001 owns the foreground thread, the stale route
|
|
// is retired and the UI worker's inactive check accepts the new epoch.
|
|
const uint64_t retired_epoch = state.retire_for_terminal_deactivation(1001);
|
|
REQUIRE(retired_epoch != 0);
|
|
REQUIRE(retired_epoch != foreign.epoch);
|
|
REQUIRE(!state.matches(3003, foreign.epoch));
|
|
REQUIRE(state.matches(0, retired_epoch));
|
|
REQUIRE_EQ(state.snapshot().client_id, 0u);
|
|
|
|
// The duplicate event from Deactivate() reuses the same epoch instead of
|
|
// being dropped or advancing it again.
|
|
REQUIRE_EQ(state.terminal_deactivation_epoch(1001), retired_epoch);
|
|
REQUIRE_EQ(state.terminal_deactivation_epoch(3003), 0u);
|
|
|
|
// A later activation supersedes the retirement.
|
|
const auto next = state.activate(3003);
|
|
REQUIRE(!state.matches(0, retired_epoch));
|
|
REQUIRE(state.matches(3003, next.epoch));
|
|
REQUIRE_EQ(state.retire_for_terminal_deactivation(0), 0u);
|
|
}
|
|
|
|
TEST_CASE(focus_session_ready_requires_nonzero_client_epoch_and_token)
|
|
{
|
|
REQUIRE(FanyImeIpc::CanSendFocusSessionReady(1001, 22, 77));
|
|
REQUIRE(!FanyImeIpc::CanSendFocusSessionReady(0, 22, 77));
|
|
REQUIRE(!FanyImeIpc::CanSendFocusSessionReady(1001, 0, 77));
|
|
REQUIRE(!FanyImeIpc::CanSendFocusSessionReady(1001, 22, 0));
|
|
}
|