| /* | 
 |  * Copyright (C) 2011 Ericsson AB. All rights reserved. | 
 |  * Copyright (C) 2012 Google Inc. All rights reserved. | 
 |  * | 
 |  * Redistribution and use in source and binary forms, with or without | 
 |  * modification, are permitted provided that the following conditions | 
 |  * are met: | 
 |  * | 
 |  * 1. Redistributions of source code must retain the above copyright | 
 |  *    notice, this list of conditions and the following disclaimer. | 
 |  * 2. Redistributions in binary form must reproduce the above copyright | 
 |  *    notice, this list of conditions and the following disclaimer | 
 |  *    in the documentation and/or other materials provided with the | 
 |  *    distribution. | 
 |  * 3. Neither the name of Ericsson nor the names of its contributors | 
 |  *    may be used to endorse or promote products derived from this | 
 |  *    software without specific prior written permission. | 
 |  * | 
 |  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 
 |  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 
 |  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 
 |  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 
 |  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 
 |  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 
 |  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
 |  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
 |  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 
 |  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
 |  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
 |  */ | 
 |  | 
 | #include "third_party/blink/renderer/modules/mediastream/user_media_request.h" | 
 |  | 
 | #include <type_traits> | 
 |  | 
 | #include "base/macros.h" | 
 | #include "base/strings/stringprintf.h" | 
 | #include "third_party/blink/public/common/privacy_budget/identifiability_metric_builder.h" | 
 | #include "third_party/blink/public/mojom/permissions_policy/permissions_policy.mojom-blink.h" | 
 | #include "third_party/blink/public/platform/modules/webrtc/webrtc_logging.h" | 
 | #include "third_party/blink/renderer/bindings/core/v8/dictionary.h" | 
 | #include "third_party/blink/renderer/bindings/modules/v8/v8_media_stream_constraints.h" | 
 | #include "third_party/blink/renderer/bindings/modules/v8/v8_media_track_constraints.h" | 
 | #include "third_party/blink/renderer/bindings/modules/v8/v8_union_boolean_mediatrackconstraints.h" | 
 | #include "third_party/blink/renderer/bindings/modules/v8/v8_union_domexception_overconstrainederror.h" | 
 | #include "third_party/blink/renderer/core/dom/dom_exception.h" | 
 | #include "third_party/blink/renderer/core/dom/space_split_string.h" | 
 | #include "third_party/blink/renderer/core/frame/deprecation.h" | 
 | #include "third_party/blink/renderer/core/frame/local_dom_window.h" | 
 | #include "third_party/blink/renderer/modules/mediastream/identifiability_metrics.h" | 
 | #include "third_party/blink/renderer/modules/mediastream/media_constraints_impl.h" | 
 | #include "third_party/blink/renderer/modules/mediastream/media_error_state.h" | 
 | #include "third_party/blink/renderer/modules/mediastream/media_stream.h" | 
 | #include "third_party/blink/renderer/modules/mediastream/overconstrained_error.h" | 
 | #include "third_party/blink/renderer/modules/mediastream/user_media_controller.h" | 
 | #include "third_party/blink/renderer/platform/bindings/exception_state.h" | 
 | #include "third_party/blink/renderer/platform/heap/heap.h" | 
 | #include "third_party/blink/renderer/platform/mediastream/media_stream_descriptor.h" | 
 | #include "third_party/blink/renderer/platform/privacy_budget/identifiability_digest_helpers.h" | 
 | #include "third_party/blink/renderer/platform/runtime_enabled_features.h" | 
 |  | 
 | namespace blink { | 
 |  | 
 | namespace { | 
 |  | 
 | template <typename NumericConstraint> | 
 | bool SetUsesNumericConstraint( | 
 |     const MediaTrackConstraintSetPlatform& set, | 
 |     NumericConstraint MediaTrackConstraintSetPlatform::*field) { | 
 |   return (set.*field).HasExact() || (set.*field).HasIdeal() || | 
 |          (set.*field).HasMin() || (set.*field).HasMax(); | 
 | } | 
 |  | 
 | template <typename DiscreteConstraint> | 
 | bool SetUsesDiscreteConstraint( | 
 |     const MediaTrackConstraintSetPlatform& set, | 
 |     DiscreteConstraint MediaTrackConstraintSetPlatform::*field) { | 
 |   return (set.*field).HasExact() || (set.*field).HasIdeal(); | 
 | } | 
 |  | 
 | template <typename NumericConstraint> | 
 | bool RequestUsesNumericConstraint( | 
 |     const MediaConstraints& constraints, | 
 |     NumericConstraint MediaTrackConstraintSetPlatform::*field) { | 
 |   if (SetUsesNumericConstraint(constraints.Basic(), field)) | 
 |     return true; | 
 |   for (const auto& advanced_set : constraints.Advanced()) { | 
 |     if (SetUsesNumericConstraint(advanced_set, field)) | 
 |       return true; | 
 |   } | 
 |   return false; | 
 | } | 
 |  | 
 | template <typename DiscreteConstraint> | 
 | bool RequestUsesDiscreteConstraint( | 
 |     const MediaConstraints& constraints, | 
 |     DiscreteConstraint MediaTrackConstraintSetPlatform::*field) { | 
 |   static_assert( | 
 |       std::is_same< | 
 |           decltype(field), | 
 |           StringConstraint MediaTrackConstraintSetPlatform::*>::value || | 
 |           std::is_same< | 
 |               decltype(field), | 
 |               BooleanConstraint MediaTrackConstraintSetPlatform::*>::value, | 
 |       "Must use StringConstraint or BooleanConstraint"); | 
 |   if (SetUsesDiscreteConstraint(constraints.Basic(), field)) | 
 |     return true; | 
 |   for (const auto& advanced_set : constraints.Advanced()) { | 
 |     if (SetUsesDiscreteConstraint(advanced_set, field)) | 
 |       return true; | 
 |   } | 
 |   return false; | 
 | } | 
 |  | 
 | class FeatureCounter { | 
 |  public: | 
 |   explicit FeatureCounter(ExecutionContext* context) | 
 |       : context_(context), is_unconstrained_(true) {} | 
 |   void Count(WebFeature feature) { | 
 |     UseCounter::Count(context_, feature); | 
 |     is_unconstrained_ = false; | 
 |   } | 
 |   bool IsUnconstrained() { return is_unconstrained_; } | 
 |  | 
 |  private: | 
 |   Persistent<ExecutionContext> context_; | 
 |   bool is_unconstrained_; | 
 |  | 
 |   DISALLOW_COPY_AND_ASSIGN(FeatureCounter); | 
 | }; | 
 |  | 
 | void CountAudioConstraintUses(ExecutionContext* context, | 
 |                               const MediaConstraints& constraints) { | 
 |   FeatureCounter counter(context); | 
 |   if (RequestUsesNumericConstraint( | 
 |           constraints, &MediaTrackConstraintSetPlatform::sample_rate)) { | 
 |     counter.Count(WebFeature::kMediaStreamConstraintsSampleRate); | 
 |   } | 
 |   if (RequestUsesNumericConstraint( | 
 |           constraints, &MediaTrackConstraintSetPlatform::sample_size)) { | 
 |     counter.Count(WebFeature::kMediaStreamConstraintsSampleSize); | 
 |   } | 
 |   if (RequestUsesDiscreteConstraint( | 
 |           constraints, &MediaTrackConstraintSetPlatform::echo_cancellation)) { | 
 |     counter.Count(WebFeature::kMediaStreamConstraintsEchoCancellation); | 
 |   } | 
 |   if (RequestUsesNumericConstraint(constraints, | 
 |                                    &MediaTrackConstraintSetPlatform::latency)) { | 
 |     counter.Count(WebFeature::kMediaStreamConstraintsLatency); | 
 |   } | 
 |   if (RequestUsesNumericConstraint( | 
 |           constraints, &MediaTrackConstraintSetPlatform::channel_count)) { | 
 |     counter.Count(WebFeature::kMediaStreamConstraintsChannelCount); | 
 |   } | 
 |   if (RequestUsesDiscreteConstraint( | 
 |           constraints, &MediaTrackConstraintSetPlatform::device_id)) { | 
 |     counter.Count(WebFeature::kMediaStreamConstraintsDeviceIdAudio); | 
 |   } | 
 |   if (RequestUsesDiscreteConstraint( | 
 |           constraints, &MediaTrackConstraintSetPlatform::disable_local_echo)) { | 
 |     counter.Count(WebFeature::kMediaStreamConstraintsDisableLocalEcho); | 
 |   } | 
 |   if (RequestUsesDiscreteConstraint( | 
 |           constraints, &MediaTrackConstraintSetPlatform::group_id)) { | 
 |     counter.Count(WebFeature::kMediaStreamConstraintsGroupIdAudio); | 
 |   } | 
 |   if (RequestUsesDiscreteConstraint( | 
 |           constraints, &MediaTrackConstraintSetPlatform::media_stream_source)) { | 
 |     counter.Count(WebFeature::kMediaStreamConstraintsMediaStreamSourceAudio); | 
 |   } | 
 |   if (RequestUsesDiscreteConstraint( | 
 |           constraints, | 
 |           &MediaTrackConstraintSetPlatform::render_to_associated_sink)) { | 
 |     counter.Count(WebFeature::kMediaStreamConstraintsRenderToAssociatedSink); | 
 |   } | 
 |   if (RequestUsesDiscreteConstraint( | 
 |           constraints, | 
 |           &MediaTrackConstraintSetPlatform::goog_echo_cancellation)) { | 
 |     counter.Count(WebFeature::kMediaStreamConstraintsGoogEchoCancellation); | 
 |   } | 
 |   if (RequestUsesDiscreteConstraint(constraints, | 
 |                                     &MediaTrackConstraintSetPlatform:: | 
 |                                         goog_experimental_echo_cancellation)) { | 
 |     counter.Count( | 
 |         WebFeature::kMediaStreamConstraintsGoogExperimentalEchoCancellation); | 
 |   } | 
 |   if (RequestUsesDiscreteConstraint( | 
 |           constraints, | 
 |           &MediaTrackConstraintSetPlatform::goog_auto_gain_control)) { | 
 |     counter.Count(WebFeature::kMediaStreamConstraintsGoogAutoGainControl); | 
 |   } | 
 |   if (RequestUsesDiscreteConstraint(constraints, | 
 |                                     &MediaTrackConstraintSetPlatform:: | 
 |                                         goog_experimental_auto_gain_control)) { | 
 |     counter.Count( | 
 |         WebFeature::kMediaStreamConstraintsGoogExperimentalAutoGainControl); | 
 |   } | 
 |   if (RequestUsesDiscreteConstraint( | 
 |           constraints, | 
 |           &MediaTrackConstraintSetPlatform::goog_noise_suppression)) { | 
 |     counter.Count(WebFeature::kMediaStreamConstraintsGoogNoiseSuppression); | 
 |   } | 
 |   if (RequestUsesDiscreteConstraint( | 
 |           constraints, | 
 |           &MediaTrackConstraintSetPlatform::goog_highpass_filter)) { | 
 |     counter.Count(WebFeature::kMediaStreamConstraintsGoogHighpassFilter); | 
 |   } | 
 |   if (RequestUsesDiscreteConstraint(constraints, | 
 |                                     &MediaTrackConstraintSetPlatform:: | 
 |                                         goog_experimental_noise_suppression)) { | 
 |     counter.Count( | 
 |         WebFeature::kMediaStreamConstraintsGoogExperimentalNoiseSuppression); | 
 |   } | 
 |   if (RequestUsesDiscreteConstraint( | 
 |           constraints, | 
 |           &MediaTrackConstraintSetPlatform::goog_audio_mirroring)) { | 
 |     counter.Count(WebFeature::kMediaStreamConstraintsGoogAudioMirroring); | 
 |   } | 
 |   if (RequestUsesDiscreteConstraint( | 
 |           constraints, | 
 |           &MediaTrackConstraintSetPlatform::goog_da_echo_cancellation)) { | 
 |     counter.Count(WebFeature::kMediaStreamConstraintsGoogDAEchoCancellation); | 
 |   } | 
 |  | 
 |   UseCounter::Count(context, WebFeature::kMediaStreamConstraintsAudio); | 
 |   if (counter.IsUnconstrained()) { | 
 |     UseCounter::Count(context, | 
 |                       WebFeature::kMediaStreamConstraintsAudioUnconstrained); | 
 |   } | 
 | } | 
 |  | 
 | void CountVideoConstraintUses(ExecutionContext* context, | 
 |                               const MediaConstraints& constraints) { | 
 |   FeatureCounter counter(context); | 
 |   if (RequestUsesNumericConstraint(constraints, | 
 |                                    &MediaTrackConstraintSetPlatform::width)) { | 
 |     counter.Count(WebFeature::kMediaStreamConstraintsWidth); | 
 |   } | 
 |   if (RequestUsesNumericConstraint(constraints, | 
 |                                    &MediaTrackConstraintSetPlatform::height)) { | 
 |     counter.Count(WebFeature::kMediaStreamConstraintsHeight); | 
 |   } | 
 |   if (RequestUsesNumericConstraint( | 
 |           constraints, &MediaTrackConstraintSetPlatform::aspect_ratio)) { | 
 |     counter.Count(WebFeature::kMediaStreamConstraintsAspectRatio); | 
 |   } | 
 |   if (RequestUsesNumericConstraint( | 
 |           constraints, &MediaTrackConstraintSetPlatform::frame_rate)) { | 
 |     counter.Count(WebFeature::kMediaStreamConstraintsFrameRate); | 
 |   } | 
 |   if (RequestUsesDiscreteConstraint( | 
 |           constraints, &MediaTrackConstraintSetPlatform::facing_mode)) { | 
 |     counter.Count(WebFeature::kMediaStreamConstraintsFacingMode); | 
 |   } | 
 |   if (RequestUsesDiscreteConstraint( | 
 |           constraints, &MediaTrackConstraintSetPlatform::device_id)) { | 
 |     counter.Count(WebFeature::kMediaStreamConstraintsDeviceIdVideo); | 
 |   } | 
 |   if (RequestUsesDiscreteConstraint( | 
 |           constraints, &MediaTrackConstraintSetPlatform::group_id)) { | 
 |     counter.Count(WebFeature::kMediaStreamConstraintsGroupIdVideo); | 
 |   } | 
 |   if (RequestUsesDiscreteConstraint( | 
 |           constraints, &MediaTrackConstraintSetPlatform::video_kind)) { | 
 |     counter.Count(WebFeature::kMediaStreamConstraintsVideoKind); | 
 |   } | 
 |   if (RequestUsesDiscreteConstraint( | 
 |           constraints, &MediaTrackConstraintSetPlatform::media_stream_source)) { | 
 |     counter.Count(WebFeature::kMediaStreamConstraintsMediaStreamSourceVideo); | 
 |   } | 
 |   if (RequestUsesDiscreteConstraint( | 
 |           constraints, | 
 |           &MediaTrackConstraintSetPlatform::goog_noise_reduction)) { | 
 |     counter.Count(WebFeature::kMediaStreamConstraintsGoogNoiseReduction); | 
 |   } | 
 |  | 
 |   UseCounter::Count(context, WebFeature::kMediaStreamConstraintsVideo); | 
 |   if (counter.IsUnconstrained()) { | 
 |     UseCounter::Count(context, | 
 |                       WebFeature::kMediaStreamConstraintsVideoUnconstrained); | 
 |   } | 
 | } | 
 |  | 
 | MediaConstraints ParseOptions( | 
 |     ExecutionContext* execution_context, | 
 |     const V8UnionBooleanOrMediaTrackConstraints* options, | 
 |     MediaErrorState& error_state) { | 
 |   if (!options) | 
 |     return MediaConstraints(); | 
 |   switch (options->GetContentType()) { | 
 |     case V8UnionBooleanOrMediaTrackConstraints::ContentType::kBoolean: | 
 |       if (options->GetAsBoolean()) | 
 |         return media_constraints_impl::Create(); | 
 |       else | 
 |         return MediaConstraints(); | 
 |     case V8UnionBooleanOrMediaTrackConstraints::ContentType:: | 
 |         kMediaTrackConstraints: | 
 |       return media_constraints_impl::Create( | 
 |           execution_context, options->GetAsMediaTrackConstraints(), | 
 |           error_state); | 
 |   } | 
 |   NOTREACHED(); | 
 |   return MediaConstraints(); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | class UserMediaRequest::V8Callbacks final : public UserMediaRequest::Callbacks { | 
 |  public: | 
 |   V8Callbacks(V8NavigatorUserMediaSuccessCallback* success_callback, | 
 |               V8NavigatorUserMediaErrorCallback* error_callback) | 
 |       : success_callback_(success_callback), error_callback_(error_callback) {} | 
 |   ~V8Callbacks() override = default; | 
 |  | 
 |   void Trace(Visitor* visitor) const override { | 
 |     visitor->Trace(success_callback_); | 
 |     visitor->Trace(error_callback_); | 
 |     UserMediaRequest::Callbacks::Trace(visitor); | 
 |   } | 
 |  | 
 |   void OnSuccess(ScriptWrappable* callback_this_value, | 
 |                  MediaStream* stream) override { | 
 |     success_callback_->InvokeAndReportException(callback_this_value, stream); | 
 |   } | 
 |   void OnError(ScriptWrappable* callback_this_value, | 
 |                const V8MediaStreamError* error) override { | 
 |     error_callback_->InvokeAndReportException(callback_this_value, error); | 
 |   } | 
 |  | 
 |  private: | 
 |   Member<V8NavigatorUserMediaSuccessCallback> success_callback_; | 
 |   Member<V8NavigatorUserMediaErrorCallback> error_callback_; | 
 | }; | 
 |  | 
 | UserMediaRequest* UserMediaRequest::Create( | 
 |     ExecutionContext* context, | 
 |     UserMediaController* controller, | 
 |     UserMediaRequest::MediaType media_type, | 
 |     const MediaStreamConstraints* options, | 
 |     Callbacks* callbacks, | 
 |     MediaErrorState& error_state, | 
 |     IdentifiableSurface surface) { | 
 |   MediaConstraints audio = ParseOptions(context, options->audio(), error_state); | 
 |   if (error_state.HadException()) | 
 |     return nullptr; | 
 |  | 
 |   MediaConstraints video = ParseOptions(context, options->video(), error_state); | 
 |   if (error_state.HadException()) | 
 |     return nullptr; | 
 |  | 
 |   if (media_type == UserMediaRequest::MediaType::kUserMedia && | 
 |       !video.IsNull()) { | 
 |     if (video.Basic().pan.HasMandatory()) { | 
 |       error_state.ThrowTypeError("Mandatory pan constraint is not supported"); | 
 |       return nullptr; | 
 |     } | 
 |     if (video.Basic().tilt.HasMandatory()) { | 
 |       error_state.ThrowTypeError("Mandatory tilt constraint is not supported"); | 
 |       return nullptr; | 
 |     } | 
 |     if (video.Basic().zoom.HasMandatory()) { | 
 |       error_state.ThrowTypeError("Mandatory zoom constraint is not supported"); | 
 |       return nullptr; | 
 |     } | 
 |   } else if (media_type == UserMediaRequest::MediaType::kDisplayMedia || | 
 |              media_type == | 
 |                  UserMediaRequest::MediaType::kGetCurrentBrowsingContextMedia) { | 
 |     // https://w3c.github.io/mediacapture-screen-share/#mediadevices-additions | 
 |     // MediaDevices Additions | 
 |     // The user agent MUST reject audio-only requests. | 
 |     // 1. Let constraints be the method's first argument. | 
 |     // 2. For each member present in constraints whose value, value, is a | 
 |     // dictionary, run the following steps: | 
 |     //   1. If value contains a member named advanced, return a promise rejected | 
 |     //   with a newly created TypeError. | 
 |     //   2. If value contains a member which in turn is a dictionary containing | 
 |     //   a member named either min or exact, return a promise rejected with a | 
 |     //   newly created TypeError. | 
 |     // 3. Let requestedMediaTypes be the set of media types in constraints with | 
 |     // either a dictionary value or a value of true. | 
 |     // 4. If requestedMediaTypes is the empty set, set requestedMediaTypes to a | 
 |     // set containing "video". | 
 |     if ((!audio.IsNull() && !audio.Advanced().IsEmpty()) || | 
 |         (!video.IsNull() && !video.Advanced().IsEmpty())) { | 
 |       error_state.ThrowTypeError("Advanced constraints are not supported"); | 
 |       return nullptr; | 
 |     } | 
 |     if ((!audio.IsNull() && audio.Basic().HasMin()) || | 
 |         (!video.IsNull() && video.Basic().HasMin())) { | 
 |       error_state.ThrowTypeError("min constraints are not supported"); | 
 |       return nullptr; | 
 |     } | 
 |     if ((!audio.IsNull() && audio.Basic().HasExact()) || | 
 |         (!video.IsNull() && video.Basic().HasExact())) { | 
 |       error_state.ThrowTypeError("exact constraints are not supported"); | 
 |       return nullptr; | 
 |     } | 
 |     if (!audio.IsNull() && video.IsNull()) { | 
 |       error_state.ThrowTypeError("Audio only requests are not supported"); | 
 |       return nullptr; | 
 |     } | 
 |     if (audio.IsNull() && video.IsNull()) { | 
 |       video = ParseOptions( | 
 |           context, | 
 |           MakeGarbageCollected<V8UnionBooleanOrMediaTrackConstraints>(true), | 
 |           error_state); | 
 |       if (error_state.HadException()) | 
 |         return nullptr; | 
 |     } | 
 |   } | 
 |  | 
 |   if (audio.IsNull() && video.IsNull()) { | 
 |     error_state.ThrowTypeError( | 
 |         "At least one of audio and video must be requested"); | 
 |     return nullptr; | 
 |   } | 
 |  | 
 |   if (!audio.IsNull()) | 
 |     CountAudioConstraintUses(context, audio); | 
 |   if (!video.IsNull()) | 
 |     CountVideoConstraintUses(context, video); | 
 |  | 
 |   return MakeGarbageCollected<UserMediaRequest>( | 
 |       context, controller, media_type, audio, video, callbacks, surface); | 
 | } | 
 |  | 
 | UserMediaRequest* UserMediaRequest::Create( | 
 |     ExecutionContext* context, | 
 |     UserMediaController* controller, | 
 |     const MediaStreamConstraints* options, | 
 |     V8NavigatorUserMediaSuccessCallback* success_callback, | 
 |     V8NavigatorUserMediaErrorCallback* error_callback, | 
 |     MediaErrorState& error_state, | 
 |     IdentifiableSurface surface) { | 
 |   return Create( | 
 |       context, controller, UserMediaRequest::MediaType::kUserMedia, options, | 
 |       MakeGarbageCollected<V8Callbacks>(success_callback, error_callback), | 
 |       error_state, surface); | 
 | } | 
 |  | 
 | UserMediaRequest* UserMediaRequest::CreateForTesting( | 
 |     const MediaConstraints& audio, | 
 |     const MediaConstraints& video) { | 
 |   return MakeGarbageCollected<UserMediaRequest>( | 
 |       nullptr, nullptr, UserMediaRequest::MediaType::kUserMedia, audio, video, | 
 |       nullptr, IdentifiableSurface()); | 
 | } | 
 |  | 
 | UserMediaRequest::UserMediaRequest(ExecutionContext* context, | 
 |                                    UserMediaController* controller, | 
 |                                    UserMediaRequest::MediaType media_type, | 
 |                                    MediaConstraints audio, | 
 |                                    MediaConstraints video, | 
 |                                    Callbacks* callbacks, | 
 |                                    IdentifiableSurface surface) | 
 |     : ExecutionContextLifecycleObserver(context), | 
 |       media_type_(media_type), | 
 |       audio_(audio), | 
 |       video_(video), | 
 |       should_disable_hardware_noise_suppression_( | 
 |           RuntimeEnabledFeatures::DisableHardwareNoiseSuppressionEnabled( | 
 |               context)), | 
 |       controller_(controller), | 
 |       callbacks_(callbacks), | 
 |       surface_(surface) { | 
 |   if (should_disable_hardware_noise_suppression_) { | 
 |     UseCounter::Count(context, | 
 |                       WebFeature::kUserMediaDisableHardwareNoiseSuppression); | 
 |   } | 
 | } | 
 |  | 
 | UserMediaRequest::~UserMediaRequest() = default; | 
 |  | 
 | UserMediaRequest::MediaType UserMediaRequest::MediaRequestType() const { | 
 |   return media_type_; | 
 | } | 
 |  | 
 | bool UserMediaRequest::Audio() const { | 
 |   return !audio_.IsNull(); | 
 | } | 
 |  | 
 | bool UserMediaRequest::Video() const { | 
 |   return !video_.IsNull(); | 
 | } | 
 |  | 
 | MediaConstraints UserMediaRequest::AudioConstraints() const { | 
 |   return audio_; | 
 | } | 
 |  | 
 | MediaConstraints UserMediaRequest::VideoConstraints() const { | 
 |   return video_; | 
 | } | 
 |  | 
 | bool UserMediaRequest::ShouldDisableHardwareNoiseSuppression() const { | 
 |   return should_disable_hardware_noise_suppression_; | 
 | } | 
 |  | 
 | bool UserMediaRequest::IsSecureContextUse(String& error_message) { | 
 |   LocalDOMWindow* window = GetWindow(); | 
 |  | 
 |   if (window->IsSecureContext(error_message)) { | 
 |     UseCounter::Count(window, WebFeature::kGetUserMediaSecureOrigin); | 
 |     window->CountUseOnlyInCrossOriginIframe( | 
 |         WebFeature::kGetUserMediaSecureOriginIframe); | 
 |  | 
 |     // Permissions policy deprecation messages. | 
 |     if (Audio()) { | 
 |       if (!window->IsFeatureEnabled( | 
 |               mojom::blink::PermissionsPolicyFeature::kMicrophone, | 
 |               ReportOptions::kReportOnFailure)) { | 
 |         UseCounter::Count( | 
 |             window, WebFeature::kMicrophoneDisabledByFeaturePolicyEstimate); | 
 |       } | 
 |     } | 
 |     if (Video()) { | 
 |       if (!window->IsFeatureEnabled( | 
 |               mojom::blink::PermissionsPolicyFeature::kCamera, | 
 |               ReportOptions::kReportOnFailure)) { | 
 |         UseCounter::Count(window, | 
 |                           WebFeature::kCameraDisabledByFeaturePolicyEstimate); | 
 |       } | 
 |     } | 
 |  | 
 |     return true; | 
 |   } | 
 |  | 
 |   // While getUserMedia is blocked on insecure origins, we still want to | 
 |   // count attempts to use it. | 
 |   Deprecation::CountDeprecation(window, | 
 |                                 WebFeature::kGetUserMediaInsecureOrigin); | 
 |   Deprecation::CountDeprecationCrossOriginIframe( | 
 |       window, WebFeature::kGetUserMediaInsecureOriginIframe); | 
 |   return false; | 
 | } | 
 |  | 
 | LocalDOMWindow* UserMediaRequest::GetWindow() { | 
 |   return To<LocalDOMWindow>(GetExecutionContext()); | 
 | } | 
 |  | 
 | void UserMediaRequest::Start() { | 
 |   if (controller_) | 
 |     controller_->RequestUserMedia(this); | 
 | } | 
 |  | 
 | void UserMediaRequest::Succeed(MediaStreamDescriptor* stream_descriptor) { | 
 |   DCHECK(!is_resolved_); | 
 |   if (!GetExecutionContext()) | 
 |     return; | 
 |  | 
 |   MediaStream::Create(GetExecutionContext(), stream_descriptor, | 
 |                       WTF::Bind(&UserMediaRequest::OnMediaStreamInitialized, | 
 |                                 WrapPersistent(this))); | 
 | } | 
 |  | 
 | void UserMediaRequest::OnMediaStreamInitialized(MediaStream* stream) { | 
 |   DCHECK(!is_resolved_); | 
 |  | 
 |   MediaStreamTrackVector audio_tracks = stream->getAudioTracks(); | 
 |   for (const auto& audio_track : audio_tracks) | 
 |     audio_track->SetConstraints(audio_); | 
 |  | 
 |   MediaStreamTrackVector video_tracks = stream->getVideoTracks(); | 
 |   for (const auto& video_track : video_tracks) | 
 |     video_track->SetConstraints(video_); | 
 |  | 
 |   RecordIdentifiabilityMetric(surface_, GetExecutionContext(), | 
 |                               IdentifiabilityBenignStringToken(g_empty_string)); | 
 |   // After this call, the execution context may be invalid. | 
 |   callbacks_->OnSuccess(nullptr, stream); | 
 |   is_resolved_ = true; | 
 | } | 
 |  | 
 | void UserMediaRequest::FailConstraint(const String& constraint_name, | 
 |                                       const String& message) { | 
 |   DCHECK(!constraint_name.IsEmpty()); | 
 |   DCHECK(!is_resolved_); | 
 |   if (!GetExecutionContext()) | 
 |     return; | 
 |   RecordIdentifiabilityMetric(surface_, GetExecutionContext(), | 
 |                               IdentifiabilityBenignStringToken(message)); | 
 |   // After this call, the execution context may be invalid. | 
 |   callbacks_->OnError( | 
 |       nullptr, MakeGarbageCollected<V8MediaStreamError>( | 
 |                    OverconstrainedError::Create(constraint_name, message))); | 
 |   is_resolved_ = true; | 
 | } | 
 |  | 
 | void UserMediaRequest::Fail(Error name, const String& message) { | 
 |   DCHECK(!is_resolved_); | 
 |   if (!GetExecutionContext()) | 
 |     return; | 
 |  | 
 |   DOMExceptionCode exception_code = DOMExceptionCode::kNotSupportedError; | 
 |   switch (name) { | 
 |     case Error::kPermissionDenied: | 
 |     case Error::kPermissionDismissed: | 
 |     case Error::kInvalidState: | 
 |     case Error::kFailedDueToShutdown: | 
 |     case Error::kKillSwitchOn: | 
 |     case Error::kSystemPermissionDenied: | 
 |       exception_code = DOMExceptionCode::kNotAllowedError; | 
 |       break; | 
 |     case Error::kDevicesNotFound: | 
 |       exception_code = DOMExceptionCode::kNotFoundError; | 
 |       break; | 
 |     case Error::kTabCapture: | 
 |     case Error::kScreenCapture: | 
 |     case Error::kCapture: | 
 |       exception_code = DOMExceptionCode::kAbortError; | 
 |       break; | 
 |     case Error::kTrackStart: | 
 |     case Error::kDeviceInUse: | 
 |       exception_code = DOMExceptionCode::kNotReadableError; | 
 |       break; | 
 |     case Error::kNotSupported: | 
 |       exception_code = DOMExceptionCode::kNotSupportedError; | 
 |       break; | 
 |     case Error::kSecurityError: | 
 |       exception_code = DOMExceptionCode::kSecurityError; | 
 |       break; | 
 |     default: | 
 |       NOTREACHED(); | 
 |   } | 
 |   RecordIdentifiabilityMetric(surface_, GetExecutionContext(), | 
 |                               IdentifiabilityBenignStringToken(message)); | 
 |   // After this call, the execution context may be invalid. | 
 |   callbacks_->OnError(nullptr, MakeGarbageCollected<V8MediaStreamError>( | 
 |                                    MakeGarbageCollected<DOMException>( | 
 |                                        exception_code, message))); | 
 |   is_resolved_ = true; | 
 | } | 
 |  | 
 | void UserMediaRequest::ContextDestroyed() { | 
 |   if (!is_resolved_) | 
 |     blink::WebRtcLogMessage("UMR::ContextDestroyed. Request not resolved."); | 
 |   if (controller_) { | 
 |     controller_->CancelUserMediaRequest(this); | 
 |     if (!is_resolved_) { | 
 |       blink::WebRtcLogMessage(base::StringPrintf( | 
 |           "UMR::ContextDestroyed. Resolving unsolved request. " | 
 |           "audio constraints=%s, video constraints=%s", | 
 |           AudioConstraints().ToString().Utf8().c_str(), | 
 |           VideoConstraints().ToString().Utf8().c_str())); | 
 |       callbacks_->OnError(nullptr, MakeGarbageCollected<V8MediaStreamError>( | 
 |                                        MakeGarbageCollected<DOMException>( | 
 |                                            DOMExceptionCode::kAbortError, | 
 |                                            "Context destroyed"))); | 
 |     } | 
 |     controller_ = nullptr; | 
 |   } | 
 | } | 
 |  | 
 | void UserMediaRequest::Trace(Visitor* visitor) const { | 
 |   visitor->Trace(controller_); | 
 |   visitor->Trace(callbacks_); | 
 |   ExecutionContextLifecycleObserver::Trace(visitor); | 
 | } | 
 |  | 
 | }  // namespace blink |