Rollout r231818, as it introduced regression on tickets.com.
[WebKit-https.git] / Source / WebKit / UIProcess / UserMediaPermissionRequestManagerProxy.cpp
1 /*
2  * Copyright (C) 2014 Igalia S.L.
3  * Copyright (C) 2016-2017 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 "UserMediaPermissionRequestManagerProxy.h"
22
23 #include "APISecurityOrigin.h"
24 #include "APIUIClient.h"
25 #include "UserMediaProcessManager.h"
26 #include "WebAutomationSession.h"
27 #include "WebPageMessages.h"
28 #include "WebPageProxy.h"
29 #include "WebProcessPool.h"
30 #include "WebProcessProxy.h"
31 #include <WebCore/MediaConstraints.h>
32 #include <WebCore/MockRealtimeMediaSourceCenter.h>
33 #include <WebCore/RealtimeMediaSource.h>
34 #include <WebCore/SecurityOriginData.h>
35 #include <WebCore/UserMediaRequest.h>
36
37 using namespace WebCore;
38
39 namespace WebKit {
40
41 #if ENABLE(MEDIA_STREAM)
42 static const MediaProducer::MediaStateFlags activeCaptureMask = MediaProducer::HasActiveAudioCaptureDevice | MediaProducer::HasActiveVideoCaptureDevice;
43 #endif
44
45 UserMediaPermissionRequestManagerProxy::UserMediaPermissionRequestManagerProxy(WebPageProxy& page)
46     : m_page(page)
47     , m_rejectionTimer(RunLoop::main(), this, &UserMediaPermissionRequestManagerProxy::rejectionTimerFired)
48     , m_watchdogTimer(RunLoop::main(), this, &UserMediaPermissionRequestManagerProxy::watchdogTimerFired)
49 {
50 #if ENABLE(MEDIA_STREAM)
51     UserMediaProcessManager::singleton().addUserMediaPermissionRequestManagerProxy(*this);
52 #endif
53 }
54
55 UserMediaPermissionRequestManagerProxy::~UserMediaPermissionRequestManagerProxy()
56 {
57 #if ENABLE(MEDIA_STREAM)
58     UserMediaProcessManager::singleton().removeUserMediaPermissionRequestManagerProxy(*this);
59 #endif
60     invalidatePendingRequests();
61 }
62
63 void UserMediaPermissionRequestManagerProxy::invalidatePendingRequests()
64 {
65     for (auto& request : m_pendingUserMediaRequests.values())
66         request->invalidate();
67     m_pendingUserMediaRequests.clear();
68
69     for (auto& request : m_pendingDeviceRequests.values())
70         request->invalidate();
71     m_pendingDeviceRequests.clear();
72 }
73
74 void UserMediaPermissionRequestManagerProxy::stopCapture()
75 {
76     invalidatePendingRequests();
77     m_page.stopMediaCapture();
78 }
79
80 void UserMediaPermissionRequestManagerProxy::clearCachedState()
81 {
82     invalidatePendingRequests();
83 }
84
85 Ref<UserMediaPermissionRequestProxy> UserMediaPermissionRequestManagerProxy::createPermissionRequest(uint64_t userMediaID, uint64_t mainFrameID, uint64_t frameID, Ref<SecurityOrigin>&& userMediaDocumentOrigin, Ref<SecurityOrigin>&& topLevelDocumentOrigin, Vector<CaptureDevice>&& audioDevices, Vector<CaptureDevice>&& videoDevices, String&& deviceIDHashSalt, MediaStreamRequest&& request)
86 {
87     auto permissionRequest = UserMediaPermissionRequestProxy::create(*this, userMediaID, mainFrameID, frameID, WTFMove(userMediaDocumentOrigin), WTFMove(topLevelDocumentOrigin), WTFMove(audioDevices), WTFMove(videoDevices), WTFMove(deviceIDHashSalt), WTFMove(request));
88     m_pendingUserMediaRequests.add(userMediaID, permissionRequest.ptr());
89     return permissionRequest;
90 }
91
92 #if ENABLE(MEDIA_STREAM)
93 static uint64_t toWebCore(UserMediaPermissionRequestProxy::UserMediaAccessDenialReason reason)
94 {
95     switch (reason) {
96     case UserMediaPermissionRequestProxy::UserMediaAccessDenialReason::NoConstraints:
97         return static_cast<uint64_t>(UserMediaRequest::MediaAccessDenialReason::NoConstraints);
98     case UserMediaPermissionRequestProxy::UserMediaAccessDenialReason::UserMediaDisabled:
99         return static_cast<uint64_t>(UserMediaRequest::MediaAccessDenialReason::UserMediaDisabled);
100     case UserMediaPermissionRequestProxy::UserMediaAccessDenialReason::NoCaptureDevices:
101         return static_cast<uint64_t>(UserMediaRequest::MediaAccessDenialReason::NoCaptureDevices);
102     case UserMediaPermissionRequestProxy::UserMediaAccessDenialReason::InvalidConstraint:
103         return static_cast<uint64_t>(UserMediaRequest::MediaAccessDenialReason::InvalidConstraint);
104     case UserMediaPermissionRequestProxy::UserMediaAccessDenialReason::HardwareError:
105         return static_cast<uint64_t>(UserMediaRequest::MediaAccessDenialReason::HardwareError);
106     case UserMediaPermissionRequestProxy::UserMediaAccessDenialReason::PermissionDenied:
107         return static_cast<uint64_t>(UserMediaRequest::MediaAccessDenialReason::PermissionDenied);
108     case UserMediaPermissionRequestProxy::UserMediaAccessDenialReason::OtherFailure:
109         return static_cast<uint64_t>(UserMediaRequest::MediaAccessDenialReason::OtherFailure);
110     }
111
112     ASSERT_NOT_REACHED();
113     return static_cast<uint64_t>(UserMediaRequest::MediaAccessDenialReason::OtherFailure);
114 }
115 #endif
116
117 void UserMediaPermissionRequestManagerProxy::userMediaAccessWasDenied(uint64_t userMediaID, UserMediaPermissionRequestProxy::UserMediaAccessDenialReason reason)
118 {
119     if (!m_page.isValid())
120         return;
121
122     auto request = m_pendingUserMediaRequests.take(userMediaID);
123     if (!request)
124         return;
125
126     if (reason == UserMediaPermissionRequestProxy::UserMediaAccessDenialReason::PermissionDenied)
127         m_deniedRequests.append(DeniedRequest { request->mainFrameID(), request->userMediaDocumentSecurityOrigin(), request->topLevelDocumentSecurityOrigin(), request->requiresAudioCapture(), request->requiresVideoCapture() });
128
129     denyRequest(userMediaID, reason, emptyString());
130 }
131
132 void UserMediaPermissionRequestManagerProxy::denyRequest(uint64_t userMediaID, UserMediaPermissionRequestProxy::UserMediaAccessDenialReason reason, const String& invalidConstraint)
133 {
134     ASSERT(m_page.isValid());
135
136 #if ENABLE(MEDIA_STREAM)
137     m_page.process().send(Messages::WebPage::UserMediaAccessWasDenied(userMediaID, toWebCore(reason), invalidConstraint), m_page.pageID());
138 #else
139     UNUSED_PARAM(reason);
140     UNUSED_PARAM(invalidConstraint);
141 #endif
142 }
143
144 void UserMediaPermissionRequestManagerProxy::userMediaAccessWasGranted(uint64_t userMediaID, CaptureDevice&& audioDevice, CaptureDevice&& videoDevice)
145 {
146     ASSERT(audioDevice || videoDevice);
147
148     if (!m_page.isValid())
149         return;
150
151 #if ENABLE(MEDIA_STREAM)
152     auto request = m_pendingUserMediaRequests.take(userMediaID);
153     if (!request)
154         return;
155
156     if (grantAccess(userMediaID, WTFMove(audioDevice), WTFMove(videoDevice), request->deviceIdentifierHashSalt()))
157         m_grantedRequests.append(request.releaseNonNull());
158
159 #else
160     UNUSED_PARAM(userMediaID);
161     UNUSED_PARAM(audioDevice);
162     UNUSED_PARAM(videoDevice);
163 #endif
164 }
165
166 #if ENABLE(MEDIA_STREAM)
167 void UserMediaPermissionRequestManagerProxy::resetAccess(uint64_t frameID)
168 {
169     m_grantedRequests.removeAllMatching([frameID](const auto& grantedRequest) {
170         return grantedRequest->mainFrameID() == frameID;
171     });
172     m_pregrantedRequests.clear();
173     m_deniedRequests.clear();
174 }
175
176 const UserMediaPermissionRequestProxy* UserMediaPermissionRequestManagerProxy::searchForGrantedRequest(uint64_t frameID, const SecurityOrigin& userMediaDocumentOrigin, const SecurityOrigin& topLevelDocumentOrigin, bool needsAudio, bool needsVideo) const
177 {
178     if (m_page.isMediaStreamCaptureMuted())
179         return nullptr;
180
181     bool checkForAudio = needsAudio;
182     bool checkForVideo = needsVideo;
183     for (const auto& grantedRequest : m_grantedRequests) {
184         if (grantedRequest->requiresDisplayCapture())
185             continue;
186         if (!grantedRequest->userMediaDocumentSecurityOrigin().isSameSchemeHostPort(userMediaDocumentOrigin))
187             continue;
188         if (!grantedRequest->topLevelDocumentSecurityOrigin().isSameSchemeHostPort(topLevelDocumentOrigin))
189             continue;
190         if (grantedRequest->frameID() != frameID)
191             continue;
192
193         if (grantedRequest->requiresVideoCapture())
194             checkForVideo = false;
195
196         if (grantedRequest->requiresAudioCapture())
197             checkForAudio = false;
198
199         if (checkForVideo || checkForAudio)
200             continue;
201
202         return grantedRequest.ptr();
203     }
204     return nullptr;
205 }
206
207 bool UserMediaPermissionRequestManagerProxy::wasRequestDenied(uint64_t mainFrameID, const SecurityOrigin& userMediaDocumentOrigin, const SecurityOrigin& topLevelDocumentOrigin, bool needsAudio, bool needsVideo)
208 {
209     for (const auto& deniedRequest : m_deniedRequests) {
210         if (!deniedRequest.userMediaDocumentOrigin->isSameSchemeHostPort(userMediaDocumentOrigin))
211             continue;
212         if (!deniedRequest.topLevelDocumentOrigin->isSameSchemeHostPort(topLevelDocumentOrigin))
213             continue;
214         if (deniedRequest.mainFrameID != mainFrameID)
215             continue;
216         if (deniedRequest.isAudioDenied && needsAudio)
217             return true;
218         if (deniedRequest.isVideoDenied && needsVideo)
219             return true;
220     }
221     return false;
222 }
223
224 bool UserMediaPermissionRequestManagerProxy::grantAccess(uint64_t userMediaID, const CaptureDevice audioDevice, const CaptureDevice videoDevice, const String& deviceIdentifierHashSalt)
225 {
226     if (!UserMediaProcessManager::singleton().willCreateMediaStream(*this, !!audioDevice, !!videoDevice)) {
227         denyRequest(userMediaID, UserMediaPermissionRequestProxy::UserMediaAccessDenialReason::OtherFailure, "Unable to extend sandbox.");
228         return false;
229     }
230
231     m_page.process().send(Messages::WebPage::UserMediaAccessWasGranted(userMediaID, audioDevice, videoDevice, deviceIdentifierHashSalt), m_page.pageID());
232     return true;
233 }
234 #endif
235
236 void UserMediaPermissionRequestManagerProxy::rejectionTimerFired()
237 {
238     uint64_t userMediaID = m_pendingRejections[0];
239     m_pendingRejections.remove(0);
240
241     denyRequest(userMediaID, UserMediaPermissionRequestProxy::UserMediaAccessDenialReason::PermissionDenied, emptyString());
242     if (!m_pendingRejections.isEmpty())
243         scheduleNextRejection();
244 }
245
246 void UserMediaPermissionRequestManagerProxy::scheduleNextRejection()
247 {
248     const double mimimumDelayBeforeReplying = .25;
249     if (!m_rejectionTimer.isActive())
250         m_rejectionTimer.startOneShot(Seconds(mimimumDelayBeforeReplying + randomNumber()));
251 }
252
253 void UserMediaPermissionRequestManagerProxy::requestUserMediaPermissionForFrame(uint64_t userMediaID, uint64_t frameID, Ref<SecurityOrigin>&& userMediaDocumentOrigin, Ref<SecurityOrigin>&& topLevelDocumentOrigin, const MediaStreamRequest& userRequest)
254 {
255 #if ENABLE(MEDIA_STREAM)
256     if (!UserMediaProcessManager::singleton().captureEnabled()) {
257         m_pendingRejections.append(userMediaID);
258         scheduleNextRejection();
259         return;
260     }
261
262     RealtimeMediaSourceCenter::InvalidConstraintsHandler invalidHandler = [this, userMediaID](const String& invalidConstraint) {
263         if (!m_page.isValid())
264             return;
265
266         denyRequest(userMediaID, UserMediaPermissionRequestProxy::UserMediaAccessDenialReason::InvalidConstraint, invalidConstraint);
267     };
268
269     auto validHandler = [this, userMediaID, frameID, userMediaDocumentOrigin = userMediaDocumentOrigin.copyRef(), topLevelDocumentOrigin = topLevelDocumentOrigin.copyRef(), localUserRequest = userRequest](Vector<CaptureDevice>&& audioDevices, Vector<CaptureDevice>&& videoDevices, String&& deviceIdentifierHashSalt) mutable {
270         if (!m_page.isValid() || !m_page.mainFrame())
271             return;
272
273         if (videoDevices.isEmpty() && audioDevices.isEmpty()) {
274             denyRequest(userMediaID, UserMediaPermissionRequestProxy::UserMediaAccessDenialReason::NoConstraints, emptyString());
275             return;
276         }
277
278         if (wasRequestDenied(m_page.mainFrame()->frameID(), userMediaDocumentOrigin.get(), topLevelDocumentOrigin.get(), !audioDevices.isEmpty(), !videoDevices.isEmpty())) {
279             denyRequest(userMediaID, UserMediaPermissionRequestProxy::UserMediaAccessDenialReason::PermissionDenied, emptyString());
280             return;
281         }
282
283         auto* grantedRequest = searchForGrantedRequest(frameID, userMediaDocumentOrigin.get(), topLevelDocumentOrigin.get(), !audioDevices.isEmpty(), !videoDevices.isEmpty());
284         if (grantedRequest) {
285             if (m_page.isViewVisible()) {
286             // We select the first available devices, but the current client API allows client to select which device to pick.
287             // FIXME: Remove the possiblity for the client to do the device selection.
288                 auto audioDevice = !audioDevices.isEmpty() ? audioDevices[0] : CaptureDevice();
289                 auto videoDevice = !videoDevices.isEmpty() ? videoDevices[0] : CaptureDevice();
290                 grantAccess(userMediaID, WTFMove(audioDevice), WTFMove(videoDevice), grantedRequest->deviceIdentifierHashSalt());
291             } else
292                 m_pregrantedRequests.append(createPermissionRequest(userMediaID, m_page.mainFrame()->frameID(), frameID, WTFMove(userMediaDocumentOrigin), WTFMove(topLevelDocumentOrigin), WTFMove(audioDevices), WTFMove(videoDevices), String(grantedRequest->deviceIdentifierHashSalt()), WTFMove(localUserRequest)));
293
294             return;
295         }
296         auto userMediaOrigin = API::SecurityOrigin::create(userMediaDocumentOrigin.get());
297         auto topLevelOrigin = API::SecurityOrigin::create(topLevelDocumentOrigin.get());
298
299         auto pendingRequest = createPermissionRequest(userMediaID, m_page.mainFrame()->frameID(), frameID, WTFMove(userMediaDocumentOrigin), WTFMove(topLevelDocumentOrigin), WTFMove(audioDevices), WTFMove(videoDevices), WTFMove(deviceIdentifierHashSalt), WTFMove(localUserRequest));
300
301         if (m_page.isControlledByAutomation()) {
302             if (WebAutomationSession* automationSession = m_page.process().processPool().automationSession()) {
303                 if (automationSession->shouldAllowGetUserMediaForPage(m_page))
304                     pendingRequest->allow();
305                 else
306                     userMediaAccessWasDenied(userMediaID, UserMediaPermissionRequestProxy::UserMediaAccessDenialReason::PermissionDenied);
307
308                 return;
309             }
310         }
311
312         if (m_page.preferences().mockCaptureDevicesEnabled() && !m_page.preferences().mockCaptureDevicesPromptEnabled()) {
313             pendingRequest->allow();
314             return;
315         }
316
317         if (!m_page.uiClient().decidePolicyForUserMediaPermissionRequest(m_page, *m_page.process().webFrame(frameID), WTFMove(userMediaOrigin), WTFMove(topLevelOrigin), pendingRequest.get()))
318             userMediaAccessWasDenied(userMediaID, UserMediaPermissionRequestProxy::UserMediaAccessDenialReason::UserMediaDisabled);
319     };
320
321     auto haveDeviceSaltHandler = [this, validHandler = WTFMove(validHandler), invalidHandler = WTFMove(invalidHandler), localUserRequest = userRequest](uint64_t userMediaID, String&& deviceIdentifierHashSalt, bool originHasPersistentAccess) mutable {
322
323         auto pendingRequest = m_pendingDeviceRequests.take(userMediaID);
324         if (!pendingRequest)
325             return;
326
327         if (!m_page.isValid())
328             return;
329         
330         localUserRequest.audioConstraints.deviceIDHashSalt = deviceIdentifierHashSalt;
331         localUserRequest.videoConstraints.deviceIDHashSalt = deviceIdentifierHashSalt;
332
333         syncWithWebCorePrefs();
334         
335         RealtimeMediaSourceCenter::singleton().validateRequestConstraints(WTFMove(validHandler), WTFMove(invalidHandler), WTFMove(localUserRequest), WTFMove(deviceIdentifierHashSalt));
336     };
337
338     getUserMediaPermissionInfo(userMediaID, frameID, WTFMove(haveDeviceSaltHandler), WTFMove(userMediaDocumentOrigin), WTFMove(topLevelDocumentOrigin));
339 #else
340     UNUSED_PARAM(userMediaID);
341     UNUSED_PARAM(frameID);
342     UNUSED_PARAM(userMediaDocumentOrigin);
343     UNUSED_PARAM(topLevelDocumentOrigin);
344     UNUSED_PARAM(userRequest);
345 #endif
346 }
347
348 #if ENABLE(MEDIA_STREAM)
349 void UserMediaPermissionRequestManagerProxy::getUserMediaPermissionInfo(uint64_t userMediaID, uint64_t frameID, UserMediaPermissionCheckProxy::CompletionHandler&& handler, Ref<SecurityOrigin>&& userMediaDocumentOrigin, Ref<SecurityOrigin>&& topLevelDocumentOrigin)
350 {
351     auto userMediaOrigin = API::SecurityOrigin::create(userMediaDocumentOrigin.get());
352     auto topLevelOrigin = API::SecurityOrigin::create(topLevelDocumentOrigin.get());
353
354     auto request = UserMediaPermissionCheckProxy::create(userMediaID, frameID, WTFMove(handler), WTFMove(userMediaDocumentOrigin), WTFMove(topLevelDocumentOrigin));
355     m_pendingDeviceRequests.add(userMediaID, request.copyRef());
356
357     if (!m_page.uiClient().checkUserMediaPermissionForOrigin(m_page, *m_page.process().webFrame(frameID), userMediaOrigin.get(), topLevelOrigin.get(), request.get()))
358         request->completionHandler()(userMediaID, String(), false);
359
360 #endif
361
362 void UserMediaPermissionRequestManagerProxy::enumerateMediaDevicesForFrame(uint64_t userMediaID, uint64_t frameID, Ref<SecurityOrigin>&& userMediaDocumentOrigin, Ref<SecurityOrigin>&& topLevelDocumentOrigin)
363 {
364 #if ENABLE(MEDIA_STREAM)
365     auto completionHandler = [this](uint64_t userMediaID, String&& deviceIdentifierHashSalt, bool originHasPersistentAccess) {
366         auto request = m_pendingDeviceRequests.take(userMediaID);
367         if (!request)
368             return;
369
370         if (!m_page.isValid())
371             return;
372
373         syncWithWebCorePrefs();
374
375         auto devices = RealtimeMediaSourceCenter::singleton().getMediaStreamDevices();
376         devices.removeAllMatching([&](auto& device) -> bool {
377             return !device.enabled() || (device.type() != WebCore::CaptureDevice::DeviceType::Camera && device.type() != WebCore::CaptureDevice::DeviceType::Microphone);
378         });
379
380         m_page.process().send(Messages::WebPage::DidCompleteMediaDeviceEnumeration(userMediaID, devices, deviceIdentifierHashSalt, originHasPersistentAccess), m_page.pageID());
381     };
382
383     getUserMediaPermissionInfo(userMediaID, frameID, WTFMove(completionHandler), WTFMove(userMediaDocumentOrigin), WTFMove(topLevelDocumentOrigin));
384 #else
385     UNUSED_PARAM(userMediaID);
386     UNUSED_PARAM(frameID);
387     UNUSED_PARAM(userMediaDocumentOrigin);
388     UNUSED_PARAM(topLevelDocumentOrigin);
389 #endif
390 }
391
392 void UserMediaPermissionRequestManagerProxy::syncWithWebCorePrefs() const
393 {
394 #if ENABLE(MEDIA_STREAM)
395     // Enable/disable the mock capture devices for the UI process as per the WebCore preferences. Note that
396     // this is a noop if the preference hasn't changed since the last time this was called.
397     bool mockDevicesEnabled = m_page.preferences().mockCaptureDevicesEnabled();
398     MockRealtimeMediaSourceCenter::setMockRealtimeMediaSourceCenterEnabled(mockDevicesEnabled);
399 #endif
400 }
401
402 void UserMediaPermissionRequestManagerProxy::captureStateChanged(MediaProducer::MediaStateFlags oldState, MediaProducer::MediaStateFlags newState)
403 {
404     if (!m_page.isValid())
405         return;
406
407 #if ENABLE(MEDIA_STREAM)
408     bool wasCapturingAudio = oldState & MediaProducer::AudioCaptureMask;
409     bool wasCapturingVideo = oldState & MediaProducer::VideoCaptureMask;
410     bool isCapturingAudio = newState & MediaProducer::AudioCaptureMask;
411     bool isCapturingVideo = newState & MediaProducer::VideoCaptureMask;
412
413     if ((wasCapturingAudio && !isCapturingAudio) || (wasCapturingVideo && !isCapturingVideo))
414         UserMediaProcessManager::singleton().endedCaptureSession(*this);
415     if ((!wasCapturingAudio && isCapturingAudio) || (!wasCapturingVideo && isCapturingVideo))
416         UserMediaProcessManager::singleton().startedCaptureSession(*this);
417
418     if (m_captureState == (newState & activeCaptureMask))
419         return;
420
421     m_captureState = newState & activeCaptureMask;
422
423     Seconds interval;
424     if (m_captureState & activeCaptureMask)
425         interval = Seconds::fromHours(m_page.preferences().longRunningMediaCaptureStreamRepromptIntervalInHours());
426     else
427         interval = Seconds::fromMinutes(m_page.preferences().inactiveMediaCaptureSteamRepromptIntervalInMinutes());
428
429     if (interval == m_currentWatchdogInterval)
430         return;
431
432     m_currentWatchdogInterval = interval;
433     m_watchdogTimer.startOneShot(m_currentWatchdogInterval);
434 #endif
435 }
436
437 void UserMediaPermissionRequestManagerProxy::processPregrantedRequests()
438 {
439     for (auto& request : m_pregrantedRequests)
440         request->allow();
441     m_pregrantedRequests.clear();
442 }
443
444 void UserMediaPermissionRequestManagerProxy::watchdogTimerFired()
445 {
446     m_grantedRequests.clear();
447     m_pregrantedRequests.clear();
448     m_currentWatchdogInterval = 0_s;
449 }
450
451 } // namespace WebKit