1/*
2 * Copyright (C) 2014 Igalia S.L.
3 * Copyright (C) 2016-2018 Apple Inc. All rights reserved.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#include "config.h"
21#include "UserMediaPermissionRequestManager.h"
22
23#if ENABLE(MEDIA_STREAM)
24
25#include "Logging.h"
26#include "WebCoreArgumentCoders.h"
27#include "WebFrame.h"
28#include "WebPage.h"
29#include "WebPageProxyMessages.h"
30#include <WebCore/CaptureDevice.h>
31#include <WebCore/Document.h>
32#include <WebCore/Frame.h>
33#include <WebCore/FrameLoader.h>
34#include <WebCore/MediaConstraints.h>
35#include <WebCore/SecurityOrigin.h>
36#include <WebCore/SecurityOriginData.h>
37
38namespace WebKit {
39using namespace WebCore;
40
41static constexpr OptionSet<WebCore::ActivityState::Flag> focusedActiveWindow = { WebCore::ActivityState::IsFocused, WebCore::ActivityState::WindowIsActive };
42
43static uint64_t generateRequestID()
44{
45 static uint64_t uniqueRequestID = 1;
46 return uniqueRequestID++;
47}
48
49UserMediaPermissionRequestManager::UserMediaPermissionRequestManager(WebPage& page)
50 : m_page(page)
51{
52}
53
54void UserMediaPermissionRequestManager::startUserMediaRequest(UserMediaRequest& request)
55{
56 Document* document = request.document();
57 Frame* frame = document ? document->frame() : nullptr;
58
59 if (!frame || !document->page()) {
60 request.deny(UserMediaRequest::OtherFailure, emptyString());
61 return;
62 }
63
64 if (document->page()->canStartMedia()) {
65 sendUserMediaRequest(request);
66 return;
67 }
68
69 auto& pendingRequests = m_blockedUserMediaRequests.add(document, Vector<RefPtr<UserMediaRequest>>()).iterator->value;
70 if (pendingRequests.isEmpty())
71 document->addMediaCanStartListener(*this);
72 pendingRequests.append(&request);
73}
74
75void UserMediaPermissionRequestManager::sendUserMediaRequest(UserMediaRequest& userRequest)
76{
77 auto* frame = userRequest.document() ? userRequest.document()->frame() : nullptr;
78 if (!frame) {
79 userRequest.deny(UserMediaRequest::OtherFailure, emptyString());
80 return;
81 }
82
83 uint64_t requestID = generateRequestID();
84 m_idToUserMediaRequestMap.add(requestID, &userRequest);
85 m_userMediaRequestToIDMap.add(&userRequest, requestID);
86
87 WebFrame* webFrame = WebFrame::fromCoreFrame(*frame);
88 ASSERT(webFrame);
89
90 auto* topLevelDocumentOrigin = userRequest.topLevelDocumentOrigin();
91 m_page.send(Messages::WebPageProxy::RequestUserMediaPermissionForFrame(requestID, webFrame->frameID(), userRequest.userMediaDocumentOrigin()->data(), topLevelDocumentOrigin->data(), userRequest.request()));
92}
93
94void UserMediaPermissionRequestManager::cancelUserMediaRequest(UserMediaRequest& request)
95{
96 uint64_t requestID = m_userMediaRequestToIDMap.take(&request);
97 if (!requestID)
98 return;
99
100 request.deny(UserMediaRequest::OtherFailure, emptyString());
101 m_idToUserMediaRequestMap.remove(requestID);
102 removeMediaRequestFromMaps(request);
103}
104
105void UserMediaPermissionRequestManager::mediaCanStart(Document& document)
106{
107 auto pendingRequests = m_blockedUserMediaRequests.take(&document);
108 while (!pendingRequests.isEmpty()) {
109 if (!document.page()->canStartMedia()) {
110 m_blockedUserMediaRequests.add(&document, pendingRequests);
111 document.addMediaCanStartListener(*this);
112 break;
113 }
114
115 sendUserMediaRequest(*pendingRequests.takeLast());
116 }
117}
118
119void UserMediaPermissionRequestManager::removeMediaRequestFromMaps(UserMediaRequest& request)
120{
121 Document* document = request.document();
122 if (!document)
123 return;
124
125 auto pendingRequests = m_blockedUserMediaRequests.take(document);
126 for (auto& pendingRequest : pendingRequests) {
127 if (&request != pendingRequest.get())
128 continue;
129
130 if (pendingRequests.isEmpty())
131 request.document()->removeMediaCanStartListener(*this);
132 else
133 m_blockedUserMediaRequests.add(document, pendingRequests);
134 break;
135 }
136
137 m_userMediaRequestToIDMap.remove(&request);
138}
139
140void UserMediaPermissionRequestManager::userMediaAccessWasGranted(uint64_t requestID, CaptureDevice&& audioDevice, CaptureDevice&& videoDevice, String&& deviceIdentifierHashSalt, CompletionHandler<void()>&& completionHandler)
141{
142 auto request = m_idToUserMediaRequestMap.take(requestID);
143 if (!request)
144 return;
145 removeMediaRequestFromMaps(*request);
146
147 request->allow(WTFMove(audioDevice), WTFMove(videoDevice), WTFMove(deviceIdentifierHashSalt), WTFMove(completionHandler));
148}
149
150void UserMediaPermissionRequestManager::userMediaAccessWasDenied(uint64_t requestID, WebCore::UserMediaRequest::MediaAccessDenialReason reason, String&& invalidConstraint)
151{
152 auto request = m_idToUserMediaRequestMap.take(requestID);
153 if (!request)
154 return;
155 removeMediaRequestFromMaps(*request);
156
157 request->deny(reason, WTFMove(invalidConstraint));
158}
159
160void UserMediaPermissionRequestManager::enumerateMediaDevices(MediaDevicesEnumerationRequest& request)
161{
162 auto* document = downcast<Document>(request.scriptExecutionContext());
163 auto* frame = document ? document->frame() : nullptr;
164
165 if (!frame) {
166 request.setDeviceInfo(Vector<CaptureDevice>(), emptyString(), false);
167 return;
168 }
169
170 uint64_t requestID = generateRequestID();
171 m_idToMediaDevicesEnumerationRequestMap.add(requestID, &request);
172 m_mediaDevicesEnumerationRequestToIDMap.add(&request, requestID);
173
174 WebFrame* webFrame = WebFrame::fromCoreFrame(*frame);
175 ASSERT(webFrame);
176
177 SecurityOrigin* topLevelDocumentOrigin = request.topLevelDocumentOrigin();
178 ASSERT(topLevelDocumentOrigin);
179 m_page.send(Messages::WebPageProxy::EnumerateMediaDevicesForFrame(requestID, webFrame->frameID(), request.userMediaDocumentOrigin()->data(), topLevelDocumentOrigin->data()));
180}
181
182void UserMediaPermissionRequestManager::cancelMediaDevicesEnumeration(WebCore::MediaDevicesEnumerationRequest& request)
183{
184 uint64_t requestID = m_mediaDevicesEnumerationRequestToIDMap.take(&request);
185 if (!requestID)
186 return;
187 request.setDeviceInfo(Vector<CaptureDevice>(), emptyString(), false);
188 m_idToMediaDevicesEnumerationRequestMap.remove(requestID);
189}
190
191void UserMediaPermissionRequestManager::didCompleteMediaDeviceEnumeration(uint64_t requestID, const Vector<CaptureDevice>& deviceList, String&& mediaDeviceIdentifierHashSalt, bool hasPersistentAccess)
192{
193 RefPtr<MediaDevicesEnumerationRequest> request = m_idToMediaDevicesEnumerationRequestMap.take(requestID);
194 if (!request)
195 return;
196 m_mediaDevicesEnumerationRequestToIDMap.remove(request);
197
198 request->setDeviceInfo(deviceList, WTFMove(mediaDeviceIdentifierHashSalt), hasPersistentAccess);
199}
200
201UserMediaClient::DeviceChangeObserverToken UserMediaPermissionRequestManager::addDeviceChangeObserver(WTF::Function<void()>&& observer)
202{
203 auto identifier = WebCore::UserMediaClient::DeviceChangeObserverToken::generate();
204 m_deviceChangeObserverMap.add(identifier, WTFMove(observer));
205
206 if (!m_monitoringDeviceChange) {
207 m_monitoringDeviceChange = true;
208 m_page.send(Messages::WebPageProxy::BeginMonitoringCaptureDevices());
209 }
210 return identifier;
211}
212
213void UserMediaPermissionRequestManager::removeDeviceChangeObserver(UserMediaClient::DeviceChangeObserverToken token)
214{
215 bool wasRemoved = m_deviceChangeObserverMap.remove(token);
216 ASSERT_UNUSED(wasRemoved, wasRemoved);
217}
218
219void UserMediaPermissionRequestManager::captureDevicesChanged()
220{
221 // When new media input and/or output devices are made available, or any available input and/or
222 // output device becomes unavailable, the User Agent MUST run the following steps in browsing
223 // contexts where at least one of the following criteria are met, but in no other contexts:
224
225 // * The permission state of the "device-info" permission is "granted",
226 // * any of the input devices are attached to an active MediaStream in the browsing context, or
227 // * the active document is fully active and has focus.
228
229 auto identifiers = m_deviceChangeObserverMap.keys();
230 for (auto& identifier : identifiers) {
231 auto iterator = m_deviceChangeObserverMap.find(identifier);
232 if (iterator != m_deviceChangeObserverMap.end())
233 (iterator->value)();
234 }
235}
236
237} // namespace WebKit
238
239#endif // ENABLE(MEDIA_STREAM)
240