[WTF] Import std::optional reference implementation as WTF::Optional
[WebKit-https.git] / Source / WebCore / bindings / js / JSMediaDevicesCustom.cpp
1 /*
2  * Copyright (C) 2016 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "JSMediaDevicesCustom.h"
28
29 #if ENABLE(MEDIA_STREAM)
30
31 #include "ArrayValue.h"
32 #include "Dictionary.h"
33 #include "ExceptionCode.h"
34 #include "JSMediaDevices.h"
35 #include "Logging.h"
36 #include "MediaConstraintsImpl.h"
37 #include "RealtimeMediaSourceCenter.h"
38 #include "RealtimeMediaSourceSupportedConstraints.h"
39 #include <wtf/HashMap.h>
40 #include <wtf/Vector.h>
41
42 using namespace JSC;
43
44 namespace WebCore {
45
46 enum class ConstraintSetType { Mandatory, Advanced };
47
48 static void initializeStringConstraintWithList(StringConstraint& constraint, void (StringConstraint::*appendValue)(const String&), const ArrayValue& list)
49 {
50     size_t size;
51     if (!list.length(size))
52         return;
53
54     for (size_t i = 0; i < size; ++i) {
55         String value;
56         if (list.get(i, value))
57             (constraint.*appendValue)(value);
58     }
59 }
60
61 static std::optional<StringConstraint> createStringConstraint(const Dictionary& mediaTrackConstraintSet, const String& name, MediaConstraintType type, ConstraintSetType constraintSetType)
62 {
63     auto constraint = StringConstraint(name, type);
64
65     // Dictionary constraint value.
66     Dictionary dictionaryValue;
67     if (mediaTrackConstraintSet.get(name, dictionaryValue) && !dictionaryValue.isUndefinedOrNull()) {
68         ArrayValue exactArrayValue;
69         if (dictionaryValue.get("exact", exactArrayValue) && !exactArrayValue.isUndefinedOrNull())
70             initializeStringConstraintWithList(constraint, &StringConstraint::appendExact, exactArrayValue);
71         else {
72             String exactStringValue;
73             if (dictionaryValue.get("exact", exactStringValue))
74                 constraint.setExact(exactStringValue);
75         }
76
77         ArrayValue idealArrayValue;
78         if (dictionaryValue.get("ideal", idealArrayValue) && !idealArrayValue.isUndefinedOrNull())
79             initializeStringConstraintWithList(constraint, &StringConstraint::appendIdeal, idealArrayValue);
80         else {
81             String idealStringValue;
82             if (!dictionaryValue.get("ideal", idealStringValue))
83                 constraint.setIdeal(idealStringValue);
84         }
85
86         if (constraint.isEmpty()) {
87             LOG(Media, "createStringConstraint() - ignoring string constraint '%s' with dictionary value since it has no valid or supported key/value pairs.", name.utf8().data());
88             return std::nullopt;
89         }
90         
91         return WTFMove(constraint);
92     }
93
94     // Array constraint value.
95     ArrayValue arrayValue;
96     if (mediaTrackConstraintSet.get(name, arrayValue) && !arrayValue.isUndefinedOrNull()) {
97         initializeStringConstraintWithList(constraint, &StringConstraint::appendIdeal, arrayValue);
98
99         if (constraint.isEmpty()) {
100             LOG(Media, "createStringConstraint() - ignoring string constraint '%s' with array value since it is empty.", name.utf8().data());
101             return std::nullopt;
102         }
103
104         return WTFMove(constraint);
105     }
106
107     // Scalar constraint value.
108     String value;
109     if (mediaTrackConstraintSet.get(name, value)) {
110         if (constraintSetType == ConstraintSetType::Mandatory)
111             constraint.setIdeal(value);
112         else
113             constraint.setExact(value);
114         
115         return WTFMove(constraint);
116     }
117
118     // Invalid constraint value.
119     LOG(Media, "createStringConstraint() - ignoring string constraint '%s' since it has neither a dictionary nor sequence nor scalar value.", name.utf8().data());
120     return std::nullopt;
121 }
122
123 static std::optional<BooleanConstraint> createBooleanConstraint(const Dictionary& mediaTrackConstraintSet, const String& name, MediaConstraintType type, ConstraintSetType constraintSetType)
124 {
125     auto constraint = BooleanConstraint(name, type);
126
127     // Dictionary constraint value.
128     Dictionary dictionaryValue;
129     if (mediaTrackConstraintSet.get(name, dictionaryValue) && !dictionaryValue.isUndefinedOrNull()) {
130         bool exactValue;
131         if (dictionaryValue.get("exact", exactValue))
132             constraint.setExact(exactValue);
133
134         bool idealValue;
135         if (dictionaryValue.get("ideal", idealValue))
136             constraint.setIdeal(idealValue);
137
138         if (constraint.isEmpty()) {
139             LOG(Media, "createBooleanConstraint() - ignoring boolean constraint '%s' with dictionary value since it has no valid or supported key/value pairs.", name.utf8().data());
140             return std::nullopt;
141         }
142
143         return WTFMove(constraint);
144     }
145
146     // Scalar constraint value.
147     bool value;
148     if (mediaTrackConstraintSet.get(name, value)) {
149         if (constraintSetType == ConstraintSetType::Mandatory)
150             constraint.setIdeal(value);
151         else
152             constraint.setExact(value);
153         
154         return WTFMove(constraint);
155     }
156
157     // Invalid constraint value.
158     LOG(Media, "createBooleanConstraint() - ignoring boolean constraint '%s' since it has neither a dictionary nor scalar value.", name.utf8().data());
159     return std::nullopt;
160 }
161
162 static std::optional<DoubleConstraint> createDoubleConstraint(const Dictionary& mediaTrackConstraintSet, const String& name, MediaConstraintType type, ConstraintSetType constraintSetType)
163 {
164     auto constraint = DoubleConstraint(name, type);
165
166     // Dictionary constraint value.
167     Dictionary dictionaryValue;
168     if (mediaTrackConstraintSet.get(name, dictionaryValue) && !dictionaryValue.isUndefinedOrNull()) {
169         double minValue;
170         if (dictionaryValue.get("min", minValue))
171             constraint.setMin(minValue);
172
173         double maxValue;
174         if (dictionaryValue.get("max", maxValue))
175             constraint.setMax(maxValue);
176
177         double exactValue;
178         if (dictionaryValue.get("exact", exactValue))
179             constraint.setExact(exactValue);
180
181         double idealValue;
182         if (dictionaryValue.get("ideal", idealValue))
183             constraint.setIdeal(idealValue);
184
185         if (constraint.isEmpty()) {
186             LOG(Media, "createDoubleConstraint() - ignoring double constraint '%s' with dictionary value since it has no valid or supported key/value pairs.", name.utf8().data());
187             return std::nullopt;
188         }
189
190         return WTFMove(constraint);
191     }
192
193     // Scalar constraint value.
194     double value;
195     if (mediaTrackConstraintSet.get(name, value)) {
196         if (constraintSetType == ConstraintSetType::Mandatory)
197             constraint.setIdeal(value);
198         else
199             constraint.setExact(value);
200         
201         return WTFMove(constraint);
202     }
203
204     // Invalid constraint value.
205     LOG(Media, "createDoubleConstraint() - ignoring double constraint '%s' since it has neither a dictionary nor scalar value.", name.utf8().data());
206     return std::nullopt;
207 }
208
209 static std::optional<IntConstraint> createIntConstraint(const Dictionary& mediaTrackConstraintSet, const String& name, MediaConstraintType type, ConstraintSetType constraintSetType)
210 {
211     auto constraint = IntConstraint(name, type);
212
213     // Dictionary constraint value.
214     Dictionary dictionaryValue;
215     if (mediaTrackConstraintSet.get(name, dictionaryValue) && !dictionaryValue.isUndefinedOrNull()) {
216         int minValue;
217         if (dictionaryValue.get("min", minValue))
218             constraint.setMin(minValue);
219
220         int maxValue;
221         if (dictionaryValue.get("max", maxValue))
222             constraint.setMax(maxValue);
223
224         int exactValue;
225         if (dictionaryValue.get("exact", exactValue))
226             constraint.setExact(exactValue);
227
228         int idealValue;
229         if (dictionaryValue.get("ideal", idealValue))
230             constraint.setIdeal(idealValue);
231
232         if (constraint.isEmpty()) {
233             LOG(Media, "createIntConstraint() - ignoring long constraint '%s' with dictionary value since it has no valid or supported key/value pairs.", name.utf8().data());
234             return std::nullopt;
235         }
236
237         return WTFMove(constraint);
238     }
239
240     // Scalar constraint value.
241     int value;
242     if (mediaTrackConstraintSet.get(name, value)) {
243         if (constraintSetType == ConstraintSetType::Mandatory)
244             constraint.setIdeal(value);
245         else
246             constraint.setExact(value);
247         
248         return WTFMove(constraint);
249     }
250
251     // Invalid constraint value.
252     LOG(Media, "createIntConstraint() - ignoring long constraint '%s' since it has neither a dictionary nor scalar value.", name.utf8().data());
253     return std::nullopt;
254 }
255
256 static void parseMediaTrackConstraintSetForKey(const Dictionary& mediaTrackConstraintSet, const String& name, MediaTrackConstraintSetMap& map, ConstraintSetType constraintSetType)
257 {
258     MediaConstraintType constraintType = RealtimeMediaSourceSupportedConstraints::constraintFromName(name);
259     switch (constraintType) {
260     case MediaConstraintType::Width:
261         map.set(constraintType, createIntConstraint(mediaTrackConstraintSet, name, constraintType, constraintSetType));
262         break;
263     case MediaConstraintType::Height:
264         map.set(constraintType, createIntConstraint(mediaTrackConstraintSet, name, constraintType, constraintSetType));
265         break;
266     case MediaConstraintType::SampleRate:
267         map.set(constraintType, createIntConstraint(mediaTrackConstraintSet, name, constraintType, constraintSetType));
268         break;
269     case MediaConstraintType::SampleSize:
270         map.set(constraintType, createIntConstraint(mediaTrackConstraintSet, name, constraintType, constraintSetType));
271         break;
272
273     case MediaConstraintType::AspectRatio:
274         map.set(constraintType, createDoubleConstraint(mediaTrackConstraintSet, name, constraintType, constraintSetType));
275         break;
276     case MediaConstraintType::FrameRate:
277         map.set(constraintType, createDoubleConstraint(mediaTrackConstraintSet, name, constraintType, constraintSetType));
278         break;
279     case MediaConstraintType::Volume:
280         map.set(constraintType, createDoubleConstraint(mediaTrackConstraintSet, name, constraintType, constraintSetType));
281         break;
282
283     case MediaConstraintType::EchoCancellation:
284         map.set(constraintType, createBooleanConstraint(mediaTrackConstraintSet, name, constraintType, constraintSetType));
285         break;
286
287     case MediaConstraintType::FacingMode:
288         map.set(constraintType, createStringConstraint(mediaTrackConstraintSet, name, constraintType, constraintSetType));
289         break;
290     case MediaConstraintType::DeviceId:
291         map.set(constraintType, createStringConstraint(mediaTrackConstraintSet, name, constraintType, constraintSetType));
292         break;
293     case MediaConstraintType::GroupId:
294         map.set(constraintType, createStringConstraint(mediaTrackConstraintSet, name, constraintType, constraintSetType));
295         break;
296
297     case MediaConstraintType::Unknown:
298         LOG(Media, "parseMediaTrackConstraintSetForKey() - ignoring unsupported constraint '%s'.", name.utf8().data());
299         return;
300     }
301 }
302
303 static void parseAdvancedConstraints(const Dictionary& mediaTrackConstraints, Vector<MediaTrackConstraintSetMap>& advancedConstraints)
304 {
305     ArrayValue sequenceOfMediaTrackConstraintSets;
306     if (!mediaTrackConstraints.get("advanced", sequenceOfMediaTrackConstraintSets) || sequenceOfMediaTrackConstraintSets.isUndefinedOrNull()) {
307         LOG(Media, "parseAdvancedConstraints() - value of advanced key is not a list.");
308         return;
309     }
310
311     size_t numberOfConstraintSets;
312     if (!sequenceOfMediaTrackConstraintSets.length(numberOfConstraintSets)) {
313         LOG(Media, "parseAdvancedConstraints() - ignoring empty advanced sequence of MediaTrackConstraintSets.");
314         return;
315     }
316
317     for (size_t i = 0; i < numberOfConstraintSets; ++i) {
318         Dictionary mediaTrackConstraintSet;
319         if (!sequenceOfMediaTrackConstraintSets.get(i, mediaTrackConstraintSet) || mediaTrackConstraintSet.isUndefinedOrNull()) {
320             LOG(Media, "parseAdvancedConstraints() - ignoring constraint set with index '%zu' in advanced list.", i);
321             continue;
322         }
323
324         MediaTrackConstraintSetMap map;
325
326         Vector<String> localKeys;
327         mediaTrackConstraintSet.getOwnPropertyNames(localKeys);
328         for (auto& localKey : localKeys)
329             parseMediaTrackConstraintSetForKey(mediaTrackConstraintSet, localKey, map, ConstraintSetType::Advanced);
330
331         if (!map.isEmpty())
332             advancedConstraints.append(WTFMove(map));
333     }
334 }
335
336 void parseMediaConstraintsDictionary(const Dictionary& mediaTrackConstraints, MediaTrackConstraintSetMap& mandatoryConstraints, Vector<MediaTrackConstraintSetMap>& advancedConstraints)
337 {
338     if (mediaTrackConstraints.isUndefinedOrNull())
339         return;
340
341     Vector<String> keys;
342     mediaTrackConstraints.getOwnPropertyNames(keys);
343
344     for (auto& key : keys) {
345         if (key == "advanced")
346             parseAdvancedConstraints(mediaTrackConstraints, advancedConstraints);
347         else
348             parseMediaTrackConstraintSetForKey(mediaTrackConstraints, key, mandatoryConstraints, ConstraintSetType::Mandatory);
349     }
350 }
351
352 static void JSMediaDevicesGetUserMediaPromiseFunction(ExecState& state, Ref<DeferredPromise>&& promise)
353 {
354     VM& vm = state.vm();
355     auto scope = DECLARE_THROW_SCOPE(vm);
356
357     if (UNLIKELY(state.argumentCount() < 1)) {
358         throwVMError(&state, scope, createNotEnoughArgumentsError(&state));
359         return;
360     }
361
362     auto constraintsDictionary = Dictionary(&state, state.uncheckedArgument(0));
363
364     MediaTrackConstraintSetMap mandatoryAudioConstraints;
365     Vector<MediaTrackConstraintSetMap> advancedAudioConstraints;
366     bool areAudioConstraintsValid = false;
367
368     Dictionary audioConstraintsDictionary;
369     if (constraintsDictionary.get("audio", audioConstraintsDictionary) && !audioConstraintsDictionary.isUndefinedOrNull()) {
370         parseMediaConstraintsDictionary(audioConstraintsDictionary, mandatoryAudioConstraints, advancedAudioConstraints);
371         areAudioConstraintsValid = true;
372     } else
373         constraintsDictionary.get("audio", areAudioConstraintsValid);
374
375     MediaTrackConstraintSetMap mandatoryVideoConstraints;
376     Vector<MediaTrackConstraintSetMap> advancedVideoConstraints;
377     bool areVideoConstraintsValid = false;
378
379     Dictionary videoConstraintsDictionary;
380     if (constraintsDictionary.get("video", videoConstraintsDictionary) && !videoConstraintsDictionary.isUndefinedOrNull()) {
381         parseMediaConstraintsDictionary(videoConstraintsDictionary, mandatoryVideoConstraints, advancedVideoConstraints);
382         areVideoConstraintsValid = true;
383     } else
384         constraintsDictionary.get("video", areVideoConstraintsValid);
385
386     auto audioConstraints = MediaConstraintsImpl::create(WTFMove(mandatoryAudioConstraints), WTFMove(advancedAudioConstraints), areAudioConstraintsValid);
387     auto videoConstraints = MediaConstraintsImpl::create(WTFMove(mandatoryVideoConstraints), WTFMove(advancedVideoConstraints), areVideoConstraintsValid);
388     propagateException(state, scope, castThisValue<JSMediaDevices>(state).wrapped().getUserMedia(WTFMove(audioConstraints), WTFMove(videoConstraints), WTFMove(promise)));
389 }
390
391 JSValue JSMediaDevices::getUserMedia(ExecState& state)
392 {
393     return callPromiseFunction<JSMediaDevicesGetUserMediaPromiseFunction, PromiseExecutionScope::WindowOnly>(state);
394 }
395
396 }
397
398 #endif