| // 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 <dlfcn.h> | |
| #include <fuchsia/mem/cpp/fidl.h> | |
| #include <lib/async-loop/cpp/loop.h> | |
| #include <lib/async/cpp/task.h> | |
| #include <lib/async/default.h> | |
| #include <lib/fdio/directory.h> | |
| #include <lib/fdio/io.h> | |
| #include <lib/fdio/namespace.h> | |
| #include <lib/ui/scenic/cpp/view_ref_pair.h> | |
| #include <lib/ui/scenic/cpp/view_token_pair.h> | |
| #include <lib/vfs/cpp/composed_service_dir.h> | |
| #include <lib/vfs/cpp/remote_dir.h> | |
| #include <lib/vfs/cpp/service.h> | |
| #include <sys/stat.h> | |
| #include <zircon/dlfcn.h> | |
| #include <zircon/status.h> | |
| #include <zircon/types.h> | |
| #include <memory> | |
| #include <regex> | |
| #include <sstream> | |
| #include "file_in_namespace_buffer.h" | |
| #include "flutter/fml/command_line.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 { | |
| // "data" and "assets" are arguments that are specific to the Flutter runner. | |
| // They will likely go away if we migrate to the ELF runner. | |
| constexpr char kDataKey[] = "data"; | |
| constexpr char kAssetsKey[] = "assets"; | |
| // "args" are how the component specifies arguments to the runner. | |
| constexpr char kArgsKey[] = "args"; | |
| constexpr char kOldGenHeapSizeKey[] = "old_gen_heap_size"; | |
| constexpr char kExposeDirsKey[] = "expose_dirs"; | |
| 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}; | |
| } | |
| } | |
| /// Parses the |args| field from the "program" field into | |
| /// |metadata|. | |
| void ParseArgs(std::vector<std::string>& args, ProgramMetadata* metadata) { | |
| // fml::CommandLine expects the first argument to be the name of the program, | |
| // so we prepend a dummy argument so we can use fml::CommandLine to parse the | |
| // arguments for us. | |
| std::vector<std::string> command_line_args = {""}; | |
| command_line_args.insert(command_line_args.end(), args.begin(), args.end()); | |
| fml::CommandLine parsed_args = fml::CommandLineFromIterators( | |
| command_line_args.begin(), command_line_args.end()); | |
| std::string old_gen_heap_size_option; | |
| if (parsed_args.GetOptionValue(kOldGenHeapSizeKey, | |
| &old_gen_heap_size_option)) { | |
| int64_t specified_old_gen_heap_size = strtol( | |
| old_gen_heap_size_option.c_str(), nullptr /* endptr */, 10 /* base */); | |
| if (specified_old_gen_heap_size != 0) { | |
| metadata->old_gen_heap_size = specified_old_gen_heap_size; | |
| } else { | |
| FML_LOG(ERROR) << "Invalid old_gen_heap_size: " | |
| << old_gen_heap_size_option; | |
| } | |
| } | |
| std::string expose_dirs_option; | |
| if (parsed_args.GetOptionValue(kExposeDirsKey, &expose_dirs_option)) { | |
| // Parse the comma delimited string | |
| std::vector<std::string> expose_dirs; | |
| std::stringstream s(expose_dirs_option); | |
| while (s.good()) { | |
| std::string dir; | |
| getline(s, dir, ','); // get first string delimited by comma | |
| metadata->expose_dirs.push_back(dir); | |
| } | |
| } | |
| } | |
| } // namespace | |
| ProgramMetadata ComponentV2::ParseProgramMetadata( | |
| const fuchsia::data::Dictionary& program_metadata) { | |
| ProgramMetadata result; | |
| for (const auto& entry : program_metadata.entries()) { | |
| if (entry.key.compare(kDataKey) == 0 && entry.value != nullptr) { | |
| result.data_path = "pkg/" + entry.value->str(); | |
| } else if (entry.key.compare(kAssetsKey) == 0 && entry.value != nullptr) { | |
| result.assets_path = "pkg/" + entry.value->str(); | |
| } else if (entry.key.compare(kArgsKey) == 0 && entry.value != nullptr) { | |
| ParseArgs(entry.value->str_vec(), &result); | |
| } | |
| } | |
| // assets_path defaults to the same as data_path if omitted. | |
| if (result.assets_path.empty()) { | |
| result.assets_path = result.data_path; | |
| } | |
| return result; | |
| } | |
| ActiveComponentV2 ComponentV2::Create( | |
| TerminationCallback termination_callback, | |
| fuchsia::component::runner::ComponentStartInfo start_info, | |
| std::shared_ptr<sys::ServiceDirectory> runner_incoming_services, | |
| fidl::InterfaceRequest<fuchsia::component::runner::ComponentController> | |
| controller) { | |
| auto thread = std::make_unique<fml::Thread>(); | |
| std::unique_ptr<ComponentV2> 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<sys::ServiceDirectory> runner_incoming_services, | |
| fidl::InterfaceRequest<fuchsia::component::runner::ComponentController> | |
| 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/88391): Dart launch arguments. | |
| FML_LOG(WARNING) << "program() arguments are currently ignored (fxb/88391)."; | |
| ProgramMetadata metadata = ParseProgramMetadata(start_info.program()); | |
| if (metadata.data_path.empty()) { | |
| FML_LOG(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_LOG(ERROR) << "Namespace entry at path (" << path | |
| << ") has no directory."; | |
| continue; | |
| } | |
| zx::channel dir; | |
| if (path == kServiceRootPath) { | |
| svc_ = std::make_unique<sys::ServiceDirectory>( | |
| 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_LOG(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(), metadata.assets_path.c_str(), mode)); | |
| FML_DCHECK(component_assets_directory_.is_valid()); | |
| component_data_directory_.reset( | |
| openat(ns_fd.get(), metadata.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::OpenFlags::RIGHT_READABLE | | |
| fuchsia::io::OpenFlags::RIGHT_WRITABLE | | |
| fuchsia::io::OpenFlags::DIRECTORY, | |
| start_info.mutable_runtime_dir()->TakeChannel()); | |
| } | |
| // ComponentStartInfo::outgoing_dir (optional). | |
| if (start_info.has_outgoing_dir()) { | |
| outgoing_dir_->Serve(fuchsia::io::OpenFlags::RIGHT_READABLE | | |
| fuchsia::io::OpenFlags::RIGHT_WRITABLE | | |
| fuchsia::io::OpenFlags::DIRECTORY, | |
| start_info.mutable_outgoing_dir()->TakeChannel()); | |
| } | |
| directory_request_ = directory_ptr_.NewRequest(); | |
| fuchsia::io::DirectoryHandle 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<vfs::ComposedServiceDir>(); | |
| composed_service_dir->set_fallback(std::move(flutter_public_dir)); | |
| // Clone and check if client is servicing the directory. | |
| directory_ptr_->Clone(fuchsia::io::OpenFlags::DESCRIBE | | |
| fuchsia::io::OpenFlags::RIGHT_READABLE | | |
| fuchsia::io::OpenFlags::RIGHT_WRITABLE, | |
| cloned_directory_ptr_.NewRequest()); | |
| // Collect our standard set of directories along with directories that are | |
| // included in the cml file to expose. | |
| std::vector<std::string> other_dirs = {"debug", "ctrl", "diagnostics"}; | |
| for (auto dir : metadata.expose_dirs) { | |
| other_dirs.push_back(dir); | |
| } | |
| cloned_directory_ptr_.events().OnOpen = [this, other_dirs](zx_status_t status, | |
| auto unused) { | |
| 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; | |
| } | |
| // add other directories as RemoteDirs. | |
| for (auto& dir_str : other_dirs) { | |
| fuchsia::io::DirectoryHandle dir; | |
| auto request = dir.NewRequest().TakeChannel(); | |
| auto status = fdio_service_connect_at(directory_ptr_.channel().get(), | |
| dir_str.c_str(), request.release()); | |
| if (status == ZX_OK) { | |
| outgoing_dir_->AddEntry( | |
| dir_str.c_str(), | |
| std::make_unique<vfs::RemoteDir>(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/89162): 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/89162): 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<vfs::Service>( | |
| [this](zx::channel channel, async_dispatcher_t* dispatcher) { | |
| shells_bindings_.AddBinding( | |
| this, fidl::InterfaceRequest<fuchsia::ui::app::ViewProvider>( | |
| 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<dart_utils::ElfSnapshot> snapshot = | |
| std::make_shared<dart_utils::ElfSnapshot>(); | |
| 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<fml::NonOwnedMapping>(vm_data, 0, hold_snapshot, | |
| true /* dontneed_safe */); | |
| }; | |
| settings_.vm_snapshot_instr = [hold_snapshot, vm_instructions]() { | |
| return std::make_unique<fml::NonOwnedMapping>( | |
| vm_instructions, 0, hold_snapshot, true /* dontneed_safe */); | |
| }; | |
| settings_.isolate_snapshot_data = [hold_snapshot, isolate_data]() { | |
| return std::make_unique<fml::NonOwnedMapping>( | |
| isolate_data, 0, hold_snapshot, true /* dontneed_safe */); | |
| }; | |
| settings_.isolate_snapshot_instr = [hold_snapshot, | |
| isolate_instructions]() { | |
| return std::make_unique<fml::NonOwnedMapping>( | |
| isolate_instructions, 0, hold_snapshot, true /* dontneed_safe */); | |
| }; | |
| isolate_snapshot_ = fml::MakeRefCounted<flutter::DartSnapshot>( | |
| std::make_shared<fml::NonOwnedMapping>(isolate_data, 0, hold_snapshot, | |
| true /* dontneed_safe */), | |
| std::make_shared<fml::NonOwnedMapping>(isolate_instructions, 0, | |
| hold_snapshot, | |
| true /* dontneed_safe */)); | |
| } 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)"}; | |
| if (metadata.old_gen_heap_size.has_value()) { | |
| settings_.old_gen_heap_size = *metadata.old_gen_heap_size; | |
| } | |
| // 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.empty()) { | |
| std::cout << tag << ": "; | |
| } | |
| std::cout << message << std::endl; | |
| }; | |
| settings_.dart_flags = {}; | |
| // 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) << "received Kill event"; | |
| // From the documentation for ComponentController, ZX_OK should be sent when | |
| // the ComponentController receives a termination request. However, if the | |
| // component exited with a non-zero return code, we indicate this by sending | |
| // an INTERNAL epitaph instead. | |
| // | |
| // TODO(fxb/86666): Communicate return code from the ComponentController once | |
| // v2 has support. | |
| auto [got_return_code, return_code] = last_return_code_; | |
| if (got_return_code && return_code == 0) { | |
| KillWithEpitaph(ZX_OK); | |
| } else { | |
| if (got_return_code) { | |
| FML_LOG(ERROR) << "Component exited with non-zero return code: " | |
| << return_code; | |
| } else { | |
| FML_LOG(ERROR) << "Failed to get return code for component"; | |
| } | |
| KillWithEpitaph(zx_status_t(fuchsia::component::Error::INTERNAL)); | |
| } | |
| // 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) << "received Stop event"; | |
| // TODO(fxb/89162): 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()) { | |
| // This indicates a deeper issue with memory management and should never | |
| // happen. | |
| FML_LOG(ERROR) << "Tried to terminate an unregistered shell holder."; | |
| FML_DCHECK(false); | |
| return; | |
| } | |
| // We may launch multiple shells 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()}; | |
| } else { | |
| FML_LOG(ERROR) << "Failed to get return code from terminated shell holder."; | |
| } | |
| shell_holders_.erase(found); | |
| if (shell_holders_.empty()) { | |
| FML_VLOG(-1) << "Killing component because all shell holders have been " | |
| "terminated."; | |
| 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<fuchsia::sys::ServiceProvider> /*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_LOG(ERROR) | |
| << "Component incoming services was invalid when attempting to " | |
| "create a shell for a view provider request."; | |
| return; | |
| } | |
| shell_holders_.emplace(std::make_unique<Engine>( | |
| *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 | |
| std::vector<std::string>(), // dart entrypoint args | |
| false // not a v1 component | |
| )); | |
| } | |
| void ComponentV2::CreateView2(fuchsia::ui::app::CreateView2Args view_args) { | |
| if (!svc_) { | |
| FML_LOG(ERROR) | |
| << "Component incoming services was invalid when attempting to " | |
| "create a shell for a view provider request."; | |
| return; | |
| } | |
| shell_holders_.emplace(std::make_unique<Engine>( | |
| *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 | |
| std::vector<std::string>(), // dart entrypoint args | |
| false // not a v1 component | |
| )); | |
| } | |
| #if !defined(DART_PRODUCT) | |
| void ComponentV2::WriteProfileToTrace() const { | |
| for (const auto& engine : shell_holders_) { | |
| engine->WriteProfileToTrace(); | |
| } | |
| } | |
| #endif // !defined(DART_PRODUCT) | |
| } // namespace flutter_runner |