cast_runner.cc 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830
  1. // Copyright 2018 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "fuchsia_web/runners/cast/cast_runner.h"
  5. #include <fuchsia/legacymetrics/cpp/fidl.h>
  6. #include <fuchsia/sys/cpp/fidl.h>
  7. #include <fuchsia/web/cpp/fidl.h>
  8. #include <lib/fit/function.h>
  9. #include <memory>
  10. #include <string>
  11. #include <utility>
  12. #include "base/bind.h"
  13. #include "base/command_line.h"
  14. #include "base/files/file_enumerator.h"
  15. #include "base/files/file_path.h"
  16. #include "base/files/file_util.h"
  17. #include "base/fuchsia/file_utils.h"
  18. #include "base/fuchsia/filtered_service_directory.h"
  19. #include "base/fuchsia/fuchsia_logging.h"
  20. #include "base/fuchsia/process_context.h"
  21. #include "base/logging.h"
  22. #include "base/process/process.h"
  23. #include "base/strings/strcat.h"
  24. #include "base/time/time.h"
  25. #include "base/values.h"
  26. #include "components/cast/common/constants.h"
  27. #include "components/fuchsia_component_support/config_reader.h"
  28. #include "fuchsia_web/cast_streaming/cast_streaming.h"
  29. #include "fuchsia_web/runners/cast/cast_streaming.h"
  30. #include "fuchsia_web/runners/cast/pending_cast_component.h"
  31. #include "fuchsia_web/runners/common/web_content_runner.h"
  32. #include "url/gurl.h"
  33. namespace {
  34. // Use a constexpr instead of the existing switch, because of the additional
  35. // dependencies required.
  36. constexpr char kAudioCapturerWithEchoCancellationSwitch[] =
  37. "audio-capturer-with-echo-cancellation";
  38. // List of services in CastRunner's Service Directory that will be passed
  39. // through to each WebEngine instance it creates. Each service in
  40. // web_instance.cmx/.cml must appear on a line below.
  41. // Although the array must not include any services handled dynamically by the
  42. // CastRunner logic (e.g., Agent redirections), they are listed here as comments
  43. // to make the list easier to validate at-a-glance.
  44. // cast_runner.cmx/.cml must include all services in the array as well as
  45. // "fuchsia.media.Audio" since OnAudioServiceRequest() may fall back to it.
  46. static constexpr const char* kServices[] = {
  47. "fuchsia.accessibility.semantics.SemanticsManager",
  48. "fuchsia.buildinfo.Provider",
  49. // "fuchsia.camera3.DeviceWatcher" is redirected to the agent.
  50. "fuchsia.device.NameProvider",
  51. "fuchsia.fonts.Provider",
  52. "fuchsia.hwinfo.Product",
  53. "fuchsia.input.virtualkeyboard.ControllerCreator",
  54. "fuchsia.intl.PropertyProvider",
  55. // "fuchsia.legacymetrics.MetricsRecorder" is redirected to the agent.
  56. "fuchsia.logger.LogSink",
  57. // "fuchsia.media.Audio" may be redirected to the agent.
  58. "fuchsia.media.AudioDeviceEnumerator",
  59. "fuchsia.media.ProfileProvider",
  60. "fuchsia.media.SessionAudioConsumerFactory",
  61. "fuchsia.media.drm.PlayReady",
  62. "fuchsia.media.drm.Widevine",
  63. "fuchsia.mediacodec.CodecFactory",
  64. "fuchsia.memorypressure.Provider",
  65. "fuchsia.net.interfaces.State",
  66. "fuchsia.net.name.Lookup",
  67. "fuchsia.posix.socket.Provider",
  68. "fuchsia.process.Launcher",
  69. "fuchsia.settings.Display",
  70. "fuchsia.sysmem.Allocator",
  71. "fuchsia.ui.composition.Allocator",
  72. "fuchsia.ui.composition.Flatland",
  73. "fuchsia.ui.input3.Keyboard",
  74. "fuchsia.ui.scenic.Scenic",
  75. "fuchsia.vulkan.loader.Loader",
  76. };
  77. // Names used to partition the Runner's persistent storage for different uses.
  78. constexpr char kCdmDataSubdirectoryName[] = "cdm_data";
  79. constexpr char kProfileSubdirectoryName[] = "web_profile";
  80. // Name of the file used to detect cache erasure.
  81. // TODO(crbug.com/1188780): Remove once an explicit cache flush signal exists.
  82. constexpr char kSentinelFileName[] = ".sentinel";
  83. // Ephemeral remote debugging port used by child contexts.
  84. const uint16_t kEphemeralRemoteDebuggingPort = 0;
  85. // Application URL for the pseudo-component providing fuchsia.web.FrameHost.
  86. constexpr char kFrameHostComponentName[] = "cast:fuchsia.web.FrameHost";
  87. // Application URL for the pseudo-component providing chromium.cast.DataReset.
  88. constexpr char kDataResetComponentName[] = "cast:chromium.cast.DataReset";
  89. // Subdirectory used to stage persistent directories to be deleted upon next
  90. // startup.
  91. const char kStagedForDeletionSubdirectory[] = "staged_for_deletion";
  92. base::FilePath GetStagedForDeletionDirectoryPath() {
  93. base::FilePath cache_directory(base::kPersistedCacheDirectoryPath);
  94. return cache_directory.Append(kStagedForDeletionSubdirectory);
  95. }
  96. // Deletes files/directories staged for deletion during the previous run.
  97. // We delete synchronously on main thread for simplicity. Note that this
  98. // overall mechanism is a temporary solution. TODO(crbug.com/1146480): migrate
  99. // to the framework mechanism of clearing session data when available.
  100. void DeleteStagedForDeletionDirectoryIfExists() {
  101. const base::FilePath staged_for_deletion_directory =
  102. GetStagedForDeletionDirectoryPath();
  103. if (!PathExists(staged_for_deletion_directory))
  104. return;
  105. const base::TimeTicks started_at = base::TimeTicks::Now();
  106. bool result = base::DeletePathRecursively(staged_for_deletion_directory);
  107. if (!result) {
  108. LOG(ERROR) << "Failed to delete the staging directory";
  109. return;
  110. }
  111. LOG(WARNING) << "Deleting old persistent data took "
  112. << (base::TimeTicks::Now() - started_at).InMillisecondsF()
  113. << " ms";
  114. }
  115. // TODO(crbug.com/1134719): Consider removing this flag once Media Capabilities
  116. // is supported.
  117. void EnsureSoftwareVideoDecodersAreDisabled(
  118. ::fuchsia::web::ContextFeatureFlags* features) {
  119. if ((*features & fuchsia::web::ContextFeatureFlags::HARDWARE_VIDEO_DECODER) ==
  120. fuchsia::web::ContextFeatureFlags::HARDWARE_VIDEO_DECODER) {
  121. *features |= fuchsia::web::ContextFeatureFlags::HARDWARE_VIDEO_DECODER_ONLY;
  122. }
  123. }
  124. // Populates |params| with web data settings. Web data persistence is only
  125. // enabled if a soft quota is explicitly specified via config-data.
  126. // Exits the Runner process if creation of data storage fails for any reason.
  127. void SetDataParamsForMainContext(fuchsia::web::CreateContextParams* params) {
  128. // Set the web data quota based on the CastRunner configuration.
  129. const absl::optional<base::Value>& config =
  130. fuchsia_component_support::LoadPackageConfig();
  131. if (!config)
  132. return;
  133. constexpr char kDataQuotaBytesSwitch[] = "data-quota-bytes";
  134. const absl::optional<int> data_quota_bytes =
  135. config->FindIntPath(kDataQuotaBytesSwitch);
  136. if (!data_quota_bytes)
  137. return;
  138. // Allow best-effort persistent of Cast application data.
  139. // TODO(crbug.com/1148334): Remove the need for an explicit quota to be
  140. // configured, once the platform provides storage quotas.
  141. const auto profile_path = base::FilePath(base::kPersistedCacheDirectoryPath)
  142. .Append(kProfileSubdirectoryName);
  143. auto error_code = base::File::Error::FILE_OK;
  144. if (!base::CreateDirectoryAndGetError(profile_path, &error_code)) {
  145. LOG(ERROR) << "Failed to create profile directory: " << error_code;
  146. base::Process::TerminateCurrentProcessImmediately(1);
  147. }
  148. params->set_data_directory(base::OpenDirectoryHandle(profile_path));
  149. if (!params->data_directory()) {
  150. LOG(ERROR) << "Unable to open data to transfer";
  151. base::Process::TerminateCurrentProcessImmediately(1);
  152. }
  153. params->set_data_quota_bytes(*data_quota_bytes);
  154. }
  155. // Populates |params| with settings to enable Widevine & PlayReady CDMs.
  156. // CDM data persistence is always enabled, with an optional soft quota.
  157. // Exits the Runner if creation of CDM storage fails for any reason.
  158. void SetCdmParamsForMainContext(fuchsia::web::CreateContextParams* params) {
  159. const absl::optional<base::Value>& config =
  160. fuchsia_component_support::LoadPackageConfig();
  161. if (config) {
  162. constexpr char kCdmDataQuotaBytesSwitch[] = "cdm-data-quota-bytes";
  163. const absl::optional<int> cdm_data_quota_bytes =
  164. config->FindIntPath(kCdmDataQuotaBytesSwitch);
  165. if (cdm_data_quota_bytes)
  166. params->set_cdm_data_quota_bytes(*cdm_data_quota_bytes);
  167. }
  168. // TODO(b/154204041): Consider using isolated-persistent-storage for CDM data.
  169. // Create an isolated-cache-storage sub-directory for CDM data.
  170. const auto cdm_data_path = base::FilePath(base::kPersistedCacheDirectoryPath)
  171. .Append(kCdmDataSubdirectoryName);
  172. auto error_code = base::File::Error::FILE_OK;
  173. if (!base::CreateDirectoryAndGetError(cdm_data_path, &error_code)) {
  174. LOG(ERROR) << "Failed to create cache directory: " << error_code;
  175. base::Process::TerminateCurrentProcessImmediately(1);
  176. }
  177. params->set_cdm_data_directory(base::OpenDirectoryHandle(cdm_data_path));
  178. if (!params->cdm_data_directory()) {
  179. LOG(ERROR) << "Unable to open cdm_data to transfer";
  180. base::Process::TerminateCurrentProcessImmediately(1);
  181. }
  182. // Enable the Widevine and Playready CDMs.
  183. *params->mutable_features() |=
  184. fuchsia::web::ContextFeatureFlags::WIDEVINE_CDM;
  185. const char kCastPlayreadyKeySystem[] = "com.chromecast.playready";
  186. params->set_playready_key_system(kCastPlayreadyKeySystem);
  187. }
  188. // TODO(crbug.com/1120914): Remove this once Component Framework v2 can be
  189. // used to route fuchsia.web.FrameHost capabilities cleanly.
  190. class FrameHostComponent final : public fuchsia::sys::ComponentController {
  191. public:
  192. // Creates a FrameHostComponent with lifetime managed by |controller_request|.
  193. // Returns the incoming service directory, in case the CastRunner needs to use
  194. // it to connect to the MetricsRecorder.
  195. static base::WeakPtr<const sys::ServiceDirectory>
  196. StartAndReturnIncomingServiceDirectory(
  197. std::unique_ptr<base::StartupContext> startup_context,
  198. fidl::InterfaceRequest<fuchsia::sys::ComponentController>
  199. controller_request,
  200. fidl::InterfaceRequestHandler<fuchsia::web::FrameHost>
  201. frame_host_request_handler) {
  202. // |frame_host_component| deletes itself when the client disconnects.
  203. auto* frame_host_component = new FrameHostComponent(
  204. std::move(startup_context), std::move(controller_request),
  205. std::move(frame_host_request_handler));
  206. return frame_host_component->weak_incoming_services_.GetWeakPtr();
  207. }
  208. private:
  209. FrameHostComponent(std::unique_ptr<base::StartupContext> startup_context,
  210. fidl::InterfaceRequest<fuchsia::sys::ComponentController>
  211. controller_request,
  212. fidl::InterfaceRequestHandler<fuchsia::web::FrameHost>
  213. frame_host_request_handler)
  214. : startup_context_(std::move(startup_context)),
  215. frame_host_binding_(startup_context_->outgoing(),
  216. std::move(frame_host_request_handler)),
  217. weak_incoming_services_(startup_context_->svc()) {
  218. startup_context_->ServeOutgoingDirectory();
  219. binding_.Bind(std::move(controller_request));
  220. binding_.set_error_handler([this](zx_status_t) { Kill(); });
  221. }
  222. ~FrameHostComponent() override = default;
  223. // fuchsia::sys::ComponentController interface.
  224. void Kill() override { delete this; }
  225. void Detach() override {
  226. binding_.Close(ZX_ERR_NOT_SUPPORTED);
  227. delete this;
  228. }
  229. const std::unique_ptr<base::StartupContext> startup_context_;
  230. const base::ScopedServicePublisher<fuchsia::web::FrameHost>
  231. frame_host_binding_;
  232. fidl::Binding<fuchsia::sys::ComponentController> binding_{this};
  233. base::WeakPtrFactory<const sys::ServiceDirectory> weak_incoming_services_;
  234. };
  235. // TODO(crbug.com/1120914): Remove this once Component Framework v2 can be
  236. // used to route chromium.cast.DataReset capabilities cleanly.
  237. class DataResetComponent final : public fuchsia::sys::ComponentController {
  238. public:
  239. // Creates a DataResetComponent with lifetime managed by |controller_request|.
  240. static void Start(chromium::cast::DataReset* data_reset_impl,
  241. std::unique_ptr<base::StartupContext> startup_context,
  242. fidl::InterfaceRequest<fuchsia::sys::ComponentController>
  243. controller_request) {
  244. new DataResetComponent(data_reset_impl, std::move(startup_context),
  245. std::move(controller_request));
  246. }
  247. private:
  248. DataResetComponent(chromium::cast::DataReset* data_reset_impl,
  249. std::unique_ptr<base::StartupContext> startup_context,
  250. fidl::InterfaceRequest<fuchsia::sys::ComponentController>
  251. controller_request)
  252. : startup_context_(std::move(startup_context)),
  253. data_reset_handler_binding_(startup_context_->outgoing(),
  254. data_reset_impl) {
  255. startup_context_->ServeOutgoingDirectory();
  256. controller_binding_.Bind(std::move(controller_request));
  257. controller_binding_.set_error_handler([this](zx_status_t) { Kill(); });
  258. }
  259. ~DataResetComponent() override = default;
  260. // fuchsia::sys::ComponentController interface.
  261. void Kill() override { delete this; }
  262. void Detach() override {
  263. controller_binding_.Close(ZX_ERR_NOT_SUPPORTED);
  264. delete this;
  265. }
  266. std::unique_ptr<base::StartupContext> startup_context_;
  267. const base::ScopedServiceBinding<chromium::cast::DataReset>
  268. data_reset_handler_binding_;
  269. fidl::Binding<fuchsia::sys::ComponentController> controller_binding_{this};
  270. };
  271. } // namespace
  272. CastRunner::CastRunner(WebInstanceHost* web_instance_host, bool is_headless)
  273. : web_instance_host_(web_instance_host),
  274. is_headless_(is_headless),
  275. main_services_(std::make_unique<base::FilteredServiceDirectory>(
  276. base::ComponentContextForProcess()->svc())),
  277. main_context_(std::make_unique<WebContentRunner>(
  278. web_instance_host_,
  279. base::BindRepeating(&CastRunner::GetMainWebInstanceConfig,
  280. base::Unretained(this)))),
  281. isolated_services_(std::make_unique<base::FilteredServiceDirectory>(
  282. base::ComponentContextForProcess()->svc())) {
  283. // Delete persisted data staged for deletion during the previous run.
  284. DeleteStagedForDeletionDirectoryIfExists();
  285. // Specify the services to connect via the Runner process' service directory.
  286. for (const char* name : kServices) {
  287. zx_status_t status = main_services_->AddService(name);
  288. ZX_CHECK(status == ZX_OK, status)
  289. << "AddService(" << name << ") to main failed";
  290. status = isolated_services_->AddService(name);
  291. ZX_CHECK(status == ZX_OK, status)
  292. << "AddService(" << name << ") to isolated failed";
  293. }
  294. // Add handlers to main context's service directory for redirected services.
  295. zx_status_t status =
  296. main_services_->outgoing_directory()
  297. ->AddPublicService<fuchsia::media::Audio>(
  298. fit::bind_member(this, &CastRunner::OnAudioServiceRequest));
  299. ZX_CHECK(status == ZX_OK, status) << "AddPublicService(Audio) to main failed";
  300. status = main_services_->outgoing_directory()
  301. ->AddPublicService<fuchsia::camera3::DeviceWatcher>(
  302. fit::bind_member(this, &CastRunner::OnCameraServiceRequest));
  303. ZX_CHECK(status == ZX_OK, status)
  304. << "AddPublicService(DeviceWatcher) to main failed";
  305. status = main_services_->outgoing_directory()
  306. ->AddPublicService<fuchsia::legacymetrics::MetricsRecorder>(
  307. fit::bind_member(
  308. this, &CastRunner::OnMetricsRecorderServiceRequest));
  309. ZX_CHECK(status == ZX_OK, status)
  310. << "AddPublicService(MetricsRecorder) to main failed";
  311. // Isolated contexts can use the normal Audio service, and don't record
  312. // metrics.
  313. status = isolated_services_->AddService(fuchsia::media::Audio::Name_);
  314. ZX_CHECK(status == ZX_OK, status) << "AddService(Audio) to isolated failed";
  315. }
  316. CastRunner::~CastRunner() = default;
  317. void CastRunner::StartComponent(
  318. fuchsia::sys::Package package,
  319. fuchsia::sys::StartupInfo startup_info,
  320. fidl::InterfaceRequest<fuchsia::sys::ComponentController>
  321. controller_request) {
  322. // Verify that |package| specifies a Cast URI, and pull the app-Id from it.
  323. constexpr char kCastPresentationUrlScheme[] = "cast";
  324. constexpr char kCastSecurePresentationUrlScheme[] = "casts";
  325. GURL cast_url(package.resolved_url);
  326. if (!cast_url.is_valid() ||
  327. (!cast_url.SchemeIs(kCastPresentationUrlScheme) &&
  328. !cast_url.SchemeIs(kCastSecurePresentationUrlScheme)) ||
  329. cast_url.GetContent().empty()) {
  330. LOG(ERROR) << "Rejected invalid URL: " << cast_url;
  331. return;
  332. }
  333. auto startup_context =
  334. std::make_unique<base::StartupContext>(std::move(startup_info));
  335. // If the persistent cache directory was erased then re-create the main Cast
  336. // app Context.
  337. if (WasPersistedCacheErased()) {
  338. LOG(WARNING) << "Cache erased. Restarting web.Context.";
  339. // The sentinel file will be re-created the next time CreateContextParams
  340. // are request for the main web.Context.
  341. was_cache_sentinel_created_ = false;
  342. main_context_->DestroyWebContext();
  343. }
  344. if (cors_exempt_headers_) {
  345. StartComponentInternal(cast_url, std::move(startup_context),
  346. std::move(controller_request));
  347. return;
  348. }
  349. // Start a request for the CORS-exempt headers list via the component's
  350. // incoming service-directory, unless a request is already in-progress.
  351. // This assumes that the set of CORS-exempt headers is the same for all
  352. // components hosted by this Runner.
  353. if (!cors_exempt_headers_provider_) {
  354. startup_context->svc()->Connect(cors_exempt_headers_provider_.NewRequest());
  355. cors_exempt_headers_provider_.set_error_handler([this](zx_status_t status) {
  356. ZX_LOG(ERROR, status) << "CorsExemptHeaderProvider disconnected.";
  357. // Clearing queued callbacks closes resources associated with those
  358. // component launch requests, effectively causing them to fail.
  359. on_have_cors_exempt_headers_.clear();
  360. });
  361. cors_exempt_headers_provider_->GetCorsExemptHeaderNames(
  362. [this](std::vector<std::vector<uint8_t>> header_names) {
  363. cors_exempt_headers_provider_.Unbind();
  364. cors_exempt_headers_ = std::move(header_names);
  365. for (auto& callback : on_have_cors_exempt_headers_)
  366. std::move(callback).Run();
  367. on_have_cors_exempt_headers_.clear();
  368. });
  369. }
  370. // Queue the component launch to be resumed once the header list is available.
  371. on_have_cors_exempt_headers_.push_back(base::BindOnce(
  372. &CastRunner::StartComponentInternal, base::Unretained(this), cast_url,
  373. std::move(startup_context), std::move(controller_request)));
  374. }
  375. void CastRunner::DeletePersistentData(DeletePersistentDataCallback callback) {
  376. if (data_reset_in_progress_) {
  377. // Repeated requests to DeletePersistentData are not supported.
  378. // It is expected that the Runner be requested to terminate shortly after
  379. // data-reset is received, in any case.
  380. callback(false);
  381. return;
  382. }
  383. callback(DeletePersistentDataInternal());
  384. }
  385. void CastRunner::LaunchPendingComponent(PendingCastComponent* pending_component,
  386. CastComponent::Params params) {
  387. DCHECK(cors_exempt_headers_);
  388. // TODO(crbug.com/1082821): Remove |web_content_url| once the Cast Streaming
  389. // Receiver component has been implemented.
  390. GURL web_content_url(params.application_config.web_url());
  391. if (IsAppConfigForCastStreaming(params.application_config))
  392. web_content_url = GURL(kCastStreamingWebUrl);
  393. auto web_instance_config =
  394. GetWebInstanceConfigForAppConfig(&params.application_config);
  395. WebContentRunner* component_owner = main_context_.get();
  396. if (web_instance_config) {
  397. component_owner =
  398. CreateIsolatedRunner(std::move(web_instance_config.value()));
  399. }
  400. auto cast_component = std::make_unique<CastComponent>(
  401. base::StrCat({"cast:", pending_component->app_id()}), component_owner,
  402. std::move(params), is_headless_);
  403. // Start the component, which creates and configures the web.Frame, and load
  404. // the specified web content into it.
  405. cast_component->SetOnDestroyedCallback(
  406. base::BindOnce(&CastRunner::OnComponentDestroyed, base::Unretained(this),
  407. base::Unretained(cast_component.get())));
  408. cast_component->StartComponent();
  409. cast_component->LoadUrl(std::move(web_content_url),
  410. std::vector<fuchsia::net::http::Header>());
  411. if (component_owner == main_context_.get()) {
  412. // For components in the main Context the cache sentinel file should have
  413. // been created as a side-effect of |CastComponent::StartComponent()|.
  414. DCHECK(was_cache_sentinel_created_);
  415. // If this component has the microphone permission then use it to route
  416. // Audio service requests through.
  417. if (cast_component->HasWebPermission(
  418. fuchsia::web::PermissionType::MICROPHONE)) {
  419. if (first_audio_capturer_agent_url_.empty()) {
  420. first_audio_capturer_agent_url_ = cast_component->agent_url();
  421. } else {
  422. LOG_IF(WARNING,
  423. first_audio_capturer_agent_url_ != cast_component->agent_url())
  424. << "Audio capturer already in use for different agent. "
  425. "Current agent: "
  426. << cast_component->agent_url();
  427. }
  428. audio_capturer_components_.emplace(cast_component.get());
  429. }
  430. if (cast_component->HasWebPermission(
  431. fuchsia::web::PermissionType::CAMERA)) {
  432. if (first_video_capturer_agent_url_.empty()) {
  433. first_video_capturer_agent_url_ = cast_component->agent_url();
  434. } else {
  435. LOG_IF(WARNING,
  436. first_video_capturer_agent_url_ != cast_component->agent_url())
  437. << "Video capturer already in use for different agent. "
  438. "Current agent: "
  439. << cast_component->agent_url();
  440. }
  441. video_capturer_components_.emplace(cast_component.get());
  442. }
  443. }
  444. // Do not launch new main context components while data reset is in progress,
  445. // so that they don't create new persisted state. We expect the session
  446. // to be restarted shortly after data reset completes.
  447. if (data_reset_in_progress_ && component_owner == main_context_.get()) {
  448. pending_components_.erase(pending_component);
  449. return;
  450. }
  451. // Register the new component and clean up the |pending_component|.
  452. component_owner->RegisterComponent(std::move(cast_component));
  453. pending_components_.erase(pending_component);
  454. }
  455. void CastRunner::CancelPendingComponent(
  456. PendingCastComponent* pending_component) {
  457. size_t count = pending_components_.erase(pending_component);
  458. DCHECK_EQ(count, 1u);
  459. }
  460. void CastRunner::OnComponentDestroyed(CastComponent* component) {
  461. audio_capturer_components_.erase(component);
  462. video_capturer_components_.erase(component);
  463. }
  464. WebContentRunner::WebInstanceConfig CastRunner::GetCommonWebInstanceConfig() {
  465. DCHECK(cors_exempt_headers_);
  466. WebContentRunner::WebInstanceConfig config;
  467. constexpr char const* kSwitchesToCopy[] = {
  468. // Must match the value in `content/public/common/content_switches.cc`.
  469. "enable-logging",
  470. };
  471. config.extra_args.CopySwitchesFrom(*base::CommandLine::ForCurrentProcess(),
  472. kSwitchesToCopy,
  473. std::size(kSwitchesToCopy));
  474. config.params.set_features(fuchsia::web::ContextFeatureFlags::AUDIO);
  475. if (is_headless_) {
  476. LOG(WARNING) << "Running in headless mode.";
  477. *config.params.mutable_features() |=
  478. fuchsia::web::ContextFeatureFlags::HEADLESS;
  479. } else {
  480. *config.params.mutable_features() |=
  481. fuchsia::web::ContextFeatureFlags::HARDWARE_VIDEO_DECODER |
  482. fuchsia::web::ContextFeatureFlags::VULKAN;
  483. }
  484. // When tests require that VULKAN be disabled, DRM must also be disabled.
  485. if (disable_vulkan_for_test_) {
  486. *config.params.mutable_features() &=
  487. ~(fuchsia::web::ContextFeatureFlags::VULKAN |
  488. fuchsia::web::ContextFeatureFlags::HARDWARE_VIDEO_DECODER);
  489. }
  490. // If there is a list of headers to exempt from CORS checks, pass the list
  491. // along to the Context.
  492. if (!cors_exempt_headers_->empty())
  493. config.params.set_cors_exempt_headers(*cors_exempt_headers_);
  494. return config;
  495. }
  496. WebContentRunner::WebInstanceConfig CastRunner::GetMainWebInstanceConfig() {
  497. auto config = GetCommonWebInstanceConfig();
  498. // AudioCapturer is redirected to the agent (see `OnAudioServiceRequest()`).
  499. // The implementation provided by the agent supports echo cancellation.
  500. //
  501. // TODO(crbug.com/852834): Remove once AudioManagerFuchsia is updated to
  502. // get this information from AudioCapturerFactory.
  503. config.extra_args.AppendSwitch(kAudioCapturerWithEchoCancellationSwitch);
  504. *config.params.mutable_features() |=
  505. fuchsia::web::ContextFeatureFlags::NETWORK |
  506. fuchsia::web::ContextFeatureFlags::LEGACYMETRICS |
  507. fuchsia::web::ContextFeatureFlags::KEYBOARD |
  508. fuchsia::web::ContextFeatureFlags::VIRTUAL_KEYBOARD;
  509. EnsureSoftwareVideoDecodersAreDisabled(config.params.mutable_features());
  510. config.params.set_remote_debugging_port(CastRunner::kRemoteDebuggingPort);
  511. config.params.set_user_agent_product("CrKey");
  512. config.params.set_user_agent_version(chromecast::kFrozenCrKeyValue);
  513. zx_status_t status = main_services_->ConnectClient(
  514. config.params.mutable_service_directory()->NewRequest());
  515. ZX_CHECK(status == ZX_OK, status) << "ConnectClient failed";
  516. if (!disable_vulkan_for_test_) {
  517. SetCdmParamsForMainContext(&config.params);
  518. }
  519. SetDataParamsForMainContext(&config.params);
  520. // Create a sentinel file to detect if the cache is erased.
  521. // TODO(crbug.com/1188780): Remove once an explicit cache flush signal exists.
  522. CreatePersistedCacheSentinel();
  523. // TODO(crbug.com/1023514): Remove this switch when it is no longer
  524. // necessary.
  525. config.params.set_unsafely_treat_insecure_origins_as_secure(
  526. {"allow-running-insecure-content", "disable-mixed-content-autoupgrade"});
  527. return config;
  528. }
  529. WebContentRunner::WebInstanceConfig
  530. CastRunner::GetIsolatedWebInstanceConfigWithFuchsiaDirs(
  531. std::vector<fuchsia::web::ContentDirectoryProvider> content_directories) {
  532. auto config = GetCommonWebInstanceConfig();
  533. EnsureSoftwareVideoDecodersAreDisabled(config.params.mutable_features());
  534. *config.params.mutable_features() |=
  535. fuchsia::web::ContextFeatureFlags::NETWORK;
  536. config.params.set_remote_debugging_port(kEphemeralRemoteDebuggingPort);
  537. config.params.set_content_directories(std::move(content_directories));
  538. zx_status_t status = isolated_services_->ConnectClient(
  539. config.params.mutable_service_directory()->NewRequest());
  540. ZX_CHECK(status == ZX_OK, status) << "ConnectClient failed";
  541. return config;
  542. }
  543. WebContentRunner::WebInstanceConfig
  544. CastRunner::GetIsolatedWebInstanceConfigForCastStreaming() {
  545. auto config = GetCommonWebInstanceConfig();
  546. ApplyCastStreamingContextParams(&config.params);
  547. config.params.set_remote_debugging_port(kEphemeralRemoteDebuggingPort);
  548. // TODO(crbug.com/1069746): Use a different FilteredServiceDirectory for Cast
  549. // Streaming Contexts.
  550. zx_status_t status = main_services_->ConnectClient(
  551. config.params.mutable_service_directory()->NewRequest());
  552. ZX_CHECK(status == ZX_OK, status) << "ConnectClient failed";
  553. return config;
  554. }
  555. absl::optional<WebContentRunner::WebInstanceConfig>
  556. CastRunner::GetWebInstanceConfigForAppConfig(
  557. chromium::cast::ApplicationConfig* app_config) {
  558. if (IsAppConfigForCastStreaming(*app_config)) {
  559. // TODO(crbug.com/1082821): Remove this once the CastStreamingReceiver
  560. // Component has been implemented.
  561. return absl::make_optional(GetIsolatedWebInstanceConfigForCastStreaming());
  562. }
  563. const bool is_isolated_app =
  564. app_config->has_content_directories_for_isolated_application();
  565. if (is_isolated_app) {
  566. return absl::make_optional(
  567. GetIsolatedWebInstanceConfigWithFuchsiaDirs(std::move(
  568. *app_config
  569. ->mutable_content_directories_for_isolated_application())));
  570. }
  571. // No need to create an isolated context in other cases.
  572. return absl::nullopt;
  573. }
  574. WebContentRunner* CastRunner::CreateIsolatedRunner(
  575. WebContentRunner::WebInstanceConfig config) {
  576. // Create an isolated context which will own the CastComponent.
  577. auto context =
  578. std::make_unique<WebContentRunner>(web_instance_host_, std::move(config));
  579. context->SetOnEmptyCallback(
  580. base::BindOnce(&CastRunner::OnIsolatedContextEmpty,
  581. base::Unretained(this), base::Unretained(context.get())));
  582. WebContentRunner* raw_context = context.get();
  583. isolated_contexts_.insert(std::move(context));
  584. return raw_context;
  585. }
  586. void CastRunner::OnIsolatedContextEmpty(WebContentRunner* context) {
  587. auto it = isolated_contexts_.find(context);
  588. DCHECK(it != isolated_contexts_.end());
  589. isolated_contexts_.erase(it);
  590. }
  591. void CastRunner::OnAudioServiceRequest(
  592. fidl::InterfaceRequest<fuchsia::media::Audio> request) {
  593. // If we have a component that allows AudioCapturer access then redirect the
  594. // fuchsia.media.Audio requests to the corresponding agent.
  595. if (!audio_capturer_components_.empty()) {
  596. CastComponent* capturer_component = *audio_capturer_components_.begin();
  597. capturer_component->ConnectAudio(std::move(request));
  598. return;
  599. }
  600. // Otherwise use the Runner's fuchsia.media.Audio service. fuchsia.media.Audio
  601. // may be used by frames without MICROPHONE permission to create AudioRenderer
  602. // instance.
  603. base::ComponentContextForProcess()->svc()->Connect(std::move(request));
  604. }
  605. void CastRunner::OnCameraServiceRequest(
  606. fidl::InterfaceRequest<fuchsia::camera3::DeviceWatcher> request) {
  607. // If we have a component that allows camera access then redirect the
  608. // fuchsia.camera3.DeviceWatcher requests to the corresponding agent.
  609. if (!video_capturer_components_.empty()) {
  610. CastComponent* capturer_component = *video_capturer_components_.begin();
  611. capturer_component->ConnectDeviceWatcher(std::move(request));
  612. return;
  613. }
  614. // fuchsia.camera3.DeviceWatcher may be requested while none of the running
  615. // apps have the CAMERA permission. Return ZX_ERR_UNAVAILABLE, which implies
  616. // that the client should try connecting again later, since the service may
  617. // become available after a web.Frame with camera access is created.
  618. request.Close(ZX_ERR_UNAVAILABLE);
  619. }
  620. void CastRunner::OnMetricsRecorderServiceRequest(
  621. fidl::InterfaceRequest<fuchsia::legacymetrics::MetricsRecorder> request) {
  622. // TODO(crbug.com/1065707): Remove this hack once the service can be routed
  623. // through the Runner's incoming service directory, in Component Framework v2.
  624. // Attempt to connect via any CastComponent's incoming services.
  625. WebComponent* any_component = main_context_->GetAnyComponent();
  626. if (any_component) {
  627. VLOG(1) << "Connecting MetricsRecorder via CastComponent.";
  628. CastComponent* component = reinterpret_cast<CastComponent*>(any_component);
  629. component->ConnectMetricsRecorder(std::move(request));
  630. return;
  631. }
  632. // Attempt to connect via a FrameHostComponent's services, if available.
  633. if (frame_host_component_incoming_services_) {
  634. VLOG(1) << "Connecting MetricsRecorder via FrameHostComponent.";
  635. frame_host_component_incoming_services_->Connect(std::move(request));
  636. return;
  637. }
  638. LOG(WARNING) << "Ignoring MetricsRecorder request.";
  639. }
  640. void CastRunner::StartComponentInternal(
  641. const GURL& url,
  642. std::unique_ptr<base::StartupContext> startup_context,
  643. fidl::InterfaceRequest<fuchsia::sys::ComponentController>
  644. controller_request) {
  645. // TODO(crbug.com/1120914): Remove this once Component Framework v2 can be
  646. // used to route fuchsia.web.FrameHost capabilities cleanly.
  647. if (enable_frame_host_component_ && (url.spec() == kFrameHostComponentName)) {
  648. frame_host_component_incoming_services_ =
  649. FrameHostComponent::StartAndReturnIncomingServiceDirectory(
  650. std::move(startup_context), std::move(controller_request),
  651. main_context_->GetFrameHostRequestHandler());
  652. return;
  653. }
  654. // TODO(crbug.com/1120914): Remove this once Component Framework v2 can be
  655. // used to route chromium.cast.DataReset capabilities cleanly.
  656. if (url.spec() == kDataResetComponentName) {
  657. // DataResetComponents are self-owned, so may outlive |this|. However,
  658. // |this| is only touched when processing DataReset protocol requests.
  659. // Since |this| is only deleted during component shutdown, no protocol
  660. // requests will be processed after deletion.
  661. DataResetComponent::Start(this, std::move(startup_context),
  662. std::move(controller_request));
  663. return;
  664. }
  665. pending_components_.emplace(std::make_unique<PendingCastComponent>(
  666. this, std::move(startup_context), std::move(controller_request),
  667. url.GetContent()));
  668. }
  669. static base::FilePath SentinelFilePath() {
  670. return base::FilePath(base::kPersistedCacheDirectoryPath)
  671. .Append(kSentinelFileName);
  672. }
  673. bool CastRunner::DeletePersistentDataInternal() {
  674. // Set data reset flag so that new components are not being started.
  675. data_reset_in_progress_ = true;
  676. // Create the staging directory.
  677. base::FilePath staged_for_deletion_directory =
  678. GetStagedForDeletionDirectoryPath();
  679. base::File::Error file_error;
  680. bool result = base::CreateDirectoryAndGetError(staged_for_deletion_directory,
  681. &file_error);
  682. if (!result) {
  683. LOG(ERROR) << "Failed to create the staging directory, error: "
  684. << file_error;
  685. return false;
  686. }
  687. // Stage everything under `/cache` for deletion.
  688. const base::FilePath cache_directory(base::kPersistedCacheDirectoryPath);
  689. base::FileEnumerator enumerator(
  690. cache_directory, /*recursive=*/false,
  691. base::FileEnumerator::FileType::FILES |
  692. base::FileEnumerator::FileType::DIRECTORIES);
  693. for (base::FilePath current = enumerator.Next(); !current.empty();
  694. current = enumerator.Next()) {
  695. // Skip the staging directory itself.
  696. if (current == staged_for_deletion_directory) {
  697. continue;
  698. }
  699. base::FilePath destination =
  700. staged_for_deletion_directory.Append(current.BaseName());
  701. result = base::Move(current, destination);
  702. if (!result) {
  703. LOG(ERROR) << "Failed to move " << current << " to " << destination;
  704. return false;
  705. }
  706. }
  707. return true;
  708. }
  709. void CastRunner::CreatePersistedCacheSentinel() {
  710. base::WriteFile(SentinelFilePath(), "");
  711. was_cache_sentinel_created_ = true;
  712. }
  713. bool CastRunner::WasPersistedCacheErased() {
  714. if (!was_cache_sentinel_created_)
  715. return false;
  716. return !base::PathExists(SentinelFilePath());
  717. }