[WebIDL] Add support for converting dictionaries to JS
[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-2016 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 "DocumentLoader.h"
40 #include "ExceptionCode.h"
41 #include "Frame.h"
42 #include "JSMediaStream.h"
43 #include "JSOverconstrainedError.h"
44 #include "MainFrame.h"
45 #include "MediaStream.h"
46 #include "MediaStreamPrivate.h"
47 #include "OverconstrainedError.h"
48 #include "RealtimeMediaSourceCenter.h"
49 #include "SecurityOrigin.h"
50 #include "Settings.h"
51 #include "UserMediaController.h"
52 #include <wtf/MainThread.h>
53
54 namespace WebCore {
55
56 ExceptionOr<void> UserMediaRequest::start(Document& document, Ref<MediaConstraintsImpl>&& audioConstraints, Ref<MediaConstraintsImpl>&& videoConstraints, MediaDevices::Promise&& promise)
57 {
58     auto* userMedia = UserMediaController::from(document.page());
59     if (!userMedia)
60         return Exception { NOT_SUPPORTED_ERR }; // FIXME: Why is it better to return an exception here instead of rejecting the promise as we do just below?
61
62     if (!audioConstraints->isValid() && !videoConstraints->isValid()) {
63         promise.reject(TypeError);
64         return { };
65     }
66
67     adoptRef(*new UserMediaRequest(document, *userMedia, WTFMove(audioConstraints), WTFMove(videoConstraints), WTFMove(promise)))->start();
68     return { };
69 }
70
71 UserMediaRequest::UserMediaRequest(Document& document, UserMediaController& controller, Ref<MediaConstraintsImpl>&& audioConstraints, Ref<MediaConstraintsImpl>&& videoConstraints, MediaDevices::Promise&& promise)
72     : ContextDestructionObserver(&document)
73     , m_audioConstraints(WTFMove(audioConstraints))
74     , m_videoConstraints(WTFMove(videoConstraints))
75     , m_controller(&controller)
76     , m_promise(WTFMove(promise))
77 {
78 }
79
80 UserMediaRequest::~UserMediaRequest()
81 {
82 }
83
84 SecurityOrigin* UserMediaRequest::userMediaDocumentOrigin() const
85 {
86     if (!m_scriptExecutionContext)
87         return nullptr;
88
89     return m_scriptExecutionContext->securityOrigin();
90 }
91
92 SecurityOrigin* UserMediaRequest::topLevelDocumentOrigin() const
93 {
94     if (!m_scriptExecutionContext)
95         return nullptr;
96
97     return m_scriptExecutionContext->topOrigin();
98 }
99
100 static bool isSecure(DocumentLoader& documentLoader)
101 {
102     if (!documentLoader.response().url().protocolIs("https"))
103         return false;
104
105     if (!documentLoader.response().certificateInfo() || documentLoader.response().certificateInfo()->containsNonRootSHA1SignedCertificate())
106         return false;
107
108     return true;
109 }
110
111 static bool canCallGetUserMedia(Document& document, String& errorMessage)
112 {
113     bool requiresSecureConnection = document.frame()->settings().mediaCaptureRequiresSecureConnection();
114     if (requiresSecureConnection && !isSecure(*document.loader())) {
115         errorMessage = "Trying to call getUserMedia from an insecure document.";
116         return false;
117     }
118
119     auto& topDocument = document.topDocument();
120     if (&document != &topDocument) {
121         auto& topOrigin = *topDocument.topOrigin();
122
123         if (!document.securityOrigin()->isSameSchemeHostPort(&topOrigin)) {
124             errorMessage = "Trying to call getUserMedia from a document with a different security origin than its top-level frame.";
125             return false;
126         }
127
128         for (auto* ancestorDocument = document.parentDocument(); ancestorDocument != &topDocument; ancestorDocument = ancestorDocument->parentDocument()) {
129             if (requiresSecureConnection && !isSecure(*ancestorDocument->loader())) {
130                 errorMessage = "Trying to call getUserMedia from a document with an insecure parent frame.";
131                 return false;
132             }
133
134             if (!ancestorDocument->securityOrigin()->isSameSchemeHostPort(&topOrigin)) {
135                 errorMessage = "Trying to call getUserMedia from a document with a different security origin than its top-level frame.";
136                 return false;
137             }
138         }
139     }
140     
141     return true;
142 }
143
144 void UserMediaRequest::start()
145 {
146     if (!m_scriptExecutionContext || !m_controller) {
147         deny(MediaAccessDenialReason::OtherFailure, emptyString());
148         return;
149     }
150
151     Document& document = downcast<Document>(*m_scriptExecutionContext);
152     DOMWindow& window = *document.domWindow();
153
154     // 10.2 - 6.3 Optionally, e.g., based on a previously-established user preference, for security reasons,
155     // or due to platform limitations, jump to the step labeled Permission Failure below.
156     String errorMessage;
157     if (!canCallGetUserMedia(document, errorMessage)) {
158         deny(MediaAccessDenialReason::PermissionDenied, emptyString());
159         window.printErrorMessage(errorMessage);
160         return;
161     }
162
163     m_controller->requestUserMediaAccess(*this);
164 }
165
166 void UserMediaRequest::allow(const String& audioDeviceUID, const String& videoDeviceUID)
167 {
168     m_allowedAudioDeviceUID = audioDeviceUID;
169     m_allowedVideoDeviceUID = videoDeviceUID;
170
171     RefPtr<UserMediaRequest> protectedThis = this;
172     RealtimeMediaSourceCenter::NewMediaStreamHandler callback = [this, protectedThis = WTFMove(protectedThis)](RefPtr<MediaStreamPrivate>&& privateStream) mutable {
173         if (!m_scriptExecutionContext)
174             return;
175
176         if (!privateStream) {
177             deny(MediaAccessDenialReason::HardwareError, emptyString());
178             return;
179         }
180
181         auto stream = MediaStream::create(*m_scriptExecutionContext, WTFMove(privateStream));
182         if (stream->getTracks().isEmpty()) {
183             deny(MediaAccessDenialReason::HardwareError, emptyString());
184             return;
185         }
186
187         for (auto& track : stream->getAudioTracks())
188             track->source().startProducingData();
189
190         for (auto& track : stream->getVideoTracks())
191             track->source().startProducingData();
192         
193         m_promise.resolve(stream);
194     };
195
196     RealtimeMediaSourceCenter::singleton().createMediaStream(WTFMove(callback), m_allowedAudioDeviceUID, m_allowedVideoDeviceUID, &m_audioConstraints.get(), &m_videoConstraints.get());
197 }
198
199 void UserMediaRequest::deny(MediaAccessDenialReason reason, const String& invalidConstraint)
200 {
201     if (!m_scriptExecutionContext)
202         return;
203
204     switch (reason) {
205     case MediaAccessDenialReason::NoConstraints:
206         m_promise.reject(TypeError);
207         break;
208     case MediaAccessDenialReason::UserMediaDisabled:
209         m_promise.reject(SECURITY_ERR);
210         break;
211     case MediaAccessDenialReason::NoCaptureDevices:
212         m_promise.reject(NOT_FOUND_ERR);
213         break;
214     case MediaAccessDenialReason::InvalidConstraint:
215         m_promise.rejectType<IDLInterface<OverconstrainedError>>(OverconstrainedError::create(invalidConstraint, ASCIILiteral("Invalid constraint")).get());
216         break;
217     case MediaAccessDenialReason::HardwareError:
218         m_promise.reject(NotReadableError);
219         break;
220     case MediaAccessDenialReason::OtherFailure:
221         m_promise.reject(ABORT_ERR);
222         break;
223     case MediaAccessDenialReason::PermissionDenied:
224         m_promise.reject(NotAllowedError);
225         break;
226     }
227 }
228
229 void UserMediaRequest::contextDestroyed()
230 {
231     ContextDestructionObserver::contextDestroyed();
232     Ref<UserMediaRequest> protectedThis(*this);
233
234     if (m_controller) {
235         m_controller->cancelUserMediaAccessRequest(*this);
236         m_controller = nullptr;
237     }
238 }
239
240 Document* UserMediaRequest::document() const
241 {
242     if (!m_scriptExecutionContext)
243         return nullptr;
244
245     return downcast<Document>(m_scriptExecutionContext);
246 }
247
248 } // namespace WebCore
249
250 #endif // ENABLE(MEDIA_STREAM)