| // 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 "gfx_platform_view.h" |
| |
| #include <fuchsia/ui/gfx/cpp/fidl.h> |
| #include <fuchsia/ui/input/cpp/fidl.h> |
| #include <fuchsia/ui/input3/cpp/fidl.h> |
| #include <fuchsia/ui/input3/cpp/fidl_test_base.h> |
| #include <fuchsia/ui/scenic/cpp/fidl.h> |
| #include <fuchsia/ui/views/cpp/fidl.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/async/default.h> |
| #include <lib/fidl/cpp/binding_set.h> |
| #include <lib/ui/scenic/cpp/view_ref_pair.h> |
| |
| #include <memory> |
| #include <ostream> |
| #include <string> |
| #include <vector> |
| |
| #include "flutter/flow/embedded_views.h" |
| #include "flutter/lib/ui/window/platform_message.h" |
| #include "flutter/lib/ui/window/pointer_data.h" |
| #include "flutter/lib/ui/window/viewport_metrics.h" |
| #include "flutter/shell/common/context_options.h" |
| #include "flutter/shell/platform/fuchsia/flutter/gfx_platform_view.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| |
| #include "platform/assert.h" |
| #include "surface.h" |
| #include "task_runner_adapter.h" |
| #include "tests/fakes/focuser.h" |
| #include "tests/fakes/platform_message.h" |
| #include "tests/fakes/touch_source.h" |
| #include "tests/fakes/view_ref_focused.h" |
| #include "tests/pointer_event_utility.h" |
| |
| namespace flutter_runner::testing { |
| namespace { |
| |
| class MockExternalViewEmbedder : public flutter::ExternalViewEmbedder { |
| public: |
| SkCanvas* GetRootCanvas() override { return nullptr; } |
| std::vector<SkCanvas*> GetCurrentCanvases() override { |
| return std::vector<SkCanvas*>(); |
| } |
| |
| void CancelFrame() override {} |
| void BeginFrame( |
| SkISize frame_size, |
| GrDirectContext* context, |
| double device_pixel_ratio, |
| fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) override {} |
| void SubmitFrame(GrDirectContext* context, |
| std::unique_ptr<flutter::SurfaceFrame> frame) override { |
| return; |
| } |
| |
| void PrerollCompositeEmbeddedView( |
| int view_id, |
| std::unique_ptr<flutter::EmbeddedViewParams> params) override {} |
| SkCanvas* CompositeEmbeddedView(int view_id) override { return nullptr; } |
| }; |
| |
| class MockPlatformViewDelegate : public flutter::PlatformView::Delegate { |
| public: |
| void Reset() { |
| message_ = nullptr; |
| metrics_ = flutter::ViewportMetrics{}; |
| semantics_features_ = 0; |
| semantics_enabled_ = false; |
| pointer_packets_.clear(); |
| } |
| |
| // |flutter::PlatformView::Delegate| |
| void OnPlatformViewCreated(std::unique_ptr<flutter::Surface> surface) { |
| ASSERT_EQ(surface_.get(), nullptr); |
| |
| surface_ = std::move(surface); |
| } |
| // |flutter::PlatformView::Delegate| |
| void OnPlatformViewDestroyed() {} |
| // |flutter::PlatformView::Delegate| |
| void OnPlatformViewScheduleFrame() {} |
| // |flutter::PlatformView::Delegate| |
| void OnPlatformViewSetNextFrameCallback(const fml::closure& closure) {} |
| // |flutter::PlatformView::Delegate| |
| void OnPlatformViewSetViewportMetrics( |
| const flutter::ViewportMetrics& metrics) { |
| metrics_ = metrics; |
| } |
| // |flutter::PlatformView::Delegate| |
| const flutter::Settings& OnPlatformViewGetSettings() const { |
| return settings_; |
| } |
| // |flutter::PlatformView::Delegate| |
| void OnPlatformViewDispatchPlatformMessage( |
| std::unique_ptr<flutter::PlatformMessage> message) { |
| message_ = std::move(message); |
| } |
| // |flutter::PlatformView::Delegate| |
| void OnPlatformViewDispatchPointerDataPacket( |
| std::unique_ptr<flutter::PointerDataPacket> packet) { |
| pointer_packets_.push_back(std::move(packet)); |
| } |
| // |flutter::PlatformView::Delegate| |
| void OnPlatformViewDispatchSemanticsAction(int32_t id, |
| flutter::SemanticsAction action, |
| fml::MallocMapping args) {} |
| // |flutter::PlatformView::Delegate| |
| void OnPlatformViewSetSemanticsEnabled(bool enabled) { |
| semantics_enabled_ = enabled; |
| } |
| // |flutter::PlatformView::Delegate| |
| void OnPlatformViewSetAccessibilityFeatures(int32_t flags) { |
| semantics_features_ = flags; |
| } |
| // |flutter::PlatformView::Delegate| |
| void OnPlatformViewRegisterTexture( |
| std::shared_ptr<flutter::Texture> texture) {} |
| // |flutter::PlatformView::Delegate| |
| void OnPlatformViewUnregisterTexture(int64_t texture_id) {} |
| // |flutter::PlatformView::Delegate| |
| void OnPlatformViewMarkTextureFrameAvailable(int64_t texture_id) {} |
| // |flutter::PlatformView::Delegate| |
| std::unique_ptr<std::vector<std::string>> ComputePlatformViewResolvedLocale( |
| const std::vector<std::string>& supported_locale_data) { |
| return nullptr; |
| } |
| // |flutter::PlatformView::Delegate| |
| void LoadDartDeferredLibrary( |
| intptr_t loading_unit_id, |
| std::unique_ptr<const fml::Mapping> snapshot_data, |
| std::unique_ptr<const fml::Mapping> snapshot_instructions) {} |
| // |flutter::PlatformView::Delegate| |
| void LoadDartDeferredLibraryError(intptr_t loading_unit_id, |
| const std::string error_message, |
| bool transient) {} |
| // |flutter::PlatformView::Delegate| |
| void UpdateAssetResolverByType( |
| std::unique_ptr<flutter::AssetResolver> updated_asset_resolver, |
| flutter::AssetResolver::AssetResolverType type) {} |
| |
| flutter::Surface* surface() const { return surface_.get(); } |
| flutter::PlatformMessage* message() const { return message_.get(); } |
| const flutter::ViewportMetrics& metrics() const { return metrics_; } |
| int32_t semantics_features() const { return semantics_features_; } |
| bool semantics_enabled() const { return semantics_enabled_; } |
| const std::vector<std::unique_ptr<flutter::PointerDataPacket>>& |
| pointer_packets() const { |
| return pointer_packets_; |
| } |
| std::vector<std::unique_ptr<flutter::PointerDataPacket>> |
| TakePointerDataPackets() { |
| auto tmp = std::move(pointer_packets_); |
| pointer_packets_.clear(); |
| return tmp; |
| } |
| |
| private: |
| std::unique_ptr<flutter::Surface> surface_; |
| std::unique_ptr<flutter::PlatformMessage> message_; |
| flutter::ViewportMetrics metrics_; |
| std::vector<std::unique_ptr<flutter::PointerDataPacket>> pointer_packets_; |
| int32_t semantics_features_ = 0; |
| bool semantics_enabled_ = false; |
| flutter::Settings settings_; |
| }; |
| |
| class MockResponse : public flutter::PlatformMessageResponse { |
| public: |
| MOCK_METHOD1(Complete, void(std::unique_ptr<fml::Mapping> data)); |
| MOCK_METHOD0(CompleteEmpty, void()); |
| }; |
| |
| class TestPlatformMessageResponse : public flutter::PlatformMessageResponse { |
| public: |
| TestPlatformMessageResponse() {} |
| void Complete(std::unique_ptr<fml::Mapping> data) override { |
| result_string = std::string( |
| reinterpret_cast<const char*>(data->GetMapping()), data->GetSize()); |
| is_complete_ = true; |
| } |
| void CompleteEmpty() override { is_complete_ = true; } |
| std::string result_string; |
| FML_DISALLOW_COPY_AND_ASSIGN(TestPlatformMessageResponse); |
| }; |
| |
| class MockKeyboard : public fuchsia::ui::input3::testing::Keyboard_TestBase { |
| public: |
| explicit MockKeyboard( |
| fidl::InterfaceRequest<fuchsia::ui::input3::Keyboard> keyboard) |
| : keyboard_(this, std::move(keyboard)) {} |
| ~MockKeyboard() = default; |
| |
| void AddListener(fuchsia::ui::views::ViewRef view_ref, |
| fuchsia::ui::input3::KeyboardListenerHandle listener, |
| AddListenerCallback callback) override { |
| FML_CHECK(!listener_.is_bound()); |
| |
| listener_ = listener.Bind(); |
| view_ref_ = std::move(view_ref); |
| |
| callback(); |
| } |
| |
| void NotImplemented_(const std::string& name) override { FAIL(); } |
| |
| fidl::Binding<fuchsia::ui::input3::Keyboard> keyboard_; |
| fuchsia::ui::input3::KeyboardListenerPtr listener_{}; |
| fuchsia::ui::views::ViewRef view_ref_{}; |
| |
| FML_DISALLOW_COPY_AND_ASSIGN(MockKeyboard); |
| }; |
| |
| // Used to construct partial instances of PlatformView for testing. The |
| // PlatformView constructor has many parameters, not all of which need to |
| // be filled out for each test. The builder allows you to initialize only |
| // those that matter to your specific test. Not all builder methods are |
| // provided: if you find some that are missing, feel free to add them. |
| class PlatformViewBuilder { |
| public: |
| PlatformViewBuilder(flutter::PlatformView::Delegate& delegate, |
| flutter::TaskRunners task_runners) |
| : delegate_(delegate), |
| task_runners_(task_runners), |
| view_ref_pair_(scenic::ViewRefPair::New()) {} |
| |
| PlatformViewBuilder& SetExternalViewEmbedder( |
| std::shared_ptr<flutter::ExternalViewEmbedder> embedder) { |
| external_external_view_embedder_ = embedder; |
| return *this; |
| } |
| |
| PlatformViewBuilder& SetImeService( |
| fuchsia::ui::input::ImeServiceHandle ime_service) { |
| ime_service_ = std::move(ime_service); |
| return *this; |
| } |
| |
| PlatformViewBuilder& SetKeyboard( |
| fuchsia::ui::input3::KeyboardHandle keyboard) { |
| keyboard_ = std::move(keyboard); |
| return *this; |
| } |
| |
| PlatformViewBuilder& SetTouchSource( |
| fuchsia::ui::pointer::TouchSourceHandle touch_source) { |
| touch_source_ = std::move(touch_source); |
| return *this; |
| } |
| |
| PlatformViewBuilder& SetMouseSource( |
| fuchsia::ui::pointer::MouseSourceHandle mouse_source) { |
| mouse_source_ = std::move(mouse_source); |
| return *this; |
| } |
| |
| PlatformViewBuilder& SetFocuser(fuchsia::ui::views::FocuserHandle focuser) { |
| focuser_ = std::move(focuser); |
| return *this; |
| } |
| |
| PlatformViewBuilder& SetViewRefFocused( |
| fuchsia::ui::views::ViewRefFocusedHandle view_ref_focused) { |
| view_ref_focused_ = std::move(view_ref_focused); |
| return *this; |
| } |
| |
| PlatformViewBuilder& SetSessionListenerRequest( |
| fidl::InterfaceRequest<fuchsia::ui::scenic::SessionListener> request) { |
| session_listener_request_ = std::move(request); |
| return *this; |
| } |
| |
| PlatformViewBuilder& SetEnableWireframeCallback(OnEnableWireframe callback) { |
| wireframe_enabled_callback_ = std::move(callback); |
| return *this; |
| } |
| |
| PlatformViewBuilder& SetCreateViewCallback(OnCreateGfxView callback) { |
| on_create_view_callback_ = std::move(callback); |
| return *this; |
| } |
| |
| PlatformViewBuilder& SetUpdateViewCallback(OnUpdateView callback) { |
| on_update_view_callback_ = std::move(callback); |
| return *this; |
| } |
| |
| PlatformViewBuilder& SetDestroyViewCallback(OnDestroyGfxView callback) { |
| on_destroy_view_callback_ = std::move(callback); |
| return *this; |
| } |
| |
| PlatformViewBuilder& SetCreateSurfaceCallback(OnCreateSurface callback) { |
| on_create_surface_callback_ = std::move(callback); |
| return *this; |
| } |
| |
| PlatformViewBuilder& SetShaderWarmupCallback(OnShaderWarmup callback) { |
| on_shader_warmup_callback_ = std::move(callback); |
| return *this; |
| } |
| |
| // Once Build is called, the instance is no longer usable. |
| GfxPlatformView Build() { |
| EXPECT_FALSE(std::exchange(built_, true)) |
| << "Build() was already called, this buider is good for one use only."; |
| return GfxPlatformView( |
| delegate_, task_runners_, std::move(view_ref_pair_.view_ref), |
| external_external_view_embedder_, std::move(ime_service_), |
| std::move(keyboard_), std::move(touch_source_), |
| std::move(mouse_source_), std::move(focuser_), |
| std::move(view_ref_focused_), std::move(session_listener_request_), |
| std::move(on_session_listener_error_callback_), |
| std::move(wireframe_enabled_callback_), |
| std::move(on_create_view_callback_), |
| std::move(on_update_view_callback_), |
| std::move(on_destroy_view_callback_), |
| std::move(on_create_surface_callback_), |
| std::move(on_semantics_node_update_callback_), |
| std::move(on_request_announce_callback_), |
| std::move(on_shader_warmup_callback_), [](auto...) {}, [](auto...) {}); |
| } |
| |
| private: |
| PlatformViewBuilder() = delete; |
| |
| flutter::PlatformView::Delegate& delegate_; |
| flutter::TaskRunners task_runners_; |
| scenic::ViewRefPair view_ref_pair_; |
| |
| std::shared_ptr<flutter::ExternalViewEmbedder> |
| external_external_view_embedder_; |
| fuchsia::ui::input::ImeServiceHandle ime_service_; |
| fuchsia::ui::input3::KeyboardHandle keyboard_; |
| fuchsia::ui::pointer::TouchSourceHandle touch_source_; |
| fuchsia::ui::pointer::MouseSourceHandle mouse_source_; |
| fuchsia::ui::views::ViewRefFocusedHandle view_ref_focused_; |
| fuchsia::ui::views::FocuserHandle focuser_; |
| fidl::InterfaceRequest<fuchsia::ui::scenic::SessionListener> |
| session_listener_request_; |
| fit::closure on_session_listener_error_callback_; |
| OnEnableWireframe wireframe_enabled_callback_; |
| OnCreateGfxView on_create_view_callback_; |
| OnUpdateView on_update_view_callback_; |
| OnDestroyGfxView on_destroy_view_callback_; |
| OnCreateSurface on_create_surface_callback_; |
| OnSemanticsNodeUpdate on_semantics_node_update_callback_; |
| OnRequestAnnounce on_request_announce_callback_; |
| OnShaderWarmup on_shader_warmup_callback_; |
| |
| bool built_{false}; |
| }; |
| |
| std::string ToString(const fml::Mapping& mapping) { |
| return std::string(mapping.GetMapping(), |
| mapping.GetMapping() + mapping.GetSize()); |
| } |
| |
| // Stolen from pointer_data_packet_converter_unittests.cc. |
| void UnpackPointerPacket(std::vector<flutter::PointerData>& output, // NOLINT |
| std::unique_ptr<flutter::PointerDataPacket> packet) { |
| size_t kBytesPerPointerData = |
| flutter::kPointerDataFieldCount * flutter::kBytesPerField; |
| auto buffer = packet->data(); |
| size_t buffer_length = buffer.size(); |
| |
| for (size_t i = 0; i < buffer_length / kBytesPerPointerData; i++) { |
| flutter::PointerData pointer_data; |
| memcpy(&pointer_data, &buffer[i * kBytesPerPointerData], |
| sizeof(flutter::PointerData)); |
| output.push_back(pointer_data); |
| } |
| packet.reset(); |
| } |
| |
| } // namespace |
| |
| class PlatformViewTests : public ::testing::Test { |
| protected: |
| PlatformViewTests() : loop_(&kAsyncLoopConfigAttachToCurrentThread) {} |
| |
| async_dispatcher_t* dispatcher() { return loop_.dispatcher(); } |
| |
| void RunLoopUntilIdle() { |
| loop_.RunUntilIdle(); |
| loop_.ResetQuit(); |
| } |
| |
| fuchsia::ui::input3::KeyEvent MakeEvent( |
| fuchsia::ui::input3::KeyEventType event_type, |
| std::optional<fuchsia::ui::input3::Modifiers> modifiers, |
| fuchsia::input::Key key) { |
| fuchsia::ui::input3::KeyEvent event; |
| event.set_timestamp(++event_timestamp_); |
| event.set_type(event_type); |
| if (modifiers.has_value()) { |
| event.set_modifiers(modifiers.value()); |
| } |
| event.set_key(key); |
| return event; |
| } |
| |
| private: |
| async::Loop loop_; |
| |
| uint64_t event_timestamp_{42}; |
| |
| FML_DISALLOW_COPY_AND_ASSIGN(PlatformViewTests); |
| }; |
| |
| // This test makes sure that the PlatformView always completes a platform |
| // message request, even for error conditions or if the request is malformed. |
| TEST_F(PlatformViewTests, InvalidPlatformMessageRequest) { |
| 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::GfxPlatformView platform_view = |
| PlatformViewBuilder(delegate, std::move(task_runners)) |
| .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); |
| |
| // Invalid platform channel. |
| auto response1 = FakePlatformMessageResponse::Create(); |
| base_view->HandlePlatformMessage(response1->WithMessage( |
| "flutter/invalid", "{\"method\":\"Invalid.invalidMethod\"}")); |
| |
| // Invalid json. |
| auto response2 = FakePlatformMessageResponse::Create(); |
| base_view->HandlePlatformMessage( |
| response2->WithMessage("flutter/platform_views", "{Invalid JSON")); |
| |
| // Invalid method. |
| auto response3 = FakePlatformMessageResponse::Create(); |
| base_view->HandlePlatformMessage(response3->WithMessage( |
| "flutter/platform_views", "{\"method\":\"View.focus.invalidMethod\"}")); |
| |
| // Missing arguments. |
| auto response4 = FakePlatformMessageResponse::Create(); |
| base_view->HandlePlatformMessage(response4->WithMessage( |
| "flutter/platform_views", "{\"method\":\"View.update\"}")); |
| auto response5 = FakePlatformMessageResponse::Create(); |
| base_view->HandlePlatformMessage( |
| response5->WithMessage("flutter/platform_views", |
| "{\"method\":\"View.update\",\"args\":{" |
| "\"irrelevantField\":\"irrelevantValue\"}}")); |
| |
| // Wrong argument types. |
| auto response6 = FakePlatformMessageResponse::Create(); |
| base_view->HandlePlatformMessage(response6->WithMessage( |
| "flutter/platform_views", |
| "{\"method\":\"View.update\",\"args\":{\"viewId\":false,\"hitTestable\":" |
| "123,\"focusable\":\"yes\"}}")); |
| |
| // Run the event loop and check our responses. |
| RunLoopUntilIdle(); |
| response1->ExpectCompleted(""); |
| response2->ExpectCompleted(""); |
| response3->ExpectCompleted(""); |
| response4->ExpectCompleted(""); |
| response5->ExpectCompleted(""); |
| response6->ExpectCompleted(""); |
| } |
| |
| // This test makes sure that the PlatformView correctly returns a Surface |
| // instance that can surface the provided gr_context and external_view_embedder. |
| TEST_F(PlatformViewTests, CreateSurfaceTest) { |
| MockPlatformViewDelegate delegate; |
| |
| flutter::TaskRunners task_runners = |
| flutter::TaskRunners("test_runners", // label |
| nullptr, // platform |
| flutter_runner::CreateFMLTaskRunner( |
| async_get_default_dispatcher()), // raster |
| nullptr, // ui |
| nullptr // io |
| ); |
| |
| // Test create surface callback function. |
| sk_sp<GrDirectContext> gr_context = GrDirectContext::MakeMock( |
| nullptr, |
| flutter::MakeDefaultContextOptions(flutter::ContextType::kRender)); |
| std::shared_ptr<MockExternalViewEmbedder> external_view_embedder = |
| std::make_shared<MockExternalViewEmbedder>(); |
| auto CreateSurfaceCallback = [&external_view_embedder, gr_context]() { |
| return std::make_unique<flutter_runner::Surface>( |
| "PlatformViewTest", external_view_embedder, gr_context.get()); |
| }; |
| |
| flutter_runner::GfxPlatformView platform_view = |
| PlatformViewBuilder(delegate, std::move(task_runners)) |
| .SetCreateSurfaceCallback(CreateSurfaceCallback) |
| .SetExternalViewEmbedder(external_view_embedder) |
| .Build(); |
| platform_view.NotifyCreated(); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(gr_context.get(), delegate.surface()->GetContext()); |
| EXPECT_EQ(external_view_embedder.get(), |
| platform_view.CreateExternalViewEmbedder().get()); |
| } |
| |
| // This test makes sure that the PlatformView correctly registers Scenic |
| // MetricsEvents sent to it via FIDL, correctly parses the metrics it receives, |
| // and calls the SetViewportMetrics callback with the appropriate parameters. |
| TEST_F(PlatformViewTests, SetViewportMetrics) { |
| constexpr float invalid_pixel_ratio = -0.75f; |
| constexpr float valid_pixel_ratio = 0.75f; |
| constexpr float invalid_max_bound = -0.75f; |
| constexpr float valid_max_bound = 0.75f; |
| |
| MockPlatformViewDelegate delegate; |
| EXPECT_EQ(delegate.metrics(), flutter::ViewportMetrics()); |
| |
| fuchsia::ui::scenic::SessionListenerPtr session_listener; |
| std::vector<fuchsia::ui::scenic::Event> events; |
| flutter::TaskRunners task_runners("test_runners", nullptr, nullptr, nullptr, |
| nullptr); |
| flutter_runner::GfxPlatformView platform_view = |
| PlatformViewBuilder(delegate, std::move(task_runners)) |
| .SetSessionListenerRequest(session_listener.NewRequest()) |
| .Build(); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(delegate.metrics(), flutter::ViewportMetrics()); |
| |
| // Test updating with an invalid pixel ratio. The final metrics should be |
| // unchanged. |
| events.clear(); |
| events.emplace_back(fuchsia::ui::scenic::Event::WithGfx( |
| fuchsia::ui::gfx::Event::WithMetrics(fuchsia::ui::gfx::MetricsEvent{ |
| .node_id = 0, |
| .metrics = |
| fuchsia::ui::gfx::Metrics{ |
| .scale_x = invalid_pixel_ratio, |
| .scale_y = 1.f, |
| .scale_z = 1.f, |
| }, |
| }))); |
| session_listener->OnScenicEvent(std::move(events)); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(delegate.metrics(), flutter::ViewportMetrics()); |
| |
| // Test updating with an invalid size. The final metrics should be unchanged. |
| events.clear(); |
| events.emplace_back( |
| fuchsia::ui::scenic::Event::WithGfx( |
| fuchsia::ui::gfx::Event::WithViewPropertiesChanged( |
| fuchsia::ui::gfx::ViewPropertiesChangedEvent{ |
| .view_id = 0, |
| .properties = |
| fuchsia::ui::gfx::ViewProperties{ |
| .bounding_box = |
| fuchsia::ui::gfx::BoundingBox{ |
| .min = |
| fuchsia::ui::gfx::vec3{ |
| .x = 0.f, |
| .y = 0.f, |
| .z = 0.f, |
| }, |
| .max = |
| fuchsia::ui::gfx::vec3{ |
| .x = invalid_max_bound, |
| .y = invalid_max_bound, |
| .z = invalid_max_bound, |
| }, |
| }, |
| }, |
| }))); |
| session_listener->OnScenicEvent(std::move(events)); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(delegate.metrics(), flutter::ViewportMetrics()); |
| |
| // Test updating the size only. The final metrics should be unchanged until |
| // both pixel ratio and size are updated. |
| events.clear(); |
| events.emplace_back( |
| fuchsia::ui::scenic::Event::WithGfx( |
| fuchsia::ui::gfx::Event::WithViewPropertiesChanged( |
| fuchsia::ui::gfx::ViewPropertiesChangedEvent{ |
| .view_id = 0, |
| .properties = |
| fuchsia::ui::gfx::ViewProperties{ |
| .bounding_box = |
| fuchsia::ui::gfx::BoundingBox{ |
| .min = |
| fuchsia::ui::gfx::vec3{ |
| .x = 0.f, |
| .y = 0.f, |
| .z = 0.f, |
| }, |
| .max = |
| fuchsia::ui::gfx::vec3{ |
| .x = valid_max_bound, |
| .y = valid_max_bound, |
| .z = valid_max_bound, |
| }, |
| }, |
| }, |
| }))); |
| session_listener->OnScenicEvent(std::move(events)); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(delegate.metrics(), flutter::ViewportMetrics()); |
| |
| // Test updating the pixel ratio only. The final metrics should change now. |
| events.clear(); |
| events.emplace_back(fuchsia::ui::scenic::Event::WithGfx( |
| fuchsia::ui::gfx::Event::WithMetrics(fuchsia::ui::gfx::MetricsEvent{ |
| .node_id = 0, |
| .metrics = |
| fuchsia::ui::gfx::Metrics{ |
| .scale_x = valid_pixel_ratio, |
| .scale_y = 1.f, |
| .scale_z = 1.f, |
| }, |
| }))); |
| session_listener->OnScenicEvent(std::move(events)); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(delegate.metrics(), |
| flutter::ViewportMetrics( |
| valid_pixel_ratio, valid_pixel_ratio * valid_max_bound, |
| valid_pixel_ratio * valid_max_bound, -1.0)); |
| } |
| |
| // This test makes sure that the PlatformView correctly registers semantics |
| // settings changes applied to it and calls the SetSemanticsEnabled / |
| // SetAccessibilityFeatures callbacks with the appropriate parameters. |
| TEST_F(PlatformViewTests, ChangesAccessibilitySettings) { |
| MockPlatformViewDelegate delegate; |
| flutter::TaskRunners task_runners = |
| flutter::TaskRunners("test_runners", nullptr, nullptr, nullptr, nullptr); |
| |
| EXPECT_FALSE(delegate.semantics_enabled()); |
| EXPECT_EQ(delegate.semantics_features(), 0); |
| |
| flutter_runner::GfxPlatformView platform_view = |
| PlatformViewBuilder(delegate, std::move(task_runners)).Build(); |
| |
| RunLoopUntilIdle(); |
| |
| platform_view.SetSemanticsEnabled(true); |
| |
| EXPECT_TRUE(delegate.semantics_enabled()); |
| EXPECT_EQ(delegate.semantics_features(), |
| static_cast<int32_t>( |
| flutter::AccessibilityFeatureFlag::kAccessibleNavigation)); |
| |
| platform_view.SetSemanticsEnabled(false); |
| |
| EXPECT_FALSE(delegate.semantics_enabled()); |
| EXPECT_EQ(delegate.semantics_features(), 0); |
| } |
| |
| // This test makes sure that the PlatformView forwards messages on the |
| // "flutter/platform_views" channel for EnableWireframe. |
| TEST_F(PlatformViewTests, EnableWireframeTest) { |
| MockPlatformViewDelegate delegate; |
| flutter::TaskRunners task_runners = |
| flutter::TaskRunners("test_runners", nullptr, nullptr, nullptr, nullptr); |
| |
| // Test wireframe callback function. If the message sent to the platform |
| // view was properly handled and parsed, this function should be called, |
| // setting |wireframe_enabled| to true. |
| bool wireframe_enabled = false; |
| auto EnableWireframeCallback = [&wireframe_enabled](bool should_enable) { |
| wireframe_enabled = should_enable; |
| }; |
| |
| flutter_runner::GfxPlatformView platform_view = |
| PlatformViewBuilder(delegate, std::move(task_runners)) |
| .SetEnableWireframeCallback(EnableWireframeCallback) |
| .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); |
| |
| // JSON for the message to be passed into the PlatformView. |
| const uint8_t txt[] = |
| "{" |
| " \"method\":\"View.enableWireframe\"," |
| " \"args\": {" |
| " \"enable\":true" |
| " }" |
| "}"; |
| |
| std::unique_ptr<flutter::PlatformMessage> message = |
| std::make_unique<flutter::PlatformMessage>( |
| "flutter/platform_views", fml::MallocMapping::Copy(txt, sizeof(txt)), |
| fml::RefPtr<flutter::PlatformMessageResponse>()); |
| base_view->HandlePlatformMessage(std::move(message)); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(wireframe_enabled); |
| } |
| |
| // This test makes sure that the PlatformView forwards messages on the |
| // "flutter/platform_views" channel for Createview. |
| TEST_F(PlatformViewTests, CreateViewTest) { |
| MockPlatformViewDelegate delegate; |
| flutter::TaskRunners task_runners = |
| flutter::TaskRunners("test_runners", // label |
| flutter_runner::CreateFMLTaskRunner( |
| async_get_default_dispatcher()), // platform |
| nullptr, // raster |
| nullptr, // ui |
| nullptr // io |
| ); |
| |
| // Test wireframe callback function. If the message sent to the platform |
| // view was properly handled and parsed, this function should be called, |
| // setting |wireframe_enabled| to true. |
| bool create_view_called = false; |
| auto CreateViewCallback = [&create_view_called]( |
| int64_t view_id, |
| flutter_runner::ViewCallback on_view_created, |
| flutter_runner::GfxViewIdCallback on_view_bound, |
| bool hit_testable, bool focusable) { |
| create_view_called = true; |
| on_view_created(); |
| on_view_bound(0); |
| }; |
| |
| flutter_runner::GfxPlatformView platform_view = |
| PlatformViewBuilder(delegate, std::move(task_runners)) |
| .SetCreateViewCallback(CreateViewCallback) |
| .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); |
| |
| // JSON for the message to be passed into the PlatformView. |
| const uint8_t txt[] = |
| "{" |
| " \"method\":\"View.create\"," |
| " \"args\": {" |
| " \"viewId\":42," |
| " \"hitTestable\":true," |
| " \"focusable\":true" |
| " }" |
| "}"; |
| |
| std::unique_ptr<flutter::PlatformMessage> message = |
| std::make_unique<flutter::PlatformMessage>( |
| "flutter/platform_views", fml::MallocMapping::Copy(txt, sizeof(txt)), |
| fml::RefPtr<flutter::PlatformMessageResponse>()); |
| base_view->HandlePlatformMessage(std::move(message)); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(create_view_called); |
| } |
| |
| // This test makes sure that the PlatformView forwards messages on the |
| // "flutter/platform_views" channel for UpdateView. |
| TEST_F(PlatformViewTests, UpdateViewTest) { |
| MockPlatformViewDelegate delegate; |
| flutter::TaskRunners task_runners = |
| flutter::TaskRunners("test_runners", nullptr, nullptr, nullptr, nullptr); |
| |
| std::optional<SkRect> occlusion_hint_for_test; |
| std::optional<bool> hit_testable_for_test; |
| std::optional<bool> focusable_for_test; |
| auto UpdateViewCallback = [&occlusion_hint_for_test, &hit_testable_for_test, |
| &focusable_for_test]( |
| int64_t view_id, SkRect occlusion_hint, |
| bool hit_testable, bool focusable) { |
| occlusion_hint_for_test = occlusion_hint; |
| hit_testable_for_test = hit_testable; |
| focusable_for_test = focusable; |
| }; |
| |
| flutter_runner::GfxPlatformView platform_view = |
| PlatformViewBuilder(delegate, std::move(task_runners)) |
| .SetUpdateViewCallback(UpdateViewCallback) |
| .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); |
| |
| // Send a basic message. |
| const uint8_t json[] = |
| "{" |
| " \"method\":\"View.update\"," |
| " \"args\": {" |
| " \"viewId\":42," |
| " \"hitTestable\":true," |
| " \"focusable\":true" |
| " }" |
| "}"; |
| std::unique_ptr<flutter::PlatformMessage> message = |
| std::make_unique<flutter::PlatformMessage>( |
| "flutter/platform_views", |
| fml::MallocMapping::Copy(json, sizeof(json)), |
| fml::RefPtr<flutter::PlatformMessageResponse>()); |
| base_view->HandlePlatformMessage(std::move(message)); |
| |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(occlusion_hint_for_test.has_value()); |
| ASSERT_TRUE(hit_testable_for_test.has_value()); |
| ASSERT_TRUE(focusable_for_test.has_value()); |
| EXPECT_EQ(occlusion_hint_for_test.value(), SkRect::MakeEmpty()); |
| EXPECT_EQ(hit_testable_for_test.value(), true); |
| EXPECT_EQ(focusable_for_test.value(), true); |
| |
| // Reset for the next message. |
| occlusion_hint_for_test.reset(); |
| hit_testable_for_test.reset(); |
| focusable_for_test.reset(); |
| |
| // Send another basic message. |
| const uint8_t json_false[] = |
| "{" |
| " \"method\":\"View.update\"," |
| " \"args\": {" |
| " \"viewId\":42," |
| " \"hitTestable\":false," |
| " \"focusable\":false" |
| " }" |
| "}"; |
| std::unique_ptr<flutter::PlatformMessage> message_false = |
| std::make_unique<flutter::PlatformMessage>( |
| "flutter/platform_views", |
| fml::MallocMapping::Copy(json_false, sizeof(json_false)), |
| fml::RefPtr<flutter::PlatformMessageResponse>()); |
| base_view->HandlePlatformMessage(std::move(message_false)); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(occlusion_hint_for_test.has_value()); |
| ASSERT_TRUE(hit_testable_for_test.has_value()); |
| ASSERT_TRUE(focusable_for_test.has_value()); |
| EXPECT_EQ(occlusion_hint_for_test.value(), SkRect::MakeEmpty()); |
| EXPECT_EQ(hit_testable_for_test.value(), false); |
| EXPECT_EQ(focusable_for_test.value(), false); |
| |
| // Reset for the next message. |
| occlusion_hint_for_test.reset(); |
| hit_testable_for_test.reset(); |
| focusable_for_test.reset(); |
| |
| // Send a message including an occlusion hint. |
| const uint8_t json_occlusion_hint[] = |
| "{" |
| " \"method\":\"View.update\"," |
| " \"args\": {" |
| " \"viewId\":42," |
| " \"hitTestable\":true," |
| " \"focusable\":true," |
| " \"viewOcclusionHintLTRB\":[0.1,0.2,0.3,0.4]" |
| " }" |
| "}"; |
| std::unique_ptr<flutter::PlatformMessage> message_occlusion_hint = |
| std::make_unique<flutter::PlatformMessage>( |
| "flutter/platform_views", |
| fml::MallocMapping::Copy(json_occlusion_hint, |
| sizeof(json_occlusion_hint)), |
| fml::RefPtr<flutter::PlatformMessageResponse>()); |
| base_view->HandlePlatformMessage(std::move(message_occlusion_hint)); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(occlusion_hint_for_test.has_value()); |
| ASSERT_TRUE(hit_testable_for_test.has_value()); |
| ASSERT_TRUE(focusable_for_test.has_value()); |
| EXPECT_EQ(occlusion_hint_for_test.value(), |
| SkRect::MakeLTRB(0.1, 0.2, 0.3, 0.4)); |
| EXPECT_EQ(hit_testable_for_test.value(), true); |
| EXPECT_EQ(focusable_for_test.value(), true); |
| } |
| |
| // This test makes sure that the PlatformView forwards messages on the |
| // "flutter/platform_views" channel for DestroyView. |
| TEST_F(PlatformViewTests, DestroyViewTest) { |
| MockPlatformViewDelegate delegate; |
| flutter::TaskRunners task_runners = |
| flutter::TaskRunners("test_runners", // label |
| flutter_runner::CreateFMLTaskRunner( |
| async_get_default_dispatcher()), // platform |
| nullptr, // raster |
| nullptr, // ui |
| nullptr // io |
| ); |
| |
| // Test wireframe callback function. If the message sent to the platform |
| // view was properly handled and parsed, this function should be called, |
| // setting |wireframe_enabled| to true. |
| bool destroy_view_called = false; |
| auto DestroyViewCallback = |
| [&destroy_view_called]( |
| int64_t view_id, flutter_runner::GfxViewIdCallback on_view_unbound) { |
| destroy_view_called = true; |
| on_view_unbound(0); |
| }; |
| |
| flutter_runner::GfxPlatformView platform_view = |
| PlatformViewBuilder(delegate, std::move(task_runners)) |
| .SetDestroyViewCallback(DestroyViewCallback) |
| .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); |
| |
| // JSON for the message to be passed into the PlatformView. |
| const uint8_t txt[] = |
| "{" |
| " \"method\":\"View.dispose\"," |
| " \"args\": {" |
| " \"viewId\":42" |
| " }" |
| "}"; |
| |
| std::unique_ptr<flutter::PlatformMessage> message = |
| std::make_unique<flutter::PlatformMessage>( |
| "flutter/platform_views", fml::MallocMapping::Copy(txt, sizeof(txt)), |
| fml::RefPtr<flutter::PlatformMessageResponse>()); |
| base_view->HandlePlatformMessage(std::move(message)); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(destroy_view_called); |
| } |
| |
| // This test makes sure that the PlatformView forwards messages on the |
| // "flutter/platform_views" channel for ViewConnected, ViewDisconnected, and |
| // ViewStateChanged events. |
| TEST_F(PlatformViewTests, ViewEventsTest) { |
| constexpr int64_t kViewId = 33; |
| constexpr scenic::ResourceId kViewHolderId = 42; |
| MockPlatformViewDelegate delegate; |
| |
| fuchsia::ui::scenic::SessionListenerPtr session_listener; |
| std::vector<fuchsia::ui::scenic::Event> events; |
| flutter::TaskRunners task_runners = |
| flutter::TaskRunners("test_runners", // label |
| flutter_runner::CreateFMLTaskRunner( |
| async_get_default_dispatcher()), // platform |
| flutter_runner::CreateFMLTaskRunner( |
| async_get_default_dispatcher()), // raster |
| flutter_runner::CreateFMLTaskRunner( |
| async_get_default_dispatcher()), // ui |
| nullptr // io |
| ); |
| |
| auto on_create_view = |
| [kViewId](int64_t view_id, flutter_runner::ViewCallback on_view_created, |
| flutter_runner::GfxViewIdCallback on_view_bound, |
| bool hit_testable, bool focusable) { |
| ASSERT_EQ(view_id, kViewId); |
| on_view_created(); |
| on_view_bound(kViewHolderId); |
| }; |
| |
| flutter_runner::GfxPlatformView platform_view = |
| PlatformViewBuilder(delegate, std::move(task_runners)) |
| .SetSessionListenerRequest(session_listener.NewRequest()) |
| .SetCreateViewCallback(on_create_view) |
| .Build(); |
| RunLoopUntilIdle(); |
| ASSERT_EQ(delegate.message(), nullptr); |
| |
| // Create initial view for testing. |
| std::ostringstream create_view_message; |
| create_view_message << "{" |
| << " \"method\":\"View.create\"," |
| << " \"args\":{" |
| << " \"viewId\":" << kViewId << "," |
| << " \"hitTestable\":true," |
| << " \"focusable\":true" |
| << " }" |
| << "}"; |
| std::string create_view_call = create_view_message.str(); |
| static_cast<flutter::PlatformView*>(&platform_view) |
| ->HandlePlatformMessage(std::make_unique<flutter::PlatformMessage>( |
| "flutter/platform_views", |
| fml::MallocMapping::Copy(create_view_call.c_str(), |
| create_view_call.size()), |
| fml::RefPtr<flutter::PlatformMessageResponse>())); |
| RunLoopUntilIdle(); |
| |
| // ViewConnected event. |
| delegate.Reset(); |
| events.clear(); |
| events.emplace_back(fuchsia::ui::scenic::Event::WithGfx( |
| fuchsia::ui::gfx::Event::WithViewConnected( |
| fuchsia::ui::gfx::ViewConnectedEvent{ |
| .view_holder_id = kViewHolderId, |
| }))); |
| session_listener->OnScenicEvent(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| flutter::PlatformMessage* view_connected_msg = delegate.message(); |
| ASSERT_NE(view_connected_msg, nullptr); |
| std::ostringstream view_connected_expected_out; |
| view_connected_expected_out |
| << "{" |
| << "\"method\":\"View.viewConnected\"," |
| << "\"args\":{" |
| << " \"viewId\":" << kViewId // ViewHolderToken handle |
| << " }" |
| << "}"; |
| EXPECT_EQ(view_connected_expected_out.str(), |
| ToString(view_connected_msg->data())); |
| |
| // ViewDisconnected event. |
| delegate.Reset(); |
| events.clear(); |
| events.emplace_back(fuchsia::ui::scenic::Event::WithGfx( |
| fuchsia::ui::gfx::Event::WithViewDisconnected( |
| fuchsia::ui::gfx::ViewDisconnectedEvent{ |
| .view_holder_id = kViewHolderId, |
| }))); |
| session_listener->OnScenicEvent(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| flutter::PlatformMessage* view_disconnected_msg = delegate.message(); |
| ASSERT_NE(view_disconnected_msg, nullptr); |
| std::ostringstream view_disconnected_expected_out; |
| view_disconnected_expected_out |
| << "{" |
| << "\"method\":\"View.viewDisconnected\"," |
| << "\"args\":{" |
| << " \"viewId\":" << kViewId // ViewHolderToken handle |
| << " }" |
| << "}"; |
| EXPECT_EQ(view_disconnected_expected_out.str(), |
| ToString(view_disconnected_msg->data())); |
| |
| // ViewStateChanged event. |
| delegate.Reset(); |
| events.clear(); |
| events.emplace_back(fuchsia::ui::scenic::Event::WithGfx( |
| fuchsia::ui::gfx::Event::WithViewStateChanged( |
| fuchsia::ui::gfx::ViewStateChangedEvent{ |
| .view_holder_id = kViewHolderId, |
| .state = |
| fuchsia::ui::gfx::ViewState{ |
| .is_rendering = true, |
| }, |
| }))); |
| session_listener->OnScenicEvent(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| flutter::PlatformMessage* view_state_changed_msg = delegate.message(); |
| ASSERT_NE(view_state_changed_msg, nullptr); |
| std::ostringstream view_state_changed_expected_out; |
| view_state_changed_expected_out |
| << "{" |
| << "\"method\":\"View.viewStateChanged\"," |
| << "\"args\":{" |
| << " \"viewId\":" << kViewId << "," // ViewHolderToken |
| << " \"is_rendering\":true," // IsViewRendering |
| << " \"state\":true" // IsViewRendering |
| << " }" |
| << "}"; |
| EXPECT_EQ(view_state_changed_expected_out.str(), |
| ToString(view_state_changed_msg->data())); |
| } |
| |
| // This test makes sure that the PlatformView forwards messages on the |
| // "flutter/platform_views" channel for View.focus.getCurrent and |
| // View.focus.getNext. |
| TEST_F(PlatformViewTests, GetFocusStatesTest) { |
| 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::GfxPlatformView platform_view = |
| PlatformViewBuilder(delegate, std::move(task_runners)) |
| .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) { |
| // View.focus.getNext should complete with the next focus state. |
| auto response1 = FakePlatformMessageResponse::Create(); |
| base_view->HandlePlatformMessage(response1->WithMessage( |
| "flutter/platform_views", "{\"method\":\"View.focus.getNext\"}")); |
| // Duplicate View.focus.getNext requests should complete empty. |
| auto response2 = FakePlatformMessageResponse::Create(); |
| base_view->HandlePlatformMessage(response2->WithMessage( |
| "flutter/platform_views", "{\"method\":\"View.focus.getNext\"}")); |
| |
| // 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 View.focus.getCurrent completes with the current focus state. |
| auto response3 = FakePlatformMessageResponse::Create(); |
| base_view->HandlePlatformMessage(response3->WithMessage( |
| "flutter/platform_views", "{\"method\":\"View.focus.getCurrent\"}")); |
| // Duplicate View.focus.getCurrent are allowed. |
| auto response4 = FakePlatformMessageResponse::Create(); |
| base_view->HandlePlatformMessage(response4->WithMessage( |
| "flutter/platform_views", "{\"method\":\"View.focus.getCurrent\"}")); |
| |
| // Run event loop and check our results. |
| RunLoopUntilIdle(); |
| response1->ExpectCompleted(vrf_states[i] ? "[true]" : "[false]"); |
| response2->ExpectCompleted("[null]"); |
| 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 View.focus.request. |
| TEST_F(PlatformViewTests, RequestFocusTest) { |
| MockPlatformViewDelegate delegate; |
| flutter::TaskRunners task_runners = |
| flutter::TaskRunners("test_runners", nullptr, nullptr, nullptr, nullptr); |
| |
| FakeFocuser focuser; |
| fidl::BindingSet<fuchsia::ui::views::Focuser> focuser_bindings; |
| auto focuser_handle = focuser_bindings.AddBinding(&focuser); |
| |
| flutter_runner::GfxPlatformView platform_view = |
| PlatformViewBuilder(delegate, std::move(task_runners)) |
| .SetFocuser(std::move(focuser_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); |
| |
| // This "Mock" ViewRef serves as the target for the RequestFocus operation. |
| auto mock_view_ref_pair = scenic::ViewRefPair::New(); |
| |
| // JSON for the message to be passed into the PlatformView. |
| std::ostringstream message; |
| message << "{" |
| << " \"method\":\"View.focus.request\"," |
| << " \"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(); |
| |
| response->ExpectCompleted("[0]"); |
| EXPECT_TRUE(focuser.request_focus_called()); |
| } |
| |
| // This test makes sure that the PlatformView correctly replies with an error |
| // response when a View.focus.request call fails. |
| TEST_F(PlatformViewTests, RequestFocusFailTest) { |
| MockPlatformViewDelegate delegate; |
| flutter::TaskRunners task_runners = |
| flutter::TaskRunners("test_runners", nullptr, nullptr, nullptr, nullptr); |
| |
| FakeFocuser focuser; |
| focuser.fail_request_focus(); |
| fidl::BindingSet<fuchsia::ui::views::Focuser> focuser_bindings; |
| auto focuser_handle = focuser_bindings.AddBinding(&focuser); |
| |
| flutter_runner::GfxPlatformView platform_view = |
| PlatformViewBuilder(delegate, std::move(task_runners)) |
| .SetFocuser(std::move(focuser_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); |
| |
| // This "Mock" ViewRef serves as the target for the RequestFocus operation. |
| auto mock_view_ref_pair = scenic::ViewRefPair::New(); |
| |
| // JSON for the message to be passed into the PlatformView. |
| std::ostringstream message; |
| message << "{" |
| << " \"method\":\"View.focus.request\"," |
| << " \"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(); |
| |
| 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()); |
| } |
| |
| // Makes sure that OnKeyEvent is dispatched as a platform message. |
| TEST_F(PlatformViewTests, OnKeyEvent) { |
| struct EventFlow { |
| fuchsia::ui::input3::KeyEvent event; |
| fuchsia::ui::input3::KeyEventStatus expected_key_event_status; |
| std::string expected_platform_message; |
| }; |
| |
| MockPlatformViewDelegate delegate; |
| flutter::TaskRunners task_runners = |
| flutter::TaskRunners("test_runners", nullptr, nullptr, nullptr, nullptr); |
| |
| fuchsia::ui::input3::KeyboardHandle keyboard_service; |
| MockKeyboard keyboard(keyboard_service.NewRequest()); |
| |
| flutter_runner::GfxPlatformView platform_view = |
| PlatformViewBuilder(delegate, std::move(task_runners)) |
| .SetKeyboard(std::move(keyboard_service)) |
| .Build(); |
| RunLoopUntilIdle(); |
| |
| std::vector<EventFlow> events; |
| // Press A. Get 'a'. |
| // The HID usage for the key A is 0x70004, or 458756. |
| events.emplace_back(EventFlow{ |
| MakeEvent(fuchsia::ui::input3::KeyEventType::PRESSED, std::nullopt, |
| fuchsia::input::Key::A), |
| fuchsia::ui::input3::KeyEventStatus::HANDLED, |
| R"({"type":"keydown","keymap":"fuchsia","hidUsage":458756,"codePoint":97,"modifiers":0})", |
| }); |
| // Release A. Get 'a' release. |
| events.emplace_back(EventFlow{ |
| MakeEvent(fuchsia::ui::input3::KeyEventType::RELEASED, std::nullopt, |
| fuchsia::input::Key::A), |
| fuchsia::ui::input3::KeyEventStatus::HANDLED, |
| R"({"type":"keyup","keymap":"fuchsia","hidUsage":458756,"codePoint":97,"modifiers":0})", |
| }); |
| // Press CAPS_LOCK. Modifier now active. |
| events.emplace_back(EventFlow{ |
| MakeEvent(fuchsia::ui::input3::KeyEventType::PRESSED, |
| fuchsia::ui::input3::Modifiers::CAPS_LOCK, |
| fuchsia::input::Key::CAPS_LOCK), |
| fuchsia::ui::input3::KeyEventStatus::HANDLED, |
| R"({"type":"keydown","keymap":"fuchsia","hidUsage":458809,"codePoint":0,"modifiers":1})", |
| }); |
| // Press A. Get 'A'. |
| events.emplace_back(EventFlow{ |
| MakeEvent(fuchsia::ui::input3::KeyEventType::PRESSED, std::nullopt, |
| fuchsia::input::Key::A), |
| fuchsia::ui::input3::KeyEventStatus::HANDLED, |
| R"({"type":"keydown","keymap":"fuchsia","hidUsage":458756,"codePoint":65,"modifiers":1})", |
| }); |
| // Release CAPS_LOCK. |
| events.emplace_back(EventFlow{ |
| MakeEvent(fuchsia::ui::input3::KeyEventType::RELEASED, |
| fuchsia::ui::input3::Modifiers::CAPS_LOCK, |
| fuchsia::input::Key::CAPS_LOCK), |
| fuchsia::ui::input3::KeyEventStatus::HANDLED, |
| R"({"type":"keyup","keymap":"fuchsia","hidUsage":458809,"codePoint":0,"modifiers":1})", |
| }); |
| // Press A again. This time get 'A'. |
| // CAPS_LOCK is latched active even if it was just released. |
| events.emplace_back(EventFlow{ |
| MakeEvent(fuchsia::ui::input3::KeyEventType::PRESSED, std::nullopt, |
| fuchsia::input::Key::A), |
| fuchsia::ui::input3::KeyEventStatus::HANDLED, |
| R"({"type":"keydown","keymap":"fuchsia","hidUsage":458756,"codePoint":65,"modifiers":1})", |
| }); |
| |
| for (const auto& event : events) { |
| fuchsia::ui::input3::KeyEvent e; |
| event.event.Clone(&e); |
| fuchsia::ui::input3::KeyEventStatus key_event_status{0u}; |
| keyboard.listener_->OnKeyEvent( |
| std::move(e), |
| [&key_event_status](fuchsia::ui::input3::KeyEventStatus status) { |
| key_event_status = status; |
| }); |
| RunLoopUntilIdle(); |
| |
| ASSERT_NOTNULL(delegate.message()); |
| EXPECT_EQ(event.expected_platform_message, |
| ToString(delegate.message()->data())); |
| EXPECT_EQ(event.expected_key_event_status, key_event_status); |
| } |
| } |
| |
| TEST_F(PlatformViewTests, OnShaderWarmup) { |
| MockPlatformViewDelegate delegate; |
| flutter::TaskRunners task_runners = |
| flutter::TaskRunners("test_runners", nullptr, nullptr, nullptr, nullptr); |
| |
| uint64_t width = 200; |
| uint64_t height = 100; |
| std::vector<std::string> shaders = {"foo.skp", "bar.skp", "baz.skp"}; |
| |
| OnShaderWarmup on_shader_warmup = |
| [&](const std::vector<std::string>& shaders_in, |
| std::function<void(uint32_t)> completion_callback, uint64_t width_in, |
| uint64_t height_in) { |
| ASSERT_EQ(shaders.size(), shaders_in.size()); |
| for (size_t i = 0; i < shaders_in.size(); i++) { |
| ASSERT_EQ(shaders[i], shaders_in[i]); |
| } |
| ASSERT_EQ(width, width_in); |
| ASSERT_EQ(height, height_in); |
| |
| completion_callback(shaders_in.size()); |
| }; |
| |
| flutter_runner::GfxPlatformView platform_view = |
| PlatformViewBuilder(delegate, std::move(task_runners)) |
| .SetShaderWarmupCallback(on_shader_warmup) |
| .Build(); |
| |
| std::ostringstream shaders_array_ostream; |
| shaders_array_ostream << "[ "; |
| for (auto it = shaders.begin(); it != shaders.end(); ++it) { |
| shaders_array_ostream << "\"" << *it << "\""; |
| if (std::next(it) != shaders.end()) { |
| shaders_array_ostream << ", "; |
| } |
| } |
| shaders_array_ostream << "]"; |
| |
| std::string shaders_array_string = shaders_array_ostream.str(); |
| |
| // Create initial view for testing. |
| std::ostringstream warmup_shaders_ostream; |
| warmup_shaders_ostream << "{" |
| << " \"method\":\"WarmupSkps\"," |
| << " \"args\":{" |
| << " \"shaders\":" << shaders_array_string << "," |
| << " \"width\":" << width << "," |
| << " \"height\":" << height << " }" |
| << "}\n"; |
| std::string warmup_shaders_string = warmup_shaders_ostream.str(); |
| |
| fml::RefPtr<TestPlatformMessageResponse> response( |
| new TestPlatformMessageResponse); |
| static_cast<flutter::PlatformView*>(&platform_view) |
| ->HandlePlatformMessage(std::make_unique<flutter::PlatformMessage>( |
| "fuchsia/shader_warmup", |
| fml::MallocMapping::Copy(warmup_shaders_string.c_str(), |
| warmup_shaders_string.size()), |
| response)); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(response->is_complete()); |
| |
| std::ostringstream expected_result_ostream; |
| expected_result_ostream << "[" << shaders.size() << "]"; |
| std::string expected_result_string = expected_result_ostream.str(); |
| EXPECT_EQ(expected_result_string, response->result_string); |
| } |
| |
| TEST_F(PlatformViewTests, TouchSourceLogicalToPhysicalConversion) { |
| constexpr std::array<std::array<float, 2>, 2> kRect = {{{0, 0}, {20, 20}}}; |
| constexpr std::array<float, 9> kIdentity = {1, 0, 0, 0, 1, 0, 0, 0, 1}; |
| constexpr fuchsia::ui::pointer::TouchInteractionId kIxnOne = { |
| .device_id = 0u, .pointer_id = 1u, .interaction_id = 2u}; |
| constexpr float valid_pixel_ratio = 2.f; |
| |
| MockPlatformViewDelegate delegate; |
| flutter::TaskRunners task_runners("test_runners", nullptr, nullptr, nullptr, |
| nullptr); |
| |
| fuchsia::ui::scenic::SessionListenerPtr session_listener; |
| FakeTouchSource touch_server; |
| fidl::BindingSet<fuchsia::ui::pointer::TouchSource> touch_bindings; |
| auto touch_handle = touch_bindings.AddBinding(&touch_server); |
| flutter_runner::GfxPlatformView platform_view = |
| PlatformViewBuilder(delegate, std::move(task_runners)) |
| .SetSessionListenerRequest(session_listener.NewRequest()) |
| .SetTouchSource(std::move(touch_handle)) |
| .Build(); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(delegate.pointer_packets().size(), 0u); |
| |
| // Set logical-to-physical metrics and logical view size |
| std::vector<fuchsia::ui::scenic::Event> scenic_events; |
| scenic_events.emplace_back(fuchsia::ui::scenic::Event::WithGfx( |
| fuchsia::ui::gfx::Event::WithMetrics(fuchsia::ui::gfx::MetricsEvent{ |
| .node_id = 0, |
| .metrics = |
| fuchsia::ui::gfx::Metrics{ |
| .scale_x = valid_pixel_ratio, |
| .scale_y = valid_pixel_ratio, |
| .scale_z = valid_pixel_ratio, |
| }, |
| }))); |
| scenic_events.emplace_back( |
| fuchsia::ui::scenic::Event::WithGfx( |
| fuchsia::ui::gfx::Event::WithViewPropertiesChanged( |
| fuchsia::ui::gfx::ViewPropertiesChangedEvent{ |
| .view_id = 0, |
| .properties = |
| fuchsia::ui::gfx::ViewProperties{ |
| .bounding_box = |
| fuchsia::ui::gfx::BoundingBox{ |
| .min = |
| fuchsia::ui::gfx::vec3{ |
| .x = 0.f, |
| .y = 0.f, |
| .z = 0.f, |
| }, |
| .max = |
| fuchsia::ui::gfx::vec3{ |
| .x = 20.f, |
| .y = 20.f, |
| .z = 20.f, |
| }, |
| }, |
| }, |
| }))); |
| session_listener->OnScenicEvent(std::move(scenic_events)); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(delegate.metrics(), flutter::ViewportMetrics(2.f, 40.f, 40.f, -1)); |
| |
| // Inject |
| std::vector<fuchsia::ui::pointer::TouchEvent> events = |
| TouchEventBuilder::New() |
| .AddTime(/* in nanoseconds */ 1111789u) |
| .AddViewParameters(kRect, kRect, kIdentity) |
| .AddSample(kIxnOne, fuchsia::ui::pointer::EventPhase::ADD, |
| {10.f, 10.f}) |
| .AddResult( |
| {.interaction = kIxnOne, |
| .status = fuchsia::ui::pointer::TouchInteractionStatus::GRANTED}) |
| .BuildAsVector(); |
| touch_server.ScheduleCallback(std::move(events)); |
| RunLoopUntilIdle(); |
| |
| // Unpack |
| std::vector<std::unique_ptr<flutter::PointerDataPacket>> packets = |
| delegate.TakePointerDataPackets(); |
| ASSERT_EQ(packets.size(), 1u); |
| std::vector<flutter::PointerData> flutter_events; |
| UnpackPointerPacket(flutter_events, std::move(packets[0])); |
| |
| // Examine phases |
| ASSERT_EQ(flutter_events.size(), 2u); |
| EXPECT_EQ(flutter_events[0].change, flutter::PointerData::Change::kAdd); |
| EXPECT_EQ(flutter_events[1].change, flutter::PointerData::Change::kDown); |
| |
| // Examine coordinates |
| // With metrics defined, observe metrics ratio applied. |
| EXPECT_EQ(flutter_events[0].physical_x, 20.f); |
| EXPECT_EQ(flutter_events[0].physical_y, 20.f); |
| EXPECT_EQ(flutter_events[1].physical_x, 20.f); |
| EXPECT_EQ(flutter_events[1].physical_y, 20.f); |
| } |
| |
| TEST_F(PlatformViewTests, DownPointerNumericNudge) { |
| constexpr float kSmallDiscrepancy = -0.00003f; |
| |
| fuchsia::ui::scenic::SessionListenerPtr session_listener; |
| MockPlatformViewDelegate delegate; |
| flutter::TaskRunners task_runners("test_runners", nullptr, nullptr, nullptr, |
| nullptr); |
| flutter_runner::GfxPlatformView platform_view = |
| PlatformViewBuilder(delegate, std::move(task_runners)) |
| .SetSessionListenerRequest(session_listener.NewRequest()) |
| .Build(); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(delegate.pointer_packets().size(), 0u); |
| |
| std::vector<fuchsia::ui::scenic::Event> events; |
| events.emplace_back( |
| fuchsia::ui::scenic::Event::WithGfx( |
| fuchsia::ui::gfx::Event::WithViewPropertiesChanged( |
| fuchsia::ui::gfx::ViewPropertiesChangedEvent{ |
| .view_id = 0, |
| .properties = |
| fuchsia::ui::gfx::ViewProperties{ |
| .bounding_box = |
| fuchsia::ui::gfx::BoundingBox{ |
| .min = |
| fuchsia::ui::gfx::vec3{ |
| .x = 0.f, |
| .y = 0.f, |
| .z = 0.f, |
| }, |
| .max = |
| fuchsia::ui::gfx::vec3{ |
| .x = 100.f, |
| .y = 100.f, |
| .z = 100.f, |
| }, |
| }, |
| }, |
| }))); |
| events.emplace_back(fuchsia::ui::scenic::Event::WithGfx( |
| fuchsia::ui::gfx::Event::WithMetrics(fuchsia::ui::gfx::MetricsEvent{ |
| .node_id = 0, |
| .metrics = |
| fuchsia::ui::gfx::Metrics{ |
| .scale_x = 1.f, |
| .scale_y = 1.f, |
| .scale_z = 1.f, |
| }, |
| }))); |
| events.emplace_back(fuchsia::ui::scenic::Event::WithInput( |
| fuchsia::ui::input::InputEvent::WithPointer( |
| fuchsia::ui::input::PointerEvent{ |
| .event_time = 1111, |
| .device_id = 2222, |
| .pointer_id = 3333, |
| .type = fuchsia::ui::input::PointerEventType::TOUCH, |
| .phase = fuchsia::ui::input::PointerEventPhase::ADD, |
| .x = 50.f, |
| .y = kSmallDiscrepancy, // floating point inaccuracy |
| .radius_major = 0.f, |
| .radius_minor = 0.f, |
| .buttons = 0u, |
| }))); |
| events.emplace_back(fuchsia::ui::scenic::Event::WithInput( |
| fuchsia::ui::input::InputEvent::WithPointer( |
| fuchsia::ui::input::PointerEvent{ |
| .event_time = 1111, |
| .device_id = 2222, |
| .pointer_id = 3333, |
| .type = fuchsia::ui::input::PointerEventType::TOUCH, |
| .phase = fuchsia::ui::input::PointerEventPhase::DOWN, |
| .x = 50.f, |
| .y = kSmallDiscrepancy, // floating point inaccuracy |
| .radius_major = 0.f, |
| .radius_minor = 0.f, |
| .buttons = 0u, |
| }))); |
| events.emplace_back(fuchsia::ui::scenic::Event::WithInput( |
| fuchsia::ui::input::InputEvent::WithPointer( |
| fuchsia::ui::input::PointerEvent{ |
| .event_time = 1111, |
| .device_id = 2222, |
| .pointer_id = 3333, |
| .type = fuchsia::ui::input::PointerEventType::TOUCH, |
| .phase = fuchsia::ui::input::PointerEventPhase::MOVE, |
| .x = 50.f, |
| .y = kSmallDiscrepancy, // floating point inaccuracy |
| .radius_major = 0.f, |
| .radius_minor = 0.f, |
| .buttons = 0u, |
| }))); |
| session_listener->OnScenicEvent(std::move(events)); |
| RunLoopUntilIdle(); |
| ASSERT_EQ(delegate.pointer_packets().size(), 3u); |
| |
| // Embedder issues pointer data in a bytestream format, PointerDataPacket. |
| // Use this handy utility to recover data as a C struct, PointerData. |
| std::vector<std::unique_ptr<flutter::PointerDataPacket>> packets = |
| delegate.TakePointerDataPackets(); |
| std::vector<flutter::PointerData> add, down, move; |
| UnpackPointerPacket(add, std::move(packets[0])); |
| UnpackPointerPacket(down, std::move(packets[1])); |
| UnpackPointerPacket(move, std::move(packets[2])); |
| |
| EXPECT_EQ(add[0].physical_x, 50.f); |
| EXPECT_EQ(add[0].physical_y, kSmallDiscrepancy); |
| EXPECT_EQ(down[0].physical_x, 50.f); |
| EXPECT_EQ(down[0].physical_y, 0.f); // clamping happened |
| EXPECT_EQ(move[0].physical_x, 50.f); |
| EXPECT_EQ(move[0].physical_y, kSmallDiscrepancy); |
| } |
| |
| } // namespace flutter_runner::testing |