Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 53 additions & 26 deletions shell/platform/fuchsia/flutter/focus_delegate.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,49 @@ void FocusDelegate::WatchLoop(std::function<void(bool)> callback) {

watch_loop_ = [this, callback = std::move(callback)](auto focus_state) {
callback(is_focused_ = focus_state.focused());
if (next_focus_request_) {
CompleteCurrentFocusState(std::exchange(next_focus_request_, nullptr));
}
Complete(std::exchange(next_focus_request_, nullptr),
is_focused_ ? "[true]" : "[false]");
view_ref_focused_->Watch(watch_loop_);
};
view_ref_focused_->Watch(watch_loop_);
}

bool FocusDelegate::CompleteCurrentFocusState(
bool FocusDelegate::HandlePlatformMessage(
rapidjson::Value request,
fml::RefPtr<flutter::PlatformMessageResponse> response) {
std::string result(is_focused_ ? "[true]" : "[false]");
response->Complete(std::make_unique<fml::DataMapping>(
std::vector<uint8_t>(result.begin(), result.end())));
return true;
}
auto method = request.FindMember("method");
if (method == request.MemberEnd() || !method->value.IsString()) {
return false;
}

bool FocusDelegate::CompleteNextFocusState(
fml::RefPtr<flutter::PlatformMessageResponse> response) {
if (next_focus_request_) {
FML_LOG(ERROR) << "An outstanding PlatformMessageResponse already exists "
"for the next focus state!";
if (method->value == "View.focus.getCurrent") {
Complete(std::move(response), is_focused_ ? "[true]" : "[false]");
} else if (method->value == "View.focus.getNext") {
if (next_focus_request_) {
FML_LOG(ERROR) << "An outstanding PlatformMessageResponse already exists "
"for the next focus state!";
Complete(std::move(response), "[null]");
} else {
next_focus_request_ = std::move(response);
}
} else if (method->value == "View.focus.request") {
return RequestFocus(std::move(request), std::move(response));
} else {
return false;
}
next_focus_request_ = std::move(response);
// All of our methods complete the platform message response.
return true;
}

void FocusDelegate::Complete(
fml::RefPtr<flutter::PlatformMessageResponse> response,
std::string value) {
if (response) {
response->Complete(std::make_unique<fml::DataMapping>(
std::vector<uint8_t>(value.begin(), value.end())));
}
}

bool FocusDelegate::RequestFocus(
rapidjson::Value request,
fml::RefPtr<flutter::PlatformMessageResponse> response) {
Expand All @@ -55,7 +71,7 @@ bool FocusDelegate::RequestFocus(

auto view_ref = args.FindMember("viewRef");
if (!view_ref->value.IsUint64()) {
FML_LOG(ERROR) << "Argument 'viewRef' is not a int64";
FML_LOG(ERROR) << "Argument 'viewRef' is not a uint64";
return false;
}

Expand All @@ -72,25 +88,36 @@ bool FocusDelegate::RequestFocus(
});
focuser_->RequestFocus(
std::move(ref),
[view_ref = view_ref->value.GetUint64(), response = std::move(response)](
[this, response = std::move(response)](
fuchsia::ui::views::Focuser_RequestFocus_Result result) {
if (!response) {
return;
}
int result_code =
result.is_err()
? static_cast<
std::underlying_type_t<fuchsia::ui::views::Error>>(
result.err())
: 0;

std::ostringstream out;
out << "[" << result_code << "]";
std::string output = out.str();
response->Complete(std::make_unique<fml::DataMapping>(
std::vector<uint8_t>(output.begin(), output.end())));
Complete(std::move(response), "[" + std::to_string(result_code) + "]");
});
return true;
}

bool FocusDelegate::CompleteCurrentFocusState(
fml::RefPtr<flutter::PlatformMessageResponse> response) {
std::string result(is_focused_ ? "[true]" : "[false]");
response->Complete(std::make_unique<fml::DataMapping>(
std::vector<uint8_t>(result.begin(), result.end())));
return true;
}

bool FocusDelegate::CompleteNextFocusState(
fml::RefPtr<flutter::PlatformMessageResponse> response) {
if (next_focus_request_) {
FML_LOG(ERROR) << "An outstanding PlatformMessageResponse already exists "
"for the next focus state!";
return false;
}
next_focus_request_ = std::move(response);
return true;
}

} // namespace flutter_runner
36 changes: 27 additions & 9 deletions shell/platform/fuchsia/flutter/focus_delegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,32 +21,50 @@ class FocusDelegate {
fidl::InterfaceHandle<fuchsia::ui::views::Focuser> focuser)
: view_ref_focused_(view_ref_focused.Bind()), focuser_(focuser.Bind()) {}

virtual ~FocusDelegate() = default;

/// Continuously watches the host viewRef for focus events, invoking a
/// callback each time.
///
/// This can only be called once.
virtual void WatchLoop(std::function<void(bool)> callback);
void WatchLoop(std::function<void(bool)> callback);

/// Handles the following focus-related platform message requests:
/// View.focus.getCurrent
/// - Completes with the FocusDelegate's most recent focus state, either
/// [true] or [false].
/// View.focus.getNext
/// - Completes with the FocusDelegate's next focus state, either [true] or
/// [false].
/// - Only one outstanding request may exist at a time. Any others will be
/// completed with [null].
/// View.focus.request
/// - Attempts to give focus for a given viewRef. Completes with [0] on
/// success, or [fuchsia::ui::views::Error] on failure.
///
/// Returns false if a malformed/invalid request needs to be completed empty.
bool HandlePlatformMessage(
rapidjson::Value request,
fml::RefPtr<flutter::PlatformMessageResponse> response);

/// Completes the platform message request with the FocusDelegate's most
/// recent focus state.
virtual bool CompleteCurrentFocusState(
// TODO(fxbug.dev/79740): Delete after soft transition.
bool CompleteCurrentFocusState(
fml::RefPtr<flutter::PlatformMessageResponse> response);

/// Completes the platform message request with the FocusDelegate's next focus
/// state.
///
/// Only one outstanding request may exist at a time. Any others will be
/// completed empty.
virtual bool CompleteNextFocusState(
// TODO(fxbug.dev/79740): Delete after soft transition.
bool CompleteNextFocusState(
fml::RefPtr<flutter::PlatformMessageResponse> response);

/// Completes a platform message request by attempting to give focus for a
/// given viewRef.
virtual bool RequestFocus(
rapidjson::Value request,
fml::RefPtr<flutter::PlatformMessageResponse> response);
// TODO(fxbug.dev/79740): Make private after soft transition.
bool RequestFocus(rapidjson::Value request,
fml::RefPtr<flutter::PlatformMessageResponse> response);

private:
fuchsia::ui::views::ViewRefFocusedPtr view_ref_focused_;
Expand All @@ -57,7 +75,7 @@ class FocusDelegate {
fml::RefPtr<flutter::PlatformMessageResponse> next_focus_request_;

void Complete(fml::RefPtr<flutter::PlatformMessageResponse> response,
bool value);
std::string value);

FML_DISALLOW_COPY_AND_ASSIGN(FocusDelegate);
};
Expand Down
129 changes: 127 additions & 2 deletions shell/platform/fuchsia/flutter/focus_delegate_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,129 @@ class FocusDelegateTest : public ::testing::Test {
// Tests that WatchLoop() should callback and complete PlatformMessageResponses
// correctly, given a series of vrf invocations.
TEST_F(FocusDelegateTest, WatchCallbackSeries) {
std::vector<bool> vrf_states{false, true, true, false,
true, false, true, true};
std::size_t vrf_index = 0;
std::size_t callback_index = 0;
focus_delegate->WatchLoop([&](bool focus_state) {
// Make sure the focus state that FocusDelegate gives us is consistent with
// what was fired from the vrf.
EXPECT_EQ(vrf_states[callback_index], focus_state);

// View.focus.getCurrent should complete with the current (up to date) focus
// state.
auto response = FakePlatformMessageResponse::Create();
EXPECT_TRUE(focus_delegate->HandlePlatformMessage(
ParsePlatformMessage("{\"method\":\"View.focus.getCurrent\"}"),
response));
response->ExpectCompleted(focus_state ? "[true]" : "[false]");

// Ensure this callback always happens in lockstep with
// vrf->ScheduleCallback.
EXPECT_EQ(vrf_index, callback_index++);
});

// Subsequent WatchLoop calls should not be respected.
focus_delegate->WatchLoop([](bool _) {
ADD_FAILURE() << "Subsequent WatchLoops should not be respected!";
});

do {
// Ensure the next focus state is handled correctly.
auto response1 = FakePlatformMessageResponse::Create();
EXPECT_TRUE(focus_delegate->HandlePlatformMessage(
ParsePlatformMessage("{\"method\":\"View.focus.getNext\"}"),
response1));

// Since there's already an outstanding PlatformMessageResponse, this one
// should be completed null.
auto response2 = FakePlatformMessageResponse::Create();
EXPECT_TRUE(focus_delegate->HandlePlatformMessage(
ParsePlatformMessage("{\"method\":\"View.focus.getNext\"}"),
response2));
response2->ExpectCompleted("[null]");

// Post watch events and trigger the next vrf event.
RunLoopUntilIdle();
vrf->ScheduleCallback(vrf_states[vrf_index]);
RunLoopUntilIdle();

// Next focus state should be completed by now.
response1->ExpectCompleted(vrf_states[vrf_index] ? "[true]" : "[false]");

// Check View.focus.getCurrent again, and increment vrf_index since we move
// on to the next focus state.
auto response3 = FakePlatformMessageResponse::Create();
EXPECT_TRUE(focus_delegate->HandlePlatformMessage(
ParsePlatformMessage("{\"method\":\"View.focus.getCurrent\"}"),
response3));
response3->ExpectCompleted(vrf_states[vrf_index++] ? "[true]" : "[false]");

// vrf->times_watched should always be 1 more than the amount of vrf events
// emitted.
EXPECT_EQ(vrf_index + 1, vrf->times_watched);
} while (vrf_index < vrf_states.size());
}

// Tests that HandlePlatformMessage() completes a "View.focus.request" response
// with a non-error status code.
TEST_F(FocusDelegateTest, RequestFocusTest) {
// This "Mock" ViewRef serves as the target for the RequestFocus operation.
auto mock_view_ref_pair = scenic::ViewRefPair::New();
// Create the platform message request.
std::ostringstream message;
message << "{"
<< " \"method\":\"View.focus.request\","
<< " \"args\": {"
<< " \"viewRef\":"
<< mock_view_ref_pair.view_ref.reference.get() << " }"
<< "}";

// Dispatch the plaform message request with an expected completion response.
auto response = FakePlatformMessageResponse::Create();
EXPECT_TRUE(focus_delegate->HandlePlatformMessage(
ParsePlatformMessage(message.str()), response));
RunLoopUntilIdle();

response->ExpectCompleted("[0]");
EXPECT_TRUE(focuser->request_focus_called());
}

// Tests that HandlePlatformMessage() completes a "View.focus.request" response
// with a Error::DENIED status code.
TEST_F(FocusDelegateTest, RequestFocusFailTest) {
// This "Mock" ViewRef serves as the target for the RequestFocus operation.
auto mock_view_ref_pair = scenic::ViewRefPair::New();
// We're testing the focus failure case.
focuser->fail_request_focus();
// Create the platform message request.
std::ostringstream message;
message << "{"
<< " \"method\":\"View.focus.request\","
<< " \"args\": {"
<< " \"viewRef\":"
<< mock_view_ref_pair.view_ref.reference.get() << " }"
<< "}";

// Dispatch the plaform message request with an expected completion response.
auto response = FakePlatformMessageResponse::Create();
EXPECT_TRUE(focus_delegate->HandlePlatformMessage(
ParsePlatformMessage(message.str()), response));
RunLoopUntilIdle();

response->ExpectCompleted(
"[" +
std::to_string(
static_cast<std::underlying_type_t<fuchsia::ui::views::Error>>(
fuchsia::ui::views::Error::DENIED)) +
"]");
EXPECT_TRUE(focuser->request_focus_called());
}

// Tests that WatchLoop() should callback and complete PlatformMessageResponses
// correctly, given a series of vrf invocations.
// TODO(fxbug.dev/79740): Delete after soft transition.
TEST_F(FocusDelegateTest, DeprecatedWatchCallbackSeries) {
std::vector<bool> vrf_states{false, true, true, false,
true, false, true, true};
std::size_t vrf_index = 0;
Expand Down Expand Up @@ -121,7 +244,8 @@ TEST_F(FocusDelegateTest, WatchCallbackSeries) {

// Tests that RequestFocus() completes the platform message's response with a
// non-error status code.
TEST_F(FocusDelegateTest, RequestFocusTest) {
// TODO(fxbug.dev/79740): Delete after soft transition.
TEST_F(FocusDelegateTest, DeprecatedRequestFocusTest) {
// This "Mock" ViewRef serves as the target for the RequestFocus operation.
auto mock_view_ref_pair = scenic::ViewRefPair::New();
// Create the platform message request.
Expand All @@ -144,7 +268,8 @@ TEST_F(FocusDelegateTest, RequestFocusTest) {

// Tests that RequestFocus() completes the platform message's response with a
// Error::DENIED status code.
TEST_F(FocusDelegateTest, RequestFocusFailTest) {
// TODO(fxbug.dev/79740): Delete after soft transition.
TEST_F(FocusDelegateTest, DeprecatedRequestFocusFailTest) {
// This "Mock" ViewRef serves as the target for the RequestFocus operation.
auto mock_view_ref_pair = scenic::ViewRefPair::New();
// We're testing the focus failure case.
Expand Down
Loading