/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sw=2 sts=2 et cindent: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/MediaKeySystemAccess.h" #include "mozilla/dom/MediaKeySystemAccessBinding.h" #include "mozilla/Preferences.h" #include "nsContentTypeParser.h" #ifdef MOZ_FMP4 #include "MP4Decoder.h" #endif #ifdef XP_WIN #include "mozilla/WindowsVersion.h" #include "WMFDecoderModule.h" #endif #ifdef XP_MACOSX #include "nsCocoaFeatures.h" #endif #include "nsContentCID.h" #include "nsServiceManagerUtils.h" #include "mozIGeckoMediaPluginService.h" #include "VideoUtils.h" #include "mozilla/Services.h" #include "nsIObserverService.h" #include "mozilla/EMEUtils.h" #include "GMPUtils.h" #include "nsAppDirectoryServiceDefs.h" #include "nsDirectoryServiceUtils.h" #include "nsDirectoryServiceDefs.h" #include "nsXULAppAPI.h" #include "gmp-audio-decode.h" #include "gmp-video-decode.h" #ifdef XP_WIN #include "WMFDecoderModule.h" #endif #if defined(XP_WIN) || defined(XP_MACOSX) #define PRIMETIME_EME_SUPPORTED 1 #endif namespace mozilla { namespace dom { NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaKeySystemAccess, mParent) NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeySystemAccess) NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeySystemAccess) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeySystemAccess) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END MediaKeySystemAccess::MediaKeySystemAccess(nsPIDOMWindow* aParent, const nsAString& aKeySystem, const nsAString& aCDMVersion, const MediaKeySystemConfiguration& aConfig) : mParent(aParent) , mKeySystem(aKeySystem) , mCDMVersion(aCDMVersion) , mConfig(aConfig) { } MediaKeySystemAccess::~MediaKeySystemAccess() { } JSObject* MediaKeySystemAccess::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return MediaKeySystemAccessBinding::Wrap(aCx, this, aGivenProto); } nsPIDOMWindow* MediaKeySystemAccess::GetParentObject() const { return mParent; } void MediaKeySystemAccess::GetKeySystem(nsString& aOutKeySystem) const { aOutKeySystem.Assign(mKeySystem); } void MediaKeySystemAccess::GetConfiguration(MediaKeySystemConfiguration& aConfig) { aConfig = mConfig; } already_AddRefed MediaKeySystemAccess::CreateMediaKeys(ErrorResult& aRv) { RefPtr keys(new MediaKeys(mParent, mKeySystem, mCDMVersion)); return keys->Init(aRv); } static bool HaveGMPFor(mozIGeckoMediaPluginService* aGMPService, const nsCString& aKeySystem, const nsCString& aAPI, const nsCString& aTag = EmptyCString()) { nsTArray tags; tags.AppendElement(aKeySystem); if (!aTag.IsEmpty()) { tags.AppendElement(aTag); } bool hasPlugin = false; if (NS_FAILED(aGMPService->HasPluginForAPI(aAPI, &tags, &hasPlugin))) { return false; } return hasPlugin; } #ifdef XP_WIN static bool AdobePluginFileExists(const nsACString& aVersionStr, const nsAString& aFilename) { MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); nsCOMPtr path; nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(path)); if (NS_WARN_IF(NS_FAILED(rv))) { return false; } rv = path->Append(NS_LITERAL_STRING("gmp-eme-adobe")); if (NS_WARN_IF(NS_FAILED(rv))) { return false; } rv = path->AppendNative(aVersionStr); if (NS_WARN_IF(NS_FAILED(rv))) { return false; } rv = path->Append(aFilename); if (NS_WARN_IF(NS_FAILED(rv))) { return false; } bool exists = false; return NS_SUCCEEDED(path->Exists(&exists)) && exists; } static bool AdobePluginDLLExists(const nsACString& aVersionStr) { return AdobePluginFileExists(aVersionStr, NS_LITERAL_STRING("eme-adobe.dll")); } static bool AdobePluginVoucherExists(const nsACString& aVersionStr) { return AdobePluginFileExists(aVersionStr, NS_LITERAL_STRING("eme-adobe.voucher")); } #endif /* static */ bool MediaKeySystemAccess::IsGMPPresentOnDisk(const nsAString& aKeySystem, const nsACString& aVersion, nsACString& aOutMessage) { MOZ_ASSERT(NS_IsMainThread()); if (XRE_GetProcessType() != GeckoProcessType_Default) { // We need to be able to access the filesystem, so call this in the // main process via ContentChild. ContentChild* contentChild = ContentChild::GetSingleton(); if (NS_WARN_IF(!contentChild)) { return false; } nsCString message; bool result = false; bool ok = contentChild->SendIsGMPPresentOnDisk(nsString(aKeySystem), nsCString(aVersion), &result, &message); aOutMessage = message; return ok && result; } bool isPresent = true; #if XP_WIN if (aKeySystem.EqualsLiteral("com.adobe.primetime")) { if (!AdobePluginDLLExists(aVersion)) { NS_WARNING("Adobe EME plugin disappeared from disk!"); aOutMessage = NS_LITERAL_CSTRING("Adobe DLL was expected to be on disk but was not"); isPresent = false; } if (!AdobePluginVoucherExists(aVersion)) { NS_WARNING("Adobe EME voucher disappeared from disk!"); aOutMessage = NS_LITERAL_CSTRING("Adobe plugin voucher was expected to be on disk but was not"); isPresent = false; } if (!isPresent) { // Reset the prefs that Firefox's GMP downloader sets, so that // Firefox will try to download the plugin next time the updater runs. Preferences::ClearUser("media.gmp-eme-adobe.lastUpdate"); Preferences::ClearUser("media.gmp-eme-adobe.version"); } else if (!EMEVoucherFileExists()) { // Gecko doesn't have a voucher file for the plugin-container. // Adobe EME isn't going to work, so don't advertise that it will. aOutMessage = NS_LITERAL_CSTRING("Plugin-container voucher not present"); isPresent = false; } } #endif return isPresent; } static MediaKeySystemStatus EnsureMinCDMVersion(mozIGeckoMediaPluginService* aGMPService, const nsAString& aKeySystem, int32_t aMinCdmVersion, nsACString& aOutMessage, nsACString& aOutCdmVersion) { nsTArray tags; tags.AppendElement(NS_ConvertUTF16toUTF8(aKeySystem)); bool hasPlugin; nsAutoCString versionStr; if (NS_FAILED(aGMPService->GetPluginVersionForAPI(NS_LITERAL_CSTRING(GMP_API_DECRYPTOR), &tags, &hasPlugin, versionStr))) { aOutMessage = NS_LITERAL_CSTRING("GetPluginVersionForAPI failed"); return MediaKeySystemStatus::Error; } aOutCdmVersion = versionStr; if (!hasPlugin) { aOutMessage = NS_LITERAL_CSTRING("CDM is not installed"); return MediaKeySystemStatus::Cdm_not_installed; } if (!MediaKeySystemAccess::IsGMPPresentOnDisk(aKeySystem, versionStr, aOutMessage)) { return MediaKeySystemStatus::Cdm_not_installed; } nsresult rv; int32_t version = versionStr.ToInteger(&rv); if (aMinCdmVersion != NO_CDM_VERSION && (NS_FAILED(rv) || version < 0 || aMinCdmVersion > version)) { aOutMessage = NS_LITERAL_CSTRING("Installed CDM version insufficient"); return MediaKeySystemStatus::Cdm_insufficient_version; } return MediaKeySystemStatus::Available; } /* static */ MediaKeySystemStatus MediaKeySystemAccess::GetKeySystemStatus(const nsAString& aKeySystem, int32_t aMinCdmVersion, nsACString& aOutMessage, nsACString& aOutCdmVersion) { MOZ_ASSERT(Preferences::GetBool("media.eme.enabled", false)); nsCOMPtr mps = do_GetService("@mozilla.org/gecko-media-plugin-service;1"); if (NS_WARN_IF(!mps)) { aOutMessage = NS_LITERAL_CSTRING("Failed to get GMP service"); return MediaKeySystemStatus::Error; } if (aKeySystem.EqualsLiteral("org.w3.clearkey")) { if (!Preferences::GetBool("media.eme.clearkey.enabled", true)) { aOutMessage = NS_LITERAL_CSTRING("ClearKey was disabled"); return MediaKeySystemStatus::Cdm_disabled; } return EnsureMinCDMVersion(mps, aKeySystem, aMinCdmVersion, aOutMessage, aOutCdmVersion); } #ifdef PRIMETIME_EME_SUPPORTED if (aKeySystem.EqualsLiteral("com.adobe.primetime")) { if (!Preferences::GetBool("media.gmp-eme-adobe.enabled", false)) { aOutMessage = NS_LITERAL_CSTRING("Adobe EME disabled"); return MediaKeySystemStatus::Cdm_disabled; } #ifdef XP_WIN // Win Vista and later only. if (!IsVistaOrLater()) { aOutMessage = NS_LITERAL_CSTRING("Minimum Windows version not met for Adobe EME"); return MediaKeySystemStatus::Cdm_not_supported; } #endif #ifdef XP_MACOSX if (!nsCocoaFeatures::OnLionOrLater()) { aOutMessage = NS_LITERAL_CSTRING("Minimum MacOSX version not met for Adobe EME"); return MediaKeySystemStatus::Cdm_not_supported; } #endif return EnsureMinCDMVersion(mps, aKeySystem, aMinCdmVersion, aOutMessage, aOutCdmVersion); } #endif return MediaKeySystemStatus::Cdm_not_supported; } static bool GMPDecryptsAndDecodesAAC(mozIGeckoMediaPluginService* aGMPS, const nsAString& aKeySystem) { MOZ_ASSERT(HaveGMPFor(aGMPS, NS_ConvertUTF16toUTF8(aKeySystem), NS_LITERAL_CSTRING(GMP_API_DECRYPTOR))); return HaveGMPFor(aGMPS, NS_ConvertUTF16toUTF8(aKeySystem), NS_LITERAL_CSTRING(GMP_API_AUDIO_DECODER), NS_LITERAL_CSTRING("aac")) #ifdef XP_WIN // Clearkey on Windows advertises that it can decode in its GMP info // file, but uses Windows Media Foundation to decode. That's not present // on Windows XP, and on some Vista, Windows N, and KN variants without // certain services packs. So for ClearKey we must check that WMF will // work. && (!aKeySystem.EqualsLiteral("org.w3.clearkey") || WMFDecoderModule::HasAAC()) #endif ; } static bool GMPDecryptsAndDecodesH264(mozIGeckoMediaPluginService* aGMPS, const nsAString& aKeySystem) { MOZ_ASSERT(HaveGMPFor(aGMPS, NS_ConvertUTF16toUTF8(aKeySystem), NS_LITERAL_CSTRING(GMP_API_DECRYPTOR))); return HaveGMPFor(aGMPS, NS_ConvertUTF16toUTF8(aKeySystem), NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER), NS_LITERAL_CSTRING("h264")) #ifdef XP_WIN // Clearkey on Windows advertises that it can decode in its GMP info // file, but uses Windows Media Foundation to decode. That's not present // on Windows XP, and on some Vista, Windows N, and KN variants without // certain services packs. So for ClearKey we must check that WMF will // work. && (!aKeySystem.EqualsLiteral("org.w3.clearkey") || WMFDecoderModule::HasH264()) #endif ; } // If this keysystem's CDM explicitly says it doesn't support decoding, // that means it's OK with passing the decrypted samples back to Gecko // for decoding. Note we special case Clearkey on Windows, where we need // to check for whether WMF is usable because the CDM uses that // to decode. static bool GMPDecryptsAndGeckoDecodesH264(mozIGeckoMediaPluginService* aGMPService, const nsAString& aKeySystem, const nsAString& aContentType) { MOZ_ASSERT(HaveGMPFor(aGMPService, NS_ConvertUTF16toUTF8(aKeySystem), NS_LITERAL_CSTRING(GMP_API_DECRYPTOR))); MOZ_ASSERT(IsH264ContentType(aContentType)); return (!HaveGMPFor(aGMPService, NS_ConvertUTF16toUTF8(aKeySystem), NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER), NS_LITERAL_CSTRING("h264")) #ifdef XP_WIN // Clearkey on Windows advertises that it can decode in its GMP info // file, but uses Windows Media Foundation to decode. That's not present // on Windows XP, and on some Vista, Windows N, and KN variants without // certain services packs. So don't try to use gmp-clearkey for decoding // if we don't have a decoder here. || (aKeySystem.EqualsLiteral("org.w3.clearkey") && !WMFDecoderModule::HasH264()) #endif ) && MP4Decoder::CanHandleMediaType(aContentType); } static bool GMPDecryptsAndGeckoDecodesAAC(mozIGeckoMediaPluginService* aGMPService, const nsAString& aKeySystem, const nsAString& aContentType) { MOZ_ASSERT(HaveGMPFor(aGMPService, NS_ConvertUTF16toUTF8(aKeySystem), NS_LITERAL_CSTRING(GMP_API_DECRYPTOR))); MOZ_ASSERT(IsAACContentType(aContentType)); return (!HaveGMPFor(aGMPService, NS_ConvertUTF16toUTF8(aKeySystem), NS_LITERAL_CSTRING(GMP_API_AUDIO_DECODER), NS_LITERAL_CSTRING("aac")) #ifdef XP_WIN // Clearkey on Windows advertises that it can decode in its GMP info // file, but uses Windows Media Foundation to decode. That's not present // on Windows XP, and on some Vista, Windows N, and KN variants without // certain services packs. So don't try to use gmp-clearkey for decoding // if we don't have a decoder here. || (aKeySystem.EqualsLiteral("org.w3.clearkey") && !WMFDecoderModule::HasAAC()) #endif ) && MP4Decoder::CanHandleMediaType(aContentType); } static bool IsSupportedAudio(mozIGeckoMediaPluginService* aGMPService, const nsAString& aKeySystem, const nsAString& aAudioType) { return IsAACContentType(aAudioType) && (GMPDecryptsAndDecodesAAC(aGMPService, aKeySystem) || GMPDecryptsAndGeckoDecodesAAC(aGMPService, aKeySystem, aAudioType)); } static bool IsSupportedVideo(mozIGeckoMediaPluginService* aGMPService, const nsAString& aKeySystem, const nsAString& aVideoType) { return IsH264ContentType(aVideoType) && (GMPDecryptsAndDecodesH264(aGMPService, aKeySystem) || GMPDecryptsAndGeckoDecodesH264(aGMPService, aKeySystem, aVideoType)); } static bool IsSupported(mozIGeckoMediaPluginService* aGMPService, const nsAString& aKeySystem, const MediaKeySystemConfiguration& aConfig) { if (aConfig.mInitDataType.IsEmpty() && aConfig.mAudioType.IsEmpty() && aConfig.mVideoType.IsEmpty()) { // Not an old-style request. return false; } // Backwards compatibility with legacy MediaKeySystemConfiguration method. if (!aConfig.mInitDataType.IsEmpty() && !aConfig.mInitDataType.EqualsLiteral("cenc")) { return false; } if (!aConfig.mAudioType.IsEmpty() && !IsSupportedAudio(aGMPService, aKeySystem, aConfig.mAudioType)) { return false; } if (!aConfig.mVideoType.IsEmpty() && !IsSupportedVideo(aGMPService, aKeySystem, aConfig.mVideoType)) { return false; } return true; } static bool GetSupportedConfig(mozIGeckoMediaPluginService* aGMPService, const nsAString& aKeySystem, const MediaKeySystemConfiguration& aCandidate, MediaKeySystemConfiguration& aOutConfig) { MediaKeySystemConfiguration config; config.mLabel = aCandidate.mLabel; if (aCandidate.mInitDataTypes.WasPassed()) { nsTArray initDataTypes; for (const nsString& candidate : aCandidate.mInitDataTypes.Value()) { // All supported keySystems can handle "cenc" initDataType. // ClearKey also supports "keyids" and "webm" initDataTypes. if (candidate.EqualsLiteral("cenc")) { initDataTypes.AppendElement(candidate); } else if ((candidate.EqualsLiteral("keyids") || candidate.EqualsLiteral("webm)")) && aKeySystem.EqualsLiteral("org.w3.clearkey")) { initDataTypes.AppendElement(candidate); } } if (initDataTypes.IsEmpty()) { return false; } config.mInitDataTypes.Construct(); config.mInitDataTypes.Value().Assign(initDataTypes); } if (aCandidate.mAudioCapabilities.WasPassed()) { nsTArray caps; for (const MediaKeySystemMediaCapability& cap : aCandidate.mAudioCapabilities.Value()) { if (IsSupportedAudio(aGMPService, aKeySystem, cap.mContentType)) { caps.AppendElement(cap); } } if (caps.IsEmpty()) { return false; } config.mAudioCapabilities.Construct(); config.mAudioCapabilities.Value().Assign(caps); } if (aCandidate.mVideoCapabilities.WasPassed()) { nsTArray caps; for (const MediaKeySystemMediaCapability& cap : aCandidate.mVideoCapabilities.Value()) { if (IsSupportedVideo(aGMPService, aKeySystem, cap.mContentType)) { caps.AppendElement(cap); } } if (caps.IsEmpty()) { return false; } config.mVideoCapabilities.Construct(); config.mVideoCapabilities.Value().Assign(caps); } aOutConfig = config; return true; } // Backwards compatibility with legacy requestMediaKeySystemAccess with fields // from old MediaKeySystemOptions dictionary. /* static */ bool MediaKeySystemAccess::IsSupported(const nsAString& aKeySystem, const Sequence& aConfigs) { nsCOMPtr mps = do_GetService("@mozilla.org/gecko-media-plugin-service;1"); if (NS_WARN_IF(!mps)) { return false; } if (!HaveGMPFor(mps, NS_ConvertUTF16toUTF8(aKeySystem), NS_LITERAL_CSTRING(GMP_API_DECRYPTOR))) { return false; } for (const MediaKeySystemConfiguration& config : aConfigs) { if (mozilla::dom::IsSupported(mps, aKeySystem, config)) { return true; } } return false; } /* static */ bool MediaKeySystemAccess::GetSupportedConfig(const nsAString& aKeySystem, const Sequence& aConfigs, MediaKeySystemConfiguration& aOutConfig) { nsCOMPtr mps = do_GetService("@mozilla.org/gecko-media-plugin-service;1"); if (NS_WARN_IF(!mps)) { return false; } if (!HaveGMPFor(mps, NS_ConvertUTF16toUTF8(aKeySystem), NS_LITERAL_CSTRING(GMP_API_DECRYPTOR))) { return false; } for (const MediaKeySystemConfiguration& config : aConfigs) { if (mozilla::dom::GetSupportedConfig(mps, aKeySystem, config, aOutConfig)) { return true; } } return false; } /* static */ void MediaKeySystemAccess::NotifyObservers(nsIDOMWindow* aWindow, const nsAString& aKeySystem, MediaKeySystemStatus aStatus) { if (aStatus == MediaKeySystemStatus::Cdm_not_supported) { // Ignore, since there's nothing the user can do to rectify this, and we // don't want the prompt to confuse them. // TODO: Remove places that call with this entirely. return; } RequestMediaKeySystemAccessNotification data; data.mKeySystem = aKeySystem; data.mStatus = aStatus; nsAutoString json; data.ToJSON(json); EME_LOG("MediaKeySystemAccess::NotifyObservers() %s", NS_ConvertUTF16toUTF8(json).get()); nsCOMPtr obs = services::GetObserverService(); if (obs) { obs->NotifyObservers(aWindow, "mediakeys-request", json.get()); } } } // namespace dom } // namespace mozilla