blob: bbac178d454b45216c8761a99bd2f830b605d011 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/modules/imagecapture/image_capture.h"
#include "base/time/time.h"
#include "media/base/video_frame.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/web/web_heap.h"
#include "third_party/blink/renderer/bindings/core/v8/script_function.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_tester.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_union_string_stringsequence.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_constrain_boolean_parameters.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_constrain_dom_string_parameters.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_constrain_double_range.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_constrain_point_2d_parameters.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_media_settings_range.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_point_2d.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_union_boolean_constrainbooleanparameters.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_union_boolean_constraindoublerange_double.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_union_constraindomstringparameters_string_stringsequence.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_union_constraindoublerange_double.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_union_constrainpoint2dparameters_point2dsequence.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/imagebitmap/image_bitmap.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/modules/imagecapture/image_capture.h"
#include "third_party/blink/renderer/modules/mediastream/media_stream_video_capturer_source.h"
#include "third_party/blink/renderer/modules/mediastream/media_stream_video_track.h"
#include "third_party/blink/renderer/modules/mediastream/mock_media_stream_track.h"
#include "third_party/blink/renderer/modules/mediastream/mock_video_capturer_source.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/testing/io_task_runner_testing_platform_support.h"
#include "third_party/blink/renderer/platform/testing/task_environment.h"
namespace blink {
namespace {
using ExpectHasExposureModeAndFocusMode =
base::StrongAlias<class ExpectHasExposureModeAndFocusModeTag, bool>;
using ExpectHasPanTiltZoom =
base::StrongAlias<class ExpectHasPanTiltZoomTag, bool>;
using PopulatePanTiltZoom =
base::StrongAlias<class PopulatePanTiltZoomZoomTag, bool>;
using testing::_;
using testing::NiceMock;
using testing::Return;
constexpr double kExposureCompensationDelta = 1;
constexpr double kExposureTimeDelta = 2;
constexpr double kColorTemperatureDelta = 3;
constexpr double kIsoDelta = 4;
constexpr double kBrightnessDelta = 5;
constexpr double kContrastDelta = 6;
constexpr double kSaturationDelta = 7;
constexpr double kSharpnessDelta = 8;
constexpr double kFocusDistanceDelta = 9;
constexpr double kPanDelta = 10;
constexpr double kTiltDelta = 11;
constexpr double kZoomDelta = 12;
// CaptureErrorFunction implements a javascript function which captures
// name, message and constraint of the exception passed as its argument.
class CaptureErrorFunction final
: public ThenCallable<IDLAny, CaptureErrorFunction> {
public:
CaptureErrorFunction() = default;
bool WasCalled() const { return was_called_; }
const String& Name() const { return name_; }
const String& Message() const { return message_; }
const String& Constraint() const { return constraint_; }
void React(ScriptState* script_state, ScriptValue value) {
was_called_ = true;
v8::Isolate* isolate = script_state->GetIsolate();
v8::Local<v8::Context> context = script_state->GetContext();
v8::Local<v8::Object> error_object =
value.V8Value()->ToObject(context).ToLocalChecked();
v8::Local<v8::Value> name =
error_object->Get(context, V8String(isolate, "name")).ToLocalChecked();
name_ = ToCoreString(isolate, name->ToString(context).ToLocalChecked());
v8::Local<v8::Value> message =
error_object->Get(context, V8String(isolate, "message"))
.ToLocalChecked();
message_ =
ToCoreString(isolate, message->ToString(context).ToLocalChecked());
v8::Local<v8::Value> constraint =
error_object->Get(context, V8String(isolate, "constraint"))
.ToLocalChecked();
constraint_ =
ToCoreString(isolate, constraint->ToString(context).ToLocalChecked());
}
private:
bool was_called_ = false;
String name_;
String message_;
String constraint_;
};
// These traits and type aliases simplify mapping from bare value types (bool,
// double, sequence, string) to constrain value dictionary types.
template <typename T>
struct ConstrainWithDictionaryTraits;
template <>
struct ConstrainWithDictionaryTraits<bool> {
using DictionaryType = ConstrainBooleanParameters;
};
template <>
struct ConstrainWithDictionaryTraits<double> {
using DictionaryType = ConstrainDoubleRange;
};
template <>
struct ConstrainWithDictionaryTraits<HeapVector<Member<Point2D>>> {
using DictionaryType = ConstrainPoint2DParameters;
};
template <>
struct ConstrainWithDictionaryTraits<String> {
using DictionaryType = ConstrainDOMStringParameters;
};
template <>
struct ConstrainWithDictionaryTraits<Vector<String>> {
using DictionaryType = ConstrainDOMStringParameters;
};
template <typename T>
using ConstrainWithDictionaryType =
ConstrainWithDictionaryTraits<T>::DictionaryType;
// The `ConstrainDOMStringParameters` dictionary type has `exact` and `ideal`
// members of type `(DOMString or sequence<DOMString>)`.
// https://w3c.github.io/mediacapture-main/#dom-constraindomstringparameters
V8UnionStringOrStringSequence* CreateDictionaryMemberValue(
const String& value) {
return MakeGarbageCollected<V8UnionStringOrStringSequence>(value);
}
V8UnionStringOrStringSequence* CreateDictionaryMemberValue(
const Vector<String>& value) {
return MakeGarbageCollected<V8UnionStringOrStringSequence>(value);
}
// All the other constrain value dictionary types (ConstrainBooleanParameters,
// ConstrainDoubleRange and ConstrainPoint2DParameters) have `exact` and
// `ideal` members of non-union types.
// https://w3c.github.io/mediacapture-main/#dom-constrainbooleanparameters
// https://w3c.github.io/mediacapture-main/#dom-constraindoublerange
// https://w3c.github.io/mediacapture-main/#dom-constrainpoint2dparameters
template <typename T>
const T& CreateDictionaryMemberValue(const T& value) {
return value;
}
// This creator creates bare value (bool, double, sequence, string)
// constraints.
struct ConstrainWithBareValueCreator {
template <typename T>
static const T& Create(const T& bare_value) {
return bare_value;
}
};
// This creator creates constrain value dictionary constraints without members.
struct ConstrainWithEmptyDictionaryCreator {
template <typename T>
static auto* Create(const T&) {
return ConstrainWithDictionaryType<T>::Create();
}
};
// This creator creates constrain value dictionary constraints with `exact`
// members.
struct ConstrainWithExactDictionaryCreator {
template <typename T>
static auto* Create(const T& exact) {
auto* constrain_value = ConstrainWithDictionaryType<T>::Create();
constrain_value->setExact(CreateDictionaryMemberValue(exact));
return constrain_value;
}
};
// This creator creates constrain value dictionary constraints with `ideal`
// members.
struct ConstrainWithIdealDictionaryCreator {
template <typename T>
static auto* Create(const T& ideal) {
auto* constrain_value = ConstrainWithDictionaryType<T>::Create();
constrain_value->setIdeal(CreateDictionaryMemberValue(ideal));
return constrain_value;
}
};
// This creator creates constrain value dictionary constraints with `max`
// members.
struct ConstrainWithMaxDictionaryCreator {
template <typename T>
static auto* Create(const T& max) {
auto* constrain_value = ConstrainWithDictionaryType<T>::Create();
constrain_value->setMax(max);
return constrain_value;
}
};
// This creator creates constrain value dictionary constraints with `max`
// members for numeric (double) values and empty constrain value dictionary
// constraints for non-numeric (bool, sequence, string) values.
struct ConstrainWithMaxOrEmptyDictionaryCreator {
static auto* Create(double max) {
return ConstrainWithMaxDictionaryCreator::Create(max);
}
template <typename T>
static auto* Create(const T&) {
return ConstrainWithDictionaryType<T>::Create();
}
};
// This creator creates constrain value dictionary constraints with `min`
// members.
struct ConstrainWithMinDictionaryCreator {
template <typename T>
static auto* Create(const T& min) {
auto* constrain_value = ConstrainWithDictionaryType<T>::Create();
constrain_value->setMin(min);
return constrain_value;
}
};
// This creator creates constrain value dictionary constraints with `min`
// members for numeric (double) values and empty constrain value dictionary
// constraints for non-numeric (bool, sequence, string) values.
struct ConstrainWithMinOrEmptyDictionaryCreator {
static auto* Create(double min) {
return ConstrainWithMinDictionaryCreator::Create(min);
}
template <typename T>
static auto* Create(const T&) {
return ConstrainWithDictionaryType<T>::Create();
}
};
MediaSettingsRange* CreateMediaSettingsRange(double min,
double max,
double step) {
auto* range = MediaSettingsRange::Create();
range->setMin(min);
range->setMax(max);
range->setStep(step);
return range;
}
MediaSettingsRange* CreateMediaSettingsRange(const char (&str)[3]) {
int min = (static_cast<int>(str[0]) << 16) + (static_cast<int>(str[1]) << 8);
return CreateMediaSettingsRange(min, min + 16, 8);
}
Point2D* CreatePoint2D(double x, double y) {
auto* point = Point2D::Create();
point->setX(x);
point->setY(y);
return point;
}
double RangeMean(const MediaSettingsRange* range) {
return (range->min() + range->max()) / 2;
}
void CheckExactValues(
const media::mojom::blink::PhotoSettingsPtr& settings,
const MediaTrackCapabilities* all_capabilities,
ExpectHasPanTiltZoom expect_has_pan_tilt_zoom = ExpectHasPanTiltZoom(true),
ExpectHasExposureModeAndFocusMode expect_has_exposure_mode_and_focus_mode =
ExpectHasExposureModeAndFocusMode(true)) {
EXPECT_TRUE(settings->has_white_balance_mode);
EXPECT_EQ(settings->white_balance_mode,
media::mojom::blink::MeteringMode::CONTINUOUS);
if (expect_has_exposure_mode_and_focus_mode) {
EXPECT_TRUE(settings->has_exposure_mode);
EXPECT_EQ(settings->exposure_mode,
media::mojom::blink::MeteringMode::MANUAL);
EXPECT_TRUE(settings->has_focus_mode);
EXPECT_EQ(settings->focus_mode, media::mojom::blink::MeteringMode::NONE);
} else {
EXPECT_FALSE(settings->has_exposure_mode);
EXPECT_FALSE(settings->has_focus_mode);
}
ASSERT_EQ(settings->points_of_interest.size(), 3u);
EXPECT_EQ(settings->points_of_interest[0]->x, 0.0);
EXPECT_EQ(settings->points_of_interest[0]->y, 0.0);
EXPECT_EQ(settings->points_of_interest[1]->x, 0.25);
EXPECT_EQ(settings->points_of_interest[1]->y, 0.75);
EXPECT_EQ(settings->points_of_interest[2]->x, 1.0);
EXPECT_EQ(settings->points_of_interest[2]->y, 1.0);
EXPECT_TRUE(settings->has_exposure_compensation);
EXPECT_EQ(settings->exposure_compensation,
all_capabilities->exposureCompensation()->min() +
kExposureCompensationDelta);
EXPECT_TRUE(settings->has_exposure_time);
EXPECT_EQ(settings->exposure_time,
all_capabilities->exposureTime()->min() + kExposureTimeDelta);
EXPECT_TRUE(settings->has_color_temperature);
EXPECT_EQ(
settings->color_temperature,
all_capabilities->colorTemperature()->min() + kColorTemperatureDelta);
EXPECT_TRUE(settings->has_iso);
EXPECT_EQ(settings->iso, all_capabilities->iso()->min() + kIsoDelta);
EXPECT_TRUE(settings->has_brightness);
EXPECT_EQ(settings->brightness,
all_capabilities->brightness()->min() + kBrightnessDelta);
EXPECT_TRUE(settings->has_contrast);
EXPECT_EQ(settings->contrast,
all_capabilities->contrast()->min() + kContrastDelta);
EXPECT_TRUE(settings->has_saturation);
EXPECT_EQ(settings->saturation,
all_capabilities->saturation()->min() + kSaturationDelta);
EXPECT_TRUE(settings->has_sharpness);
EXPECT_EQ(settings->sharpness,
all_capabilities->sharpness()->min() + kSharpnessDelta);
EXPECT_TRUE(settings->has_focus_distance);
EXPECT_EQ(settings->focus_distance,
all_capabilities->focusDistance()->min() + kFocusDistanceDelta);
if (expect_has_pan_tilt_zoom) {
EXPECT_TRUE(settings->has_pan);
EXPECT_EQ(settings->pan, all_capabilities->pan()->min() + kPanDelta);
EXPECT_TRUE(settings->has_tilt);
EXPECT_EQ(settings->tilt, all_capabilities->tilt()->min() + kTiltDelta);
EXPECT_TRUE(settings->has_zoom);
EXPECT_EQ(settings->zoom, all_capabilities->zoom()->min() + kZoomDelta);
} else {
EXPECT_FALSE(settings->has_pan);
EXPECT_FALSE(settings->has_tilt);
EXPECT_FALSE(settings->has_zoom);
}
EXPECT_TRUE(settings->has_torch);
EXPECT_EQ(settings->torch, true);
EXPECT_TRUE(settings->has_background_blur_mode);
EXPECT_EQ(settings->background_blur_mode,
media::mojom::blink::BackgroundBlurMode::BLUR);
EXPECT_TRUE(settings->eye_gaze_correction_mode.has_value());
EXPECT_EQ(settings->eye_gaze_correction_mode.value(),
media::mojom::blink::EyeGazeCorrectionMode::OFF);
EXPECT_TRUE(settings->has_face_framing_mode);
EXPECT_EQ(settings->face_framing_mode,
media::mojom::blink::MeteringMode::CONTINUOUS);
EXPECT_TRUE(settings->background_segmentation_mask_state.has_value());
EXPECT_FALSE(settings->background_segmentation_mask_state.value());
}
void CheckMaxValues(const media::mojom::blink::PhotoSettingsPtr& settings,
const MediaTrackCapabilities* all_capabilities,
const MediaTrackSettings* default_settings,
ExpectHasPanTiltZoom expect_has_pan_tilt_zoom =
ExpectHasPanTiltZoom(true)) {
EXPECT_FALSE(settings->has_white_balance_mode);
EXPECT_FALSE(settings->has_exposure_mode);
EXPECT_FALSE(settings->has_focus_mode);
EXPECT_EQ(settings->points_of_interest.size(), 0u);
EXPECT_TRUE(settings->has_exposure_compensation);
EXPECT_EQ(settings->exposure_compensation,
std::min(all_capabilities->exposureCompensation()->min() +
kExposureCompensationDelta,
default_settings->exposureCompensation()));
EXPECT_TRUE(settings->has_exposure_time);
EXPECT_EQ(
settings->exposure_time,
std::min(all_capabilities->exposureTime()->min() + kExposureTimeDelta,
default_settings->exposureTime()));
EXPECT_TRUE(settings->has_color_temperature);
EXPECT_EQ(settings->color_temperature,
std::min(all_capabilities->colorTemperature()->min() +
kColorTemperatureDelta,
default_settings->colorTemperature()));
EXPECT_TRUE(settings->has_iso);
EXPECT_EQ(settings->iso, std::min(all_capabilities->iso()->min() + kIsoDelta,
default_settings->iso()));
EXPECT_TRUE(settings->has_brightness);
EXPECT_EQ(settings->brightness,
std::min(all_capabilities->brightness()->min() + kBrightnessDelta,
default_settings->brightness()));
EXPECT_TRUE(settings->has_contrast);
EXPECT_EQ(settings->contrast,
std::min(all_capabilities->contrast()->min() + kContrastDelta,
default_settings->contrast()));
EXPECT_TRUE(settings->has_saturation);
EXPECT_EQ(settings->saturation,
std::min(all_capabilities->saturation()->min() + kSaturationDelta,
default_settings->saturation()));
EXPECT_TRUE(settings->has_sharpness);
EXPECT_EQ(settings->sharpness,
std::min(all_capabilities->sharpness()->min() + kSharpnessDelta,
default_settings->sharpness()));
EXPECT_TRUE(settings->has_focus_distance);
EXPECT_EQ(
settings->focus_distance,
std::min(all_capabilities->focusDistance()->min() + kFocusDistanceDelta,
default_settings->focusDistance()));
if (expect_has_pan_tilt_zoom) {
EXPECT_TRUE(settings->has_pan);
EXPECT_EQ(settings->pan,
std::min(all_capabilities->pan()->min() + kPanDelta,
default_settings->pan()));
EXPECT_TRUE(settings->has_tilt);
EXPECT_EQ(settings->tilt,
std::min(all_capabilities->tilt()->min() + kTiltDelta,
default_settings->tilt()));
EXPECT_TRUE(settings->has_zoom);
EXPECT_EQ(settings->zoom,
std::min(all_capabilities->zoom()->min() + kZoomDelta,
default_settings->zoom()));
} else {
EXPECT_FALSE(settings->has_pan);
EXPECT_FALSE(settings->has_tilt);
EXPECT_FALSE(settings->has_zoom);
}
EXPECT_FALSE(settings->has_torch);
EXPECT_FALSE(settings->has_background_blur_mode);
EXPECT_FALSE(settings->eye_gaze_correction_mode.has_value());
EXPECT_FALSE(settings->has_face_framing_mode);
EXPECT_FALSE(settings->background_segmentation_mask_state.has_value());
}
void CheckMinValues(const media::mojom::blink::PhotoSettingsPtr& settings,
const MediaTrackCapabilities* all_capabilities,
const MediaTrackSettings* default_settings,
ExpectHasPanTiltZoom expect_has_pan_tilt_zoom =
ExpectHasPanTiltZoom(true)) {
EXPECT_FALSE(settings->has_white_balance_mode);
EXPECT_FALSE(settings->has_exposure_mode);
EXPECT_FALSE(settings->has_focus_mode);
EXPECT_EQ(settings->points_of_interest.size(), 0u);
EXPECT_TRUE(settings->has_exposure_compensation);
EXPECT_EQ(settings->exposure_compensation,
std::max(all_capabilities->exposureCompensation()->min() +
kExposureCompensationDelta,
default_settings->exposureCompensation()));
EXPECT_TRUE(settings->has_exposure_time);
EXPECT_EQ(
settings->exposure_time,
std::max(all_capabilities->exposureTime()->min() + kExposureTimeDelta,
default_settings->exposureTime()));
EXPECT_TRUE(settings->has_color_temperature);
EXPECT_EQ(settings->color_temperature,
std::max(all_capabilities->colorTemperature()->min() +
kColorTemperatureDelta,
default_settings->colorTemperature()));
EXPECT_TRUE(settings->has_iso);
EXPECT_EQ(settings->iso, std::max(all_capabilities->iso()->min() + kIsoDelta,
default_settings->iso()));
EXPECT_TRUE(settings->has_brightness);
EXPECT_EQ(settings->brightness,
std::max(all_capabilities->brightness()->min() + kBrightnessDelta,
default_settings->brightness()));
EXPECT_TRUE(settings->has_contrast);
EXPECT_EQ(settings->contrast,
std::max(all_capabilities->contrast()->min() + kContrastDelta,
default_settings->contrast()));
EXPECT_TRUE(settings->has_saturation);
EXPECT_EQ(settings->saturation,
std::max(all_capabilities->saturation()->min() + kSaturationDelta,
default_settings->saturation()));
EXPECT_TRUE(settings->has_sharpness);
EXPECT_EQ(settings->sharpness,
std::max(all_capabilities->sharpness()->min() + kSharpnessDelta,
default_settings->sharpness()));
EXPECT_TRUE(settings->has_focus_distance);
EXPECT_EQ(
settings->focus_distance,
std::max(all_capabilities->focusDistance()->min() + kFocusDistanceDelta,
default_settings->focusDistance()));
if (expect_has_pan_tilt_zoom) {
EXPECT_TRUE(settings->has_pan);
EXPECT_EQ(settings->pan,
std::max(all_capabilities->pan()->min() + kPanDelta,
default_settings->pan()));
EXPECT_TRUE(settings->has_tilt);
EXPECT_EQ(settings->tilt,
std::max(all_capabilities->tilt()->min() + kTiltDelta,
default_settings->tilt()));
EXPECT_TRUE(settings->has_zoom);
EXPECT_EQ(settings->zoom,
std::max(all_capabilities->zoom()->min() + kZoomDelta,
default_settings->zoom()));
} else {
EXPECT_FALSE(settings->has_pan);
EXPECT_FALSE(settings->has_tilt);
EXPECT_FALSE(settings->has_zoom);
}
EXPECT_FALSE(settings->has_torch);
EXPECT_FALSE(settings->has_background_blur_mode);
EXPECT_FALSE(settings->eye_gaze_correction_mode.has_value());
EXPECT_FALSE(settings->has_face_framing_mode);
EXPECT_FALSE(settings->background_segmentation_mask_state.has_value());
}
void CheckNoValues(const media::mojom::blink::PhotoSettingsPtr& settings,
size_t expected_points_of_interest_size = 0u) {
EXPECT_FALSE(settings->has_white_balance_mode);
EXPECT_FALSE(settings->has_exposure_mode);
EXPECT_FALSE(settings->has_focus_mode);
EXPECT_EQ(settings->points_of_interest.size(),
expected_points_of_interest_size);
EXPECT_FALSE(settings->has_exposure_compensation);
EXPECT_FALSE(settings->has_exposure_time);
EXPECT_FALSE(settings->has_color_temperature);
EXPECT_FALSE(settings->has_iso);
EXPECT_FALSE(settings->has_brightness);
EXPECT_FALSE(settings->has_contrast);
EXPECT_FALSE(settings->has_saturation);
EXPECT_FALSE(settings->has_sharpness);
EXPECT_FALSE(settings->has_focus_distance);
EXPECT_FALSE(settings->has_pan);
EXPECT_FALSE(settings->has_tilt);
EXPECT_FALSE(settings->has_zoom);
EXPECT_FALSE(settings->has_torch);
EXPECT_FALSE(settings->has_background_blur_mode);
EXPECT_FALSE(settings->eye_gaze_correction_mode.has_value());
EXPECT_FALSE(settings->has_face_framing_mode);
EXPECT_FALSE(settings->background_segmentation_mask_state.has_value());
}
template <typename ConstraintCreator>
void PopulateConstraintSet(
MediaTrackConstraintSet* constraint_set,
const MediaTrackCapabilities* all_capabilities,
PopulatePanTiltZoom populate_pan_tilt_zoom = PopulatePanTiltZoom(true)) {
constraint_set->setWhiteBalanceMode(
MakeGarbageCollected<
V8UnionConstrainDOMStringParametersOrStringOrStringSequence>(
ConstraintCreator::Create(all_capabilities->whiteBalanceMode()[0])));
constraint_set->setExposureMode(
MakeGarbageCollected<
V8UnionConstrainDOMStringParametersOrStringOrStringSequence>(
ConstraintCreator::Create(all_capabilities->exposureMode())));
constraint_set->setFocusMode(
MakeGarbageCollected<
V8UnionConstrainDOMStringParametersOrStringOrStringSequence>(
ConstraintCreator::Create(all_capabilities->focusMode())));
HeapVector<Member<Point2D>> points_of_interest = {CreatePoint2D(-0.75, -0.25),
CreatePoint2D(0.25, 0.75),
CreatePoint2D(1.25, 1.75)};
constraint_set->setPointsOfInterest(
MakeGarbageCollected<V8UnionConstrainPoint2DParametersOrPoint2DSequence>(
ConstraintCreator::Create(points_of_interest)));
constraint_set->setExposureCompensation(
MakeGarbageCollected<V8UnionConstrainDoubleRangeOrDouble>(
ConstraintCreator::Create(
all_capabilities->exposureCompensation()->min() +
kExposureCompensationDelta)));
constraint_set->setExposureTime(
MakeGarbageCollected<V8UnionConstrainDoubleRangeOrDouble>(
ConstraintCreator::Create(all_capabilities->exposureTime()->min() +
kExposureTimeDelta)));
constraint_set->setColorTemperature(
MakeGarbageCollected<V8UnionConstrainDoubleRangeOrDouble>(
ConstraintCreator::Create(
all_capabilities->colorTemperature()->min() +
kColorTemperatureDelta)));
constraint_set->setIso(
MakeGarbageCollected<V8UnionConstrainDoubleRangeOrDouble>(
ConstraintCreator::Create(all_capabilities->iso()->min() +
kIsoDelta)));
constraint_set->setBrightness(
MakeGarbageCollected<V8UnionConstrainDoubleRangeOrDouble>(
ConstraintCreator::Create(all_capabilities->brightness()->min() +
kBrightnessDelta)));
constraint_set->setContrast(
MakeGarbageCollected<V8UnionConstrainDoubleRangeOrDouble>(
ConstraintCreator::Create(all_capabilities->contrast()->min() +
kContrastDelta)));
constraint_set->setSaturation(
MakeGarbageCollected<V8UnionConstrainDoubleRangeOrDouble>(
ConstraintCreator::Create(all_capabilities->saturation()->min() +
kSaturationDelta)));
constraint_set->setSharpness(
MakeGarbageCollected<V8UnionConstrainDoubleRangeOrDouble>(
ConstraintCreator::Create(all_capabilities->sharpness()->min() +
kSharpnessDelta)));
constraint_set->setFocusDistance(
MakeGarbageCollected<V8UnionConstrainDoubleRangeOrDouble>(
ConstraintCreator::Create(all_capabilities->focusDistance()->min() +
kFocusDistanceDelta)));
if (populate_pan_tilt_zoom) {
constraint_set->setPan(
MakeGarbageCollected<V8UnionBooleanOrConstrainDoubleRangeOrDouble>(
ConstraintCreator::Create(all_capabilities->pan()->min() +
kPanDelta)));
constraint_set->setTilt(
MakeGarbageCollected<V8UnionBooleanOrConstrainDoubleRangeOrDouble>(
ConstraintCreator::Create(all_capabilities->tilt()->min() +
kTiltDelta)));
constraint_set->setZoom(
MakeGarbageCollected<V8UnionBooleanOrConstrainDoubleRangeOrDouble>(
ConstraintCreator::Create(all_capabilities->zoom()->min() +
kZoomDelta)));
}
constraint_set->setTorch(
MakeGarbageCollected<V8UnionBooleanOrConstrainBooleanParameters>(
ConstraintCreator::Create(true)));
constraint_set->setBackgroundBlur(
MakeGarbageCollected<V8UnionBooleanOrConstrainBooleanParameters>(
ConstraintCreator::Create(all_capabilities->backgroundBlur()[0])));
constraint_set->setEyeGazeCorrection(
MakeGarbageCollected<V8UnionBooleanOrConstrainBooleanParameters>(
ConstraintCreator::Create(all_capabilities->eyeGazeCorrection()[0])));
constraint_set->setFaceFraming(
MakeGarbageCollected<V8UnionBooleanOrConstrainBooleanParameters>(
ConstraintCreator::Create(all_capabilities->faceFraming()[0])));
constraint_set->setBackgroundSegmentationMask(
MakeGarbageCollected<V8UnionBooleanOrConstrainBooleanParameters>(
ConstraintCreator::Create(
all_capabilities->backgroundSegmentationMask()[0])));
}
class MockMediaStreamComponent
: public GarbageCollected<MockMediaStreamComponent>,
public MediaStreamComponent {
public:
virtual ~MockMediaStreamComponent() = default;
MOCK_CONST_METHOD0(Clone, MediaStreamComponent*());
MOCK_CONST_METHOD0(Source, MediaStreamSource*());
MOCK_CONST_METHOD0(Id, String());
MOCK_CONST_METHOD0(UniqueId, int());
MOCK_CONST_METHOD0(GetSourceType, MediaStreamSource::StreamType());
MOCK_CONST_METHOD0(GetSourceName, const String&());
MOCK_CONST_METHOD0(GetReadyState, MediaStreamSource::ReadyState());
MOCK_CONST_METHOD0(Remote, bool());
MOCK_CONST_METHOD0(Enabled, bool());
MOCK_METHOD1(SetEnabled, void(bool));
MOCK_METHOD0(ContentHint, WebMediaStreamTrack::ContentHintType());
MOCK_METHOD1(SetContentHint, void(WebMediaStreamTrack::ContentHintType));
MOCK_CONST_METHOD0(GetPlatformTrack, MediaStreamTrackPlatform*());
MOCK_METHOD1(SetPlatformTrack,
void(std::unique_ptr<MediaStreamTrackPlatform>));
MOCK_METHOD1(GetSettings, void(MediaStreamTrackPlatform::Settings&));
MOCK_METHOD0(GetCaptureHandle, MediaStreamTrackPlatform::CaptureHandle());
MOCK_METHOD0(CreationFrame, WebLocalFrame*());
MOCK_METHOD1(SetCreationFrameGetter,
void(base::RepeatingCallback<WebLocalFrame*()>));
MOCK_METHOD1(AddSourceObserver, void(MediaStreamSource::Observer*));
MOCK_METHOD1(AddSink, void(WebMediaStreamAudioSink*));
MOCK_METHOD4(AddSink,
void(WebMediaStreamSink*,
const VideoCaptureDeliverFrameCB&,
MediaStreamVideoSink::IsSecure,
MediaStreamVideoSink::UsesAlpha));
MOCK_CONST_METHOD0(ToString, String());
};
} // namespace
class ImageCaptureTest : public testing::Test {
public:
ImageCaptureTest()
: component_(MakeGarbageCollected<MockMediaStreamComponent>()),
track_(MakeGarbageCollected<MockMediaStreamTrack>()),
image_capture_(MakeGarbageCollected<ImageCapture>(
/*execution_context=*/nullptr,
track_,
/*pan_tilt_zoom_allowed=*/true,
base::DoNothing(),
base::Milliseconds(1))) {
track_->SetComponent(component_);
}
void TearDown() override { WebHeap::CollectAllGarbageForTesting(); }
void SetupTrackMocks(V8TestingScope& scope,
bool produce_frame_on_add_sink = true) {
produce_frame_on_add_sink_ = produce_frame_on_add_sink;
source_ = std::make_unique<MediaStreamVideoCapturerSource>(
scope.GetFrame().GetTaskRunner(TaskType::kInternalMediaRealTime),
&scope.GetFrame(),
MediaStreamVideoCapturerSource::SourceStoppedCallback(),
std::make_unique<NiceMock<MockVideoCapturerSource>>());
platform_track_ = std::make_unique<MediaStreamVideoTrack>(
source_.get(), WebPlatformMediaStreamSource::ConstraintsOnceCallback(),
/*enabled=*/true);
EXPECT_CALL(*component_, GetPlatformTrack)
.WillRepeatedly(Return(platform_track_.get()));
EXPECT_CALL(*component_, GetSourceType)
.WillRepeatedly(Return(MediaStreamSource::kTypeVideo));
ON_CALL(*component_, AddSink(_, _, _, _))
.WillByDefault([&](WebMediaStreamSink* sink,
const VideoCaptureDeliverFrameCB& callback,
MediaStreamVideoSink::IsSecure is_secure,
MediaStreamVideoSink::UsesAlpha uses_alpha) {
platform_track_->AddSink(sink, callback, is_secure, uses_alpha);
if (produce_frame_on_add_sink_) {
callback.Run(media::VideoFrame::CreateBlackFrame(gfx::Size(1, 1)),
/*estimated_capture_time=*/base::TimeTicks());
}
});
}
protected:
test::TaskEnvironment task_environment_;
Persistent<MockMediaStreamComponent> component_;
Persistent<MockMediaStreamTrack> track_;
Persistent<ImageCapture> image_capture_;
ScopedTestingPlatformSupport<IOTaskRunnerTestingPlatformSupport> platform_;
std::unique_ptr<MediaStreamVideoCapturerSource> source_;
std::unique_ptr<MediaStreamVideoTrack> platform_track_;
bool produce_frame_on_add_sink_ = true;
};
class ImageCaptureConstraintTest : public ImageCaptureTest {
public:
ImageCaptureConstraintTest() {
all_capabilities_ = MediaTrackCapabilities::Create();
all_non_capabilities_ = MediaTrackCapabilities::Create();
all_capabilities_->setWhiteBalanceMode({"continuous", "manual"});
all_non_capabilities_->setWhiteBalanceMode({"none"});
all_capabilities_->setExposureMode({"manual", "none"});
all_non_capabilities_->setExposureMode({"continuous"});
all_capabilities_->setFocusMode({"none", "continuous"});
all_non_capabilities_->setFocusMode({"manual"});
all_capabilities_->setExposureCompensation(CreateMediaSettingsRange("ec"));
all_capabilities_->setExposureTime(CreateMediaSettingsRange("et"));
all_capabilities_->setColorTemperature(CreateMediaSettingsRange("ct"));
all_capabilities_->setIso(CreateMediaSettingsRange("is"));
all_capabilities_->setBrightness(CreateMediaSettingsRange("br"));
all_capabilities_->setContrast(CreateMediaSettingsRange("co"));
all_capabilities_->setSaturation(CreateMediaSettingsRange("sa"));
all_capabilities_->setSharpness(CreateMediaSettingsRange("sh"));
all_capabilities_->setFocusDistance(CreateMediaSettingsRange("fd"));
all_capabilities_->setPan(CreateMediaSettingsRange("pa"));
all_capabilities_->setTilt(CreateMediaSettingsRange("ti"));
all_capabilities_->setZoom(CreateMediaSettingsRange("zo"));
all_capabilities_->setTorch(true);
all_capabilities_->setBackgroundBlur({true});
all_capabilities_->setEyeGazeCorrection({false});
all_capabilities_->setFaceFraming({true, false});
all_capabilities_->setBackgroundSegmentationMask({false, true});
all_non_capabilities_->setBackgroundBlur({false});
all_non_capabilities_->setEyeGazeCorrection({true});
default_settings_ = MediaTrackSettings::Create();
default_settings_->setWhiteBalanceMode(
all_capabilities_->whiteBalanceMode()[0]);
default_settings_->setExposureMode(all_capabilities_->exposureMode()[0]);
default_settings_->setFocusMode(all_capabilities_->focusMode()[0]);
default_settings_->setExposureCompensation(
RangeMean(all_capabilities_->exposureCompensation()));
default_settings_->setExposureTime(
RangeMean(all_capabilities_->exposureTime()));
default_settings_->setColorTemperature(
RangeMean(all_capabilities_->colorTemperature()));
default_settings_->setIso(RangeMean(all_capabilities_->iso()));
default_settings_->setBrightness(
RangeMean(all_capabilities_->brightness()));
default_settings_->setContrast(RangeMean(all_capabilities_->contrast()));
default_settings_->setSaturation(
RangeMean(all_capabilities_->saturation()));
default_settings_->setSharpness(RangeMean(all_capabilities_->sharpness()));
default_settings_->setFocusDistance(
RangeMean(all_capabilities_->focusDistance()));
default_settings_->setPan(RangeMean(all_capabilities_->pan()));
default_settings_->setTilt(RangeMean(all_capabilities_->tilt()));
default_settings_->setZoom(RangeMean(all_capabilities_->zoom()));
default_settings_->setTorch(false);
default_settings_->setBackgroundBlur(true);
default_settings_->setEyeGazeCorrection(false);
default_settings_->setFaceFraming(false);
default_settings_->setBackgroundSegmentationMask(false);
// Capabilities and default settings must be chosen so that at least
// the constraint set {exposureCompensation: {max: ...}} with
// `all_capabilities_->exposureCompensation()->min() +
// kExposureCompensationDelta` is not satisfied by the default settings.
// Otherwise `CheckMaxValues` does not really check anything.
DCHECK_LT(all_capabilities_->exposureCompensation()->min() +
kExposureCompensationDelta,
default_settings_->exposureCompensation());
// Capabilities and default settings must be chosen so that at least
// the constraint set {focusDistance: {min: ...}} with
// `all_capabilities_->focusDistance()->min() +
// kFocusDistanceDelta` is not satisfied by the default settings.
// Otherwise `CheckMinValues` does not really check anything.
DCHECK_GT(all_capabilities_->focusDistance()->min() + kFocusDistanceDelta,
default_settings_->focusDistance());
}
protected:
void SetUp() override {
image_capture_->SetCapabilitiesForTesting(all_capabilities_);
image_capture_->SetSettingsForTesting(default_settings_);
}
void TearDown() override {
image_capture_->SetExecutionContext(nullptr);
ImageCaptureTest::TearDown();
}
Persistent<MediaTrackCapabilities> all_capabilities_;
Persistent<MediaTrackCapabilities> all_non_capabilities_;
Persistent<MediaTrackSettings> default_settings_;
};
TEST_F(ImageCaptureConstraintTest, ApplyBasicBareValueConstraints) {
V8TestingScope scope;
image_capture_->SetExecutionContext(scope.GetExecutionContext());
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
scope.GetScriptState());
// Create constraints:
// {
// whiteBalanceMode: "...",
// exposureMode: ["...", ...],
// focusMode: ["...", ...],
// exposureCompensation: ...,
// ...
// }
auto* constraints = MediaTrackConstraints::Create();
PopulateConstraintSet<ConstrainWithBareValueCreator>(constraints,
all_capabilities_);
auto settings = media::mojom::blink::PhotoSettings::New();
// Should apply the constraints to the settings as is and succeed.
EXPECT_TRUE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, constraints, resolver));
CheckExactValues(settings, all_capabilities_);
// Create constraints: {exposureCompensation: ...}
constraints = MediaTrackConstraints::Create();
constraints->setExposureCompensation(
MakeGarbageCollected<V8UnionConstrainDoubleRangeOrDouble>(
all_capabilities_->exposureCompensation()->max() + 1));
settings = media::mojom::blink::PhotoSettings::New();
// Should apply the closest setting within the capability range and succeed.
EXPECT_TRUE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, constraints, resolver));
EXPECT_TRUE(settings->has_exposure_compensation);
EXPECT_EQ(settings->exposure_compensation,
all_capabilities_->exposureCompensation()->max());
}
TEST_F(ImageCaptureConstraintTest, ApplyBasicExactConstraints) {
V8TestingScope scope;
image_capture_->SetExecutionContext(scope.GetExecutionContext());
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
scope.GetScriptState());
// Create constraints:
// {
// whiteBalanceMode: {exact: "..."},
// exposureMode: {exact: ["...", ...]},
// focusMode: {exact: ["...", ...]},
// exposureCompensation: {exact: ...},
// ...
// }
auto* constraints = MediaTrackConstraints::Create();
PopulateConstraintSet<ConstrainWithExactDictionaryCreator>(constraints,
all_capabilities_);
auto settings = media::mojom::blink::PhotoSettings::New();
// Should apply the constraints to the settings as is and succeed.
EXPECT_TRUE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, constraints, resolver));
CheckExactValues(settings, all_capabilities_);
}
TEST_F(ImageCaptureConstraintTest, ApplyBasicIdealConstraints) {
V8TestingScope scope;
image_capture_->SetExecutionContext(scope.GetExecutionContext());
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
scope.GetScriptState());
// Create constraints:
// {
// whiteBalanceMode: {ideal: "..."},
// exposureMode: {ideal: ["...", ...]},
// focusMode: {ideal: ["...", ...]},
// exposureCompensation: {ideal: ...},
// ...
// }
auto* full_constraints = MediaTrackConstraints::Create();
PopulateConstraintSet<ConstrainWithIdealDictionaryCreator>(full_constraints,
all_capabilities_);
auto settings = media::mojom::blink::PhotoSettings::New();
// Should apply the constraints to the settings as is and succeed.
EXPECT_TRUE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, full_constraints, resolver));
CheckExactValues(settings, all_capabilities_);
// Create constraints: {exposureCompensation: {ideal: ...}}
auto* constraints = MediaTrackConstraints::Create();
constraints->setExposureCompensation(
MakeGarbageCollected<V8UnionConstrainDoubleRangeOrDouble>(
ConstrainWithIdealDictionaryCreator::Create(
all_capabilities_->exposureCompensation()->max() + 1)));
settings = media::mojom::blink::PhotoSettings::New();
// Should apply the closest setting within the capability range and succeed.
EXPECT_TRUE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, constraints, resolver));
EXPECT_TRUE(settings->has_exposure_compensation);
EXPECT_EQ(settings->exposure_compensation,
all_capabilities_->exposureCompensation()->max());
// Reuse `full_constraints` but remove capabilities.
image_capture_->SetCapabilitiesForTesting(
MakeGarbageCollected<MediaTrackCapabilities>());
settings = media::mojom::blink::PhotoSettings::New();
// Shuold ignore ideal constraints without capabilities and succeed.
EXPECT_TRUE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, full_constraints, resolver));
CheckNoValues(settings, full_constraints->pointsOfInterest()
->GetAsConstrainPoint2DParameters()
->ideal()
.size());
}
TEST_F(ImageCaptureConstraintTest, ApplyBasicMaxConstraints) {
V8TestingScope scope;
image_capture_->SetExecutionContext(scope.GetExecutionContext());
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
scope.GetScriptState());
// Create constraints:
// {
// whiteBalanceMode: {},
// exposureMode: {},
// focusMode: {},
// exposureCompensation: {max: ...},
// ...
// }
auto* constraints = MediaTrackConstraints::Create();
PopulateConstraintSet<ConstrainWithMaxOrEmptyDictionaryCreator>(
constraints, all_capabilities_);
auto settings = media::mojom::blink::PhotoSettings::New();
// Should apply the max constraints to the current settings and succeed.
EXPECT_TRUE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, constraints, resolver));
CheckMaxValues(settings, all_capabilities_, default_settings_);
}
TEST_F(ImageCaptureConstraintTest, ApplyBasicMinConstraints) {
V8TestingScope scope;
image_capture_->SetExecutionContext(scope.GetExecutionContext());
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
scope.GetScriptState());
// Create constraints:
// {
// whiteBalanceMode: {},
// exposureMode: {},
// focusMode: {},
// exposureCompensation: {min: ...},
// ...
// }
auto* constraints = MediaTrackConstraints::Create();
PopulateConstraintSet<ConstrainWithMinOrEmptyDictionaryCreator>(
constraints, all_capabilities_);
auto settings = media::mojom::blink::PhotoSettings::New();
// Should apply the min constraints to the current settings and succeed.
EXPECT_TRUE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, constraints, resolver));
CheckMinValues(settings, all_capabilities_, default_settings_);
}
// If an empty list has been given as the value for a constraint, it MUST be
// interpreted as if the constraint were not specified (in other words,
// an empty constraint == no constraint).
// https://w3c.github.io/mediacapture-main/#dfn-selectsettings
TEST_F(ImageCaptureConstraintTest, ApplyBasicNoConstraints) {
V8TestingScope scope;
image_capture_->SetExecutionContext(scope.GetExecutionContext());
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
scope.GetScriptState());
// Create constraints:
// {
// whiteBalanceMode: [],
// exposureMode: {exact: []},
// focusMode: {ideal: []},
// pointsOfInterest: {exact: []}
// }
auto* constraints = MediaTrackConstraints::Create();
constraints->setWhiteBalanceMode(
MakeGarbageCollected<
V8UnionConstrainDOMStringParametersOrStringOrStringSequence>(
Vector<String>()));
constraints->setExposureMode(
MakeGarbageCollected<
V8UnionConstrainDOMStringParametersOrStringOrStringSequence>(
ConstrainWithExactDictionaryCreator::Create(Vector<String>())));
constraints->setFocusMode(
MakeGarbageCollected<
V8UnionConstrainDOMStringParametersOrStringOrStringSequence>(
ConstrainWithIdealDictionaryCreator::Create(Vector<String>())));
constraints->setPointsOfInterest(
MakeGarbageCollected<V8UnionConstrainPoint2DParametersOrPoint2DSequence>(
ConstrainWithExactDictionaryCreator::Create(
HeapVector<Member<Point2D>>())));
auto settings = media::mojom::blink::PhotoSettings::New();
// Should ignore empty sequences and succeed.
EXPECT_TRUE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, constraints, resolver));
CheckNoValues(settings);
}
TEST_F(ImageCaptureConstraintTest, ApplyBasicOverconstrainedConstraints) {
V8TestingScope scope;
image_capture_->SetExecutionContext(scope.GetExecutionContext());
auto settings = media::mojom::blink::PhotoSettings::New();
// Create constraints: {whiteBalanceMode: {exact: "..."}}
auto* constraints = MediaTrackConstraints::Create();
constraints->setWhiteBalanceMode(
MakeGarbageCollected<
V8UnionConstrainDOMStringParametersOrStringOrStringSequence>(
ConstrainWithExactDictionaryCreator::Create(
all_non_capabilities_->whiteBalanceMode()[0])));
auto* capture_error = MakeGarbageCollected<CaptureErrorFunction>();
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
scope.GetScriptState());
resolver->Promise().Catch(scope.GetScriptState(), capture_error);
EXPECT_FALSE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, constraints, resolver));
scope.PerformMicrotaskCheckpoint(); // Resolve/reject promises.
EXPECT_TRUE(capture_error->WasCalled());
EXPECT_EQ(capture_error->Name(), "OverconstrainedError");
EXPECT_EQ(capture_error->Constraint(), "whiteBalanceMode");
// Create constraints: {whiteBalanceMode: {exact: ["..."]}}
constraints = MediaTrackConstraints::Create();
constraints->setWhiteBalanceMode(
MakeGarbageCollected<
V8UnionConstrainDOMStringParametersOrStringOrStringSequence>(
ConstrainWithExactDictionaryCreator::Create(
all_non_capabilities_->whiteBalanceMode())));
capture_error = MakeGarbageCollected<CaptureErrorFunction>();
resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
scope.GetScriptState());
resolver->Promise().Catch(scope.GetScriptState(), capture_error);
EXPECT_FALSE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, constraints, resolver));
scope.PerformMicrotaskCheckpoint(); // Resolve/reject promises.
EXPECT_TRUE(capture_error->WasCalled());
EXPECT_EQ(capture_error->Name(), "OverconstrainedError");
EXPECT_EQ(capture_error->Constraint(), "whiteBalanceMode");
// Create constraints: {exposureCompensation: {exact: ...}}
constraints = MediaTrackConstraints::Create();
constraints->setExposureCompensation(
MakeGarbageCollected<V8UnionConstrainDoubleRangeOrDouble>(
ConstrainWithExactDictionaryCreator::Create(
all_capabilities_->exposureCompensation()->min() - 1)));
capture_error = MakeGarbageCollected<CaptureErrorFunction>();
resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
scope.GetScriptState());
resolver->Promise().Catch(scope.GetScriptState(), capture_error);
EXPECT_FALSE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, constraints, resolver));
scope.PerformMicrotaskCheckpoint(); // Resolve/reject promises.
EXPECT_TRUE(capture_error->WasCalled());
EXPECT_EQ(capture_error->Name(), "OverconstrainedError");
EXPECT_EQ(capture_error->Constraint(), "exposureCompensation");
// Create constraints: {exposureCompensation: {max: ...}}
constraints = MediaTrackConstraints::Create();
constraints->setExposureCompensation(
MakeGarbageCollected<V8UnionConstrainDoubleRangeOrDouble>(
ConstrainWithMaxDictionaryCreator::Create(
all_capabilities_->exposureCompensation()->min() - 1)));
capture_error = MakeGarbageCollected<CaptureErrorFunction>();
resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
scope.GetScriptState());
resolver->Promise().Catch(scope.GetScriptState(), capture_error);
EXPECT_FALSE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, constraints, resolver));
scope.PerformMicrotaskCheckpoint(); // Resolve/reject promises.
EXPECT_TRUE(capture_error->WasCalled());
EXPECT_EQ(capture_error->Name(), "OverconstrainedError");
EXPECT_EQ(capture_error->Constraint(), "exposureCompensation");
// Create constraints: {exposureCompensation: {min: ...}}
constraints = MediaTrackConstraints::Create();
constraints->setExposureCompensation(
MakeGarbageCollected<V8UnionConstrainDoubleRangeOrDouble>(
ConstrainWithMinDictionaryCreator::Create(
all_capabilities_->exposureCompensation()->max() + 1)));
capture_error = MakeGarbageCollected<CaptureErrorFunction>();
resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
scope.GetScriptState());
resolver->Promise().Catch(scope.GetScriptState(), capture_error);
EXPECT_FALSE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, constraints, resolver));
scope.PerformMicrotaskCheckpoint(); // Resolve/reject promises.
EXPECT_TRUE(capture_error->WasCalled());
EXPECT_EQ(capture_error->Name(), "OverconstrainedError");
EXPECT_EQ(capture_error->Constraint(), "exposureCompensation");
// Create constraints: {backgroundBlur: {exact: ...}}
constraints = MediaTrackConstraints::Create();
constraints->setBackgroundBlur(
MakeGarbageCollected<V8UnionBooleanOrConstrainBooleanParameters>(
ConstrainWithExactDictionaryCreator::Create(
all_non_capabilities_->backgroundBlur()[0])));
capture_error = MakeGarbageCollected<CaptureErrorFunction>();
resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
scope.GetScriptState());
resolver->Promise().Catch(scope.GetScriptState(), capture_error);
EXPECT_FALSE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, constraints, resolver));
scope.PerformMicrotaskCheckpoint(); // Resolve/reject promises.
EXPECT_TRUE(capture_error->WasCalled());
EXPECT_EQ(capture_error->Name(), "OverconstrainedError");
EXPECT_EQ(capture_error->Constraint(), "backgroundBlur");
// Reuse previous constraints but remove capabilities.
image_capture_->SetCapabilitiesForTesting(
MakeGarbageCollected<MediaTrackCapabilities>());
capture_error = MakeGarbageCollected<CaptureErrorFunction>();
resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
scope.GetScriptState());
resolver->Promise().Catch(scope.GetScriptState(), capture_error);
EXPECT_FALSE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, constraints, resolver));
scope.PerformMicrotaskCheckpoint(); // Resolve/reject promises.
EXPECT_TRUE(capture_error->WasCalled());
EXPECT_EQ(capture_error->Name(), "OverconstrainedError");
EXPECT_EQ(capture_error->Message(), "Unsupported constraint");
}
TEST_F(ImageCaptureConstraintTest, ApplyFirstAdvancedBareValueConstraints) {
V8TestingScope scope;
image_capture_->SetExecutionContext(scope.GetExecutionContext());
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
scope.GetScriptState());
// Create constraints:
// {advanced: [
// {
// whiteBalanceMode: "...",
// exposureMode: ["...", ...],
// focusMode: ["...", ...],
// exposureCompensation: ...,
// ...
// }
// ]}
auto* constraint_set = MediaTrackConstraintSet::Create();
PopulateConstraintSet<ConstrainWithBareValueCreator>(constraint_set,
all_capabilities_);
auto* constraints = MediaTrackConstraints::Create();
constraints->setAdvanced({constraint_set});
auto settings = media::mojom::blink::PhotoSettings::New();
// Should apply the constraints to the settings as is and succeed.
EXPECT_TRUE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, constraints, resolver));
// TODO(crbug.com/1408091): This is not spec compliant.
// ImageCapture should support DOMString sequence constraints (used above for
// exposureMode and focusMode) in the first advanced constraint set.
CheckExactValues(settings, all_capabilities_, ExpectHasPanTiltZoom(true),
ExpectHasExposureModeAndFocusMode(false));
}
TEST_F(ImageCaptureConstraintTest, ApplyFirstAdvancedExactConstraints) {
V8TestingScope scope;
image_capture_->SetExecutionContext(scope.GetExecutionContext());
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
scope.GetScriptState());
// Create constraints:
// {advanced: [
// {
// whiteBalanceMode: {exact: "..."},
// exposureMode: {exact: ["...", ...]},
// focusMode: {exact: ["...", ...]},
// exposureCompensation: {exact: ...},
// ...
// }
// ]}
auto* constraint_set = MediaTrackConstraintSet::Create();
PopulateConstraintSet<ConstrainWithExactDictionaryCreator>(constraint_set,
all_capabilities_);
auto* constraints = MediaTrackConstraints::Create();
constraints->setAdvanced({constraint_set});
auto settings = media::mojom::blink::PhotoSettings::New();
// Should apply the constraints to the settings as is and succeed.
EXPECT_TRUE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, constraints, resolver));
// TODO(crbug.com/1408091): This is not spec compliant.
// ImageCapture should support non-bare value constraints in the first
// advanced constraint set.
CheckNoValues(settings);
}
TEST_F(ImageCaptureConstraintTest, ApplyFirstAdvancedIdealConstraints) {
V8TestingScope scope;
image_capture_->SetExecutionContext(scope.GetExecutionContext());
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
scope.GetScriptState());
// Create constraints:
// {advanced: [
// {
// whiteBalanceMode: {ideal: "..."},
// exposureMode: {ideal: ["...", ...]},
// focusMode: {ideal: ["...", ...]},
// exposureCompensation: {ideal: ...},
// ...
// }
// ]}
auto* constraint_set = MediaTrackConstraintSet::Create();
PopulateConstraintSet<ConstrainWithIdealDictionaryCreator>(constraint_set,
all_capabilities_);
auto* constraints = MediaTrackConstraints::Create();
constraints->setAdvanced({constraint_set});
auto settings = media::mojom::blink::PhotoSettings::New();
// Shuold ignore ideal constraints in advanced constraint sets and succeed.
EXPECT_TRUE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, constraints, resolver));
// The fitness distance
// (https://w3c.github.io/mediacapture-main/#dfn-fitness-distance) between
// an ideal constraint and a setting in a settings dictionary is always
// between 0.0 and 1.0 (inclusive).
// Therefore, the fitness distance between a constraint set containing only
// ideal constraints and a settings dictionary (being the sum of the above
// fitness distances in [0.0, 1.0]) is always finite.
// On the other hand, the SelectSettings algorithm
// (https://w3c.github.io/mediacapture-main/#dfn-selectsettings) iterates
// over the advanced constraint sets and computes the fitness distance
// between the advanced constraint sets and each settings dictionary
// candidate and if the fitness distance is finite for one or more settings
// dictionary candidates, it keeps those settings dictionary candidates.
//
// All in all, in this test case all the fitness distances are finite and
// therefore the SelectSettings algorithm keeps all settings dictionary
// candidates instead of favouring a particular settings dictionary and
// therefore `CheckAndApplyMediaTrackConstraintsToSettings` does not set
// settings in `settings`.
CheckNoValues(settings);
}
TEST_F(ImageCaptureConstraintTest,
ApplyFirstAdvancedOverconstrainedConstraints) {
V8TestingScope scope;
image_capture_->SetExecutionContext(scope.GetExecutionContext());
const HeapVector<Member<Point2D>> points_of_interest = {
CreatePoint2D(0.25, 0.75)};
auto settings = media::mojom::blink::PhotoSettings::New();
// Create constraints: {advanced: [{whiteBalanceMode: "..."}]}
auto* constraint_set = MediaTrackConstraintSet::Create();
constraint_set->setWhiteBalanceMode(
MakeGarbageCollected<
V8UnionConstrainDOMStringParametersOrStringOrStringSequence>(
all_non_capabilities_->whiteBalanceMode()[0]));
auto* constraints = MediaTrackConstraints::Create();
constraints->setAdvanced({constraint_set});
auto* capture_error = MakeGarbageCollected<CaptureErrorFunction>();
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
scope.GetScriptState());
resolver->Promise().Catch(scope.GetScriptState(), capture_error);
// TODO(crbug.com/1408091): This is not spec compliant. This should not fail.
// Instead, should discard the first advanced constraint set and succeed.
EXPECT_FALSE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, constraints, resolver));
scope.PerformMicrotaskCheckpoint(); // Resolve/reject promises.
EXPECT_TRUE(capture_error->WasCalled());
EXPECT_EQ(capture_error->Name(), "OverconstrainedError");
EXPECT_EQ(capture_error->Constraint(), "whiteBalanceMode");
// Create constraints: {advanced: [{pointsOfInterest: [...], pan: false}]}
constraint_set = MediaTrackConstraintSet::Create();
constraint_set->setPointsOfInterest(
MakeGarbageCollected<V8UnionConstrainPoint2DParametersOrPoint2DSequence>(
points_of_interest));
constraint_set->setPan(
MakeGarbageCollected<V8UnionBooleanOrConstrainDoubleRangeOrDouble>(
false));
constraints = MediaTrackConstraints::Create();
constraints->setAdvanced({constraint_set});
capture_error = MakeGarbageCollected<CaptureErrorFunction>();
resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
scope.GetScriptState());
resolver->Promise().Catch(scope.GetScriptState(), capture_error);
// TODO(crbug.com/1408091): This is not spec compliant. This should not fail.
// Instead, should discard the first advanced constraint set and succeed.
EXPECT_FALSE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, constraints, resolver));
scope.PerformMicrotaskCheckpoint(); // Resolve/reject promises.
EXPECT_TRUE(capture_error->WasCalled());
EXPECT_EQ(capture_error->Name(), "OverconstrainedError");
EXPECT_EQ(capture_error->Constraint(), "pan");
// Remove capabilities (does not affect pointsOfInterest).
image_capture_->SetCapabilitiesForTesting(
MakeGarbageCollected<MediaTrackCapabilities>());
// Create constraints: {advanced: [{pointsOfInterest: [...], pan: true}]}
constraint_set = MediaTrackConstraintSet::Create();
constraint_set->setPointsOfInterest(
MakeGarbageCollected<V8UnionConstrainPoint2DParametersOrPoint2DSequence>(
points_of_interest));
constraint_set->setPan(
MakeGarbageCollected<V8UnionBooleanOrConstrainDoubleRangeOrDouble>(true));
constraints = MediaTrackConstraints::Create();
constraints->setAdvanced({constraint_set});
capture_error = MakeGarbageCollected<CaptureErrorFunction>();
resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
scope.GetScriptState());
resolver->Promise().Catch(scope.GetScriptState(), capture_error);
// TODO(crbug.com/1408091): This is not spec compliant. This should not fail.
// Instead, should discard the first advanced constraint set and succeed.
EXPECT_FALSE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, constraints, resolver));
scope.PerformMicrotaskCheckpoint(); // Resolve/reject promises.
EXPECT_TRUE(capture_error->WasCalled());
EXPECT_EQ(capture_error->Name(), "OverconstrainedError");
EXPECT_EQ(capture_error->Constraint(), "pan");
}
TEST_F(ImageCaptureConstraintTest, ApplyAdvancedBareValueConstraints) {
V8TestingScope scope;
image_capture_->SetExecutionContext(scope.GetExecutionContext());
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
scope.GetScriptState());
// Create constraints:
// {advanced: [
// {},
// {
// whiteBalanceMode: "...",
// exposureMode: ["...", ...],
// focusMode: ["...", ...],
// exposureCompensation: ...,
// ...
// }
// ]}
auto* constraint_set = MediaTrackConstraintSet::Create();
PopulateConstraintSet<ConstrainWithBareValueCreator>(constraint_set,
all_capabilities_);
auto* constraints = MediaTrackConstraints::Create();
constraints->setAdvanced({MediaTrackConstraintSet::Create(), constraint_set});
auto settings = media::mojom::blink::PhotoSettings::New();
// Should apply the constraints to the settings as is and succeed.
EXPECT_TRUE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, constraints, resolver));
CheckExactValues(settings, all_capabilities_);
}
TEST_F(ImageCaptureConstraintTest, ApplyAdvancedExactConstraints) {
V8TestingScope scope;
image_capture_->SetExecutionContext(scope.GetExecutionContext());
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
scope.GetScriptState());
// Create constraints:
// {advanced: [
// {},
// {
// whiteBalanceMode: {exact: "..."},
// exposureMode: {exact: ["...", ...]},
// focusMode: {exact: ["...", ...]},
// exposureCompensation: {exact: ...},
// ...
// }
// ]}
auto* constraint_set = MediaTrackConstraintSet::Create();
PopulateConstraintSet<ConstrainWithExactDictionaryCreator>(constraint_set,
all_capabilities_);
auto* constraints = MediaTrackConstraints::Create();
constraints->setAdvanced({MediaTrackConstraintSet::Create(), constraint_set});
auto settings = media::mojom::blink::PhotoSettings::New();
// Should apply the constraints to the settings as is and succeed.
EXPECT_TRUE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, constraints, resolver));
CheckExactValues(settings, all_capabilities_);
}
TEST_F(ImageCaptureConstraintTest, ApplyAdvancedIdealConstraints) {
V8TestingScope scope;
image_capture_->SetExecutionContext(scope.GetExecutionContext());
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
scope.GetScriptState());
// Create constraints:
// {advanced: [
// {},
// {
// whiteBalanceMode: {ideal: "..."},
// exposureMode: {ideal: ["...", ...]},
// focusMode: {ideal: ["...", ...]},
// exposureCompensation: {ideal: ...},
// ...
// }
// ]}
auto* constraint_set = MediaTrackConstraintSet::Create();
PopulateConstraintSet<ConstrainWithIdealDictionaryCreator>(constraint_set,
all_capabilities_);
auto* constraints = MediaTrackConstraints::Create();
constraints->setAdvanced({MediaTrackConstraintSet::Create(), constraint_set});
auto settings = media::mojom::blink::PhotoSettings::New();
// Shuold ignore ideal constraints in advanced constraint sets and succeed.
EXPECT_TRUE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, constraints, resolver));
// The fitness distance
// (https://w3c.github.io/mediacapture-main/#dfn-fitness-distance) between
// an ideal constraint and a setting in a settings dictionary is always
// between 0.0 and 1.0 (inclusive).
// Therefore, the fitness distance between a constraint set containing only
// ideal constraints and a settings dictionary (being the sum of the above
// fitness distances in [0.0, 1.0]) is always finite.
// On the other hand, the SelectSettings algorithm
// (https://w3c.github.io/mediacapture-main/#dfn-selectsettings) iterates
// over the advanced constraint sets and computes the fitness distance
// between the advanced constraint sets and each settings dictionary
// candidate and if the fitness distance is finite for one or more settings
// dictionary candidates, it keeps those settings dictionary candidates.
//
// All in all, in this test case all the fitness distances are finite and
// therefore the SelectSettings algorithm keeps all settings dictionary
// candidates instead of favouring a particular settings dictionary and
// therefore `CheckAndApplyMediaTrackConstraintsToSettings` does not set
// settings in `settings`.
CheckNoValues(settings);
}
// If an empty list has been given as the value for a constraint, it MUST be
// interpreted as if the constraint were not specified (in other words,
// an empty constraint == no constraint).
// https://w3c.github.io/mediacapture-main/#dfn-selectsettings
TEST_F(ImageCaptureConstraintTest, ApplyAdvancedNoConstraints) {
V8TestingScope scope;
image_capture_->SetExecutionContext(scope.GetExecutionContext());
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
scope.GetScriptState());
// Create constraints:
// {advanced: [
// {},
// {
// whiteBalanceMode: [],
// exposureMode: {exact: []},
// focusMode: {ideal: []},
// pointsOfInterest: {exact: []}
// }
// ]}
auto* constraint_set = MediaTrackConstraintSet::Create();
constraint_set->setWhiteBalanceMode(
MakeGarbageCollected<
V8UnionConstrainDOMStringParametersOrStringOrStringSequence>(
Vector<String>()));
constraint_set->setExposureMode(
MakeGarbageCollected<
V8UnionConstrainDOMStringParametersOrStringOrStringSequence>(
ConstrainWithExactDictionaryCreator::Create(Vector<String>())));
constraint_set->setFocusMode(
MakeGarbageCollected<
V8UnionConstrainDOMStringParametersOrStringOrStringSequence>(
ConstrainWithIdealDictionaryCreator::Create(Vector<String>())));
constraint_set->setPointsOfInterest(
MakeGarbageCollected<V8UnionConstrainPoint2DParametersOrPoint2DSequence>(
ConstrainWithExactDictionaryCreator::Create(
HeapVector<Member<Point2D>>())));
auto* constraints = MediaTrackConstraints::Create();
constraints->setAdvanced({MediaTrackConstraintSet::Create(), constraint_set});
auto settings = media::mojom::blink::PhotoSettings::New();
// Should ignore empty sequences and succeed.
EXPECT_TRUE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, constraints, resolver));
CheckNoValues(settings);
}
TEST_F(ImageCaptureConstraintTest, ApplyAdvancedOverconstrainedConstraints) {
V8TestingScope scope;
image_capture_->SetExecutionContext(scope.GetExecutionContext());
const HeapVector<Member<Point2D>> points_of_interest = {
CreatePoint2D(0.25, 0.75)};
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
scope.GetScriptState());
// Create constraints: {advanced: [{}, {whiteBalanceMode: "..."}]}
auto* constraint_set = MediaTrackConstraintSet::Create();
constraint_set->setWhiteBalanceMode(
MakeGarbageCollected<
V8UnionConstrainDOMStringParametersOrStringOrStringSequence>(
all_non_capabilities_->whiteBalanceMode()[0]));
auto* constraints = MediaTrackConstraints::Create();
constraints->setAdvanced({MediaTrackConstraintSet::Create(), constraint_set});
auto settings = media::mojom::blink::PhotoSettings::New();
// Should discard the last advanced constraint set and succeed.
EXPECT_TRUE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, constraints, resolver));
CheckNoValues(settings);
// Create constraints: {advanced: [{}, {pointsOfInterest: [...], pan: false}]}
constraint_set = MediaTrackConstraintSet::Create();
constraint_set->setPointsOfInterest(
MakeGarbageCollected<V8UnionConstrainPoint2DParametersOrPoint2DSequence>(
points_of_interest));
constraint_set->setPan(
MakeGarbageCollected<V8UnionBooleanOrConstrainDoubleRangeOrDouble>(
false));
constraints = MediaTrackConstraints::Create();
constraints->setAdvanced({MediaTrackConstraintSet::Create(), constraint_set});
settings = media::mojom::blink::PhotoSettings::New();
// Should discard the last advanced constraint set and succeed.
EXPECT_TRUE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, constraints, resolver));
CheckNoValues(settings);
// Remove capabilities (does not affect pointsOfInterest).
image_capture_->SetCapabilitiesForTesting(
MakeGarbageCollected<MediaTrackCapabilities>());
// Create constraints: {advanced: [{}, {pointsOfInterest: [...], pan: true}]}
constraint_set = MediaTrackConstraintSet::Create();
constraint_set->setPointsOfInterest(
MakeGarbageCollected<V8UnionConstrainPoint2DParametersOrPoint2DSequence>(
points_of_interest));
constraint_set->setPan(
MakeGarbageCollected<V8UnionBooleanOrConstrainDoubleRangeOrDouble>(true));
constraints = MediaTrackConstraints::Create();
constraints->setAdvanced({MediaTrackConstraintSet::Create(), constraint_set});
settings = media::mojom::blink::PhotoSettings::New();
// Should discard the last advanced constraint set and succeed.
EXPECT_TRUE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, constraints, resolver));
CheckNoValues(settings);
}
// If the visibilityState of the top-level browsing context value is "hidden",
// the `applyConstraints()` algorithm MUST throw a `SecurityError` if `pan`,
// `tilt` or `zoom` dictionary member exists with a value other than `false`.
// https://w3c.github.io/mediacapture-image/#pan
// https://w3c.github.io/mediacapture-image/#tilt
// https://w3c.github.io/mediacapture-image/#zoom
TEST_F(ImageCaptureConstraintTest, ApplySecurityErrorConstraints) {
V8TestingScope scope;
scope.GetPage().SetVisibilityState(blink::mojom::PageVisibilityState::kHidden,
/*is_initial_state=*/true);
image_capture_->SetExecutionContext(scope.GetExecutionContext());
auto settings = media::mojom::blink::PhotoSettings::New();
// Create constraints: {pan: ...}
auto* constraints = MediaTrackConstraints::Create();
constraints->setPan(
MakeGarbageCollected<V8UnionBooleanOrConstrainDoubleRangeOrDouble>(
default_settings_->pan()));
auto* capture_error = MakeGarbageCollected<CaptureErrorFunction>();
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
scope.GetScriptState());
resolver->Promise().Catch(scope.GetScriptState(), capture_error);
EXPECT_FALSE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, constraints, resolver));
scope.PerformMicrotaskCheckpoint(); // Resolve/reject promises.
EXPECT_TRUE(capture_error->WasCalled());
EXPECT_EQ(capture_error->Name(), "SecurityError");
// Create constraints: {advanced: [{tilt: ...}]}
auto* constraint_set = MediaTrackConstraintSet::Create();
constraint_set->setTilt(
MakeGarbageCollected<V8UnionBooleanOrConstrainDoubleRangeOrDouble>(
default_settings_->tilt()));
constraints = MediaTrackConstraints::Create();
constraints->setAdvanced({constraint_set});
capture_error = MakeGarbageCollected<CaptureErrorFunction>();
resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
scope.GetScriptState());
resolver->Promise().Catch(scope.GetScriptState(), capture_error);
EXPECT_FALSE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, constraints, resolver));
scope.PerformMicrotaskCheckpoint(); // Resolve/reject promises.
EXPECT_TRUE(capture_error->WasCalled());
EXPECT_EQ(capture_error->Name(), "SecurityError");
// Create constraints: {advanced: [{}, {zoom: ...}]}
constraint_set = MediaTrackConstraintSet::Create();
constraint_set->setZoom(
MakeGarbageCollected<V8UnionBooleanOrConstrainDoubleRangeOrDouble>(
default_settings_->zoom()));
constraints = MediaTrackConstraints::Create();
constraints->setAdvanced({MediaTrackConstraintSet::Create(), constraint_set});
capture_error = MakeGarbageCollected<CaptureErrorFunction>();
resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
scope.GetScriptState());
resolver->Promise().Catch(scope.GetScriptState(), capture_error);
EXPECT_FALSE(image_capture_->CheckAndApplyMediaTrackConstraintsToSettings(
&*settings, constraints, resolver));
scope.PerformMicrotaskCheckpoint(); // Resolve/reject promises.
EXPECT_TRUE(capture_error->WasCalled());
EXPECT_EQ(capture_error->Name(), "SecurityError");
}
TEST_F(ImageCaptureTest, GrabFrameOfLiveTrackIsFulfilled) {
V8TestingScope scope;
SetupTrackMocks(scope);
track_->SetReadyState(V8MediaStreamTrackState::Enum::kLive);
track_->setEnabled(true);
track_->SetMuted(false);
auto result = image_capture_->grabFrame(scope.GetScriptState());
ScriptPromiseTester tester(scope.GetScriptState(), result);
tester.WaitUntilSettled();
EXPECT_TRUE(tester.IsFulfilled());
}
TEST_F(ImageCaptureTest, GrabFrameOfMutedTrackIsFulfilled) {
V8TestingScope scope;
SetupTrackMocks(scope);
track_->SetReadyState(V8MediaStreamTrackState::Enum::kLive);
track_->setEnabled(true);
track_->SetMuted(true);
auto result = image_capture_->grabFrame(scope.GetScriptState());
ScriptPromiseTester tester(scope.GetScriptState(), result);
tester.WaitUntilSettled();
EXPECT_TRUE(tester.IsFulfilled());
}
TEST_F(ImageCaptureTest, GrabFrameOfMutedTrackWithoutFramesIsRejected) {
V8TestingScope scope;
SetupTrackMocks(scope, /*produce_frame_on_add_sink=*/false);
track_->SetReadyState(V8MediaStreamTrackState::Enum::kLive);
track_->setEnabled(true);
track_->SetMuted(true);
auto result = image_capture_->grabFrame(scope.GetScriptState());
ScriptPromiseTester tester(scope.GetScriptState(), result);
tester.WaitUntilSettled();
EXPECT_TRUE(tester.IsRejected());
}
TEST_F(ImageCaptureTest, GrabFrameOfEndedTrackRejects) {
V8TestingScope scope;
track_->SetReadyState(V8MediaStreamTrackState::Enum::kEnded);
track_->setEnabled(true);
track_->SetMuted(false);
auto result = image_capture_->grabFrame(scope.GetScriptState());
ScriptPromiseTester tester(scope.GetScriptState(), result);
tester.WaitUntilSettled();
EXPECT_TRUE(tester.IsRejected());
}
TEST_F(ImageCaptureTest, GrabFrameOfDisabledTrackRejects) {
V8TestingScope scope;
track_->SetReadyState(V8MediaStreamTrackState::Enum::kLive);
track_->setEnabled(false);
track_->SetMuted(false);
auto result = image_capture_->grabFrame(scope.GetScriptState());
ScriptPromiseTester tester(scope.GetScriptState(), result);
tester.WaitUntilSettled();
EXPECT_TRUE(tester.IsRejected());
}
} // namespace blink