blob: 1d80e72fc0abad421c842dc7977952adbf88fc7f [file] [log] [blame]
/*
* 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/metrics/histogram_functions.h"
#include "base/strings/stringprintf.h"
#include "media/base/media_switches.h"
#include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom-blink.h"
#include "third_party/blink/public/common/privacy_budget/identifiability_metric_builder.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/deprecation.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/modules/mediastream/capture_controller.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_stream.h"
#include "third_party/blink/renderer/modules/mediastream/media_stream_set.h"
#include "third_party/blink/renderer/modules/mediastream/overconstrained_error.h"
#include "third_party/blink/renderer/modules/mediastream/transferred_media_stream_track.h"
#include "third_party/blink/renderer/modules/mediastream/user_media_client.h"
#include "third_party/blink/renderer/modules/peerconnection/peer_connection_tracker.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.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 {
using mojom::blink::MediaStreamType;
using Result = mojom::blink::MediaStreamRequestResult;
namespace {
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class GetDisplayMediaIncludeExcludeConstraint {
kNotSpecified = 0,
kInclude = 1,
kExclude = 2,
kMaxValue = kExclude
};
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class GetDisplayMediaSystemWindowOrExcludeConstraint {
kNotSpecified = 0,
kSystem = 1,
kWindow = 2,
kExclude = 3,
kMaxValue = kExclude
};
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class GetDisplayMediaConstraintsDisplaySurface {
kNotSpecified = 0,
kTab = 1,
kWindow = 2,
kMonitor = 3,
kMaxValue = kMonitor
};
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class GetDisplayMediaBooleanConstraint {
kNotSpecified = 0,
kTrue = 1,
kFalse = 2,
kMaxValue = kFalse
};
void RecordUma(GetDisplayMediaConstraintsDisplaySurface value) {
base::UmaHistogramEnumeration(
"Media.GetDisplayMedia.Constraints.DisplaySurface", value);
}
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 ||
std::is_same<decltype(field),
BooleanOrStringConstraint
MediaTrackConstraintSetPlatform::*>::value,
"Must use StringConstraint, BooleanConstraint or "
"BooleanOrStringConstraint");
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) {}
FeatureCounter(const FeatureCounter&) = delete;
FeatureCounter& operator=(const FeatureCounter&) = delete;
void Count(WebFeature feature) {
UseCounter::Count(context_, feature);
is_unconstrained_ = false;
}
void CountDeprecation(WebFeature feature) {
UseCounter::CountDeprecation(context_, feature);
is_unconstrained_ = false;
}
bool IsUnconstrained() { return is_unconstrained_; }
private:
Persistent<ExecutionContext> context_;
bool is_unconstrained_;
};
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 (RequestUsesDiscreteConstraint(
constraints, &MediaTrackConstraintSetPlatform::auto_gain_control)) {
counter.Count(WebFeature::kMediaStreamConstraintsAutoGainControl);
}
if (RequestUsesDiscreteConstraint(
constraints, &MediaTrackConstraintSetPlatform::noise_suppression)) {
counter.Count(WebFeature::kMediaStreamConstraintsNoiseSuppression);
}
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);
}
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::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);
}
}
void RecordGetDisplayMediaIncludeExcludeConstraintUma(
std::optional<V8DisplayMediaIncludeOrExclude::Enum> include_or_exclude,
const std::string& histogram_name) {
const GetDisplayMediaIncludeExcludeConstraint value =
(!include_or_exclude.has_value()
? GetDisplayMediaIncludeExcludeConstraint::kNotSpecified
: include_or_exclude == V8DisplayMediaIncludeOrExclude::Enum::kInclude
? GetDisplayMediaIncludeExcludeConstraint::kInclude
: GetDisplayMediaIncludeExcludeConstraint::kExclude);
base::UmaHistogramEnumeration(histogram_name, value);
}
void RecordGetDisplayMediaSystemWindowOrExcludeConstraintUma(
std::optional<V8DisplayMediaSystemWindowOrExclude::Enum>
system_window_or_exclude,
const std::string& histogram_name) {
GetDisplayMediaSystemWindowOrExcludeConstraint value =
GetDisplayMediaSystemWindowOrExcludeConstraint::kNotSpecified;
if (system_window_or_exclude.has_value()) {
switch (system_window_or_exclude.value()) {
case V8DisplayMediaSystemWindowOrExclude::Enum::kExclude:
value = GetDisplayMediaSystemWindowOrExcludeConstraint::kExclude;
break;
case V8DisplayMediaSystemWindowOrExclude::Enum::kWindow:
value = GetDisplayMediaSystemWindowOrExcludeConstraint::kWindow;
break;
case V8DisplayMediaSystemWindowOrExclude::Enum::kSystem:
value = GetDisplayMediaSystemWindowOrExcludeConstraint::kSystem;
break;
}
}
base::UmaHistogramEnumeration(histogram_name, value);
}
void RecordPreferredDisplaySurfaceConstraintUma(
const mojom::blink::PreferredDisplaySurface preferred_display_surface) {
switch (preferred_display_surface) {
case mojom::blink::PreferredDisplaySurface::NO_PREFERENCE:
RecordUma(GetDisplayMediaConstraintsDisplaySurface::kNotSpecified);
return;
case mojom::blink::PreferredDisplaySurface::MONITOR:
RecordUma(GetDisplayMediaConstraintsDisplaySurface::kMonitor);
return;
case mojom::blink::PreferredDisplaySurface::WINDOW:
RecordUma(GetDisplayMediaConstraintsDisplaySurface::kWindow);
return;
case mojom::blink::PreferredDisplaySurface::BROWSER:
RecordUma(GetDisplayMediaConstraintsDisplaySurface::kTab);
return;
}
NOTREACHED();
}
void RecordBooleanConstraintUma(std::optional<bool> boolean,
const std::string& histogram_name) {
const GetDisplayMediaBooleanConstraint value =
(!boolean.has_value() ? GetDisplayMediaBooleanConstraint::kNotSpecified
: boolean.value() ? GetDisplayMediaBooleanConstraint::kTrue
: GetDisplayMediaBooleanConstraint::kFalse);
base::UmaHistogramEnumeration(histogram_name, value);
}
MediaConstraints ParseOptions(
ExecutionContext* execution_context,
const V8UnionBooleanOrMediaTrackConstraints* options,
ExceptionState& exception_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:
String error_message;
auto constraints = media_constraints_impl::Create(
execution_context, options->GetAsMediaTrackConstraints(),
error_message);
if (constraints.IsNull()) {
exception_state.ThrowTypeError(error_message);
}
return constraints;
}
NOTREACHED();
}
} // namespace
UserMediaRequest* UserMediaRequest::Create(
ExecutionContext* context,
UserMediaClient* client,
UserMediaRequestType media_type,
const MediaStreamConstraints* options,
Callbacks* callbacks,
ExceptionState& exception_state,
IdentifiableSurface surface) {
MediaConstraints audio =
ParseOptions(context, options->audio(), exception_state);
if (exception_state.HadException()) {
return nullptr;
}
MediaConstraints video =
ParseOptions(context, options->video(), exception_state);
if (exception_state.HadException()) {
return nullptr;
}
std::string display_surface_constraint;
std::optional<bool> suppress_local_audio_playback;
std::optional<bool> restrict_own_audio;
if (media_type == UserMediaRequestType::kUserMedia) {
if (audio.IsNull() && video.IsNull()) {
exception_state.ThrowTypeError(
"At least one of audio and video must be requested");
return nullptr;
} else if (!video.IsNull()) {
auto& video_basic = video.MutableBasic();
const BaseConstraint* constraints[] = {
&video_basic.pan,
&video_basic.tilt,
&video_basic.zoom,
&video_basic.background_blur,
&video_basic.background_segmentation_mask,
&video_basic.eye_gaze_correction,
&video_basic.face_framing,
};
for (const BaseConstraint* constraint : constraints) {
if (constraint->HasMandatory()) {
exception_state.ThrowTypeError(
String::Format("Mandatory %s constraints are not supported",
constraint->GetName()));
return nullptr;
}
}
BaseConstraint* compatibility_constraints[] = {
&video_basic.exposure_compensation,
&video_basic.exposure_time,
&video_basic.color_temperature,
&video_basic.iso,
&video_basic.brightness,
&video_basic.contrast,
&video_basic.saturation,
&video_basic.sharpness,
&video_basic.focus_distance,
&video_basic.torch,
};
for (BaseConstraint* constraint : compatibility_constraints) {
if (constraint->HasMandatory()) {
// This should throw a TypeError, but that cannot be done due
// to backward compatibility.
// Thus instead of that, let's ignore the constraint.
constraint->ResetToUnconstrained();
}
}
}
} else if (media_type == UserMediaRequestType::kDisplayMedia ||
media_type == UserMediaRequestType::kAllScreensMedia) {
// 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.
if (media_type == UserMediaRequestType::kAllScreensMedia) {
if (!audio.IsNull()) {
exception_state.ThrowTypeError("Audio requests are not supported");
return nullptr;
} else if (options->preferCurrentTab()) {
exception_state.ThrowTypeError("preferCurrentTab is not supported");
return nullptr;
}
}
if (audio.IsNull() && video.IsNull()) {
exception_state.ThrowTypeError("either audio or video must be requested");
return nullptr;
}
if ((!audio.IsNull() && !audio.Advanced().empty()) ||
(!video.IsNull() && !video.Advanced().empty())) {
exception_state.ThrowTypeError("Advanced constraints are not supported");
return nullptr;
}
if ((!audio.IsNull() && audio.Basic().HasMin()) ||
(!video.IsNull() && video.Basic().HasMin())) {
exception_state.ThrowTypeError("min constraints are not supported");
return nullptr;
}
if ((!audio.IsNull() && audio.Basic().HasExact()) ||
(!video.IsNull() && video.Basic().HasExact())) {
exception_state.ThrowTypeError("exact constraints are not supported");
return nullptr;
}
if (!video.IsNull() && video.Basic().display_surface.HasIdeal() &&
video.Basic().display_surface.Ideal().size() > 0) {
display_surface_constraint =
video.Basic().display_surface.Ideal()[0].Utf8();
}
if (!audio.IsNull() &&
audio.Basic().suppress_local_audio_playback.HasIdeal()) {
suppress_local_audio_playback =
audio.Basic().suppress_local_audio_playback.Ideal();
}
if (!audio.IsNull() && audio.Basic().restrict_own_audio.HasIdeal() &&
media::IsRestrictOwnAudioSupported()) {
restrict_own_audio = audio.Basic().restrict_own_audio.Ideal();
}
}
if (!audio.IsNull())
CountAudioConstraintUses(context, audio);
if (!video.IsNull())
CountVideoConstraintUses(context, video);
UserMediaRequest* const result = MakeGarbageCollected<UserMediaRequest>(
context, client, media_type, audio, video, options->preferCurrentTab(),
options->getControllerOr(nullptr), callbacks, surface);
// The default is to include.
// Note that this option is no-op if audio is not requested.
result->set_exclude_system_audio(
options->hasSystemAudio() &&
options->systemAudio().AsEnum() ==
V8DisplayMediaIncludeOrExclude::Enum::kExclude);
if (RuntimeEnabledFeatures::GetDisplayMediaWindowAudioCaptureEnabled()) {
// Default is kSystem
mojom::blink::WindowAudioPreference value =
mojom::blink::WindowAudioPreference::kSystem;
if (options->hasWindowAudio()) {
switch (options->windowAudio().AsEnum()) {
case V8DisplayMediaSystemWindowOrExclude::Enum::kExclude:
value = mojom::blink::WindowAudioPreference::kExclude;
break;
case V8DisplayMediaSystemWindowOrExclude::Enum::kWindow:
value = mojom::blink::WindowAudioPreference::kWindow;
break;
case V8DisplayMediaSystemWindowOrExclude::Enum::kSystem:
value = mojom::blink::WindowAudioPreference::kSystem;
break;
}
}
result->set_window_audio_preference(value);
if (media_type == UserMediaRequestType::kDisplayMedia) {
std::optional<V8DisplayMediaSystemWindowOrExclude::Enum>
window_audio_preference;
if (options->hasWindowAudio()) {
window_audio_preference = options->windowAudio().AsEnum();
}
RecordGetDisplayMediaSystemWindowOrExcludeConstraintUma(
window_audio_preference,
"Media.GetDisplayMedia.Constraints.WindowAudio");
}
} else {
// if the feature is not enabled, we'll set kExclude to never share audio
// when sharing windows.
result->set_window_audio_preference(
mojom::blink::WindowAudioPreference::kExclude);
}
if (media_type == UserMediaRequestType::kDisplayMedia) {
std::optional<V8DisplayMediaIncludeOrExclude::Enum> include_or_exclude;
if (options->hasSystemAudio()) {
include_or_exclude = options->systemAudio().AsEnum();
}
RecordGetDisplayMediaIncludeExcludeConstraintUma(
include_or_exclude, "Media.GetDisplayMedia.Constraints.SystemAudio");
}
// The default is to include.
const bool exclude_self_browser_surface =
options->hasSelfBrowserSurface() &&
options->selfBrowserSurface().AsEnum() ==
V8DisplayMediaIncludeOrExclude::Enum::kExclude;
if (exclude_self_browser_surface && options->preferCurrentTab()) {
exception_state.ThrowTypeError(
"Self-contradictory configuration (preferCurrentTab and "
"selfBrowserSurface=exclude).");
return nullptr;
}
result->set_exclude_self_browser_surface(exclude_self_browser_surface);
if (media_type == UserMediaRequestType::kDisplayMedia) {
std::optional<V8DisplayMediaIncludeOrExclude::Enum> include_or_exclude;
if (options->hasSelfBrowserSurface()) {
include_or_exclude = options->selfBrowserSurface().AsEnum();
}
RecordGetDisplayMediaIncludeExcludeConstraintUma(
include_or_exclude,
"Media.GetDisplayMedia.Constraints.SelfBrowserSurface");
}
mojom::blink::PreferredDisplaySurface preferred_display_surface =
mojom::blink::PreferredDisplaySurface::NO_PREFERENCE;
if (display_surface_constraint == "monitor") {
preferred_display_surface = mojom::blink::PreferredDisplaySurface::MONITOR;
} else if (display_surface_constraint == "window") {
preferred_display_surface = mojom::blink::PreferredDisplaySurface::WINDOW;
} else if (display_surface_constraint == "browser") {
preferred_display_surface = mojom::blink::PreferredDisplaySurface::BROWSER;
}
result->set_preferred_display_surface(preferred_display_surface);
if (media_type == UserMediaRequestType::kDisplayMedia)
RecordPreferredDisplaySurfaceConstraintUma(preferred_display_surface);
// The default is to request dynamic surface switching.
result->set_dynamic_surface_switching_requested(
!options->hasSurfaceSwitching() ||
options->surfaceSwitching().AsEnum() ==
V8DisplayMediaIncludeOrExclude::Enum::kInclude);
if (media_type == UserMediaRequestType::kDisplayMedia) {
std::optional<V8DisplayMediaIncludeOrExclude::Enum> include_or_exclude;
if (options->hasSurfaceSwitching()) {
include_or_exclude = options->surfaceSwitching().AsEnum();
}
RecordGetDisplayMediaIncludeExcludeConstraintUma(
include_or_exclude,
"Media.GetDisplayMedia.Constraints.SurfaceSwitching");
}
// The default is to include.
const bool exclude_monitor_type_surfaces =
options->hasMonitorTypeSurfaces() &&
options->monitorTypeSurfaces().AsEnum() ==
V8DisplayMediaIncludeOrExclude::Enum::kExclude;
if (exclude_monitor_type_surfaces &&
display_surface_constraint == "monitor") {
exception_state.ThrowTypeError(
"Self-contradictory configuration (displaySurface=monitor and "
"monitorTypeSurfaces=exclude).");
return nullptr;
}
result->set_exclude_monitor_type_surfaces(exclude_monitor_type_surfaces);
if (media_type == UserMediaRequestType::kDisplayMedia) {
std::optional<V8DisplayMediaIncludeOrExclude::Enum> include_or_exclude;
if (options->hasMonitorTypeSurfaces()) {
include_or_exclude = options->monitorTypeSurfaces().AsEnum();
}
RecordGetDisplayMediaIncludeExcludeConstraintUma(
include_or_exclude,
"Media.GetDisplayMedia.Constraints.MonitorTypeSurfaces");
}
result->set_suppress_local_audio_playback(
suppress_local_audio_playback.value_or(false));
if (media_type == UserMediaRequestType::kDisplayMedia) {
RecordBooleanConstraintUma(
suppress_local_audio_playback,
"Media.GetDisplayMedia.Constraints.SuppressLocalAudioPlayback");
}
result->set_restrict_own_audio(restrict_own_audio.value_or(false));
if (RuntimeEnabledFeatures::RestrictOwnAudioEnabled()) {
if (media_type == UserMediaRequestType::kDisplayMedia) {
RecordBooleanConstraintUma(
restrict_own_audio,
"Media.GetDisplayMedia.Constraints.RestrictOwnAudio");
}
}
return result;
}
UserMediaRequest* UserMediaRequest::CreateForTesting(
const MediaConstraints& audio,
const MediaConstraints& video,
bool is_user_media) {
return MakeGarbageCollected<UserMediaRequest>(
nullptr, nullptr,
is_user_media ? UserMediaRequestType::kUserMedia
: UserMediaRequestType::kDisplayMedia,
audio, video,
/*should_prefer_current_tab=*/false,
/*capture_controller=*/nullptr, /*callbacks=*/nullptr,
IdentifiableSurface());
}
UserMediaRequest::UserMediaRequest(ExecutionContext* context,
UserMediaClient* client,
UserMediaRequestType media_type,
MediaConstraints audio,
MediaConstraints video,
bool should_prefer_current_tab,
CaptureController* capture_controller,
Callbacks* callbacks,
IdentifiableSurface surface)
: ExecutionContextLifecycleObserver(context),
media_type_(media_type),
audio_(audio),
video_(video),
capture_controller_(capture_controller),
should_prefer_current_tab_(should_prefer_current_tab),
client_(client),
callbacks_(callbacks),
surface_(surface) {
}
UserMediaRequest::~UserMediaRequest() = default;
UserMediaRequestType 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_;
}
MediaStreamType UserMediaRequest::AudioMediaStreamType() const {
if (!Audio()) {
return MediaStreamType::NO_SERVICE;
}
if (MediaRequestType() == UserMediaRequestType::kDisplayMedia) {
return MediaStreamType::DISPLAY_AUDIO_CAPTURE;
}
if (MediaRequestType() == UserMediaRequestType::kAllScreensMedia) {
return MediaStreamType::NO_SERVICE;
}
DCHECK_EQ(UserMediaRequestType::kUserMedia, MediaRequestType());
// Check if this is a getUserMedia display capture.
const MediaConstraints& constraints = AudioConstraints();
String source_constraint =
constraints.Basic().media_stream_source.Exact().empty()
? String()
: String(constraints.Basic().media_stream_source.Exact()[0]);
if (!source_constraint.empty()) {
// This is a getUserMedia display capture call.
if (source_constraint == blink::kMediaStreamSourceTab) {
return MediaStreamType::GUM_TAB_AUDIO_CAPTURE;
} else if (source_constraint == blink::kMediaStreamSourceDesktop ||
source_constraint == blink::kMediaStreamSourceSystem) {
return MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE;
}
return MediaStreamType::NO_SERVICE;
}
return MediaStreamType::DEVICE_AUDIO_CAPTURE;
}
MediaStreamType UserMediaRequest::VideoMediaStreamType() const {
if (!Video()) {
return MediaStreamType::NO_SERVICE;
}
if (MediaRequestType() == UserMediaRequestType::kDisplayMedia) {
return should_prefer_current_tab()
? MediaStreamType::DISPLAY_VIDEO_CAPTURE_THIS_TAB
: MediaStreamType::DISPLAY_VIDEO_CAPTURE;
}
if (MediaRequestType() == UserMediaRequestType::kAllScreensMedia) {
DCHECK(!should_prefer_current_tab());
return MediaStreamType::DISPLAY_VIDEO_CAPTURE_SET;
}
DCHECK_EQ(UserMediaRequestType::kUserMedia, MediaRequestType());
// Check if this is a getUserMedia display capture.
const MediaConstraints& constraints = VideoConstraints();
String source_constraint =
constraints.Basic().media_stream_source.Exact().empty()
? String()
: String(constraints.Basic().media_stream_source.Exact()[0]);
if (!source_constraint.empty()) {
// This is a getUserMedia display capture call.
if (source_constraint == blink::kMediaStreamSourceTab) {
return MediaStreamType::GUM_TAB_VIDEO_CAPTURE;
} else if (source_constraint == blink::kMediaStreamSourceDesktop ||
source_constraint == blink::kMediaStreamSourceScreen) {
return MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE;
}
return MediaStreamType::NO_SERVICE;
}
return MediaStreamType::DEVICE_VIDEO_CAPTURE;
}
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(
network::mojom::PermissionsPolicyFeature::kMicrophone,
ReportOptions::kReportOnFailure)) {
UseCounter::Count(
window, WebFeature::kMicrophoneDisabledByFeaturePolicyEstimate);
}
}
if (Video() &&
VideoMediaStreamType() != MediaStreamType::DISPLAY_VIDEO_CAPTURE_SET) {
if (!window->IsFeatureEnabled(
network::mojom::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 (client_)
client_->RequestUserMedia(this);
}
void UserMediaRequest::Succeed(
const GCedMediaStreamDescriptorVector& streams_descriptors) {
DCHECK(!is_resolved_);
DCHECK(transferred_track_ == nullptr);
if (!GetExecutionContext())
return;
MediaStreamSet::Create(GetExecutionContext(), streams_descriptors,
media_type_,
BindOnce(&UserMediaRequest::OnMediaStreamsInitialized,
WrapPersistent(this)));
}
void UserMediaRequest::OnMediaStreamInitialized(MediaStream* stream) {
OnMediaStreamsInitialized({stream});
}
void UserMediaRequest::OnMediaStreamsInitialized(MediaStreamVector streams) {
DCHECK(!is_resolved_);
for (const Member<MediaStream>& stream : streams) {
MediaStreamTrackVector audio_tracks = stream->getAudioTracks();
for (const auto& audio_track : audio_tracks)
audio_track->SetInitialConstraints(audio_);
MediaStreamTrackVector video_tracks = stream->getVideoTracks();
for (const auto& video_track : video_tracks)
video_track->SetInitialConstraints(video_);
RecordIdentifiabilityMetric(
surface_, GetExecutionContext(),
IdentifiabilityBenignStringToken(g_empty_string));
if (auto* window = GetWindow()) {
if (media_type_ == UserMediaRequestType::kUserMedia) {
PeerConnectionTracker::From(*window).TrackGetUserMediaSuccess(this,
stream);
} else if (media_type_ == UserMediaRequestType::kDisplayMedia ||
media_type_ == UserMediaRequestType::kAllScreensMedia) {
PeerConnectionTracker::From(*window).TrackGetDisplayMediaSuccess(
this, stream);
} else {
NOTREACHED();
}
}
}
// After this call, the execution context may be invalid.
callbacks_->OnSuccess(streams, capture_controller_);
is_resolved_ = true;
}
void UserMediaRequest::FailConstraint(const String& constraint_name,
const String& message) {
DCHECK(!constraint_name.empty());
DCHECK(!is_resolved_);
if (!GetExecutionContext())
return;
RecordIdentifiabilityMetric(surface_, GetExecutionContext(),
IdentifiabilityBenignStringToken(message));
if (auto* window = GetWindow()) {
if (media_type_ == UserMediaRequestType::kUserMedia) {
PeerConnectionTracker::From(*window).TrackGetUserMediaFailure(
this, "OverConstrainedError", message);
} else if (media_type_ == UserMediaRequestType::kDisplayMedia ||
media_type_ == UserMediaRequestType::kAllScreensMedia) {
PeerConnectionTracker::From(*window).TrackGetDisplayMediaFailure(
this, "OverConstrainedError", message);
} else {
NOTREACHED();
}
}
// After this call, the execution context may be invalid.
callbacks_->OnError(
nullptr,
MakeGarbageCollected<V8MediaStreamError>(
OverconstrainedError::Create(constraint_name, message)),
capture_controller_, UserMediaRequestResult::kOverConstrainedError);
is_resolved_ = true;
}
void UserMediaRequest::Fail(Result error, const String& message) {
DCHECK(!is_resolved_);
if (!GetExecutionContext()) {
return;
}
std::optional<DOMExceptionCode> exception_code;
std::optional<UserMediaRequestResult> result_enum;
switch (error) {
case Result::OK:
NOTREACHED(); // Not a failure.
case Result::PERMISSION_DENIED:
case Result::PERMISSION_DENIED_BY_SYSTEM:
case Result::PERMISSION_DISMISSED:
case Result::NO_TRANSIENT_ACTIVATION:
// TODO(crbug.com/453600255): Use `result_enum` kInvalidStateError for
// NO_TRANSIENT_ACTIVATION once all new enum values are added.
case Result::KILL_SWITCH_ON:
exception_code = DOMExceptionCode::kNotAllowedError;
result_enum = UserMediaRequestResult::kNotAllowedError;
break;
case Result::PERMISSION_DENIED_BY_USER:
exception_code = DOMExceptionCode::kNotAllowedError;
result_enum = UserMediaRequestResult::kNotAllowedByUserError;
break;
case Result::NO_HARDWARE:
exception_code = DOMExceptionCode::kNotFoundError;
result_enum = UserMediaRequestResult::kNotFoundError;
break;
case Result::INVALID_STATE:
case Result::FAILED_DUE_TO_SHUTDOWN:
case Result::TAB_CAPTURE_FAILURE:
case Result::SCREEN_CAPTURE_FAILURE:
case Result::CAPTURE_FAILURE:
case Result::START_TIMEOUT:
exception_code = DOMExceptionCode::kAbortError;
result_enum = UserMediaRequestResult::kAbortError;
break;
case Result::TRACK_START_FAILURE_AUDIO:
case Result::TRACK_START_FAILURE_VIDEO:
case Result::AUDIO_DEVICE_SOCKET_ERROR:
case Result::DEVICE_IN_USE:
exception_code = DOMExceptionCode::kNotReadableError;
result_enum = UserMediaRequestResult::kNotReadableError;
break;
case Result::NOT_SUPPORTED:
exception_code = DOMExceptionCode::kNotSupportedError;
result_enum = UserMediaRequestResult::kNotSupportedError;
break;
case Result::INVALID_SECURITY_ORIGIN:
exception_code = DOMExceptionCode::kSecurityError;
result_enum = UserMediaRequestResult::kSecurityError;
break;
case Result::CONSTRAINT_NOT_SATISFIED:
case Result::REQUEST_CANCELLED:
// TODO(crbug.com/416456028): Either handle these or document why
// they cannot be encountered by this method.
NOTREACHED();
case Result::NUM_MEDIA_REQUEST_RESULTS:
NOTREACHED(); // Not a valid enum value.
}
CHECK(exception_code.has_value());
CHECK(result_enum.has_value());
RecordIdentifiabilityMetric(surface_, GetExecutionContext(),
IdentifiabilityBenignStringToken(message));
if (auto* window = GetWindow()) {
if (media_type_ == UserMediaRequestType::kUserMedia) {
PeerConnectionTracker::From(*window).TrackGetUserMediaFailure(
this, DOMException::GetErrorName(*exception_code), message);
} else if (media_type_ == UserMediaRequestType::kDisplayMedia ||
media_type_ == UserMediaRequestType::kAllScreensMedia) {
PeerConnectionTracker::From(*window).TrackGetDisplayMediaFailure(
this, DOMException::GetErrorName(*exception_code), message);
} else {
NOTREACHED();
}
}
// After this call, the execution context may be invalid.
callbacks_->OnError(
nullptr,
MakeGarbageCollected<V8MediaStreamError>(
MakeGarbageCollected<DOMException>(*exception_code, message)),
capture_controller_, *result_enum);
is_resolved_ = true;
}
void UserMediaRequest::ContextDestroyed() {
if (!is_resolved_)
blink::WebRtcLogMessage("UMR::ContextDestroyed. Request not resolved.");
if (client_) {
client_->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")),
capture_controller_, UserMediaRequestResult::kContextDestroyed);
}
client_ = nullptr;
}
}
void UserMediaRequest::SetTransferredTrackComponent(
MediaStreamComponent* component) {
transferred_track_->SetComponentImplementation(component);
}
void UserMediaRequest::FinalizeTransferredTrackInitialization(
const GCedMediaStreamDescriptorVector& streams_descriptors) {
DCHECK(transferred_track_);
DCHECK_EQ(streams_descriptors.size(), 1u);
if (!GetExecutionContext())
return;
MediaStream::Create(GetExecutionContext(), streams_descriptors[0],
transferred_track_,
BindOnce(&UserMediaRequest::OnMediaStreamInitialized,
WrapPersistent(this)));
}
void UserMediaRequest::Trace(Visitor* visitor) const {
visitor->Trace(client_);
visitor->Trace(callbacks_);
visitor->Trace(transferred_track_);
visitor->Trace(capture_controller_);
ExecutionContextLifecycleObserver::Trace(visitor);
}
} // namespace blink