[Embedder] Wire view focus event and focus request (#163930)

This wires `PlatformDispatcher.onViewFocusChange` and
`PlatformDispatches.requestViewFocusChange` through embedder API.

*If you had to change anything in the [flutter/tests] repo, include a
link to the migration guide as per the [breaking change policy].*

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I readand followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md

---------

Co-authored-by: Matthew Kosarek <matt.kosarek@canonical.com>
Co-authored-by: Loïc Sharma <737941+loic-sharma@users.noreply.github.com>
This commit is contained in:
Matej Knopp 2025-02-25 18:37:06 +01:00 committed by GitHub
parent 289993ab83
commit 940fb3c8b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 716 additions and 1 deletions

View File

@ -42555,6 +42555,8 @@ ORIGIN: ../../../flutter/lib/ui/window/pointer_data_packet.cc + ../../../flutter
ORIGIN: ../../../flutter/lib/ui/window/pointer_data_packet.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/ui/window/pointer_data_packet_converter.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/ui/window/pointer_data_packet_converter.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/ui/window/view_focus.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/ui/window/view_focus.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/ui/window/viewport_metrics.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/ui/window/viewport_metrics.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/flutter_js/src/browser_environment.js + ../../../flutter/LICENSE
@ -45471,6 +45473,8 @@ FILE: ../../../flutter/lib/ui/window/pointer_data_packet.cc
FILE: ../../../flutter/lib/ui/window/pointer_data_packet.h
FILE: ../../../flutter/lib/ui/window/pointer_data_packet_converter.cc
FILE: ../../../flutter/lib/ui/window/pointer_data_packet_converter.h
FILE: ../../../flutter/lib/ui/window/view_focus.cc
FILE: ../../../flutter/lib/ui/window/view_focus.h
FILE: ../../../flutter/lib/ui/window/viewport_metrics.cc
FILE: ../../../flutter/lib/ui/window/viewport_metrics.h
FILE: ../../../flutter/lib/web_ui/flutter_js/src/browser_environment.js

View File

@ -156,6 +156,8 @@ source_set("ui") {
"window/pointer_data_packet.h",
"window/pointer_data_packet_converter.cc",
"window/pointer_data_packet_converter.h",
"window/view_focus.cc",
"window/view_focus.h",
"window/viewport_metrics.cc",
"window/viewport_metrics.h",
]

View File

@ -107,6 +107,7 @@ typedef CanvasPath Path;
V(PlatformConfigurationNativeApi::GetRootIsolateToken) \
V(PlatformConfigurationNativeApi::RegisterBackgroundIsolate) \
V(PlatformConfigurationNativeApi::SendPortPlatformMessage) \
V(PlatformConfigurationNativeApi::RequestViewFocusChange) \
V(PlatformConfigurationNativeApi::SendChannelUpdate) \
V(PlatformConfigurationNativeApi::GetScaledFontSize) \
V(PlatformIsolateNativeApi::IsRunningOnPlatformThread) \

View File

@ -58,6 +58,16 @@ void _removeView(int viewId) {
PlatformDispatcher.instance._removeView(viewId);
}
@pragma('vm:entry-point')
void _sendViewFocusEvent(int viewId, int viewFocusState, int viewFocusDirection) {
final ViewFocusEvent viewFocusEvent = ViewFocusEvent(
viewId: viewId,
state: ViewFocusState.values[viewFocusState],
direction: ViewFocusDirection.values[viewFocusDirection],
);
PlatformDispatcher.instance._sendViewFocusEvent(viewFocusEvent);
}
@pragma('vm:entry-point')
void _updateDisplays(
List<int> ids,

View File

@ -301,6 +301,10 @@ class PlatformDispatcher {
_invoke(onMetricsChanged, _onMetricsChangedZone);
}
void _sendViewFocusEvent(ViewFocusEvent event) {
_invoke1<ViewFocusEvent>(onViewFocusChange, _onViewFocusChangeZone, event);
}
// Called from the engine, via hooks.dart.
//
// Updates the available displays.
@ -384,9 +388,14 @@ class PlatformDispatcher {
required ViewFocusState state,
required ViewFocusDirection direction,
}) {
// TODO(tugorez): implement this method. At the moment will be a no op call.
_requestViewFocusChange(viewId, state.index, direction.index);
}
@Native<Void Function(Int64, Int64, Int64)>(
symbol: 'PlatformConfigurationNativeApi::RequestViewFocusChange',
)
external static void _requestViewFocusChange(int viewId, int state, int direction);
/// A callback invoked when any view begins a frame.
///
/// A callback that is invoked to notify the application that it is an

View File

@ -46,6 +46,9 @@ void PlatformConfiguration::DidCreateIsolate() {
Dart_GetField(library, tonic::ToDart("_addView")));
remove_view_.Set(tonic::DartState::Current(),
Dart_GetField(library, tonic::ToDart("_removeView")));
send_view_focus_event_.Set(
tonic::DartState::Current(),
Dart_GetField(library, tonic::ToDart("_sendViewFocusEvent")));
update_window_metrics_.Set(
tonic::DartState::Current(),
Dart_GetField(library, tonic::ToDart("_updateWindowMetrics")));
@ -149,6 +152,22 @@ bool PlatformConfiguration::RemoveView(int64_t view_id) {
return true;
}
bool PlatformConfiguration::SendFocusEvent(const ViewFocusEvent& event) {
std::shared_ptr<tonic::DartState> dart_state =
remove_view_.dart_state().lock();
if (!dart_state) {
return false;
}
tonic::DartState::Scope scope(dart_state);
tonic::CheckAndHandleError(tonic::DartInvoke(
send_view_focus_event_.Get(), {
tonic::ToDart(event.view_id()),
tonic::ToDart(event.state()),
tonic::ToDart(event.direction()),
}));
return true;
}
bool PlatformConfiguration::UpdateViewMetrics(
int64_t view_id,
const ViewportMetrics& view_metrics) {
@ -552,6 +571,17 @@ Dart_Handle PlatformConfigurationNativeApi::SendPortPlatformMessage(
return HandlePlatformMessage(dart_state, name, data_handle, response);
}
void PlatformConfigurationNativeApi::RequestViewFocusChange(int64_t view_id,
int64_t state,
int64_t direction) {
ViewFocusChangeRequest request{view_id, //
static_cast<ViewFocusState>(state),
static_cast<ViewFocusDirection>(direction)};
UIDartState* dart_state = UIDartState::Current();
dart_state->platform_configuration()->client()->RequestViewFocusChange(
request);
}
void PlatformConfigurationNativeApi::RespondToPlatformMessage(
int response_id,
const tonic::DartByteData& data) {

View File

@ -16,6 +16,7 @@
#include "flutter/lib/ui/semantics/semantics_update.h"
#include "flutter/lib/ui/window/platform_message_response.h"
#include "flutter/lib/ui/window/pointer_data_packet.h"
#include "flutter/lib/ui/window/view_focus.h"
#include "flutter/lib/ui/window/viewport_metrics.h"
#include "flutter/shell/common/display.h"
#include "fml/macros.h"
@ -257,6 +258,14 @@ class PlatformConfigurationClient {
virtual double GetScaledFontSize(double unscaled_font_size,
int configuration_id) const = 0;
//--------------------------------------------------------------------------
/// @brief Notifies the client that the Flutter view focus state has
/// changed and the platform view should be updated.
///
/// @param[in] request The request to change the focus state of the view.
virtual void RequestViewFocusChange(
const ViewFocusChangeRequest& request) = 0;
virtual std::shared_ptr<PlatformIsolateManager>
GetPlatformIsolateManager() = 0;
@ -338,6 +347,15 @@ class PlatformConfiguration final {
///
bool RemoveView(int64_t view_id);
//----------------------------------------------------------------------------
/// @brief Notify the isolate that the focus state of a native view has
/// changed.
///
/// @param[in] event The focus event describing the change.
///
/// @return Whether the focus event was sent.
bool SendFocusEvent(const ViewFocusEvent& event);
//----------------------------------------------------------------------------
/// @brief Update the view metrics for the specified view.
///
@ -529,6 +547,7 @@ class PlatformConfiguration final {
tonic::DartPersistentValue on_error_;
tonic::DartPersistentValue add_view_;
tonic::DartPersistentValue remove_view_;
tonic::DartPersistentValue send_view_focus_event_;
tonic::DartPersistentValue update_window_metrics_;
tonic::DartPersistentValue update_displays_;
tonic::DartPersistentValue update_locales_;
@ -617,6 +636,10 @@ class PlatformConfigurationNativeApi {
static void SendChannelUpdate(const std::string& name, bool listening);
static void RequestViewFocusChange(int64_t view_id,
int64_t state,
int64_t direction);
//--------------------------------------------------------------------------
/// @brief Requests the Dart VM to adjusts the GC heuristics based on
/// the requested `performance_mode`. Returns the old performance

View File

@ -0,0 +1,23 @@
// 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 "flutter/lib/ui/window/view_focus.h"
namespace flutter {
ViewFocusChangeRequest::ViewFocusChangeRequest(int64_t view_id,
ViewFocusState state,
ViewFocusDirection direction)
: view_id_(view_id), state_(state), direction_(direction) {}
int64_t ViewFocusChangeRequest::view_id() const {
return view_id_;
}
ViewFocusState ViewFocusChangeRequest::state() const {
return state_;
}
ViewFocusDirection ViewFocusChangeRequest::direction() const {
return direction_;
}
} // namespace flutter

View File

@ -0,0 +1,69 @@
// 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_LIB_UI_WINDOW_VIEW_FOCUS_H_
#define FLUTTER_LIB_UI_WINDOW_VIEW_FOCUS_H_
#include <cstdint>
namespace flutter {
// Focus state of a View.
// Must match ViewFocusState in ui/platform_dispatcher.dart.
enum class ViewFocusState : int64_t {
kUnfocused = 0,
kFocused,
};
// Represents the direction of which the focus transitioned over
// a FlutterView.
// Must match ViewFocusDirection in ui/platform_dispatcher.dart.
enum class ViewFocusDirection : int64_t {
kUndefined = 0,
kForward,
kBackward,
};
// Event sent by the embedder to the engine indicating that native view focus
// state has changed.
class ViewFocusEvent {
public:
ViewFocusEvent(int64_t view_id,
ViewFocusState state,
ViewFocusDirection direction)
: view_id_(view_id), state_(state), direction_(direction) {}
int64_t view_id() const { return view_id_; }
ViewFocusState state() const { return state_; }
ViewFocusDirection direction() const { return direction_; }
private:
int64_t view_id_;
ViewFocusState state_;
ViewFocusDirection direction_;
};
// Request sent by the engine to the embedder indicating that the FlutterView
// focus state has changed and the native view should be updated.
class ViewFocusChangeRequest {
public:
ViewFocusChangeRequest(int64_t view_id,
ViewFocusState state,
ViewFocusDirection direction);
int64_t view_id() const;
ViewFocusState state() const;
ViewFocusDirection direction() const;
private:
ViewFocusChangeRequest() = delete;
int64_t view_id_ = 0;
ViewFocusState state_ = ViewFocusState::kUnfocused;
ViewFocusDirection direction_ = ViewFocusDirection::kUndefined;
};
} // namespace flutter
#endif // FLUTTER_LIB_UI_WINDOW_VIEW_FOCUS_H_

View File

@ -735,6 +735,7 @@ class FakePlatformConfigurationClient : public PlatformConfigurationClient {
int configuration_id) const override {
return 0;
}
void RequestViewFocusChange(const ViewFocusChangeRequest& request) override {}
};
TEST_F(DartIsolateTest, PlatformIsolateCreationAndShutdown) {

View File

@ -209,6 +209,14 @@ bool RuntimeController::RemoveView(int64_t view_id) {
return platform_configuration->RemoveView(view_id);
}
bool RuntimeController::SendViewFocusEvent(const ViewFocusEvent& event) {
auto* platform_configuration = GetPlatformConfigurationIfAvailable();
if (!platform_configuration) {
return false;
}
return platform_configuration->SendFocusEvent(event);
}
bool RuntimeController::ViewExists(int64_t view_id) const {
return platform_data_.viewport_metrics_for_views.count(view_id) != 0;
}
@ -649,6 +657,11 @@ double RuntimeController::GetScaledFontSize(double unscaled_font_size,
return client_.GetScaledFontSize(unscaled_font_size, configuration_id);
}
void RuntimeController::RequestViewFocusChange(
const ViewFocusChangeRequest& request) {
client_.RequestViewFocusChange(request);
}
void RuntimeController::ShutdownPlatformIsolates() {
platform_isolate_manager_->ShutdownPlatformIsolates();
}

View File

@ -20,6 +20,7 @@
#include "flutter/lib/ui/window/platform_configuration.h"
#include "flutter/lib/ui/window/pointer_data_packet.h"
#include "flutter/lib/ui/window/pointer_data_packet_converter.h"
#include "flutter/lib/ui/window/view_focus.h"
#include "flutter/runtime/dart_vm.h"
#include "flutter/runtime/platform_data.h"
#include "flutter/runtime/platform_isolate_manager.h"
@ -224,6 +225,13 @@ class RuntimeController : public PlatformConfigurationClient,
/// cancelled and the return value is always false.
bool RemoveView(int64_t view_id);
//----------------------------------------------------------------------------
/// @brief Notify the isolate that the focus state of a native view has
/// changed.
///
/// @param[in] event The focus event describing the change.
bool SendViewFocusEvent(const ViewFocusEvent& event);
//----------------------------------------------------------------------------
/// @brief Forward the specified viewport metrics to the running isolate.
/// If the isolate is not running, these metrics will be saved and
@ -785,6 +793,9 @@ class RuntimeController : public PlatformConfigurationClient,
double GetScaledFontSize(double unscaled_font_size,
int configuration_id) const override;
// |PlatformConfigurationClient|
void RequestViewFocusChange(const ViewFocusChangeRequest& request) override;
FML_DISALLOW_COPY_AND_ASSIGN(RuntimeController);
};

View File

@ -14,6 +14,7 @@
#include "flutter/lib/ui/semantics/semantics_node.h"
#include "flutter/lib/ui/text/font_collection.h"
#include "flutter/lib/ui/window/platform_message.h"
#include "flutter/lib/ui/window/view_focus.h"
#include "flutter/shell/common/platform_message_handler.h"
#include "third_party/dart/runtime/include/dart_api.h"
@ -62,6 +63,9 @@ class RuntimeDelegate {
virtual double GetScaledFontSize(double unscaled_font_size,
int configuration_id) const = 0;
virtual void RequestViewFocusChange(
const ViewFocusChangeRequest& request) = 0;
protected:
virtual ~RuntimeDelegate();
};

View File

@ -312,6 +312,10 @@ bool Engine::RemoveView(int64_t view_id) {
return runtime_controller_->RemoveView(view_id);
}
bool Engine::SendViewFocusEvent(const ViewFocusEvent& event) {
return runtime_controller_->SendViewFocusEvent(event);
}
void Engine::SetViewportMetrics(int64_t view_id,
const ViewportMetrics& metrics) {
runtime_controller_->SetViewportMetrics(view_id, metrics);
@ -522,6 +526,10 @@ double Engine::GetScaledFontSize(double unscaled_font_size,
return delegate_.GetScaledFontSize(unscaled_font_size, configuration_id);
}
void Engine::RequestViewFocusChange(const ViewFocusChangeRequest& request) {
delegate_.RequestViewFocusChange(request);
}
void Engine::SetNeedsReportTimings(bool needs_reporting) {
delegate_.SetNeedsReportTimings(needs_reporting);
}

View File

@ -323,6 +323,14 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate {
///
virtual double GetScaledFontSize(double unscaled_font_size,
int configuration_id) const = 0;
//--------------------------------------------------------------------------
/// @brief Notifies the client that the Flutter view focus state has
/// changed and the platform view should be updated.
///
/// @param[in] request The request to change the focus state of the view.
virtual void RequestViewFocusChange(
const ViewFocusChangeRequest& request) = 0;
};
//----------------------------------------------------------------------------
@ -747,6 +755,13 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate {
///
bool RemoveView(int64_t view_id);
//----------------------------------------------------------------------------
/// @brief Notify the Flutter application that the focus state of a
/// native view has changed.
///
/// @param[in] event The focus event describing the change.
bool SendViewFocusEvent(const ViewFocusEvent& event);
//----------------------------------------------------------------------------
/// @brief Updates the viewport metrics for a view. The viewport metrics
/// detail the size of the rendering viewport in texels as well as
@ -1024,6 +1039,9 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate {
double GetScaledFontSize(double unscaled_font_size,
int configuration_id) const override;
// |RuntimeDelegate|
void RequestViewFocusChange(const ViewFocusChangeRequest& request) override;
void SetNeedsReportTimings(bool value) override;
bool HandleLifecyclePlatformMessage(PlatformMessage* message);

View File

@ -82,6 +82,10 @@ class MockDelegate : public Engine::Delegate {
GetScaledFontSize,
(double font_size, int configuration_id),
(const, override));
MOCK_METHOD(void,
RequestViewFocusChange,
(const ViewFocusChangeRequest&),
(override));
};
class MockAnimatorDelegate : public Animator::Delegate {

View File

@ -90,6 +90,10 @@ class MockDelegate : public Engine::Delegate {
GetScaledFontSize,
(double font_size, int configuration_id),
(const, override));
MOCK_METHOD(void,
RequestViewFocusChange,
(const ViewFocusChangeRequest&),
(override));
};
class MockResponse : public PlatformMessageResponse {
@ -137,6 +141,10 @@ class MockRuntimeDelegate : public RuntimeDelegate {
GetScaledFontSize,
(double font_size, int configuration_id),
(const, override));
MOCK_METHOD(void,
RequestViewFocusChange,
(const ViewFocusChangeRequest&),
(override));
};
class MockRuntimeController : public RuntimeController {

View File

@ -632,3 +632,11 @@ void testDispatchEvents() {
notifyNative();
};
}
@pragma('vm:entry-point')
void testSendViewFocusEvent() {
PlatformDispatcher.instance.onViewFocusChange = (ViewFocusEvent event) {
notifyMessage('${event.viewId} ${event.state} ${event.direction}');
};
notifyNative();
}

View File

@ -97,6 +97,10 @@ void PlatformView::RemoveView(int64_t view_id, RemoveViewCallback callback) {
delegate_.OnPlatformViewRemoveView(view_id, std::move(callback));
}
void PlatformView::SendViewFocusEvent(const ViewFocusEvent& event) {
delegate_.OnPlatformViewSendViewFocusEvent(event);
}
sk_sp<GrDirectContext> PlatformView::CreateResourceContext() const {
FML_DLOG(WARNING) << "This platform does not set up the resource "
"context on the IO thread for async texture uploads.";
@ -219,4 +223,9 @@ double PlatformView::GetScaledFontSize(double unscaled_font_size,
return -1;
}
void PlatformView::RequestViewFocusChange(
const ViewFocusChangeRequest& request) {
// No-op by default.
}
} // namespace flutter

View File

@ -121,6 +121,12 @@ class PlatformView {
virtual void OnPlatformViewRemoveView(int64_t view_id,
RemoveViewCallback callback) = 0;
/// @brief Notify the delegate that platform view focus state has changed.
///
/// @param[in] event The focus event describing the change.
virtual void OnPlatformViewSendViewFocusEvent(
const ViewFocusEvent& event) = 0;
//--------------------------------------------------------------------------
/// @brief Notifies the delegate that the specified callback needs to
/// be invoked after the rasterizer is done rendering the next
@ -605,6 +611,8 @@ class PlatformView {
///
void RemoveView(int64_t view_id, RemoveViewCallback callback);
void SendViewFocusEvent(const ViewFocusEvent& event);
//----------------------------------------------------------------------------
/// @brief Used by the shell to obtain a Skia GPU context that is capable
/// of operating on the IO thread. The context must be in the same
@ -954,6 +962,15 @@ class PlatformView {
virtual double GetScaledFontSize(double unscaled_font_size,
int configuration_id) const;
//--------------------------------------------------------------------------
/// @brief Notifies the client that the Flutter view focus state has
/// changed and the platform view should be updated.
///
/// Called on platform thread.
///
/// @param[in] request The request to change the focus state of the view.
virtual void RequestViewFocusChange(const ViewFocusChangeRequest& request);
protected:
// This is the only method called on the raster task runner.
virtual std::unique_ptr<Surface> CreateRenderingSurface();

View File

@ -1550,6 +1550,18 @@ double Shell::GetScaledFontSize(double unscaled_font_size,
configuration_id);
}
void Shell::RequestViewFocusChange(const ViewFocusChangeRequest& request) {
FML_DCHECK(is_set_up_);
fml::TaskRunner::RunNowOrPostTask(
task_runners_.GetPlatformTaskRunner(),
[view = platform_view_->GetWeakPtr(), request] {
if (view) {
view->RequestViewFocusChange(request);
}
});
}
void Shell::ReportTimings() {
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread());
@ -2102,6 +2114,20 @@ void Shell::OnPlatformViewRemoveView(int64_t view_id,
});
}
void Shell::OnPlatformViewSendViewFocusEvent(const ViewFocusEvent& event) {
TRACE_EVENT0("flutter", "Shell:: OnPlatformViewSendViewFocusEvent");
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
task_runners_.GetUITaskRunner()->RunNowOrPostTask(
task_runners_.GetUITaskRunner(),
[engine = engine_->GetWeakPtr(), event = event] {
if (engine) {
engine->SendViewFocusEvent(event);
}
});
}
Rasterizer::Screenshot Shell::Screenshot(
Rasterizer::ScreenshotType screenshot_type,
bool base64_encode) {

View File

@ -588,6 +588,9 @@ class Shell final : public PlatformView::Delegate,
void OnPlatformViewRemoveView(int64_t view_id,
RemoveViewCallback callback) override;
// |PlatformView::Delegate|
void OnPlatformViewSendViewFocusEvent(const ViewFocusEvent& event) override;
// |PlatformView::Delegate|
void OnPlatformViewSetViewportMetrics(
int64_t view_id,
@ -702,6 +705,9 @@ class Shell final : public PlatformView::Delegate,
double GetScaledFontSize(double unscaled_font_size,
int configuration_id) const override;
// |Engine::Delegate|
void RequestViewFocusChange(const ViewFocusChangeRequest& request) override;
// |Rasterizer::Delegate|
void OnFrameRasterized(const FrameTiming&) override;

View File

@ -116,6 +116,11 @@ class MockPlatformViewDelegate : public PlatformView::Delegate {
(int64_t view_id, RemoveViewCallback callback),
(override));
MOCK_METHOD(void,
OnPlatformViewSendViewFocusEvent,
(const ViewFocusEvent& event),
(override));
MOCK_METHOD(void,
OnPlatformViewSetNextFrameCallback,
(const fml::closure& closure),
@ -4952,6 +4957,56 @@ TEST_F(ShellTest, WillLogWarningWhenImpellerIsOptedOut) {
DestroyShell(std::move(shell), task_runners);
}
TEST_F(ShellTest, SendViewFocusEvent) {
Settings settings = CreateSettingsForFixture();
TaskRunners task_runners = GetTaskRunnersForFixture();
fml::AutoResetWaitableEvent latch;
std::string last_event;
AddNativeCallback(
"NotifyNative",
CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { latch.Signal(); }));
AddNativeCallback("NotifyMessage",
CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
const auto message_from_dart =
tonic::DartConverter<std::string>::FromDart(
Dart_GetNativeArgument(args, 0));
last_event = message_from_dart;
latch.Signal();
}));
fml::AutoResetWaitableEvent check_latch;
std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
ASSERT_TRUE(shell->IsSetup());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("testSendViewFocusEvent");
RunEngine(shell.get(), std::move(configuration));
latch.Wait();
latch.Reset();
PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() {
shell->GetPlatformView()->SendViewFocusEvent(ViewFocusEvent(
1, ViewFocusState::kFocused, ViewFocusDirection::kUndefined));
});
latch.Wait();
ASSERT_EQ(last_event,
"1 ViewFocusState.focused ViewFocusDirection.undefined");
latch.Reset();
PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() {
shell->GetPlatformView()->SendViewFocusEvent(ViewFocusEvent(
2, ViewFocusState::kUnfocused, ViewFocusDirection::kBackward));
});
latch.Wait();
ASSERT_EQ(last_event,
"2 ViewFocusState.unfocused ViewFocusDirection.backward");
DestroyShell(std::move(shell), task_runners);
}
} // namespace testing
} // namespace flutter

View File

@ -28,6 +28,7 @@ class FakeDelegate : public PlatformView::Delegate {
const ViewportMetrics& viewport_metrics,
AddViewCallback callback) override {}
void OnPlatformViewRemoveView(int64_t view_id, RemoveViewCallback callback) override {}
void OnPlatformViewSendViewFocusEvent(const ViewFocusEvent& event) override {}
void OnPlatformViewSetNextFrameCallback(const fml::closure& closure) override {}
void OnPlatformViewSetViewportMetrics(int64_t view_id, const ViewportMetrics& metrics) override {}
const flutter::Settings& OnPlatformViewGetSettings() const override { return settings_; }

View File

@ -263,6 +263,7 @@ class FlutterPlatformViewsTestMockPlatformViewDelegate : public PlatformView::De
const ViewportMetrics& viewport_metrics,
AddViewCallback callback) override {}
void OnPlatformViewRemoveView(int64_t view_id, RemoveViewCallback callback) override {}
void OnPlatformViewSendViewFocusEvent(const ViewFocusEvent& event) override {};
void OnPlatformViewSetNextFrameCallback(const fml::closure& closure) override {}
void OnPlatformViewSetViewportMetrics(int64_t view_id, const ViewportMetrics& metrics) override {}
const flutter::Settings& OnPlatformViewGetSettings() const override { return settings_; }

View File

@ -75,6 +75,7 @@ class MockDelegate : public PlatformView::Delegate {
const ViewportMetrics& viewport_metrics,
AddViewCallback callback) override {}
void OnPlatformViewRemoveView(int64_t view_id, RemoveViewCallback callback) override {}
void OnPlatformViewSendViewFocusEvent(const ViewFocusEvent& event) override {};
void OnPlatformViewSetNextFrameCallback(const fml::closure& closure) override {}
void OnPlatformViewSetViewportMetrics(int64_t view_id, const ViewportMetrics& metrics) override {}
const flutter::Settings& OnPlatformViewGetSettings() const override { return settings_; }

View File

@ -2226,6 +2226,24 @@ FlutterEngineResult FlutterEngineInitialize(size_t version,
};
}
flutter::PlatformViewEmbedder::ViewFocusChangeRequestCallback
view_focus_change_request_callback = nullptr;
if (SAFE_ACCESS(args, view_focus_change_request_callback, nullptr) !=
nullptr) {
view_focus_change_request_callback =
[ptr = args->view_focus_change_request_callback,
user_data](const flutter::ViewFocusChangeRequest& request) {
FlutterViewFocusChangeRequest embedder_request{
.struct_size = sizeof(FlutterViewFocusChangeRequest),
.view_id = request.view_id(),
.state = static_cast<FlutterViewFocusState>(request.state()),
.direction =
static_cast<FlutterViewFocusDirection>(request.direction()),
};
ptr(&embedder_request, user_data);
};
}
auto external_view_embedder_result = InferExternalViewEmbedderFromArgs(
SAFE_ACCESS(args, compositor, nullptr), settings.enable_impeller);
if (external_view_embedder_result.second) {
@ -2241,6 +2259,7 @@ FlutterEngineResult FlutterEngineInitialize(size_t version,
compute_platform_resolved_locale_callback, //
on_pre_engine_restart_callback, //
channel_update_callback, //
view_focus_change_request_callback, //
};
auto on_create_platform_view = InferPlatformViewCreationCallback(
@ -2556,6 +2575,38 @@ FlutterEngineResult FlutterEngineRemoveView(FLUTTER_API_SYMBOL(FlutterEngine)
return kSuccess;
}
FlutterEngineResult FlutterEngineSendViewFocusEvent(
FLUTTER_API_SYMBOL(FlutterEngine) engine,
const FlutterViewFocusEvent* event) {
if (!engine) {
return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid.");
}
if (!event) {
return LOG_EMBEDDER_ERROR(kInvalidArguments,
"View focus event must not be null.");
}
// The engine must be running to focus a view.
auto embedder_engine = reinterpret_cast<flutter::EmbedderEngine*>(engine);
if (!embedder_engine->IsValid()) {
return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid.");
}
if (!STRUCT_HAS_MEMBER(event, direction)) {
return LOG_EMBEDDER_ERROR(kInvalidArguments,
"The event struct has invalid size.");
}
flutter::ViewFocusEvent flutter_event(
event->view_id, //
static_cast<flutter::ViewFocusState>(event->state),
static_cast<flutter::ViewFocusDirection>(event->direction));
embedder_engine->GetShell().GetPlatformView()->SendViewFocusEvent(
flutter_event);
return kSuccess;
}
FLUTTER_EXPORT
FlutterEngineResult FlutterEngineDeinitialize(FLUTTER_API_SYMBOL(FlutterEngine)
engine) {
@ -3657,6 +3708,7 @@ FlutterEngineResult FlutterEngineGetProcAddresses(
SET_PROC(SetNextFrameCallback, FlutterEngineSetNextFrameCallback);
SET_PROC(AddView, FlutterEngineAddView);
SET_PROC(RemoveView, FlutterEngineRemoveView);
SET_PROC(SendViewFocusEvent, FlutterEngineSendViewFocusEvent);
#undef SET_PROC
return kSuccess;

View File

@ -1060,6 +1060,74 @@ typedef struct {
FlutterRemoveViewCallback remove_view_callback;
} FlutterRemoveViewInfo;
/// Represents the direction in which the focus transitioned across
/// [FlutterView]s.
typedef enum {
/// Indicates the focus transition did not have a direction.
///
/// This is typically associated with focus being programmatically requested
/// or when focus is lost.
kUndefined,
/// Indicates the focus transition was performed in a forward direction.
///
/// This is typically result of the user pressing tab.
kForward,
/// Indicates the focus transition was performed in a backward direction.
///
/// This is typically result of the user pressing shift + tab.
kBackward,
} FlutterViewFocusDirection;
/// Represents the focus state of a given [FlutterView].
typedef enum {
/// Specifies that a view does not have platform focus.
kUnfocused,
/// Specifies that a view has platform focus.
kFocused,
} FlutterViewFocusState;
/// A view focus event is sent to the engine by the embedder when a native view
/// focus state has changed.
///
/// Passed through FlutterEngineSendViewFocusEvent.
typedef struct {
/// The size of this struct.
/// Must be sizeof(FlutterViewFocusEvent).
size_t struct_size;
/// The identifier of the view that received the focus event.
FlutterViewId view_id;
/// The focus state of the view.
FlutterViewFocusState state;
/// The direction in which the focus transitioned across [FlutterView]s.
FlutterViewFocusDirection direction;
} FlutterViewFocusEvent;
/// A FlutterViewFocusChangeRequest is sent by the engine to the embedder when
/// when a FlutterView focus state has changed and native view focus
/// needs to be updated.
///
/// Received in FlutterProjectArgs.view_focus_change_request_callback.
typedef struct {
/// The size of this struct.
/// Must be sizeof(FlutterViewFocusChangeRequest).
size_t struct_size;
/// The identifier of the view that received the focus event.
FlutterViewId view_id;
/// The focus state of the view.
FlutterViewFocusState state;
/// The direction in which the focus transitioned across [FlutterView]s.
FlutterViewFocusDirection direction;
} FlutterViewFocusChangeRequest;
/// The phase of the pointer event.
typedef enum {
kCancel,
@ -1644,6 +1712,10 @@ typedef void (*FlutterChannelUpdateCallback)(
const FlutterChannelUpdate* /* channel update */,
void* /* user data */);
typedef void (*FlutterViewFocusChangeRequestCallback)(
const FlutterViewFocusChangeRequest* /* request */,
void* /* user data */);
typedef struct _FlutterTaskRunner* FlutterTaskRunner;
typedef struct {
@ -2553,6 +2625,12 @@ typedef struct {
/// being registered on the framework side. The callback is invoked from
/// a task posted to the platform thread.
FlutterChannelUpdateCallback channel_update_callback;
/// The callback invoked by the engine when FlutterView focus state has
/// changed. The embedder can use this callback to request focus change for
/// the native view. The callback is invoked from a task posted to the
/// platform thread.
FlutterViewFocusChangeRequestCallback view_focus_change_request_callback;
} FlutterProjectArgs;
#ifndef FLUTTER_ENGINE_NO_PROTOTYPES
@ -2757,6 +2835,16 @@ FlutterEngineResult FlutterEngineRemoveView(FLUTTER_API_SYMBOL(FlutterEngine)
engine,
const FlutterRemoveViewInfo* info);
//------------------------------------------------------------------------------
/// @brief Notifies the engine that platform view focus state has changed.
///
/// @param[in] engine A running engine instance
/// @param[in] event The focus event data describing the change.
FLUTTER_EXPORT
FlutterEngineResult FlutterEngineSendViewFocusEvent(
FLUTTER_API_SYMBOL(FlutterEngine) engine,
const FlutterViewFocusEvent* event);
FLUTTER_EXPORT
FlutterEngineResult FlutterEngineSendWindowMetricsEvent(
FLUTTER_API_SYMBOL(FlutterEngine) engine,
@ -3433,6 +3521,9 @@ typedef FlutterEngineResult (*FlutterEngineAddViewFnPtr)(
typedef FlutterEngineResult (*FlutterEngineRemoveViewFnPtr)(
FLUTTER_API_SYMBOL(FlutterEngine) engine,
const FlutterRemoveViewInfo* info);
typedef FlutterEngineResult (*FlutterEngineSendViewFocusEventFnPtr)(
FLUTTER_API_SYMBOL(FlutterEngine) engine,
const FlutterViewFocusEvent* event);
/// Function-pointer-based versions of the APIs above.
typedef struct {
@ -3481,6 +3572,7 @@ typedef struct {
FlutterEngineSetNextFrameCallbackFnPtr SetNextFrameCallback;
FlutterEngineAddViewFnPtr AddView;
FlutterEngineRemoveViewFnPtr RemoveView;
FlutterEngineSendViewFocusEventFnPtr SendViewFocusEvent;
} FlutterEngineProcTable;
//------------------------------------------------------------------------------

View File

@ -1600,3 +1600,30 @@ Future<void> render_impeller_image_snapshot_test() async {
final bool result = (pixel & 0xFF) == color.alpha && ((pixel >> 8) & 0xFF) == color.blue;
notifyBoolValue(result);
}
@pragma('vm:entry-point')
void testSendViewFocusEvent() {
PlatformDispatcher.instance.onViewFocusChange = (ViewFocusEvent event) {
notifyStringValue('${event.viewId} ${event.state} ${event.direction}');
};
signalNativeTest();
}
@pragma('vm:entry-point')
void testSendViewFocusChangeRequest() {
PlatformDispatcher.instance.requestViewFocusChange(
viewId: 1,
state: ViewFocusState.unfocused,
direction: ViewFocusDirection.undefined,
);
PlatformDispatcher.instance.requestViewFocusChange(
viewId: 2,
state: ViewFocusState.focused,
direction: ViewFocusDirection.forward,
);
PlatformDispatcher.instance.requestViewFocusChange(
viewId: 3,
state: ViewFocusState.focused,
direction: ViewFocusDirection.backward,
);
}

View File

@ -207,6 +207,13 @@ void PlatformViewEmbedder::SendChannelUpdate(const std::string& name,
}
}
void PlatformViewEmbedder::RequestViewFocusChange(
const ViewFocusChangeRequest& request) {
if (platform_dispatch_table_.view_focus_change_request_callback != nullptr) {
platform_dispatch_table_.view_focus_change_request_callback(request);
}
}
std::shared_ptr<PlatformMessageHandler>
PlatformViewEmbedder::GetPlatformMessageHandler() const {
return platform_message_handler_;

View File

@ -45,6 +45,8 @@ class PlatformViewEmbedder final : public PlatformView {
const std::vector<std::string>& supported_locale_data)>;
using OnPreEngineRestartCallback = std::function<void()>;
using ChanneUpdateCallback = std::function<void(const std::string&, bool)>;
using ViewFocusChangeRequestCallback =
std::function<void(const ViewFocusChangeRequest&)>;
struct PlatformDispatchTable {
UpdateSemanticsCallback update_semantics_callback; // optional
@ -55,6 +57,8 @@ class PlatformViewEmbedder final : public PlatformView {
compute_platform_resolved_locale_callback;
OnPreEngineRestartCallback on_pre_engine_restart_callback; // optional
ChanneUpdateCallback on_channel_update; // optional
ViewFocusChangeRequestCallback
view_focus_change_request_callback; // optional
};
// Create a platform view that sets up a software rasterizer.
@ -142,6 +146,9 @@ class PlatformViewEmbedder final : public PlatformView {
// |PlatformView|
void SendChannelUpdate(const std::string& name, bool listening) override;
// |PlatformView|
void RequestViewFocusChange(const ViewFocusChangeRequest& request) override;
FML_DISALLOW_COPY_AND_ASSIGN(PlatformViewEmbedder);
};

View File

@ -32,6 +32,10 @@ class MockDelegate : public PlatformView::Delegate {
OnPlatformViewRemoveView,
(int64_t view_id, RemoveViewCallback callback),
(override));
MOCK_METHOD(void,
OnPlatformViewSendViewFocusEvent,
(const ViewFocusEvent& event),
(override));
MOCK_METHOD(void,
OnPlatformViewSetNextFrameCallback,
(const fml::closure& closure),

View File

@ -37,6 +37,7 @@ EmbedderConfigBuilder::EmbedderConfigBuilder(
SetLogMessageCallbackHook();
SetLocalizationCallbackHooks();
SetChannelUpdateCallbackHook();
SetViewFocusChangeRequestHook();
AddCommandLineArgument("--disable-vm-service");
if (preference == InitializationPreference::kSnapshotsInitialize ||
@ -112,6 +113,11 @@ void EmbedderConfigBuilder::SetChannelUpdateCallbackHook() {
context_.GetChannelUpdateCallbackHook();
}
void EmbedderConfigBuilder::SetViewFocusChangeRequestHook() {
project_args_.view_focus_change_request_callback =
context_.GetViewFocusChangeRequestCallbackHook();
}
void EmbedderConfigBuilder::SetLogTag(std::string tag) {
log_tag_ = std::move(tag);
project_args_.log_tag = log_tag_.c_str();
@ -194,6 +200,11 @@ void EmbedderConfigBuilder::SetPlatformMessageCallback(
context_.SetPlatformMessageCallback(callback);
}
void EmbedderConfigBuilder::SetViewFocusChangeRequestCallback(
const std::function<void(const FlutterViewFocusChangeRequest*)>& callback) {
context_.SetViewFocusChangeRequestCallback(callback);
}
void EmbedderConfigBuilder::SetCompositor(bool avoid_backing_store_cache,
bool use_present_layers_callback) {
context_.SetupCompositor();

View File

@ -59,6 +59,8 @@ class EmbedderConfigBuilder {
void SetChannelUpdateCallbackHook();
void SetViewFocusChangeRequestHook();
// Used to set a custom log tag.
void SetLogTag(std::string tag);
@ -81,6 +83,10 @@ class EmbedderConfigBuilder {
void SetPlatformMessageCallback(
const std::function<void(const FlutterPlatformMessage*)>& callback);
void SetViewFocusChangeRequestCallback(
const std::function<void(const FlutterViewFocusChangeRequest*)>&
callback);
void SetCompositor(bool avoid_backing_store_cache = false,
bool use_present_layers_callback = false);
@ -101,6 +107,9 @@ class EmbedderConfigBuilder {
// text context vis `SetVsyncCallback`.
void SetupVsyncCallback();
void SetViewFocusChangeRequestCallback(
const FlutterViewFocusChangeRequestCallback& callback);
private:
EmbedderTestContext& context_;
FlutterProjectArgs project_args_ = {};

View File

@ -156,6 +156,11 @@ void EmbedderTestContext::SetChannelUpdateCallback(
channel_update_callback_ = callback;
}
void EmbedderTestContext::SetViewFocusChangeRequestCallback(
const ViewFocusChangeRequestCallback& callback) {
view_focus_change_request_callback_ = callback;
}
void EmbedderTestContext::PlatformMessageCallback(
const FlutterPlatformMessage* message) {
if (platform_message_callback_) {
@ -255,6 +260,16 @@ EmbedderTestContext::GetChannelUpdateCallbackHook() {
};
}
FlutterViewFocusChangeRequestCallback
EmbedderTestContext::GetViewFocusChangeRequestCallbackHook() {
return [](const FlutterViewFocusChangeRequest* request, void* user_data) {
auto context = reinterpret_cast<EmbedderTestContext*>(user_data);
if (context->view_focus_change_request_callback_) {
context->view_focus_change_request_callback_(request);
}
};
}
FlutterTransformation EmbedderTestContext::GetRootSurfaceTransformation() {
return FlutterTransformationMake(root_surface_transformation_);
}

View File

@ -34,6 +34,8 @@ using SemanticsActionCallback =
using LogMessageCallback =
std::function<void(const char* tag, const char* message)>;
using ChannelUpdateCallback = std::function<void(const FlutterChannelUpdate*)>;
using ViewFocusChangeRequestCallback =
std::function<void(const FlutterViewFocusChangeRequest*)>;
struct AOTDataDeleter {
void operator()(FlutterEngineAOTData aot_data) {
@ -94,6 +96,9 @@ class EmbedderTestContext {
void SetChannelUpdateCallback(const ChannelUpdateCallback& callback);
void SetViewFocusChangeRequestCallback(
const ViewFocusChangeRequestCallback& callback);
std::future<sk_sp<SkImage>> GetNextSceneImage();
EmbedderTestCompositor& GetCompositor();
@ -133,6 +138,7 @@ class EmbedderTestContext {
SemanticsNodeCallback update_semantics_node_callback_;
SemanticsActionCallback update_semantics_custom_action_callback_;
ChannelUpdateCallback channel_update_callback_;
ViewFocusChangeRequestCallback view_focus_change_request_callback_;
std::function<void(const FlutterPlatformMessage*)> platform_message_callback_;
LogMessageCallback log_message_callback_;
std::unique_ptr<EmbedderTestCompositor> compositor_;
@ -158,6 +164,8 @@ class EmbedderTestContext {
FlutterChannelUpdateCallback GetChannelUpdateCallbackHook();
FlutterViewFocusChangeRequestCallback GetViewFocusChangeRequestCallbackHook();
void SetupAOTMappingsIfNecessary();
void SetupAOTDataIfNecessary();

View File

@ -2045,6 +2045,124 @@ TEST_F(EmbedderTest, CanRenderMultipleViews) {
latch123.Wait();
}
bool operator==(const FlutterViewFocusChangeRequest& lhs,
const FlutterViewFocusChangeRequest& rhs) {
return lhs.view_id == rhs.view_id && lhs.state == rhs.state &&
lhs.direction == rhs.direction;
}
TEST_F(EmbedderTest, SendsViewFocusChangeRequest) {
auto& context = GetEmbedderContext<EmbedderTestContextSoftware>();
auto platform_task_runner = CreateNewThread("test_platform_thread");
UniqueEngine engine;
static std::mutex engine_mutex;
EmbedderTestTaskRunner test_platform_task_runner(
platform_task_runner, [&](FlutterTask task) {
std::scoped_lock lock(engine_mutex);
if (!engine.is_valid()) {
return;
}
FlutterEngineRunTask(engine.get(), &task);
});
fml::CountDownLatch latch(3);
std::vector<FlutterViewFocusChangeRequest> received_requests;
platform_task_runner->PostTask([&]() {
EmbedderConfigBuilder builder(context);
builder.SetSurface(SkISize::Make(1, 1));
builder.SetDartEntrypoint("testSendViewFocusChangeRequest");
const auto platform_task_runner_description =
test_platform_task_runner.GetFlutterTaskRunnerDescription();
builder.SetPlatformTaskRunner(&platform_task_runner_description);
builder.SetViewFocusChangeRequestCallback(
[&](const FlutterViewFocusChangeRequest* request) {
EXPECT_TRUE(platform_task_runner->RunsTasksOnCurrentThread());
received_requests.push_back(*request);
latch.CountDown();
});
engine = builder.LaunchEngine();
ASSERT_TRUE(engine.is_valid());
});
latch.Wait();
std::vector<FlutterViewFocusChangeRequest> expected_requests{
{.view_id = 1, .state = kUnfocused, .direction = kUndefined},
{.view_id = 2, .state = kFocused, .direction = kForward},
{.view_id = 3, .state = kFocused, .direction = kBackward},
};
ASSERT_EQ(received_requests.size(), expected_requests.size());
for (size_t i = 0; i < received_requests.size(); ++i) {
ASSERT_TRUE(received_requests[i] == expected_requests[i]);
}
fml::AutoResetWaitableEvent kill_latch;
platform_task_runner->PostTask(fml::MakeCopyable([&]() mutable {
std::scoped_lock lock(engine_mutex);
engine.reset();
// There may still be pending tasks on the platform thread that were queued
// by the test_task_runner. Signal the latch after these tasks have been
// consumed.
platform_task_runner->PostTask([&kill_latch] { kill_latch.Signal(); });
}));
kill_latch.Wait();
}
TEST_F(EmbedderTest, CanSendViewFocusEvent) {
auto& context = GetEmbedderContext<EmbedderTestContextSoftware>();
EmbedderConfigBuilder builder(context);
builder.SetSurface(SkISize::Make(1, 1));
builder.SetDartEntrypoint("testSendViewFocusEvent");
fml::AutoResetWaitableEvent latch;
std::string last_event;
context.AddNativeCallback(
"SignalNativeTest",
CREATE_NATIVE_ENTRY(
[&latch](Dart_NativeArguments args) { latch.Signal(); }));
context.AddNativeCallback("NotifyStringValue",
CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
const auto message_from_dart =
tonic::DartConverter<std::string>::FromDart(
Dart_GetNativeArgument(args, 0));
last_event = message_from_dart;
latch.Signal();
}));
auto engine = builder.LaunchEngine();
ASSERT_TRUE(engine.is_valid());
// Wait until the focus change handler is attached.
latch.Wait();
latch.Reset();
FlutterViewFocusEvent event1{
.struct_size = sizeof(FlutterViewFocusEvent),
.view_id = 1,
.state = kFocused,
.direction = kUndefined,
};
FlutterEngineResult result =
FlutterEngineSendViewFocusEvent(engine.get(), &event1);
ASSERT_EQ(result, kSuccess);
latch.Wait();
ASSERT_EQ(last_event,
"1 ViewFocusState.focused ViewFocusDirection.undefined");
FlutterViewFocusEvent event2{
.struct_size = sizeof(FlutterViewFocusEvent),
.view_id = 2,
.state = kUnfocused,
.direction = kBackward,
};
latch.Reset();
result = FlutterEngineSendViewFocusEvent(engine.get(), &event2);
ASSERT_EQ(result, kSuccess);
latch.Wait();
ASSERT_EQ(last_event,
"2 ViewFocusState.unfocused ViewFocusDirection.backward");
}
//------------------------------------------------------------------------------
/// Test that the backing store is created with the correct view ID, is used
/// for the correct view, and is cached according to their views.

View File

@ -157,6 +157,9 @@ class MockPlatformViewDelegate : public flutter::PlatformView::Delegate {
void UpdateAssetResolverByType(
std::unique_ptr<flutter::AssetResolver> updated_asset_resolver,
flutter::AssetResolver::AssetResolverType type) {}
// |flutter::PlatformView::Delegate|
void OnPlatformViewSendViewFocusEvent(const flutter::ViewFocusEvent& event) {
};
flutter::Surface* surface() const { return surface_.get(); }
flutter::PlatformMessage* message() const { return message_.get(); }