[MediaStream] 'devicechange' event should not fire in frames that can't access captur...
[WebKit-https.git] / Source / WebCore / Modules / mediastream / MediaDevices.cpp
1 /*
2  * Copyright (C) 2015 Ericsson AB. All rights reserved.
3  * Copyright (C) 2015-2018 Apple Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer
13  *    in the documentation and/or other materials provided with the
14  *    distribution.
15  * 3. Neither the name of Ericsson nor the names of its contributors
16  *    may be used to endorse or promote products derived from this
17  *    software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31
32 #include "config.h"
33 #include "MediaDevices.h"
34
35 #if ENABLE(MEDIA_STREAM)
36
37 #include "Document.h"
38 #include "Event.h"
39 #include "EventNames.h"
40 #include "MediaDevicesRequest.h"
41 #include "MediaTrackSupportedConstraints.h"
42 #include "RealtimeMediaSourceSettings.h"
43 #include "RuntimeEnabledFeatures.h"
44 #include "UserMediaController.h"
45 #include "UserMediaRequest.h"
46 #include <wtf/RandomNumber.h>
47
48 namespace WebCore {
49
50 inline MediaDevices::MediaDevices(Document& document)
51     : ActiveDOMObject(&document)
52     , m_scheduledEventTimer(*this, &MediaDevices::scheduledEventTimerFired)
53     , m_eventNames(eventNames())
54 {
55     suspendIfNeeded();
56
57     static_assert(static_cast<size_t>(MediaDevices::DisplayCaptureSurfaceType::Monitor) == static_cast<size_t>(RealtimeMediaSourceSettings::DisplaySurfaceType::Monitor), "MediaDevices::DisplayCaptureSurfaceType::Monitor is not equal to RealtimeMediaSourceSettings::DisplaySurfaceType::Monitor as expected");
58     static_assert(static_cast<size_t>(MediaDevices::DisplayCaptureSurfaceType::Window) == static_cast<size_t>(RealtimeMediaSourceSettings::DisplaySurfaceType::Window), "MediaDevices::DisplayCaptureSurfaceType::Window is not RealtimeMediaSourceSettings::DisplaySurfaceType::Window as expected");
59     static_assert(static_cast<size_t>(MediaDevices::DisplayCaptureSurfaceType::Application) == static_cast<size_t>(RealtimeMediaSourceSettings::DisplaySurfaceType::Application), "MediaDevices::DisplayCaptureSurfaceType::Application is not RealtimeMediaSourceSettings::DisplaySurfaceType::Application as expected");
60     static_assert(static_cast<size_t>(MediaDevices::DisplayCaptureSurfaceType::Browser) == static_cast<size_t>(RealtimeMediaSourceSettings::DisplaySurfaceType::Browser), "MediaDevices::DisplayCaptureSurfaceType::Browser is not RealtimeMediaSourceSettings::DisplaySurfaceType::Browser as expected");
61 }
62
63 MediaDevices::~MediaDevices() = default;
64
65 void MediaDevices::stop()
66 {
67     if (m_deviceChangeToken) {
68         auto* document = this->document();
69         auto* controller = document ? UserMediaController::from(document->page()) : nullptr;
70         if (document && controller)
71             controller->removeDeviceChangeObserver(m_deviceChangeToken);
72     }
73 }
74
75 Ref<MediaDevices> MediaDevices::create(Document& document)
76 {
77     return adoptRef(*new MediaDevices(document));
78 }
79
80 Document* MediaDevices::document() const
81 {
82     return downcast<Document>(scriptExecutionContext());
83 }
84
85 static MediaConstraints createMediaConstraints(const Variant<bool, MediaTrackConstraints>& constraints)
86 {
87     return WTF::switchOn(constraints,
88         [&] (bool isValid) {
89             MediaConstraints constraints;
90             constraints.isValid = isValid;
91             return constraints;
92         },
93         [&] (const MediaTrackConstraints& trackConstraints) {
94             return createMediaConstraints(trackConstraints);
95         }
96     );
97 }
98
99 void MediaDevices::getUserMedia(const StreamConstraints& constraints, Promise&& promise) const
100 {
101     auto* document = this->document();
102     if (!document) {
103         promise.reject(Exception { InvalidStateError });
104         return;
105     }
106
107     auto audioConstraints = createMediaConstraints(constraints.audio);
108     auto videoConstraints = createMediaConstraints(constraints.video);
109     if (videoConstraints.isValid)
110         videoConstraints.setDefaultVideoConstraints();
111
112     auto request = UserMediaRequest::create(*document, { MediaStreamRequest::Type::UserMedia, WTFMove(audioConstraints), WTFMove(videoConstraints) }, WTFMove(promise));
113     if (request)
114         request->start();
115
116     return;
117 }
118
119 ExceptionOr<void> MediaDevices::getDisplayMedia(const StreamConstraints& constraints, Promise&& promise) const
120 {
121     auto* document = this->document();
122     if (!document)
123         return Exception { InvalidStateError };
124
125     auto request = UserMediaRequest::create(*document, { MediaStreamRequest::Type::DisplayMedia, { }, createMediaConstraints(constraints.video) }, WTFMove(promise));
126     if (request)
127         request->start();
128
129     return { };
130 }
131
132 void MediaDevices::enumerateDevices(EnumerateDevicesPromise&& promise) const
133 {
134     auto* document = this->document();
135     if (!document)
136         return;
137     MediaDevicesRequest::create(*document, WTFMove(promise))->start();
138 }
139
140 MediaTrackSupportedConstraints MediaDevices::getSupportedConstraints()
141 {
142     auto& supported = RealtimeMediaSourceCenter::singleton().supportedConstraints();
143     MediaTrackSupportedConstraints result;
144     result.width = supported.supportsWidth();
145     result.height = supported.supportsHeight();
146     result.aspectRatio = supported.supportsAspectRatio();
147     result.frameRate = supported.supportsFrameRate();
148     result.facingMode = supported.supportsFacingMode();
149     result.volume = supported.supportsVolume();
150     result.sampleRate = supported.supportsSampleRate();
151     result.sampleSize = supported.supportsSampleSize();
152     result.echoCancellation = supported.supportsEchoCancellation();
153     result.deviceId = supported.supportsDeviceId();
154     result.groupId = supported.supportsGroupId();
155
156     return result;
157 }
158
159 void MediaDevices::scheduledEventTimerFired()
160 {
161     if (scriptExecutionContext())
162         dispatchEvent(Event::create(eventNames().devicechangeEvent, Event::CanBubble::No, Event::IsCancelable::No));
163 }
164
165 bool MediaDevices::hasPendingActivity() const
166 {
167     return !isContextStopped() && hasEventListeners(m_eventNames.devicechangeEvent);
168 }
169
170 const char* MediaDevices::activeDOMObjectName() const
171 {
172     return "MediaDevices";
173 }
174
175 bool MediaDevices::canSuspendForDocumentSuspension() const
176 {
177     return true;
178 }
179
180 bool MediaDevices::addEventListener(const AtomicString& eventType, Ref<EventListener>&& listener, const AddEventListenerOptions& options)
181 {
182     if (!m_listeningForDeviceChanges && eventType == eventNames().devicechangeEvent) {
183         auto* document = this->document();
184         auto* controller = document ? UserMediaController::from(document->page()) : nullptr;
185         if (document && controller) {
186             m_listeningForDeviceChanges = true;
187
188             m_deviceChangeToken = controller->addDeviceChangeObserver([weakThis = makeWeakPtr(*this), this]() {
189
190                 if (!weakThis || m_scheduledEventTimer.isActive())
191                     return;
192
193                 auto* document = this->document();
194                 auto* controller = document ? UserMediaController::from(document->page()) : nullptr;
195                 if (!controller)
196                     return;
197
198                 bool canAccessMicrophone = controller->canCallGetUserMedia(*document, { UserMediaController::CaptureType::Microphone }) == UserMediaController::GetUserMediaAccess::CanCall;
199                 bool canAccessCamera = controller->canCallGetUserMedia(*document, { UserMediaController::CaptureType::Camera }) == UserMediaController::GetUserMediaAccess::CanCall;
200                 if (!canAccessMicrophone && !canAccessCamera)
201                     return;
202
203                 m_scheduledEventTimer.startOneShot(Seconds(randomNumber() / 2));
204             });
205         }
206     }
207
208     return EventTargetWithInlineData::addEventListener(eventType, WTFMove(listener), options);
209 }
210
211 } // namespace WebCore
212
213 #endif // ENABLE(MEDIA_STREAM)