[iOS] Replace "node assistance" terminology in WebKit with "focused element"
[WebKit-https.git] / Source / WebKit / UIProcess / UserMediaProcessManager.cpp
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 "WebPageMessages.h"
27 #include "WebPageProxy.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 class ProcessState {
43 public:
44     ProcessState() { }
45     ProcessState(const ProcessState&) = delete;
46
47     void addRequestManager(UserMediaPermissionRequestManagerProxy&);
48     void removeRequestManager(UserMediaPermissionRequestManagerProxy&);
49     Vector<UserMediaPermissionRequestManagerProxy*>& managers() { return m_managers; }
50
51     enum SandboxExtensionType : uint32_t {
52         None = 0,
53         Video = 1 << 0,
54         Audio = 1 << 1
55     };
56     typedef uint32_t SandboxExtensionsGranted;
57
58     bool hasVideoExtension() const { return m_pageSandboxExtensionsGranted & Video; }
59     void grantVideoExtension()  { m_pageSandboxExtensionsGranted |= Video; }
60     void revokeVideoExtension()  { m_pageSandboxExtensionsGranted &= ~Video; }
61
62     bool hasAudioExtension() const { return m_pageSandboxExtensionsGranted & Audio; }
63     void grantAudioExtension()  { m_pageSandboxExtensionsGranted |= Audio; }
64     void revokeAudioExtension()  { m_pageSandboxExtensionsGranted &= ~Audio; }
65
66 private:
67
68     Vector<UserMediaPermissionRequestManagerProxy*> m_managers;
69     SandboxExtensionsGranted m_pageSandboxExtensionsGranted { SandboxExtensionType::None };
70 };
71
72 static HashMap<WebProcessProxy*, std::unique_ptr<ProcessState>>& stateMap()
73 {
74     static NeverDestroyed<HashMap<WebProcessProxy*, std::unique_ptr<ProcessState>>> map;
75     return map;
76 }
77
78 static ProcessState& processState(WebProcessProxy& process)
79 {
80     auto& state = stateMap().add(&process, nullptr).iterator->value;
81     if (state)
82         return *state;
83
84     state = std::make_unique<ProcessState>();
85     return *state;
86 }
87
88 void ProcessState::addRequestManager(UserMediaPermissionRequestManagerProxy& proxy)
89 {
90     ASSERT(!m_managers.contains(&proxy));
91     m_managers.append(&proxy);
92 }
93
94 void ProcessState::removeRequestManager(UserMediaPermissionRequestManagerProxy& proxy)
95 {
96     ASSERT(m_managers.contains(&proxy));
97     m_managers.removeFirstMatching([&proxy](auto other) {
98         return other == &proxy;
99     });
100 }
101
102 UserMediaProcessManager& UserMediaProcessManager::singleton()
103 {
104     static NeverDestroyed<UserMediaProcessManager> manager;
105     return manager;
106 }
107
108 UserMediaProcessManager::UserMediaProcessManager()
109     : m_debounceTimer(RunLoop::main(), this, &UserMediaProcessManager::captureDevicesChanged)
110 {
111 }
112
113 void UserMediaProcessManager::addUserMediaPermissionRequestManagerProxy(UserMediaPermissionRequestManagerProxy& proxy)
114 {
115     processState(proxy.page().process()).addRequestManager(proxy);
116 }
117
118 void UserMediaProcessManager::removeUserMediaPermissionRequestManagerProxy(UserMediaPermissionRequestManagerProxy& proxy)
119 {
120     endedCaptureSession(proxy);
121
122     auto& state = processState(proxy.page().process());
123     state.removeRequestManager(proxy);
124     if (state.managers().isEmpty()) {
125         auto it = stateMap().find(&proxy.page().process());
126         stateMap().remove(it);
127     }
128 }
129
130 void UserMediaProcessManager::muteCaptureMediaStreamsExceptIn(WebPageProxy& pageStartingCapture)
131 {
132 #if PLATFORM(COCOA)
133     for (auto& state : stateMap()) {
134         for (auto& manager : state.value->managers()) {
135             if (&manager->page() == &pageStartingCapture)
136                 continue;
137             manager->page().setMediaStreamCaptureMuted(true);
138         }
139     }
140 #else
141     UNUSED_PARAM(pageStartingCapture);
142 #endif
143 }
144
145 bool UserMediaProcessManager::willCreateMediaStream(UserMediaPermissionRequestManagerProxy& proxy, bool withAudio, bool withVideo)
146 {
147     ASSERT(withAudio || withVideo);
148
149     if (m_denyNextRequest) {
150         m_denyNextRequest = false;
151         return false;
152     }
153     
154     if (proxy.page().preferences().mockCaptureDevicesEnabled())
155         return true;
156     
157 #if ENABLE(SANDBOX_EXTENSIONS) && USE(APPLE_INTERNAL_SDK)
158     auto& processStartingCapture = proxy.page().process();
159
160     ASSERT(stateMap().contains(&processStartingCapture));
161
162     auto& state = processState(processStartingCapture);
163     size_t extensionCount = 0;
164
165     if (withAudio && !state.hasAudioExtension())
166         extensionCount++;
167     else
168         withAudio = false;
169
170     if (withVideo && !state.hasVideoExtension())
171         extensionCount++;
172     else
173         withVideo = false;
174
175     if (extensionCount) {
176         SandboxExtension::HandleArray handles;
177         handles.allocate(extensionCount);
178
179         Vector<String> ids;
180         ids.reserveCapacity(extensionCount);
181
182         if (withAudio && SandboxExtension::createHandleForGenericExtension(audioExtensionPath, handles[--extensionCount]))
183             ids.append(audioExtensionPath);
184
185         if (withVideo && SandboxExtension::createHandleForGenericExtension(videoExtensionPath, handles[--extensionCount]))
186             ids.append(videoExtensionPath);
187
188         if (ids.size() != handles.size()) {
189             WTFLogAlways("Could not create a required sandbox extension, capture will fail!");
190             return false;
191         }
192
193         for (const auto& id : ids)
194             RELEASE_LOG(WebRTC, "UserMediaProcessManager::willCreateMediaStream - granting extension %s", id.utf8().data());
195
196         if (withAudio)
197             state.grantAudioExtension();
198         if (withVideo)
199             state.grantVideoExtension();
200         processStartingCapture.send(Messages::WebPage::GrantUserMediaDeviceSandboxExtensions(MediaDeviceSandboxExtensions(ids, WTFMove(handles))), proxy.page().pageID());
201     }
202 #else
203     UNUSED_PARAM(proxy);
204     UNUSED_PARAM(withAudio);
205     UNUSED_PARAM(withVideo);
206 #endif
207
208     proxy.page().activateMediaStreamCaptureInPage();
209
210     return true;
211 }
212
213 void UserMediaProcessManager::startedCaptureSession(UserMediaPermissionRequestManagerProxy& proxy)
214 {
215     ASSERT(stateMap().contains(&proxy.page().process()));
216 }
217
218 void UserMediaProcessManager::endedCaptureSession(UserMediaPermissionRequestManagerProxy& proxy)
219 {
220 #if ENABLE(SANDBOX_EXTENSIONS)
221     ASSERT(stateMap().contains(&proxy.page().process()));
222
223     auto& state = processState(proxy.page().process());
224     bool hasAudioCapture = false;
225     bool hasVideoCapture = false;
226     for (auto& manager : state.managers()) {
227         if (manager == &proxy)
228             continue;
229         if (manager->page().hasActiveAudioStream())
230             hasAudioCapture = true;
231         if (manager->page().hasActiveVideoStream())
232             hasVideoCapture = true;
233     }
234
235     if (hasAudioCapture && hasVideoCapture)
236         return;
237
238     Vector<String> params;
239     if (!hasAudioCapture && state.hasAudioExtension()) {
240         params.append(audioExtensionPath);
241         state.revokeAudioExtension();
242     }
243     if (!hasVideoCapture && state.hasVideoExtension()) {
244         params.append(videoExtensionPath);
245         state.revokeVideoExtension();
246     }
247
248     if (params.isEmpty())
249         return;
250
251     for (const auto& id : params)
252         RELEASE_LOG(WebRTC, "UserMediaProcessManager::endedCaptureSession - revoking extension %s", id.utf8().data());
253
254     proxy.page().process().send(Messages::WebPage::RevokeUserMediaDeviceSandboxExtensions(params), proxy.page().pageID());
255 #endif
256 }
257
258 void UserMediaProcessManager::setCaptureEnabled(bool enabled)
259 {
260     if (enabled == m_captureEnabled)
261         return;
262
263     m_captureEnabled = enabled;
264
265     if (enabled)
266         return;
267
268     for (auto& state : stateMap()) {
269         for (auto& manager : state.value->managers())
270             manager->stopCapture();
271     }
272 }
273
274 void UserMediaProcessManager::captureDevicesChanged()
275 {
276     auto& map = stateMap();
277     for (auto& state : map) {
278         auto* process = state.key;
279         for (auto& manager : state.value->managers()) {
280             if (map.find(process) == map.end())
281                 break;
282             manager->captureDevicesChanged();
283         }
284     }
285 }
286
287 void UserMediaProcessManager::beginMonitoringCaptureDevices()
288 {
289     static std::once_flag onceFlag;
290
291     std::call_once(onceFlag, [this] {
292         m_captureDevices = WebCore::RealtimeMediaSourceCenter::singleton().getMediaStreamDevices();
293
294         WebCore::RealtimeMediaSourceCenter::singleton().setDevicesChangedObserver([this]() {
295             auto oldDevices = WTFMove(m_captureDevices);
296             m_captureDevices = WebCore::RealtimeMediaSourceCenter::singleton().getMediaStreamDevices();
297
298             if (m_captureDevices.size() == oldDevices.size()) {
299                 bool haveChanges = false;
300                 for (auto &newDevice : m_captureDevices) {
301                     if (newDevice.type() != WebCore::CaptureDevice::DeviceType::Camera && newDevice.type() != WebCore::CaptureDevice::DeviceType::Microphone)
302                         continue;
303
304                     auto index = oldDevices.findMatching([&newDevice] (auto& oldDevice) {
305                         return newDevice.persistentId() == oldDevice.persistentId() && newDevice.enabled() != oldDevice.enabled();
306                     });
307
308                     if (index == notFound) {
309                         haveChanges = true;
310                         break;
311                     }
312                 }
313
314                 if (!haveChanges)
315                     return;
316             }
317
318             // When a device with camera and microphone is attached or detached, the CaptureDevice notification for
319             // the different devices won't arrive at the same time so delay a bit so we can coalesce the callbacks.
320             if (!m_debounceTimer.isActive())
321                 m_debounceTimer.startOneShot(deviceChangeDebounceTimerInterval);
322         });
323     });
324 }
325
326 } // namespace WebKit
327
328 #endif