Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/media/MediaDevices.cpp
Line
Count
Source (jump to first uncovered line)
1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
#include "mozilla/dom/MediaDevices.h"
6
#include "mozilla/dom/MediaStreamBinding.h"
7
#include "mozilla/dom/MediaDeviceInfo.h"
8
#include "mozilla/dom/MediaDevicesBinding.h"
9
#include "mozilla/dom/NavigatorBinding.h"
10
#include "mozilla/dom/Promise.h"
11
#include "mozilla/MediaManager.h"
12
#include "MediaTrackConstraints.h"
13
#include "nsContentUtils.h"
14
#include "nsIEventTarget.h"
15
#include "nsINamed.h"
16
#include "nsIScriptGlobalObject.h"
17
#include "nsIPermissionManager.h"
18
#include "nsPIDOMWindow.h"
19
#include "nsQueryObject.h"
20
21
0
#define DEVICECHANGE_HOLD_TIME_IN_MS 1000
22
23
namespace mozilla {
24
namespace dom {
25
26
class FuzzTimerCallBack final : public nsITimerCallback, public nsINamed
27
{
28
0
  ~FuzzTimerCallBack() {}
29
30
public:
31
0
  explicit FuzzTimerCallBack(MediaDevices* aMediaDevices) : mMediaDevices(aMediaDevices) {}
32
33
  NS_DECL_ISUPPORTS
34
35
  NS_IMETHOD Notify(nsITimer* aTimer) final
36
0
  {
37
0
    mMediaDevices->DispatchTrustedEvent(NS_LITERAL_STRING("devicechange"));
38
0
    return NS_OK;
39
0
  }
40
41
  NS_IMETHOD GetName(nsACString& aName) override
42
0
  {
43
0
    aName.AssignLiteral("FuzzTimerCallBack");
44
0
    return NS_OK;
45
0
  }
46
47
private:
48
  nsCOMPtr<MediaDevices> mMediaDevices;
49
};
50
51
NS_IMPL_ISUPPORTS(FuzzTimerCallBack, nsITimerCallback, nsINamed)
52
53
class MediaDevices::GumResolver : public nsIDOMGetUserMediaSuccessCallback
54
{
55
public:
56
  NS_DECL_ISUPPORTS
57
58
0
  explicit GumResolver(Promise* aPromise) : mPromise(aPromise) {}
59
60
  NS_IMETHOD
61
  OnSuccess(nsISupports* aStream) override
62
0
  {
63
0
    RefPtr<DOMMediaStream> stream = do_QueryObject(aStream);
64
0
    if (!stream) {
65
0
      return NS_ERROR_FAILURE;
66
0
    }
67
0
    mPromise->MaybeResolve(stream);
68
0
    return NS_OK;
69
0
  }
70
71
private:
72
0
  virtual ~GumResolver() {}
73
  RefPtr<Promise> mPromise;
74
};
75
76
class MediaDevices::EnumDevResolver : public nsIGetUserMediaDevicesSuccessCallback
77
{
78
public:
79
  NS_DECL_ISUPPORTS
80
81
  EnumDevResolver(Promise* aPromise, uint64_t aWindowId)
82
0
  : mPromise(aPromise), mWindowId(aWindowId) {}
83
84
  NS_IMETHOD
85
  OnSuccess(nsIVariant* aDevices) override
86
0
  {
87
0
    // Create array for nsIMediaDevice
88
0
    nsTArray<nsCOMPtr<nsIMediaDevice>> devices;
89
0
    // Contain the fumes
90
0
    {
91
0
      uint16_t vtype;
92
0
      nsresult rv = aDevices->GetDataType(&vtype);
93
0
      NS_ENSURE_SUCCESS(rv, rv);
94
0
      if (vtype != nsIDataType::VTYPE_EMPTY_ARRAY) {
95
0
        nsIID elementIID;
96
0
        uint16_t elementType;
97
0
        void* rawArray;
98
0
        uint32_t arrayLen;
99
0
        rv = aDevices->GetAsArray(&elementType, &elementIID, &arrayLen, &rawArray);
100
0
        NS_ENSURE_SUCCESS(rv, rv);
101
0
        if (elementType != nsIDataType::VTYPE_INTERFACE) {
102
0
          free(rawArray);
103
0
          return NS_ERROR_FAILURE;
104
0
        }
105
0
106
0
        nsISupports **supportsArray = reinterpret_cast<nsISupports **>(rawArray);
107
0
        for (uint32_t i = 0; i < arrayLen; ++i) {
108
0
          nsCOMPtr<nsIMediaDevice> device(do_QueryInterface(supportsArray[i]));
109
0
          devices.AppendElement(device);
110
0
          NS_IF_RELEASE(supportsArray[i]); // explicitly decrease refcount for rawptr
111
0
        }
112
0
        free(rawArray); // explicitly free memory from nsIVariant::GetAsArray
113
0
      }
114
0
    }
115
0
    nsTArray<RefPtr<MediaDeviceInfo>> infos;
116
0
    for (auto& device : devices) {
117
0
      MediaDeviceKind kind = static_cast<MediaDevice*>(device.get())->mKind;
118
0
      MOZ_ASSERT(kind == dom::MediaDeviceKind::Audioinput
119
0
                  || kind == dom::MediaDeviceKind::Videoinput
120
0
                  || kind == dom::MediaDeviceKind::Audiooutput);
121
0
      nsString id;
122
0
      nsString name;
123
0
      device->GetId(id);
124
0
      // Include name only if page currently has a gUM stream active or
125
0
      // persistent permissions (audio or video) have been granted
126
0
      if (MediaManager::Get()->IsActivelyCapturingOrHasAPermission(mWindowId) ||
127
0
          Preferences::GetBool("media.navigator.permission.disabled", false)) {
128
0
        device->GetName(name);
129
0
      }
130
0
      RefPtr<MediaDeviceInfo> info = new MediaDeviceInfo(id, kind, name);
131
0
      infos.AppendElement(info);
132
0
    }
133
0
    mPromise->MaybeResolve(infos);
134
0
    return NS_OK;
135
0
  }
136
137
private:
138
0
  virtual ~EnumDevResolver() {}
139
  RefPtr<Promise> mPromise;
140
  uint64_t mWindowId;
141
};
142
143
class MediaDevices::GumRejecter : public nsIDOMGetUserMediaErrorCallback
144
{
145
public:
146
  NS_DECL_ISUPPORTS
147
148
0
  explicit GumRejecter(Promise* aPromise) : mPromise(aPromise) {}
149
150
  NS_IMETHOD
151
  OnError(nsISupports* aError) override
152
0
  {
153
0
    RefPtr<MediaStreamError> error = do_QueryObject(aError);
154
0
    if (!error) {
155
0
      return NS_ERROR_FAILURE;
156
0
    }
157
0
    mPromise->MaybeReject(error);
158
0
    return NS_OK;
159
0
  }
160
161
private:
162
0
  virtual ~GumRejecter() {}
163
  RefPtr<Promise> mPromise;
164
};
165
166
MediaDevices::~MediaDevices()
167
0
{
168
0
  MediaManager* mediamanager = MediaManager::GetIfExists();
169
0
  if (mediamanager) {
170
0
    mediamanager->RemoveDeviceChangeCallback(this);
171
0
  }
172
0
}
173
174
NS_IMPL_ISUPPORTS(MediaDevices::GumResolver, nsIDOMGetUserMediaSuccessCallback)
175
NS_IMPL_ISUPPORTS(MediaDevices::EnumDevResolver, nsIGetUserMediaDevicesSuccessCallback)
176
NS_IMPL_ISUPPORTS(MediaDevices::GumRejecter, nsIDOMGetUserMediaErrorCallback)
177
178
already_AddRefed<Promise>
179
MediaDevices::GetUserMedia(const MediaStreamConstraints& aConstraints,
180
                           CallerType aCallerType,
181
                           ErrorResult &aRv)
182
0
{
183
0
  RefPtr<Promise> p = Promise::Create(GetParentObject(), aRv);
184
0
  NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
185
0
186
0
  MediaManager::GetUserMediaSuccessCallback resolver(new GumResolver(p));
187
0
  MediaManager::GetUserMediaErrorCallback rejecter(new GumRejecter(p));
188
0
189
0
  aRv = MediaManager::Get()->GetUserMedia(GetOwner(), aConstraints,
190
0
                                          std::move(resolver),
191
0
                                          std::move(rejecter),
192
0
                                          aCallerType);
193
0
  return p.forget();
194
0
}
195
196
already_AddRefed<Promise>
197
MediaDevices::EnumerateDevices(CallerType aCallerType, ErrorResult &aRv)
198
0
{
199
0
  RefPtr<Promise> p = Promise::Create(GetParentObject(), aRv);
200
0
  NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
201
0
202
0
  RefPtr<EnumDevResolver> resolver = new EnumDevResolver(p, GetOwner()->WindowID());
203
0
  RefPtr<GumRejecter> rejecter = new GumRejecter(p);
204
0
205
0
  aRv = MediaManager::Get()->EnumerateDevices(GetOwner(), resolver, rejecter, aCallerType);
206
0
  return p.forget();
207
0
}
208
209
NS_IMPL_ADDREF_INHERITED(MediaDevices, DOMEventTargetHelper)
210
NS_IMPL_RELEASE_INHERITED(MediaDevices, DOMEventTargetHelper)
211
0
NS_INTERFACE_MAP_BEGIN(MediaDevices)
212
0
  NS_INTERFACE_MAP_ENTRY(MediaDevices)
213
0
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
214
215
void
216
MediaDevices::OnDeviceChange()
217
0
{
218
0
  MOZ_ASSERT(NS_IsMainThread());
219
0
  nsresult rv = CheckInnerWindowCorrectness();
220
0
  if (NS_FAILED(rv)) {
221
0
    MOZ_ASSERT(false);
222
0
    return;
223
0
  }
224
0
225
0
  if (!(MediaManager::Get()->IsActivelyCapturingOrHasAPermission(GetOwner()->WindowID()) ||
226
0
    Preferences::GetBool("media.navigator.permission.disabled", false))) {
227
0
    return;
228
0
  }
229
0
230
0
  // Do not fire event to content script when
231
0
  // privacy.resistFingerprinting is true.
232
0
  if (nsContentUtils::ShouldResistFingerprinting()) {
233
0
    return;
234
0
  }
235
0
236
0
  if (!mFuzzTimer)
237
0
  {
238
0
    mFuzzTimer = NS_NewTimer();
239
0
  }
240
0
241
0
  if (!mFuzzTimer) {
242
0
    MOZ_ASSERT(false);
243
0
    return;
244
0
  }
245
0
246
0
  mFuzzTimer->Cancel();
247
0
  RefPtr<FuzzTimerCallBack> cb = new FuzzTimerCallBack(this);
248
0
  mFuzzTimer->InitWithCallback(cb, DEVICECHANGE_HOLD_TIME_IN_MS, nsITimer::TYPE_ONE_SHOT);
249
0
}
250
251
mozilla::dom::EventHandlerNonNull*
252
MediaDevices::GetOndevicechange()
253
0
{
254
0
  return GetEventHandler(nsGkAtoms::ondevicechange);
255
0
}
256
257
void
258
MediaDevices::SetOndevicechange(mozilla::dom::EventHandlerNonNull* aCallback)
259
0
{
260
0
  SetEventHandler(nsGkAtoms::ondevicechange, aCallback);
261
0
262
0
  MediaManager::Get()->AddDeviceChangeCallback(this);
263
0
}
264
265
void
266
MediaDevices::EventListenerAdded(nsAtom* aType)
267
0
{
268
0
  MediaManager::Get()->AddDeviceChangeCallback(this);
269
0
  DOMEventTargetHelper::EventListenerAdded(aType);
270
0
}
271
272
JSObject*
273
MediaDevices::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
274
0
{
275
0
  return MediaDevices_Binding::Wrap(aCx, this, aGivenProto);
276
0
}
277
278
} // namespace dom
279
} // namespace mozilla