diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index bec85aa0620c7..16d43f886ec08 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1413,9 +1413,11 @@ FILE: ../../../flutter/shell/platform/fuchsia/dart_runner/vmservice/meta/vmservi FILE: ../../../flutter/shell/platform/fuchsia/flutter/accessibility_bridge.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/accessibility_bridge.h FILE: ../../../flutter/shell/platform/fuchsia/flutter/accessibility_bridge_unittest.cc -FILE: ../../../flutter/shell/platform/fuchsia/flutter/component.cc -FILE: ../../../flutter/shell/platform/fuchsia/flutter/component.h -FILE: ../../../flutter/shell/platform/fuchsia/flutter/component_unittest.cc +FILE: ../../../flutter/shell/platform/fuchsia/flutter/component_v1.cc +FILE: ../../../flutter/shell/platform/fuchsia/flutter/component_v1.h +FILE: ../../../flutter/shell/platform/fuchsia/flutter/component_v1_unittest.cc +FILE: ../../../flutter/shell/platform/fuchsia/flutter/component_v2.cc +FILE: ../../../flutter/shell/platform/fuchsia/flutter/component_v2.h FILE: ../../../flutter/shell/platform/fuchsia/flutter/engine.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/engine.h FILE: ../../../flutter/shell/platform/fuchsia/flutter/file_in_namespace_buffer.cc diff --git a/shell/platform/fuchsia/dart_runner/dart_component_controller_v2.cc b/shell/platform/fuchsia/dart_runner/dart_component_controller_v2.cc index 206f7ffa0c0b7..5e3570dd86a22 100644 --- a/shell/platform/fuchsia/dart_runner/dart_component_controller_v2.cc +++ b/shell/platform/fuchsia/dart_runner/dart_component_controller_v2.cc @@ -354,6 +354,13 @@ void DartComponentControllerV2::Run() { loop_->Run(); if (binding_.is_bound()) { + // TODO(fxb/79871): This is likely a bug. We're taking the return_code + // of the process (a uint32_t) and implicitly converting it into a + // zx_status_t that is used as the epitaph. The uint32_t return code is + // unlikely to correspond to the epitaph status that is expected to close + // the connection (with the exception of 0 == ZX_OK). For the documentation + // of what epitaph status we should choose, see + // https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/fidl/fuchsia.component.runner/component_runner.fidl;l=118;drc=e3b39f2b57e720770773b857feca4f770ee0619e binding_.Close(return_code_); } } diff --git a/shell/platform/fuchsia/flutter/BUILD.gn b/shell/platform/fuchsia/flutter/BUILD.gn index eb8cf308ba800..eb460376439c8 100644 --- a/shell/platform/fuchsia/flutter/BUILD.gn +++ b/shell/platform/fuchsia/flutter/BUILD.gn @@ -57,8 +57,10 @@ template("runner_sources") { sources = [ "accessibility_bridge.cc", "accessibility_bridge.h", - "component.cc", - "component.h", + "component_v1.cc", + "component_v1.h", + "component_v2.cc", + "component_v2.h", "engine.cc", "engine.h", "file_in_namespace_buffer.cc", @@ -138,6 +140,7 @@ template("runner_sources") { deps = [ "$fuchsia_sdk_root/fidl:fuchsia.accessibility.semantics", + "$fuchsia_sdk_root/fidl:fuchsia.component.runner", "$fuchsia_sdk_root/fidl:fuchsia.fonts", "$fuchsia_sdk_root/fidl:fuchsia.images", "$fuchsia_sdk_root/fidl:fuchsia.intl", @@ -465,7 +468,7 @@ executable("flutter_runner_unittests") { sources = [ "accessibility_bridge_unittest.cc", - "component_unittest.cc", + "component_v1_unittest.cc", "flutter_runner_fakes.h", "focus_delegate_unittests.cc", "fuchsia_intl_unittest.cc", diff --git a/shell/platform/fuchsia/flutter/component.cc b/shell/platform/fuchsia/flutter/component_v1.cc similarity index 94% rename from shell/platform/fuchsia/flutter/component.cc rename to shell/platform/fuchsia/flutter/component_v1.cc index 438101951edcd..df19bcdc757a1 100644 --- a/shell/platform/fuchsia/flutter/component.cc +++ b/shell/platform/fuchsia/flutter/component_v1.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "component.h" +#include "component_v1.h" #include #include @@ -59,7 +59,7 @@ std::string DebugLabelForUrl(const std::string& url) { } // namespace -void Component::ParseProgramMetadata( +void ComponentV1::ParseProgramMetadata( const fidl::VectorPtr& program_metadata, std::string* data_path, std::string* assets_path) { @@ -80,21 +80,21 @@ void Component::ParseProgramMetadata( } } -ActiveComponent Component::Create( +ActiveComponentV1 ComponentV1::Create( TerminationCallback termination_callback, fuchsia::sys::Package package, fuchsia::sys::StartupInfo startup_info, std::shared_ptr runner_incoming_services, fidl::InterfaceRequest controller) { auto thread = std::make_unique(); - std::unique_ptr component; + std::unique_ptr component; fml::AutoResetWaitableEvent latch; thread->GetTaskRunner()->PostTask([&]() mutable { - component.reset(new Component(std::move(termination_callback), - std::move(package), std::move(startup_info), - runner_incoming_services, - std::move(controller))); + component.reset(new ComponentV1(std::move(termination_callback), + std::move(package), std::move(startup_info), + runner_incoming_services, + std::move(controller))); latch.Signal(); }); @@ -103,7 +103,7 @@ ActiveComponent Component::Create( .component = std::move(component)}; } -Component::Component( +ComponentV1::ComponentV1( TerminationCallback termination_callback, fuchsia::sys::Package package, fuchsia::sys::StartupInfo startup_info, @@ -215,9 +215,9 @@ Component::Component( [this](zx_status_t status, std::unique_ptr info) { cloned_directory_ptr_.Unbind(); if (status != ZX_OK) { - FML_LOG(ERROR) << "could not bind out directory for flutter app(" - << debug_label_ - << "): " << zx_status_get_string(status); + FML_LOG(ERROR) + << "could not bind out directory for flutter component(" + << debug_label_ << "): " << zx_status_get_string(status); return; } const char* other_dirs[] = {"debug", "ctrl", "diagnostics"}; @@ -433,7 +433,7 @@ Component::Component( // happening on the UI thread. If the Component dtor and thread // termination happen (on the platform thread) between the previous // line and the next line, a crash will occur since we'll be posting - // to a dead thread. See Runner::OnComponentTerminate() in + // to a dead thread. See Runner::OnComponentV1Terminate() in // runner.cc. platform_task_runner->PostTask([weak, runner_incoming_services, component_url, error, stack_trace]() { @@ -458,13 +458,13 @@ Component::Component( }; } -Component::~Component() = default; +ComponentV1::~ComponentV1() = default; -const std::string& Component::GetDebugLabel() const { +const std::string& ComponentV1::GetDebugLabel() const { return debug_label_; } -void Component::Kill() { +void ComponentV1::Kill() { component_controller_.events().OnTerminated( last_return_code_.second, fuchsia::sys::TerminationReason::EXITED); @@ -473,11 +473,11 @@ void Component::Kill() { // collected. } -void Component::Detach() { +void ComponentV1::Detach() { component_controller_.set_error_handler(nullptr); } -void Component::OnEngineTerminate(const Engine* shell_holder) { +void ComponentV1::OnEngineTerminate(const Engine* shell_holder) { auto found = std::find_if(shell_holders_.begin(), shell_holders_.end(), [shell_holder](const auto& holder) { return holder.get() == shell_holder; @@ -504,7 +504,7 @@ void Component::OnEngineTerminate(const Engine* shell_holder) { } } -void Component::CreateView( +void ComponentV1::CreateView( zx::eventpair token, fidl::InterfaceRequest /*incoming_services*/, fidl::InterfaceHandle< @@ -514,7 +514,7 @@ void Component::CreateView( std::move(view_ref_pair.view_ref)); } -void Component::CreateViewWithViewRef( +void ComponentV1::CreateViewWithViewRef( zx::eventpair view_token, fuchsia::ui::views::ViewRefControl control_ref, fuchsia::ui::views::ViewRef view_ref) { @@ -542,7 +542,7 @@ void Component::CreateViewWithViewRef( )); } -void Component::CreateView2(fuchsia::ui::app::CreateView2Args view_args) { +void ComponentV1::CreateView2(fuchsia::ui::app::CreateView2Args view_args) { if (!svc_) { FML_DLOG(ERROR) << "Component incoming services was invalid when attempting to " @@ -566,7 +566,7 @@ void Component::CreateView2(fuchsia::ui::app::CreateView2Args view_args) { } #if !defined(DART_PRODUCT) -void Component::WriteProfileToTrace() const { +void ComponentV1::WriteProfileToTrace() const { for (const auto& engine : shell_holders_) { engine->WriteProfileToTrace(); } diff --git a/shell/platform/fuchsia/flutter/component.h b/shell/platform/fuchsia/flutter/component_v1.h similarity index 83% rename from shell/platform/fuchsia/flutter/component.h rename to shell/platform/fuchsia/flutter/component_v1.h index c6f9fbe2fe3a1..53b657f41eb92 100644 --- a/shell/platform/fuchsia/flutter/component.h +++ b/shell/platform/fuchsia/flutter/component_v1.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_COMPONENT_H_ -#define FLUTTER_SHELL_PLATFORM_FUCHSIA_COMPONENT_H_ +#ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_COMPONENT_V1_H_ +#define FLUTTER_SHELL_PLATFORM_FUCHSIA_COMPONENT_V1_H_ #include #include @@ -30,13 +30,13 @@ namespace flutter_runner { -class Component; +class ComponentV1; -struct ActiveComponent { +struct ActiveComponentV1 { std::unique_ptr platform_thread; - std::unique_ptr component; + std::unique_ptr component; - ActiveComponent& operator=(ActiveComponent&& other) noexcept { + ActiveComponentV1& operator=(ActiveComponentV1&& other) noexcept { if (this != &other) { this->platform_thread.reset(other.platform_thread.release()); this->component.reset(other.component.release()); @@ -44,21 +44,21 @@ struct ActiveComponent { return *this; } - ~ActiveComponent() = default; + ~ActiveComponentV1() = default; }; -// Represents an instance of a Flutter component that contains one of more +// Represents an instance of a CF v1 Flutter component that contains one or more // Flutter engine instances. -class Component final : public Engine::Delegate, - public fuchsia::sys::ComponentController, - public fuchsia::ui::app::ViewProvider { +class ComponentV1 final : public Engine::Delegate, + public fuchsia::sys::ComponentController, + public fuchsia::ui::app::ViewProvider { public: - using TerminationCallback = fit::function; + using TerminationCallback = fit::function; // Creates a dedicated thread to run the component and creates the // component on it. The component can be accessed only on this thread. // This is a synchronous operation. - static ActiveComponent Create( + static ActiveComponentV1 Create( TerminationCallback termination_callback, fuchsia::sys::Package package, fuchsia::sys::StartupInfo startup_info, @@ -67,7 +67,7 @@ class Component final : public Engine::Delegate, // Must be called on the same thread returned from the create call. The thread // may be collected after. - ~Component(); + ~ComponentV1(); static void ParseProgramMetadata( const fidl::VectorPtr& program_metadata, @@ -101,9 +101,9 @@ class Component final : public Engine::Delegate, fml::RefPtr isolate_snapshot_; std::set> shell_holders_; std::pair last_return_code_; - fml::WeakPtrFactory weak_factory_; + fml::WeakPtrFactory weak_factory_; - Component( + ComponentV1( TerminationCallback termination_callback, fuchsia::sys::Package package, fuchsia::sys::StartupInfo startup_info, @@ -134,9 +134,9 @@ class Component final : public Engine::Delegate, // |flutter::Engine::Delegate| void OnEngineTerminate(const Engine* holder) override; - FML_DISALLOW_COPY_AND_ASSIGN(Component); + FML_DISALLOW_COPY_AND_ASSIGN(ComponentV1); }; } // namespace flutter_runner -#endif // FLUTTER_SHELL_PLATFORM_FUCHSIA_COMPONENT_H_ +#endif // FLUTTER_SHELL_PLATFORM_FUCHSIA_COMPONENT_V1_H_ diff --git a/shell/platform/fuchsia/flutter/component_unittest.cc b/shell/platform/fuchsia/flutter/component_v1_unittest.cc similarity index 72% rename from shell/platform/fuchsia/flutter/component_unittest.cc rename to shell/platform/fuchsia/flutter/component_v1_unittest.cc index a51faeeb338ea..26bcbd4c44a69 100644 --- a/shell/platform/fuchsia/flutter/component_unittest.cc +++ b/shell/platform/fuchsia/flutter/component_v1_unittest.cc @@ -2,20 +2,20 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "flutter/shell/platform/fuchsia/flutter/component.h" +#include "component_v1.h" #include namespace flutter_runner { namespace { -TEST(Component, ParseProgramMetadata) { +TEST(ComponentV1, ParseProgramMetadata) { std::string data_path; std::string assets_path; // The ProgramMetadata field may be null. We should parse this as if no // fields were specified. - Component::ParseProgramMetadata(nullptr, &data_path, &assets_path); + ComponentV1::ParseProgramMetadata(nullptr, &data_path, &assets_path); EXPECT_EQ(data_path, ""); EXPECT_EQ(assets_path, ""); @@ -23,7 +23,7 @@ TEST(Component, ParseProgramMetadata) { // The ProgramMetadata field may be empty. Treat this the same as null. fidl::VectorPtr program_metadata(size_t{0}); - Component::ParseProgramMetadata(program_metadata, &data_path, &assets_path); + ComponentV1::ParseProgramMetadata(program_metadata, &data_path, &assets_path); EXPECT_EQ(data_path, ""); EXPECT_EQ(assets_path, ""); @@ -31,7 +31,7 @@ TEST(Component, ParseProgramMetadata) { // The assets_path defaults to the "data" value if unspecified program_metadata = {{"data", "foobar"}}; - Component::ParseProgramMetadata(program_metadata, &data_path, &assets_path); + ComponentV1::ParseProgramMetadata(program_metadata, &data_path, &assets_path); EXPECT_EQ(data_path, "pkg/foobar"); EXPECT_EQ(assets_path, "pkg/foobar"); @@ -41,7 +41,7 @@ TEST(Component, ParseProgramMetadata) { program_metadata = {{"not_data", "foo"}, {"data", "bar"}, {"assets", "baz"}}; - Component::ParseProgramMetadata(program_metadata, &data_path, &assets_path); + ComponentV1::ParseProgramMetadata(program_metadata, &data_path, &assets_path); EXPECT_EQ(data_path, "pkg/bar"); EXPECT_EQ(assets_path, "pkg/baz"); diff --git a/shell/platform/fuchsia/flutter/component_v2.cc b/shell/platform/fuchsia/flutter/component_v2.cc new file mode 100644 index 0000000000000..aa1708432414b --- /dev/null +++ b/shell/platform/fuchsia/flutter/component_v2.cc @@ -0,0 +1,618 @@ +// 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 "component_v2.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "file_in_namespace_buffer.h" +#include "flutter/fml/mapping.h" +#include "flutter/fml/platform/fuchsia/task_observers.h" +#include "flutter/fml/synchronization/waitable_event.h" +#include "flutter/fml/unique_fd.h" +#include "flutter/runtime/dart_vm_lifecycle.h" +#include "flutter/shell/common/switches.h" +#include "runtime/dart/utils/files.h" +#include "runtime/dart/utils/handle_exception.h" +#include "runtime/dart/utils/mapped_resource.h" +#include "runtime/dart/utils/tempfs.h" +#include "runtime/dart/utils/vmo.h" + +namespace flutter_runner { +namespace { + +constexpr char kDataKey[] = "data"; +constexpr char kAssetsKey[] = "assets"; +constexpr char kTmpPath[] = "/tmp"; +constexpr char kServiceRootPath[] = "/svc"; +constexpr char kRunnerConfigPath[] = "/config/data/flutter_runner_config"; + +std::string DebugLabelForUrl(const std::string& url) { + auto found = url.rfind("/"); + if (found == std::string::npos) { + return url; + } else { + return {url, found + 1}; + } +} + +} // namespace + +void ComponentV2::ParseProgramMetadata( + const fuchsia::data::Dictionary& program_metadata, + std::string* data_path, + std::string* assets_path) { + for (const auto& entry : program_metadata.entries()) { + if (entry.key.compare(kDataKey) == 0 && entry.value != nullptr) { + *data_path = "pkg/" + entry.value->str(); + } else if (entry.key.compare(kAssetsKey) == 0 && entry.value != nullptr) { + *assets_path = "pkg/" + entry.value->str(); + } + } + + // assets_path defaults to the same as data_path if omitted. + if (assets_path->empty()) { + *assets_path = *data_path; + } +} + +ActiveComponentV2 ComponentV2::Create( + TerminationCallback termination_callback, + fuchsia::component::runner::ComponentStartInfo start_info, + std::shared_ptr runner_incoming_services, + fidl::InterfaceRequest + controller) { + auto thread = std::make_unique(); + std::unique_ptr component; + + fml::AutoResetWaitableEvent latch; + thread->GetTaskRunner()->PostTask([&]() mutable { + component.reset( + new ComponentV2(std::move(termination_callback), std::move(start_info), + runner_incoming_services, std::move(controller))); + latch.Signal(); + }); + + latch.Wait(); + return {.platform_thread = std::move(thread), + .component = std::move(component)}; +} + +ComponentV2::ComponentV2( + TerminationCallback termination_callback, + fuchsia::component::runner::ComponentStartInfo start_info, + std::shared_ptr runner_incoming_services, + fidl::InterfaceRequest + component_controller_request) + : termination_callback_(std::move(termination_callback)), + debug_label_(DebugLabelForUrl(start_info.resolved_url())), + component_controller_(this), + outgoing_dir_(new vfs::PseudoDir()), + runtime_dir_(new vfs::PseudoDir()), + runner_incoming_services_(runner_incoming_services), + weak_factory_(this) { + component_controller_.set_error_handler([this](zx_status_t status) { + FML_LOG(ERROR) << "ComponentController binding error for component(" + << debug_label_ << "): " << zx_status_get_string(status); + KillWithEpitaph( + zx_status_t(fuchsia::component::Error::INSTANCE_CANNOT_START)); + }); + + FML_DCHECK(fdio_ns_.is_valid()); + + // TODO(fxb/50694): Dart launch arguments. + FML_LOG(WARNING) << "program() arguments are currently ignored (fxb/50694)."; + + // Determine where data and assets are stored within /pkg. + std::string data_path; + std::string assets_path; + ParseProgramMetadata(start_info.program(), &data_path, &assets_path); + + if (data_path.empty()) { + FML_DLOG(ERROR) << "Could not find a /pkg/data directory for " + << start_info.resolved_url(); + return; + } + + // Setup /tmp to be mapped to the process-local memfs. + dart_utils::RunnerTemp::SetupComponent(fdio_ns_.get()); + + // ComponentStartInfo::ns (optional) + if (start_info.has_ns()) { + for (auto& entry : *start_info.mutable_ns()) { + // /tmp/ is mapped separately to the process-level memfs, so we ignore it + // here. + const auto& path = entry.path(); + if (path == kTmpPath) { + continue; + } + + // We should never receive namespace entries without a directory, but we + // check it anyways to avoid crashing if we do. + if (!entry.has_directory()) { + FML_DLOG(ERROR) << "Namespace entry at path (" << path + << ") has no directory."; + continue; + } + + zx::channel dir; + if (path == kServiceRootPath) { + svc_ = std::make_unique( + std::move(*entry.mutable_directory())); + dir = svc_->CloneChannel().TakeChannel(); + } else { + dir = entry.mutable_directory()->TakeChannel(); + } + + zx_handle_t dir_handle = dir.release(); + if (fdio_ns_bind(fdio_ns_.get(), path.data(), dir_handle) != ZX_OK) { + FML_DLOG(ERROR) << "Could not bind path to namespace: " << path; + zx_handle_close(dir_handle); + } + } + } + + // Open the data and assets directories inside our namespace. + { + fml::UniqueFD ns_fd(fdio_ns_opendir(fdio_ns_.get())); + FML_DCHECK(ns_fd.is_valid()); + + constexpr mode_t mode = O_RDONLY | O_DIRECTORY; + + component_assets_directory_.reset( + openat(ns_fd.get(), assets_path.c_str(), mode)); + FML_DCHECK(component_assets_directory_.is_valid()); + + component_data_directory_.reset( + openat(ns_fd.get(), data_path.c_str(), mode)); + FML_DCHECK(component_data_directory_.is_valid()); + } + + // ComponentStartInfo::runtime_dir (optional). + if (start_info.has_runtime_dir()) { + runtime_dir_->Serve(fuchsia::io::OPEN_RIGHT_READABLE | + fuchsia::io::OPEN_RIGHT_WRITABLE | + fuchsia::io::OPEN_FLAG_DIRECTORY, + start_info.mutable_runtime_dir()->TakeChannel()); + } + + // ComponentStartInfo::outgoing_dir (optional). + if (start_info.has_outgoing_dir()) { + outgoing_dir_->Serve(fuchsia::io::OPEN_RIGHT_READABLE | + fuchsia::io::OPEN_RIGHT_WRITABLE | + fuchsia::io::OPEN_FLAG_DIRECTORY, + start_info.mutable_outgoing_dir()->TakeChannel()); + } + + directory_request_ = directory_ptr_.NewRequest(); + + fidl::InterfaceHandle flutter_public_dir; + // TODO(anmittal): when fixing enumeration using new c++ vfs, make sure that + // flutter_public_dir is only accessed once we receive OnOpen Event. + // That will prevent FL-175 for public directory + auto request = flutter_public_dir.NewRequest().TakeChannel(); + fdio_service_connect_at(directory_ptr_.channel().get(), "svc", + request.release()); + + auto composed_service_dir = std::make_unique(); + composed_service_dir->set_fallback(std::move(flutter_public_dir)); + + // Clone and check if client is servicing the directory. + directory_ptr_->Clone(fuchsia::io::OPEN_FLAG_DESCRIBE | + fuchsia::io::OPEN_RIGHT_READABLE | + fuchsia::io::OPEN_RIGHT_WRITABLE, + cloned_directory_ptr_.NewRequest()); + + cloned_directory_ptr_.events().OnOpen = + [this](zx_status_t status, std::unique_ptr info) { + cloned_directory_ptr_.Unbind(); + if (status != ZX_OK) { + FML_LOG(ERROR) + << "could not bind out directory for flutter component(" + << debug_label_ << "): " << zx_status_get_string(status); + return; + } + const char* other_dirs[] = {"debug", "ctrl", "diagnostics"}; + // add other directories as RemoteDirs. + for (auto& dir_str : other_dirs) { + fidl::InterfaceHandle dir; + auto request = dir.NewRequest().TakeChannel(); + auto status = fdio_service_connect_at(directory_ptr_.channel().get(), + dir_str, request.release()); + if (status == ZX_OK) { + outgoing_dir_->AddEntry( + dir_str, std::make_unique(dir.TakeChannel())); + } else { + FML_LOG(ERROR) << "could not add out directory entry(" << dir_str + << ") for flutter component(" << debug_label_ + << "): " << zx_status_get_string(status); + } + } + }; + + cloned_directory_ptr_.set_error_handler( + [this](zx_status_t status) { cloned_directory_ptr_.Unbind(); }); + + // TODO(fxb/50694): Close handles from ComponentStartInfo::numbered_handles + // since we're not using them. See documentation from ComponentController: + // https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/fidl/fuchsia.component.runner/component_runner.fidl;l=97;drc=e3b39f2b57e720770773b857feca4f770ee0619e + + // TODO(fxb/50694): There's an OnPublishDiagnostics event we may want to + // fire for diagnostics. See documentation from ComponentController: + // https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/fidl/fuchsia.component.runner/component_runner.fidl;l=181;drc=e3b39f2b57e720770773b857feca4f770ee0619e + + // All launch arguments have been read. Perform service binding and + // final settings configuration. The next call will be to create a view + // for this component. + composed_service_dir->AddService( + fuchsia::ui::app::ViewProvider::Name_, + std::make_unique( + [this](zx::channel channel, async_dispatcher_t* dispatcher) { + shells_bindings_.AddBinding( + this, fidl::InterfaceRequest( + std::move(channel))); + })); + outgoing_dir_->AddEntry("svc", std::move(composed_service_dir)); + + // Setup the component controller binding. + if (component_controller_request) { + component_controller_.Bind(std::move(component_controller_request)); + } + + // Load and use runner-specific configuration, if it exists. + std::string json_string; + if (dart_utils::ReadFileToString(kRunnerConfigPath, &json_string)) { + product_config_ = FlutterRunnerProductConfiguration(json_string); + FML_LOG(INFO) << "Successfully loaded runner configuration: " + << json_string; + } else { + FML_LOG(WARNING) << "Failed to load runner configuration from " + << kRunnerConfigPath << "; using default config values."; + } + + // Load VM and component bytecode. + // For AOT, compare with flutter_aot_app in flutter_app.gni. + // For JIT, compare flutter_jit_runner in BUILD.gn. + if (flutter::DartVM::IsRunningPrecompiledCode()) { + std::shared_ptr snapshot = + std::make_shared(); + if (snapshot->Load(component_data_directory_.get(), + "app_aot_snapshot.so")) { + const uint8_t* isolate_data = snapshot->IsolateData(); + const uint8_t* isolate_instructions = snapshot->IsolateInstrs(); + const uint8_t* vm_data = snapshot->VmData(); + const uint8_t* vm_instructions = snapshot->VmInstrs(); + if (isolate_data == nullptr || isolate_instructions == nullptr || + vm_data == nullptr || vm_instructions == nullptr) { + FML_LOG(FATAL) << "ELF snapshot missing AOT symbols."; + return; + } + auto hold_snapshot = [snapshot](const uint8_t* _, size_t __) {}; + settings_.vm_snapshot_data = [hold_snapshot, vm_data]() { + return std::make_unique(vm_data, 0, + hold_snapshot); + }; + settings_.vm_snapshot_instr = [hold_snapshot, vm_instructions]() { + return std::make_unique(vm_instructions, 0, + hold_snapshot); + }; + settings_.isolate_snapshot_data = [hold_snapshot, isolate_data]() { + return std::make_unique(isolate_data, 0, + hold_snapshot); + }; + settings_.isolate_snapshot_instr = [hold_snapshot, + isolate_instructions]() { + return std::make_unique(isolate_instructions, 0, + hold_snapshot); + }; + isolate_snapshot_ = fml::MakeRefCounted( + std::make_shared(isolate_data, 0, + hold_snapshot), + std::make_shared(isolate_instructions, 0, + hold_snapshot)); + } else { + const int namespace_fd = component_data_directory_.get(); + settings_.vm_snapshot_data = [namespace_fd]() { + return LoadFile(namespace_fd, "vm_snapshot_data.bin", + false /* executable */); + }; + settings_.vm_snapshot_instr = [namespace_fd]() { + return LoadFile(namespace_fd, "vm_snapshot_instructions.bin", + true /* executable */); + }; + settings_.isolate_snapshot_data = [namespace_fd]() { + return LoadFile(namespace_fd, "isolate_snapshot_data.bin", + false /* executable */); + }; + settings_.isolate_snapshot_instr = [namespace_fd]() { + return LoadFile(namespace_fd, "isolate_snapshot_instructions.bin", + true /* executable */); + }; + } + } else { + settings_.vm_snapshot_data = []() { + return MakeFileMapping("/pkg/data/vm_snapshot_data.bin", + false /* executable */); + }; + settings_.vm_snapshot_instr = []() { + return MakeFileMapping("/pkg/data/vm_snapshot_instructions.bin", + true /* executable */); + }; + + settings_.isolate_snapshot_data = []() { + return MakeFileMapping("/pkg/data/isolate_core_snapshot_data.bin", + false /* executable */); + }; + settings_.isolate_snapshot_instr = [] { + return MakeFileMapping("/pkg/data/isolate_core_snapshot_instructions.bin", + true /* executable */); + }; + } + +#if defined(DART_PRODUCT) + settings_.enable_observatory = false; +#else + settings_.enable_observatory = true; + + // TODO(cbracken): pass this in as a param to allow 0.0.0.0, ::1, etc. + settings_.observatory_host = "127.0.0.1"; +#endif + + // Controls whether category "skia" trace events are enabled. + settings_.trace_skia = true; + + settings_.verbose_logging = true; + + settings_.advisory_script_uri = debug_label_; + + settings_.advisory_script_entrypoint = debug_label_; + + settings_.icu_data_path = ""; + + settings_.assets_dir = component_assets_directory_.get(); + + // Compare flutter_jit_app in flutter_app.gni. + settings_.application_kernel_list_asset = "app.dilplist"; + + settings_.log_tag = debug_label_ + std::string{"(flutter)"}; + + // No asserts in debug or release product. + // No asserts in release with flutter_profile=true (non-product) + // Yes asserts in non-product debug. +#if !defined(DART_PRODUCT) && (!defined(FLUTTER_PROFILE) || !defined(NDEBUG)) + // Debug mode + settings_.disable_dart_asserts = false; +#else + // Release mode + settings_.disable_dart_asserts = true; +#endif + + // Do not leak the VM; allow it to shut down normally when the last shell + // terminates. + settings_.leak_vm = false; + + settings_.task_observer_add = + std::bind(&fml::CurrentMessageLoopAddAfterTaskObserver, + std::placeholders::_1, std::placeholders::_2); + + settings_.task_observer_remove = std::bind( + &fml::CurrentMessageLoopRemoveAfterTaskObserver, std::placeholders::_1); + + settings_.log_message_callback = [](const std::string& tag, + const std::string& message) { + if (tag.size() > 0) { + std::cout << tag << ": "; + } + std::cout << message << std::endl; + }; + + settings_.dart_flags = {"--lazy_async_stacks"}; + + // Don't collect CPU samples from Dart VM C++ code. + settings_.dart_flags.push_back("--no_profile_vm"); + + // Scale back CPU profiler sampling period on ARM64 to avoid overloading + // the tracing engine. +#if defined(__aarch64__) + settings_.dart_flags.push_back("--profile_period=10000"); +#endif // defined(__aarch64__) + + auto platform_task_runner = fml::MessageLoop::GetCurrent().GetTaskRunner(); + const std::string component_url = start_info.resolved_url(); + settings_.unhandled_exception_callback = [weak = weak_factory_.GetWeakPtr(), + platform_task_runner, + runner_incoming_services, + component_url]( + const std::string& error, + const std::string& stack_trace) { + if (weak) { + // TODO(cbracken): unsafe. The above check and the PostTask below are + // happening on the UI thread. If the Component dtor and thread + // termination happen (on the platform thread) between the previous + // line and the next line, a crash will occur since we'll be posting + // to a dead thread. See Runner::OnComponentV2Terminate() in + // runner.cc. + platform_task_runner->PostTask([weak, runner_incoming_services, + component_url, error, stack_trace]() { + if (weak) { + dart_utils::HandleException(runner_incoming_services, component_url, + error, stack_trace); + } else { + FML_LOG(WARNING) + << "Exception was thrown which was not caught in Flutter app: " + << error; + } + }); + } else { + FML_LOG(WARNING) + << "Exception was thrown which was not caught in Flutter app: " + << error; + } + // Ideally we would return whether HandleException returned ZX_OK, but + // short of knowing if the exception was correctly handled, we return + // false to have the error and stack trace printed in the logs. + return false; + }; +} + +ComponentV2::~ComponentV2() = default; + +const std::string& ComponentV2::GetDebugLabel() const { + return debug_label_; +} + +void ComponentV2::Kill() { + FML_VLOG(-1) << "ComponentController: received Kill"; + + // From the documentation for ComponentController, ZX_OK should be sent when + // the ComponentController receives a termination request. + // + // TODO(fxb/50694): How should we communicate the return code of the process + // with the epitaph? Should we avoid sending ZX_OK if the return code is not + // 0? + // + // CF v1 logic for reference (the OnTerminated event no longer exists): + // component_controller_.events().OnTerminated( + // last_return_code_.second, fuchsia::sys::TerminationReason::EXITED); + + KillWithEpitaph(ZX_OK); + + // WARNING: Don't do anything past this point as this instance may have been + // collected. +} + +void ComponentV2::KillWithEpitaph(zx_status_t epitaph_status) { + component_controller_.set_error_handler(nullptr); + component_controller_.Close(epitaph_status); + + termination_callback_(this); + // WARNING: Don't do anything past this point as this instance may have been + // collected. +} + +void ComponentV2::Stop() { + FML_VLOG(-1) << "ComponentController v2: received Stop"; + + // TODO(fxb/50694): Any other cleanup logic we should do that's appropriate + // for Stop but not for Kill? + KillWithEpitaph(ZX_OK); +} + +void ComponentV2::OnEngineTerminate(const Engine* shell_holder) { + auto found = std::find_if(shell_holders_.begin(), shell_holders_.end(), + [shell_holder](const auto& holder) { + return holder.get() == shell_holder; + }); + + if (found == shell_holders_.end()) { + return; + } + + // We may launch multiple shell in this component. However, we will + // terminate when the last shell goes away. The error code returned to the + // component controller will be the last isolate that had an error. + auto return_code = shell_holder->GetEngineReturnCode(); + if (return_code.has_value()) { + last_return_code_ = {true, return_code.value()}; + } + + shell_holders_.erase(found); + + if (shell_holders_.size() == 0) { + Kill(); + // WARNING: Don't do anything past this point because the delegate may have + // collected this instance via the termination callback. + } +} + +void ComponentV2::CreateView( + zx::eventpair token, + fidl::InterfaceRequest /*incoming_services*/, + fidl::InterfaceHandle< + fuchsia::sys::ServiceProvider> /*outgoing_services*/) { + auto view_ref_pair = scenic::ViewRefPair::New(); + CreateViewWithViewRef(std::move(token), std::move(view_ref_pair.control_ref), + std::move(view_ref_pair.view_ref)); +} + +void ComponentV2::CreateViewWithViewRef( + zx::eventpair view_token, + fuchsia::ui::views::ViewRefControl control_ref, + fuchsia::ui::views::ViewRef view_ref) { + if (!svc_) { + FML_DLOG(ERROR) + << "Component incoming services was invalid when attempting to " + "create a shell for a view provider request."; + return; + } + + shell_holders_.emplace(std::make_unique( + *this, // delegate + debug_label_, // thread label + svc_, // Component incoming services + runner_incoming_services_, // Runner incoming services + settings_, // settings + scenic::ToViewToken(std::move(view_token)), // view token + scenic::ViewRefPair{ + .control_ref = std::move(control_ref), + .view_ref = std::move(view_ref), + }, + std::move(fdio_ns_), // FDIO namespace + std::move(directory_request_), // outgoing request + product_config_ // product configuration + )); +} + +void ComponentV2::CreateView2(fuchsia::ui::app::CreateView2Args view_args) { + if (!svc_) { + FML_DLOG(ERROR) + << "Component incoming services was invalid when attempting to " + "create a shell for a view provider request."; + return; + } + + shell_holders_.emplace(std::make_unique( + *this, // delegate + debug_label_, // thread label + svc_, // Component incoming services + runner_incoming_services_, // Runner incoming services + settings_, // settings + std::move( + *view_args.mutable_view_creation_token()), // view creation token + scenic::ViewRefPair::New(), // view ref pair + std::move(fdio_ns_), // FDIO namespace + std::move(directory_request_), // outgoing request + product_config_ // product configuration + )); +} + +#if !defined(DART_PRODUCT) +void ComponentV2::WriteProfileToTrace() const { + for (const auto& engine : shell_holders_) { + engine->WriteProfileToTrace(); + } +} +#endif // !defined(DART_PRODUCT) + +} // namespace flutter_runner diff --git a/shell/platform/fuchsia/flutter/component_v2.h b/shell/platform/fuchsia/flutter/component_v2.h new file mode 100644 index 0000000000000..a75d4a8110516 --- /dev/null +++ b/shell/platform/fuchsia/flutter/component_v2.h @@ -0,0 +1,158 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_COMPONENT_V2_H_ +#define FLUTTER_SHELL_PLATFORM_FUCHSIA_COMPONENT_V2_H_ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "flutter/common/settings.h" +#include "flutter/fml/macros.h" + +#include "engine.h" +#include "flutter_runner_product_configuration.h" +#include "unique_fdio_ns.h" + +namespace flutter_runner { + +class ComponentV2; + +struct ActiveComponentV2 { + std::unique_ptr platform_thread; + std::unique_ptr component; + + ActiveComponentV2& operator=(ActiveComponentV2&& other) noexcept { + if (this != &other) { + this->platform_thread.reset(other.platform_thread.release()); + this->component.reset(other.component.release()); + } + return *this; + } + + ~ActiveComponentV2() = default; +}; + +// Represents an instance of a CF v2 Flutter component that contains one or more +// Flutter engine instances. +// +// TODO(fxb/50694): Add unit tests once we've verified that the current behavior +// is working correctly. +class ComponentV2 final + : public Engine::Delegate, + public fuchsia::component::runner::ComponentController, + public fuchsia::ui::app::ViewProvider { + public: + using TerminationCallback = fit::function; + + // Creates a dedicated thread to run the component and creates the + // component on it. The component can be accessed only on this thread. + // This is a synchronous operation. + static ActiveComponentV2 Create( + TerminationCallback termination_callback, + fuchsia::component::runner::ComponentStartInfo start_info, + std::shared_ptr runner_incoming_services, + fidl::InterfaceRequest + controller); + + // Must be called on the same thread returned from the create call. The thread + // may be collected after. + ~ComponentV2(); + + static void ParseProgramMetadata( + const fuchsia::data::Dictionary& program_metadata, + std::string* data_path, + std::string* assets_path); + + const std::string& GetDebugLabel() const; + +#if !defined(DART_PRODUCT) + void WriteProfileToTrace() const; +#endif // !defined(DART_PRODUCT) + + private: + flutter::Settings settings_; + FlutterRunnerProductConfiguration product_config_; + TerminationCallback termination_callback_; + const std::string debug_label_; + UniqueFDIONS fdio_ns_ = UniqueFDIONSCreate(); + fml::UniqueFD component_data_directory_; + fml::UniqueFD component_assets_directory_; + + fidl::Binding + component_controller_; + fuchsia::io::DirectoryPtr directory_ptr_; + fuchsia::io::NodePtr cloned_directory_ptr_; + fidl::InterfaceRequest directory_request_; + std::unique_ptr outgoing_dir_; + std::unique_ptr runtime_dir_; + std::shared_ptr svc_; + std::shared_ptr runner_incoming_services_; + fidl::BindingSet shells_bindings_; + + fml::RefPtr isolate_snapshot_; + std::set> shell_holders_; + std::pair last_return_code_; + fml::WeakPtrFactory weak_factory_; + + ComponentV2( + TerminationCallback termination_callback, + fuchsia::component::runner::ComponentStartInfo start_info, + std::shared_ptr runner_incoming_services, + fidl::InterfaceRequest + controller); + + // |fuchsia::component::runner::ComponentController| + void Kill() override; + + /// Helper to actually |Kill| the component, closing the connection via an + /// epitaph with the given |epitaph_status|. Call this instead of + /// Kill() in the implementation of this class, as |Kill| is only intended for + /// clients of the ComponentController protocol to call. + /// + /// To determine what |epitaph_status| is appropriate for your situation, + /// see the documentation for |fuchsia.component.runner.ComponentController|. + void KillWithEpitaph(zx_status_t epitaph_status); + + // |fuchsia::component::runner::ComponentController| + void Stop() override; + + // |fuchsia::ui::app::ViewProvider| + void CreateView( + zx::eventpair token, + fidl::InterfaceRequest incoming_services, + fidl::InterfaceHandle outgoing_services) + override; + + // |fuchsia::ui::app::ViewProvider| + void CreateViewWithViewRef(zx::eventpair view_token, + fuchsia::ui::views::ViewRefControl control_ref, + fuchsia::ui::views::ViewRef view_ref) override; + + // |fuchsia::ui::app::ViewProvider| + void CreateView2(fuchsia::ui::app::CreateView2Args view_args) override; + + // |flutter::Engine::Delegate| + void OnEngineTerminate(const Engine* holder) override; + + FML_DISALLOW_COPY_AND_ASSIGN(ComponentV2); +}; + +} // namespace flutter_runner + +#endif // FLUTTER_SHELL_PLATFORM_FUCHSIA_COMPONENT_V2_H_ diff --git a/shell/platform/fuchsia/flutter/runner.cc b/shell/platform/fuchsia/flutter/runner.cc index 93708c6be58b8..6c59e6cdd0815 100644 --- a/shell/platform/fuchsia/flutter/runner.cc +++ b/shell/platform/fuchsia/flutter/runner.cc @@ -188,7 +188,10 @@ Runner::Runner(fml::RefPtr task_runner, SetThreadName("io.flutter.runner.main"); context_->outgoing()->AddPublicService( - std::bind(&Runner::RegisterComponent, this, std::placeholders::_1)); + std::bind(&Runner::RegisterComponentV1, this, std::placeholders::_1)); + context_->outgoing() + ->AddPublicService( + std::bind(&Runner::RegisterComponentV2, this, std::placeholders::_1)); #if !defined(DART_PRODUCT) if (Dart_IsPrecompiledRuntime()) { @@ -203,15 +206,19 @@ Runner::Runner(fml::RefPtr task_runner, Runner::~Runner() { context_->outgoing()->RemovePublicService(); + context_->outgoing() + ->RemovePublicService(); #if !defined(DART_PRODUCT) trace_observer_->Stop(); #endif // !defined(DART_PRODUCT) } -void Runner::RegisterComponent( +// CF v1 lifecycle methods. + +void Runner::RegisterComponentV1( fidl::InterfaceRequest request) { - active_components_bindings_.AddBinding(this, std::move(request)); + active_components_v1_bindings_.AddBinding(this, std::move(request)); } void Runner::StartComponent( @@ -233,15 +240,15 @@ void Runner::StartComponent( // we capture the runner in the termination callback. There is no risk of // there being multiple component runner instances in the process at the same // time. So it is safe to use the raw pointer. - Component::TerminationCallback termination_callback = - [component_runner = this](const Component* component) { + ComponentV1::TerminationCallback termination_callback = + [component_runner = this](const ComponentV1* component) { component_runner->task_runner_->PostTask( [component_runner, component]() { - component_runner->OnComponentTerminate(component); + component_runner->OnComponentV1Terminate(component); }); }; - ActiveComponent active_component = Component::Create( + ActiveComponentV1 active_component = ComponentV1::Create( std::move(termination_callback), // termination callback std::move(package), // component package std::move(startup_info), // startup info @@ -250,29 +257,103 @@ void Runner::StartComponent( ); auto key = active_component.component.get(); - active_components_[key] = std::move(active_component); + active_components_v1_[key] = std::move(active_component); +} + +void Runner::OnComponentV1Terminate(const ComponentV1* component) { + auto app = active_components_v1_.find(component); + if (app == active_components_v1_.end()) { + FML_LOG(INFO) + << "The remote end of the component runner tried to terminate an " + "component that has already been terminated, possibly because we " + "initiated the termination"; + return; + } + ActiveComponentV1& active_component = app->second; + + // Grab the items out of the entry because we will have to rethread the + // destruction. + std::unique_ptr component_to_destroy = + std::move(active_component.component); + std::unique_ptr component_thread = + std::move(active_component.platform_thread); + + // Delete the entry. + active_components_v1_.erase(component); + + // Post the task to destroy the component and quit its message loop. + component_thread->GetTaskRunner()->PostTask(fml::MakeCopyable( + [instance = std::move(component_to_destroy), + thread = component_thread.get()]() mutable { instance.reset(); })); + + // Terminate and join the thread's message loop. + component_thread->Join(); +} + +// CF v2 lifecycle methods. + +void Runner::RegisterComponentV2( + fidl::InterfaceRequest + request) { + active_components_v2_bindings_.AddBinding(this, std::move(request)); +} + +void Runner::Start( + fuchsia::component::runner::ComponentStartInfo start_info, + fidl::InterfaceRequest + controller) { + // TRACE_DURATION currently requires that the string data does not change + // in the traced scope. Since |package| gets moved in the ComponentV2::Create + // call below, we cannot ensure that |package.resolved_url| does not move or + // change, so we make a copy to pass to TRACE_DURATION. + // TODO(PT-169): Remove this copy when TRACE_DURATION reads string arguments + // eagerly. + const std::string url_copy = start_info.resolved_url(); + TRACE_EVENT1("flutter", "Start", "url", url_copy.c_str()); + + // Notes on component termination: Components typically terminate on the + // thread on which they were created. This usually means the thread was + // specifically created to host the component. But we want to ensure that + // access to the active components collection is made on the same thread. So + // we capture the runner in the termination callback. There is no risk of + // there being multiple component runner instances in the process at the same + // time. So it is safe to use the raw pointer. + ComponentV2::TerminationCallback termination_callback = + [component_runner = this](const ComponentV2* component) { + component_runner->task_runner_->PostTask( + [component_runner, component]() { + component_runner->OnComponentV2Terminate(component); + }); + }; + + ActiveComponentV2 active_component = ComponentV2::Create( + std::move(termination_callback), std::move(start_info), + context_->svc() /* runner_incoming_services */, std::move(controller)); + + auto key = active_component.component.get(); + active_components_v2_[key] = std::move(active_component); } -void Runner::OnComponentTerminate(const Component* component) { - auto app = active_components_.find(component); - if (app == active_components_.end()) { +void Runner::OnComponentV2Terminate(const ComponentV2* component) { + auto active_component_it = active_components_v2_.find(component); + if (active_component_it == active_components_v2_.end()) { FML_LOG(INFO) << "The remote end of the component runner tried to terminate an " "component that has already been terminated, possibly because we " "initiated the termination"; return; } - ActiveComponent& active_component = app->second; + ActiveComponentV2& active_component = active_component_it->second; // Grab the items out of the entry because we will have to rethread the // destruction. - std::unique_ptr component_to_destroy = + std::unique_ptr component_to_destroy = std::move(active_component.component); std::unique_ptr component_thread = std::move(active_component.platform_thread); // Delete the entry. - active_components_.erase(component); + active_components_v2_.erase(component); // Post the task to destroy the component and quit its message loop. component_thread->GetTaskRunner()->PostTask(fml::MakeCopyable( @@ -317,7 +398,7 @@ void Runner::SetupTraceObserver() { runner->prolonged_context_ = trace_acquire_prolonged_context(); Dart_StartProfiling(); } else if (trace_state() == TRACE_STOPPING) { - for (auto& it : runner->active_components_) { + for (auto& it : runner->active_components_v1_) { fml::AutoResetWaitableEvent latch; fml::TaskRunner::RunNowOrPostTask( it.second.platform_thread->GetTaskRunner(), [&]() { @@ -326,6 +407,8 @@ void Runner::SetupTraceObserver() { }); latch.Wait(); } + // TODO(fxb/50694): Write v2 component profiles to trace once we're + // convinced they're stable. Dart_StopProfiling(); trace_release_prolonged_context(runner->prolonged_context_); } diff --git a/shell/platform/fuchsia/flutter/runner.h b/shell/platform/fuchsia/flutter/runner.h index e5373a2870ab9..ea3442b8ec295 100644 --- a/shell/platform/fuchsia/flutter/runner.h +++ b/shell/platform/fuchsia/flutter/runner.h @@ -8,13 +8,15 @@ #include #include +#include #include #include #include #include #include -#include "component.h" +#include "component_v1.h" +#include "component_v2.h" #include "flutter/fml/macros.h" #include "fml/memory/ref_ptr.h" #include "fml/task_runner.h" @@ -23,9 +25,14 @@ namespace flutter_runner { -// Publishes the |fuchsia::sys::Runner| service and runs components on -// their own threads. -class Runner final : public fuchsia::sys::Runner { +/// Publishes the CF v1 and CF v2 runner services. +/// +/// Each component will be run on a separate thread dedicated to that component. +/// +/// TODO(fxb/50694): Add unit tests for CF v2. +class Runner final + : public fuchsia::sys::Runner /* CF v1 */, + public fuchsia::component::runner::ComponentRunner /* CF v2 */ { public: // Does not take ownership of context. Runner(fml::RefPtr task_runner, @@ -34,15 +41,42 @@ class Runner final : public fuchsia::sys::Runner { ~Runner(); private: + // CF v1 lifecycle methods. + // TODO(fxb/50694) Deprecate these once all Flutter components have been + // ported to CF v2. + // |fuchsia::sys::Runner| void StartComponent(fuchsia::sys::Package package, fuchsia::sys::StartupInfo startup_info, fidl::InterfaceRequest controller) override; - void RegisterComponent(fidl::InterfaceRequest request); + /// Registers a new CF v1 component with this runner, binding the component + /// to this runner. + void RegisterComponentV1( + fidl::InterfaceRequest request); + + /// Callback that should be fired when a registered CF v2 component is + /// terminated. + void OnComponentV1Terminate(const ComponentV1* component); + + // CF v2 lifecycle methods. + + // |fuchsia::component::runner::ComponentRunner| + void Start( + fuchsia::component::runner::ComponentStartInfo start_info, + fidl::InterfaceRequest + controller) override; + + /// Registers a new CF v2 component with this runner, binding the component + /// to this runner. + void RegisterComponentV2( + fidl::InterfaceRequest + request); - void OnComponentTerminate(const Component* component); + /// Callback that should be fired when a registered CF v2 component is + /// terminated. + void OnComponentV2Terminate(const ComponentV2* component); void SetupICU(); @@ -62,8 +96,21 @@ class Runner final : public fuchsia::sys::Runner { fml::RefPtr task_runner_; sys::ComponentContext* context_; - fidl::BindingSet active_components_bindings_; - std::unordered_map active_components_; + + // CF v1 component state. + fidl::BindingSet active_components_v1_bindings_; + std::unordered_map + active_components_v1_; + + // CF v2 component state. + + /// The components that are currently bound to this runner. + fidl::BindingSet + active_components_v2_bindings_; + + /// The components that are currently actively running on threads. + std::unordered_map + active_components_v2_; #if !defined(DART_PRODUCT) // The connection between the Dart VM service and The Hub.