| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "fuchsia_web/webinstance_host/web_instance_host.h" |
| |
| #include <fuchsia/component/decl/cpp/fidl.h> |
| #include <fuchsia/io/cpp/fidl.h> |
| #include <lib/fit/function.h> |
| #include <lib/sys/cpp/component_context.h> |
| #include <lib/sys/cpp/outgoing_directory.h> |
| #include <lib/sys/cpp/service_directory.h> |
| #include <lib/vfs/cpp/pseudo_dir.h> |
| #include <lib/vfs/cpp/pseudo_file.h> |
| #include <lib/vfs/cpp/remote_dir.h> |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/check.h" |
| #include "base/containers/fixed_flat_map.h" |
| #include "base/fuchsia/fuchsia_logging.h" |
| #include "base/fuchsia/process_context.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/raw_ref.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_piece.h" |
| #include "base/strings/string_util.h" |
| #include "base/types/expected.h" |
| #include "base/types/expected_macros.h" |
| #include "components/fuchsia_component_support/serialize_arguments.h" |
| #include "fuchsia_web/webengine/switches.h" |
| #include "fuchsia_web/webinstance_host/web_instance_host_constants.h" |
| #include "fuchsia_web/webinstance_host/web_instance_host_internal.h" |
| |
| namespace { |
| |
| namespace fcdecl = ::fuchsia::component::decl; |
| |
| // The name of the component collection hosting the instances. |
| constexpr char kCollectionName[] = "web_instances"; |
| |
| // Returns the default package URL for WebEngine. |
| // It is possible that this is not the actual URL for the current package. |
| // The package URL for the current component cannot be obtained programmatically |
| // (see fxbug.dev/51490), and this should always work in production, which is |
| // when this is needed. |
| // TODO(crbug.com/1211174): Remove when a different mechanism is available. |
| // TODO(crbug.com/1395054): Replace with constant once `with_webui` is removed. |
| std::string GetAbsoluteWebEnginePackageUrl(bool with_webui) { |
| return base::StrCat({"fuchsia-pkg://fuchsia.com/", |
| (with_webui ? "web_engine_with_webui" : "web_engine")}); |
| } |
| |
| // Returns the URL of the WebInstance component to be launched. |
| // `with_webui` is a test-only feature for `web_engine_shell` that causes |
| // `web_instance.cm` to be run from the `web_engine_with_webui` package rather |
| // than the production `web_engine` package. |
| std::string MakeWebInstanceComponentUrl(bool use_relative_url, |
| bool with_webui, |
| base::StringPiece component_name) { |
| return base::StrCat( |
| {(use_relative_url ? "" : GetAbsoluteWebEnginePackageUrl(with_webui)), |
| "#meta/", component_name}); |
| } |
| |
| // Returns the "/web_instances" dir from the component's outgoing directory, |
| // creating it if necessary. |
| vfs::PseudoDir* GetWebInstancesCollectionDir( |
| sys::OutgoingDirectory& outgoing_directory) { |
| return outgoing_directory.GetOrCreateDirectory(kCollectionName); |
| } |
| |
| // Returns an instance's name given its unique id. |
| std::string InstanceNameFromId(const base::Uuid& id) { |
| return base::StrCat({kCollectionName, "_", id.AsLowercaseString()}); |
| } |
| |
| void DestroyInstance(fuchsia::component::Realm& realm, |
| const std::string& name) { |
| realm.DestroyChild( |
| fcdecl::ChildRef{.name = name, .collection = kCollectionName}, |
| [](::fuchsia::component::Realm_DestroyChild_Result destroy_result) { |
| DCHECK(!destroy_result.is_err()) |
| << static_cast<uint32_t>(destroy_result.err()); |
| }); |
| } |
| |
| void DestroyInstanceDirectory(vfs::PseudoDir* instances_dir, |
| const std::string& name) { |
| zx_status_t status = instances_dir->RemoveEntry(name); |
| ZX_DCHECK(status == ZX_OK, status); |
| } |
| |
| struct Instance { |
| base::Uuid id; |
| fuchsia::component::BinderPtr binder_ptr; |
| |
| Instance(base::Uuid id, fuchsia::component::BinderPtr binder_ptr) |
| : id(std::move(id)), binder_ptr(std::move(binder_ptr)) {} |
| }; |
| |
| // A helper class for building a web_instance as a dynamic child of the |
| // component that hosts WebInstanceHost. |
| class InstanceBuilder { |
| public: |
| static base::expected<std::unique_ptr<InstanceBuilder>, zx_status_t> Create( |
| sys::OutgoingDirectory& outgoing_directory, |
| fuchsia::component::Realm& realm, |
| const base::CommandLine& launch_args); |
| ~InstanceBuilder(); |
| |
| base::CommandLine& args() { return args_; } |
| |
| // Offers the services named in `services` to the instance as dynamic |
| // protocol offers. |
| void AppendOffersForServices(const std::vector<std::string>& services); |
| |
| // Serves `service_directory` to the instance as the 'svc' read-write |
| // directory. |
| void ServeServiceDirectory( |
| fidl::InterfaceHandle<fuchsia::io::Directory> service_directory); |
| |
| // Offers the read-only root-ssl-certificates directory from the parent. |
| void ServeRootSslCertificates(); |
| |
| // Serves `data_directory` to the instance as the `data` read-write |
| // directory. |
| void ServeDataDirectory( |
| fidl::InterfaceHandle<fuchsia::io::Directory> data_directory); |
| |
| // Serves the directories in `providers` under the `content-directories` |
| // read-only directory. |
| zx_status_t ServeContentDirectories( |
| std::vector<fuchsia::web::ContentDirectoryProvider> providers); |
| |
| // Serves `cdm_data_directory` to the instance as the `cdm_data` read-write |
| // directory. |
| void ServeCdmDataDirectory( |
| fidl::InterfaceHandle<fuchsia::io::Directory> cdm_data_directory); |
| |
| // Serves `tmp_dir` to the instance as the `tmp` read-write directory. |
| void ServeTmpDirectory(fuchsia::io::DirectoryHandle tmp_dir); |
| |
| // Sets an optional request to connect to the instance's `fuchsia.web/Debug` |
| // protocol upon `Build()`. |
| void SetDebugRequest( |
| fidl::InterfaceRequest<fuchsia::web::Debug> debug_request); |
| |
| // Builds and returns the instance, or an error status value. |
| // `fuchsia.web/Debug` will be published in `outgoing_services_request` if |
| // `SetDebugRequest()` has been called. |
| Instance Build( |
| base::StringPiece instance_component_url, |
| fidl::InterfaceRequest<fuchsia::io::Directory> outgoing_services_request); |
| |
| private: |
| InstanceBuilder(sys::OutgoingDirectory& outgoing_directory, |
| fuchsia::component::Realm& realm, |
| base::Uuid id, |
| std::string name, |
| vfs::PseudoDir* instance_dir, |
| const base::CommandLine& launch_args); |
| |
| // Serves the arguments from the builder's `args()` command line in a file |
| // named `argv.json` via the instance's `command-line-config` read-only |
| // directory. |
| void ServeCommandLine(); |
| |
| // Adds offers from `void` for any optionally-offered directories that are not |
| // being served for the invoker. |
| void OfferMissingDirectoriesFromVoid(); |
| |
| // The directories that are optionally offered to `web_instance.cm` based on |
| // the invoker's configuration. |
| enum class OptionalDirectory { |
| kFirst = 0, |
| kCdmData = kFirst, |
| kCommandLineConfig, |
| kContentDirectories, |
| kData, |
| kTmp, |
| kCount, |
| }; |
| |
| // Returns a bitmask for `directory` for use with the `served_directories_` |
| // bitfield. |
| static uint32_t directory_bitmask(OptionalDirectory directory) { |
| return 1u << static_cast<int>(directory); |
| } |
| |
| // Returns true if the host will serve `directory` to the instance. |
| bool is_directory_served(OptionalDirectory directory) const { |
| return served_directories_ & directory_bitmask(directory); |
| } |
| |
| // Records that `directory` will be served to the instance. |
| void set_directory_served(OptionalDirectory directory) { |
| served_directories_ |= directory_bitmask(directory); |
| } |
| |
| // Returns the capability and directory name for `directory`. |
| static base::StringPiece GetDirectoryName(OptionalDirectory directory); |
| |
| // Serves `fs_directory` as `directory`. `fs_directory` may be specific to |
| // this instance (e.g., persistent data storage) or required only in |
| // particular configurations (e.g., CDM data storage), to the instance. Most |
| // common read-only directories (e.g., "root-ssl-certificates") should instead |
| // be offered statically to the `web_instances` collection. |
| void ServeOptionalDirectory( |
| OptionalDirectory directory, |
| std::unique_ptr<vfs::internal::Directory> fs_directory, |
| fuchsia::io::Operations rights); |
| |
| // Offers the directory `directory` from `void`. |
| void OfferOptionalDirectoryFromVoid(OptionalDirectory directory); |
| |
| // Serves the directory `name` as `offer` in the instance's subtree as a |
| // read-only or a read-write (if `writeable`) directory. |
| void ServeDirectory(base::StringPiece name, |
| std::unique_ptr<vfs::internal::Directory> fs_directory, |
| fuchsia::io::Operations rights); |
| |
| const raw_ref<sys::OutgoingDirectory> outgoing_directory_; |
| const raw_ref<fuchsia::component::Realm> realm_; |
| const base::Uuid id_; |
| const std::string name_; |
| raw_ptr<vfs::PseudoDir> instance_dir_; |
| base::CommandLine args_; |
| |
| // A bitfield of `directory_bitmask()` values indicating which optional |
| // directories are being served to the instance. |
| uint32_t served_directories_ = 0; |
| std::vector<fuchsia::component::decl::Offer> dynamic_offers_; |
| fidl::InterfaceRequest<fuchsia::web::Debug> debug_request_; |
| }; |
| |
| // static |
| base::expected<std::unique_ptr<InstanceBuilder>, zx_status_t> |
| InstanceBuilder::Create(sys::OutgoingDirectory& outgoing_directory, |
| fuchsia::component::Realm& realm, |
| const base::CommandLine& launch_args) { |
| // Pick a unique identifier for the new instance. |
| base::Uuid instance_id = base::Uuid::GenerateRandomV4(); |
| auto instance_name = InstanceNameFromId(instance_id); |
| |
| // Create a pseudo-directory to contain the various directory capabilities |
| // routed to this instance; i.e., `cdm_data`, `command-line-config`, |
| // `content-directories`, `data`, and `tmp`. The builder is responsible for |
| // removing it in case of error until `Build()` succeeds, at which point it is |
| // the caller's responsibility to remove it when the instance goes away. |
| auto instance_dir = std::make_unique<vfs::PseudoDir>(); |
| auto* const instance_dir_ptr = instance_dir.get(); |
| if (zx_status_t status = |
| GetWebInstancesCollectionDir(outgoing_directory) |
| ->AddEntry(instance_name, std::move(instance_dir)); |
| status != ZX_OK) { |
| ZX_DLOG(ERROR, status) << "AddEntry(name)"; |
| return base::unexpected(status); |
| } |
| |
| return base::WrapUnique(new InstanceBuilder( |
| outgoing_directory, realm, std::move(instance_id), |
| std::move(instance_name), instance_dir_ptr, launch_args)); |
| } |
| |
| InstanceBuilder::InstanceBuilder(sys::OutgoingDirectory& outgoing_directory, |
| fuchsia::component::Realm& realm, |
| base::Uuid id, |
| std::string name, |
| vfs::PseudoDir* instance_dir, |
| const base::CommandLine& launch_args) |
| : outgoing_directory_(outgoing_directory), |
| realm_(realm), |
| id_(std::move(id)), |
| name_(std::move(name)), |
| instance_dir_(instance_dir), |
| args_(launch_args) {} |
| |
| InstanceBuilder::~InstanceBuilder() { |
| if (instance_dir_) { |
| DestroyInstanceDirectory(GetWebInstancesCollectionDir(*outgoing_directory_), |
| name_); |
| } |
| } |
| |
| void InstanceBuilder::AppendOffersForServices( |
| const std::vector<std::string>& services) { |
| for (const auto& service_name : services) { |
| dynamic_offers_.push_back(fcdecl::Offer::WithProtocol(std::move( |
| fcdecl::OfferProtocol() |
| .set_source(fcdecl::Ref::WithParent({})) |
| .set_source_name(service_name) |
| .set_target_name(service_name) |
| .set_dependency_type(fcdecl::DependencyType::STRONG) |
| .set_availability(fcdecl::Availability::SAME_AS_TARGET)))); |
| } |
| } |
| |
| void InstanceBuilder::ServeServiceDirectory( |
| fidl::InterfaceHandle<fuchsia::io::Directory> service_directory) { |
| DCHECK(instance_dir_); |
| ServeDirectory( |
| "svc", std::make_unique<vfs::RemoteDir>(std::move(service_directory)), |
| fuchsia::io::Operations::CONNECT | fuchsia::io::Operations::ENUMERATE | |
| fuchsia::io::Operations::TRAVERSE); |
| } |
| |
| void InstanceBuilder::ServeDataDirectory( |
| fidl::InterfaceHandle<fuchsia::io::Directory> data_directory) { |
| DCHECK(instance_dir_); |
| ServeOptionalDirectory( |
| OptionalDirectory::kData, |
| std::make_unique<vfs::RemoteDir>(std::move(data_directory)), |
| fuchsia::io::RW_STAR_DIR); |
| } |
| |
| zx_status_t InstanceBuilder::ServeContentDirectories( |
| std::vector<fuchsia::web::ContentDirectoryProvider> providers) { |
| DCHECK(instance_dir_); |
| |
| auto content_dirs = std::make_unique<vfs::PseudoDir>(); |
| |
| for (auto& provider : providers) { |
| zx_status_t status = content_dirs->AddEntry( |
| provider.name(), std::make_unique<vfs::RemoteDir>( |
| std::move(*provider.mutable_directory()))); |
| if (status != ZX_OK) { |
| ZX_LOG(ERROR, status) |
| << "Conflicting content directory name \"" << provider.name() << "\""; |
| return status; |
| } |
| } |
| |
| ServeOptionalDirectory(OptionalDirectory::kContentDirectories, |
| std::move(content_dirs), fuchsia::io::R_STAR_DIR); |
| return ZX_OK; |
| } |
| |
| void InstanceBuilder::ServeCdmDataDirectory( |
| fidl::InterfaceHandle<fuchsia::io::Directory> cdm_data_directory) { |
| DCHECK(instance_dir_); |
| ServeOptionalDirectory( |
| OptionalDirectory::kCdmData, |
| std::make_unique<vfs::RemoteDir>(std::move(cdm_data_directory)), |
| fuchsia::io::RW_STAR_DIR); |
| } |
| |
| void InstanceBuilder::ServeTmpDirectory(fuchsia::io::DirectoryHandle tmp_dir) { |
| ServeOptionalDirectory(OptionalDirectory::kTmp, |
| std::make_unique<vfs::RemoteDir>(std::move(tmp_dir)), |
| fuchsia::io::RW_STAR_DIR); |
| } |
| |
| void InstanceBuilder::SetDebugRequest( |
| fidl::InterfaceRequest<fuchsia::web::Debug> debug_request) { |
| debug_request_ = std::move(debug_request); |
| } |
| |
| Instance InstanceBuilder::Build( |
| base::StringPiece instance_component_url, |
| fidl::InterfaceRequest<fuchsia::io::Directory> outgoing_services_request) { |
| ServeCommandLine(); |
| |
| // Create dynamic offers from `void` for any optional directories |
| // expected by web_instance.cm that are not being provided by the invoker. |
| OfferMissingDirectoriesFromVoid(); |
| |
| fcdecl::Child child_decl; |
| child_decl.set_name(name_); |
| child_decl.set_url(std::string(instance_component_url)); |
| child_decl.set_startup(fcdecl::StartupMode::LAZY); |
| |
| ::fuchsia::component::CreateChildArgs create_child_args; |
| create_child_args.set_dynamic_offers(std::move(dynamic_offers_)); |
| |
| realm_->CreateChild( |
| fcdecl::CollectionRef{.name = kCollectionName}, std::move(child_decl), |
| std::move(create_child_args), |
| [](::fuchsia::component::Realm_CreateChild_Result create_result) { |
| LOG_IF(ERROR, create_result.is_err()) |
| << "CreateChild error: " |
| << static_cast<uint32_t>(create_result.err()); |
| }); |
| |
| fidl::InterfaceHandle<fuchsia::io::Directory> instance_services_handle; |
| realm_->OpenExposedDir( |
| fcdecl::ChildRef{.name = name_, .collection = kCollectionName}, |
| instance_services_handle.NewRequest(), |
| [](::fuchsia::component::Realm_OpenExposedDir_Result open_result) { |
| LOG_IF(ERROR, open_result.is_err()) |
| << "OpenExposedDir error: " |
| << static_cast<uint32_t>(open_result.err()); |
| }); |
| |
| sys::ServiceDirectory instance_services(std::move(instance_services_handle)); |
| fuchsia::component::BinderPtr binder_ptr; |
| instance_services.Connect(binder_ptr.NewRequest()); |
| if (debug_request_) { |
| instance_services.Connect(std::move(debug_request_)); |
| } |
| instance_services.CloneChannel(std::move(outgoing_services_request)); |
| |
| // Ownership of the child and `instance_dir_` are now passed to the caller. |
| instance_dir_ = nullptr; |
| return Instance(id_, std::move(binder_ptr)); |
| } |
| |
| void InstanceBuilder::ServeCommandLine() { |
| DCHECK(instance_dir_); |
| |
| if (args_.argv().size() < 2) { |
| return; |
| } |
| |
| auto config_dir = std::make_unique<vfs::PseudoDir>(); |
| |
| std::vector<uint8_t> data( |
| fuchsia_component_support::SerializeArguments(args_)); |
| const auto data_size = data.size(); |
| zx_status_t status = config_dir->AddEntry( |
| "argv.json", |
| std::make_unique<vfs::PseudoFile>( |
| data_size, [data = std::move(data)](std::vector<uint8_t>* output, |
| size_t max_bytes) { |
| DCHECK_EQ(max_bytes, data.size()); |
| *output = data; |
| return ZX_OK; |
| })); |
| ZX_DCHECK(status == ZX_OK, status); |
| |
| ServeOptionalDirectory(OptionalDirectory::kCommandLineConfig, |
| std::move(config_dir), fuchsia::io::R_STAR_DIR); |
| } |
| |
| void InstanceBuilder::OfferMissingDirectoriesFromVoid() { |
| for (auto directory = OptionalDirectory::kFirst; |
| directory != OptionalDirectory::kCount; |
| directory = |
| static_cast<OptionalDirectory>(static_cast<int>(directory) + 1)) { |
| if (!is_directory_served(directory)) { |
| OfferOptionalDirectoryFromVoid(directory); |
| } |
| } |
| } |
| |
| // static |
| base::StringPiece InstanceBuilder::GetDirectoryName( |
| OptionalDirectory directory) { |
| static constexpr auto kNames = |
| base::MakeFixedFlatMap<OptionalDirectory, base::StringPiece>({ |
| {OptionalDirectory::kCdmData, "cdm_data"}, |
| {OptionalDirectory::kCommandLineConfig, "command-line-config"}, |
| {OptionalDirectory::kContentDirectories, "content-directories"}, |
| {OptionalDirectory::kData, "data"}, |
| {OptionalDirectory::kTmp, "tmp"}, |
| }); |
| static_assert(kNames.size() == static_cast<int>(OptionalDirectory::kCount)); |
| return kNames.at(directory); |
| } |
| |
| void InstanceBuilder::ServeOptionalDirectory( |
| OptionalDirectory directory, |
| std::unique_ptr<vfs::internal::Directory> fs_directory, |
| fuchsia::io::Operations rights) { |
| DCHECK(instance_dir_); |
| DCHECK(!is_directory_served(directory)); |
| |
| set_directory_served(directory); |
| ServeDirectory(GetDirectoryName(directory), std::move(fs_directory), rights); |
| } |
| |
| void InstanceBuilder::OfferOptionalDirectoryFromVoid( |
| OptionalDirectory directory) { |
| DCHECK(!is_directory_served(directory)); |
| |
| const auto name = GetDirectoryName(directory); |
| dynamic_offers_.push_back(fcdecl::Offer::WithDirectory( |
| std::move(fcdecl::OfferDirectory() |
| .set_source(fcdecl::Ref::WithVoidType({})) |
| .set_source_name(std::string(name)) |
| .set_target_name(std::string(name)) |
| .set_dependency_type(fcdecl::DependencyType::STRONG) |
| .set_availability(fcdecl::Availability::OPTIONAL)))); |
| } |
| |
| void InstanceBuilder::ServeDirectory( |
| base::StringPiece name, |
| std::unique_ptr<vfs::internal::Directory> fs_directory, |
| fuchsia::io::Operations rights) { |
| DCHECK(instance_dir_); |
| zx_status_t status = |
| instance_dir_->AddEntry(std::string(name), std::move(fs_directory)); |
| ZX_DCHECK(status == ZX_OK, status); |
| |
| dynamic_offers_.push_back(fcdecl::Offer::WithDirectory( |
| std::move(fcdecl::OfferDirectory() |
| .set_source(fcdecl::Ref::WithSelf({})) |
| .set_source_name(kCollectionName) |
| .set_target_name(std::string(name)) |
| .set_rights(rights) |
| .set_subdir(base::StrCat({name_, "/", name})) |
| .set_dependency_type(fcdecl::DependencyType::STRONG) |
| .set_availability(fcdecl::Availability::REQUIRED)))); |
| } |
| |
| void HandleCdmDataDirectoryParam(InstanceBuilder& builder, |
| fuchsia::web::CreateContextParams& params) { |
| if (!params.has_cdm_data_directory()) { |
| return; |
| } |
| |
| static constexpr char kCdmDataPath[] = "/cdm_data"; |
| |
| builder.args().AppendSwitchNative(switches::kCdmDataDirectory, kCdmDataPath); |
| builder.ServeCdmDataDirectory( |
| std::move(*params.mutable_cdm_data_directory())); |
| if (params.has_cdm_data_quota_bytes()) { |
| builder.args().AppendSwitchNative( |
| switches::kCdmDataQuotaBytes, |
| base::NumberToString(params.cdm_data_quota_bytes())); |
| } |
| } |
| |
| void HandleDataDirectoryParam(InstanceBuilder& builder, |
| fuchsia::web::CreateContextParams& params) { |
| if (!params.has_data_directory()) { |
| // Caller requested a web instance without any peristence. |
| builder.args().AppendSwitch(switches::kIncognito); |
| return; |
| } |
| |
| builder.ServeDataDirectory(std::move(*params.mutable_data_directory())); |
| |
| if (params.has_data_quota_bytes()) { |
| builder.args().AppendSwitchNative( |
| switches::kDataQuotaBytes, |
| base::NumberToString(params.data_quota_bytes())); |
| } |
| } |
| |
| bool HandleContentDirectoriesParam(InstanceBuilder& builder, |
| fuchsia::web::CreateContextParams& params) { |
| if (!params.has_content_directories()) { |
| return true; |
| } |
| |
| for (const auto& directory : params.content_directories()) { |
| if (!IsValidContentDirectoryName(directory.name())) { |
| DLOG(ERROR) << "Invalid directory name: " << directory.name(); |
| return false; |
| } |
| } |
| |
| builder.args().AppendSwitch(switches::kEnableContentDirectories); |
| return builder.ServeContentDirectories( |
| std::move(*params.mutable_content_directories())) == ZX_OK; |
| } |
| |
| } // namespace |
| |
| WebInstanceHost::WebInstanceHost(sys::OutgoingDirectory& outgoing_directory, |
| bool is_web_instance_component_in_same_package) |
| : outgoing_directory_(outgoing_directory), |
| is_web_instance_component_in_same_package_( |
| is_web_instance_component_in_same_package) {} |
| |
| WebInstanceHost::~WebInstanceHost() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| Uninitialize(); |
| } |
| |
| zx_status_t WebInstanceHost::CreateInstanceForContextWithCopiedArgsAndUrl( |
| fuchsia::web::CreateContextParams params, |
| fidl::InterfaceRequest<fuchsia::io::Directory> outgoing_services_request, |
| base::CommandLine extra_args, |
| base::StringPiece component_name, |
| std::vector<std::string> services_to_offer) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!is_initialized()) { |
| Initialize(); |
| } |
| |
| ASSIGN_OR_RETURN(auto builder, |
| InstanceBuilder::Create(*outgoing_directory_, *realm_, |
| std::move(extra_args))); |
| |
| if (zx_status_t status = AppendLaunchArgs(params, builder->args()); |
| status != ZX_OK) { |
| return status; |
| } |
| |
| // Only one method of providing services should be used. |
| CHECK_NE(!services_to_offer.empty(), params.has_service_directory()); |
| if (!services_to_offer.empty()) { |
| builder->AppendOffersForServices(services_to_offer); |
| } else { |
| builder->ServeServiceDirectory( |
| std::move(*params.mutable_service_directory())); |
| } |
| |
| // The `config-data` directory is statically offered to all instances. |
| // The `root-ssl-certificates` directory is statically offered to all |
| // instances regardless of whether networking is requested. |
| |
| HandleCdmDataDirectoryParam(*builder, params); |
| |
| HandleDataDirectoryParam(*builder, params); |
| |
| if (!HandleContentDirectoriesParam(*builder, params)) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| // TODO(crbug.com/1395774): Replace this with normal routing of tmp from |
| // web_engine_shell's parent down to web_instance. |
| if (tmp_dir_.is_valid()) { |
| builder->ServeTmpDirectory(std::move(tmp_dir_)); |
| } |
| |
| // If one or more Debug protocol clients are active then enable debugging, |
| // and connect the instance to the fuchsia.web.Debug proxy. |
| if (debug_proxy_.has_clients()) { |
| builder->args().AppendSwitch(switches::kEnableRemoteDebugMode); |
| fidl::InterfaceHandle<fuchsia::web::Debug> debug_handle; |
| builder->SetDebugRequest(debug_handle.NewRequest()); |
| debug_proxy_.RegisterInstance(std::move(debug_handle)); |
| } |
| |
| const bool with_webui = |
| base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kWithWebui); |
| |
| auto component_url_to_launch = MakeWebInstanceComponentUrl( |
| is_web_instance_component_in_same_package_, with_webui, component_name); |
| |
| auto component_url_to_register = component_url_to_launch; |
| |
| if (is_web_instance_component_in_same_package_) { |
| // RegisterWebInstanceProductData() requires an absolute component URL, but |
| // the package URL for the current component cannot be obtained |
| // programmatically (see fxbug.dev/51490). Use the default absolute package |
| // URL for WebEngine; this should always work in production, which is |
| // when registration is needed. |
| // TODO(crbug.com/1211174): Remove when a different mechanism is available. |
| component_url_to_register = |
| MakeWebInstanceComponentUrl(false, with_webui, component_name); |
| } |
| |
| // Ensure WebInstance is registered before launching it. |
| RegisterWebInstanceProductData(component_url_to_register); |
| |
| auto instance = builder->Build(component_url_to_launch, |
| std::move(outgoing_services_request)); |
| |
| // Monitor the instance's Binder to track its destruction. |
| instance.binder_ptr.set_error_handler( |
| [this, id = instance.id](zx_status_t status) { |
| this->OnComponentBinderClosed(id, status); |
| }); |
| instances_.emplace(std::move(instance.id), std::move(instance.binder_ptr)); |
| |
| return ZX_OK; |
| } |
| |
| void WebInstanceHost::Initialize() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(!realm_); |
| |
| zx_status_t status = |
| base::ComponentContextForProcess()->svc()->Connect(realm_.NewRequest()); |
| ZX_CHECK(status == ZX_OK, status) << "Connect(fuchsia.component/Realm)"; |
| realm_.set_error_handler( |
| fit::bind_member(this, &WebInstanceHost::OnRealmError)); |
| } |
| |
| void WebInstanceHost::Uninitialize() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // Destroy all child instances and each one's outgoing directory subtree. |
| auto* const instances_dir = |
| GetWebInstancesCollectionDir(*outgoing_directory_); |
| for (auto& [id, binder_ptr] : instances_) { |
| const std::string name(InstanceNameFromId(id)); |
| if (realm_) { |
| DestroyInstance(*realm_, name); |
| } |
| DestroyInstanceDirectory(instances_dir, name); |
| binder_ptr.Unbind(); |
| } |
| instances_.clear(); |
| |
| realm_.Unbind(); |
| |
| // Note: the entry in the outgoing directory for the top-level instances dir |
| // is leaked in support of having multiple hosts active in a single process. |
| } |
| |
| void WebInstanceHost::OnRealmError(zx_status_t status) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| ZX_LOG(ERROR, status) << "RealmBuilder channel error"; |
| Uninitialize(); |
| } |
| |
| void WebInstanceHost::OnComponentBinderClosed(const base::Uuid& id, |
| zx_status_t status) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // Destroy the child instance. |
| const std::string name(InstanceNameFromId(id)); |
| DestroyInstance(*realm_, name); |
| |
| // Drop the directory subtree for the child instance. |
| DestroyInstanceDirectory(GetWebInstancesCollectionDir(*outgoing_directory_), |
| name); |
| |
| // Drop the hold on the instance's Binder. Note: destroying the InterfacePtr |
| // here also deletes the lambda into which `id` was bound, so `id` must not |
| // be touched after this next statement. |
| auto count = instances_.erase(id); |
| DCHECK_EQ(count, 1UL); |
| |
| if (instances_.empty()) { |
| Uninitialize(); |
| } |
| } |
| |
| WebInstanceHostWithServicesFromThisComponent:: |
| WebInstanceHostWithServicesFromThisComponent( |
| sys::OutgoingDirectory& outgoing_directory, |
| bool is_web_instance_component_in_same_package) |
| : WebInstanceHost(outgoing_directory, |
| is_web_instance_component_in_same_package) {} |
| |
| zx_status_t WebInstanceHostWithServicesFromThisComponent:: |
| CreateInstanceForContextWithCopiedArgs( |
| fuchsia::web::CreateContextParams params, |
| fidl::InterfaceRequest<fuchsia::io::Directory> |
| outgoing_services_request, |
| const base::CommandLine& extra_args) { |
| // Services are offered from this Component, so they should not be provided. |
| CHECK(!params.has_service_directory()); |
| |
| std::vector<std::string> services; |
| const auto features = params.has_features() |
| ? params.features() |
| : fuchsia::web::ContextFeatureFlags(); |
| AppendDynamicServices(features, params.has_playready_key_system(), services); |
| |
| return CreateInstanceForContextWithCopiedArgsAndUrl( |
| std::move(params), std::move(outgoing_services_request), extra_args, |
| "web_instance.cm", services); |
| } |
| |
| WebInstanceHostWithoutServices::WebInstanceHostWithoutServices( |
| sys::OutgoingDirectory& outgoing_directory, |
| bool is_web_instance_component_in_same_package) |
| : WebInstanceHost(outgoing_directory, |
| is_web_instance_component_in_same_package) {} |
| |
| zx_status_t |
| WebInstanceHostWithoutServices::CreateInstanceForContextWithCopiedArgs( |
| fuchsia::web::CreateContextParams params, |
| fidl::InterfaceRequest<fuchsia::io::Directory> outgoing_services_request, |
| const base::CommandLine& extra_args) { |
| // Services are not offered from this Component, so they must be provided. |
| CHECK(params.has_service_directory()); |
| |
| // Web UI resources are not supported with a service directory. |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kWithWebui)) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| return CreateInstanceForContextWithCopiedArgsAndUrl( |
| std::move(params), std::move(outgoing_services_request), extra_args, |
| "web_instance_with_svc_directory.cm", {}); |
| } |