Clean up some includes to make the build a bit faster: DOMPromise
[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 "JSDOMPromiseDeferred.h"
42 #include "JSMediaStream.h"
43 #include "JSOverconstrainedError.h"
44 #include "Logging.h"
45 #include "MediaConstraints.h"
46 #include "PlatformMediaSessionManager.h"
47 #include "RealtimeMediaSourceCenter.h"
48 #include "SchemeRegistry.h"
49 #include "Settings.h"
50 #include "UserMediaController.h"
51 #include <wtf/Scope.h>
52
53 namespace WebCore {
54
55 Ref<UserMediaRequest> UserMediaRequest::create(Document& document, MediaStreamRequest&& request, DOMPromiseDeferred<IDLInterface<MediaStream>>&& promise)
56 {
57     auto result = adoptRef(*new UserMediaRequest(document, WTFMove(request), WTFMove(promise)));
58     result->suspendIfNeeded();
59     return result;
60 }
61
62 UserMediaRequest::UserMediaRequest(Document& document, MediaStreamRequest&& request, DOMPromiseDeferred<IDLInterface<MediaStream>>&& promise)
63     : ActiveDOMObject(document)
64     , m_identifier(UserMediaRequestIdentifier::generate())
65     , m_promise(makeUniqueRef<DOMPromiseDeferred<IDLInterface<MediaStream>>>(WTFMove(promise)))
66     , m_request(WTFMove(request))
67 {
68 }
69
70 UserMediaRequest::~UserMediaRequest() = default;
71
72 SecurityOrigin* UserMediaRequest::userMediaDocumentOrigin() const
73 {
74     if (!m_scriptExecutionContext)
75         return nullptr;
76     return m_scriptExecutionContext->securityOrigin();
77 }
78
79 SecurityOrigin* UserMediaRequest::topLevelDocumentOrigin() const
80 {
81     if (!m_scriptExecutionContext)
82         return nullptr;
83     return &m_scriptExecutionContext->topOrigin();
84 }
85
86 static bool hasInvalidGetDisplayMediaConstraint(const MediaConstraints& constraints)
87 {
88     // https://w3c.github.io/mediacapture-screen-share/#navigator-additions
89     // 1. Let constraints be the method's first argument.
90     // 2. For each member present in constraints whose value, value, is a dictionary, run the following steps:
91     //     1. If value contains a member named advanced, return a promise rejected with a newly created TypeError.
92     //     2. If value contains a member which in turn is a dictionary containing a member named either min or
93     //        exact, return a promise rejected with a newly created TypeError.
94     if (!constraints.isValid)
95         return false;
96
97     if (!constraints.advancedConstraints.isEmpty())
98         return true;
99
100     bool invalid = false;
101     constraints.mandatoryConstraints.filter([&invalid] (const MediaConstraint& constraint) mutable {
102         switch (constraint.constraintType()) {
103         case MediaConstraintType::Width:
104         case MediaConstraintType::Height: {
105             auto& intConstraint = downcast<IntConstraint>(constraint);
106             int value;
107             invalid = intConstraint.getExact(value) || intConstraint.getMin(value);
108             break;
109         }
110
111         case MediaConstraintType::AspectRatio:
112         case MediaConstraintType::FrameRate: {
113             auto& doubleConstraint = downcast<DoubleConstraint>(constraint);
114             double value;
115             invalid = doubleConstraint.getExact(value) || doubleConstraint.getMin(value);
116             break;
117         }
118
119         case MediaConstraintType::DisplaySurface:
120         case MediaConstraintType::LogicalSurface: {
121             auto& boolConstraint = downcast<BooleanConstraint>(constraint);
122             bool value;
123             invalid = boolConstraint.getExact(value);
124             break;
125         }
126
127         case MediaConstraintType::FacingMode:
128         case MediaConstraintType::DeviceId:
129         case MediaConstraintType::GroupId: {
130             auto& stringConstraint = downcast<StringConstraint>(constraint);
131             Vector<String> values;
132             invalid = stringConstraint.getExact(values);
133             break;
134         }
135
136         case MediaConstraintType::SampleRate:
137         case MediaConstraintType::SampleSize:
138         case MediaConstraintType::Volume:
139         case MediaConstraintType::EchoCancellation:
140             // Ignored.
141             break;
142
143         case MediaConstraintType::Unknown:
144             ASSERT_NOT_REACHED();
145             break;
146         }
147
148         return invalid;
149     });
150
151     return invalid;
152 }
153
154 void UserMediaRequest::start()
155 {
156     ASSERT(m_scriptExecutionContext);
157     if (!m_scriptExecutionContext) {
158         deny(MediaAccessDenialReason::UserMediaDisabled);
159         return;
160     }
161
162     if (m_request.type == MediaStreamRequest::Type::DisplayMedia) {
163         if (hasInvalidGetDisplayMediaConstraint(m_request.videoConstraints)) {
164             deny(MediaAccessDenialReason::IllegalConstraint);
165             return;
166         }
167     }
168
169     // https://w3c.github.io/mediacapture-main/getusermedia.html#dom-mediadevices-getusermedia()
170     // 1. Let constraints be the method's first argument.
171     // 2. Let requestedMediaTypes be the set of media types in constraints with either a dictionary
172     //    value or a value of "true".
173     // 3. If requestedMediaTypes is the empty set, return a promise rejected with a TypeError. The word
174     //    "optional" occurs in the WebIDL due to WebIDL rules, but the argument must be supplied in order
175     //    for the call to succeed.
176     if (!m_request.audioConstraints.isValid && !m_request.videoConstraints.isValid) {
177         deny(MediaAccessDenialReason::NoConstraints);
178         return;
179     }
180
181     // 4. If the current settings object's responsible document is NOT allowed to use the feature indicated by
182     //    attribute name allowusermedia, return a promise rejected with a DOMException object whose name
183     //    attribute has the value SecurityError.
184     auto& document = downcast<Document>(*m_scriptExecutionContext);
185     auto* controller = UserMediaController::from(document.page());
186     if (!controller) {
187         deny(MediaAccessDenialReason::UserMediaDisabled);
188         return;
189     }
190
191     // 6.3 Optionally, e.g., based on a previously-established user preference, for security reasons,
192     //     or due to platform limitations, jump to the step labeled Permission Failure below.
193     // ...
194     // 6.10 Permission Failure: Reject p with a new DOMException object whose name attribute has
195     //      the value NotAllowedError.
196
197     OptionSet<UserMediaController::CaptureType> types;
198     UserMediaController::BlockedCaller caller;
199     if (m_request.type == MediaStreamRequest::Type::DisplayMedia) {
200         types.add(UserMediaController::CaptureType::Display);
201         caller = UserMediaController::BlockedCaller::GetDisplayMedia;
202     } else {
203         if (m_request.audioConstraints.isValid)
204             types.add(UserMediaController::CaptureType::Microphone);
205         if (m_request.videoConstraints.isValid)
206             types.add(UserMediaController::CaptureType::Camera);
207         caller = UserMediaController::BlockedCaller::GetUserMedia;
208     }
209     auto access = controller->canCallGetUserMedia(document, types);
210     if (access != UserMediaController::GetUserMediaAccess::CanCall) {
211         deny(MediaAccessDenialReason::PermissionDenied);
212         controller->logGetUserMediaDenial(document, access, caller);
213         return;
214     }
215
216     PlatformMediaSessionManager::sharedManager().prepareToSendUserMediaPermissionRequest();
217     controller->requestUserMediaAccess(*this);
218 }
219
220 static inline bool isMediaStreamCorrectlyStarted(const MediaStream& stream)
221 {
222     if (stream.getTracks().isEmpty())
223         return false;
224
225     return WTF::allOf(stream.getTracks(), [](auto& track) {
226         return !track->source().captureDidFail();
227     });
228 }
229
230 void UserMediaRequest::allow(CaptureDevice&& audioDevice, CaptureDevice&& videoDevice, String&& deviceIdentifierHashSalt, CompletionHandler<void()>&& completionHandler)
231 {
232     RELEASE_LOG(MediaStream, "UserMediaRequest::allow %s %s", audioDevice ? audioDevice.persistentId().utf8().data() : "", videoDevice ? videoDevice.persistentId().utf8().data() : "");
233
234     auto callback = [this, protector = makePendingActivity(*this), completionHandler = WTFMove(completionHandler)](RefPtr<MediaStreamPrivate>&& privateStream) mutable {
235         auto scopeExit = makeScopeExit([completionHandler = WTFMove(completionHandler)]() mutable {
236             completionHandler();
237         });
238         if (isContextStopped())
239             return;
240
241         if (!privateStream) {
242             RELEASE_LOG(MediaStream, "UserMediaRequest::allow failed to create media stream!");
243             deny(MediaAccessDenialReason::HardwareError);
244             return;
245         }
246
247         auto& document = downcast<Document>(*m_scriptExecutionContext);
248         privateStream->monitorOrientation(document.orientationNotifier());
249
250         auto stream = MediaStream::create(document, privateStream.releaseNonNull());
251         stream->startProducingData();
252
253         if (!isMediaStreamCorrectlyStarted(stream)) {
254             deny(MediaAccessDenialReason::HardwareError);
255             return;
256         }
257
258         ASSERT(document.isCapturing());
259         stream->document()->setHasCaptureMediaStreamTrack();
260         m_promise->resolve(WTFMove(stream));
261     };
262
263     auto& document = downcast<Document>(*scriptExecutionContext());
264     document.setDeviceIDHashSalt(deviceIdentifierHashSalt);
265
266     RealtimeMediaSourceCenter::singleton().createMediaStream(document.logger(), WTFMove(callback), WTFMove(deviceIdentifierHashSalt), WTFMove(audioDevice), WTFMove(videoDevice), m_request);
267
268     if (!m_scriptExecutionContext)
269         return;
270
271 #if ENABLE(WEB_RTC)
272     if (auto* page = document.page())
273         page->rtcController().disableICECandidateFilteringForDocument(document);
274 #endif
275 }
276
277 void UserMediaRequest::deny(MediaAccessDenialReason reason, const String& message)
278 {
279     if (!m_scriptExecutionContext)
280         return;
281
282     ExceptionCode code;
283     switch (reason) {
284     case MediaAccessDenialReason::IllegalConstraint:
285         RELEASE_LOG(MediaStream, "UserMediaRequest::deny - invalid constraints");
286         code = TypeError;
287         break;
288     case MediaAccessDenialReason::NoConstraints:
289         RELEASE_LOG(MediaStream, "UserMediaRequest::deny - no constraints");
290         code = TypeError;
291         break;
292     case MediaAccessDenialReason::UserMediaDisabled:
293         RELEASE_LOG(MediaStream, "UserMediaRequest::deny - user media disabled");
294         code = SecurityError;
295         break;
296     case MediaAccessDenialReason::NoCaptureDevices:
297         RELEASE_LOG(MediaStream, "UserMediaRequest::deny - no capture devices");
298         code = NotFoundError;
299         break;
300     case MediaAccessDenialReason::InvalidConstraint:
301         RELEASE_LOG(MediaStream, "UserMediaRequest::deny - invalid constraint - %s", message.utf8().data());
302         m_promise->rejectType<IDLInterface<OverconstrainedError>>(OverconstrainedError::create(message, "Invalid constraint"_s).get());
303         return;
304     case MediaAccessDenialReason::HardwareError:
305         RELEASE_LOG(MediaStream, "UserMediaRequest::deny - hardware error");
306         code = NotReadableError;
307         break;
308     case MediaAccessDenialReason::OtherFailure:
309         RELEASE_LOG(MediaStream, "UserMediaRequest::deny - other failure");
310         code = AbortError;
311         break;
312     case MediaAccessDenialReason::PermissionDenied:
313         RELEASE_LOG(MediaStream, "UserMediaRequest::deny - permission denied");
314         code = NotAllowedError;
315         break;
316     case MediaAccessDenialReason::InvalidAccess:
317         RELEASE_LOG(MediaStream, "UserMediaRequest::deny - invalid access");
318         code = InvalidAccessError;
319         break;
320     }
321
322     if (!message.isEmpty())
323         m_promise->reject(code, message);
324     else
325         m_promise->reject(code);
326 }
327
328 void UserMediaRequest::stop()
329 {
330     auto& document = downcast<Document>(*m_scriptExecutionContext);
331     if (auto* controller = UserMediaController::from(document.page()))
332         controller->cancelUserMediaAccessRequest(*this);
333 }
334
335 const char* UserMediaRequest::activeDOMObjectName() const
336 {
337     return "UserMediaRequest";
338 }
339
340 bool UserMediaRequest::canSuspendForDocumentSuspension() const
341 {
342     return !hasPendingActivity();
343 }
344
345 Document* UserMediaRequest::document() const
346 {
347     return downcast<Document>(m_scriptExecutionContext);
348 }
349
350 void UserMediaRequest::mediaStreamDidFail(RealtimeMediaSource::Type type)
351 {
352     RELEASE_LOG(MediaStream, "UserMediaRequest::mediaStreamDidFail");
353     const char* typeDescription = "";
354     switch (type) {
355     case RealtimeMediaSource::Type::Audio:
356         typeDescription = "audio";
357         break;
358     case RealtimeMediaSource::Type::Video:
359         typeDescription = "video";
360         break;
361     case RealtimeMediaSource::Type::None:
362         typeDescription = "unknown";
363         break;
364     }
365     m_promise->reject(NotReadableError, makeString("Failed starting capture of a "_s, typeDescription, " track"_s));
366 }
367
368 } // namespace WebCore
369
370 #endif // ENABLE(MEDIA_STREAM)