chromium/chromecast/common/cast_content_client.cc

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromecast/common/cast_content_client.h"

#include <stdint.h>

#include <memory>
#include <string_view>
#include <utility>

#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/native_library.h"
#include "base/path_service.h"
#include "base/task/sequenced_task_runner.h"
#include "build/build_config.h"
#include "chromecast/base/cast_constants.h"
#include "chromecast/base/cast_paths.h"
#include "chromecast/base/version.h"
#include "chromecast/chromecast_buildflags.h"
#include "components/cast/common/constants.h"
#include "content/public/common/cdm_info.h"
#include "media/base/media_switches.h"
#include "media/cdm/cdm_type.h"
#include "media/media_buildflags.h"
#include "mojo/public/cpp/bindings/binder_map.h"
#include "third_party/widevine/cdm/buildflags.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "url/url_util.h"

#if BUILDFLAG(IS_ANDROID)
#include <optional>

#include "chromecast/common/media/cast_media_drm_bridge_client.h"
#include "components/cdm/common/android_cdm_registration.h"
#endif

#if !BUILDFLAG(IS_FUCHSIA)
#include "base/no_destructor.h"
#include "components/services/heap_profiling/public/cpp/profiling_client.h"  // nogncheck
#include "mojo/public/cpp/bindings/pending_receiver.h"
#endif

#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
#include "media/cdm/cdm_paths.h"  // nogncheck
#endif

#if BUILDFLAG(BUNDLE_WIDEVINE_CDM) && BUILDFLAG(IS_LINUX)
#include "base/no_destructor.h"
#include "components/cdm/common/cdm_manifest.h"
#include "media/base/cdm_capability.h"
#include "third_party/widevine/cdm/widevine_cdm_common.h"  // nogncheck
// component updated CDM on all desktop platforms and remove this.
// This file is In SHARED_INTERMEDIATE_DIR.
#include "widevine_cdm_version.h"  // nogncheck
#endif

namespace chromecast {
namespace shell {

namespace {

#if BUILDFLAG(BUNDLE_WIDEVINE_CDM) && BUILDFLAG(IS_LINUX)
// Copied from chrome_content_client.cc
std::unique_ptr<content::CdmInfo> CreateWidevineCdmInfo(
    const base::Version& version,
    const base::FilePath& cdm_library_path,
    media::CdmCapability capability) {
  return std::make_unique<content::CdmInfo>(
      kWidevineKeySystem, content::CdmInfo::Robustness::kSoftwareSecure,
      std::move(capability), /*supports_sub_key_systems=*/false,
      kWidevineCdmDisplayName, kWidevineCdmType, version, cdm_library_path);
}

// On desktop Linux, given |cdm_base_path| that points to a folder containing
// the Widevine CDM and associated files, read the manifest included in that
// directory and create a CdmInfo. If that is successful, return the CdmInfo. If
// not, return nullptr.
// Copied from chrome_content_client.cc
// TODO(crbug.com/40746872): move the functions to a common file.
std::unique_ptr<content::CdmInfo> CreateCdmInfoFromWidevineDirectory(
    const base::FilePath& cdm_base_path) {
  // Library should be inside a platform specific directory.
  auto cdm_library_path =
      media::GetPlatformSpecificDirectory(cdm_base_path)
          .Append(base::GetNativeLibraryName(kWidevineCdmLibraryName));
  if (!base::PathExists(cdm_library_path)) {
    LOG(ERROR) << "cdm library path doesn't exist";
    return nullptr;
  }

  // Manifest should be at the top level.
  auto manifest_path = cdm_base_path.Append(FILE_PATH_LITERAL("manifest.json"));
  base::Version version;
  media::CdmCapability capability;
  if (!ParseCdmManifestFromPath(manifest_path, &version, &capability))
    return nullptr;

  return CreateWidevineCdmInfo(version, cdm_library_path,
                               std::move(capability));
}

// This code checks to see if the Widevine CDM was bundled with Chrome. If one
// can be found and looks valid, it returns the CdmInfo for the CDM. Otherwise
// it returns nullptr.
// Copied from chrome_content_client.cc
content::CdmInfo* GetBundledWidevine() {
  // We only want to do this on the first call, as if Widevine wasn't bundled
  // with Chrome (or it was deleted/removed) it won't be loaded into the zygote.
  static base::NoDestructor<std::unique_ptr<content::CdmInfo>> s_cdm_info(
      []() -> std::unique_ptr<content::CdmInfo> {
        base::FilePath install_dir;
        CHECK(base::PathService::Get(chromecast::DIR_BUNDLED_WIDEVINE_CDM,
                                     &install_dir));

        // On desktop Linux the MANIFEST is bundled with the CDM.
        return CreateCdmInfoFromWidevineDirectory(install_dir);
      }());
  return s_cdm_info->get();
}
#endif  // BUILDFLAG(BUNDLE_WIDEVINE_CDM) && BUILDFLAG(IS_LINUX)

}  // namespace

CastContentClient::~CastContentClient() {
}

void CastContentClient::SetActiveURL(const GURL& url, std::string top_origin) {
  if (url.is_empty() || url == last_active_url_)
    return;
  LOG(INFO) << "Active URL: " << url.possibly_invalid_spec() << " for origin '"
            << top_origin << "'";
  last_active_url_ = url;
}

void CastContentClient::AddAdditionalSchemes(Schemes* schemes) {
  schemes->standard_schemes.push_back(kChromeResourceScheme);
}

std::u16string CastContentClient::GetLocalizedString(int message_id) {
  return l10n_util::GetStringUTF16(message_id);
}

std::string_view CastContentClient::GetDataResource(
    int resource_id,
    ui::ResourceScaleFactor scale_factor) {
  return ui::ResourceBundle::GetSharedInstance().GetRawDataResourceForScale(
      resource_id, scale_factor);
}

base::RefCountedMemory* CastContentClient::GetDataResourceBytes(
    int resource_id) {
  // Chromecast loads localized resources for the home screen via this code
  // path. See crbug.com/643886 for details.
  return ui::ResourceBundle::GetSharedInstance().LoadLocalizedResourceBytes(
      resource_id);
}

std::string CastContentClient::GetDataResourceString(int resource_id) {
  return ui::ResourceBundle::GetSharedInstance().LoadDataResourceString(
      resource_id);
}

gfx::Image& CastContentClient::GetNativeImageNamed(int resource_id) {
  return ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed(
      resource_id);
}

#if BUILDFLAG(IS_ANDROID)
::media::MediaDrmBridgeClient* CastContentClient::GetMediaDrmBridgeClient() {
  return new media::CastMediaDrmBridgeClient();
}
#endif  // BUILDFLAG(IS_ANDROID)

void CastContentClient::ExposeInterfacesToBrowser(
    scoped_refptr<base::SequencedTaskRunner> io_task_runner,
    mojo::BinderMap* binders) {
#if !BUILDFLAG(IS_FUCHSIA)
  binders->Add<heap_profiling::mojom::ProfilingClient>(
      base::BindRepeating(
          [](mojo::PendingReceiver<heap_profiling::mojom::ProfilingClient>
                 receiver) {
            static base::NoDestructor<heap_profiling::ProfilingClient>
                profiling_client;
            profiling_client->BindToInterface(std::move(receiver));
          }),
      io_task_runner);
#endif  // !BUILDFLAG(IS_FUCHSIA)
}

void CastContentClient::AddContentDecryptionModules(
    std::vector<content::CdmInfo>* cdms,
    std::vector<::media::CdmHostFilePath>* cdm_host_file_paths) {
  if (cdms) {
#if BUILDFLAG(BUNDLE_WIDEVINE_CDM) && BUILDFLAG(IS_LINUX)
    // The Widevine CDM on Linux needs to be registered (and loaded) before the
    // zygote is locked down. The CDM can be found from the version bundled with
    // Chrome (if BUNDLE_WIDEVINE_CDM = true).
    content::CdmInfo* bundled_widevine = GetBundledWidevine();

    if (bundled_widevine) {
      DVLOG(1) << "Registering bundled Widevine " << bundled_widevine->version;
      cdms->push_back(*bundled_widevine);
    } else {
      DVLOG(1) << "Widevine enabled but no library found";
    }
#elif BUILDFLAG(IS_ANDROID)
#if BUILDFLAG(ENABLE_WIDEVINE)
    cdm::AddAndroidWidevineCdm(cdms);
#endif  // BUILDFLAG(ENABLE_WIDEVINE)

#if BUILDFLAG(ENABLE_PLAYREADY)
    // PlayReady may be supported on the devices. Register it anyway without
    // any capabilities so that it will be checked the first time it is used.
    // CdmInfo needs a CdmType, but on Android it is not used as the key system
    // is supported by MediaDrm. Using a random value as something needs to be
    // specified, and it must be different than other CdmTypes specified.
    // (On Android the key system is identified by UUID, and that mapping is
    // maintained by MediaDrmBridge.)
    const ::media::CdmType kPlayReadyCdmType{0x86eb6b54497627b0ull,
                                             0xdd48f67486daf152ull};
    // TODO(jrummell): Move kChromecastPlayreadyKeySystem from
    // chromecast/media/base/key_systems_common.h to someplace more accessible.
    const char kChromecastPlayreadyKeySystem[] = "com.chromecast.playready";
    cdms->push_back(
        content::CdmInfo(kChromecastPlayreadyKeySystem,
                         content::CdmInfo::Robustness::kSoftwareSecure,
                         std::nullopt, kPlayReadyCdmType));
    cdms->push_back(
        content::CdmInfo(kChromecastPlayreadyKeySystem,
                         content::CdmInfo::Robustness::kHardwareSecure,
                         std::nullopt, kPlayReadyCdmType));
#endif  // BUILDFLAG(ENABLE_PLAYREADY)
#endif  // BUILDFLAG(BUNDLE_WIDEVINE_CDM) && BUILDFLAG(IS_LINUX)
  }
}

}  // namespace shell
}  // namespace chromecast