summaryrefslogtreecommitdiffstats
path: root/dom/media/eme/MediaKeySystemAccessManager.h
blob: 992de2a9e7944d9711a3acada39f90214475588e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
/* 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
 *
 */

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 {
    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;
    // The KeySystem passed for this request.
    const nsString mKeySystem;
    // The config(s) passed for this request.
    const Sequence<MediaKeySystemConfiguration> mConfigs;

    // 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_