| // Copyright 2018 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/webengine/browser/web_engine_browser_main_parts.h" |
| |
| #include <fuchsia/web/cpp/fidl.h> |
| #include <lib/inspect/component/cpp/component.h> |
| #include <lib/sys/cpp/component_context.h> |
| #include <lib/sys/cpp/outgoing_directory.h> |
| #include <utility> |
| #include <vector> |
| |
| #include <lib/async/default.h> |
| #include "base/command_line.h" |
| #include "base/files/file_path.h" |
| #include "base/files/important_file_writer_cleaner.h" |
| #include "base/fuchsia/file_utils.h" |
| #include "base/fuchsia/fuchsia_logging.h" |
| #include "base/fuchsia/intl_profile_watcher.h" |
| #include "base/fuchsia/koid.h" |
| #include "base/fuchsia/process_context.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/i18n/rtl.h" |
| #include "base/logging.h" |
| #include "base/no_destructor.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/system/sys_info.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "build/build_config.h" |
| #include "build/chromecast_buildflags.h" |
| #include "components/fuchsia_component_support/inspect.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/gpu_data_manager.h" |
| #include "content/public/browser/histogram_fetcher.h" |
| #include "content/public/browser/network_quality_observer_factory.h" |
| #include "content/public/browser/network_service_instance.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/common/main_function_params.h" |
| #include "content/public/common/result_codes.h" |
| #include "fuchsia_web/webengine/browser/context_impl.h" |
| #include "fuchsia_web/webengine/browser/web_engine_browser_context.h" |
| #include "fuchsia_web/webengine/browser/web_engine_devtools_controller.h" |
| #include "fuchsia_web/webengine/browser/web_engine_memory_inspector.h" |
| #include "fuchsia_web/webengine/switches.h" |
| #include "gpu/command_buffer/service/gpu_switches.h" |
| #include "media/mojo/services/fuchsia_cdm_manager.h" |
| #include "net/http/http_util.h" |
| #include "services/network/public/cpp/network_quality_tracker.h" |
| #include "services/network/public/mojom/network_context.mojom.h" |
| #include "third_party/widevine/cdm/buildflags.h" |
| #include "ui/aura/screen_ozone.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/display/screen.h" |
| #include "ui/gfx/switches.h" |
| #include "ui/ozone/public/ozone_platform.h" |
| #include "ui/ozone/public/ozone_switches.h" |
| |
| #if BUILDFLAG(ENABLE_CAST_RECEIVER) |
| #include "components/fuchsia_legacymetrics/legacymetrics_client.h" // nogncheck |
| #include "fuchsia_web/webengine/common/cast_streaming.h" // nogncheck |
| #endif |
| |
| #if BUILDFLAG(ENABLE_WIDEVINE) |
| #include "third_party/widevine/cdm/widevine_cdm_common.h" // nogncheck |
| #endif |
| |
| namespace { |
| |
| fidl::InterfaceRequest<fuchsia::web::Context>& GetTestRequest() { |
| static base::NoDestructor<fidl::InterfaceRequest<fuchsia::web::Context>> |
| request; |
| return *request; |
| } |
| |
| #if BUILDFLAG(ENABLE_CAST_RECEIVER) |
| constexpr base::TimeDelta kMetricsReportingInterval = base::Minutes(1); |
| |
| constexpr base::TimeDelta kChildProcessHistogramFetchTimeout = |
| base::Seconds(10); |
| |
| // Merge child process' histogram deltas into the browser process' histograms. |
| void FetchHistogramsFromChildProcesses( |
| base::OnceCallback<void(std::vector<fuchsia::legacymetrics::Event>)> |
| done_cb) { |
| content::FetchHistogramsAsynchronously( |
| base::SingleThreadTaskRunner::GetCurrentDefault(), |
| base::BindOnce(std::move(done_cb), |
| std::vector<fuchsia::legacymetrics::Event>()), |
| kChildProcessHistogramFetchTimeout); |
| } |
| #endif |
| |
| template <typename KeySystemInterface> |
| fidl::InterfaceHandle<fuchsia::media::drm::KeySystem> ConnectToKeySystem() { |
| static_assert( |
| (std::is_same<KeySystemInterface, fuchsia::media::drm::Widevine>::value || |
| std::is_same<KeySystemInterface, fuchsia::media::drm::PlayReady>::value), |
| "KeySystemInterface must be either fuchsia::media::drm::Widevine or " |
| "fuchsia::media::drm::PlayReady"); |
| |
| fidl::InterfaceHandle<fuchsia::media::drm::KeySystem> key_system; |
| base::ComponentContextForProcess()->svc()->Connect(key_system.NewRequest(), |
| KeySystemInterface::Name_); |
| return key_system; |
| } |
| |
| std::unique_ptr<media::FuchsiaCdmManager> CreateCdmManager() { |
| media::FuchsiaCdmManager::CreateKeySystemCallbackMap |
| create_key_system_callbacks; |
| |
| const auto* command_line = base::CommandLine::ForCurrentProcess(); |
| if (command_line->HasSwitch(switches::kEnableWidevine)) { |
| #if BUILDFLAG(ENABLE_WIDEVINE) |
| create_key_system_callbacks.emplace( |
| kWidevineKeySystem, |
| base::BindRepeating( |
| &ConnectToKeySystem<fuchsia::media::drm::Widevine>)); |
| #else |
| LOG(WARNING) << "Widevine is not supported."; |
| #endif |
| } |
| |
| std::string playready_key_system = |
| command_line->GetSwitchValueASCII(switches::kPlayreadyKeySystem); |
| if (!playready_key_system.empty()) { |
| #if BUILDFLAG(ENABLE_WIDEVINE) && BUILDFLAG(ENABLE_CAST_RECEIVER) |
| create_key_system_callbacks.emplace( |
| playready_key_system, |
| base::BindRepeating( |
| &ConnectToKeySystem<fuchsia::media::drm::PlayReady>)); |
| #else |
| LOG(WARNING) << "PlayReady is not supported."; |
| #endif |
| } |
| |
| std::string cdm_data_directory = |
| command_line->GetSwitchValueASCII(switches::kCdmDataDirectory); |
| |
| std::optional<uint64_t> cdm_data_quota_bytes; |
| if (command_line->HasSwitch(switches::kCdmDataQuotaBytes)) { |
| uint64_t value = 0; |
| CHECK(base::StringToUint64( |
| command_line->GetSwitchValueASCII(switches::kCdmDataQuotaBytes), |
| &value)); |
| cdm_data_quota_bytes = value; |
| } |
| |
| return std::make_unique<media::FuchsiaCdmManager>( |
| std::move(create_key_system_callbacks), |
| base::FilePath(cdm_data_directory), cdm_data_quota_bytes); |
| } |
| |
| } // namespace |
| |
| void FrameHostImpl::CreateFrameWithParams( |
| fuchsia::web::CreateFrameParams params, |
| fidl::InterfaceRequest<fuchsia::web::Frame> request) { |
| context_.CreateFrameWithParams(std::move(params), std::move(request)); |
| } |
| |
| WebEngineBrowserMainParts::WebEngineBrowserMainParts( |
| content::ContentBrowserClient* browser_client) |
| : browser_client_(browser_client) {} |
| |
| WebEngineBrowserMainParts::~WebEngineBrowserMainParts() = default; |
| |
| std::vector<content::BrowserContext*> |
| WebEngineBrowserMainParts::browser_contexts() const { |
| std::vector<content::BrowserContext*> contexts; |
| contexts.reserve(context_bindings_.size()); |
| for (auto& binding : context_bindings_.bindings()) |
| contexts.push_back(binding->impl()->browser_context()); |
| return contexts; |
| } |
| |
| void WebEngineBrowserMainParts::PostEarlyInitialization() { |
| base::ImportantFileWriterCleaner::GetInstance().Initialize(); |
| } |
| |
| int WebEngineBrowserMainParts::PreMainMessageLoopRun() { |
| DCHECK_EQ(context_bindings_.size(), 0u); |
| |
| // Initialize the |component_inspector_| to allow diagnostics to be published. |
| component_inspector_ = std::make_unique<inspect::ComponentInspector>( |
| async_get_default_dispatcher(), inspect::PublishOptions{}); |
| fuchsia_component_support::PublishVersionInfoToInspect( |
| &component_inspector_->root()); |
| |
| // Add a node providing memory details for this whole web instance. |
| memory_inspector_ = |
| std::make_unique<WebEngineMemoryInspector>(component_inspector_->root()); |
| |
| const auto* command_line = base::CommandLine::ForCurrentProcess(); |
| |
| // If Vulkan is not enabled then disable hardware acceleration. Otherwise gpu |
| // process will be restarted several times trying to initialize GL before |
| // falling back to software compositing. |
| if (!command_line->HasSwitch(switches::kUseVulkan)) { |
| content::GpuDataManager* gpu_data_manager = |
| content::GpuDataManager::GetInstance(); |
| DCHECK(gpu_data_manager); |
| gpu_data_manager->DisableHardwareAcceleration(); |
| } |
| |
| devtools_controller_ = |
| WebEngineDevToolsController::CreateFromCommandLine(*command_line); |
| |
| #if BUILDFLAG(ENABLE_CAST_RECEIVER) |
| if (command_line->HasSwitch(switches::kUseLegacyMetricsService)) { |
| legacy_metrics_client_ = |
| std::make_unique<fuchsia_legacymetrics::LegacyMetricsClient>(); |
| |
| // Add a hook to asynchronously pull metrics from child processes just prior |
| // to uploading. |
| legacy_metrics_client_->SetReportAdditionalMetricsCallback( |
| base::BindRepeating(&FetchHistogramsFromChildProcesses)); |
| |
| legacy_metrics_client_->Start(kMetricsReportingInterval); |
| } |
| #endif |
| |
| // Configure SysInfo to report total/free space under "/data" based on the |
| // requested soft-quota, if any. This only affects persistent instances. |
| if (command_line->HasSwitch(switches::kDataQuotaBytes)) { |
| // Setting quota on "/data" is benign in incognito contexts, but indicates |
| // that the client probably mis-configured this instance. |
| DCHECK(!command_line->HasSwitch(switches::kIncognito)) |
| << "data_quota_bytes set for incognito instance."; |
| |
| uint64_t quota_bytes = 0; |
| CHECK(base::StringToUint64( |
| command_line->GetSwitchValueASCII(switches::kDataQuotaBytes), |
| "a_bytes)); |
| base::SysInfo::SetAmountOfTotalDiskSpace( |
| base::FilePath(base::kPersistedDataDirectoryPath), quota_bytes); |
| } |
| |
| // Watch for changes to the user's locale setting. |
| intl_profile_watcher_ = std::make_unique<base::FuchsiaIntlProfileWatcher>( |
| base::BindRepeating(&WebEngineBrowserMainParts::OnIntlProfileChanged, |
| base::Unretained(this))); |
| |
| // Configure Ozone with an Aura implementation of the Screen abstraction. |
| screen_ = std::make_unique<aura::ScreenOzone>(); |
| |
| // Create the FuchsiaCdmManager at startup rather than on-demand, to allow it |
| // to perform potentially expensive startup work in the background. |
| cdm_manager_ = CreateCdmManager(); |
| |
| // Disable RenderFrameHost's Javascript injection restrictions so that the |
| // Context and Frames can implement their own JS injection policy at a higher |
| // level. |
| content::RenderFrameHost::AllowInjectingJavaScript(); |
| |
| // Make sure temporary files associated with this process are cleaned up. |
| base::ImportantFileWriterCleaner::GetInstance().Start(); |
| |
| // Publish the fuchsia.web.Context and fuchsia.web.FrameHost capabilities. |
| base::ComponentContextForProcess()->outgoing()->AddPublicService( |
| fidl::InterfaceRequestHandler<fuchsia::web::Context>(fit::bind_member( |
| this, &WebEngineBrowserMainParts::HandleContextRequest))); |
| base::ComponentContextForProcess()->outgoing()->AddPublicService( |
| fidl::InterfaceRequestHandler<fuchsia::web::FrameHost>(fit::bind_member( |
| this, &WebEngineBrowserMainParts::HandleFrameHostRequest))); |
| |
| // TODO(crbug.com/1315601): Create a base::ProcessLifecycle instance here, to |
| // trigger graceful shutdown on component stop, when migrated to CFv2. |
| |
| // Manage network-quality signals and send them to renderers. Provides input |
| // for networking-related Client Hints. |
| network_quality_tracker_ = std::make_unique<network::NetworkQualityTracker>( |
| base::BindRepeating(&content::GetNetworkService)); |
| network_quality_observer_ = |
| content::CreateNetworkQualityObserver(network_quality_tracker_.get()); |
| |
| // Now that all services have been published, it is safe to start processing |
| // requests to the service directory. |
| base::ComponentContextForProcess()->outgoing()->ServeFromStartupInfo(); |
| |
| // TODO(crbug.com/1163073): Update tests to make a service connection to the |
| // Context and remove this workaround. |
| fidl::InterfaceRequest<fuchsia::web::Context>& request = GetTestRequest(); |
| if (request) |
| HandleContextRequest(std::move(request)); |
| |
| return content::RESULT_CODE_NORMAL_EXIT; |
| } |
| |
| void WebEngineBrowserMainParts::WillRunMainMessageLoop( |
| std::unique_ptr<base::RunLoop>& run_loop) { |
| quit_closure_ = run_loop->QuitClosure(); |
| } |
| |
| void WebEngineBrowserMainParts::PostMainMessageLoopRun() { |
| // Main loop should quit only after all Context instances have been destroyed. |
| DCHECK_EQ(context_bindings_.size(), 0u); |
| |
| // FrameHost channels may still be active and contain live Frames. Close them |
| // here so that they are torn-down before their dependent resources. |
| frame_host_bindings_.CloseAll(); |
| |
| // These resources must be freed while a MessageLoop is still available, so |
| // that they may post cleanup tasks during teardown. |
| // NOTE: Objects are destroyed in the reverse order of their creation. |
| #if BUILDFLAG(ENABLE_CAST_RECEIVER) |
| legacy_metrics_client_.reset(); |
| #endif |
| intl_profile_watcher_.reset(); |
| |
| base::ImportantFileWriterCleaner::GetInstance().Stop(); |
| } |
| |
| // static |
| void WebEngineBrowserMainParts::SetContextRequestForTest( |
| fidl::InterfaceRequest<fuchsia::web::Context> request) { |
| GetTestRequest() = std::move(request); |
| } |
| |
| ContextImpl* WebEngineBrowserMainParts::context_for_test() const { |
| if (context_bindings_.size() == 0) |
| return nullptr; |
| return context_bindings_.bindings().front()->impl().get(); |
| } |
| |
| std::vector<FrameHostImpl*> WebEngineBrowserMainParts::frame_hosts_for_test() |
| const { |
| std::vector<FrameHostImpl*> frame_host_impls; |
| for (auto& binding : frame_host_bindings_.bindings()) { |
| frame_host_impls.push_back(binding->impl().get()); |
| } |
| return frame_host_impls; |
| } |
| |
| void WebEngineBrowserMainParts::HandleContextRequest( |
| fidl::InterfaceRequest<fuchsia::web::Context> request) { |
| if (context_bindings_.size() > 0) { |
| request.Close(ZX_ERR_BAD_STATE); |
| return; |
| } |
| |
| // Create the BrowserContext for the fuchsia.web.Context, with persistence |
| // configured as requested via the command-line. |
| std::unique_ptr<WebEngineBrowserContext> browser_context; |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kIncognito)) { |
| browser_context = WebEngineBrowserContext::CreateIncognito( |
| network_quality_tracker_.get()); |
| } else { |
| browser_context = WebEngineBrowserContext::CreatePersistent( |
| base::FilePath(base::kPersistedDataDirectoryPath), |
| network_quality_tracker_.get()); |
| } |
| |
| auto inspect_node_name = |
| base::StringPrintf("context-%lu", *base::GetKoid(request.channel())); |
| auto context_impl = std::make_unique<ContextImpl>( |
| std::move(browser_context), |
| component_inspector_->root().CreateChild(inspect_node_name), |
| devtools_controller_.get()); |
| |
| #if BUILDFLAG(ENABLE_CAST_RECEIVER) |
| // If this web instance should allow CastStreaming then enable it in this |
| // ContextImpl. CastStreaming will not be available in FrameHost contexts. |
| if (IsCastStreamingEnabled()) |
| context_impl->SetCastStreamingEnabled(); |
| #endif |
| |
| // Create the fuchsia.web.Context implementation using the BrowserContext and |
| // configure it to terminate the process when the client goes away. |
| context_bindings_.AddBinding( |
| std::move(context_impl), std::move(request), /* dispatcher */ nullptr, |
| // Quit the browser main loop when the Context connection is |
| // dropped. |
| [this](zx_status_t status) { |
| ZX_LOG_IF(ERROR, status != ZX_ERR_PEER_CLOSED, status) |
| << " Context disconnected."; |
| BeginGracefulShutdown(); |
| }); |
| } |
| |
| void WebEngineBrowserMainParts::HandleFrameHostRequest( |
| fidl::InterfaceRequest<fuchsia::web::FrameHost> request) { |
| auto inspect_node_name = |
| base::StringPrintf("framehost-%lu", *base::GetKoid(request.channel())); |
| frame_host_bindings_.AddBinding( |
| std::make_unique<FrameHostImpl>( |
| component_inspector_->root().CreateChild(inspect_node_name), |
| devtools_controller_.get(), network_quality_tracker_.get()), |
| std::move(request)); |
| } |
| |
| void WebEngineBrowserMainParts::OnIntlProfileChanged( |
| const fuchsia::intl::Profile& profile) { |
| // Configure the ICU library in this process with the new primary locale. |
| std::string primary_locale = |
| base::FuchsiaIntlProfileWatcher::GetPrimaryLocaleIdFromProfile(profile); |
| base::i18n::SetICUDefaultLocale(primary_locale); |
| |
| { |
| // Reloading locale-specific resources requires synchronous blocking. |
| // Locale changes should not be frequent enough for this to cause jank. |
| base::ScopedAllowBlocking allow_blocking; |
| |
| std::string loaded_locale = |
| ui::ResourceBundle::GetSharedInstance().ReloadLocaleResources( |
| base::i18n::GetConfiguredLocale()); |
| |
| VLOG(1) << "Reloaded locale resources: " << loaded_locale; |
| } |
| |
| // Reconfigure each web.Context's NetworkContext with the new setting. |
| for (auto& binding : context_bindings_.bindings()) { |
| content::BrowserContext* const browser_context = |
| binding->impl()->browser_context(); |
| std::string accept_language = net::HttpUtil::GenerateAcceptLanguageHeader( |
| browser_client_->GetAcceptLangs(browser_context)); |
| browser_context->GetDefaultStoragePartition() |
| ->GetNetworkContext() |
| ->SetAcceptLanguage(accept_language); |
| } |
| } |
| |
| void WebEngineBrowserMainParts::BeginGracefulShutdown() { |
| if (quit_closure_) |
| std::move(quit_closure_).Run(); |
| } |