Skip to content

Commit

Permalink
[refactor] Migrate to View.focus.*. (flutter#27005)
Browse files Browse the repository at this point in the history
  • Loading branch information
chandarrengoog authored Jun 29, 2021
1 parent b035192 commit 9e69740
Show file tree
Hide file tree
Showing 5 changed files with 385 additions and 51 deletions.
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

0 comments on commit 9e69740

Please sign in to comment.