diff options
Diffstat (limited to 'dom/media/eme/MediaKeySystemAccessManager.h')
-rw-r--r-- | dom/media/eme/MediaKeySystemAccessManager.h | 237 |
1 files changed, 237 insertions, 0 deletions
diff --git a/dom/media/eme/MediaKeySystemAccessManager.h b/dom/media/eme/MediaKeySystemAccessManager.h new file mode 100644 index 0000000000..77feded701 --- /dev/null +++ b/dom/media/eme/MediaKeySystemAccessManager.h @@ -0,0 +1,237 @@ +/* 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/. */ + +#ifndef DOM_MEDIA_MEDIAKEYSYSTEMACCESSMANAGER_H_ +#define DOM_MEDIA_MEDIAKEYSYSTEMACCESSMANAGER_H_ + +#include "mozilla/dom/MediaKeySystemAccess.h" +#include "mozilla/MozPromise.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIObserver.h" +#include "nsISupportsImpl.h" +#include "nsITimer.h" + +namespace mozilla::dom { + +class DetailedPromise; +class TestGMPVideoDecoder; + +/** + * MediaKeySystemAccessManager implements the functionality for + * Navigator.requestMediaKeySystemAccess(). The navigator may perform its own + * logic before passing the request to this class, but the majority of + * processing happens the MediaKeySystemAccessManager. The manager is expected + * to be run entirely on the main thread of the content process for whichever + * window it is associated with. + * + * As well as implementing the Navigator.requestMediaKeySystemAccess() + * algorithm, the manager performs Gecko specific logic. For example, the EME + * specification does not specify a process to check if a CDM is installed as + * part of requesting access, but that is an important part of obtaining access + * for Gecko, and is handled by the manager. + * + * A request made to the manager can be thought of as entering a pipeline. + * In this pipeline the request must pass through various stages that can + * reject the request and remove it from the pipeline. If a request is not + * rejected by the end of the pipeline it is approved/resolved. + * + * The pipeline is structured in such a way that each step should be executed + * even if it will quickly be exited. For example, the step that checks if a + * window supports protected media is an instant approve on non-Windows OSes, + * but we want to execute the function representing that step to ensure a + * deterministic execution and logging path. The hope is this reduces + * complexity for when we need to debug or change the code. + * + * While the pipeline metaphor generally holds, the implementation details of + * the manager mean that processing is not always linear: a request may be + * re-injected earlier into the pipeline for reprocessing. This can happen + * if the request was pending some other operation, e.g. CDM install, after + * which we wish to reprocess that request. However, we strive to keep it + * as linear as possible. + * + * A high level version of the happy path pipeline is depicted below. If a + * request were to fail any of the steps below it would be rejected and ejected + * from the pipeline. + * + * Request arrives from navigator + * + + * | + * v + * Check if window supports protected media + * + + * +<-------------------+ + * v | + * Check request args are sane | + * + | + * | Wait for CDM and retry + * v | + * Check if CDM is installed | + * + | + * | | + * +--------------------+ + * | + * v + * Check if CDM supports args + * + + * | + * v + * Check if app allows protected media + * (used by GeckoView) + * + + * | + * v + * Provide access + * + */ + +struct MediaKeySystemAccessRequest { + MediaKeySystemAccessRequest( + const nsAString& aKeySystem, + const Sequence<MediaKeySystemConfiguration>& aConfigs) + : mKeySystem(aKeySystem), mConfigs(aConfigs) {} + virtual ~MediaKeySystemAccessRequest() = default; + // The KeySystem passed for this request. + const nsString mKeySystem; + // The config(s) passed for this request. + const Sequence<MediaKeySystemConfiguration> mConfigs; +}; + +class MediaKeySystemAccessManager final : public nsIObserver, public nsINamed { + public: + explicit MediaKeySystemAccessManager(nsPIDOMWindowInner* aWindow); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(MediaKeySystemAccessManager, + nsIObserver) + NS_DECL_NSIOBSERVER + NS_DECL_NSINAMED + + // Entry point for the navigator to call into the manager. + void Request(DetailedPromise* aPromise, const nsAString& aKeySystem, + const Sequence<MediaKeySystemConfiguration>& aConfig); + + void Shutdown(); + + private: + // Encapsulates the information for a Navigator.requestMediaKeySystemAccess() + // request that is being processed. + struct PendingRequest : public MediaKeySystemAccessRequest { + enum class RequestType { Initial, Subsequent }; + + PendingRequest(DetailedPromise* aPromise, const nsAString& aKeySystem, + const Sequence<MediaKeySystemConfiguration>& aConfigs); + ~PendingRequest(); + + // The JS promise associated with this request. + RefPtr<DetailedPromise> mPromise; + + // If the request is + // - A first attempt request from JS: RequestType::Initial. + // - A request we're reprocessing due to a GMP being installed: + // RequestType::Subsequent. + RequestType mRequestType = RequestType::Initial; + + // If we find a supported config for this request during processing it + // should be stored here. Only if we have a supported config should a + // request have access provided. + Maybe<MediaKeySystemConfiguration> mSupportedConfig; + + // Will be set to trigger a timeout and re-processing of the request if the + // request is pending on some potentially time consuming operation, e.g. + // CDM install. + nsCOMPtr<nsITimer> mTimer = nullptr; + + // Convenience methods to reject the wrapped promise. + void RejectPromiseWithInvalidAccessError(const nsACString& aReason); + void RejectPromiseWithNotSupportedError(const nsACString& aReason); + void RejectPromiseWithTypeError(const nsACString& aReason); + + void CancelTimer(); + }; + + // Check if the application (e.g. a GeckoView app) allows protected media in + // this window. + // + // This function is always expected to be executed as part of the pipeline of + // processing a request, but its behavior differs depending on prefs set. + // + // If the `media_eme_require_app_approval` pref is false, then the function + // assumes app approval and early returns. Otherwise the function will + // create a permission request to be approved by the embedding app. If the + // test prefs detailed in MediaKeySystemAccessPermissionRequest.h are set + // then they will control handling, otherwise it is up to the embedding + // app to handle the request. + // + // At the time of writing, only GeckoView based apps are expected to pref + // on this behavior. + // + // This function is expected to run late/last in the pipeline so that if we + // ask the app for permission we don't fail after the app okays the request. + // This is to avoid cases where a user may be prompted by the app to approve + // eme, this check then passes, but we fail later in the pipeline, leaving + // the user wondering why their approval didn't work. + void CheckDoesAppAllowProtectedMedia(UniquePtr<PendingRequest> aRequest); + + // Handles the result of the app allowing or disallowing protected media. + // If there are pending requests in mPendingAppApprovalRequests then this + // needs to be called on each. + void OnDoesAppAllowProtectedMedia(bool aIsAllowed, + UniquePtr<PendingRequest> aRequest); + + // Checks if the Window associated with this manager supports protected media + // and calls into OnDoesWindowSupportEncryptedMedia with the result. + void CheckDoesWindowSupportProtectedMedia(UniquePtr<PendingRequest> aRequest); + + // Handle the result of checking if the window associated with this manager + // supports protected media. If the window doesn't support protected media + // this will reject the request, otherwise the request will continue to be + // processed. + void OnDoesWindowSupportProtectedMedia(bool aIsSupportedInWindow, + UniquePtr<PendingRequest> aRequest); + + // Performs the 'requestMediaKeySystemAccess' algorithm detailed in the EME + // specification. Gecko may need to install a CDM to satisfy this check. If + // CDM install is needed this function may be called again for the same + // request once the CDM is installed or a timeout is reached. + void RequestMediaKeySystemAccess(UniquePtr<PendingRequest> aRequest); + + // Approves aRequest and provides MediaKeySystemAccess by resolving the + // promise associated with the request. + void ProvideAccess(UniquePtr<PendingRequest> aRequest); + + ~MediaKeySystemAccessManager(); + + bool EnsureObserversAdded(); + + bool AwaitInstall(UniquePtr<PendingRequest> aRequest); + + void RetryRequest(UniquePtr<PendingRequest> aRequest); + + // Requests waiting on approval from the application to be processed. + nsTArray<UniquePtr<PendingRequest>> mPendingAppApprovalRequests; + + // Requests waiting on CDM installation to be processed. + nsTArray<UniquePtr<PendingRequest>> mPendingInstallRequests; + + nsCOMPtr<nsPIDOMWindowInner> mWindow; + bool mAddedObservers = false; + + // Has the app approved protected media playback? If it has we cache the + // value so we don't need to check again. + Maybe<bool> mAppAllowsProtectedMedia; + + // If we're waiting for permission from the app to enable EME this holder + // should contain the request. + // + // Note the type in the holder should match + // MediaKeySystemAccessPermissionRequest::RequestPromise, but we can't + // include MediaKeySystemAccessPermissionRequest's header here without + // breaking the build, so we do this hack. + MozPromiseRequestHolder<MozPromise<bool, bool, true>> + mAppAllowsProtectedMediaPromiseRequest; +}; + +} // namespace mozilla::dom + +#endif // DOM_MEDIA_MEDIAKEYSYSTEMACCESSMANAGER_H_ |