| // Copyright 2018 The Chromium 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 "fuchsia/runners/cast/cast_runner.h" |
| |
| #include <fuchsia/sys/cpp/fidl.h> |
| #include <fuchsia/web/cpp/fidl.h> |
| #include <lib/fit/function.h> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/callback_forward.h" |
| #include "base/files/file_enumerator.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/fuchsia/file_utils.h" |
| #include "base/fuchsia/filtered_service_directory.h" |
| #include "base/fuchsia/fuchsia_logging.h" |
| #include "base/fuchsia/process_context.h" |
| #include "base/logging.h" |
| #include "base/strings/strcat.h" |
| #include "base/time/time.h" |
| #include "base/values.h" |
| #include "fuchsia/base/agent_manager.h" |
| #include "fuchsia/base/config_reader.h" |
| #include "fuchsia/runners/cast/cast_streaming.h" |
| #include "fuchsia/runners/cast/pending_cast_component.h" |
| #include "fuchsia/runners/common/web_content_runner.h" |
| #include "url/gurl.h" |
| |
| namespace { |
| |
| // List of services provided to the WebEngine context. |
| // All services must be listed in cast_runner.cmx. |
| static constexpr const char* kServices[] = { |
| "fuchsia.accessibility.semantics.SemanticsManager", |
| "fuchsia.device.NameProvider", |
| "fuchsia.fonts.Provider", |
| "fuchsia.intl.PropertyProvider", |
| "fuchsia.logger.LogSink", |
| "fuchsia.media.SessionAudioConsumerFactory", |
| "fuchsia.media.drm.PlayReady", |
| "fuchsia.media.drm.Widevine", |
| "fuchsia.mediacodec.CodecFactory", |
| "fuchsia.memorypressure.Provider", |
| "fuchsia.net.NameLookup", |
| "fuchsia.net.interfaces.State", |
| "fuchsia.posix.socket.Provider", |
| "fuchsia.process.Launcher", |
| "fuchsia.settings.Display", |
| "fuchsia.sysmem.Allocator", |
| "fuchsia.ui.input.ImeService", |
| "fuchsia.ui.input.ImeVisibilityService", |
| "fuchsia.ui.scenic.Scenic", |
| "fuchsia.vulkan.loader.Loader", |
| |
| // These services are redirected to the Agent: |
| // * fuchsia.camera3.DeviceWatcher |
| // * fuchsia.legacymetrics.MetricsRecorder |
| // * fuchsia.media.Audio |
| }; |
| |
| bool IsPermissionGrantedInAppConfig( |
| const chromium::cast::ApplicationConfig& application_config, |
| fuchsia::web::PermissionType permission_type) { |
| if (application_config.has_permissions()) { |
| for (auto& permission : application_config.permissions()) { |
| if (permission.has_type() && permission.type() == permission_type) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // Names used to partition the Runner's persistent storage for different uses. |
| constexpr char kCdmDataSubdirectoryName[] = "cdm_data"; |
| constexpr char kProfileSubdirectoryName[] = "web_profile"; |
| |
| // Name of the file used to detect cache erasure. |
| // TODO(crbug.com/1188780): Remove once an explicit cache flush signal exists. |
| constexpr char kSentinelFileName[] = ".sentinel"; |
| |
| // Ephemeral remote debugging port used by child contexts. |
| const uint16_t kEphemeralRemoteDebuggingPort = 0; |
| |
| // Application URL for the pseudo-component providing fuchsia.web.FrameHost. |
| constexpr char kFrameHostComponentName[] = "cast:fuchsia.web.FrameHost"; |
| |
| // Application URL for the pseudo-component providing chromium.cast.DataReset. |
| constexpr char kDataResetComponentName[] = "cast:chromium.cast.DataReset"; |
| |
| // Subdirectory used to stage persistent directories to be deleted upon next |
| // startup. |
| const char kStagedForDeletionSubdirectory[] = "staged_for_deletion"; |
| |
| base::FilePath GetStagedForDeletionDirectoryPath() { |
| base::FilePath cache_directory(base::kPersistedCacheDirectoryPath); |
| return cache_directory.Append(kStagedForDeletionSubdirectory); |
| } |
| |
| // Deletes files/directories staged for deletion during the previous run. |
| // We delete synchronously on main thread for simplicity. Note that this |
| // overall mechanism is a temporary solution. TODO(crbug.com/1146480): migrate |
| // to the framework mechanism of clearing session data when available. |
| void DeleteStagedForDeletionDirectoryIfExists() { |
| const base::FilePath staged_for_deletion_directory = |
| GetStagedForDeletionDirectoryPath(); |
| |
| if (!PathExists(staged_for_deletion_directory)) |
| return; |
| |
| const base::TimeTicks started_at = base::TimeTicks::Now(); |
| bool result = base::DeletePathRecursively(staged_for_deletion_directory); |
| if (!result) { |
| LOG(ERROR) << "Failed to delete the staging directory"; |
| return; |
| } |
| |
| LOG(WARNING) << "Deleting old persistent data took " |
| << (base::TimeTicks::Now() - started_at).InMillisecondsF() |
| << " ms"; |
| } |
| |
| // Populates |params| with web data settings. Web data persistence is only |
| // enabled if a soft quota is explicitly specified via config-data. |
| void SetDataParamsForMainContext(fuchsia::web::CreateContextParams* params) { |
| // Set the web data quota based on the CastRunner configuration. |
| const base::Optional<base::Value>& config = cr_fuchsia::LoadPackageConfig(); |
| if (!config) |
| return; |
| |
| constexpr char kDataQuotaBytesSwitch[] = "data-quota-bytes"; |
| const base::Optional<int> data_quota_bytes = |
| config->FindIntPath(kDataQuotaBytesSwitch); |
| if (!data_quota_bytes) |
| return; |
| |
| // Allow best-effort persistent of Cast application data. |
| // TODO(crbug.com/1148334): Remove the need for an explicit quota to be |
| // configured, once the platform provides storage quotas. |
| const auto profile_path = base::FilePath(base::kPersistedCacheDirectoryPath) |
| .Append(kProfileSubdirectoryName); |
| CHECK(base::CreateDirectory(profile_path)); |
| params->set_data_directory(base::OpenDirectoryHandle(profile_path)); |
| CHECK(params->data_directory()); |
| params->set_data_quota_bytes(*data_quota_bytes); |
| } |
| |
| // Populates |params| with settings to enable Widevine & PlayReady CDMs. |
| // CDM data persistence is always enabled, with an optional soft quota. |
| void SetCdmParamsForMainContext(fuchsia::web::CreateContextParams* params) { |
| const base::Optional<base::Value>& config = cr_fuchsia::LoadPackageConfig(); |
| if (config) { |
| constexpr char kCdmDataQuotaBytesSwitch[] = "cdm-data-quota-bytes"; |
| const base::Optional<int> cdm_data_quota_bytes = |
| config->FindIntPath(kCdmDataQuotaBytesSwitch); |
| if (cdm_data_quota_bytes) |
| params->set_cdm_data_quota_bytes(*cdm_data_quota_bytes); |
| } |
| |
| // TODO(b/154204041): Consider using isolated-persistent-storage for CDM data. |
| // Create an isolated-cache-storage sub-directory for CDM data. |
| const auto cdm_data_path = base::FilePath(base::kPersistedCacheDirectoryPath) |
| .Append(kCdmDataSubdirectoryName); |
| CHECK(base::CreateDirectory(cdm_data_path)); |
| params->set_cdm_data_directory(base::OpenDirectoryHandle(cdm_data_path)); |
| CHECK(params->cdm_data_directory()); |
| |
| // Enable the Widevine and Playready CDMs. |
| *params->mutable_features() |= |
| fuchsia::web::ContextFeatureFlags::WIDEVINE_CDM; |
| const char kCastPlayreadyKeySystem[] = "com.chromecast.playready"; |
| params->set_playready_key_system(kCastPlayreadyKeySystem); |
| } |
| |
| // TODO(crbug.com/1120914): Remove this once Component Framework v2 can be |
| // used to route fuchsia.web.FrameHost capabilities cleanly. |
| class FrameHostComponent : public fuchsia::sys::ComponentController { |
| public: |
| // Creates a FrameHostComponent with lifetime managed by |controller_request|. |
| // Returns the incoming service directory, in case the CastRunner needs to use |
| // it to connect to the MetricsRecorder. |
| static base::WeakPtr<const sys::ServiceDirectory> |
| StartAndReturnIncomingServiceDirectory( |
| std::unique_ptr<base::StartupContext> startup_context, |
| fidl::InterfaceRequest<fuchsia::sys::ComponentController> |
| controller_request, |
| fuchsia::web::FrameHost* const frame_host_impl) { |
| // |frame_host_component| deletes itself when the client disconnects. |
| auto* frame_host_component = |
| new FrameHostComponent(std::move(startup_context), |
| std::move(controller_request), frame_host_impl); |
| return frame_host_component->weak_incoming_services_.GetWeakPtr(); |
| } |
| |
| private: |
| FrameHostComponent(std::unique_ptr<base::StartupContext> startup_context, |
| fidl::InterfaceRequest<fuchsia::sys::ComponentController> |
| controller_request, |
| fuchsia::web::FrameHost* const frame_host_impl) |
| : startup_context_(std::move(startup_context)), |
| frame_host_binding_(startup_context_->outgoing(), frame_host_impl), |
| weak_incoming_services_(startup_context_->svc()) { |
| startup_context_->ServeOutgoingDirectory(); |
| binding_.Bind(std::move(controller_request)); |
| binding_.set_error_handler([this](zx_status_t) { Kill(); }); |
| } |
| ~FrameHostComponent() final = default; |
| |
| // fuchsia::sys::ComponentController interface. |
| void Kill() final { delete this; } |
| void Detach() final { |
| binding_.Close(ZX_ERR_NOT_SUPPORTED); |
| delete this; |
| } |
| |
| const std::unique_ptr<base::StartupContext> startup_context_; |
| const base::ScopedServiceBinding<fuchsia::web::FrameHost> frame_host_binding_; |
| fidl::Binding<fuchsia::sys::ComponentController> binding_{this}; |
| |
| base::WeakPtrFactory<const sys::ServiceDirectory> weak_incoming_services_; |
| }; |
| |
| // TODO(crbug.com/1120914): Remove this once Component Framework v2 can be |
| // used to route chromium.cast.DataReset capabilities cleanly. |
| class DataResetComponent : public fuchsia::sys::ComponentController, |
| public chromium::cast::DataReset { |
| public: |
| // Creates a DataResetComponent with lifetime managed by |controller_request|. |
| static void Start(base::OnceCallback<bool()> delete_persistent_data, |
| std::unique_ptr<base::StartupContext> startup_context, |
| fidl::InterfaceRequest<fuchsia::sys::ComponentController> |
| controller_request) { |
| new DataResetComponent(std::move(delete_persistent_data), |
| std::move(startup_context), |
| std::move(controller_request)); |
| } |
| |
| private: |
| DataResetComponent(base::OnceCallback<bool()> delete_persistent_data, |
| std::unique_ptr<base::StartupContext> startup_context, |
| fidl::InterfaceRequest<fuchsia::sys::ComponentController> |
| controller_request) |
| : delete_persistent_data_(std::move(delete_persistent_data)), |
| startup_context_(std::move(startup_context)), |
| data_reset_handler_binding_(startup_context_->outgoing(), this) { |
| DCHECK(delete_persistent_data_); |
| startup_context_->ServeOutgoingDirectory(); |
| binding_.Bind(std::move(controller_request)); |
| binding_.set_error_handler([this](zx_status_t) { Kill(); }); |
| } |
| ~DataResetComponent() final = default; |
| |
| // fuchsia::sys::ComponentController interface. |
| void Kill() final { delete this; } |
| void Detach() final { |
| binding_.Close(ZX_ERR_NOT_SUPPORTED); |
| delete this; |
| } |
| |
| // chromium::cast::DataReset interface. |
| void DeletePersistentData(DeletePersistentDataCallback callback) final { |
| if (!delete_persistent_data_) { |
| // Repeated requests to DeletePersistentData are not supported. |
| binding_.Close(ZX_ERR_NOT_SUPPORTED); |
| return; |
| } |
| callback(std::move(delete_persistent_data_).Run()); |
| } |
| |
| base::OnceCallback<bool()> delete_persistent_data_; |
| std::unique_ptr<base::StartupContext> startup_context_; |
| const base::ScopedServiceBinding<chromium::cast::DataReset> |
| data_reset_handler_binding_; |
| fidl::Binding<fuchsia::sys::ComponentController> binding_{this}; |
| }; |
| |
| } // namespace |
| |
| CastRunner::CastRunner(bool is_headless) |
| : is_headless_(is_headless), |
| main_services_(std::make_unique<base::FilteredServiceDirectory>( |
| base::ComponentContextForProcess()->svc().get())), |
| main_context_(std::make_unique<WebContentRunner>( |
| base::BindRepeating(&CastRunner::GetMainContextParams, |
| base::Unretained(this)))), |
| isolated_services_(std::make_unique<base::FilteredServiceDirectory>( |
| base::ComponentContextForProcess()->svc().get())) { |
| // Delete persisted data staged for deletion during the previous run. |
| DeleteStagedForDeletionDirectoryIfExists(); |
| |
| // Specify the services to connect via the Runner process' service directory. |
| for (const char* name : kServices) { |
| main_services_->AddService(name); |
| isolated_services_->AddService(name); |
| } |
| |
| // Add handlers to main context's service directory for redirected services. |
| main_services_->outgoing_directory()->AddPublicService<fuchsia::media::Audio>( |
| fit::bind_member(this, &CastRunner::OnAudioServiceRequest)); |
| main_services_->outgoing_directory() |
| ->AddPublicService<fuchsia::camera3::DeviceWatcher>( |
| fit::bind_member(this, &CastRunner::OnCameraServiceRequest)); |
| main_services_->outgoing_directory() |
| ->AddPublicService<fuchsia::legacymetrics::MetricsRecorder>( |
| fit::bind_member(this, &CastRunner::OnMetricsRecorderServiceRequest)); |
| |
| // Isolated contexts can use the normal Audio service, and don't record |
| // metrics. |
| isolated_services_->AddService(fuchsia::media::Audio::Name_); |
| } |
| |
| CastRunner::~CastRunner() = default; |
| |
| void CastRunner::StartComponent( |
| fuchsia::sys::Package package, |
| fuchsia::sys::StartupInfo startup_info, |
| fidl::InterfaceRequest<fuchsia::sys::ComponentController> |
| controller_request) { |
| // Verify that |package| specifies a Cast URI, and pull the app-Id from it. |
| constexpr char kCastPresentationUrlScheme[] = "cast"; |
| constexpr char kCastSecurePresentationUrlScheme[] = "casts"; |
| |
| GURL cast_url(package.resolved_url); |
| if (!cast_url.is_valid() || |
| (!cast_url.SchemeIs(kCastPresentationUrlScheme) && |
| !cast_url.SchemeIs(kCastSecurePresentationUrlScheme)) || |
| cast_url.GetContent().empty()) { |
| LOG(ERROR) << "Rejected invalid URL: " << cast_url; |
| return; |
| } |
| |
| auto startup_context = |
| std::make_unique<base::StartupContext>(std::move(startup_info)); |
| |
| // If the persistent cache directory was erased then re-create the main Cast |
| // app Context. |
| if (WasPersistedCacheErased()) { |
| LOG(WARNING) << "Cache erased. Restarting web.Context."; |
| // The sentinel file will be re-created the next time CreateContextParams |
| // are request for the main web.Context. |
| was_cache_sentinel_created_ = false; |
| main_context_->DestroyWebContext(); |
| } |
| |
| if (cors_exempt_headers_) { |
| StartComponentInternal(cast_url, std::move(startup_context), |
| std::move(controller_request)); |
| return; |
| } |
| |
| // Start a request for the CORS-exempt headers list via the component's |
| // incoming service-directory, unless a request is already in-progress. |
| // This assumes that the set of CORS-exempt headers is the same for all |
| // components hosted by this Runner. |
| if (!cors_exempt_headers_provider_) { |
| startup_context->svc()->Connect(cors_exempt_headers_provider_.NewRequest()); |
| |
| cors_exempt_headers_provider_.set_error_handler([this](zx_status_t status) { |
| ZX_LOG(ERROR, status) << "CorsExemptHeaderProvider disconnected."; |
| // Clearing queued callbacks closes resources associated with those |
| // component launch requests, effectively causing them to fail. |
| on_have_cors_exempt_headers_.clear(); |
| }); |
| |
| cors_exempt_headers_provider_->GetCorsExemptHeaderNames( |
| [this](std::vector<std::vector<uint8_t>> header_names) { |
| cors_exempt_headers_provider_.Unbind(); |
| cors_exempt_headers_ = std::move(header_names); |
| for (auto& callback : on_have_cors_exempt_headers_) |
| std::move(callback).Run(); |
| on_have_cors_exempt_headers_.clear(); |
| }); |
| } |
| |
| // Queue the component launch to be resumed once the header list is available. |
| on_have_cors_exempt_headers_.push_back(base::BindOnce( |
| &CastRunner::StartComponentInternal, base::Unretained(this), cast_url, |
| std::move(startup_context), std::move(controller_request))); |
| } |
| |
| bool CastRunner::DeletePersistentData() { |
| // Set data reset flag so that new components are not being started. |
| data_reset_in_progress_ = true; |
| |
| // Create the staging directory. |
| base::FilePath staged_for_deletion_directory = |
| GetStagedForDeletionDirectoryPath(); |
| base::File::Error file_error; |
| bool result = base::CreateDirectoryAndGetError(staged_for_deletion_directory, |
| &file_error); |
| if (!result) { |
| LOG(ERROR) << "Failed to create the staging directory, error: " |
| << file_error; |
| return false; |
| } |
| |
| // Stage everything under `/cache` for deletion. |
| const base::FilePath cache_directory(base::kPersistedCacheDirectoryPath); |
| base::FileEnumerator enumerator( |
| cache_directory, /*recursive=*/false, |
| base::FileEnumerator::FileType::FILES | |
| base::FileEnumerator::FileType::DIRECTORIES); |
| for (base::FilePath current = enumerator.Next(); !current.empty(); |
| current = enumerator.Next()) { |
| // Skip the staging directory itself. |
| if (current == staged_for_deletion_directory) { |
| continue; |
| } |
| |
| base::FilePath destination = |
| staged_for_deletion_directory.Append(current.BaseName()); |
| result = base::Move(current, destination); |
| if (!result) { |
| LOG(ERROR) << "Failed to move " << current << " to " << destination; |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| void CastRunner::LaunchPendingComponent(PendingCastComponent* pending_component, |
| CastComponent::Params params) { |
| DCHECK(cors_exempt_headers_); |
| |
| // TODO(crbug.com/1082821): Remove |web_content_url| once the Cast Streaming |
| // Receiver component has been implemented. |
| GURL web_content_url(params.application_config.web_url()); |
| if (IsAppConfigForCastStreaming(params.application_config)) |
| web_content_url = GURL(kCastStreamingWebUrl); |
| |
| base::Optional<fuchsia::web::CreateContextParams> create_context_params = |
| GetContextParamsForAppConfig(¶ms.application_config); |
| |
| WebContentRunner* component_owner = main_context_.get(); |
| if (create_context_params) { |
| component_owner = CreateIsolatedContextForParams( |
| std::move(create_context_params.value())); |
| } |
| |
| auto cast_component = std::make_unique<CastComponent>( |
| base::StrCat({"cast:", pending_component->app_id()}), component_owner, |
| std::move(params), is_headless_); |
| |
| // Start the component, which creates and configures the web.Frame, and load |
| // the specified web content into it. |
| cast_component->SetOnDestroyedCallback( |
| base::BindOnce(&CastRunner::OnComponentDestroyed, base::Unretained(this), |
| base::Unretained(cast_component.get()))); |
| cast_component->StartComponent(); |
| cast_component->LoadUrl(std::move(web_content_url), |
| std::vector<fuchsia::net::http::Header>()); |
| |
| if (component_owner == main_context_.get()) { |
| // For components in the main Context the cache sentinel file should have |
| // been created as a side-effect of |CastComponent::StartComponent()|. |
| DCHECK(was_cache_sentinel_created_); |
| |
| const auto& application_config = cast_component->application_config(); |
| |
| // If this component has the microphone permission then use it to route |
| // Audio service requests through. |
| if (IsPermissionGrantedInAppConfig( |
| application_config, fuchsia::web::PermissionType::MICROPHONE)) { |
| if (first_audio_capturer_agent_url_.empty()) { |
| first_audio_capturer_agent_url_ = application_config.agent_url(); |
| } else { |
| LOG_IF(WARNING, first_audio_capturer_agent_url_ != |
| application_config.agent_url()) |
| << "Audio capturer already in use for different agent. " |
| "Current agent: " |
| << application_config.agent_url(); |
| } |
| audio_capturer_components_.emplace(cast_component.get()); |
| } |
| |
| if (IsPermissionGrantedInAppConfig(application_config, |
| fuchsia::web::PermissionType::CAMERA)) { |
| if (first_video_capturer_agent_url_.empty()) { |
| first_video_capturer_agent_url_ = application_config.agent_url(); |
| } else { |
| LOG_IF(WARNING, first_video_capturer_agent_url_ != |
| application_config.agent_url()) |
| << "Video capturer already in use for different agent. " |
| "Current agent: " |
| << application_config.agent_url(); |
| } |
| video_capturer_components_.emplace(cast_component.get()); |
| } |
| } |
| |
| // Do not launch new main context components while data reset is in progress, |
| // so that they don't create new persisted state. We expect the session |
| // to be restarted shortly after data reset completes. |
| if (data_reset_in_progress_ && component_owner == main_context_.get()) { |
| pending_components_.erase(pending_component); |
| return; |
| } |
| |
| // Register the new component and clean up the |pending_component|. |
| component_owner->RegisterComponent(std::move(cast_component)); |
| pending_components_.erase(pending_component); |
| } |
| |
| void CastRunner::CancelPendingComponent( |
| PendingCastComponent* pending_component) { |
| size_t count = pending_components_.erase(pending_component); |
| DCHECK_EQ(count, 1u); |
| } |
| |
| void CastRunner::OnComponentDestroyed(CastComponent* component) { |
| audio_capturer_components_.erase(component); |
| video_capturer_components_.erase(component); |
| } |
| |
| fuchsia::web::CreateContextParams CastRunner::GetCommonContextParams() { |
| fuchsia::web::CreateContextParams params; |
| params.set_features(fuchsia::web::ContextFeatureFlags::AUDIO); |
| |
| if (is_headless_) { |
| LOG(WARNING) << "Running in headless mode."; |
| *params.mutable_features() |= fuchsia::web::ContextFeatureFlags::HEADLESS; |
| } else { |
| // TODO(crbug.com/1078227): Remove HARDWARE_VIDEO_DECODER_ONLY. |
| *params.mutable_features() |= |
| fuchsia::web::ContextFeatureFlags::HARDWARE_VIDEO_DECODER | |
| fuchsia::web::ContextFeatureFlags::HARDWARE_VIDEO_DECODER_ONLY | |
| fuchsia::web::ContextFeatureFlags::VULKAN; |
| } |
| |
| // TODO(crbug.com/1166790): Fetch UserAgent version strings from Agent. |
| params.set_user_agent_product("CrKey"); |
| params.set_user_agent_version("1.52.999999"); |
| |
| // When tests require that VULKAN be disabled, DRM must also be disabled. |
| if (disable_vulkan_for_test_) { |
| *params.mutable_features() &= |
| ~(fuchsia::web::ContextFeatureFlags::VULKAN | |
| fuchsia::web::ContextFeatureFlags::HARDWARE_VIDEO_DECODER | |
| fuchsia::web::ContextFeatureFlags::HARDWARE_VIDEO_DECODER_ONLY); |
| } |
| |
| // If there is a list of headers to exempt from CORS checks, pass the list |
| // along to the Context. |
| CHECK(cors_exempt_headers_); |
| if (!cors_exempt_headers_->empty()) |
| params.set_cors_exempt_headers(*cors_exempt_headers_); |
| |
| return params; |
| } |
| |
| fuchsia::web::CreateContextParams CastRunner::GetMainContextParams() { |
| fuchsia::web::CreateContextParams params = GetCommonContextParams(); |
| params.set_remote_debugging_port(CastRunner::kRemoteDebuggingPort); |
| *params.mutable_features() |= |
| fuchsia::web::ContextFeatureFlags::NETWORK | |
| fuchsia::web::ContextFeatureFlags::LEGACYMETRICS; |
| main_services_->ConnectClient( |
| params.mutable_service_directory()->NewRequest()); |
| |
| if (!disable_vulkan_for_test_) |
| SetCdmParamsForMainContext(¶ms); |
| |
| SetDataParamsForMainContext(¶ms); |
| |
| // Create a sentinel file to detect if the cache is erased. |
| // TODO(crbug.com/1188780): Remove once an explicit cache flush signal exists. |
| CreatePersistedCacheSentinel(); |
| |
| // TODO(crbug.com/1023514): Remove this switch when it is no longer |
| // necessary. |
| params.set_unsafely_treat_insecure_origins_as_secure( |
| {"allow-running-insecure-content", "disable-mixed-content-autoupgrade"}); |
| |
| return params; |
| } |
| |
| fuchsia::web::CreateContextParams |
| CastRunner::GetIsolatedContextParamsWithFuchsiaDirs( |
| std::vector<fuchsia::web::ContentDirectoryProvider> content_directories) { |
| fuchsia::web::CreateContextParams params = GetCommonContextParams(); |
| params.set_remote_debugging_port(kEphemeralRemoteDebuggingPort); |
| params.set_content_directories(std::move(content_directories)); |
| isolated_services_->ConnectClient( |
| params.mutable_service_directory()->NewRequest()); |
| return params; |
| } |
| |
| fuchsia::web::CreateContextParams |
| CastRunner::GetIsolatedContextParamsForCastStreaming() { |
| fuchsia::web::CreateContextParams params = GetCommonContextParams(); |
| params.set_remote_debugging_port(kEphemeralRemoteDebuggingPort); |
| ApplyCastStreamingContextParams(¶ms); |
| // TODO(crbug.com/1069746): Use a different FilteredServiceDirectory for Cast |
| // Streaming Contexts. |
| main_services_->ConnectClient( |
| params.mutable_service_directory()->NewRequest()); |
| return params; |
| } |
| |
| base::Optional<fuchsia::web::CreateContextParams> |
| CastRunner::GetContextParamsForAppConfig( |
| chromium::cast::ApplicationConfig* app_config) { |
| base::Optional<fuchsia::web::CreateContextParams> params; |
| |
| if (IsAppConfigForCastStreaming(*app_config)) { |
| // TODO(crbug.com/1082821): Remove this once the CastStreamingReceiver |
| // Component has been implemented. |
| return base::make_optional(GetIsolatedContextParamsForCastStreaming()); |
| } |
| |
| const bool is_isolated_app = |
| app_config->has_content_directories_for_isolated_application(); |
| if (is_isolated_app) { |
| return base::make_optional( |
| GetIsolatedContextParamsWithFuchsiaDirs(std::move( |
| *app_config |
| ->mutable_content_directories_for_isolated_application()))); |
| } |
| |
| // No need to create an isolated context in other cases. |
| return base::nullopt; |
| } |
| |
| WebContentRunner* CastRunner::CreateIsolatedContextForParams( |
| fuchsia::web::CreateContextParams create_context_params) { |
| // Create an isolated context which will own the CastComponent. |
| auto context = |
| std::make_unique<WebContentRunner>(std::move(create_context_params)); |
| context->SetOnEmptyCallback( |
| base::BindOnce(&CastRunner::OnIsolatedContextEmpty, |
| base::Unretained(this), base::Unretained(context.get()))); |
| WebContentRunner* raw_context = context.get(); |
| isolated_contexts_.insert(std::move(context)); |
| return raw_context; |
| } |
| |
| void CastRunner::OnIsolatedContextEmpty(WebContentRunner* context) { |
| auto it = isolated_contexts_.find(context); |
| DCHECK(it != isolated_contexts_.end()); |
| isolated_contexts_.erase(it); |
| } |
| |
| void CastRunner::OnAudioServiceRequest( |
| fidl::InterfaceRequest<fuchsia::media::Audio> request) { |
| // If we have a component that allows AudioCapturer access then redirect the |
| // fuchsia.media.Audio requests to the corresponding agent. |
| if (!audio_capturer_components_.empty()) { |
| CastComponent* capturer_component = *audio_capturer_components_.begin(); |
| capturer_component->agent_manager()->ConnectToAgentService( |
| capturer_component->application_config().agent_url(), |
| std::move(request)); |
| return; |
| } |
| |
| // Otherwise use the Runner's fuchsia.media.Audio service. fuchsia.media.Audio |
| // may be used by frames without MICROPHONE permission to create AudioRenderer |
| // instance. |
| base::ComponentContextForProcess()->svc()->Connect(std::move(request)); |
| } |
| |
| void CastRunner::OnCameraServiceRequest( |
| fidl::InterfaceRequest<fuchsia::camera3::DeviceWatcher> request) { |
| // If we have a component that allows camera access then redirect the |
| // fuchsia.camera3.DeviceWatcher requests to the corresponding agent. |
| if (!video_capturer_components_.empty()) { |
| CastComponent* capturer_component = *video_capturer_components_.begin(); |
| capturer_component->agent_manager()->ConnectToAgentService( |
| capturer_component->application_config().agent_url(), |
| std::move(request)); |
| return; |
| } |
| |
| // fuchsia.camera3.DeviceWatcher may be requested while none of the running |
| // apps have the CAMERA permission. Return ZX_ERR_UNAVAILABLE, which implies |
| // that the client should try connecting again later, since the service may |
| // become available after a web.Frame with camera access is created. |
| request.Close(ZX_ERR_UNAVAILABLE); |
| } |
| |
| void CastRunner::OnMetricsRecorderServiceRequest( |
| fidl::InterfaceRequest<fuchsia::legacymetrics::MetricsRecorder> request) { |
| // TODO(crbug.com/1065707): Remove this hack once the service can be routed |
| // through the Runner's incoming service directory, in Component Framework v2. |
| |
| // Attempt to connect via any CastComponent's incoming services. |
| WebComponent* any_component = main_context_->GetAnyComponent(); |
| if (any_component) { |
| VLOG(1) << "Connecting MetricsRecorder via CastComponent."; |
| CastComponent* component = reinterpret_cast<CastComponent*>(any_component); |
| component->startup_context()->svc()->Connect(std::move(request)); |
| return; |
| } |
| |
| // Attempt to connect via a FrameHostComponent's services, if available. |
| if (frame_host_component_incoming_services_) { |
| VLOG(1) << "Connecting MetricsRecorder via FrameHostComponent."; |
| frame_host_component_incoming_services_->Connect(std::move(request)); |
| return; |
| } |
| |
| LOG(WARNING) << "Ignoring MetricsRecorder request."; |
| } |
| |
| void CastRunner::StartComponentInternal( |
| const GURL& url, |
| std::unique_ptr<base::StartupContext> startup_context, |
| fidl::InterfaceRequest<fuchsia::sys::ComponentController> |
| controller_request) { |
| // TODO(crbug.com/1120914): Remove this once Component Framework v2 can be |
| // used to route fuchsia.web.FrameHost capabilities cleanly. |
| if (enable_frame_host_component_ && (url.spec() == kFrameHostComponentName)) { |
| frame_host_component_incoming_services_ = |
| FrameHostComponent::StartAndReturnIncomingServiceDirectory( |
| std::move(startup_context), std::move(controller_request), |
| main_context_.get()); |
| return; |
| } |
| |
| // TODO(crbug.com/1120914): Remove this once Component Framework v2 can be |
| // used to route chromium.cast.DataReset capabilities cleanly. |
| if (url.spec() == kDataResetComponentName) { |
| DataResetComponent::Start(base::BindOnce(&CastRunner::DeletePersistentData, |
| base::Unretained(this)), |
| std::move(startup_context), |
| std::move(controller_request)); |
| return; |
| } |
| |
| pending_components_.emplace(std::make_unique<PendingCastComponent>( |
| this, std::move(startup_context), std::move(controller_request), |
| url.GetContent())); |
| } |
| |
| static base::FilePath SentinelFilePath() { |
| return base::FilePath(base::kPersistedCacheDirectoryPath) |
| .Append(kSentinelFileName); |
| } |
| |
| void CastRunner::CreatePersistedCacheSentinel() { |
| base::WriteFile(SentinelFilePath(), ""); |
| was_cache_sentinel_created_ = true; |
| } |
| |
| bool CastRunner::WasPersistedCacheErased() { |
| if (!was_cache_sentinel_created_) |
| return false; |
| return !base::PathExists(SentinelFilePath()); |
| } |