diff --git a/fuchsia_web/common/test/test_realm_support.cc b/fuchsia_web/common/test/test_realm_support.cc index caebd9d432f92..e7a6203108e02 100644 --- a/fuchsia_web/common/test/test_realm_support.cc +++ b/fuchsia_web/common/test/test_realm_support.cc @@ -12,6 +12,7 @@ #include "base/check.h" #include "base/command_line.h" +#include "base/ranges/algorithm.h" using ::component_testing::ChildRef; using ::component_testing::Directory; @@ -22,24 +23,61 @@ using ::component_testing::Route; namespace test { +namespace { + +void AppendCommandLineArgumentsToProgram( + fuchsia::component::decl::Program& program, + const base::CommandLine& command_line) { + // Find the "args" list in the program declaration. + fuchsia::data::DictionaryEntry* args_entry = nullptr; + auto* entries = program.mutable_info()->mutable_entries(); + for (auto& entry : *entries) { + if (entry.key == "args") { + DCHECK(entry.value->is_str_vec()); + args_entry = &entry; + break; + } + } + if (!args_entry) { + // Create a new "args" list and insert it at the proper location in the + // program's entries; entries' keys must be sorted as per + // https://fuchsia.dev/reference/fidl/fuchsia.data?hl=en#Dictionary. + auto lower_bound = base::ranges::lower_bound( + *entries, "args", /*comp=*/{}, + [](const fuchsia::data::DictionaryEntry& entry) { return entry.key; }); + auto it = entries->emplace(lower_bound); + it->key = "args"; + it->value = fuchsia::data::DictionaryValue::New(); + it->value->set_str_vec({}); + args_entry = &*it; + } + + // Append all args following the program name in `command_line` to the + // program's args. + args_entry->value->str_vec().insert(args_entry->value->str_vec().end(), + command_line.argv().begin() + 1, + command_line.argv().end()); +} + +} // namespace + void AppendCommandLineArguments(RealmBuilder& realm_builder, base::StringPiece child_name, const base::CommandLine& command_line) { const std::string child_name_str(child_name); - auto context_provider_decl = realm_builder.GetComponentDecl(child_name_str); - for (auto& entry : *context_provider_decl.mutable_program() - ->mutable_info() - ->mutable_entries()) { - if (entry.key == "args") { - DCHECK(entry.value->is_str_vec()); - entry.value->str_vec().insert(entry.value->str_vec().end(), - command_line.argv().begin() + 1, - command_line.argv().end()); - break; - } - } + auto child_component_decl = realm_builder.GetComponentDecl(child_name_str); + AppendCommandLineArgumentsToProgram(*child_component_decl.mutable_program(), + command_line); realm_builder.ReplaceComponentDecl(child_name_str, - std::move(context_provider_decl)); + std::move(child_component_decl)); +} + +void AppendCommandLineArgumentsForRealm( + ::component_testing::RealmBuilder& realm_builder, + const base::CommandLine& command_line) { + auto decl = realm_builder.GetRealmDecl(); + AppendCommandLineArgumentsToProgram(*decl.mutable_program(), command_line); + realm_builder.ReplaceRealmDecl(std::move(decl)); } void AddSyslogRoutesFromParent(RealmBuilder& realm_builder, diff --git a/fuchsia_web/common/test/test_realm_support.h b/fuchsia_web/common/test/test_realm_support.h index 5cd155412840a..2d3cd57f2b143 100644 --- a/fuchsia_web/common/test/test_realm_support.h +++ b/fuchsia_web/common/test/test_realm_support.h @@ -24,6 +24,12 @@ void AppendCommandLineArguments( base::StringPiece child_name, const base::CommandLine& command_line); +// Appends the arguments of `command_line` (ignoring the program name at +// position zero) to the command line for the realm. +void AppendCommandLineArgumentsForRealm( + ::component_testing::RealmBuilder& realm_builder, + const base::CommandLine& command_line); + // In the functions below, the term "parent" is the `#realm_builder` collection // for the test component that is using these helpers to build a test realm. // Each test component must use a .cml fragment that routes the required diff --git a/fuchsia_web/shell/BUILD.gn b/fuchsia_web/shell/BUILD.gn index 85111da3999d4..74c969c2fed98 100644 --- a/fuchsia_web/shell/BUILD.gn +++ b/fuchsia_web/shell/BUILD.gn @@ -39,9 +39,13 @@ fuchsia_component("web_engine_shell_component") { data_deps = [ ":web_engine_shell_exec" ] } -fuchsia_component("web_engine_shell_component_v1") { +# WebInstanceHost needs to serve capabilities to web_instance. Since the +# primary web_engine_shell component is a test component, it is not able to do +# this directly. Instead, it launches this here component to start instances via +# WebInstanceHost. +fuchsia_component("web_engine_shell_for_web_instance_host_component") { testonly = true - manifest = "web_engine_shell.cmx" + manifest = "web_engine_shell_for_web_instance_host.cml" data_deps = [ ":web_engine_shell_exec" ] } @@ -52,7 +56,7 @@ fuchsia_package("web_engine_shell_pkg") { package_name = "web_engine_shell" deps = [ ":web_engine_shell_component", - ":web_engine_shell_component_v1", + ":web_engine_shell_for_web_instance_host_component", ] } @@ -82,11 +86,15 @@ executable("web_engine_shell_exec") { ":remote_debugging_port", "//base", "//fuchsia_web/common", - "//fuchsia_web/webinstance_host:webinstance_host_v1", + "//fuchsia_web/common/test:test_support", + "//fuchsia_web/webinstance_host:webinstance_host", + "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.component:fuchsia.component_hlcpp", "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.ui.policy:fuchsia.ui.policy_hlcpp", "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.web:fuchsia.web_hlcpp", "//third_party/fuchsia-sdk/sdk/pkg/fdio", + "//third_party/fuchsia-sdk/sdk/pkg/fidl_cpp", "//third_party/fuchsia-sdk/sdk/pkg/scenic_cpp", + "//third_party/fuchsia-sdk/sdk/pkg/sys_component_cpp_testing", "//third_party/fuchsia-sdk/sdk/pkg/sys_cpp", "//third_party/widevine/cdm:buildflags", "//url", diff --git a/fuchsia_web/shell/README.md b/fuchsia_web/shell/README.md index e4fcb424bb244..1174d607129ad 100644 --- a/fuchsia_web/shell/README.md +++ b/fuchsia_web/shell/README.md @@ -12,8 +12,7 @@ To build and run WebEngine Shell, execute the following commands: $ autoninja -C $OUTDIR web_engine_shell $ $OUTDIR/bin/deploy_web_engine_shell --fuchsia-out-dir $FUCHSIA_OUTDIR $ cd $FUCHSIA -$ fx shell -$ run fuchsia-pkg://fuchsia.com/web_engine_shell#meta/web_engine_shell.cmx --remote-debugging-port=1234 http://www.example.com +$ ffx test run fuchsia-pkg://fuchsia.com/web_engine_shell#meta/web_engine_shell.cm -- --remote-debugging-port=1234 http://www.example.com ``` Local files can be deployed with the WebEngine Shell and accessed via the @@ -22,5 +21,5 @@ by placing them under the path `//fuchsia_web/webengine/data`. Here is an example command line which loads a local file: ``` -$ run fuchsia-pkg://fuchsia.com/web_engine_shell#meta/web_engine_shell.cmx fuchsia-dir://data/index.html +$ ffx test run fuchsia-pkg://fuchsia.com/web_engine_shell#meta/web_engine_shell.cm -- fuchsia-dir://data/index.html ``` diff --git a/fuchsia_web/shell/web_engine_shell.cc b/fuchsia_web/shell/web_engine_shell.cc index 5c3ffa469bb6d..e5855d149a1c9 100644 --- a/fuchsia_web/shell/web_engine_shell.cc +++ b/fuchsia_web/shell/web_engine_shell.cc @@ -2,16 +2,21 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <fuchsia/component/cpp/fidl.h> #include <fuchsia/ui/policy/cpp/fidl.h> #include <fuchsia/web/cpp/fidl.h> #include <lib/fdio/directory.h> +#include <lib/fidl/cpp/interface_ptr.h> +#include <lib/sys/component/cpp/testing/realm_builder.h> #include <lib/sys/cpp/component_context.h> +#include <lib/sys/cpp/service_directory.h> #include <lib/ui/scenic/cpp/view_token_pair.h> -#include <lib/vfs/cpp/pseudo_file.h> + #include <iostream> #include <utility> #include "base/base_paths.h" +#include "base/check.h" #include "base/command_line.h" #include "base/files/file_util.h" #include "base/fuchsia/file_utils.h" @@ -27,9 +32,10 @@ #include "base/values.h" #include "build/build_config.h" #include "fuchsia_web/common/init_logging.h" +#include "fuchsia_web/common/test/test_realm_support.h" #include "fuchsia_web/shell/remote_debugging_port.h" +#include "fuchsia_web/webinstance_host/web_instance_host.h" #include "fuchsia_web/webinstance_host/web_instance_host_constants.h" -#include "fuchsia_web/webinstance_host/web_instance_host_v1.h" #include "third_party/widevine/cdm/buildflags.h" #include "url/gurl.h" @@ -38,6 +44,9 @@ namespace { constexpr char kHeadlessSwitch[] = "headless"; constexpr char kEnableProtectedMediaIdentifier[] = "enable-protected-media-identifier"; +// Included on the command line when the shell is relaunched for use of +// WebInstanceHost; see web_engine_shell_for_web_instance_host.cml. +constexpr char kFromLauncher[] = "from-launcher"; constexpr char kUseWebInstance[] = "use-web-instance"; constexpr char kEnableWebInstanceTmp[] = "enable-web-instance-tmp"; @@ -69,17 +78,51 @@ GURL GetUrlFromArgs(const base::CommandLine::StringVector& args) { return url; } +// web_engine_shell needs to provide capabilities to children it launches (via +// WebInstanceHost, for example). Test components are not able to do this, so +// use RealmBuilder to relaunch web_engine_shell via +// `web_engine_shell_for_web_instance_host_component` (which includes +// `--from-launcher` on its command line) with the contents of this process's +// command line. +int RelaunchForWebInstanceHost(const base::CommandLine& command_line) { + auto realm_builder = component_testing::RealmBuilder::CreateFromRelativeUrl( + "#meta/web_engine_shell_for_web_instance_host.cm"); + test::AppendCommandLineArgumentsForRealm(realm_builder, // IN-TEST + command_line); + + auto realm = realm_builder.Build(); + + fuchsia::component::BinderPtr binder_proxy = + realm.component().Connect<fuchsia::component::Binder>(); + + // Wait for binder_proxy to be closed. + base::RunLoop run_loop; + binder_proxy.set_error_handler( + [quit_closure = run_loop.QuitClosure()](zx_status_t status) { + std::move(quit_closure).Run(); + }); + run_loop.Run(); + + // Nothing depends on the process exit code of web_engine_shell today, so + // simply return success in all cases. + return 0; +} + } // namespace int main(int argc, char** argv) { + base::CommandLine::Init(argc, argv); + + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + CHECK(InitLoggingFromCommandLine(*command_line)); + base::SingleThreadTaskExecutor executor(base::MessagePumpType::IO); - // Parse the command line arguments and set up logging. - CHECK(base::CommandLine::Init(argc, argv)); - base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); - - CHECK(InitLoggingFromCommandLineDefaultingToStderrForTest( // IN-TEST - command_line)); + const bool is_run_from_launcher = command_line->HasSwitch(kFromLauncher); + const bool use_context_provider = !command_line->HasSwitch(kUseWebInstance); + if (!is_run_from_launcher && !use_context_provider) { + return RelaunchForWebInstanceHost(*command_line); + } absl::optional<uint16_t> remote_debugging_port = GetRemoteDebuggingPort(*command_line); @@ -91,7 +134,6 @@ int main(int argc, char** argv) { const bool is_headless = command_line->HasSwitch(kHeadlessSwitch); const bool enable_protected_media_identifier_access = command_line->HasSwitch(kEnableProtectedMediaIdentifier); - const bool use_context_provider = !command_line->HasSwitch(kUseWebInstance); const bool enable_web_instance_tmp = command_line->HasSwitch(kEnableWebInstanceTmp); const bool with_webui = command_line->HasSwitch(switches::kWithWebui); @@ -173,12 +215,9 @@ int main(int argc, char** argv) { base::RunLoop run_loop; - // Create the browser |context|. - fuchsia::web::ContextPtr context; - - // Keep alive in run_loop scope. fuchsia::web::ContextProviderPtr web_context_provider; - std::unique_ptr<WebInstanceHostV1> web_instance_host; + std::unique_ptr<WebInstanceHost> web_instance_host; + fuchsia::web::ContextPtr context; fuchsia::io::DirectoryHandle tmp_directory; if (use_context_provider) { @@ -189,7 +228,7 @@ int main(int argc, char** argv) { web_context_provider->Create(std::move(create_context_params), context.NewRequest()); } else { - web_instance_host = std::make_unique<WebInstanceHostV1>(); + web_instance_host = std::make_unique<WebInstanceHost>(); if (enable_web_instance_tmp) { const zx_status_t status = fdio_open( "/tmp", @@ -290,6 +329,8 @@ int main(int argc, char** argv) { LOG(INFO) << "Launched browser at URL " << url.spec(); + base::ComponentContextForProcess()->outgoing()->ServeFromStartupInfo(); + // Run until the process is killed with CTRL-C or the connections to Web // Engine interfaces are dropped. run_loop.Run(); diff --git a/fuchsia_web/shell/web_engine_shell.cml b/fuchsia_web/shell/web_engine_shell.cml index 1ea9df29071f5..5b32a331c1101 100644 --- a/fuchsia_web/shell/web_engine_shell.cml +++ b/fuchsia_web/shell/web_engine_shell.cml @@ -10,38 +10,36 @@ // to function correctly. "//build/config/fuchsia/test/chromium_test_facet.shard.test-cml", "//build/config/fuchsia/test/elf_test_ambient_exec_runner.shard.test-cml", + "sys/component/realm_builder_absolute.shard.cml", "syslog/client.shard.cml", ], program: { binary: "web_engine_shell_exec", }, use: [ - { - protocol: [ - "fuchsia.feedback.ComponentDataRegister", - "fuchsia.feedback.CrashReportingProductRegister", - "fuchsia.sys.Environment", - "fuchsia.sys.Launcher", - "fuchsia.sys.Loader", - ], - }, - // Used conditionally based on the absence of `--use-web-instance` on the // command line at runtime. { protocol: "fuchsia.web.ContextProvider", availability: "optional", }, + + // Used to hold the cdm_data directory passed to web_instance. { storage: "data", path: "/data", }, + + // Needed when launched with --enable-web-instance-tmp. { storage: "tmp", path: "/tmp", + availability: "optional", }, - // Uses below this line are for web_instance.cmx + // Uses below this line are for web_instance.cmx. + // TODO(crbug.com/1280703): Revise as needed when context_provider is + // migrated to launch web_instance.cm. { protocol: [ "fuchsia.buildinfo.Provider", @@ -93,10 +91,103 @@ availability: "optional", }, ], + offer: [ + { + storage: [ + "data", + "tmp", + ], + from: "parent", + to: "#realm_builder", + }, + { + directory: "root-ssl-certificates", + from: "parent", + to: "#realm_builder", + }, + + // Offers for web_instance.cm. + // The chromium test realm offers the system-wide config-data dir to test + // components. Route the web_engine sub-directory of this as required by + // WebInstanceHost. + { + directory: "config-data", + from: "parent", + as: "config-data-for-web-instance", + to: "#realm_builder", + subdir: "web_engine", + availability: "optional", + }, + { + protocol: [ + "fuchsia.buildinfo.Provider", + "fuchsia.device.NameProvider", + "fuchsia.feedback.ComponentDataRegister", + "fuchsia.feedback.CrashReportingProductRegister", + "fuchsia.fonts.Provider", + "fuchsia.hwinfo.Product", + "fuchsia.input.virtualkeyboard.ControllerCreator", + "fuchsia.intl.PropertyProvider", + "fuchsia.kernel.VmexResource", + "fuchsia.media.Audio", + "fuchsia.media.AudioDeviceEnumerator", + "fuchsia.media.ProfileProvider", + "fuchsia.media.SessionAudioConsumerFactory", + "fuchsia.mediacodec.CodecFactory", + "fuchsia.memorypressure.Provider", + "fuchsia.net.interfaces.State", + "fuchsia.net.name.Lookup", + "fuchsia.posix.socket.Provider", + "fuchsia.process.Launcher", + "fuchsia.sysmem.Allocator", + "fuchsia.ui.input3.Keyboard", + ], + from: "parent", + to: "#realm_builder", + }, + + // Used conditionally based on the value of `enable_widevine` at build time. + // TODO(crbug.com/1379411): Use a shard to conditionally use based on + // build-time config. + { + protocol: "fuchsia.media.drm.Widevine", + from: "parent", + to: "#realm_builder", + availability: "optional", + }, + + // Used conditionally based on the absence of `--headless` on the command + // line at runtime. + { + protocol: [ + "fuchsia.accessibility.semantics.SemanticsManager", + "fuchsia.tracing.provider.Registry", + "fuchsia.ui.composition.Allocator", + "fuchsia.ui.composition.Flatland", + "fuchsia.ui.policy.Presenter", + "fuchsia.ui.scenic.Scenic", + "fuchsia.vulkan.loader.Loader", + ], + from: "parent", + to: "#realm_builder", + availability: "optional", + }, + + // Optional capabilities, dependent on availability of tracing services. + { + protocol: "fuchsia.tracing.perfetto.ProducerConnector", + from: "parent", + to: "#realm_builder", + availability: "optional", + }, + ], facets: { - "fuchsia.test.deprecated-allowed-packages": [ - "web_engine", - "web_engine_with_webui", - ], + "fuchsia.test": { + "deprecated-allowed-packages": [ + "test_manager", + "web_engine", + "web_engine_with_webui", + ], + }, }, } diff --git a/fuchsia_web/shell/web_engine_shell.cmx b/fuchsia_web/shell/web_engine_shell.cmx deleted file mode 100644 index fd05f249eec4b..0000000000000 --- a/fuchsia_web/shell/web_engine_shell.cmx +++ /dev/null @@ -1,50 +0,0 @@ -{ - "program": { - "binary": "web_engine_shell_exec" - }, - "sandbox": { - "features": [ - "deprecated-ambient-replace-as-executable", - "isolated-persistent-storage", - "isolated-temp" - ], - "services": [ - "fuchsia.accessibility.semantics.SemanticsManager", - "fuchsia.buildinfo.Provider", - "fuchsia.camera3.DeviceWatcher", - "fuchsia.device.NameProvider", - "fuchsia.feedback.ComponentDataRegister", - "fuchsia.feedback.CrashReportingProductRegister", - "fuchsia.fonts.Provider", - "fuchsia.hwinfo.Product", - "fuchsia.input.virtualkeyboard.ControllerCreator", - "fuchsia.intl.PropertyProvider", - "fuchsia.kernel.VmexResource", - "fuchsia.logger.LogSink", - "fuchsia.media.Audio", - "fuchsia.media.AudioDeviceEnumerator", - "fuchsia.media.ProfileProvider", - "fuchsia.media.SessionAudioConsumerFactory", - "fuchsia.media.drm.Widevine", - "fuchsia.mediacodec.CodecFactory", - "fuchsia.memorypressure.Provider", - "fuchsia.net.interfaces.State", - "fuchsia.net.name.Lookup", - "fuchsia.posix.socket.Provider", - "fuchsia.process.Launcher", - "fuchsia.sys.Environment", - "fuchsia.sys.Launcher", - "fuchsia.sys.Loader", - "fuchsia.sysmem.Allocator", - "fuchsia.tracing.perfetto.ProducerConnector", - "fuchsia.tracing.provider.Registry", - "fuchsia.ui.composition.Allocator", - "fuchsia.ui.composition.Flatland", - "fuchsia.ui.input3.Keyboard", - "fuchsia.ui.policy.Presenter", - "fuchsia.ui.scenic.Scenic", - "fuchsia.vulkan.loader.Loader", - "fuchsia.web.ContextProvider" - ] - } -} diff --git a/fuchsia_web/shell/web_engine_shell_for_web_instance_host.cml b/fuchsia_web/shell/web_engine_shell_for_web_instance_host.cml new file mode 100644 index 0000000000000..c022c67a7fdc1 --- /dev/null +++ b/fuchsia_web/shell/web_engine_shell_for_web_instance_host.cml @@ -0,0 +1,47 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// A component that is launched by web_engine_shell since web_engine_shell +// itself runs as a test component, and therefore cannot run WebInstanceHost +// directly. +{ + include: [ + "//fuchsia_web/webinstance_host/web_instance_host.shard.cml", + "syslog/client.shard.cml", + ], + program: { + runner: "elf", + binary: "web_engine_shell_exec", + args: [ + // Inform web_engine_shell that it is running as the sub-process in which + // WebInstanceHost may be used. In this scenario, the main test component + // is running only as a launcher of this child. + "--from-launcher", + ], + + // Required to allow JIT in child processes such as renderers. + // Known as 'deprecated-ambient-replace-as-executable' in CFv1. + job_policy_ambient_mark_vmo_exec: "true", + }, + use: [ + // Required if not run with --headless. + { + protocol: "fuchsia.ui.policy.Presenter", + availability: "optional", + }, + + // Used to hold the cdm_data directory passed to web_instance. + { + storage: "data", + path: "/data", + }, + + // Needed when launched with --enable-web-instance-tmp. + { + storage: "tmp", + path: "/tmp", + availability: "optional", + }, + ], +} diff --git a/fuchsia_web/webengine/BUILD.gn b/fuchsia_web/webengine/BUILD.gn index 1004e511fd0a6..113715d9ac34b 100644 --- a/fuchsia_web/webengine/BUILD.gn +++ b/fuchsia_web/webengine/BUILD.gn @@ -491,6 +491,7 @@ fuchsia_package_installer("web_engine_installer") { fuchsia_package("web_engine_with_webui") { # TODO(fxbug.dev/100944): Add appropriate visibility when fixed. deps = [ + ":web_instance_component", ":web_instance_component_cfv1", ":webui_resources", ] diff --git a/fuchsia_web/webengine/web_instance.cml b/fuchsia_web/webengine/web_instance.cml index a99d0d450467e..6fa3d8bca7225 100644 --- a/fuchsia_web/webengine/web_instance.cml +++ b/fuchsia_web/webengine/web_instance.cml @@ -77,6 +77,13 @@ path: "/config/command-line", availability: "optional", }, + // Temporary directory specified by WebInstanceHost.set_tmp_dir. + { + directory: "tmp", + path: "/tmp", + rights: [ "rw*" ], + availability: "optional", + }, { // Required capabilities for all configurations. protocol: [ @@ -155,10 +162,5 @@ ], availability: "optional", }, - { - storage: "cache", - path: "/cache", - availability: "optional", - }, ] } diff --git a/fuchsia_web/webinstance_host/BUILD.gn b/fuchsia_web/webinstance_host/BUILD.gn index 3a7b6d0e93cef..d51a0bb4af96f 100644 --- a/fuchsia_web/webinstance_host/BUILD.gn +++ b/fuchsia_web/webinstance_host/BUILD.gn @@ -29,7 +29,10 @@ source_set("webinstance_host") { "web_instance_host_internal.cc", "web_instance_host_internal.h", ] - public = [ "web_instance_host.h" ] + public = [ + "web_instance_host.h", + "web_instance_host_constants.h", + ] deps = [ "//base:base_static", "//build:chromecast_buildflags", diff --git a/fuchsia_web/webinstance_host/web_instance_host.cc b/fuchsia_web/webinstance_host/web_instance_host.cc index 28dbfd3aedf5f..86a69dd0fb444 100644 --- a/fuchsia_web/webinstance_host/web_instance_host.cc +++ b/fuchsia_web/webinstance_host/web_instance_host.cc @@ -20,6 +20,7 @@ #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" @@ -32,6 +33,7 @@ #include "base/types/expected.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 { @@ -43,6 +45,10 @@ namespace fcdecl = ::fuchsia::component::decl; constexpr char kWebInstanceComponentUrl[] = "fuchsia-pkg://fuchsia.com/web_engine#meta/web_instance.cm"; +// Test-only URL for web hosting Component instances with WebUI resources. +const char kWebInstanceWithWebUiComponentUrl[] = + "fuchsia-pkg://fuchsia.com/web_engine_with_webui#meta/web_instance.cm"; + // The name of the component collection hosting the instances. constexpr char kCollectionName[] = "web_instances"; @@ -58,7 +64,8 @@ std::string InstanceNameFromId(const base::GUID& id) { return base::StrCat({kCollectionName, "_", id.AsLowercaseString()}); } -void DestroyChild(fuchsia::component::Realm& realm, const std::string& name) { +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) { @@ -67,8 +74,8 @@ void DestroyChild(fuchsia::component::Realm& realm, const std::string& name) { }); } -void DestroyChildDirectory(vfs::PseudoDir* instances_dir, - const std::string& name) { +void DestroyInstanceDirectory(vfs::PseudoDir* instances_dir, + const std::string& name) { zx_status_t status = instances_dir->RemoveEntry(name); ZX_DCHECK(status == ZX_OK, status); } @@ -138,13 +145,50 @@ class InstanceBuilder { // directory. void ServeCommandLine(); - // Serves `directory` as `name` in the instance's subtree as a read-only or - // a read-write (if `writeable`) directory. `name` is both the name of the - // directory and the name of the capability expected by the instance. - void ServeDirectory(base::StringPiece name, - std::unique_ptr<vfs::internal::Directory> directory, + // Adds offers from `void` for any 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 `directory` as `offer` in the instance's subtree as a read-only or + // a read-write (if `writeable`) directory. + void ServeDirectory(OptionalDirectory directory, + std::unique_ptr<vfs::internal::Directory> fs_directory, bool writeable); + // Offers the directory `directory` from `void`. + void OfferDirectoryFromVoid(OptionalDirectory directory); + // Offers the read-only directory capability named `name` from the parent. void OfferDirectoryFromParent(base::StringPiece name); @@ -153,6 +197,10 @@ class InstanceBuilder { 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_; }; @@ -197,7 +245,7 @@ InstanceBuilder::InstanceBuilder(fuchsia::component::Realm& realm, InstanceBuilder::~InstanceBuilder() { if (instance_dir_) { - DestroyChildDirectory(GetWebInstancesCollectionDir(), name_); + DestroyInstanceDirectory(GetWebInstancesCollectionDir(), name_); } } @@ -222,7 +270,7 @@ void InstanceBuilder::ServeRootSslCertificates() { void InstanceBuilder::ServeDataDirectory( fidl::InterfaceHandle<fuchsia::io::Directory> data_directory) { DCHECK(instance_dir_); - ServeDirectory("data", + ServeDirectory(OptionalDirectory::kData, std::make_unique<vfs::RemoteDir>(std::move(data_directory)), /*writeable=*/true); } @@ -244,7 +292,8 @@ zx_status_t InstanceBuilder::ServeContentDirectories( } } - ServeDirectory("content-directories", std::move(content_dirs), + ServeDirectory(OptionalDirectory::kContentDirectories, + std::move(content_dirs), /*writeable=*/false); return ZX_OK; } @@ -253,13 +302,14 @@ void InstanceBuilder::ServeCdmDataDirectory( fidl::InterfaceHandle<fuchsia::io::Directory> cdm_data_directory) { DCHECK(instance_dir_); ServeDirectory( - "cdm_data", + OptionalDirectory::kCdmData, std::make_unique<vfs::RemoteDir>(std::move(cdm_data_directory)), /*writeable=*/true); } void InstanceBuilder::ServeTmpDirectory(fuchsia::io::DirectoryHandle tmp_dir) { - ServeDirectory("tmp", std::make_unique<vfs::RemoteDir>(std::move(tmp_dir)), + ServeDirectory(OptionalDirectory::kTmp, + std::make_unique<vfs::RemoteDir>(std::move(tmp_dir)), /*writeable=*/true); } @@ -272,9 +322,20 @@ Instance InstanceBuilder::Build( fidl::InterfaceRequest<fuchsia::io::Directory> 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(kWebInstanceComponentUrl); + // TODO(crbug.com/1010222): Make kWebInstanceComponentUrl a relative + // component URL and remove this workaround. + // TODO(crbug.com/1395054): Better yet, replace the with_webui component with + // direct routing of the resources from web_engine_shell. + child_decl.set_url( + base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kWithWebui) + ? kWebInstanceWithWebUiComponentUrl + : kWebInstanceComponentUrl); child_decl.set_startup(fcdecl::StartupMode::LAZY); ::fuchsia::component::CreateChildArgs create_child_args; @@ -335,17 +396,48 @@ void InstanceBuilder::ServeCommandLine() { })); ZX_DCHECK(status == ZX_OK, status); - ServeDirectory("command-line-config", std::move(config_dir), + ServeDirectory(OptionalDirectory::kCommandLineConfig, std::move(config_dir), /*writeable=*/false); } +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)) { + OfferDirectoryFromVoid(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::ServeDirectory( - base::StringPiece name, - std::unique_ptr<vfs::internal::Directory> directory, + OptionalDirectory directory, + std::unique_ptr<vfs::internal::Directory> fs_directory, bool writeable) { DCHECK(instance_dir_); + DCHECK(!is_directory_served(directory)); + + set_directory_served(directory); + const auto name = GetDirectoryName(directory); + zx_status_t status = - instance_dir_->AddEntry(std::string(name), std::move(directory)); + instance_dir_->AddEntry(std::string(name), std::move(fs_directory)); ZX_DCHECK(status == ZX_OK, status); dynamic_offers_.push_back(fcdecl::Offer::WithDirectory( @@ -360,6 +452,22 @@ void InstanceBuilder::ServeDirectory( .set_availability(fcdecl::Availability::REQUIRED)))); } +void InstanceBuilder::OfferDirectoryFromVoid(OptionalDirectory directory) { + DCHECK(!is_directory_served(directory)); + + // TODO(fxbug.dev/121722): Enable this once dynamic offer-from-void is + // supported in Fuchsia. +#if 0 + const auto name = GetDirectoryName(directory); + dynamic_offers_.push_back(fcdecl::Offer::WithDirectory( + std::move(fcdecl::OfferDirectory() + .set_source(fcdecl::Ref::WithVoidType({})) + .set_target_name(std::string(name)) + .set_dependency_type(fcdecl::DependencyType::STRONG) + .set_availability(fcdecl::Availability::OPTIONAL)))); +#endif +} + void InstanceBuilder::OfferDirectoryFromParent(base::StringPiece name) { DCHECK(instance_dir_); dynamic_offers_.push_back(fcdecl::Offer::WithDirectory( @@ -446,7 +554,7 @@ WebInstanceHost::WebInstanceHost() { WebInstanceHost::~WebInstanceHost() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - DCHECK(instances_.empty()); + Uninitialize(); } zx_status_t WebInstanceHost::CreateInstanceForContextWithCopiedArgs( @@ -530,28 +638,29 @@ void WebInstanceHost::Initialize() { void WebInstanceHost::Uninitialize() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - DCHECK(instances_.empty()); + + // Destroy all child instances and each one's outgoing directory subtree. + auto* const instances_dir = GetWebInstancesCollectionDir(); + 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 case multiple hosts are active in the same process. + // 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"; - - // Disconnect from all children and remove their directories. - auto* const instances_dir = GetWebInstancesCollectionDir(); - for (auto& [id, binder_ptr] : instances_) { - DestroyChildDirectory(instances_dir, InstanceNameFromId(id)); - binder_ptr.Unbind(); - } - instances_.clear(); - - // Go back to the initial state. Uninitialize(); } @@ -561,10 +670,10 @@ void WebInstanceHost::OnComponentBinderClosed(const base::GUID& id, // Destroy the child instance. const std::string name(InstanceNameFromId(id)); - DestroyChild(*realm_, name); + DestroyInstance(*realm_, name); // Drop the directory subtree for the child instance. - DestroyChildDirectory(GetWebInstancesCollectionDir(), name); + DestroyInstanceDirectory(GetWebInstancesCollectionDir(), 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 diff --git a/fuchsia_web/webinstance_host/web_instance_host.h b/fuchsia_web/webinstance_host/web_instance_host.h index 2eaa9effe8693..9363d80653654 100644 --- a/fuchsia_web/webinstance_host/web_instance_host.h +++ b/fuchsia_web/webinstance_host/web_instance_host.h @@ -77,8 +77,8 @@ class WebInstanceHost { // Connects to the fuchsia.component/Realm protocol. void Initialize(); - // Unbinds from the fuchsia.component/Realm protocol. May only be called once - // all web_instances have terminated. + // Destroys all child instances and associated resources and unbinds from the + // fuchsia.component/Realm protocol. void Uninitialize(); // Error handler for the channel to RealmBuilder. diff --git a/fuchsia_web/webinstance_host/web_instance_host.shard.cml b/fuchsia_web/webinstance_host/web_instance_host.shard.cml index 17b66ebdd78b4..0b3165231867d 100644 --- a/fuchsia_web/webinstance_host/web_instance_host.shard.cml +++ b/fuchsia_web/webinstance_host/web_instance_host.shard.cml @@ -56,7 +56,6 @@ "fuchsia.memorypressure.Provider", "fuchsia.process.Launcher", "fuchsia.sysmem.Allocator", - "fuchsia.tracing.provider.Registry", ], from: "parent", to: "#web_instances", @@ -78,12 +77,6 @@ from: "parent", to: "#web_instances", availability: "optional", - source_availability: "unknown", - }, - { - storage: "cache", - from: "parent", - to: "#web_instances", }, ], } diff --git a/fuchsia_web/webinstance_host/web_instance_host_internal.cc b/fuchsia_web/webinstance_host/web_instance_host_internal.cc index 1f8776d3a5153..9c7d064c33303 100644 --- a/fuchsia_web/webinstance_host/web_instance_host_internal.cc +++ b/fuchsia_web/webinstance_host/web_instance_host_internal.cc @@ -434,6 +434,8 @@ void AppendDynamicServices(fuchsia::web::ContextFeatureFlags features, "fuchsia.media.AudioDeviceEnumerator"}, {ContextFeatureFlags::AUDIO, ContextFeatureFlags::AUDIO, "fuchsia.media.SessionAudioConsumerFactory"}, + {ContextFeatureFlags::VULKAN, ContextFeatureFlags::VULKAN, + "fuchsia.tracing.provider.Registry"}, {ContextFeatureFlags::VULKAN, ContextFeatureFlags::VULKAN, "fuchsia.vulkan.loader.Loader"}, {ContextFeatureFlags::HARDWARE_VIDEO_DECODER, diff --git a/fuchsia_web/webinstance_host/web_instance_host_v1.cc b/fuchsia_web/webinstance_host/web_instance_host_v1.cc index 7de75f25bfec5..dd7e08ae1104b 100644 --- a/fuchsia_web/webinstance_host/web_instance_host_v1.cc +++ b/fuchsia_web/webinstance_host/web_instance_host_v1.cc @@ -146,7 +146,6 @@ std::vector<std::string> GetRequiredServicesForConfig( "fuchsia.settings.Display", // Used if preferred theme is DEFAULT. "fuchsia.sysmem.Allocator", "fuchsia.tracing.perfetto.ProducerConnector", - "fuchsia.tracing.provider.Registry", "fuchsia.ui.scenic.Scenic"}; // TODO(crbug.com/1209031): Provide these conditionally, once corresponding @@ -186,6 +185,7 @@ std::vector<std::string> GetRequiredServicesForConfig( if ((features & fuchsia::web::ContextFeatureFlags::VULKAN) == fuchsia::web::ContextFeatureFlags::VULKAN) { + services.emplace_back("fuchsia.tracing.provider.Registry"); services.emplace_back("fuchsia.vulkan.loader.Loader"); }