tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 2fed715f7f0300ebdea0b1c1e8ebd870e702456f
parent 2dd7e21d0a0c81276d957e4e7417beb190e1d817
Author: Andrew Osmond <aosmond@gmail.com>
Date:   Tue,  4 Nov 2025 14:22:42 +0000

Bug 1900626 - Allow Android to use our bundled clearkey CDM. r=geckoview-reviewers,media-playback-reviewers,jolin,nalexander,tcampbell

This patch makes it so that we include libclearkey.so with our APK so
that our existing GMP plumbing can launch using that library on Android.
On other platforms we have a specific plugin file system layout that we
follow and unfortunately that is not easy to do on Android. As such, we
manually setup the plugin and force the GMP plugin process to find/load
the library from our environment instead of using the absolute path.

Differential Revision: https://phabricator.services.mozilla.com/D270967

Diffstat:
Mdom/media/eme/KeySystemConfig.cpp | 7++++++-
Mdom/media/gmp/GMPChild.cpp | 40++++++++++++++++++++++++----------------
Mdom/media/gmp/GMPParent.cpp | 35++++++++++++++++++++++++++++++++---
Mdom/media/gmp/GMPParent.h | 4++++
Mdom/media/gmp/GMPServiceParent.cpp | 46+++++++++++++++++++++++++++++++++++-----------
Mdom/media/gmp/GMPServiceParent.h | 2++
Mdom/media/test/mochitest_eme.toml | 15---------------
Mdom/media/test/mochitest_eme_compat.toml | 5-----
Mmedia/gmp-clearkey/0.1/gmp-clearkey.cpp | 5++++-
Mmedia/gmp-clearkey/0.1/moz.build | 8+++++++-
Mmobile/android/installer/package-manifest.in | 3+++
Mtesting/web-platform/meta/permissions-policy/reporting/encrypted-media-report-only.https.html.ini | 2--
12 files changed, 117 insertions(+), 55 deletions(-)

diff --git a/dom/media/eme/KeySystemConfig.cpp b/dom/media/eme/KeySystemConfig.cpp @@ -32,11 +32,16 @@ namespace mozilla { /* static */ bool KeySystemConfig::Supports(const nsAString& aKeySystem) { #ifdef MOZ_WIDGET_ANDROID - // No GMP on Android, check if we can use MediaDrm for this keysystem. + // Check if we can use MediaDrm for this keysystem. if (mozilla::java::MediaDrmProxy::IsSchemeSupported( NS_ConvertUTF16toUTF8(aKeySystem))) { return true; } + // Check if we can use our bundled Clearkey plugin. + if (IsClearkeyKeySystem(aKeySystem)) { + return HaveGMPFor(nsCString(CHROMIUM_CDM_API), + {NS_ConvertUTF16toUTF8(aKeySystem)}); + } #else # ifdef MOZ_WMF_CDM // Test only, pretend we have already installed CDMs. diff --git a/dom/media/gmp/GMPChild.cpp b/dom/media/gmp/GMPChild.cpp @@ -241,18 +241,21 @@ mozilla::ipc::IPCResult GMPChild::RecvPreloadLibs(const nsCString& aLibs) { } bool GMPChild::GetUTF8LibPath(nsACString& aOutLibPath) { +#ifdef MOZ_WIDGET_ANDROID + aOutLibPath = "lib"_ns + NS_ConvertUTF16toUTF8(mPluginPath) + ".so"_ns; +#else nsCOMPtr<nsIFile> libFile; -#define GMP_PATH_CRASH(explain) \ - do { \ - nsAutoString path; \ - if (!libFile || NS_FAILED(libFile->GetPath(path))) { \ - path = mPluginPath; \ - } \ - CrashReporter::RecordAnnotationNSString( \ - CrashReporter::Annotation::GMPLibraryPath, path); \ - MOZ_CRASH(explain); \ - } while (false) +# define GMP_PATH_CRASH(explain) \ + do { \ + nsAutoString path; \ + if (!libFile || NS_FAILED(libFile->GetPath(path))) { \ + path = mPluginPath; \ + } \ + CrashReporter::RecordAnnotationNSString( \ + CrashReporter::Annotation::GMPLibraryPath, path); \ + MOZ_CRASH(explain); \ + } while (false) nsresult rv = NS_NewLocalFile(mPluginPath, getter_AddRefs(libFile)); if (NS_WARN_IF(NS_FAILED(rv))) { @@ -277,15 +280,15 @@ bool GMPChild::GetUTF8LibPath(nsACString& aOutLibPath) { nsAutoString baseName; baseName = Substring(parentLeafName, 4, parentLeafName.Length() - 1); -#if defined(XP_MACOSX) +# if defined(XP_MACOSX) nsAutoString binaryName = u"lib"_ns + baseName + u".dylib"_ns; -#elif defined(XP_UNIX) +# elif defined(XP_UNIX) nsAutoString binaryName = u"lib"_ns + baseName + u".so"_ns; -#elif defined(XP_WIN) +# elif defined(XP_WIN) nsAutoString binaryName = baseName + u".dll"_ns; -#else -# error not defined -#endif +# else +# error not defined +# endif rv = libFile->AppendRelativePath(binaryName); if (NS_WARN_IF(NS_FAILED(rv))) { GMP_PATH_CRASH("Failed to append lib to plugin file"); @@ -305,10 +308,14 @@ bool GMPChild::GetUTF8LibPath(nsACString& aOutLibPath) { } CopyUTF16toUTF8(path, aOutLibPath); +#endif return true; } bool GMPChild::GetPluginName(nsACString& aPluginName) const { +#ifdef MOZ_WIDGET_ANDROID + aPluginName = NS_ConvertUTF16toUTF8(mPluginPath); +#else // Extract the plugin directory name if possible. nsCOMPtr<nsIFile> libFile; nsresult rv = NS_NewLocalFile(mPluginPath, getter_AddRefs(libFile)); @@ -323,6 +330,7 @@ bool GMPChild::GetPluginName(nsACString& aPluginName) const { NS_ENSURE_SUCCESS(rv, false); aPluginName.Assign(NS_ConvertUTF16toUTF8(parentLeafName)); +#endif return true; } diff --git a/dom/media/gmp/GMPParent.cpp b/dom/media/gmp/GMPParent.cpp @@ -93,7 +93,7 @@ GMPParent::~GMPParent() { void GMPParent::CloneFrom(const GMPParent* aOther) { MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread()); - MOZ_ASSERT(aOther->mDirectory && aOther->mService, "null plugin directory"); + MOZ_ASSERT(aOther->mService); mService = aOther->mService; mDirectory = aOther->mDirectory; @@ -165,6 +165,29 @@ nsresult GMPParent::GetPluginFileArch(nsIFile* aPluginDir, } #endif // defined(XP_WIN) || defined(XP_MACOSX) +#ifdef MOZ_WIDGET_ANDROID +void GMPParent::InitForClearkey(GeckoMediaPluginServiceParent* aService) { + MOZ_ASSERT(aService); + MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread()); + + mService = aService; + mName = u"clearkey"_ns; + mDisplayName = "clearkey"_ns; + mVersion = "0.1"_ns; + mDescription = "ClearKey Gecko Media Plugin"_ns; + mPluginType = GMPPluginType::Clearkey; + mAdapter = u"chromium"_ns; + + mCapabilities.SetCapacity(1); + auto& video = *mCapabilities.AppendElement(); + video.mAPIName = nsLiteralCString(CHROMIUM_CDM_API); + video.mAPITags.SetCapacity(2); + video.mAPITags.AppendElement(nsCString{kClearKeyKeySystemName}); + video.mAPITags.AppendElement( + nsCString{kClearKeyWithProtectionQueryKeySystemName}); +} +#endif + RefPtr<GenericPromise> GMPParent::Init(GeckoMediaPluginServiceParent* aService, nsIFile* aPluginDir) { MOZ_ASSERT(aPluginDir); @@ -342,7 +365,6 @@ class NotifyGMPProcessLoadedTask : public Runnable { }; nsresult GMPParent::LoadProcess() { - MOZ_ASSERT(mDirectory, "Plugin directory cannot be NULL!"); MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread()); MOZ_ASSERT(mState == GMPState::NotLoaded); @@ -353,9 +375,16 @@ nsresult GMPParent::LoadProcess() { } nsAutoString path; - if (NS_WARN_IF(NS_FAILED(mDirectory->GetPath(path)))) { +#ifdef MOZ_WIDGET_ANDROID + // We need to bundle any CDMs with the APK, so we can just supply the library + // name to the child process. + path = mName; +#else + if (NS_WARN_IF(!mDirectory) || + NS_WARN_IF(NS_FAILED(mDirectory->GetPath(path)))) { return NS_ERROR_FAILURE; } +#endif GMP_PARENT_LOG_DEBUG("%s: for %s", __FUNCTION__, NS_ConvertUTF16toUTF8(path).get()); diff --git a/dom/media/gmp/GMPParent.h b/dom/media/gmp/GMPParent.h @@ -66,6 +66,10 @@ class GMPParent final : public PGMPParent, GMPParent(); +#ifdef MOZ_WIDGET_ANDROID + void InitForClearkey(GeckoMediaPluginServiceParent* aService); +#endif + RefPtr<GenericPromise> Init(GeckoMediaPluginServiceParent* aService, nsIFile* aPluginDir); void CloneFrom(const GMPParent* aOther); diff --git a/dom/media/gmp/GMPServiceParent.cpp b/dom/media/gmp/GMPServiceParent.cpp @@ -532,6 +532,19 @@ RefPtr<GenericPromise> GeckoMediaPluginServiceParent::LoadFromEnvironment() { return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); } +#ifdef MOZ_WIDGET_ANDROID + if (RefPtr<GMPParent> clearkeyGmp = CreateGMPParent()) { + clearkeyGmp->InitForClearkey(this); + + { + MutexAutoLock lock(mMutex); + mPlugins.AppendElement(std::move(clearkeyGmp)); + } + + UpdateContentProcessGMPCapabilities(); + } +#endif + const char* env = PR_GetEnv("MOZ_GMP_PATH"); if (!env || !*env) { return GenericPromise::CreateAndResolve(true, __func__); @@ -642,8 +655,11 @@ void GeckoMediaPluginServiceParent::UpdateContentProcessGMPCapabilities( } #ifdef MOZ_WMF_CDM if (name.Equals("gmp-widevinecdm-l1")) { - nsCOMPtr<nsIFile> pluginFile = gmp->GetDirectory(); - MFCDMService::UpdateWidevineL1Path(pluginFile); + if (nsCOMPtr<nsIFile> pluginFile = gmp->GetDirectory()) { + MFCDMService::UpdateWidevineL1Path(pluginFile); + } else { + MOZ_ASSERT_UNREACHABLE("Missing directory for Widevine L1 plugin!"); + } } #endif caps.AppendElement(std::move(x)); @@ -916,8 +932,12 @@ GeckoMediaPluginServiceParent::FindPluginDirectoryForAPI( size_t index = 0; RefPtr<GMPParent> gmp = FindPluginForAPIFrom(index, api, aTags, &index); if (gmp) { - nsCOMPtr<nsIFile> dir = gmp->GetDirectory(); - dir.forget(aDirectory); + if (nsCOMPtr<nsIFile> dir = gmp->GetDirectory()) { + dir.forget(aDirectory); + } else { + NS_WARNING("Found plugin but missing directory."); + return NS_ERROR_FAILURE; + } } } @@ -1015,7 +1035,7 @@ already_AddRefed<GMPParent> GeckoMediaPluginServiceParent::SelectPluginForAPI( return nullptr; } -static already_AddRefed<GMPParent> CreateGMPParent() { +already_AddRefed<GMPParent> GeckoMediaPluginServiceParent::CreateGMPParent() { // Should run on the GMP thread. #if defined(XP_LINUX) && defined(MOZ_SANDBOX) if (!SandboxInfo::Get().CanSandboxMedia()) { @@ -1122,7 +1142,8 @@ void GeckoMediaPluginServiceParent::RemoveOnGMPThread( for (size_t i = mPlugins.Length(); i-- > 0;) { nsCOMPtr<nsIFile> pluginpath = mPlugins[i]->GetDirectory(); bool equals; - if (NS_FAILED(directory->Equals(pluginpath, &equals)) || !equals) { + if (!pluginpath || NS_FAILED(directory->Equals(pluginpath, &equals)) || + !equals) { continue; } @@ -1181,11 +1202,14 @@ void GeckoMediaPluginServiceParent::PluginTerminated( if (aPlugin->IsMarkedForDeletion()) { nsString path; - RefPtr<nsIFile> dir = aPlugin->GetDirectory(); - nsresult rv = dir->GetPath(path); - NS_ENSURE_SUCCESS_VOID(rv); - if (mPluginsWaitingForDeletion.Contains(path)) { - RemoveOnGMPThread(path, true /* delete */, true /* can defer */); + if (RefPtr<nsIFile> dir = aPlugin->GetDirectory()) { + nsresult rv = dir->GetPath(path); + NS_ENSURE_SUCCESS_VOID(rv); + if (mPluginsWaitingForDeletion.Contains(path)) { + RemoveOnGMPThread(path, true /* delete */, true /* can defer */); + } + } else { + MOZ_ASSERT_UNREACHABLE("Plugin without directory marked for deletion?"); } } } diff --git a/dom/media/gmp/GMPServiceParent.h b/dom/media/gmp/GMPServiceParent.h @@ -158,6 +158,8 @@ class GeckoMediaPluginServiceParent final const nsACString& aAPI, const nsTArray<nsCString>& aTags) override; private: + already_AddRefed<GMPParent> CreateGMPParent(); + // Creates a copy of aOriginal. Note that the caller is responsible for // adding this to GeckoMediaPluginServiceParent::mPlugins. already_AddRefed<GMPParent> ClonePlugin(const GMPParent* aOriginal); diff --git a/dom/media/test/mochitest_eme.toml b/dom/media/test/mochitest_eme.toml @@ -697,40 +697,32 @@ support-files = [ ] ["test_eme_autoplay.html"] -skip-if = ["os == 'android'"] # bug 1149374 scheme = "https" ["test_eme_canvas_blocked.html"] scheme = "https" skip-if = [ - "os == 'android'", # bug 1149374 "apple_silicon", # bug 1707737 "os == 'mac' && os_version == '14.70' && processor == 'x86_64'", # Bug 1929444 ] ["test_eme_detach_reattach_same_mediakeys_during_playback.html"] -skip-if = ["os == 'android'"] # bug 1149374 scheme = "https" ["test_eme_initDataTypes.html"] -skip-if = ["os == 'android'"] scheme = "https" ["test_eme_missing_pssh.html"] -skip-if = ["os == 'android'"] scheme = "https" ["test_eme_non_mse_fails.html"] -skip-if = ["os == 'android'"] # bug 1149374 scheme = "https" ["test_eme_output_config_check.html"] -skip-if = ["os == 'android'"] # bug 1900626 : no clearkey on Android scheme = "https" ["test_eme_playback.html"] skip-if = [ - "os == 'android'", # bug 1149374 "apple_silicon", # bug 1707737 "os == 'mac' && os_version == '14.70' && processor == 'x86_64'", # Bug 1929444 ] @@ -746,29 +738,24 @@ skip-if = [ ["test_eme_pssh_in_moof.html"] scheme = "https" skip-if = [ - "os == 'android'", # bug 1149374 "os == 'mac' && os_version == '14.70' && processor == 'x86_64' && debug", # Bug 1929444 "os == 'mac' && os_version == '14.70' && processor == 'x86_64' && opt && socketprocess_e10s", # Bug 1929444 ] ["test_eme_sample_groups_playback.html"] -skip-if = ["os == 'android'"] # bug 1149374 scheme = "https" ["test_eme_special_key_system.html"] -skip-if = ["os == 'android'"] # bug 1149374 scheme = "https" ["test_eme_stream_capture_blocked_case1.html"] tags = "mtg capturestream" -skip-if = ["os == 'android'"] # bug 1149374 scheme = "https" ["test_eme_stream_capture_blocked_case2.html"] tags = "mtg capturestream" scheme = "https" skip-if = [ - "os == 'android'", # bug 1149374 "apple_silicon", # bug 1707737 "os == 'mac' && os_version == '14.70' && processor == 'x86_64'", # Bug 1929444 ] @@ -777,7 +764,6 @@ skip-if = [ tags = "mtg capturestream" scheme = "https" skip-if = [ - "os == 'android'", # bug 1149374 "apple_silicon", # bug 1707737 "os == 'mac' && os_version == '14.70' && processor == 'x86_64'", # Bug 1929444 ] @@ -795,7 +781,6 @@ skip-if = [ scheme = "https" skip-if = [ "xorigin", - "os == 'android'", # bug 1149374 "apple_silicon", # bug 1707737 "os == 'mac' && os_version == '14.70' && processor == 'x86_64'", # Bug 1929444 ] diff --git a/dom/media/test/mochitest_eme_compat.toml b/dom/media/test/mochitest_eme_compat.toml @@ -698,15 +698,12 @@ skip-if = ["os == 'android'"] # bug 1149374 scheme = "https" ["test_eme_detach_media_keys.html"] -skip-if = ["os == 'android'"] scheme = "https" ["test_eme_getstatusforpolicy.html"] -skip-if = ["os == 'android'"] # bug 1149374 scheme = "https" ["test_eme_requestKeySystemAccess.html"] -skip-if = ["os == 'android'"] # bug 1149374 scheme = "https" ["test_eme_requestMediaKeySystemAccess_origin_check_widevine.html"] @@ -714,7 +711,6 @@ run-if = ["os == 'win'"] # origin check is only supported on windows scheme = "https" ["test_eme_requestMediaKeySystemAccess_with_app_approval.html"] -skip-if = ["os == 'android'"] # bug 1149374 scheme = "https" ["test_eme_request_notifications.html"] @@ -725,5 +721,4 @@ scheme = "https" scheme = "https" ["test_eme_setMediaKeys_before_attach_MediaSource.html"] -skip-if = ["os == 'android'"] # bug 1149374 scheme = "https" diff --git a/media/gmp-clearkey/0.1/gmp-clearkey.cpp b/media/gmp-clearkey/0.1/gmp-clearkey.cpp @@ -120,7 +120,10 @@ void ClosePlatformFile(cdm::PlatformFile aFile) { static uint32_t NumExpectedHostFiles(const cdm::HostFile* aHostFiles, uint32_t aNumFiles) { -#if !defined(XP_WIN) +#if defined(ANDROID) + // We expect 1 binary: clearkey + return 1; +#elif !defined(XP_WIN) // We expect 4 binaries: clearkey, libxul, plugin-container, and Firefox. return 4; #else diff --git a/media/gmp-clearkey/0.1/moz.build b/media/gmp-clearkey/0.1/moz.build @@ -9,7 +9,13 @@ with Files("**"): GeckoSharedLibrary("clearkey") -FINAL_TARGET = "dist/bin/gmp-clearkey/0.1" +# Android wants all libraries to be packaged under dist/bin so that we can +# place them in the APK under lib/<arch>. We have special logic in GMP that +# configure the clearkey library for us. +if CONFIG["OS_TARGET"] == "Android": + FINAL_TARGET = "dist/bin" +else: + FINAL_TARGET = "dist/bin/gmp-clearkey/0.1" FINAL_TARGET_PP_FILES += ["manifest.json.in"] diff --git a/mobile/android/installer/package-manifest.in b/mobile/android/installer/package-manifest.in @@ -80,6 +80,9 @@ @BINPATH@/@DLL_PREFIX@minidump_analyzer@DLL_SUFFIX@ #endif +; GMP clearkey plugin +@BINPATH@/@DLL_PREFIX@clearkey@DLL_SUFFIX@ + [browser] ; [Base Browser Files] @BINPATH@/application.ini diff --git a/testing/web-platform/meta/permissions-policy/reporting/encrypted-media-report-only.https.html.ini b/testing/web-platform/meta/permissions-policy/reporting/encrypted-media-report-only.https.html.ini @@ -1,9 +1,7 @@ [encrypted-media-report-only.https.html] expected: - if os == "android": OK TIMEOUT [Encrypted Media report only mode] expected: - if os == "android": FAIL TIMEOUT