7387a38ab05138674c67bbaac93293778995c54e
[WebKit-https.git] / Source / WebCore / Modules / mediastream / UserMediaRequest.cpp
1 /*
2  * Copyright (C) 2011 Ericsson AB. All rights reserved.
3  * Copyright (C) 2012 Google Inc. All rights reserved.
4  * Copyright (C) 2013-2018 Apple Inc. All rights reserved.
5  * Copyright (C) 2013 Nokia Corporation and/or its subsidiary(-ies).
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer
15  *    in the documentation and/or other materials provided with the
16  *    distribution.
17  * 3. Neither the name of Ericsson nor the names of its contributors
18  *    may be used to endorse or promote products derived from this
19  *    software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32  */
33
34 #include "config.h"
35 #include "UserMediaRequest.h"
36
37 #if ENABLE(MEDIA_STREAM)
38
39 #include "Document.h"
40 #include "Frame.h"
41 #include "JSMediaStream.h"
42 #include "JSOverconstrainedError.h"
43 #include "Logging.h"
44 #include "MediaConstraints.h"
45 #include "PlatformMediaSessionManager.h"
46 #include "RealtimeMediaSourceCenter.h"
47 #include "SchemeRegistry.h"
48 #include "Settings.h"
49 #include "UserMediaController.h"
50 #include <wtf/Scope.h>
51
52 namespace WebCore {
53
54 Ref<UserMediaRequest> UserMediaRequest::create(Document& document, MediaStreamRequest&& request, DOMPromiseDeferred<IDLInterface<MediaStream>>&& promise)
55 {
56     auto result = adoptRef(*new UserMediaRequest(document, WTFMove(request), WTFMove(promise)));
57     result->suspendIfNeeded();
58     return result;
59 }
60
61 UserMediaRequest::UserMediaRequest(Document& document, MediaStreamRequest&& request, DOMPromiseDeferred<IDLInterface<MediaStream>>&& promise)
62     : ActiveDOMObject(document)
63     , m_identifier(UserMediaRequestIdentifier::generate())
64     , m_promise(WTFMove(promise))
65     , m_request(WTFMove(request))
66 {
67 }
68
69 UserMediaRequest::~UserMediaRequest() = default;
70
71 SecurityOrigin* UserMediaRequest::userMediaDocumentOrigin() const
72 {
73     if (!m_scriptExecutionContext)
74         return nullptr;
75     return m_scriptExecutionContext->securityOrigin();
76 }
77
78 SecurityOrigin* UserMediaRequest::topLevelDocumentOrigin() const
79 {
80     if (!m_scriptExecutionContext)
81         return nullptr;
82     return &m_scriptExecutionContext->topOrigin();
83 }
84
85 static bool hasInvalidGetDisplayMediaConstraint(const MediaConstraints& constraints)
86 {
87     // https://w3c.github.io/mediacapture-screen-share/#navigator-additions
88     // 1. Let constraints be the method's first argument.
89     // 2. For each member present in constraints whose value, value, is a dictionary, run the following steps:
90     //     1. If value contains a member named advanced, return a promise rejected with a newly created TypeError.
91     //     2. If value contains a member which in turn is a dictionary containing a member named either min or
92     //        exact, return a promise rejected with a newly created TypeError.
93     if (!constraints.isValid)
94         return false;
95
96     if (!constraints.advancedConstraints.isEmpty())
97         return true;
98
99     bool invalid = false;
100     constraints.mandatoryConstraints.filter([&invalid] (const MediaConstraint& constraint) mutable {
101         switch (constraint.constraintType()) {
102         case MediaConstraintType::Width:
103         case MediaConstraintType::Height: {
104             auto& intConstraint = downcast<IntConstraint>(constraint);
105             int value;
106             invalid = intConstraint.getExact(value) || intConstraint.getMin(value);
107             break;
108         }
109
110         case MediaConstraintType::AspectRatio:
111         case MediaConstraintType::FrameRate: {
112             auto& doubleConstraint = downcast<DoubleConstraint>(constraint);
113             double value;
114             invalid = doubleConstraint.getExact(value) || doubleConstraint.getMin(value);
115             break;
116         }
117
118         case MediaConstraintType::DisplaySurface:
119         case MediaConstraintType::LogicalSurface: {
120             auto& boolConstraint = downcast<BooleanConstraint>(constraint);
121             bool value;
122             invalid = boolConstraint.getExact(value);
123             break;
124         }
125
126         case MediaConstraintType::FacingMode:
127         case MediaConstraintType::DeviceId:
128         case MediaConstraintType::GroupId: {
129             auto& stringConstraint = downcast<StringConstraint>(constraint);
130             Vector<String> values;
131             invalid = stringConstraint.getExact(values);
132             break;
133         }
134
135         case MediaConstraintType::SampleRate:
136         case MediaConstraintType::SampleSize:
137         case MediaConstraintType::Volume:
138         case MediaConstraintType::EchoCancellation:
139             // Ignored.
140             break;
141
142         case MediaConstraintType::Unknown:
143             ASSERT_NOT_REACHED();
144             break;
145         }
146
147         return invalid;
148     });
149
150     return invalid;
151 }
152
153 void UserMediaRequest::start()
154 {
155     ASSERT(m_scriptExecutionContext);
156     if (!m_scriptExecutionContext) {
157         deny(MediaAccessDenialReason::UserMediaDisabled);
158         return;
159     }
160
161     if (m_request.type == MediaStreamRequest::Type::DisplayMedia) {
162         if (hasInvalidGetDisplayMediaConstraint(m_request.videoConstraints)) {
163             deny(MediaAccessDenialReason::IllegalConstraint);
164             return;
165         }
166     }
167
168     // https://w3c.github.io/mediacapture-main/getusermedia.html#dom-mediadevices-getusermedia()
169     // 1. Let constraints be the method's first argument.
170     // 2. Let requestedMediaTypes be the set of media types in constraints with either a dictionary
171     //    value or a value of "true".
172     // 3. If requestedMediaTypes is the empty set, return a promise rejected with a TypeError. The word
173     //    "optional" occurs in the WebIDL due to WebIDL rules, but the argument must be supplied in order
174     //    for the call to succeed.
175     if (!m_request.audioConstraints.isValid && !m_request.videoConstraints.isValid) {
176         deny(MediaAccessDenialReason::NoConstraints);
177         return;
178     }
179
180     // 4. If the current settings object's responsible document is NOT allowed to use the feature indicated by
181     //    attribute name allowusermedia, return a promise rejected with a DOMException object whose name
182     //    attribute has the value SecurityError.
183     auto& document = downcast<Document>(*m_scriptExecutionContext);
184     auto* controller = UserMediaController::from(document.page());
185     if (!controller) {
186         deny(MediaAccessDenialReason::UserMediaDisabled);
187         return;
188     }
189
190     // 6.3 Optionally, e.g., based on a previously-established user preference, for security reasons,
191     //     or due to platform limitations, jump to the step labeled Permission Failure below.
192     // ...
193     // 6.10 Permission Failure: Reject p with a new DOMException object whose name attribute has
194     //      the value NotAllowedError.
195
196     OptionSet<UserMediaController::CaptureType> types;
197     UserMediaController::BlockedCaller caller;
198     if (m_request.type == MediaStreamRequest::Type::DisplayMedia) {
199         types.add(UserMediaController::CaptureType::Display);
200         caller = UserMediaController::BlockedCaller::GetDisplayMedia;
201     } else {
202         if (m_request.audioConstraints.isValid)
203             types.add(UserMediaController::CaptureType::Microphone);
204         if (m_request.videoConstraints.isValid)
205             types.add(UserMediaController::CaptureType::Camera);
206         caller = UserMediaController::BlockedCaller::GetUserMedia;
207     }
208     auto access = controller->canCallGetUserMedia(document, types);
209     if (access != UserMediaController::GetUserMediaAccess::CanCall) {
210         deny(MediaAccessDenialReason::PermissionDenied);
211         controller->logGetUserMediaDenial(document, access, caller);
212         return;
213     }
214
215     PlatformMediaSessionManager::sharedManager().prepareToSendUserMediaPermissionRequest();
216     controller->requestUserMediaAccess(*this);
217 }
218
219 static inline bool isMediaStreamCorrectlyStarted(const MediaStream& stream)
220 {
221     if (stream.getTracks().isEmpty())
222         return false;
223
224     return WTF::allOf(stream.getTracks(), [](auto& track) {
225         return !track->source().captureDidFail();
226     });
227 }
228
229 void UserMediaRequest::allow(CaptureDevice&& audioDevice, CaptureDevice&& videoDevice, String&& deviceIdentifierHashSalt, CompletionHandler<void()>&& completionHandler)
230 {
231     RELEASE_LOG(MediaStream, "UserMediaRequest::allow %s %s", audioDevice ? audioDevice.persistentId().utf8().data() : "", videoDevice ? videoDevice.persistentId().utf8().data() : "");
232
233     auto callback = [this, protector = makePendingActivity(*this), completionHandler = WTFMove(completionHandler)](RefPtr<MediaStreamPrivate>&& privateStream) mutable {
234         auto scopeExit = makeScopeExit([completionHandler = WTFMove(completionHandler)]() mutable {
235             completionHandler();
236         });
237         if (isContextStopped())
238             return;
239
240         if (!privateStream) {
241             RELEASE_LOG(MediaStream, "UserMediaRequest::allow failed to create media stream!");
242             deny(MediaAccessDenialReason::HardwareError);
243             return;
244         }
245
246         auto& document = downcast<Document>(*m_scriptExecutionContext);
247         privateStream->monitorOrientation(document.orientationNotifier());
248
249         auto stream = MediaStream::create(document, privateStream.releaseNonNull());
250         stream->startProducingData();
251
252         if (!isMediaStreamCorrectlyStarted(stream)) {
253             deny(MediaAccessDenialReason::HardwareError);
254             return;
255         }
256
257         ASSERT(document.isCapturing());
258         stream->document()->setHasCaptureMediaStreamTrack();
259         m_promise.resolve(WTFMove(stream));
260     };
261
262     auto& document = downcast<Document>(*scriptExecutionContext());
263     document.setDeviceIDHashSalt(deviceIdentifierHashSalt);
264
265     RealtimeMediaSourceCenter::singleton().createMediaStream(document.logger(), WTFMove(callback), WTFMove(deviceIdentifierHashSalt), WTFMove(audioDevice), WTFMove(videoDevice), m_request);
266
267     if (!m_scriptExecutionContext)
268         return;
269
270 #if ENABLE(WEB_RTC)
271     if (auto* page = document.page())
272         page->rtcController().disableICECandidateFilteringForDocument(document);
273 #endif
274 }
275
276 void UserMediaRequest::deny(MediaAccessDenialReason reason, const String& message)
277 {
278     if (!m_scriptExecutionContext)
279         return;
280
281     ExceptionCode code;
282     switch (reason) {
283     case MediaAccessDenialReason::IllegalConstraint:
284         RELEASE_LOG(MediaStream, "UserMediaRequest::deny - invalid constraints");
285         code = TypeError;
286         break;
287     case MediaAccessDenialReason::NoConstraints:
288         RELEASE_LOG(MediaStream, "UserMediaRequest::deny - no constraints");
289         code = TypeError;
290         break;
291     case MediaAccessDenialReason::UserMediaDisabled:
292         RELEASE_LOG(MediaStream, "UserMediaRequest::deny - user media disabled");
293         code = SecurityError;
294         break;
295     case MediaAccessDenialReason::NoCaptureDevices:
296         RELEASE_LOG(MediaStream, "UserMediaRequest::deny - no capture devices");
297         code = NotFoundError;
298         break;
299     case MediaAccessDenialReason::InvalidConstraint:
300         RELEASE_LOG(MediaStream, "UserMediaRequest::deny - invalid constraint - %s", message.utf8().data());
301         m_promise.rejectType<IDLInterface<OverconstrainedError>>(OverconstrainedError::create(message, "Invalid constraint"_s).get());
302         return;
303     case MediaAccessDenialReason::HardwareError:
304         RELEASE_LOG(MediaStream, "UserMediaRequest::deny - hardware error");
305         code = NotReadableError;
306         break;
307     case MediaAccessDenialReason::OtherFailure:
308         RELEASE_LOG(MediaStream, "UserMediaRequest::deny - other failure");
309         code = AbortError;
310         break;
311     case MediaAccessDenialReason::PermissionDenied:
312         RELEASE_LOG(MediaStream, "UserMediaRequest::deny - permission denied");
313         code = NotAllowedError;
314         break;
315     case MediaAccessDenialReason::InvalidAccess:
316         RELEASE_LOG(MediaStream, "UserMediaRequest::deny - invalid access");
317         code = InvalidAccessError;
318         break;
319     }
320
321     if (!message.isEmpty())
322         m_promise.reject(code, message);
323     else
324         m_promise.reject(code);
325 }
326
327 void UserMediaRequest::stop()
328 {
329     auto& document = downcast<Document>(*m_scriptExecutionContext);
330     if (auto* controller = UserMediaController::from(document.page()))
331         controller->cancelUserMediaAccessRequest(*this);
332 }
333
334 const char* UserMediaRequest::activeDOMObjectName() const
335 {
336     return "UserMediaRequest";
337 }
338
339 bool UserMediaRequest::canSuspendForDocumentSuspension() const
340 {
341     return !hasPendingActivity();
342 }
343
344 Document* UserMediaRequest::document() const
345 {
346     return downcast<Document>(m_scriptExecutionContext);
347 }
348
349 void UserMediaRequest::mediaStreamDidFail(RealtimeMediaSource::Type type)
350 {
351     RELEASE_LOG(MediaStream, "UserMediaRequest::mediaStreamDidFail");
352     const char* typeDescription = "";
353     switch (type) {
354     case RealtimeMediaSource::Type::Audio:
355         typeDescription = "audio";
356         break;
357     case RealtimeMediaSource::Type::Video:
358         typeDescription = "video";
359         break;
360     case RealtimeMediaSource::Type::None:
361         typeDescription = "unknown";
362         break;
363     }
364     m_promise.reject(NotReadableError, makeString("Failed starting capture of a "_s, typeDescription, " track"_s));
365 }
366
367 } // namespace WebCore
368
369 #endif // ENABLE(MEDIA_STREAM)