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 completionHandler();
145 return;
146 }
147 removeMediaRequestFromMaps(*request);
148
149 request->allow(WTFMove(audioDevice), WTFMove(videoDevice), WTFMove(deviceIdentifierHashSalt), WTFMove(completionHandler));
150}
151
152void UserMediaPermissionRequestManager::userMediaAccessWasDenied(uint64_t requestID, WebCore::UserMediaRequest::MediaAccessDenialReason reason, String&& invalidConstraint)
153{
154 auto request = m_idToUserMediaRequestMap.take(requestID);
155 if (!request)
156 return;
157 removeMediaRequestFromMaps(*request);
158
159 request->deny(reason, WTFMove(invalidConstraint));
160}
161
162void UserMediaPermissionRequestManager::enumerateMediaDevices(MediaDevicesEnumerationRequest& request)
163{
164 auto* document = downcast<Document>(request.scriptExecutionContext());
165 auto* frame = document ? document->frame() : nullptr;
166
167 if (!frame) {
168 request.setDeviceInfo(Vector<CaptureDevice>(), emptyString(), false);
169 return;
170 }
171
172 uint64_t requestID = generateRequestID();
173 m_idToMediaDevicesEnumerationRequestMap.add(requestID, &request);
174 m_mediaDevicesEnumerationRequestToIDMap.add(&request, requestID);
175
176 WebFrame* webFrame = WebFrame::fromCoreFrame(*frame);
177 ASSERT(webFrame);
178
179 SecurityOrigin* topLevelDocumentOrigin = request.topLevelDocumentOrigin();
180 ASSERT(topLevelDocumentOrigin);
181 m_page.send(Messages::WebPageProxy::EnumerateMediaDevicesForFrame(requestID, webFrame->frameID(), request.userMediaDocumentOrigin()->data(), topLevelDocumentOrigin->data()));
182}
183
184void UserMediaPermissionRequestManager::cancelMediaDevicesEnumeration(WebCore::MediaDevicesEnumerationRequest& request)
185{
186 uint64_t requestID = m_mediaDevicesEnumerationRequestToIDMap.take(&request);
187 if (!requestID)
188 return;
189 request.setDeviceInfo(Vector<CaptureDevice>(), emptyString(), false);
190 m_idToMediaDevicesEnumerationRequestMap.remove(requestID);
191}
192
193void UserMediaPermissionRequestManager::didCompleteMediaDeviceEnumeration(uint64_t requestID, const Vector<CaptureDevice>& deviceList, String&& mediaDeviceIdentifierHashSalt, bool hasPersistentAccess)
194{
195 RefPtr<MediaDevicesEnumerationRequest> request = m_idToMediaDevicesEnumerationRequestMap.take(requestID);
196 if (!request)
197 return;
198 m_mediaDevicesEnumerationRequestToIDMap.remove(request);
199
200 request->setDeviceInfo(deviceList, WTFMove(mediaDeviceIdentifierHashSalt), hasPersistentAccess);
201}
202
203UserMediaClient::DeviceChangeObserverToken UserMediaPermissionRequestManager::addDeviceChangeObserver(WTF::Function<void()>&& observer)
204{
205 auto identifier = WebCore::UserMediaClient::DeviceChangeObserverToken::generate();
206 m_deviceChangeObserverMap.add(identifier, WTFMove(observer));
207
208 if (!m_monitoringDeviceChange) {
209 m_monitoringDeviceChange = true;
210 m_page.send(Messages::WebPageProxy::BeginMonitoringCaptureDevices());
211 }
212 return identifier;
213}
214
215void UserMediaPermissionRequestManager::removeDeviceChangeObserver(UserMediaClient::DeviceChangeObserverToken token)
216{
217 bool wasRemoved = m_deviceChangeObserverMap.remove(token);
218 ASSERT_UNUSED(wasRemoved, wasRemoved);
219}
220
221void UserMediaPermissionRequestManager::captureDevicesChanged()
222{
223 // When new media input and/or output devices are made available, or any available input and/or
224 // output device becomes unavailable, the User Agent MUST run the following steps in browsing
225 // contexts where at least one of the following criteria are met, but in no other contexts:
226
227 // * The permission state of the "device-info" permission is "granted",
228 // * any of the input devices are attached to an active MediaStream in the browsing context, or
229 // * the active document is fully active and has focus.
230
231 auto identifiers = m_deviceChangeObserverMap.keys();
232 for (auto& identifier : identifiers) {
233 auto iterator = m_deviceChangeObserverMap.find(identifier);
234 if (iterator != m_deviceChangeObserverMap.end())
235 (iterator->value)();
236 }
237}
238
239} // namespace WebKit
240
241#endif // ENABLE(MEDIA_STREAM)
242