1 | /* |
2 | * Copyright (C) 2016-2018 Apple Inc. All rights reserved. |
3 | * |
4 | * This library is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU Lesser General Public |
6 | * License as published by the Free Software Foundation; either |
7 | * version 2 of the License, or (at your option) any later version. |
8 | * |
9 | * This library is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | * Lesser General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU Lesser General Public |
15 | * License along with this library; if not, write to the Free Software |
16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
17 | */ |
18 | |
19 | #include "config.h" |
20 | #include "UserMediaProcessManager.h" |
21 | |
22 | #if ENABLE(MEDIA_STREAM) |
23 | |
24 | #include "Logging.h" |
25 | #include "MediaDeviceSandboxExtensions.h" |
26 | #include "WebPageProxy.h" |
27 | #include "WebProcessMessages.h" |
28 | #include "WebProcessProxy.h" |
29 | #include <WebCore/RealtimeMediaSourceCenter.h> |
30 | #include <wtf/HashMap.h> |
31 | #include <wtf/NeverDestroyed.h> |
32 | |
33 | namespace WebKit { |
34 | |
35 | #if ENABLE(SANDBOX_EXTENSIONS) |
36 | static const ASCIILiteral audioExtensionPath { "com.apple.webkit.microphone"_s }; |
37 | static const ASCIILiteral videoExtensionPath { "com.apple.webkit.camera"_s }; |
38 | #endif |
39 | |
40 | static const Seconds deviceChangeDebounceTimerInterval { 200_ms }; |
41 | |
42 | UserMediaProcessManager& UserMediaProcessManager::singleton() |
43 | { |
44 | static NeverDestroyed<UserMediaProcessManager> manager; |
45 | return manager; |
46 | } |
47 | |
48 | UserMediaProcessManager::UserMediaProcessManager() |
49 | : m_debounceTimer(RunLoop::main(), this, &UserMediaProcessManager::captureDevicesChanged) |
50 | { |
51 | } |
52 | |
53 | void UserMediaProcessManager::muteCaptureMediaStreamsExceptIn(WebPageProxy& pageStartingCapture) |
54 | { |
55 | #if PLATFORM(COCOA) |
56 | UserMediaPermissionRequestManagerProxy::forEach([&pageStartingCapture](auto& proxy) { |
57 | if (&proxy.page() != &pageStartingCapture) |
58 | proxy.page().setMediaStreamCaptureMuted(true); |
59 | }); |
60 | #else |
61 | UNUSED_PARAM(pageStartingCapture); |
62 | #endif |
63 | } |
64 | |
65 | bool UserMediaProcessManager::willCreateMediaStream(UserMediaPermissionRequestManagerProxy& proxy, bool withAudio, bool withVideo) |
66 | { |
67 | ASSERT(withAudio || withVideo); |
68 | |
69 | if (m_denyNextRequest) { |
70 | m_denyNextRequest = false; |
71 | return false; |
72 | } |
73 | |
74 | #if ENABLE(SANDBOX_EXTENSIONS) && USE(APPLE_INTERNAL_SDK) |
75 | auto& process = proxy.page().process(); |
76 | size_t extensionCount = 0; |
77 | |
78 | if (withAudio && !process.hasAudioCaptureExtension()) |
79 | extensionCount++; |
80 | else |
81 | withAudio = false; |
82 | |
83 | if (withVideo && !process.hasVideoCaptureExtension()) |
84 | extensionCount++; |
85 | else |
86 | withVideo = false; |
87 | |
88 | if (extensionCount) { |
89 | SandboxExtension::HandleArray handles; |
90 | Vector<String> ids; |
91 | |
92 | if (!proxy.page().preferences().mockCaptureDevicesEnabled()) { |
93 | handles.allocate(extensionCount); |
94 | ids.reserveInitialCapacity(extensionCount); |
95 | |
96 | if (withAudio && SandboxExtension::createHandleForGenericExtension(audioExtensionPath, handles[--extensionCount])) |
97 | ids.append(audioExtensionPath); |
98 | |
99 | if (withVideo && SandboxExtension::createHandleForGenericExtension(videoExtensionPath, handles[--extensionCount])) |
100 | ids.append(videoExtensionPath); |
101 | |
102 | if (ids.size() != handles.size()) { |
103 | WTFLogAlways("Could not create a required sandbox extension, capture will fail!" ); |
104 | return false; |
105 | } |
106 | } |
107 | |
108 | for (const auto& id : ids) |
109 | RELEASE_LOG(WebRTC, "UserMediaProcessManager::willCreateMediaStream - granting extension %s" , id.utf8().data()); |
110 | |
111 | if (withAudio) |
112 | process.grantAudioCaptureExtension(); |
113 | if (withVideo) |
114 | process.grantVideoCaptureExtension(); |
115 | process.send(Messages::WebProcess::GrantUserMediaDeviceSandboxExtensions(MediaDeviceSandboxExtensions(ids, WTFMove(handles))), 0); |
116 | } |
117 | #else |
118 | UNUSED_PARAM(proxy); |
119 | UNUSED_PARAM(withAudio); |
120 | UNUSED_PARAM(withVideo); |
121 | #endif |
122 | |
123 | proxy.page().activateMediaStreamCaptureInPage(); |
124 | |
125 | return true; |
126 | } |
127 | |
128 | void UserMediaProcessManager::revokeSandboxExtensionsIfNeeded(WebProcessProxy& process) |
129 | { |
130 | #if ENABLE(SANDBOX_EXTENSIONS) |
131 | bool hasAudioCapture = false; |
132 | bool hasVideoCapture = false; |
133 | |
134 | UserMediaPermissionRequestManagerProxy::forEach([&hasAudioCapture, &hasVideoCapture, &process](auto& managerProxy) { |
135 | if (&process != &managerProxy.page().process()) |
136 | return; |
137 | hasAudioCapture |= managerProxy.page().isCapturingAudio(); |
138 | hasVideoCapture |= managerProxy.page().isCapturingVideo(); |
139 | }); |
140 | |
141 | if (hasAudioCapture && hasVideoCapture) |
142 | return; |
143 | |
144 | Vector<String> params; |
145 | if (!hasAudioCapture && process.hasAudioCaptureExtension()) { |
146 | params.append(audioExtensionPath); |
147 | process.revokeAudioCaptureExtension(); |
148 | } |
149 | if (!hasVideoCapture && process.hasVideoCaptureExtension()) { |
150 | params.append(videoExtensionPath); |
151 | process.revokeVideoCaptureExtension(); |
152 | } |
153 | |
154 | if (params.isEmpty()) |
155 | return; |
156 | |
157 | for (const auto& id : params) |
158 | RELEASE_LOG(WebRTC, "UserMediaProcessManager::endedCaptureSession - revoking extension %s" , id.utf8().data()); |
159 | |
160 | process.send(Messages::WebProcess::RevokeUserMediaDeviceSandboxExtensions(params), 0); |
161 | #endif |
162 | } |
163 | |
164 | void UserMediaProcessManager::setCaptureEnabled(bool enabled) |
165 | { |
166 | if (enabled == m_captureEnabled) |
167 | return; |
168 | |
169 | m_captureEnabled = enabled; |
170 | |
171 | if (enabled) |
172 | return; |
173 | |
174 | UserMediaPermissionRequestManagerProxy::forEach([](auto& proxy) { |
175 | proxy.stopCapture(); |
176 | }); |
177 | } |
178 | |
179 | void UserMediaProcessManager::captureDevicesChanged() |
180 | { |
181 | UserMediaPermissionRequestManagerProxy::forEach([](auto& proxy) { |
182 | proxy.captureDevicesChanged(); |
183 | }); |
184 | } |
185 | |
186 | void UserMediaProcessManager::beginMonitoringCaptureDevices() |
187 | { |
188 | static std::once_flag onceFlag; |
189 | |
190 | std::call_once(onceFlag, [this] { |
191 | m_captureDevices = WebCore::RealtimeMediaSourceCenter::singleton().getMediaStreamDevices(); |
192 | |
193 | WebCore::RealtimeMediaSourceCenter::singleton().setDevicesChangedObserver([this]() { |
194 | auto oldDevices = WTFMove(m_captureDevices); |
195 | m_captureDevices = WebCore::RealtimeMediaSourceCenter::singleton().getMediaStreamDevices(); |
196 | |
197 | if (m_captureDevices.size() == oldDevices.size()) { |
198 | bool haveChanges = false; |
199 | for (auto &newDevice : m_captureDevices) { |
200 | if (newDevice.type() != WebCore::CaptureDevice::DeviceType::Camera && newDevice.type() != WebCore::CaptureDevice::DeviceType::Microphone) |
201 | continue; |
202 | |
203 | auto index = oldDevices.findMatching([&newDevice] (auto& oldDevice) { |
204 | return newDevice.persistentId() == oldDevice.persistentId() && newDevice.enabled() != oldDevice.enabled(); |
205 | }); |
206 | |
207 | if (index == notFound) { |
208 | haveChanges = true; |
209 | break; |
210 | } |
211 | } |
212 | |
213 | if (!haveChanges) |
214 | return; |
215 | } |
216 | |
217 | // When a device with camera and microphone is attached or detached, the CaptureDevice notification for |
218 | // the different devices won't arrive at the same time so delay a bit so we can coalesce the callbacks. |
219 | if (!m_debounceTimer.isActive()) |
220 | m_debounceTimer.startOneShot(deviceChangeDebounceTimerInterval); |
221 | }); |
222 | }); |
223 | } |
224 | |
225 | } // namespace WebKit |
226 | |
227 | #endif |
228 | |