[fuchsia] Migrate integration test to CFv2 (flutter/engine#35318)

This commit is contained in:
David Worsham 2022-08-19 17:58:53 -07:00 committed by GitHub
parent 08440907ca
commit b0d80cc091
39 changed files with 1103 additions and 1808 deletions

View File

@ -2129,18 +2129,6 @@ FILE: ../../../flutter/shell/platform/fuchsia/flutter/gfx_platform_view.cc
FILE: ../../../flutter/shell/platform/fuchsia/flutter/gfx_platform_view.h
FILE: ../../../flutter/shell/platform/fuchsia/flutter/gfx_session_connection.cc
FILE: ../../../flutter/shell/platform/fuchsia/flutter/gfx_session_connection.h
FILE: ../../../flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/child-view2/child_view2.dart
FILE: ../../../flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/child-view2/meta/child-view2.cmx
FILE: ../../../flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/flutter-embedder-test2.cc
FILE: ../../../flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/flutter-embedder-test2.h
FILE: ../../../flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/meta/flutter-embedder-test2.cmx
FILE: ../../../flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/parent-view2/meta/parent-view2.cmx
FILE: ../../../flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/parent-view2/parent_view2.dart
FILE: ../../../flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view/base_view.cc
FILE: ../../../flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view/base_view.h
FILE: ../../../flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view/embedded_view_utils.cc
FILE: ../../../flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view/embedded_view_utils.h
FILE: ../../../flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view/math.h
FILE: ../../../flutter/shell/platform/fuchsia/flutter/isolate_configurator.cc
FILE: ../../../flutter/shell/platform/fuchsia/flutter/isolate_configurator.h
FILE: ../../../flutter/shell/platform/fuchsia/flutter/kernel/extract_far.dart

View File

@ -457,6 +457,34 @@ jit_runner("flutter_jit_product_runner") {
product = true
}
# "OOT" copy of the runner used by tests, to avoid conflicting with the runner
# in the base fuchsia image.
# TODO(fxbug.dev/106575): Fix this with subpackages.
aot_runner("oot_flutter_aot_runner") {
product = false
}
# "OOT" copy of the runner used by tests, to avoid conflicting with the runner
# in the base fuchsia image.
# TODO(fxbug.dev/106575): Fix this with subpackages.
aot_runner("oot_flutter_aot_product_runner") {
product = true
}
# "OOT" copy of the runner used by tests, to avoid conflicting with the runner
# in the base fuchsia image.
# TODO(fxbug.dev/106575): Fix this with subpackages.
jit_runner("oot_flutter_jit_runner") {
product = false
}
# "OOT" copy of the runner used by tests, to avoid conflicting with the runner
# in the base fuchsia image.
# TODO(fxbug.dev/106575): Fix this with subpackages.
jit_runner("oot_flutter_jit_product_runner") {
product = true
}
test_fixtures("flutter_runner_fixtures") {
fixtures = []
}
@ -885,7 +913,7 @@ if (enable_unittests) {
":testing_tests",
":txt_tests",
":ui_tests",
"integration_flutter_tests",
"tests/integration",
]
}
}

View File

@ -1,5 +0,0 @@
dworsham@google.com
sanjayc@google.com
richkadel@google.com
# COMPONENT: FlutteronFuchsia

View File

@ -1,67 +0,0 @@
# Copyright 2013 The Flutter Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
assert(is_fuchsia)
import("//build/fuchsia/sdk.gni")
import("//flutter/tools/fuchsia/fuchsia_archive.gni")
group("tests") {
testonly = true
deps = [
":flutter-embedder-test2",
"//flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/child-view2",
"//flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/parent-view2",
]
}
executable("flutter-embedder-test2-bin") {
testonly = true
output_name = "flutter-embedder-test2"
sources = [
"flutter-embedder-test2.cc",
"flutter-embedder-test2.h",
]
# This is needed for //third_party/googletest for linking zircon symbols.
libs = [ "$fuchsia_sdk_path/arch/$target_cpu/sysroot/lib/libzircon.so" ]
include_dirs = [ "//flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing" ]
public_deps = [ "//third_party/googletest:gtest" ]
deps = [
"$fuchsia_sdk_root/fidl:fuchsia.sys",
"$fuchsia_sdk_root/fidl:fuchsia.ui.app",
"$fuchsia_sdk_root/fidl:fuchsia.ui.input",
"$fuchsia_sdk_root/fidl:fuchsia.ui.lifecycle",
"$fuchsia_sdk_root/fidl:fuchsia.ui.policy",
"$fuchsia_sdk_root/fidl:fuchsia.ui.scenic",
"$fuchsia_sdk_root/pkg:async-loop-cpp",
"$fuchsia_sdk_root/pkg:async-loop-default",
"$fuchsia_sdk_root/pkg:fit",
"$fuchsia_sdk_root/pkg:scenic_cpp",
"$fuchsia_sdk_root/pkg:sys_cpp",
"$fuchsia_sdk_root/pkg:sys_cpp_testing",
"$fuchsia_sdk_root/pkg:zx",
"//flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view",
"//flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/ui/testing/views",
"//third_party/dart/runtime:libdart_jit",
"//third_party/googletest:gtest_main",
]
}
fuchsia_test_archive("flutter-embedder-test2") {
deps = [
":flutter-embedder-test2-bin",
"//flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/child-view2:package",
"//flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/parent-view2:package",
]
binary = "$target_name"
cmx_file = rebase_path("meta/$target_name.cmx")
}

View File

@ -1,173 +0,0 @@
# `flutter scenic embedder tests`
## Configure and build fuchsia
For tests that require scenic, for example, run `fx set` with the required
targets; for example:
```shell
$ cd "$FUCHSIA_DIR"
$ fx set core.x64 \
--with //src/ui/scenic \
--with //src/ui/bin/root_presenter \
--with //src/ui/bin/hardware_display_controller_provider
$ fx build
```
Note 1: You could use `--with-base` here, instead of `--with`, but if so, you
would also need to add `--with-base //garnet/bin/run_test_component`. More on
this below, under [Start the package servers](#start-the-package-servers).
Note 2: The `fx set` flags, above, offer a minimized fuchsia platform
configuration to successfully execute the test, but some optional services may
be missing. Be aware that the Fuchsia system logs may include multiple
occurrences `WARNING: error resolving ...` messages, such as the following,
which can be ignored:
```
[pkg-resolver] WARNING: error resolving fuchsia-pkg://fuchsia.com/fonts/0 ...
[pkg-resolver] WARNING: error resolving fuchsia-pkg://fuchsia.com/ime_service/0 ...
[pkg-resolver] WARNING: error resolving fuchsia-pkg://fuchsia.com/intl_property_manager/0 ...
```
## Restart and reboot your device
_(Optional)_ If developing with the emulator, launch (or shutdown and relaunch)
the emulator.
```shell
fx vdl start -N
```
NOTE: Do _not_ run the default package server. The instructions below describe
how to launch a flutter-specific package server.
Or if you've rebuilt fuchsia for a device that is already running a version of
fuchsia, you may be able to reboot without restarting the device:
```shell
$ fx reboot -r
```
If you are building a device that launches the UI at startup, you will likely
need to kill Scenic before running the test.
```shell
$ fx shell killall scenic.cmx
```
## Build the test
You can specify the test's package target to build only the test package, with
its dependencies. This will also build the required runner.
```shell
$ cd "$FLUTTER_ENGINE_DIR/src"
$ ./flutter/tools/gn --fuchsia <flags> \
# for example: --goma --fuchsia-cpu=x64 --runtime-mode=debug
$ ninja -C out/fuchsia_debug_x64 \
flutter/shell/platform/fuchsia/flutter/integration_flutter_tests
```
## Publish the test packages to the Fuchsia package server
The tests currently specify the Fuchsia package server's standard domain,
`fuchsia.com`, as the server to use to resolve (locate and load) the test
packages. So, before running the test, the most recently built `.far` files
need to be published to the Fuchsia package repo:
```shell
$ fx pm publish -a -repo "$(cat $FUCHSIA_DIR/.fx-build-dir)/amber-files/" \
-f "$FLUTTER_ENGINE_DIR"/src/out/fuchsia_*64/flutter-embedder-test2-0.far
$ fx pm publish -a -repo "$(cat $FUCHSIA_DIR/.fx-build-dir)/amber-files/" \
-f $(find "$FLUTTER_ENGINE_DIR"/src/out/fuchsia_*64 -name parent-view2.far)
$ fx pm publish -a -repo "$(cat $FUCHSIA_DIR/.fx-build-dir)/amber-files/" \
-f $(find "$FLUTTER_ENGINE_DIR"/src/out/fuchsia_*64 -name child-view2.far)
```
## Run the test (using the package server at `fuchsia.com`)
```shell
$ fx test flutter-embedder-test2
```
## Make a change and re-run the test
If, for example, you only make a change to the Dart code in `parent-view2`, you
can rebuild only the parent-view2 package target, republish it, and then re-run
the test, with:
```shell
$ ninja -C out/fuchsia_debug_x64 \
flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/parent-view2:package
$ fx pm publish -a -repo "$(cat $FUCHSIA_DIR/.fx-build-dir)/amber-files/" \
-f $(find "$FLUTTER_ENGINE_DIR"/src/out/fuchsia_*64 -name parent-view2.far)
$ fx test flutter-embedder-test2
```
From here, you can modify the Flutter test, rebuild flutter, and usually rerun
the test without rebooting, by repeating the commands above.
The embedder tests must be run on a product without a graphical base shell,
such as `core` because it starts and stops Scenic.
## (Alternative) Serving flutter packages from a custom package server
If you want to use a custom package server, you will need to edit these sources:
* `//flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/flutter-embedder-test2.cc`
* `//flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/parent-view2/parent_view2.dart`
Search for the component URLs with `fuchsia.com`, and change it to `engine`,
which is the domain currently registered with the custom package server in
`//tools/fuchsia/devshell/serve.sh`.
WARNING: Be careful not to check in that change because CI requires using the
`fuchsia.com` domain/package server.
The default fuchsia package server (launched via `fx serve`) is normally
required, unless all of your test's package dependencies are included in the
fuchsia system image. You can force additional packages into the system image
with `fx set ... --with-base <package>` (instead of using `--with`). For
example, in the `fx set` command above, using, `--with-base scenic`, and so on.
Note, however, that the default `core.x64` configuration bundles the test
runner as if it was included via
`--with //garnet/bin/run_test_component`, so to include the test runner in the
system image requires adding that package as well, via `--with-base`, instead.
In order to serve fuchsia package dependencies (like `scenic`, `root_presenter`,
and `hardware-display-controller-provider`), without forcing them into the
system image, you will need to run the fuchsia default package server, via `fx
serve`.
The `flutter/engine` packages (tests and flutter runners, for dart-based tests)
are served from a separate package server. The `flutter/engine` repo's
`serve.sh` script launches this secondary package server, and configures
package URL rewrite rules to redirect fuchsia's requests for flutter- and
dart-runner packages from `fuchsia.com` to flutter's package server instead.
**IMPORTANT:** _The flutter package server must be launched **after** the
default package server, because both `fx serve` and flutter's `serve.sh` set
package URL rewrite rules, and only the last one wins._
Launch each package server in a separate window or shell:
```shell
$ cd "${FUCHSIA_DIR}"
$ fx serve
```
From the flutter engine `src` directory, run the following script to launch the
`engine` package server, to serve the flutter runner and test components.
```shell
$ flutter/tools/fuchsia/devshell/serve.sh --out out/fuchsia_debug_x64 --only-serve-runners
```
## Run the test, using `engine` as the package server domain
```shell
$ fx test flutter-embedder-test2
```
You can recompile and run the test without needing to re-publish the `.far`.

View File

@ -1,38 +0,0 @@
# 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.
import("//build/fuchsia/sdk.gni")
import("//flutter/tools/fuchsia/dart/dart_library.gni")
import("//flutter/tools/fuchsia/flutter/flutter_component.gni")
import("//flutter/tools/fuchsia/gn-sdk/package.gni")
dart_library("child-view2_dart_library") {
package_name = "child-view2"
source_dir = "."
sources = [ "child_view2.dart" ]
deps = []
}
flutter_component("child-view2_flutter_component") {
main_package = "child-view2"
component_name = "child-view2"
main_dart = "child_view2.dart"
manifest = rebase_path("meta/child-view2.cmx")
deps = [ ":child-view2_dart_library" ]
}
# TODO(richkadel): The target name is set differently compared to fuchsia.git's flutter_app().
# Unlike in fuchsia.git's version of fuchsia_component, the Fuchsia GN SDK
# version passes the component name to fuchsia_component via it's target_name only.
# GN SDK's fuchsia_component doesn't have a `component_name` argument! So I'm forced to set
# the component name via "target_name". This is a problem in fuchsia_package, which uses
# the target_name to name the fuchsia_pm_tool target, creating duplicate target IDs!
# So I have to change the fuchsia_package name to something that is NOT the component name,
# and then set the package_name (which fuchsia_package does support).
fuchsia_package("package") {
package_name = "child-view2"
deps = [ ":child-view2_flutter_component" ]
}

View File

@ -1,13 +0,0 @@
{
"program": {
"data": "data/child-view2"
},
"sandbox": {
"services": [
"fuchsia.fonts.Provider",
"fuchsia.sys.Environment",
"fuchsia.ui.input.ImeService",
"fuchsia.ui.scenic.Scenic"
]
}
}

View File

@ -1,218 +0,0 @@
// 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-embedder-test2.h"
namespace flutter_embedder_test2 {
// TODO(richkadel): To run the test serving the runner and test packages from
// the flutter/engine package server (via
// `//flutter/tools/fuchsia/devshell/serve.sh`), change `fuchsia.com` to
// `engine`.
constexpr char kParentViewUrl[] =
"fuchsia-pkg://fuchsia.com/parent-view2#meta/parent-view2.cmx";
constexpr scenic::Color kParentBackgroundColor = {0x00, 0x00, 0xFF,
0xFF}; // Blue
constexpr scenic::Color kParentTappedColor = {0x00, 0x00, 0x00, 0xFF}; // Black
constexpr scenic::Color kChildBackgroundColor = {0xFF, 0x00, 0xFF,
0xFF}; // Pink
constexpr scenic::Color kChildTappedColor = {0xFF, 0xFF, 0x00, 0xFF}; // Yellow
// TODO(fxb/94000): The new flutter renderer draws overlays as a single, large
// layer. Some parts of this layer are fully transparent, so we want the
// compositor to treat the layer as transparent and blend it with the contents
// below.
//
// The gfx Scenic API only provides one way to mark this layer as transparent
// which is to set an opacity < 1.0 for the entire layer. In practice, we use
// 0.9961 (254 / 255) as an opacity value to force transparency. Unfortunately
// this causes the overlay to blend very slightly and it looks wrong.
//
// Flatland allows marking a layer as transparent while still using a 1.0
// opacity value when blending, so migrating flutter to Flatland will fix this
// issue. For now we just hard-code the broken, blended values.
constexpr scenic::Color kOverlayBackgroundColor1 = {
0x00, 0xFF, 0x0E, 0xFF}; // Green, blended with blue (FEMU local)
constexpr scenic::Color kOverlayBackgroundColor2 = {
0x0E, 0xFF, 0x0E, 0xFF}; // Green, blended with pink (FEMU local)
constexpr scenic::Color kOverlayBackgroundColor3 = {
0x00, 0xFF, 0x0D, 0xFF}; // Green, blended with blue (AEMU infra)
constexpr scenic::Color kOverlayBackgroundColor4 = {
0x0D, 0xFF, 0x0D, 0xFF}; // Green, blended with pink (AEMU infra)
constexpr scenic::Color kOverlayBackgroundColor5 = {
0x00, 0xFE, 0x0D, 0xFF}; // Green, blended with blue (NUC)
constexpr scenic::Color kOverlayBackgroundColor6 = {
0x0D, 0xFF, 0x00, 0xFF}; // Green, blended with pink (NUC)
static size_t OverlayPixelCount(std::map<scenic::Color, size_t>& histogram) {
return histogram[kOverlayBackgroundColor1] +
histogram[kOverlayBackgroundColor2] +
histogram[kOverlayBackgroundColor3] +
histogram[kOverlayBackgroundColor4] +
histogram[kOverlayBackgroundColor5] +
histogram[kOverlayBackgroundColor6];
}
/// Defines a list of services that are injected into the test environment.
/// Unlike the injected-services in CMX which are injected per test package,
/// these are injected per test and result in a more hermetic test environment.
const std::vector<std::pair<const char*, const char*>> GetInjectedServices() {
std::vector<std::pair<const char*, const char*>> injected_services = {{
{"fuchsia.accessibility.semantics.SemanticsManager",
"fuchsia-pkg://fuchsia.com/a11y-manager#meta/a11y-manager.cmx"},
{"fuchsia.fonts.Provider",
"fuchsia-pkg://fuchsia.com/fonts#meta/fonts.cmx"},
{"fuchsia.hardware.display.Provider",
"fuchsia-pkg://fuchsia.com/"
"fake-hardware-display-controller-provider#meta/hdcp.cmx"},
{"fuchsia.intl.PropertyProvider",
"fuchsia-pkg://fuchsia.com/intl_property_manager#meta/"
"intl_property_manager.cmx"},
{"fuchsia.netstack.Netstack",
"fuchsia-pkg://fuchsia.com/network-legacy-deprecated#meta/netstack.cmx"},
{"fuchsia.posix.socket.Provider",
"fuchsia-pkg://fuchsia.com/network-legacy-deprecated#meta/netstack.cmx"},
{"fuchsia.tracing.provider.Registry",
"fuchsia-pkg://fuchsia.com/trace_manager#meta/trace_manager.cmx"},
{"fuchsia.ui.input.ImeService",
"fuchsia-pkg://fuchsia.com/text_manager#meta/text_manager.cmx"},
{"fuchsia.ui.input.ImeVisibilityService",
"fuchsia-pkg://fuchsia.com/text_manager#meta/text_manager.cmx"},
{"fuchsia.ui.scenic.Scenic",
"fuchsia-pkg://fuchsia.com/scenic#meta/scenic.cmx"},
{"fuchsia.ui.pointerinjector.Registry",
"fuchsia-pkg://fuchsia.com/scenic#meta/scenic.cmx"}, // For
// root_presenter
// TODO(fxbug.dev/82655): Remove this after migrating to RealmBuilder.
{"fuchsia.ui.lifecycle.LifecycleController",
"fuchsia-pkg://fuchsia.com/scenic#meta/scenic.cmx"},
{"fuchsia.ui.policy.Presenter",
"fuchsia-pkg://fuchsia.com/root_presenter#meta/root_presenter.cmx"},
{"fuchsia.ui.input.InputDeviceRegistry",
"fuchsia-pkg://fuchsia.com/root_presenter#meta/root_presenter.cmx"},
}};
return injected_services;
}
TEST_F(FlutterScenicEmbedderTests, Embedding) {
RunAppWithArgs(kParentViewUrl);
// Take screenshot until we see the child-view2's embedded color.
ASSERT_TRUE(TakeScreenshotUntil(
kChildBackgroundColor, [](scenic::Screenshot screenshot,
std::map<scenic::Color, size_t> histogram) {
// Expect parent and child background colors, with parent color > child
// color.
EXPECT_GT(histogram[kParentBackgroundColor], 0u);
EXPECT_GT(histogram[kChildBackgroundColor], 0u);
EXPECT_GT(histogram[kParentBackgroundColor],
histogram[kChildBackgroundColor]);
// Expect all corners to be the parent-view2 background color
EXPECT_EQ(kParentBackgroundColor, screenshot.ColorAtPixelXY(10, 10));
EXPECT_EQ(kParentBackgroundColor,
screenshot.ColorAtPixelXY(screenshot.width() - 10, 0));
EXPECT_EQ(kParentBackgroundColor,
screenshot.ColorAtPixelXY(0, screenshot.height() - 10));
EXPECT_EQ(kParentBackgroundColor,
screenshot.ColorAtPixelXY(screenshot.width() - 10,
screenshot.height() - 10));
}));
}
TEST_F(FlutterScenicEmbedderTests, HittestEmbedding) {
RunAppWithArgs(kParentViewUrl);
// Take screenshot until we see the child-view2's embedded color.
ASSERT_TRUE(TakeScreenshotUntil(kChildBackgroundColor));
// Tap the center of child view2.
InjectInput();
// Take screenshot until we see the child-view2's tapped color.
ASSERT_TRUE(TakeScreenshotUntil(
kChildTappedColor, [](scenic::Screenshot screenshot,
std::map<scenic::Color, size_t> histogram) {
// Expect parent and child background colors, with parent color > child
// color.
EXPECT_GT(histogram[kParentBackgroundColor], 0u);
EXPECT_EQ(histogram[kChildBackgroundColor], 0u);
EXPECT_GT(histogram[kChildTappedColor], 0u);
EXPECT_GT(histogram[kParentBackgroundColor],
histogram[kChildTappedColor]);
}));
}
TEST_F(FlutterScenicEmbedderTests, HittestDisabledEmbedding) {
RunAppWithArgs(kParentViewUrl, {"--no-hitTestable"});
// Take screenshots until we see the child-view2's embedded color.
ASSERT_TRUE(TakeScreenshotUntil(kChildBackgroundColor));
// Tap the center of child view2. Since it's not hit-testable, the tap should
// go to the parent.
InjectInput();
// The parent-view2 should change color.
ASSERT_TRUE(TakeScreenshotUntil(
kParentTappedColor, [](scenic::Screenshot screenshot,
std::map<scenic::Color, size_t> histogram) {
// Expect parent and child background colors, with parent color > child
// color.
EXPECT_EQ(histogram[kParentBackgroundColor], 0u);
EXPECT_GT(histogram[kParentTappedColor], 0u);
EXPECT_GT(histogram[kChildBackgroundColor], 0u);
EXPECT_EQ(histogram[kChildTappedColor], 0u);
EXPECT_GT(histogram[kParentTappedColor],
histogram[kChildBackgroundColor]);
}));
}
TEST_F(FlutterScenicEmbedderTests, EmbeddingWithOverlay) {
RunAppWithArgs(kParentViewUrl, {"--showOverlay"});
// Take screenshot until we see the child-view2's embedded color.
ASSERT_TRUE(TakeScreenshotUntil(
kChildBackgroundColor, [](scenic::Screenshot screenshot,
std::map<scenic::Color, size_t> histogram) {
// Expect parent, overlay and child background colors.
// With parent color > child color and overlay color > child color.
const size_t overlay_pixel_count = OverlayPixelCount(histogram);
EXPECT_GT(histogram[kParentBackgroundColor], 0u);
EXPECT_GT(overlay_pixel_count, 0u);
EXPECT_GT(histogram[kChildBackgroundColor], 0u);
EXPECT_GT(histogram[kParentBackgroundColor],
histogram[kChildBackgroundColor]);
EXPECT_GT(overlay_pixel_count, histogram[kChildBackgroundColor]);
}));
}
TEST_F(FlutterScenicEmbedderTests, HittestEmbeddingWithOverlay) {
RunAppWithArgs(kParentViewUrl, {"--showOverlay"});
// Take screenshot until we see the child-view2's embedded color.
ASSERT_TRUE(TakeScreenshotUntil(kChildBackgroundColor));
// Tap the center of child view2.
InjectInput();
// Take screenshot until we see the child-view2's tapped color.
ASSERT_TRUE(TakeScreenshotUntil(
kChildTappedColor, [](scenic::Screenshot screenshot,
std::map<scenic::Color, size_t> histogram) {
// Expect parent, overlay and child background colors.
// With parent color > child color and overlay color > child color.
const size_t overlay_pixel_count = OverlayPixelCount(histogram);
EXPECT_GT(histogram[kParentBackgroundColor], 0u);
EXPECT_GT(overlay_pixel_count, 0u);
EXPECT_EQ(histogram[kChildBackgroundColor], 0u);
EXPECT_GT(histogram[kChildTappedColor], 0u);
EXPECT_GT(histogram[kParentBackgroundColor],
histogram[kChildTappedColor]);
EXPECT_GT(overlay_pixel_count, histogram[kChildTappedColor]);
}));
}
} // namespace flutter_embedder_test2

View File

@ -1,234 +0,0 @@
// 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 SRC_UI_TESTS_INTEGRATION_FLUTTER_TESTS_EMBEDDER_flutter_embedder_test2_H_
#define SRC_UI_TESTS_INTEGRATION_FLUTTER_TESTS_EMBEDDER_flutter_embedder_test2_H_
#include <fuchsia/ui/lifecycle/cpp/fidl.h>
#include <fuchsia/ui/policy/cpp/fidl.h>
#include <fuchsia/ui/scenic/cpp/fidl.h>
#include <fuchsia/ui/views/cpp/fidl.h>
#include <gtest/gtest.h>
#include <lib/async/cpp/task.h>
#include <lib/fidl/cpp/binding_set.h>
#include <lib/sys/cpp/component_context.h>
#include <lib/sys/cpp/testing/test_with_environment.h>
#include <lib/ui/scenic/cpp/view_token_pair.h>
#include <lib/zx/clock.h>
#include <zircon/status.h>
#include <zircon/time.h>
#include "flutter/fml/logging.h"
#include <vector>
#include "src/lib/ui/base_view/embedded_view_utils.h"
#include "src/ui/testing/views/color.h"
#include "src/ui/testing/views/embedder_view.h"
namespace flutter_embedder_test2 {
/// Defines a list of services that are injected into the test environment.
/// Unlike the injected-services in CMX which are injected per test package,
/// these are injected per test and result in a more hermetic test environment.
const std::vector<std::pair<const char*, const char*>> GetInjectedServices();
// Timeout when waiting on Scenic API calls like |GetDisplayInfo|.
constexpr zx::duration kCallTimeout = zx::sec(5);
// Timeout for Scenic's |TakeScreenshot| FIDL call.
constexpr zx::duration kScreenshotTimeout = zx::sec(10);
// Timeout to fail the test if it goes beyond this duration.
constexpr zx::duration kTestTimeout = zx::min(1);
class FlutterScenicEmbedderTests : public sys::testing::TestWithEnvironment,
public ::testing::Test {
public:
// |testing::Test|
void SetUp() override {
Test::SetUp();
// Create test-specific launchable services.
auto services = TestWithEnvironment::CreateServices();
for (const auto& service_info : GetInjectedServices()) {
zx_status_t status = services->AddServiceWithLaunchInfo(
{.url = service_info.second}, service_info.first);
FML_CHECK(status == ZX_OK)
<< "Failed to add service " << service_info.first;
}
environment_ = CreateNewEnclosingEnvironment(
"flutter-embedder-test2s", std::move(services),
{.inherit_parent_services = true});
WaitForEnclosingEnvToStart(environment());
FML_VLOG(fml::LOG_INFO) << "Created test environment.";
// Connects to scenic lifecycle controller in order to shutdown scenic at
// the end of the test. This ensures the correct ordering of shutdown under
// CFv1: first scenic, then the fake display controller.
//
// TODO(fxbug.dev/82655): Remove this after migrating to RealmBuilder.
environment_->ConnectToService<fuchsia::ui::lifecycle::LifecycleController>(
scenic_lifecycle_controller_.NewRequest());
environment_->ConnectToService(scenic_.NewRequest());
scenic_.set_error_handler([](zx_status_t status) {
FAIL() << "Lost connection to Scenic: " << zx_status_get_string(status);
});
// Post a "just in case" quit task, if the test hangs.
async::PostDelayedTask(
dispatcher(),
[] {
FML_LOG(FATAL)
<< "\n\n>> Test did not complete in time, terminating. <<\n\n";
},
kTestTimeout);
}
// |testing::Test|
void TearDown() override {
// Avoid spurious errors since we are about to kill scenic.
//
// TODO(fxbug.dev/82655): Remove this after migrating to RealmBuilder.
scenic_.set_error_handler(nullptr);
zx_status_t terminate_status = scenic_lifecycle_controller_->Terminate();
FML_CHECK(terminate_status == ZX_OK)
<< "Failed to terminate Scenic with status: "
<< zx_status_get_string(terminate_status);
}
sys::testing::EnclosingEnvironment* environment() {
return environment_.get();
}
fuchsia::ui::views::ViewToken CreatePresentationViewToken() {
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
auto presenter =
environment()->ConnectToService<fuchsia::ui::policy::Presenter>();
presenter.set_error_handler([](zx_status_t status) {
FAIL() << "presenter: " << zx_status_get_string(status);
});
presenter->PresentView(std::move(view_holder_token), nullptr);
return std::move(view_token);
}
void RunAppWithArgs(const std::string& component_url,
const std::vector<std::string>& component_args = {}) {
scenic::EmbeddedViewInfo flutter_runner =
scenic::LaunchComponentAndCreateView(environment()->launcher_ptr(),
component_url, component_args);
flutter_runner.controller.events().OnTerminated = [](auto...) { FAIL(); };
// Present the view.
embedder_view_.emplace(scenic::ViewContext{
.session_and_listener_request =
scenic::CreateScenicSessionPtrAndListenerRequest(scenic_.get()),
.view_token = CreatePresentationViewToken(),
});
// Embed the view.
bool is_rendering = false;
embedder_view_->EmbedView(
std::move(flutter_runner),
[&is_rendering](fuchsia::ui::gfx::ViewState view_state) {
is_rendering = view_state.is_rendering;
});
RunLoopUntil([&is_rendering] { return is_rendering; });
FML_LOG(INFO) << "Launched component: " << component_url;
}
scenic::Screenshot TakeScreenshot() {
FML_LOG(INFO) << "Taking screenshot... ";
fuchsia::ui::scenic::ScreenshotData screenshot_out;
scenic_->TakeScreenshot(
[this, &screenshot_out](fuchsia::ui::scenic::ScreenshotData screenshot,
bool status) {
EXPECT_TRUE(status) << "Failed to take screenshot";
screenshot_out = std::move(screenshot);
QuitLoop();
});
EXPECT_FALSE(RunLoopWithTimeout(kScreenshotTimeout))
<< "Timed out waiting for screenshot.";
FML_LOG(INFO) << "Screenshot captured.";
return scenic::Screenshot(screenshot_out);
}
bool TakeScreenshotUntil(
scenic::Color color,
fit::function<void(scenic::Screenshot, std::map<scenic::Color, size_t>)>
callback = nullptr,
zx::duration timeout = kTestTimeout) {
return RunLoopWithTimeoutOrUntil(
[this, &callback, &color] {
auto screenshot = TakeScreenshot();
auto histogram = screenshot.Histogram();
bool color_found = histogram[color] > 0;
if (color_found && callback != nullptr) {
callback(std::move(screenshot), std::move(histogram));
}
return color_found;
},
timeout);
}
// Inject directly into Root Presenter, using fuchsia.ui.input FIDLs.
void InjectInput() {
using fuchsia::ui::input::InputReport;
// Device parameters
auto parameters = fuchsia::ui::input::TouchscreenDescriptor::New();
*parameters = {.x = {.range = {.min = -1000, .max = 1000}},
.y = {.range = {.min = -1000, .max = 1000}},
.max_finger_id = 10};
FML_LOG(INFO) << "Injecting input... ";
// Register it against Root Presenter.
fuchsia::ui::input::DeviceDescriptor device{.touchscreen =
std::move(parameters)};
auto registry =
environment()
->ConnectToService<fuchsia::ui::input::InputDeviceRegistry>();
fuchsia::ui::input::InputDevicePtr connection;
registry->RegisterDevice(std::move(device), connection.NewRequest());
{
// Inject one input report, then a conclusion (empty) report.
auto touch = fuchsia::ui::input::TouchscreenReport::New();
*touch = {
.touches = {{.finger_id = 1, .x = 0, .y = 0}}}; // center of display
InputReport report{
.event_time = static_cast<uint64_t>(zx::clock::get_monotonic().get()),
.touchscreen = std::move(touch)};
connection->DispatchReport(std::move(report));
}
{
auto touch = fuchsia::ui::input::TouchscreenReport::New();
InputReport report{
.event_time = static_cast<uint64_t>(zx::clock::get_monotonic().get()),
.touchscreen = std::move(touch)};
connection->DispatchReport(std::move(report));
}
FML_LOG(INFO) << "Input dispatched.";
}
private:
const std::unique_ptr<sys::ComponentContext> component_context_;
std::unique_ptr<sys::testing::EnclosingEnvironment> environment_;
fuchsia::ui::lifecycle::LifecycleControllerSyncPtr
scenic_lifecycle_controller_;
fuchsia::ui::scenic::ScenicPtr scenic_;
// Wrapped in optional since the view is not created until the middle of SetUp
std::optional<scenic::EmbedderView> embedder_view_;
};
} // namespace flutter_embedder_test2
#endif // SRC_UI_TESTS_INTEGRATION_FLUTTER_TESTS_EMBEDDER_flutter_embedder_test2_H_

View File

@ -1,25 +0,0 @@
{
"facets": {
"fuchsia.test": {
"system-services": [
"fuchsia.scheduler.ProfileProvider",
"fuchsia.sysmem.Allocator",
"fuchsia.vulkan.loader.Loader"
]
}
},
"program": {
"binary": "bin/app"
},
"sandbox": {
"services": [
"fuchsia.logger.LogSink",
"fuchsia.sys.Environment",
"fuchsia.sys.Launcher",
"fuchsia.sys.Loader",
"fuchsia.sysmem.Allocator",
"fuchsia.tracing.provider.Registry",
"fuchsia.vulkan.loader.Loader"
]
}
}

View File

@ -1,46 +0,0 @@
# 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.
import("//build/fuchsia/sdk.gni")
import("//flutter/tools/fuchsia/dart/dart_library.gni")
import("//flutter/tools/fuchsia/flutter/flutter_component.gni")
import("//flutter/tools/fuchsia/gn-sdk/package.gni")
dart_library("parent-view2_dart_library") {
package_name = "parent-view2"
source_dir = "."
sources = [ "parent_view2.dart" ]
deps = [
"//flutter/shell/platform/fuchsia/dart:args",
"//flutter/shell/platform/fuchsia/dart:vector_math",
"//flutter/tools/fuchsia/dart:fuchsia_services",
"//flutter/tools/fuchsia/dart:zircon",
"//flutter/tools/fuchsia/fidl:fuchsia.sys",
"//flutter/tools/fuchsia/fidl:fuchsia.ui.app",
"//flutter/tools/fuchsia/fidl:fuchsia.ui.views",
]
}
flutter_component("parent-view2_flutter_component") {
main_package = "parent-view2"
component_name = "parent-view2"
main_dart = "parent_view2.dart"
manifest = rebase_path("meta/parent-view2.cmx")
deps = [ ":parent-view2_dart_library" ]
}
# TODO(richkadel): The target name is set differently compared to fuchsia.git's flutter_app().
# Unlike in fuchsia.git's version of fuchsia_component, the Fuchsia GN SDK
# version passes the component name to fuchsia_component via it's target_name only.
# GN SDK's fuchsia_component doesn't have a `component_name` argument! So I'm forced to set
# the component name via "target_name". This is a problem in fuchsia_package, which uses
# the target_name to name the fuchsia_pm_tool target, creating duplicate target IDs!
# So I have to change the fuchsia_package name to something that is NOT the component name,
# and then set the package_name (which fuchsia_package does support).
fuchsia_package("package") {
package_name = "parent-view2"
deps = [ ":parent-view2_flutter_component" ]
}

View File

@ -1,14 +0,0 @@
{
"program": {
"data": "data/parent-view2"
},
"sandbox": {
"services": [
"fuchsia.fonts.Provider",
"fuchsia.sys.Environment",
"fuchsia.sys.Launcher",
"fuchsia.ui.input.ImeService",
"fuchsia.ui.scenic.Scenic"
]
}
}

View File

@ -1,30 +0,0 @@
# 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.
import("//build/fuchsia/sdk.gni")
source_set("base_view") {
sources = [
"base_view.cc",
"base_view.h",
"embedded_view_utils.cc",
"embedded_view_utils.h",
"math.h",
]
include_dirs = [ "//flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing" ]
public_deps = [
"$fuchsia_sdk_root/fidl:fuchsia.sys",
"$fuchsia_sdk_root/fidl:fuchsia.ui.app",
"$fuchsia_sdk_root/fidl:fuchsia.ui.gfx",
"$fuchsia_sdk_root/fidl:fuchsia.ui.input",
"$fuchsia_sdk_root/fidl:fuchsia.ui.views",
"$fuchsia_sdk_root/pkg:scenic_cpp",
"$fuchsia_sdk_root/pkg:sys_cpp",
"//flutter/fml",
]
deps = [ "$fuchsia_sdk_root/pkg:trace" ]
}

View File

@ -1,240 +0,0 @@
// 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 "src/lib/ui/base_view/base_view.h"
#include <lib/trace/event.h>
#include <lib/ui/scenic/cpp/commands.h>
#include <lib/ui/scenic/cpp/view_token_pair.h>
#include <zircon/status.h>
#include "flutter/fml/logging.h"
namespace scenic {
BaseView::BaseView(ViewContext context, const std::string& debug_name)
: component_context_(context.component_context),
listener_binding_(this,
std::move(context.session_and_listener_request.second)),
session_(std::move(context.session_and_listener_request.first)),
root_node_(&session_),
ime_client_(this),
enable_ime_(context.enable_ime) {
if (!context.view_ref_pair) {
context.view_ref_pair = scenic::ViewRefPair::New();
}
view_.emplace(&session_, std::move(context.view_token),
std::move(context.view_ref_pair->control_ref),
std::move(context.view_ref_pair->view_ref), debug_name);
FML_DCHECK(view_);
session_.SetDebugName(debug_name);
// Listen for metrics events on our top node.
root_node_.SetEventMask(fuchsia::ui::gfx::kMetricsEventMask);
view_->AddChild(root_node_);
if (enable_ime_) {
ime_manager_ =
component_context_->svc()->Connect<fuchsia::ui::input::ImeService>();
ime_.set_error_handler([](zx_status_t status) {
FML_LOG(ERROR) << "Interface error on: Input Method Editor "
<< zx_status_get_string(status);
});
ime_manager_.set_error_handler([](zx_status_t status) {
FML_LOG(ERROR) << "Interface error on: Text Sync Service "
<< zx_status_get_string(status);
});
}
// We must immediately invalidate the scene, otherwise we wouldn't ever hook
// the View up to the ViewHolder. An alternative would be to require
// subclasses to call an Init() method to set up the initial connection.
InvalidateScene();
}
void BaseView::SetReleaseHandler(fit::function<void(zx_status_t)> callback) {
listener_binding_.set_error_handler(std::move(callback));
}
void BaseView::InvalidateScene(PresentCallback present_callback) {
TRACE_DURATION("view", "BaseView::InvalidateScene");
if (present_callback) {
callbacks_for_next_present_.push_back(std::move(present_callback));
}
if (invalidate_pending_)
return;
invalidate_pending_ = true;
// Present the scene ASAP. Pass in the last presentation time; otherwise, if
// presentation_time argument is less than the previous time passed to
// PresentScene, the Session will be closed.
// (We cannot use the current time because the last requested presentation
// time, |last_presentation_time_|, could still be in the future. This is
// because Session.Present() returns after it _begins_ preparing the given
// frame, not after it is presented.)
if (!present_pending_)
PresentScene(last_presentation_time_);
}
void BaseView::PresentScene() {
PresentScene(last_presentation_time_);
}
void BaseView::OnScenicEvent(std::vector<fuchsia::ui::scenic::Event> events) {
TRACE_DURATION("view", "BaseView::OnScenicEvent");
for (auto& event : events) {
switch (event.Which()) {
case ::fuchsia::ui::scenic::Event::Tag::kGfx:
switch (event.gfx().Which()) {
case ::fuchsia::ui::gfx::Event::Tag::kViewPropertiesChanged: {
auto& evt = event.gfx().view_properties_changed();
FML_DCHECK(view_->id() == evt.view_id);
auto old_props = view_properties_;
view_properties_ = evt.properties;
::fuchsia::ui::gfx::BoundingBox layout_box =
ViewPropertiesLayoutBox(view_properties_);
logical_size_ = scenic::Max(layout_box.max - layout_box.min, 0.f);
physical_size_.x = logical_size_.x * metrics_.scale_x;
physical_size_.y = logical_size_.y * metrics_.scale_y;
physical_size_.z = logical_size_.z * metrics_.scale_z;
OnPropertiesChanged(std::move(old_props));
InvalidateScene();
break;
}
case fuchsia::ui::gfx::Event::Tag::kMetrics: {
auto& evt = event.gfx().metrics();
if (evt.node_id == root_node_.id()) {
auto old_metrics = metrics_;
metrics_ = std::move(evt.metrics);
physical_size_.x = logical_size_.x * metrics_.scale_x;
physical_size_.y = logical_size_.y * metrics_.scale_y;
physical_size_.z = logical_size_.z * metrics_.scale_z;
OnMetricsChanged(std::move(old_metrics));
InvalidateScene();
}
break;
}
default: {
OnScenicEvent(std::move(event));
}
}
break;
case ::fuchsia::ui::scenic::Event::Tag::kInput: {
if (event.input().Which() ==
fuchsia::ui::input::InputEvent::Tag::kFocus &&
enable_ime_) {
OnHandleFocusEvent(event.input().focus());
}
OnInputEvent(std::move(event.input()));
break;
}
case ::fuchsia::ui::scenic::Event::Tag::kUnhandled: {
OnUnhandledCommand(std::move(event.unhandled()));
break;
}
default: {
OnScenicEvent(std::move(event));
}
}
}
}
void BaseView::PresentScene(zx_time_t presentation_time) {
TRACE_DURATION("view", "BaseView::PresentScene");
// TODO(fxbug.dev/24406): Remove this when BaseView::PresentScene() is
// deprecated, see fxbug.dev/24573.
if (present_pending_)
return;
present_pending_ = true;
// Keep track of the most recent presentation time we've passed to
// Session.Present(), for use in InvalidateScene().
last_presentation_time_ = presentation_time;
TRACE_FLOW_BEGIN("gfx", "Session::Present", session_present_count_);
++session_present_count_;
session()->Present(
presentation_time,
[this, present_callbacks = std::move(callbacks_for_next_present_)](
fuchsia::images::PresentationInfo info) mutable {
TRACE_DURATION("view", "BaseView::PresentationCallback");
TRACE_FLOW_END("gfx", "present_callback", info.presentation_time);
FML_DCHECK(present_pending_);
zx_time_t next_presentation_time =
info.presentation_time + info.presentation_interval;
bool present_needed = false;
if (invalidate_pending_) {
invalidate_pending_ = false;
OnSceneInvalidated(std::move(info));
present_needed = true;
}
for (auto& callback : present_callbacks) {
callback(info);
}
present_pending_ = false;
if (present_needed)
PresentScene(next_presentation_time);
});
callbacks_for_next_present_.clear();
}
// |fuchsia::ui::input::InputMethodEditorClient|
void BaseView::DidUpdateState(
fuchsia::ui::input::TextInputState state,
std::unique_ptr<fuchsia::ui::input::InputEvent> input_event) {
if (input_event) {
const fuchsia::ui::input::InputEvent& input = *input_event;
fuchsia::ui::input::InputEvent input_event_copy;
fidl::Clone(input, &input_event_copy);
OnInputEvent(std::move(input_event_copy));
}
}
// |fuchsia::ui::input::InputMethodEditorClient|
void BaseView::OnAction(fuchsia::ui::input::InputMethodAction action) {}
bool BaseView::OnHandleFocusEvent(const fuchsia::ui::input::FocusEvent& focus) {
if (focus.focused) {
ActivateIme();
return true;
} else if (!focus.focused) {
DeactivateIme();
return true;
}
return false;
}
void BaseView::ActivateIme() {
ime_manager_->GetInputMethodEditor(
fuchsia::ui::input::KeyboardType::TEXT, // keyboard type
fuchsia::ui::input::InputMethodAction::DONE, // input method action
fuchsia::ui::input::TextInputState{}, // initial state
ime_client_.NewBinding(), // client
ime_.NewRequest() // editor
);
}
void BaseView::DeactivateIme() {
if (ime_) {
ime_manager_->HideKeyboard();
ime_ = nullptr;
}
if (ime_client_.is_bound()) {
ime_client_.Unbind();
}
}
} // namespace scenic

View File

@ -1,210 +0,0 @@
// 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 SRC_LIB_UI_BASE_VIEW_BASE_VIEW_H_
#define SRC_LIB_UI_BASE_VIEW_BASE_VIEW_H_
#include <fuchsia/ui/gfx/cpp/fidl.h>
#include <fuchsia/ui/input/cpp/fidl.h>
#include <fuchsia/ui/scenic/cpp/fidl.h>
#include <lib/sys/cpp/component_context.h>
#include <lib/ui/scenic/cpp/resources.h>
#include <lib/ui/scenic/cpp/session.h>
#include <lib/ui/scenic/cpp/view_ref_pair.h>
#include "src/lib/ui/base_view/embedded_view_utils.h"
#include "src/lib/ui/base_view/math.h"
namespace scenic {
// Parameters for creating a BaseView.
struct ViewContext {
scenic::SessionPtrAndListenerRequest session_and_listener_request;
fuchsia::ui::views::ViewToken view_token;
std::optional<ViewRefPair> view_ref_pair;
sys::ComponentContext* component_context;
bool enable_ime = false;
};
// Abstract base implementation of a view for simple applications.
// Subclasses must handle layout and provide content for the scene by
// overriding the virtual methods defined in this class.
//
// It is not necessary to use this class to implement all Views.
// This class is merely intended to make the simple apps easier to write.
class BaseView : private fuchsia::ui::scenic::SessionListener,
private fuchsia::ui::input::InputMethodEditorClient {
public:
using PresentCallback =
fit::function<void(const fuchsia::images::PresentationInfo& info)>;
// Subclasses are typically created by ViewProviderService::CreateView(),
// which provides the necessary args to pass down to this base class.
BaseView(ViewContext context, const std::string& debug_name);
BaseView(const BaseView&) = delete;
// |root_node| is the node directly under our View; i.e. it's the top-most
// node within the tree under our View. Use it to attach any resources for
// your UI.
scenic::EntityNode& root_node() { return root_node_; }
Session* session() { return &session_; }
sys::ComponentContext* component_context() { return component_context_; }
fuchsia::ui::gfx::ViewProperties view_properties() const {
return view_properties_;
}
// Returns true if the view has a non-empty size in logical pixels.
bool has_logical_size() const {
auto& sz = logical_size();
return sz.x > 0.f && sz.y > 0.f && sz.z > 0.f;
}
// Gets the size of the view in logical pixels.
// This value is zero until the view receives a layout from its parent.
const fuchsia::ui::gfx::vec3& logical_size() const { return logical_size_; }
// Returns true if the view has a non-empty size in physical pixels.
bool has_physical_size() const {
auto& sz = physical_size();
return sz.x > 0.f && sz.y > 0.f && sz.z > 0.f;
}
// Gets the size of the view in physical pixels.
// This value is zero until the view receives a layout from its parent
// and metrics from its session.
const fuchsia::ui::gfx::vec3& physical_size() const { return physical_size_; }
// Returns true if the view has received metrics from its session.
bool has_metrics() const {
return metrics_.scale_x > 0.f && metrics_.scale_y > 0.f &&
metrics_.scale_z > 0.f;
}
// Gets the view's metrics.
// This value is zero until the view receives metrics from its session.
const fuchsia::ui::gfx::Metrics& metrics() const { return metrics_; }
// Sets a callback which is invoked when the view's owner releases the
// view causing the view manager to unregister it.
//
// This should be used to implement cleanup policies to release resources
// associated with the view (including the object itself).
void SetReleaseHandler(fit::function<void(zx_status_t)> callback);
// Invalidates the scene, causing |OnSceneInvalidated()| to be invoked
// during the next frame. When the Present() callback corresponding to this
// invalidate is invoked, the optional |present_callback| will also be
// invoked.
void InvalidateScene(PresentCallback present_callback = nullptr);
// Called when it's time for the view to update its scene contents due to
// invalidation. The new contents are presented once this function returns.
//
// The default implementation does nothing.
virtual void OnSceneInvalidated(
fuchsia::images::PresentationInfo presentation_info) {}
// Called when the view's properties have changed.
//
// The subclass should compare the old and new properties and make note of
// whether these property changes will affect the layout or content of
// the view then update accordingly.
//
// The default implementation does nothing.
virtual void OnPropertiesChanged(
fuchsia::ui::gfx::ViewProperties old_properties) {}
// Called when the view's metrics have changed.
//
// The subclass should compare the old and new metrics and make note of
// whether this change will affect the layout or content of the view then
// update accordingly.
//
// The default implementation does nothing.
virtual void OnMetricsChanged(fuchsia::ui::gfx::Metrics old_metrics){};
// Called to handle an input event.
//
// The default implementation does nothing.
virtual void OnInputEvent(fuchsia::ui::input::InputEvent event) {}
// Called when a command sent by the client was not handled by Scenic.
//
// The default implementation does nothing.
virtual void OnUnhandledCommand(fuchsia::ui::scenic::Command unhandled) {}
// Called when an event that is not handled directly by BaseView is received.
// For example, BaseView handles fuchsia::ui::gfx::ViewPropertiesChangedEvent,
// and notifies the subclass via OnPropertiesChanged(); not all events are
// handled in this way.
//
// The default implementation does nothing.
virtual void OnScenicEvent(fuchsia::ui::scenic::Event) {}
protected:
// An alternative way to update the scene. Provide a faster way to cause a
// present in comparison to InvalidateScene(). Caller should update the
// scene contents before calling this method.
void PresentScene();
private:
// |scenic::SessionListener|
//
// Iterates over the received events and either handles them in a sensible way
// (e.g. fuchsia::ui::gfx::ViewPropertiesChangedEvent is handled by invoking
// the virtual method OnPropertiesChanged()), or delegates handling to the
// subclass via the single-event version of OnEvent() above.
//
// Subclasses should not override this.
void OnScenicEvent(std::vector<fuchsia::ui::scenic::Event> events) override;
// |fuchsia::ui::input::InputMethodEditorClient|
void DidUpdateState(
fuchsia::ui::input::TextInputState state,
std::unique_ptr<fuchsia::ui::input::InputEvent> event) override;
// |fuchsia::ui::input::InputMethodEditorClient|
void OnAction(fuchsia::ui::input::InputMethodAction action) override;
void PresentScene(zx_time_t presentation_time);
// Handles focus event when IME is enabled. This event is used to activate
// or deactivate the IME client.
bool OnHandleFocusEvent(const fuchsia::ui::input::FocusEvent& focus);
// Gets a new input method editor from the IME manager.
void ActivateIme();
// Detaches the input method editor connection, ending the edit session and
// closing the onscreen keyboard.
void DeactivateIme();
sys::ComponentContext* const component_context_;
fidl::Binding<fuchsia::ui::scenic::SessionListener> listener_binding_;
Session session_;
std::optional<scenic::View> view_;
scenic::EntityNode root_node_;
fidl::Binding<fuchsia::ui::input::InputMethodEditorClient> ime_client_;
fuchsia::ui::input::InputMethodEditorPtr ime_;
fuchsia::ui::input::ImeServicePtr ime_manager_;
fuchsia::ui::gfx::vec3 logical_size_;
fuchsia::ui::gfx::vec3 physical_size_;
fuchsia::ui::gfx::ViewProperties view_properties_;
fuchsia::ui::gfx::Metrics metrics_;
zx_time_t last_presentation_time_ = 0;
size_t session_present_count_ = 0;
bool invalidate_pending_ = false;
std::vector<PresentCallback> callbacks_for_next_present_;
bool present_pending_ = false;
bool enable_ime_ = false;
};
} // namespace scenic
#endif // SRC_LIB_UI_BASE_VIEW_BASE_VIEW_H_

View File

@ -1,48 +0,0 @@
// 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 "src/lib/ui/base_view/embedded_view_utils.h"
#include <lib/ui/scenic/cpp/view_ref_pair.h>
#include <lib/ui/scenic/cpp/view_token_pair.h>
#include "flutter/fml/logging.h"
namespace scenic {
EmbeddedViewInfo LaunchComponentAndCreateView(
const fuchsia::sys::LauncherPtr& launcher,
const std::string& component_url,
const std::vector<std::string>& component_args) {
FML_DCHECK(launcher);
EmbeddedViewInfo info;
// Configure the information to launch the component with.
fuchsia::sys::LaunchInfo launch_info;
info.app_services =
sys::ServiceDirectory::CreateWithRequest(&launch_info.directory_request);
launch_info.url = component_url;
launch_info.arguments = fidl::VectorPtr(
std::vector<std::string>(component_args.begin(), component_args.end()));
launcher->CreateComponent(std::move(launch_info),
info.controller.NewRequest());
info.view_provider =
info.app_services->Connect<fuchsia::ui::app::ViewProvider>();
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
info.view_holder_token = std::move(view_holder_token);
auto [view_ref_control, view_ref] = scenic::ViewRefPair::New();
fidl::Clone(view_ref, &info.view_ref);
info.view_provider->CreateViewWithViewRef(std::move(view_token.value),
std::move(view_ref_control),
std::move(view_ref));
return info;
}
} // namespace scenic

View File

@ -1,58 +0,0 @@
// 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 SRC_LIB_UI_BASE_VIEW_EMBEDDED_VIEW_UTILS_H_
#define SRC_LIB_UI_BASE_VIEW_EMBEDDED_VIEW_UTILS_H_
#include <fuchsia/ui/app/cpp/fidl.h>
#include <fuchsia/ui/gfx/cpp/fidl.h>
#include <fuchsia/ui/input/cpp/fidl.h>
#include <fuchsia/ui/scenic/cpp/fidl.h>
#include <fuchsia/ui/views/cpp/fidl.h>
#include <lib/sys/cpp/service_directory.h>
#include <lib/ui/scenic/cpp/resources.h>
#include <lib/ui/scenic/cpp/session.h>
namespace scenic {
// Serves as the return value for LaunchAppAndCreateView(), below.
struct EmbeddedViewInfo {
// Controls the launched app. The app will be destroyed if this connection
// is closed.
fuchsia::sys::ComponentControllerPtr controller;
// Services provided by the launched app. Must not be destroyed
// immediately, otherwise the |view_provider| connection may not be
// established.
std::shared_ptr<sys::ServiceDirectory> app_services;
// ViewProvider service obtained from the app via |app_services|. Must not
// be destroyed immediately, otherwise the call to CreateView() might not be
// processed.
fuchsia::ui::app::ViewProviderPtr view_provider;
// A token that can be used to create a ViewHolder; the corresponding token
// was provided to |view_provider| via ViewProvider.CreateView(). The
// launched app is expected to create a View, which will be connected to the
// ViewHolder created with this token.
fuchsia::ui::views::ViewHolderToken view_holder_token;
// The ViewRef of the embedded View.
fuchsia::ui::views::ViewRef view_ref;
};
// Launch a component and connect to its ViewProvider service, passing it the
// necessary information to attach itself as a child view2. Populates the
// returned EmbeddedViewInfo, which the caller can use to embed the child.
// For example, an interface to a ViewProvider is obtained, a pair of
// zx::eventpairs is created, CreateView is called, etc. This encapsulates
// the boilerplate the client would otherwise write themselves.
EmbeddedViewInfo LaunchComponentAndCreateView(
const fuchsia::sys::LauncherPtr& launcher,
const std::string& component_url,
const std::vector<std::string>& component_args = {});
} // namespace scenic
#endif // SRC_LIB_UI_BASE_VIEW_EMBEDDED_VIEW_UTILS_H_

View File

@ -1,92 +0,0 @@
// 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 SRC_LIB_UI_BASE_VIEW_MATH_H_
#define SRC_LIB_UI_BASE_VIEW_MATH_H_
#include <fuchsia/ui/gfx/cpp/fidl.h>
namespace scenic {
// Return a vec3 consisting of the component-wise sum of the two arguments.
inline fuchsia::ui::gfx::vec3 operator+(const fuchsia::ui::gfx::vec3& a,
const fuchsia::ui::gfx::vec3& b) {
return {.x = a.x + b.x, .y = a.y + b.y, .z = a.z + b.z};
}
// Return a vec3 consisting of the component-wise difference of the two args.
inline fuchsia::ui::gfx::vec3 operator-(const fuchsia::ui::gfx::vec3& a,
const fuchsia::ui::gfx::vec3& b) {
return {.x = a.x - b.x, .y = a.y - b.y, .z = a.z - b.z};
}
// Return true if |point| is contained by |box|, including when it is on the
// box boundary, and false otherwise.
inline bool ContainsPoint(const fuchsia::ui::gfx::BoundingBox& box,
const fuchsia::ui::gfx::vec3& point) {
return point.x >= box.min.x && point.y >= box.min.y && point.z >= box.min.z &&
point.x <= box.max.x && point.y <= box.max.y && point.z <= box.max.z;
}
// Similar to fuchsia::ui::gfx::ViewProperties: adds the inset to box.min, and
// subtracts it from box.max.
inline fuchsia::ui::gfx::BoundingBox InsetBy(
const fuchsia::ui::gfx::BoundingBox& box,
const fuchsia::ui::gfx::vec3& inset) {
return {.min = box.min + inset, .max = box.max - inset};
}
// Similar to fuchsia::ui::gfx::ViewProperties: adds the inset to box.min, and
// subtracts it from box.max.
inline fuchsia::ui::gfx::BoundingBox InsetBy(
const fuchsia::ui::gfx::BoundingBox& box,
const fuchsia::ui::gfx::vec3& inset_from_min,
const fuchsia::ui::gfx::vec3& inset_from_max) {
return {.min = box.min + inset_from_min, .max = box.max - inset_from_max};
}
// Inset the view properties' outer box by its insets.
inline fuchsia::ui::gfx::BoundingBox ViewPropertiesLayoutBox(
const fuchsia::ui::gfx::ViewProperties& view_properties) {
return InsetBy(view_properties.bounding_box, view_properties.inset_from_min,
view_properties.inset_from_max);
}
// Return a vec3 consisting of the maximum x/y/z from the two arguments.
inline fuchsia::ui::gfx::vec3 Max(const fuchsia::ui::gfx::vec3& a,
const fuchsia::ui::gfx::vec3& b) {
return {.x = std::max(a.x, b.x),
.y = std::max(a.y, b.y),
.z = std::max(a.z, b.z)};
}
// Return a vec3 consisting of the maximum of the x/y/z components of |v|,
// compared with |min_val|.
inline fuchsia::ui::gfx::vec3 Max(const fuchsia::ui::gfx::vec3& v,
float min_val) {
return {.x = std::max(v.x, min_val),
.y = std::max(v.y, min_val),
.z = std::max(v.z, min_val)};
}
// Return a vec3 consisting of the minimum x/y/z from the two arguments.
inline fuchsia::ui::gfx::vec3 Min(const fuchsia::ui::gfx::vec3& a,
const fuchsia::ui::gfx::vec3& b) {
return {.x = std::min(a.x, b.x),
.y = std::min(a.y, b.y),
.z = std::min(a.z, b.z)};
}
// Return a vec3 consisting of the minimum of the x/y/z components of |v|,
// compared with |max_val|.
inline fuchsia::ui::gfx::vec3 Min(const fuchsia::ui::gfx::vec3& v,
float max_val) {
return {.x = std::min(v.x, max_val),
.y = std::min(v.y, max_val),
.z = std::min(v.z, max_val)};
}
} // namespace scenic
#endif // SRC_LIB_UI_BASE_VIEW_MATH_H_

View File

@ -1,24 +0,0 @@
# 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.
import("//build/fuchsia/sdk.gni")
source_set("views") {
testonly = true
sources = [
"color.cc",
"color.h",
"embedder_view.cc",
"embedder_view.h",
]
include_dirs = [ "//flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing" ]
public_deps = [
"$fuchsia_sdk_root/pkg:scenic_cpp",
"//flutter/fml",
"//flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view",
]
}

View File

@ -1,88 +0,0 @@
// 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 <zircon/status.h>
#include "flutter/fml/logging.h"
#include "src/ui/testing/views/embedder_view.h"
namespace scenic {
EmbedderView::EmbedderView(ViewContext context, const std::string& debug_name)
: binding_(this, std::move(context.session_and_listener_request.second)),
session_(std::move(context.session_and_listener_request.first)),
view_(&session_, std::move(context.view_token), debug_name),
top_node_(&session_) {
binding_.set_error_handler([](zx_status_t status) {
FML_LOG(FATAL) << "Session listener binding: "
<< zx_status_get_string(status);
});
view_.AddChild(top_node_);
// Call |Session::Present| in order to flush events having to do with
// creation of |view_| and |top_node_|.
session_.Present(0, [](auto) {});
}
// Sets the EmbeddedViewInfo and attaches the embedded View to the scene. Any
// callbacks for the embedded View's ViewState are delivered to the supplied
// callback.
void EmbedderView::EmbedView(EmbeddedViewInfo info,
std::function<void(fuchsia::ui::gfx::ViewState)>
view_state_changed_callback) {
// Only one EmbeddedView is currently supported.
FML_CHECK(!embedded_view_);
embedded_view_ = std::make_unique<EmbeddedView>(
std::move(info), &session_, std::move(view_state_changed_callback));
// Attach the embedded view to the scene.
top_node_.Attach(embedded_view_->view_holder);
// Call |Session::Present| to apply the embedded view to the scene graph.
session_.Present(0, [](auto) {});
}
void EmbedderView::OnScenicEvent(
std::vector<fuchsia::ui::scenic::Event> events) {
for (const auto& event : events) {
if (event.Which() == fuchsia::ui::scenic::Event::Tag::kGfx &&
event.gfx().Which() ==
fuchsia::ui::gfx::Event::Tag::kViewPropertiesChanged) {
const auto& evt = event.gfx().view_properties_changed();
// Naively apply the parent's ViewProperties to any EmbeddedViews.
if (embedded_view_) {
embedded_view_->view_holder.SetViewProperties(
std::move(evt.properties));
session_.Present(0, [](auto) {});
}
} else if (event.Which() == fuchsia::ui::scenic::Event::Tag::kGfx &&
event.gfx().Which() ==
fuchsia::ui::gfx::Event::Tag::kViewStateChanged) {
const auto& evt = event.gfx().view_state_changed();
if (embedded_view_ &&
evt.view_holder_id == embedded_view_->view_holder.id()) {
// Clients of |EmbedderView| *must* set a view state changed
// callback. Failure to do so is a usage error.
FML_CHECK(embedded_view_->view_state_changed_callback);
embedded_view_->view_state_changed_callback(evt.state);
}
}
}
}
void EmbedderView::OnScenicError(std::string error) {
FML_LOG(FATAL) << "OnScenicError: " << error;
}
EmbedderView::EmbeddedView::EmbeddedView(
EmbeddedViewInfo info,
Session* session,
std::function<void(fuchsia::ui::gfx::ViewState)> view_state_callback,
const std::string& debug_name)
: embedded_info(std::move(info)),
view_holder(session,
std::move(embedded_info.view_holder_token),
debug_name + " ViewHolder"),
view_state_changed_callback(std::move(view_state_callback)) {}
} // namespace scenic

View File

@ -1,59 +0,0 @@
// 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 SRC_UI_TESTING_VIEWS_EMBEDDER_VIEW_H_
#define SRC_UI_TESTING_VIEWS_EMBEDDER_VIEW_H_
#include <fuchsia/ui/scenic/cpp/fidl.h>
#include <lib/ui/scenic/cpp/resources.h>
#include <lib/ui/scenic/cpp/session.h>
#include "src/lib/ui/base_view/base_view.h"
namespace scenic {
// This is a simplified |BaseView| that exposes view state events.
//
// See also lib/ui/base_view.
class EmbedderView : public fuchsia::ui::scenic::SessionListener {
public:
EmbedderView(ViewContext context,
const std::string& debug_name = "EmbedderView");
// Sets the EmbeddedViewInfo and attaches the embedded View to the scene. Any
// callbacks for the embedded View's ViewState are delivered to the supplied
// callback.
void EmbedView(EmbeddedViewInfo info,
std::function<void(fuchsia::ui::gfx::ViewState)>
view_state_changed_callback);
private:
// |fuchsia::ui::scenic::SessionListener|
void OnScenicEvent(std::vector<fuchsia::ui::scenic::Event> events) override;
// |fuchsia::ui::scenic::SessionListener|
void OnScenicError(std::string error) override;
struct EmbeddedView {
EmbeddedView(
EmbeddedViewInfo info,
Session* session,
std::function<void(fuchsia::ui::gfx::ViewState)> view_state_callback,
const std::string& debug_name = "EmbedderView");
EmbeddedViewInfo embedded_info;
ViewHolder view_holder;
std::function<void(fuchsia::ui::gfx::ViewState)>
view_state_changed_callback;
};
fidl::Binding<fuchsia::ui::scenic::SessionListener> binding_;
Session session_;
View view_;
EntityNode top_node_;
std::optional<fuchsia::ui::gfx::ViewProperties> embedded_view_properties_;
std::unique_ptr<EmbeddedView> embedded_view_;
};
} // namespace scenic
#endif // SRC_UI_TESTING_VIEWS_EMBEDDER_VIEW_H_

View File

@ -2,7 +2,11 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
group("integration_flutter_tests") {
assert(is_fuchsia)
import("//build/fuchsia/sdk.gni")
group("integration") {
testonly = true
deps = [ "embedder:tests" ]
}

View File

@ -0,0 +1,84 @@
# `flutter integration tests`
## Configure and build fuchsia
```shell
$ cd "$FUCHSIA_DIR"
$ fx set terminal.x64
$ fx build
```
## Build the test
You can specify the test's package target to build only the test package, with
its dependencies. This will also build the required runner.
```shell
$ cd "$ENGINE_DIR/src"
$ ./flutter/tools/gn --fuchsia <flags> \
# for example: --goma --fuchsia-cpu=x64 --runtime-mode=debug
$ ninja -C out/fuchsia_debug_x64 \
flutter/shell/platform/fuchsia/flutter/tests/integration
```
## Start an emulator
```shell
ffx emu start --net tap
```
NOTE: Do _not_ run the default package server. The instructions below describe
how to launch a flutter-specific package server.
## Publish the test packages to the Fuchsia package server
The tests currently specify the Fuchsia package server's standard domain,
`fuchsia.com`, as the server to use to resolve (locate and load) the test
packages. So, before running the test, the most recently built `.far` files
need to be published to the Fuchsia package repo:
```shell
$ fx pm publish -a -repo "$(cat $FUCHSIA_DIR/.fx-build-dir)/amber-files/" \
-f "$FLUTTER_ENGINE_DIR"/src/out/fuchsia_*64/oot_flutter_jit_runner-0.far
$ fx pm publish -a -repo "$(cat $FUCHSIA_DIR/.fx-build-dir)/amber-files/" \
-f "$FLUTTER_ENGINE_DIR"/src/out/fuchsia_*64/flutter-embedder-test-0.far
$ fx pm publish -a -repo "$(cat $FUCHSIA_DIR/.fx-build-dir)/amber-files/" \
-f $(find "$FLUTTER_ENGINE_DIR"/src/out/fuchsia_*64 -name parent-view.far)
$ fx pm publish -a -repo "$(cat $FUCHSIA_DIR/.fx-build-dir)/amber-files/" \
-f $(find "$FLUTTER_ENGINE_DIR"/src/out/fuchsia_*64 -name child-view.far)
```
## Run the test
```shell
$ ffx test run fuchsia-pkg:://fuchsia.com/flutter-embedder-test#meta/flutter-embedder-test.cm
```
If, for example, you only make a change to the Dart code in `parent-view`, you
can rebuild only the parent-view package target, and republish it.
```shell
$ ninja -C out/fuchsia_debug_x64 \
flutter/shell/platform/fuchsia/flutter/tests/integration/embedder/parent-view:package
$ fx pm publish -a -repo "$(cat $FUCHSIA_DIR/.fx-build-dir)/amber-files/" \
-f $(find "$FLUTTER_ENGINE_DIR"/src/out/fuchsia_*64 -name parent-view.far)
```
Then re-run the test as above.
The tests use a flutter runner with "oot_" prefixed to its package name, to
avoid conflicting with any flutter_runner package in the base fuchsia image.
After making a change to the flutter_runner you can re-deploy it with:
```shell
$ ninja -C out/fuchsia_debug_x64 \
flutter/shell/platform/fuchsia/flutter:oot_flutter_jit_runner
$ fx pm publish -a -repo "$(cat $FUCHSIA_DIR/.fx-build-dir)/amber-files/" \
-f $(find "$FLUTTER_ENGINE_DIR"/src/out/fuchsia_*64 -name oot_flutter_jit_runner.far)
```
Then re-run the test as above.
From here, you can modify the Flutter test, rebuild flutter, and usually rerun
the test without rebooting, by repeating the commands above.

View File

@ -0,0 +1,65 @@
# Copyright 2013 The Flutter Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
assert(is_fuchsia)
import("//build/fuchsia/sdk.gni")
import("//flutter/tools/fuchsia/fuchsia_archive.gni")
group("tests") {
testonly = true
deps = [ ":flutter-embedder-test" ]
}
executable("flutter-embedder-test-bin") {
testonly = true
output_name = "flutter-embedder-test"
sources = [
"color.cc",
"color.h",
"flutter-embedder-test.cc",
]
# This is needed for //third_party/googletest for linking zircon symbols.
libs = [ "$fuchsia_sdk_path/arch/$target_cpu/sysroot/lib/libzircon.so" ]
deps = [
"$fuchsia_sdk_root/fidl:fuchsia.logger",
"$fuchsia_sdk_root/fidl:fuchsia.tracing.provider",
"$fuchsia_sdk_root/fidl:fuchsia.ui.app",
"$fuchsia_sdk_root/fidl:fuchsia.ui.composition",
"$fuchsia_sdk_root/fidl:fuchsia.ui.observation.geometry",
"$fuchsia_sdk_root/fidl:fuchsia.ui.scenic",
"$fuchsia_sdk_root/fidl:fuchsia.ui.test.input",
"$fuchsia_sdk_root/fidl:fuchsia.ui.test.scene",
"$fuchsia_sdk_root/pkg:async",
"$fuchsia_sdk_root/pkg:async-loop-testing",
"$fuchsia_sdk_root/pkg:fidl_cpp",
"$fuchsia_sdk_root/pkg:scenic_cpp",
"$fuchsia_sdk_root/pkg:sys_component_cpp_testing",
"$fuchsia_sdk_root/pkg:zx",
"//flutter/fml",
"//third_party/googletest:gtest",
"//third_party/googletest:gtest_main",
]
}
fuchsia_test_archive("flutter-embedder-test") {
deps = [
":flutter-embedder-test-bin",
"child-view:package",
"parent-view:package",
# "OOT" copies of the runners used by tests, to avoid conflicting with the
# runners in the base fuchsia image.
# TODO(fxbug.dev/106575): Fix this with subpackages.
"//flutter/shell/platform/fuchsia/flutter:oot_flutter_jit_runner",
]
binary = "$target_name"
cml_file = rebase_path("meta/$target_name.cml")
}

View File

@ -0,0 +1,26 @@
# 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.
import("//build/fuchsia/sdk.gni")
import("//flutter/tools/fuchsia/dart/dart_library.gni")
import("//flutter/tools/fuchsia/flutter/flutter_component.gni")
import("//flutter/tools/fuchsia/gn-sdk/package.gni")
dart_library("lib") {
package_name = "child-view"
sources = [ "child_view.dart" ]
}
flutter_component("component") {
main_package = "child-view"
component_name = "child-view"
main_dart = "child_view.dart"
manifest = rebase_path("meta/child-view.cml")
deps = [ ":lib" ]
}
fuchsia_package("package") {
package_name = "child-view"
deps = [ ":component" ]
}

View File

@ -4,10 +4,10 @@
import 'dart:ui';
TestApp app;
void main(List<String> args) {
app = TestApp();
print('child-view: starting');
TestApp app = TestApp();
app.run();
}
@ -19,11 +19,15 @@ class TestApp {
void run() {
window.onPointerDataPacket = (PointerDataPacket packet) {
app.pointerDataPacket(packet);
this.pointerDataPacket(packet);
};
window.onMetricsChanged = () {
window.scheduleFrame();
};
window.onBeginFrame = (Duration duration) {
app.beginFrame(duration);
this.beginFrame(duration);
};
window.scheduleFrame();
}
@ -46,7 +50,7 @@ class TestApp {
void pointerDataPacket(PointerDataPacket packet) {
for (final data in packet.data) {
if (data.change == PointerChange.up) {
if (data.change == PointerChange.down) {
this._backgroundColor = _yellow;
}
}

View File

@ -0,0 +1,21 @@
{
include: [ "syslog/client.shard.cml" ],
program: {
data: "data/child-view",
// Always use the jit runner for now.
// TODO(fxbug.dev/106577): Implement manifest merging build rules for V2 components.
runner: "flutter_jit_runner",
},
capabilities: [
{
protocol: [ "fuchsia.ui.app.ViewProvider" ],
},
],
expose: [
{
protocol: [ "fuchsia.ui.app.ViewProvider" ],
from: "self",
},
],
}

View File

@ -2,10 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "color.h"
#include <zircon/status.h>
#include "flutter/fml/logging.h"
#include "src/ui/testing/views/color.h"
namespace scenic {

View File

@ -0,0 +1,602 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fuchsia/logger/cpp/fidl.h>
#include <fuchsia/tracing/provider/cpp/fidl.h>
#include <fuchsia/ui/app/cpp/fidl.h>
#include <fuchsia/ui/composition/cpp/fidl.h>
#include <fuchsia/ui/observation/geometry/cpp/fidl.h>
#include <fuchsia/ui/scenic/cpp/fidl.h>
#include <fuchsia/ui/test/input/cpp/fidl.h>
#include <fuchsia/ui/test/scene/cpp/fidl.h>
#include <lib/async-loop/testing/cpp/real_loop.h>
#include <lib/async/cpp/task.h>
#include <lib/fidl/cpp/binding_set.h>
#include <lib/sys/component/cpp/testing/realm_builder.h>
#include <lib/sys/component/cpp/testing/realm_builder_types.h>
#include <lib/ui/scenic/cpp/view_ref_pair.h>
#include <lib/zx/clock.h>
#include <zircon/status.h>
#include <zircon/time.h>
#include <optional>
#include <vector>
#include "flutter/fml/logging.h"
#include "gtest/gtest.h"
#include "color.h"
namespace flutter_embedder_test {
namespace {
// Types imported for the realm_builder library.
using component_testing::ChildOptions;
using component_testing::ChildRef;
using component_testing::DirectoryContents;
using component_testing::ParentRef;
using component_testing::Protocol;
using component_testing::RealmRoot;
using component_testing::Route;
using component_testing::StartupMode;
// The FIDL bindings for this service are not exposed in the Fuchsia SDK, so we
// must encode the name manually here.
constexpr auto kVulkanLoaderServiceName = "fuchsia.vulkan.loader.Loader";
constexpr auto kFlutterJitRunnerUrl =
"fuchsia-pkg://fuchsia.com/oot_flutter_jit_runner#meta/"
"flutter_jit_runner.cm";
constexpr auto kFlutterJitProductRunnerUrl =
"fuchsia-pkg://fuchsia.com/oot_flutter_jit_product_runner#meta/"
"flutter_jit_product_runner.cm";
constexpr auto kFlutterAotRunnerUrl =
"fuchsia-pkg://fuchsia.com/oot_flutter_aot_runner#meta/"
"flutter_aot_runner.cm";
constexpr auto kFlutterAotProductRunnerUrl =
"fuchsia-pkg://fuchsia.com/oot_flutter_aot_product_runner#meta/"
"flutter_aot_product_runner.cm";
constexpr char kChildViewUrl[] =
"fuchsia-pkg://fuchsia.com/child-view#meta/child-view.cm";
constexpr char kParentViewUrl[] =
"fuchsia-pkg://fuchsia.com/parent-view#meta/parent-view.cm";
static constexpr auto kTestUIStackUrl =
"fuchsia-pkg://fuchsia.com/test-ui-stack#meta/test-ui-stack.cm";
constexpr auto kFlutterRunnerEnvironment = "flutter_runner_env";
constexpr auto kFlutterJitRunner = "flutter_jit_runner";
constexpr auto kFlutterJitRunnerRef = ChildRef{kFlutterJitRunner};
constexpr auto kFlutterJitProductRunner = "flutter_jit_product_runner";
constexpr auto kFlutterJitProductRunnerRef = ChildRef{kFlutterJitProductRunner};
constexpr auto kFlutterAotRunner = "flutter_aot_runner";
constexpr auto kFlutterAotRunnerRef = ChildRef{kFlutterAotRunner};
constexpr auto kFlutterAotProductRunner = "flutter_aot_product_runner";
constexpr auto kFlutterAotProductRunnerRef = ChildRef{kFlutterAotProductRunner};
constexpr auto kChildView = "child_view";
constexpr auto kChildViewRef = ChildRef{kChildView};
constexpr auto kParentView = "parent_view";
constexpr auto kParentViewRef = ChildRef{kParentView};
constexpr auto kTestUIStack = "ui";
constexpr auto kTestUIStackRef = ChildRef{kTestUIStack};
constexpr scenic::Color kParentBackgroundColor = {0x00, 0x00, 0xFF,
0xFF}; // Blue
constexpr scenic::Color kParentTappedColor = {0x00, 0x00, 0x00, 0xFF}; // Black
constexpr scenic::Color kChildBackgroundColor = {0xFF, 0x00, 0xFF,
0xFF}; // Pink
constexpr scenic::Color kChildTappedColor = {0xFF, 0xFF, 0x00, 0xFF}; // Yellow
// TODO(fxb/64201): Remove forced opacity colors when Flatland is enabled.
constexpr scenic::Color kOverlayBackgroundColor1 = {
0x00, 0xFF, 0x0E, 0xFF}; // Green, blended with blue (FEMU local)
constexpr scenic::Color kOverlayBackgroundColor2 = {
0x0E, 0xFF, 0x0E, 0xFF}; // Green, blended with pink (FEMU local)
constexpr scenic::Color kOverlayBackgroundColor3 = {
0x00, 0xFF, 0x0D, 0xFF}; // Green, blended with blue (AEMU infra)
constexpr scenic::Color kOverlayBackgroundColor4 = {
0x0D, 0xFF, 0x0D, 0xFF}; // Green, blended with pink (AEMU infra)
constexpr scenic::Color kOverlayBackgroundColor5 = {
0x00, 0xFE, 0x0D, 0xFF}; // Green, blended with blue (NUC)
constexpr scenic::Color kOverlayBackgroundColor6 = {
0x0D, 0xFF, 0x00, 0xFF}; // Green, blended with pink (NUC)
static size_t OverlayPixelCount(std::map<scenic::Color, size_t>& histogram) {
return histogram[kOverlayBackgroundColor1] +
histogram[kOverlayBackgroundColor2] +
histogram[kOverlayBackgroundColor3] +
histogram[kOverlayBackgroundColor4] +
histogram[kOverlayBackgroundColor5] +
histogram[kOverlayBackgroundColor6];
}
// Timeout for Scenic's |TakeScreenshot| FIDL call.
constexpr zx::duration kScreenshotTimeout = zx::sec(10);
// Timeout to fail the test if it goes beyond this duration.
constexpr zx::duration kTestTimeout = zx::min(1);
bool CheckViewExistsInSnapshot(
const fuchsia::ui::observation::geometry::ViewTreeSnapshot& snapshot,
zx_koid_t view_ref_koid) {
if (!snapshot.has_views()) {
return false;
}
auto snapshot_count =
std::count_if(snapshot.views().begin(), snapshot.views().end(),
[view_ref_koid](const auto& view) {
return view.view_ref_koid() == view_ref_koid;
});
return snapshot_count > 0;
}
bool CheckViewExistsInUpdates(
const std::vector<fuchsia::ui::observation::geometry::ViewTreeSnapshot>&
updates,
zx_koid_t view_ref_koid) {
auto update_count = std::count_if(
updates.begin(), updates.end(), [view_ref_koid](auto& snapshot) {
return CheckViewExistsInSnapshot(snapshot, view_ref_koid);
});
return update_count > 0;
}
} // namespace
class FlutterEmbedderTest : public ::loop_fixture::RealLoop,
public ::testing::Test {
public:
FlutterEmbedderTest()
: realm_builder_(component_testing::RealmBuilder::Create()) {
FML_VLOG(-1) << "Setting up base realm";
SetUpRealmBase();
// Post a "just in case" quit task, if the test hangs.
async::PostDelayedTask(
dispatcher(),
[] {
FML_LOG(FATAL)
<< "\n\n>> Test did not complete in time, terminating. <<\n\n";
},
kTestTimeout);
}
bool HasViewConnected(
const fuchsia::ui::observation::geometry::ViewTreeWatcherPtr&
view_tree_watcher,
std::optional<fuchsia::ui::observation::geometry::WatchResponse>&
watch_response,
zx_koid_t view_ref_koid);
void LaunchParentViewInRealm(
const std::vector<std::string>& component_args = {});
scenic::Screenshot TakeScreenshot();
bool TakeScreenshotUntil(
scenic::Color color,
fit::function<void(std::map<scenic::Color, size_t>)> callback = nullptr,
zx::duration timeout = kTestTimeout);
// Simulates a tap at location (x, y).
void InjectTap(int32_t x, int32_t y);
// Injects an input event, and posts a task to retry after
// `kTapRetryInterval`.
//
// We post the retry task because the first input event we send to Flutter may
// be lost. The reason the first event may be lost is that there is a race
// condition as the scene owner starts up.
//
// More specifically: in order for our app
// to receive the injected input, two things must be true before we inject
// touch input:
// * The Scenic root view must have been installed, and
// * The Input Pipeline must have received a viewport to inject touch into.
//
// The problem we have is that the `is_rendering` signal that we monitor only
// guarantees us the view is ready. If the viewport is not ready in Input
// Pipeline at that time, it will drop the touch event.
//
// TODO(fxbug.dev/96986): Improve synchronization and remove retry logic.
void TryInject(int32_t x, int32_t y);
private:
fuchsia::ui::scenic::Scenic* scenic() { return scenic_.get(); }
void SetUpRealmBase();
// Registers a fake touch screen device with an injection coordinate space
// spanning [-1000, 1000] on both axes.
void RegisterTouchScreen();
fuchsia::ui::scenic::ScenicPtr scenic_;
fuchsia::ui::test::input::RegistryPtr input_registry_;
fuchsia::ui::test::input::TouchScreenPtr fake_touchscreen_;
fuchsia::ui::test::scene::ControllerPtr scene_provider_;
fuchsia::ui::observation::geometry::ViewTreeWatcherPtr view_tree_watcher_;
// Wrapped in optional since the view is not created until the middle of SetUp
component_testing::RealmBuilder realm_builder_;
std::unique_ptr<component_testing::RealmRoot> realm_;
// The typical latency on devices we've tested is ~60 msec. The retry interval
// is chosen to be a) Long enough that it's unlikely that we send a new tap
// while a previous tap is still being
// processed. That is, it should be far more likely that a new tap is sent
// because the first tap was lost, than because the system is just running
// slowly.
// b) Short enough that we don't slow down tryjobs.
//
// The first property is important to avoid skewing the latency metrics that
// we collect. For an explanation of why a tap might be lost, see the
// documentation for TryInject().
static constexpr auto kTapRetryInterval = zx::sec(1);
};
void FlutterEmbedderTest::SetUpRealmBase() {
FML_LOG(INFO) << "Setting up realm base.";
// First, add the flutter runner(s) as children.
realm_builder_.AddChild(kFlutterJitRunner, kFlutterJitRunnerUrl);
realm_builder_.AddChild(kFlutterJitProductRunner,
kFlutterJitProductRunnerUrl);
realm_builder_.AddChild(kFlutterAotRunner, kFlutterAotRunnerUrl);
realm_builder_.AddChild(kFlutterAotProductRunner,
kFlutterAotProductRunnerUrl);
// Then, add an environment providing them.
fuchsia::component::decl::Environment flutter_runner_environment;
flutter_runner_environment.set_name(kFlutterRunnerEnvironment);
flutter_runner_environment.set_extends(
fuchsia::component::decl::EnvironmentExtends::REALM);
flutter_runner_environment.set_runners({});
auto environment_runners = flutter_runner_environment.mutable_runners();
fuchsia::component::decl::RunnerRegistration flutter_jit_runner_reg;
flutter_jit_runner_reg.set_source(fuchsia::component::decl::Ref::WithChild(
fuchsia::component::decl::ChildRef{.name = kFlutterJitRunner}));
flutter_jit_runner_reg.set_source_name(kFlutterJitRunner);
flutter_jit_runner_reg.set_target_name(kFlutterJitRunner);
environment_runners->push_back(std::move(flutter_jit_runner_reg));
fuchsia::component::decl::RunnerRegistration flutter_jit_product_runner_reg;
flutter_jit_product_runner_reg.set_source(
fuchsia::component::decl::Ref::WithChild(
fuchsia::component::decl::ChildRef{.name =
kFlutterJitProductRunner}));
flutter_jit_product_runner_reg.set_source_name(kFlutterJitProductRunner);
flutter_jit_product_runner_reg.set_target_name(kFlutterJitProductRunner);
environment_runners->push_back(std::move(flutter_jit_product_runner_reg));
fuchsia::component::decl::RunnerRegistration flutter_aot_runner_reg;
flutter_aot_runner_reg.set_source(fuchsia::component::decl::Ref::WithChild(
fuchsia::component::decl::ChildRef{.name = kFlutterAotRunner}));
flutter_aot_runner_reg.set_source_name(kFlutterAotRunner);
flutter_aot_runner_reg.set_target_name(kFlutterAotRunner);
environment_runners->push_back(std::move(flutter_aot_runner_reg));
fuchsia::component::decl::RunnerRegistration flutter_aot_product_runner_reg;
flutter_aot_product_runner_reg.set_source(
fuchsia::component::decl::Ref::WithChild(
fuchsia::component::decl::ChildRef{.name =
kFlutterAotProductRunner}));
flutter_aot_product_runner_reg.set_source_name(kFlutterAotProductRunner);
flutter_aot_product_runner_reg.set_target_name(kFlutterAotProductRunner);
environment_runners->push_back(std::move(flutter_aot_product_runner_reg));
auto realm_decl = realm_builder_.GetRealmDecl();
if (!realm_decl.has_environments()) {
realm_decl.set_environments({});
}
auto realm_environments = realm_decl.mutable_environments();
realm_environments->push_back(std::move(flutter_runner_environment));
realm_builder_.ReplaceRealmDecl(std::move(realm_decl));
// Add test UI stack component.
realm_builder_.AddChild(kTestUIStack, kTestUIStackUrl);
// Add embedded parent and child components.
realm_builder_.AddChild(kChildView, kChildViewUrl,
ChildOptions{
.environment = kFlutterRunnerEnvironment,
});
realm_builder_.AddChild(kParentView, kParentViewUrl,
ChildOptions{
.environment = kFlutterRunnerEnvironment,
});
// Route base system services to flutter runners.
realm_builder_.AddRoute(
Route{.capabilities =
{
Protocol{fuchsia::logger::LogSink::Name_},
Protocol{fuchsia::sysmem::Allocator::Name_},
Protocol{fuchsia::tracing::provider::Registry::Name_},
Protocol{kVulkanLoaderServiceName},
},
.source = ParentRef{},
.targets = {kFlutterJitRunnerRef, kFlutterJitProductRunnerRef,
kFlutterAotRunnerRef, kFlutterAotProductRunnerRef}});
// Route base system services to the test UI stack.
realm_builder_.AddRoute(Route{
.capabilities = {Protocol{fuchsia::logger::LogSink::Name_},
Protocol{fuchsia::sysmem::Allocator::Name_},
Protocol{fuchsia::tracing::provider::Registry::Name_},
Protocol{kVulkanLoaderServiceName}},
.source = ParentRef{},
.targets = {kTestUIStackRef}});
// Route UI capabilities from test UI stack to flutter runners.
realm_builder_.AddRoute(Route{
.capabilities = {Protocol{fuchsia::ui::composition::Flatland::Name_},
Protocol{fuchsia::ui::scenic::Scenic::Name_}},
.source = kTestUIStackRef,
.targets = {kFlutterJitRunnerRef, kFlutterJitProductRunnerRef,
kFlutterAotRunnerRef, kFlutterAotProductRunnerRef}});
// Route test capabilities from test UI stack to test driver.
realm_builder_.AddRoute(Route{
.capabilities = {Protocol{fuchsia::ui::test::input::Registry::Name_},
Protocol{fuchsia::ui::test::scene::Controller::Name_},
Protocol{fuchsia::ui::scenic::Scenic::Name_}},
.source = kTestUIStackRef,
.targets = {ParentRef{}}});
// Route ViewProvider from child to parent, and parent to test.
realm_builder_.AddRoute(
Route{.capabilities = {Protocol{fuchsia::ui::app::ViewProvider::Name_}},
.source = kParentViewRef,
.targets = {ParentRef()}});
realm_builder_.AddRoute(
Route{.capabilities = {Protocol{fuchsia::ui::app::ViewProvider::Name_}},
.source = kChildViewRef,
.targets = {kParentViewRef}});
}
// Checks whether the view with |view_ref_koid| has connected to the view tree.
// The response of a f.u.o.g.Provider.Watch call is stored in |watch_response|
// if it contains |view_ref_koid|.
bool FlutterEmbedderTest::HasViewConnected(
const fuchsia::ui::observation::geometry::ViewTreeWatcherPtr&
view_tree_watcher,
std::optional<fuchsia::ui::observation::geometry::WatchResponse>&
watch_response,
zx_koid_t view_ref_koid) {
std::optional<fuchsia::ui::observation::geometry::WatchResponse> watch_result;
view_tree_watcher->Watch(
[&watch_result](auto response) { watch_result = std::move(response); });
FML_LOG(INFO) << "Waiting for view tree watch result";
RunLoopUntil([&watch_result] { return watch_result.has_value(); });
FML_LOG(INFO) << "Received for view tree watch result";
if (CheckViewExistsInUpdates(watch_result->updates(), view_ref_koid)) {
watch_response = std::move(watch_result);
};
return watch_response.has_value();
}
void FlutterEmbedderTest::LaunchParentViewInRealm(
const std::vector<std::string>& component_args) {
FML_LOG(INFO) << "Launching parent-view";
if (!component_args.empty()) {
// Construct a args.csv file containing the specified comma-separated
// component args.
std::string csv;
for (const auto& arg : component_args) {
csv += arg + ',';
}
// Remove last comma.
csv.pop_back();
auto config_directory_contents = DirectoryContents();
config_directory_contents.AddFile("args.csv", csv);
realm_builder_.RouteReadOnlyDirectory("config-data", {kParentViewRef},
std::move(config_directory_contents));
}
realm_ = std::make_unique<RealmRoot>(realm_builder_.Build());
// Register fake touch screen device.
RegisterTouchScreen();
// Instruct Test UI Stack to present parent-view's View.
std::optional<zx_koid_t> view_ref_koid;
scene_provider_ = realm_->Connect<fuchsia::ui::test::scene::Controller>();
scene_provider_.set_error_handler(
[](auto) { FML_LOG(ERROR) << "Error from test scene provider"; });
fuchsia::ui::test::scene::ControllerAttachClientViewRequest request;
request.set_view_provider(realm_->Connect<fuchsia::ui::app::ViewProvider>());
scene_provider_->RegisterViewTreeWatcher(view_tree_watcher_.NewRequest(),
[]() {});
scene_provider_->AttachClientView(
std::move(request), [&view_ref_koid](auto client_view_ref_koid) {
view_ref_koid = client_view_ref_koid;
});
FML_LOG(INFO) << "Waiting for client view ref koid";
RunLoopUntil([&view_ref_koid] { return view_ref_koid.has_value(); });
// Wait for the client view to get attached to the view tree.
std::optional<fuchsia::ui::observation::geometry::WatchResponse>
watch_response;
FML_LOG(INFO) << "Waiting for client view to render; koid is "
<< (view_ref_koid.has_value() ? view_ref_koid.value() : 0);
RunLoopUntil([this, &watch_response, &view_ref_koid] {
return HasViewConnected(view_tree_watcher_, watch_response, *view_ref_koid);
});
FML_LOG(INFO) << "Client view has rendered";
scenic_ = realm_->Connect<fuchsia::ui::scenic::Scenic>();
FML_LOG(INFO) << "Launched parent-view";
}
scenic::Screenshot FlutterEmbedderTest::TakeScreenshot() {
FML_LOG(INFO) << "Taking screenshot... ";
fuchsia::ui::scenic::ScreenshotData screenshot_out;
scenic_->TakeScreenshot(
[this, &screenshot_out](fuchsia::ui::scenic::ScreenshotData screenshot,
bool status) {
EXPECT_TRUE(status) << "Failed to take screenshot";
screenshot_out = std::move(screenshot);
QuitLoop();
});
EXPECT_FALSE(RunLoopWithTimeout(kScreenshotTimeout))
<< "Timed out waiting for screenshot.";
FML_LOG(INFO) << "Screenshot captured.";
return scenic::Screenshot(screenshot_out);
}
bool FlutterEmbedderTest::TakeScreenshotUntil(
scenic::Color color,
fit::function<void(std::map<scenic::Color, size_t>)> callback,
zx::duration timeout) {
return RunLoopWithTimeoutOrUntil(
[this, &callback, &color] {
auto screenshot = TakeScreenshot();
auto histogram = screenshot.Histogram();
bool color_found = histogram[color] > 0;
if (color_found && callback != nullptr) {
callback(std::move(histogram));
}
return color_found;
},
timeout);
}
void FlutterEmbedderTest::RegisterTouchScreen() {
FML_LOG(INFO) << "Registering fake touch screen";
input_registry_ = realm_->Connect<fuchsia::ui::test::input::Registry>();
input_registry_.set_error_handler(
[](auto) { FML_LOG(ERROR) << "Error from input helper"; });
bool touchscreen_registered = false;
fuchsia::ui::test::input::RegistryRegisterTouchScreenRequest request;
request.set_device(fake_touchscreen_.NewRequest());
input_registry_->RegisterTouchScreen(
std::move(request),
[&touchscreen_registered]() { touchscreen_registered = true; });
RunLoopUntil([&touchscreen_registered] { return touchscreen_registered; });
FML_LOG(INFO) << "Touchscreen registered";
}
void FlutterEmbedderTest::InjectTap(int32_t x, int32_t y) {
fuchsia::ui::test::input::TouchScreenSimulateTapRequest tap_request;
tap_request.mutable_tap_location()->x = x;
tap_request.mutable_tap_location()->y = y;
fake_touchscreen_->SimulateTap(std::move(tap_request), [x, y]() {
FML_LOG(INFO) << "Tap injected at (" << x << ", " << y << ")";
});
}
void FlutterEmbedderTest::TryInject(int32_t x, int32_t y) {
InjectTap(x, y);
async::PostDelayedTask(
dispatcher(), [this, x, y] { TryInject(x, y); }, kTapRetryInterval);
}
TEST_F(FlutterEmbedderTest, Embedding) {
LaunchParentViewInRealm();
// Take screenshot until we see the child-view's embedded color.
ASSERT_TRUE(TakeScreenshotUntil(
kChildBackgroundColor, [](std::map<scenic::Color, size_t> histogram) {
// Expect parent and child background colors, with parent color > child
// color.
EXPECT_GT(histogram[kParentBackgroundColor], 0u);
EXPECT_GT(histogram[kChildBackgroundColor], 0u);
EXPECT_GT(histogram[kParentBackgroundColor],
histogram[kChildBackgroundColor]);
}));
}
TEST_F(FlutterEmbedderTest, HittestEmbedding) {
LaunchParentViewInRealm();
// Take screenshot until we see the child-view's embedded color.
ASSERT_TRUE(TakeScreenshotUntil(kChildBackgroundColor));
// Simulate a tap at the center of the child view.
TryInject(/* x = */ 0, /* y = */ 0);
// Take screenshot until we see the child-view's tapped color.
ASSERT_TRUE(TakeScreenshotUntil(
kChildTappedColor, [](std::map<scenic::Color, size_t> histogram) {
// Expect parent and child background colors, with parent color > child
// color.
EXPECT_GT(histogram[kParentBackgroundColor], 0u);
EXPECT_EQ(histogram[kChildBackgroundColor], 0u);
EXPECT_GT(histogram[kChildTappedColor], 0u);
EXPECT_GT(histogram[kParentBackgroundColor],
histogram[kChildTappedColor]);
}));
}
TEST_F(FlutterEmbedderTest, HittestDisabledEmbedding) {
LaunchParentViewInRealm({"--no-hitTestable"});
// Take screenshots until we see the child-view's embedded color.
ASSERT_TRUE(TakeScreenshotUntil(kChildBackgroundColor));
// Simulate a tap at the center of the child view.
TryInject(/* x = */ 0, /* y = */ 0);
// The parent-view should change color.
ASSERT_TRUE(TakeScreenshotUntil(
kParentTappedColor, [](std::map<scenic::Color, size_t> histogram) {
// Expect parent and child background colors, with parent color > child
// color.
EXPECT_EQ(histogram[kParentBackgroundColor], 0u);
EXPECT_GT(histogram[kParentTappedColor], 0u);
EXPECT_GT(histogram[kChildBackgroundColor], 0u);
EXPECT_EQ(histogram[kChildTappedColor], 0u);
EXPECT_GT(histogram[kParentTappedColor],
histogram[kChildBackgroundColor]);
}));
}
TEST_F(FlutterEmbedderTest, EmbeddingWithOverlay) {
LaunchParentViewInRealm({"--showOverlay"});
// Take screenshot until we see the child-view's embedded color.
ASSERT_TRUE(TakeScreenshotUntil(
kChildBackgroundColor, [](std::map<scenic::Color, size_t> histogram) {
// Expect parent, overlay and child background colors.
// With parent color > child color and overlay color > child color.
const size_t overlay_pixel_count = OverlayPixelCount(histogram);
EXPECT_GT(histogram[kParentBackgroundColor], 0u);
EXPECT_GT(overlay_pixel_count, 0u);
EXPECT_GT(histogram[kChildBackgroundColor], 0u);
EXPECT_GT(histogram[kParentBackgroundColor],
histogram[kChildBackgroundColor]);
EXPECT_GT(overlay_pixel_count, histogram[kChildBackgroundColor]);
}));
}
TEST_F(FlutterEmbedderTest, HittestEmbeddingWithOverlay) {
LaunchParentViewInRealm({"--showOverlay"});
// Take screenshot until we see the child-view's embedded color.
ASSERT_TRUE(TakeScreenshotUntil(kChildBackgroundColor));
// The bottom-left corner of the overlay is at the center of the screen,
// which is at (0, 0) in the injection coordinate space. Inject a pointer
// event just outside the overlay's bounds, and ensure that it goes to the
// embedded view.
TryInject(/* x = */ -1, /* y = */ 1);
// Take screenshot until we see the child-view's tapped color.
ASSERT_TRUE(TakeScreenshotUntil(
kChildTappedColor, [](std::map<scenic::Color, size_t> histogram) {
// Expect parent, overlay and child background colors.
// With parent color > child color and overlay color > child color.
const size_t overlay_pixel_count = OverlayPixelCount(histogram);
EXPECT_GT(histogram[kParentBackgroundColor], 0u);
EXPECT_GT(overlay_pixel_count, 0u);
EXPECT_EQ(histogram[kChildBackgroundColor], 0u);
EXPECT_GT(histogram[kChildTappedColor], 0u);
EXPECT_GT(histogram[kParentBackgroundColor],
histogram[kChildTappedColor]);
EXPECT_GT(overlay_pixel_count, histogram[kChildTappedColor]);
}));
}
} // namespace flutter_embedder_test

View File

@ -0,0 +1,27 @@
{
include: [
"gtest_runner.shard.cml",
"sys/component/realm_builder_absolute.shard.cml",
// This test needs both the vulkan facet and the hermetic-tier-2 facet,
// so we are forced to make it a system test.
"sys/testing/system-test.shard.cml",
],
program: {
binary: "bin/app",
},
offer: [
{
// Offer capabilities needed by components in this test realm.
// Keep it minimal, describe only what's actually needed.
protocol: [
"fuchsia.logger.LogSink",
"fuchsia.sysmem.Allocator",
"fuchsia.tracing.provider.Registry",
"fuchsia.vulkan.loader.Loader",
],
from: "parent",
to: "#realm_builder",
},
],
}

View File

@ -0,0 +1,14 @@
{
program: {
runner: "gtest_runner",
},
capabilities: [
{ protocol: "fuchsia.test.Suite" },
],
expose: [
{
protocol: "fuchsia.test.Suite",
from: "self",
},
],
}

View File

@ -0,0 +1,36 @@
# 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.
import("//build/fuchsia/sdk.gni")
import("//flutter/tools/fuchsia/dart/dart_library.gni")
import("//flutter/tools/fuchsia/flutter/flutter_component.gni")
import("//flutter/tools/fuchsia/gn-sdk/package.gni")
dart_library("lib") {
package_name = "parent-view"
sources = [ "parent_view.dart" ]
deps = [
"//flutter/shell/platform/fuchsia/dart:args",
"//flutter/shell/platform/fuchsia/dart:vector_math",
"//flutter/tools/fuchsia/dart:fuchsia_services",
"//flutter/tools/fuchsia/dart:zircon",
"//flutter/tools/fuchsia/fidl:fuchsia.sys",
"//flutter/tools/fuchsia/fidl:fuchsia.ui.app",
"//flutter/tools/fuchsia/fidl:fuchsia.ui.views",
]
}
flutter_component("component") {
main_package = "parent-view"
component_name = "parent-view"
main_dart = "parent_view.dart"
manifest = rebase_path("meta/parent-view.cml")
deps = [ ":lib" ]
}
fuchsia_package("package") {
package_name = "parent-view"
deps = [ ":component" ]
}

View File

@ -4,42 +4,49 @@
import 'dart:convert';
import 'dart:typed_data';
import 'dart:io';
import 'dart:ui';
import 'package:vector_math/vector_math_64.dart' as vector_math_64;
import 'package:args/args.dart';
import 'package:fidl_fuchsia_sys/fidl_async.dart';
import 'package:fidl_fuchsia_ui_app/fidl_async.dart';
import 'package:fidl_fuchsia_ui_views/fidl_async.dart';
import 'package:fuchsia_services/services.dart';
import 'package:vector_math/vector_math_64.dart' as vector_math_64;
import 'package:zircon/zircon.dart';
// TODO(richkadel): To run the test serving the runner and test packages from
// the flutter/engine package server (via
// `//flutter/tools/fuchsia/devshell/serve.sh`), change `fuchsia.com` to
// `engine`.
const _kChildAppUrl =
'fuchsia-pkg://fuchsia.com/child-view2#meta/child-view2.cmx';
TestApp app;
final _argsCsvFilePath = '/config/data/args.csv';
void main(List<String> args) {
print('parent-view: starting');
args = args + _GetArgsFromConfigFile();
final parser = ArgParser()
..addFlag('showOverlay', defaultsTo: false)
..addFlag('hitTestable', defaultsTo: true)
..addFlag('focusable', defaultsTo: true);
..addFlag('focusable', defaultsTo: true)
..addFlag('useFlatland', defaultsTo: false);
final arguments = parser.parse(args);
for (final option in arguments.options) {
print('parent-view2: $option: ${arguments[option]}');
print('parent-view: $option: ${arguments[option]}');
}
final childViewToken = _launchApp(_kChildAppUrl);
app = TestApp(
ChildView(childViewToken),
showOverlay: arguments['showOverlay'],
hitTestable: arguments['hitTestable'],
focusable: arguments['focusable'],
);
TestApp app;
final useFlatland = arguments['useFlatland'];
if (useFlatland) {
app = TestApp(
ChildView(_launchFlatlandChildView()),
showOverlay: arguments['showOverlay'],
hitTestable: arguments['hitTestable'],
focusable: arguments['focusable'],
);
} else {
app = TestApp(
ChildView.gfx(_launchGfxChildView()),
showOverlay: arguments['showOverlay'],
hitTestable: arguments['hitTestable'],
focusable: arguments['focusable'],
);
}
app.run();
}
@ -64,19 +71,24 @@ class TestApp {
void run() {
childView.create(hitTestable, focusable, (ByteData reply) {
// The child-view2 should be attached to Scenic now.
// Ready to build the scene.
// Set up window allbacks.
window.onPointerDataPacket = (PointerDataPacket packet) {
for (final data in packet.data) {
if (data.change == PointerChange.up) {
if (data.change == PointerChange.down) {
this._backgroundColor = _black;
}
}
window.scheduleFrame();
};
window.onBeginFrame = (Duration duration) {
app.beginFrame(duration);
window.onMetricsChanged = () {
window.scheduleFrame();
};
window.onBeginFrame = (Duration duration) {
this.beginFrame(duration);
};
// The child view should be attached to Scenic now.
// Ready to build the scene.
window.scheduleFrame();
});
}
@ -149,45 +161,16 @@ class TestApp {
}
}
ViewHolderToken _launchApp(String componentUrl) {
final incoming = Incoming();
final componentController = ComponentControllerProxy();
final launcher = LauncherProxy();
Incoming.fromSvcPath()
..connectToService(launcher)
..close();
launcher.createComponent(
LaunchInfo(
url: componentUrl,
directoryRequest: incoming.request().passChannel(),
),
componentController.ctrl.request(),
);
launcher.ctrl.close();
ViewProviderProxy viewProvider = ViewProviderProxy();
incoming
..connectToService(viewProvider)
..close();
final viewTokens = EventPairPair();
assert(viewTokens.status == ZX.OK);
final viewHolderToken = ViewHolderToken(value: viewTokens.first);
final viewToken = ViewToken(value: viewTokens.second);
viewProvider.createView(viewToken.value, null, null);
viewProvider.ctrl.close();
return viewHolderToken;
}
class ChildView {
final ViewHolderToken viewToken;
final ViewHolderToken viewHolderToken;
final ViewportCreationToken viewportCreationToken;
final int viewId;
ChildView(this.viewToken) : viewId = viewToken.value.handle.handle {
ChildView(this.viewportCreationToken) : viewHolderToken = null, viewId = viewportCreationToken.value.handle.handle {
assert(viewId != null);
}
ChildView.gfx(this.viewHolderToken) : viewportCreationToken = null, viewId = viewHolderToken.value.handle.handle {
assert(viewId != null);
}
@ -227,3 +210,49 @@ class ChildView {
callback);
}
}
ViewportCreationToken _launchFlatlandChildView() {
ViewProviderProxy viewProvider = ViewProviderProxy();
Incoming.fromSvcPath()
..connectToService(viewProvider)
..close();
final viewTokens = ChannelPair();
assert(viewTokens.status == ZX.OK);
final viewportCreationToken = ViewportCreationToken(value: viewTokens.first);
final viewCreationToken = ViewCreationToken(value: viewTokens.second);
final createViewArgs = CreateView2Args(viewCreationToken: viewCreationToken);
viewProvider.createView2(createViewArgs);
viewProvider.ctrl.close();
return viewportCreationToken;
}
ViewHolderToken _launchGfxChildView() {
ViewProviderProxy viewProvider = ViewProviderProxy();
Incoming.fromSvcPath()
..connectToService(viewProvider)
..close();
final viewTokens = EventPairPair();
assert(viewTokens.status == ZX.OK);
final viewHolderToken = ViewHolderToken(value: viewTokens.first);
final viewToken = ViewToken(value: viewTokens.second);
viewProvider.createView(viewToken.value, null, null);
viewProvider.ctrl.close();
return viewHolderToken;
}
List<String> _GetArgsFromConfigFile() {
List<String> args;
final f = File(_argsCsvFilePath);
if (!f.existsSync()) {
return List.empty();
}
final fileContentCsv = f.readAsStringSync();
args = fileContentCsv.split('\n');
return args;
}

View File

@ -0,0 +1,33 @@
{
include: [ "syslog/client.shard.cml" ],
program: {
data: "data/parent-view",
// Always use the jit runner for now.
// TODO(fxbug.dev/106577): Implement manifest merging build rules for V2 components.
runner: "flutter_jit_runner",
},
capabilities: [
{
protocol: [ "fuchsia.ui.app.ViewProvider" ],
},
],
use: [
{
protocol: [
"fuchsia.ui.app.ViewProvider",
],
},
{
directory: "config-data",
rights: [ "r*" ],
path: "/config/data",
},
],
expose: [
{
protocol: [ "fuchsia.ui.app.ViewProvider" ],
from: "self",
},
],
}

View File

@ -1,15 +1,13 @@
# This configuration file specifies several test suites with their package and
# test command for femu_test.py.
# Legacy Component Framework v1 components.
- test_command: run-test-component fuchsia-pkg://fuchsia.com/flutter-embedder-test2#meta/flutter-embedder-test2.cmx
packages:
- flutter-embedder-test2-0.far
- gen/flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/child-view2/child-view2/child-view2.far
- gen/flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/parent-view2/parent-view2/parent-view2.far
- flutter_jit_runner-0.far
# v2 components.
- test_command: run-test-suite fuchsia-pkg://fuchsia.com/flutter-embedder-test#meta/flutter-embedder-test.cm
packages:
- flutter-embedder-test-0.far
- oot_flutter_jit_runner-0.far
- gen/flutter/shell/platform/fuchsia/flutter/tests/integration/embedder/child-view/child-view/child-view.far
- gen/flutter/shell/platform/fuchsia/flutter/tests/integration/embedder/parent-view/parent-view/parent-view.far
- test_command: run-test-suite fuchsia-pkg://fuchsia.com/dart_runner_tests#meta/dart_runner_tests.cm
package: dart_runner_tests-0.far
- test_command: run-test-suite fuchsia-pkg://fuchsia.com/flutter_runner_tests#meta/flutter_runner_tests.cm

View File

@ -36,6 +36,8 @@ template("_compile_cml") {
"--output",
rebase_path(invoker.output, root_build_dir),
"--includepath",
rebase_path("$fuchsia_sdk/pkg/", root_build_dir),
"--includepath",
get_path_info(invoker.manifest, "dir"),
"--includepath",
rebase_path("//"),
@ -258,42 +260,66 @@ template("fuchsia_archive") {
# cmx_file (optional):
# A path to the .cmx file for the test archive.
# If not defined, a generated .cml file for the test archive will be used instead.
# cml_file (optional):
# The path to the V2 component manifest (.cml file) for the test archive's component.
# Should include the file extension.
# If not defined, a generated .cml file for the test archive will be used instead.
# libraries (optional):
# Paths to .so libraries that should be dynamically linked to the binary.
# resources (optional):
# Files that should be placed into the `data/` directory of the archive.
template("fuchsia_test_archive") {
assert(defined(invoker.deps), "package must define deps")
# Interpolate test_suite.cml template with the test suite's name.
test_suite = target_name
interpolate_cml_target = "${test_suite}_interpolate_cml"
generated_cml_file = "$root_out_dir/$test_suite.cml"
action(interpolate_cml_target) {
testonly = true
script = "//flutter/tools/fuchsia/interpolate_test_suite.py"
sources = [ "//flutter/testing/fuchsia/meta/test_suite.cml" ]
args = [
"--input",
rebase_path("//flutter/testing/fuchsia/meta/test_suite.cml"),
"--test-suite",
test_suite,
"--output",
rebase_path(generated_cml_file),
]
outputs = [ generated_cml_file ]
_deps = []
if (defined(invoker.deps)) {
_deps += invoker.deps
}
far_base_dir = "$root_out_dir/${target_name}_far"
if (defined(invoker.cml_file)) {
_far_base_dir = "$root_out_dir/${target_name}_far"
_cml_file_name = get_path_info(invoker.cml_file, "name")
_compile_cml_target = "${target_name}_${_cml_file_name}_compile_cml"
# Compile the resulting interpolated test suite's cml.
compile_test_suite_cml_target = "${test_suite}_test_suite_compile_cml"
_compile_cml(compile_test_suite_cml_target) {
testonly = true
deps = [ ":$interpolate_cml_target" ]
_compile_cml(_compile_cml_target) {
forward_variables_from(invoker, [ "testonly" ])
manifest = generated_cml_file
output = "$far_base_dir/meta/${test_suite}.cm"
manifest = invoker.cml_file
output = "$_far_base_dir/meta/${_cml_file_name}.cm"
}
_deps += [ ":$_compile_cml_target" ]
} else {
# Interpolate test_suite.cml template with the test suite's name.
test_suite = target_name
interpolate_cml_target = "${test_suite}_interpolate_cml"
generated_cml_file = "$root_out_dir/$test_suite.cml"
action(interpolate_cml_target) {
testonly = true
script = "//flutter/tools/fuchsia/interpolate_test_suite.py"
sources = [ "//flutter/testing/fuchsia/meta/test_suite.cml" ]
args = [
"--input",
rebase_path("//flutter/testing/fuchsia/meta/test_suite.cml"),
"--test-suite",
test_suite,
"--output",
rebase_path(generated_cml_file),
]
outputs = [ generated_cml_file ]
}
far_base_dir = "$root_out_dir/${target_name}_far"
# Compile the resulting interpolated test suite's cml.
compile_test_suite_cml_target = "${test_suite}_test_suite_compile_cml"
_compile_cml(compile_test_suite_cml_target) {
testonly = true
deps = [ ":$interpolate_cml_target" ]
manifest = generated_cml_file
output = "$far_base_dir/meta/${test_suite}.cm"
}
_deps += [ ":$compile_test_suite_cml_target" ]
}
_fuchsia_archive(target_name) {
@ -309,15 +335,6 @@ template("fuchsia_test_archive") {
libraries += invoker.libraries
}
# TODO(fxbug.dev/79873): Only cfv2 components should be allowed after
# FakeScenic is available.
if (defined(invoker.cmx_file)) {
cmx_file = invoker.cmx_file
# Don't include cml files.
deps = invoker.deps
} else {
deps = invoker.deps + [ ":$compile_test_suite_cml_target" ]
}
deps = _deps
}
}