未验证 提交 9d4d73e5 编写于 作者: D Darren Chan 提交者: GitHub

Emit viewRefFocused events in the flutter runner. (#26791)

Fulfill HostView.getCurrentFocusState and HostView.getNextFocusState platform message requests to deliver focus-related events to dart code.

Consolidate focus functionality (including requestFocus handling) to a new "FocusDelegate" class.

See https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/fidl/fuchsia.ui.views/view_ref_focused.fidl for details about focus state transitions and their meanings.

See https://fxbug.dev/77481.
上级 90105f3f
......@@ -1349,6 +1349,9 @@ FILE: ../../../flutter/shell/platform/fuchsia/flutter/engine.h
FILE: ../../../flutter/shell/platform/fuchsia/flutter/flutter_runner_fakes.h
FILE: ../../../flutter/shell/platform/fuchsia/flutter/flutter_runner_product_configuration.cc
FILE: ../../../flutter/shell/platform/fuchsia/flutter/flutter_runner_product_configuration.h
FILE: ../../../flutter/shell/platform/fuchsia/flutter/focus_delegate.cc
FILE: ../../../flutter/shell/platform/fuchsia/flutter/focus_delegate.h
FILE: ../../../flutter/shell/platform/fuchsia/flutter/focus_delegate_unittests.cc
FILE: ../../../flutter/shell/platform/fuchsia/flutter/fuchsia_external_view_embedder.cc
FILE: ../../../flutter/shell/platform/fuchsia/flutter/fuchsia_external_view_embedder.h
FILE: ../../../flutter/shell/platform/fuchsia/flutter/fuchsia_intl.cc
......
......@@ -59,6 +59,8 @@ template("runner_sources") {
"engine.h",
"flutter_runner_product_configuration.cc",
"flutter_runner_product_configuration.h",
"focus_delegate.cc",
"focus_delegate.h",
"fuchsia_external_view_embedder.cc",
"fuchsia_external_view_embedder.h",
"fuchsia_intl.cc",
......@@ -447,6 +449,7 @@ executable("flutter_runner_unittests") {
"accessibility_bridge_unittest.cc",
"component_unittest.cc",
"flutter_runner_fakes.h",
"focus_delegate_unittests.cc",
"fuchsia_intl_unittest.cc",
"keyboard_unittest.cc",
"platform_view_unittest.cc",
......@@ -471,6 +474,7 @@ executable("flutter_runner_unittests") {
]
deps = [
"tests/fakes",
":flutter_runner_fixtures",
":flutter_runner_sources",
"//build/fuchsia/pkg:sys_cpp_testing",
......
......@@ -21,6 +21,7 @@
#include "third_party/skia/include/ports/SkFontMgr_fuchsia.h"
#include "../runtime/dart/utils/files.h"
#include "focus_delegate.h"
#include "fuchsia_intl.h"
#include "platform_view.h"
#include "surface.h"
......@@ -99,6 +100,8 @@ Engine::Engine(Delegate& delegate,
endpoints.set_session_listener(session_listener.Bind());
fidl::InterfaceHandle<fuchsia::ui::views::Focuser> focuser;
endpoints.set_view_focuser(focuser.NewRequest());
fidl::InterfaceHandle<fuchsia::ui::views::ViewRefFocused> view_ref_focused;
endpoints.set_view_ref_focused(view_ref_focused.NewRequest());
scenic->CreateSessionT(std::move(endpoints), [] {});
// Make clones of the `ViewRef` before sending it down to Scenic, since the
......@@ -275,6 +278,7 @@ Engine::Engine(Delegate& delegate,
std::move(parent_environment_service_provider),
session_listener_request = std::move(session_listener_request),
focuser = std::move(focuser),
view_ref_focused = std::move(view_ref_focused),
on_session_listener_error_callback =
std::move(on_session_listener_error_callback),
on_enable_wireframe_callback =
......@@ -339,7 +343,7 @@ Engine::Engine(Delegate& delegate,
std::move(runner_services),
std::move(parent_environment_service_provider), // services
std::move(session_listener_request), // session listener
std::move(focuser),
std::move(view_ref_focused), std::move(focuser),
// Server-side part of the fuchsia.ui.input3.KeyboardListener
// connection.
std::move(keyboard_listener_request),
......
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <ostream>
#include "focus_delegate.h"
namespace flutter_runner {
void FocusDelegate::WatchLoop(std::function<void(bool)> callback) {
if (watch_loop_) {
FML_LOG(ERROR) << "FocusDelegate::WatchLoop() cannot be called twice.";
return;
}
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));
}
view_ref_focused_->Watch(watch_loop_);
};
view_ref_focused_->Watch(watch_loop_);
}
void 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())));
}
void FocusDelegate::CompleteNextFocusState(
fml::RefPtr<flutter::PlatformMessageResponse> response) {
if (next_focus_request_) {
FML_LOG(ERROR) << "An outstanding PlatformMessageResponse already exists "
"for the next focus state!";
response->CompleteEmpty();
} else {
next_focus_request_ = std::move(response);
}
}
void FocusDelegate::RequestFocus(
rapidjson::Value request,
fml::RefPtr<flutter::PlatformMessageResponse> response) {
auto args_it = request.FindMember("args");
if (args_it == request.MemberEnd() || !args_it->value.IsObject()) {
FML_LOG(ERROR) << "No arguments found.";
return;
}
const auto& args = args_it->value;
auto view_ref = args.FindMember("viewRef");
if (!view_ref->value.IsUint64()) {
FML_LOG(ERROR) << "Argument 'viewRef' is not a int64";
return;
}
zx_handle_t handle = view_ref->value.GetUint64();
zx_handle_t out_handle;
zx_status_t status =
zx_handle_duplicate(handle, ZX_RIGHT_SAME_RIGHTS, &out_handle);
if (status != ZX_OK) {
FML_LOG(ERROR) << "Argument 'viewRef' is not valid";
return;
}
auto ref = fuchsia::ui::views::ViewRef({
.reference = zx::eventpair(out_handle),
});
focuser_->RequestFocus(
std::move(ref),
[view_ref = view_ref->value.GetUint64(), response = std::move(response)](
fuchsia::ui::views::Focuser_RequestFocus_Result result) {
if (!response.get()) {
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())));
});
}
} // namespace flutter_runner
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_FOCUS_DELEGATE_H_
#define FLUTTER_SHELL_PLATFORM_FUCHSIA_FOCUS_DELEGATE_H_
#include <fuchsia/sys/cpp/fidl.h>
#include <fuchsia/ui/views/cpp/fidl.h>
#include "flutter/fml/macros.h"
#include "flutter/lib/ui/window/platform_message.h"
#include "third_party/rapidjson/include/rapidjson/document.h"
namespace flutter_runner {
class FocusDelegate {
public:
FocusDelegate(fidl::InterfaceHandle<fuchsia::ui::views::ViewRefFocused>
view_ref_focused,
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);
/// Completes the platform message request with the FocusDelegate's most
/// recent focus state.
virtual void 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 void CompleteNextFocusState(
fml::RefPtr<flutter::PlatformMessageResponse> response);
/// Completes a platform message request by attempting to give focus for a
/// given viewRef.
virtual void RequestFocus(
rapidjson::Value request,
fml::RefPtr<flutter::PlatformMessageResponse> response);
private:
fuchsia::ui::views::ViewRefFocusedPtr view_ref_focused_;
fuchsia::ui::views::FocuserPtr focuser_;
std::function<void(fuchsia::ui::views::FocusState)> watch_loop_;
bool is_focused_;
fml::RefPtr<flutter::PlatformMessageResponse> next_focus_request_;
void Complete(fml::RefPtr<flutter::PlatformMessageResponse> response,
bool value);
FML_DISALLOW_COPY_AND_ASSIGN(FocusDelegate);
};
} // namespace flutter_runner
#endif // FLUTTER_SHELL_PLATFORM_FUCHSIA_FOCUS_DELEGATE_H_
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fuchsia/ui/views/cpp/fidl.h>
#include <gtest/gtest.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/fidl/cpp/binding_set.h>
#include <lib/ui/scenic/cpp/view_ref_pair.h>
#include "focus_delegate.h"
#include "tests/fakes/focuser.h"
#include "tests/fakes/platform_message.h"
#include "tests/fakes/view_ref_focused.h"
#include "third_party/rapidjson/include/rapidjson/document.h"
rapidjson::Value ParsePlatformMessage(std::string json) {
rapidjson::Document document;
document.Parse(json);
if (document.HasParseError() || !document.IsObject()) {
FML_LOG(ERROR) << "Could not parse document";
return rapidjson::Value();
}
return document.GetObject();
}
namespace flutter_runner::testing {
class FocusDelegateTest : public ::testing::Test {
public:
std::unique_ptr<FakeViewRefFocused> vrf;
std::unique_ptr<FakeFocuser> focuser;
std::unique_ptr<FocusDelegate> focus_delegate;
void RunLoopUntilIdle() { loop_.RunUntilIdle(); }
protected:
FocusDelegateTest() : loop_(&kAsyncLoopConfigAttachToCurrentThread) {}
void SetUp() override {
vrf = std::make_unique<FakeViewRefFocused>();
focuser = std::make_unique<FakeFocuser>();
focus_delegate = std::make_unique<FocusDelegate>(
vrf_bindings.AddBinding(vrf.get()),
focuser_bindings.AddBinding(focuser.get()));
}
void TearDown() override {
vrf_bindings.CloseAll();
focuser_bindings.CloseAll();
loop_.Quit();
loop_.ResetQuit();
}
private:
async::Loop loop_;
fidl::BindingSet<ViewRefFocused> vrf_bindings;
fidl::BindingSet<Focuser> focuser_bindings;
FML_DISALLOW_COPY_AND_ASSIGN(FocusDelegateTest);
};
// 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);
// CompleteCurrentFocusState should complete with the current (up to date)
// focus state.
auto response = FakePlatformMessageResponse::Create();
focus_delegate->CompleteCurrentFocusState(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();
focus_delegate->CompleteNextFocusState(response1);
// Since there's already an outstanding PlatformMessageResponse, this one
// should be completed empty.
auto response = FakePlatformMessageResponse::Create();
focus_delegate->CompleteNextFocusState(response);
response->ExpectCompleted("");
// 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 CompleteCurrentFocusState again, and increment vrf_index, since we
// move on to the next focus state.
response = FakePlatformMessageResponse::Create();
focus_delegate->CompleteCurrentFocusState(response);
response->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 RequestFocus() completes the platform message's 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.requestFocus\","
<< " \"args\": {"
<< " \"viewRef\":"
<< mock_view_ref_pair.view_ref.reference.get() << " }"
<< "}";
// Dispatch the plaform message request with an expected completion response.
auto response = FakePlatformMessageResponse::Create();
focus_delegate->RequestFocus(ParsePlatformMessage(message.str()), response);
RunLoopUntilIdle();
response->ExpectCompleted("[0]");
EXPECT_TRUE(focuser->request_focus_called());
}
// Tests that RequestFocus() completes the platform message's 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.requestFocus\","
<< " \"args\": {"
<< " \"viewRef\":"
<< mock_view_ref_pair.view_ref.reference.get() << " }"
<< "}";
// Dispatch the plaform message request with an expected completion response.
auto response = FakePlatformMessageResponse::Create();
focus_delegate->RequestFocus(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());
}
} // namespace flutter_runner::testing
......@@ -64,6 +64,7 @@ PlatformView::PlatformView(
parent_environment_service_provider_handle,
fidl::InterfaceRequest<fuchsia::ui::scenic::SessionListener>
session_listener_request,
fidl::InterfaceHandle<fuchsia::ui::views::ViewRefFocused> vrf,
fidl::InterfaceHandle<fuchsia::ui::views::Focuser> focuser,
fidl::InterfaceRequest<fuchsia::ui::input3::KeyboardListener>
keyboard_listener_request,
......@@ -83,7 +84,8 @@ PlatformView::PlatformView(
: flutter::PlatformView(delegate, std::move(task_runners)),
debug_label_(std::move(debug_label)),
view_ref_(std::move(view_ref)),
focuser_(focuser.Bind()),
focus_delegate_(
std::make_shared<FocusDelegate>(std::move(vrf), std::move(focuser))),
session_listener_binding_(this, std::move(session_listener_request)),
session_listener_error_callback_(
std::move(session_listener_error_callback)),
......@@ -119,6 +121,16 @@ PlatformView::PlatformView(
fuchsia::ui::input::ImeService::Name_,
text_sync_service_.NewRequest().TakeChannel());
focus_delegate_->WatchLoop([&](bool focused) {
// Ensure last_text_state_ is set to make sure Flutter actually wants
// an IME.
if (focused && last_text_state_ != nullptr) {
ActivateIme();
} else if (!focused) {
DeactivateIme();
}
});
// Finally! Register the native platform message handlers.
RegisterPlatformMessageHandlers();
}
......@@ -341,10 +353,8 @@ void PlatformView::OnScenicEvent(
break;
case fuchsia::ui::scenic::Event::Tag::kInput:
switch (event.input().Which()) {
case fuchsia::ui::input::InputEvent::Tag::kFocus: {
OnHandleFocusEvent(event.input().focus());
case fuchsia::ui::input::InputEvent::Tag::kFocus:
break;
}
case fuchsia::ui::input::InputEvent::Tag::kPointer: {
OnHandlePointerEvent(event.input().pointer());
break;
......@@ -662,19 +672,6 @@ void PlatformView::OnKeyEvent(
callback(fuchsia::ui::input3::KeyEventStatus::HANDLED);
}
bool PlatformView::OnHandleFocusEvent(
const fuchsia::ui::input::FocusEvent& focus) {
// Ensure last_text_state_ is set to make sure Flutter actually wants an IME.
if (focus.focused && last_text_state_ != nullptr) {
ActivateIme();
return true;
} else if (!focus.focused) {
DeactivateIme();
return true;
}
return false;
}
void PlatformView::ActivateIme() {
DEBUG_CHECK(last_text_state_ != nullptr, LOG_TAG, "");
......@@ -1089,50 +1086,12 @@ void PlatformView::HandleFlutterPlatformViewsChannelPlatformMessage(
});
};
on_destroy_view_callback_(view_id_raw, std::move(on_view_unbound));
} else if (method->value == "HostView.getCurrentFocusState") {
focus_delegate_->CompleteCurrentFocusState(message->response());
} else if (method->value == "HostView.getNextFocusState") {
focus_delegate_->CompleteNextFocusState(message->response());
} else if (method->value == "View.requestFocus") {
auto args_it = root.FindMember("args");
if (args_it == root.MemberEnd() || !args_it->value.IsObject()) {
FML_LOG(ERROR) << "No arguments found.";
return;
}
const auto& args = args_it->value;
auto view_ref = args.FindMember("viewRef");
if (!view_ref->value.IsUint64()) {
FML_LOG(ERROR) << "Argument 'viewRef' is not a int64";
return;
}
zx_handle_t handle = view_ref->value.GetUint64();
zx_handle_t out_handle;
zx_status_t status =
zx_handle_duplicate(handle, ZX_RIGHT_SAME_RIGHTS, &out_handle);
if (status != ZX_OK) {
FML_LOG(ERROR) << "Argument 'viewRef' is not valid";
return;
}
auto ref = fuchsia::ui::views::ViewRef({
.reference = zx::eventpair(out_handle),
});
focuser_->RequestFocus(
std::move(ref),
[view_ref = view_ref->value.GetUint64(), message = std::move(message)](
fuchsia::ui::views::Focuser_RequestFocus_Result result) {
if (message->response().get()) {
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 << "]";
message->response()->Complete(
std::make_unique<fml::NonOwnedMapping>(
(const uint8_t*)out.str().c_str(), out.str().length()));
}
});
focus_delegate_->RequestFocus(root, message->response());
} else {
FML_DLOG(ERROR) << "Unknown " << message->channel() << " method "
<< method->value.GetString();
......
......@@ -26,6 +26,7 @@
#include "flutter/shell/platform/fuchsia/flutter/fuchsia_external_view_embedder.h"
#include "flutter/shell/platform/fuchsia/flutter/keyboard.h"
#include "flutter/shell/platform/fuchsia/flutter/vsync_waiter.h"
#include "focus_delegate.h"
namespace flutter_runner {
......@@ -72,6 +73,7 @@ class PlatformView final : public flutter::PlatformView,
parent_environment_service_provider,
fidl::InterfaceRequest<fuchsia::ui::scenic::SessionListener>
session_listener_request,
fidl::InterfaceHandle<fuchsia::ui::views::ViewRefFocused> vrf,
fidl::InterfaceHandle<fuchsia::ui::views::Focuser> focuser,
fidl::InterfaceRequest<fuchsia::ui::input3::KeyboardListener>
keyboard_listener,
......@@ -183,7 +185,7 @@ class PlatformView final : public flutter::PlatformView,
// TODO(MI4-2490): remove once ViewRefControl is passed to Scenic and kept
// alive there
const fuchsia::ui::views::ViewRef view_ref_;
fuchsia::ui::views::FocuserPtr focuser_;
std::shared_ptr<FocusDelegate> focus_delegate_;
// Logical size and logical->physical ratio. These are optional to provide
// an "unset" state during program startup, before Scenic has sent any
......
......@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/shell/platform/fuchsia/flutter/platform_view.h"
#include <fuchsia/ui/gfx/cpp/fidl.h>
#include <fuchsia/ui/scenic/cpp/fidl.h>
#include <fuchsia/ui/views/cpp/fidl.h>
......@@ -22,11 +20,14 @@
#include "flutter/flow/embedded_views.h"
#include "flutter/lib/ui/window/platform_message.h"
#include "flutter/lib/ui/window/viewport_metrics.h"
#include "flutter/shell/platform/fuchsia/flutter/platform_view.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "surface.h"
#include "task_runner_adapter.h"
#include "tests/fakes/focuser.h"
#include "tests/fakes/platform_message.h"
#include "tests/fakes/view_ref_focused.h"
namespace flutter_runner::testing {
namespace {
......@@ -150,30 +151,6 @@ class MockPlatformViewDelegate : public flutter::PlatformView::Delegate {
bool semantics_enabled_ = false;
};
class MockFocuser : public fuchsia::ui::views::Focuser {
public:
MockFocuser(bool fail_request_focus = false)
: fail_request_focus_(fail_request_focus) {}
bool request_focus_called() const { return request_focus_called_; }
private:
void RequestFocus(fuchsia::ui::views::ViewRef view_ref,
RequestFocusCallback callback) override {
request_focus_called_ = true;
auto result =
fail_request_focus_
? fuchsia::ui::views::Focuser_RequestFocus_Result::WithErr(
fuchsia::ui::views::Error::DENIED)
: fuchsia::ui::views::Focuser_RequestFocus_Result::WithResponse(
fuchsia::ui::views::Focuser_RequestFocus_Response());
callback(std::move(result));
}
bool request_focus_called_ = false;
bool fail_request_focus_ = false;
};
class MockResponse : public flutter::PlatformMessageResponse {
public:
MOCK_METHOD1(Complete, void(std::unique_ptr<fml::Mapping> data));
......@@ -204,6 +181,12 @@ class PlatformViewBuilder {
return *this;
}
PlatformViewBuilder& SetViewRefFocused(
fidl::InterfaceHandle<fuchsia::ui::views::ViewRefFocused> vrf) {
vrf_ = std::move(vrf);
return *this;
}
PlatformViewBuilder& SetFocuser(
fidl::InterfaceHandle<fuchsia::ui::views::Focuser> focuser) {
focuser_ = std::move(focuser);
......@@ -260,14 +243,13 @@ class PlatformViewBuilder {
// Once Build is called, the instance is no longer usable.
PlatformView Build() {
EXPECT_EQ(false, built_)
EXPECT_FALSE(std::exchange(built_, true))
<< "Build() was already called, this buider is good for one use only.";
built_ = true;
return PlatformView(
delegate_, debug_label_, std::move(view_ref_), task_runners_,
runner_services_, std::move(parent_environment_service_provider_),
std::move(session_listener_request_), std::move(focuser_),
std::move(keyboard_listener_),
std::move(session_listener_request_), std::move(vrf_),
std::move(focuser_), std::move(keyboard_listener_),
std::move(on_session_listener_error_callback_),
std::move(wireframe_enabled_callback_),
std::move(on_create_view_callback_),
......@@ -297,6 +279,7 @@ class PlatformViewBuilder {
// Optional elements.
fidl::InterfaceRequest<fuchsia::ui::scenic::SessionListener>
session_listener_request_{nullptr};
fidl::InterfaceHandle<fuchsia::ui::views::ViewRefFocused> vrf_{nullptr};
fidl::InterfaceHandle<fuchsia::ui::views::Focuser> focuser_{nullptr};
fidl::InterfaceRequest<fuchsia::ui::input3::KeyboardListener>
keyboard_listener_{nullptr};
......@@ -967,6 +950,74 @@ TEST_F(PlatformViewTests, ViewEventsTest) {
ToString(view_state_changed_msg->data()));
}
// This test makes sure that the PlatformView forwards messages on the
// "flutter/platform_views" channel for GetCurrentFocusState and
// GetNextFocusState.
TEST_F(PlatformViewTests, GetFocusStatesTest) {
sys::testing::ServiceDirectoryProvider services_provider(dispatcher());
MockPlatformViewDelegate delegate;
flutter::TaskRunners task_runners =
flutter::TaskRunners("test_runners", nullptr, nullptr, nullptr, nullptr);
FakeViewRefFocused vrf;
fidl::BindingSet<fuchsia::ui::views::ViewRefFocused> vrf_bindings;
auto vrf_handle = vrf_bindings.AddBinding(&vrf);
flutter_runner::PlatformView platform_view =
PlatformViewBuilder(delegate, std::move(task_runners),
services_provider.service_directory())
.SetViewRefFocused(std::move(vrf_handle))
.Build();
// Cast platform_view to its base view so we can have access to the public
// "HandlePlatformMessage" function.
auto base_view = static_cast<flutter::PlatformView*>(&platform_view);
EXPECT_TRUE(base_view);
std::vector<bool> vrf_states{false, true, true, false,
true, false, true, true};
for (std::size_t i = 0; i < vrf_states.size(); ++i) {
// HostView.getNextFocusState should complete with the next focus state.
auto response1 = FakePlatformMessageResponse::Create();
base_view->HandlePlatformMessage(
response1->WithMessage("flutter/platform_views",
"{\"method\":\"HostView.getNextFocusState\"}"));
// Duplicate HostView.getNextFocusState requests should complete empty.
auto response2 = FakePlatformMessageResponse::Create();
base_view->HandlePlatformMessage(
response2->WithMessage("flutter/platform_views",
"{\"method\":\"HostView.getNextFocusState\"}"));
// Post watch events and make sure the hanging get is invoked each time.
RunLoopUntilIdle();
EXPECT_EQ(vrf.times_watched, i + 1);
// Dispatch the next vrf event.
vrf.ScheduleCallback(vrf_states[i]);
RunLoopUntilIdle();
// Make sure HostView.getCurrentFocusState completes with the current focus
// state.
auto response3 = FakePlatformMessageResponse::Create();
base_view->HandlePlatformMessage(response3->WithMessage(
"flutter/platform_views",
"{\"method\":\"HostView.getCurrentFocusState\"}"));
// Duplicate HostView.getCurrentFocusState are allowed.
auto response4 = FakePlatformMessageResponse::Create();
base_view->HandlePlatformMessage(response4->WithMessage(
"flutter/platform_views",
"{\"method\":\"HostView.getCurrentFocusState\"}"));
// Run event loop and check our results.
RunLoopUntilIdle();
response1->ExpectCompleted(vrf_states[i] ? "[true]" : "[false]");
response2->ExpectCompleted("");
response3->ExpectCompleted(vrf_states[i] ? "[true]" : "[false]");
response4->ExpectCompleted(vrf_states[i] ? "[true]" : "[false]");
}
}
// This test makes sure that the PlatformView forwards messages on the
// "flutter/platform_views" channel for RequestFocus.
TEST_F(PlatformViewTests, RequestFocusTest) {
......@@ -975,9 +1026,9 @@ TEST_F(PlatformViewTests, RequestFocusTest) {
flutter::TaskRunners task_runners =
flutter::TaskRunners("test_runners", nullptr, nullptr, nullptr, nullptr);
MockFocuser mock_focuser;
FakeFocuser focuser;
fidl::BindingSet<fuchsia::ui::views::Focuser> focuser_bindings;
auto focuser_handle = focuser_bindings.AddBinding(&mock_focuser);
auto focuser_handle = focuser_bindings.AddBinding(&focuser);
flutter_runner::PlatformView platform_view =
PlatformViewBuilder(delegate, std::move(task_runners),
......@@ -994,39 +1045,22 @@ TEST_F(PlatformViewTests, RequestFocusTest) {
auto mock_view_ref_pair = scenic::ViewRefPair::New();
// JSON for the message to be passed into the PlatformView.
char buff[254];
snprintf(buff, sizeof(buff),
"{"
" \"method\":\"View.requestFocus\","
" \"args\": {"
" \"viewRef\":%u"
" }"
"}",
mock_view_ref_pair.view_ref.reference.get());
// Define a custom gmock matcher to capture the response to platform message.
struct DataArg {
void Complete(std::unique_ptr<fml::Mapping> data) {
this->data = std::move(data);
}
std::unique_ptr<fml::Mapping> data;
};
DataArg data_arg;
fml::RefPtr<MockResponse> response = fml::MakeRefCounted<MockResponse>();
EXPECT_CALL(*response, Complete(::testing::_))
.WillOnce(::testing::Invoke(&data_arg, &DataArg::Complete));
std::unique_ptr<flutter::PlatformMessage> message =
std::make_unique<flutter::PlatformMessage>(
"flutter/platform_views",
fml::MallocMapping::Copy(buff, sizeof(buff)), response);
base_view->HandlePlatformMessage(std::move(message));
std::ostringstream message;
message << "{"
<< " \"method\":\"View.requestFocus\","
<< " \"args\": {"
<< " \"viewRef\":"
<< mock_view_ref_pair.view_ref.reference.get() << " }"
<< "}";
// Dispatch the plaform message request.
auto response = FakePlatformMessageResponse::Create();
base_view->HandlePlatformMessage(
response->WithMessage("flutter/platform_views", message.str()));
RunLoopUntilIdle();
EXPECT_TRUE(mock_focuser.request_focus_called());
auto result = ToString(*data_arg.data);
EXPECT_EQ(std::string("[0]"), result);
response->ExpectCompleted("[0]");
EXPECT_TRUE(focuser.request_focus_called());
}
// This test makes sure that the PlatformView correctly replies with an error
......@@ -1037,9 +1071,10 @@ TEST_F(PlatformViewTests, RequestFocusFailTest) {
flutter::TaskRunners task_runners =
flutter::TaskRunners("test_runners", nullptr, nullptr, nullptr, nullptr);
MockFocuser mock_focuser(true /*fail_request_focus*/);
FakeFocuser focuser;
focuser.fail_request_focus();
fidl::BindingSet<fuchsia::ui::views::Focuser> focuser_bindings;
auto focuser_handle = focuser_bindings.AddBinding(&mock_focuser);
auto focuser_handle = focuser_bindings.AddBinding(&focuser);
flutter_runner::PlatformView platform_view =
PlatformViewBuilder(delegate, std::move(task_runners),
......@@ -1056,44 +1091,27 @@ TEST_F(PlatformViewTests, RequestFocusFailTest) {
auto mock_view_ref_pair = scenic::ViewRefPair::New();
// JSON for the message to be passed into the PlatformView.
char buff[254];
snprintf(buff, sizeof(buff),
"{"
" \"method\":\"View.requestFocus\","
" \"args\": {"
" \"viewRef\":%u"
" }"
"}",
mock_view_ref_pair.view_ref.reference.get());
// Define a custom gmock matcher to capture the response to platform message.
struct DataArg {
void Complete(std::unique_ptr<fml::Mapping> data) {
this->data = std::move(data);
}
std::unique_ptr<fml::Mapping> data;
};
DataArg data_arg;
fml::RefPtr<MockResponse> response = fml::MakeRefCounted<MockResponse>();
EXPECT_CALL(*response, Complete(::testing::_))
.WillOnce(::testing::Invoke(&data_arg, &DataArg::Complete));
std::unique_ptr<flutter::PlatformMessage> message =
std::make_unique<flutter::PlatformMessage>(
"flutter/platform_views",
fml::MallocMapping::Copy(buff, sizeof(buff)), response);
base_view->HandlePlatformMessage(std::move(message));
std::ostringstream message;
message << "{"
<< " \"method\":\"View.requestFocus\","
<< " \"args\": {"
<< " \"viewRef\":"
<< mock_view_ref_pair.view_ref.reference.get() << " }"
<< "}";
// Dispatch the plaform message request.
auto response = FakePlatformMessageResponse::Create();
base_view->HandlePlatformMessage(
response->WithMessage("flutter/platform_views", message.str()));
RunLoopUntilIdle();
EXPECT_TRUE(mock_focuser.request_focus_called());
auto result = ToString(*data_arg.data);
std::ostringstream out;
out << "["
<< static_cast<std::underlying_type_t<fuchsia::ui::views::Error>>(
fuchsia::ui::views::Error::DENIED)
<< "]";
EXPECT_EQ(out.str(), result);
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());
}
struct EventFlow {
......
# Copyright 2013 The Flutter Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
assert(is_fuchsia)
source_set("fakes") {
testonly = true
sources = [
"focuser.h",
"platform_message.h",
"view_ref_focused.h",
]
deps = [
"//build/fuchsia/pkg:sys_cpp_testing",
"//flutter/lib/ui",
"//flutter/testing",
"//third_party/rapidjson",
]
}
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_TESTS_FAKES_FOCUSER_H_
#define FLUTTER_SHELL_PLATFORM_FUCHSIA_TESTS_FAKES_FOCUSER_H_
#include <fuchsia/ui/views/cpp/fidl.h>
using Focuser = fuchsia::ui::views::Focuser;
namespace flutter_runner::testing {
class FakeFocuser : public Focuser {
public:
bool request_focus_called() const { return request_focus_called_; }
void fail_request_focus(bool fail_request = true) {
fail_request_focus_ = fail_request;
}
private:
void RequestFocus(fuchsia::ui::views::ViewRef view_ref,
RequestFocusCallback callback) override {
request_focus_called_ = true;
auto result =
fail_request_focus_
? fuchsia::ui::views::Focuser_RequestFocus_Result::WithErr(
fuchsia::ui::views::Error::DENIED)
: fuchsia::ui::views::Focuser_RequestFocus_Result::WithResponse(
fuchsia::ui::views::Focuser_RequestFocus_Response());
callback(std::move(result));
}
bool request_focus_called_ = false;
bool fail_request_focus_ = false;
};
} // namespace flutter_runner::testing
#endif // FLUTTER_SHELL_PLATFORM_FUCHSIA_TESTS_FAKES_FOCUSER_H_
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_TESTS_FAKES_PLATFORM_MESSAGE_H_
#define FLUTTER_SHELL_PLATFORM_FUCHSIA_TESTS_FAKES_PLATFORM_MESSAGE_H_
#include <gtest/gtest.h>
#include <optional>
#include "flutter/lib/ui/window/platform_message.h"
#include "third_party/rapidjson/include/rapidjson/document.h"
using PlatformMessageResponse = flutter::PlatformMessageResponse;
using PlatformMessage = flutter::PlatformMessage;
namespace flutter_runner::testing {
class FakePlatformMessageResponse : public PlatformMessageResponse {
public:
static fml::RefPtr<FakePlatformMessageResponse> Create() {
return fml::AdoptRef(new FakePlatformMessageResponse());
}
void ExpectCompleted(std::string expected) {
EXPECT_TRUE(is_complete_);
if (is_complete_) {
EXPECT_EQ(expected, response_);
}
}
std::unique_ptr<PlatformMessage> WithMessage(std::string channel,
std::string message) {
return std::make_unique<PlatformMessage>(
channel,
fml::MallocMapping::Copy(message.c_str(),
message.c_str() + message.size()),
fml::RefPtr<FakePlatformMessageResponse>(this));
}
void Complete(std::unique_ptr<fml::Mapping> data) override {
response_ =
std::string(data->GetMapping(), data->GetMapping() + data->GetSize());
FinalizeComplete();
};
void CompleteEmpty() override { FinalizeComplete(); };
private:
// Private constructors.
FakePlatformMessageResponse() {}
void FinalizeComplete() {
EXPECT_FALSE(std::exchange(is_complete_, true))
<< "Platform message responses can only be completed once!";
}
std::string response_;
};
} // namespace flutter_runner::testing
#endif // FLUTTER_SHELL_PLATFORM_FUCHSIA_TESTS_FAKES_PLATFORM_MESSAGE_H_
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_TESTS_FAKES_VIEW_REF_FOCUSED_H_
#define FLUTTER_SHELL_PLATFORM_FUCHSIA_TESTS_FAKES_VIEW_REF_FOCUSED_H_
#include <fuchsia/ui/views/cpp/fidl.h>
using ViewRefFocused = fuchsia::ui::views::ViewRefFocused;
namespace flutter_runner::testing {
class FakeViewRefFocused : public ViewRefFocused {
public:
using WatchCallback = ViewRefFocused::WatchCallback;
std::size_t times_watched = 0;
void Watch(WatchCallback callback) override {
callback_ = std::move(callback);
++times_watched;
}
void ScheduleCallback(bool focused) {
fuchsia::ui::views::FocusState focus_state;
focus_state.set_focused(focused);
callback_(std::move(focus_state));
}
private:
WatchCallback callback_;
};
} // namespace flutter_runner::testing
#endif // FLUTTER_SHELL_PLATFORM_FUCHSIA_TESTS_FAKES_VIEW_REF_FOCUSED_H_
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册