// Copyright 2019 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/media_capabilities/media_capabilities.h"

#include <math.h>
#include <stdint.h>

#include <algorithm>

#include "base/memory/raw_ptr.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "media/base/media_switches.h"
#include "media/base/supported_types.h"
#include "media/base/video_codecs.h"
#include "media/mojo/clients/mojo_video_encoder_metrics_provider.h"
#include "media/mojo/mojom/media_metrics_provider.mojom-blink.h"
#include "media/mojo/mojom/media_types.mojom-blink.h"
#include "media/mojo/mojom/video_decode_perf_history.mojom-blink.h"
#include "media/mojo/mojom/watch_time_recorder.mojom-blink.h"
#include "media/mojo/mojom/webrtc_video_perf.mojom-blink.h"
#include "media/video/mock_gpu_video_accelerator_factories.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/blink/public/platform/browser_interface_broker_proxy.h"
#include "third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.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/modules/v8/v8_audio_configuration.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_key_system_track_configuration.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_media_capabilities_decoding_info.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_media_capabilities_info.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_media_configuration.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_media_decoding_configuration.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_media_encoding_configuration.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_video_configuration.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/navigator.h"
#include "third_party/blink/renderer/core/testing/page_test_base.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/peerconnection/rtc_video_encoder_factory.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
#include "third_party/blink/renderer/platform/testing/task_environment.h"
#include "third_party/blink/renderer/platform/testing/testing_platform_support.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
#include "third_party/blink/renderer/platform/wtf/text/string_view.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
#include "third_party/blink/renderer/platform/wtf/wtf_size_t.h"
#include "third_party/googletest/src/googlemock/include/gmock/gmock-actions.h"
#include "ui/gfx/geometry/size.h"

using ::testing::_;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::Unused;

namespace blink {

namespace {

// Simulating the browser-side service.
class MockPerfHistoryService
    : public media::mojom::blink::VideoDecodePerfHistory {
 public:
  void BindRequest(mojo::ScopedMessagePipeHandle handle) {
    receiver_.Bind(
        mojo::PendingReceiver<media::mojom::blink::VideoDecodePerfHistory>(
            std::move(handle)));
    receiver_.set_disconnect_handler(blink::BindOnce(
        &MockPerfHistoryService::OnConnectionError, base::Unretained(this)));
  }

  void OnConnectionError() { receiver_.reset(); }
  void Disconnect() { receiver_.reset(); }

  // media::mojom::blink::VideoDecodePerfHistory implementation:
  MOCK_METHOD2(GetPerfInfo,
               void(media::mojom::blink::PredictionFeaturesPtr features,
                    GetPerfInfoCallback got_info_cb));

 private:
  mojo::Receiver<media::mojom::blink::VideoDecodePerfHistory> receiver_{this};
};

class MockWebrtcPerfHistoryService
    : public media::mojom::blink::WebrtcVideoPerfHistory {
 public:
  void BindRequest(mojo::ScopedMessagePipeHandle handle) {
    receiver_.Bind(
        mojo::PendingReceiver<media::mojom::blink::WebrtcVideoPerfHistory>(
            std::move(handle)));
    receiver_.set_disconnect_handler(
        blink::BindOnce(&MockWebrtcPerfHistoryService::OnConnectionError,
                        base::Unretained(this)));
  }

  void OnConnectionError() { receiver_.reset(); }
  void Disconnect() { receiver_.reset(); }

  // media::mojom::blink::WebrtcVideoPerfHistory implementation:
  MOCK_METHOD3(GetPerfInfo,
               void(media::mojom::blink::WebrtcPredictionFeaturesPtr features,
                    int frames_per_second,
                    GetPerfInfoCallback got_info_cb));

 private:
  mojo::Receiver<media::mojom::blink::WebrtcVideoPerfHistory> receiver_{this};
};

class FakeMediaMetricsProvider
    : public media::mojom::blink::MediaMetricsProvider {
 public:
  // Raw pointers to services owned by the test.
  FakeMediaMetricsProvider() = default;
  ~FakeMediaMetricsProvider() override = default;

  void BindRequest(mojo::ScopedMessagePipeHandle handle) {
    receiver_.Bind(
        mojo::PendingReceiver<media::mojom::blink::MediaMetricsProvider>(
            std::move(handle)));
    receiver_.set_disconnect_handler(blink::BindOnce(
        &FakeMediaMetricsProvider::OnConnectionError, base::Unretained(this)));
  }

  void OnConnectionError() { receiver_.reset(); }

  // mojom::WatchTimeRecorderProvider implementation:
  void AcquireWatchTimeRecorder(
      media::mojom::blink::PlaybackPropertiesPtr properties,
      mojo::PendingReceiver<media::mojom::blink::WatchTimeRecorder> receiver)
      override {
    FAIL();
  }
  void AcquireVideoDecodeStatsRecorder(
      mojo::PendingReceiver<media::mojom::blink::VideoDecodeStatsRecorder>
          receiver) override {
    FAIL();
  }
  void AcquirePlaybackEventsRecorder(
      mojo::PendingReceiver<media::mojom::blink::PlaybackEventsRecorder>
          receiver) override {
    FAIL();
  }
  void Initialize(bool is_mse,
                  media::mojom::MediaURLScheme url_scheme,
                  media::mojom::MediaStreamType media_stream_type) override {}
  void OnStarted(const media::PipelineStatus& status) override {}
  void OnError(const media::PipelineStatus& status) override {}
  void OnFallback(const media::PipelineStatus& status) override {}
  void SetIsEME() override {}
  void SetHasTrackChange() override {}
  void SetTimeToMetadata(base::TimeDelta elapsed) override {}
  void SetTimeToFirstFrame(base::TimeDelta elapsed) override {}
  void SetTimeToPlayReady(base::TimeDelta elapsed) override {}
  void SetVisibilityRatioAtPlaybackStart(double ratio) override {}
  void SetContainerName(
      media::container_names::MediaContainerName container_name) override {}
  void SetRendererType(media::RendererType renderer_type) override {}
  void SetDemuxerType(media::DemuxerType demuxer_type) override {}
  void SetKeySystem(const String& key_system) override {}
  void SetHasWaitingForKey() override {}
  void SetIsHardwareSecure() override {}
  void SetHasPlayed() override {}
  void SetHaveEnough() override {}
  void SetHasAudio(media::AudioCodec audio_codec) override {}
  void SetHasVideo(media::VideoCodec video_codec) override {}
  void SetVideoPipelineInfo(const media::VideoPipelineInfo& info) override {}
  void SetAudioPipelineInfo(const media::AudioPipelineInfo& info) override {}

 private:
  mojo::Receiver<media::mojom::blink::MediaMetricsProvider> receiver_{this};
};

// Simple helper for saving back-end callbacks for pending decodingInfo() calls.
// Callers can then manually fire the callbacks, gaining fine-grain control of
// the timing and order of their arrival.
class CallbackSaver {
 public:
  void SavePerfHistoryCallback(
      media::mojom::blink::PredictionFeaturesPtr features,
      MockPerfHistoryService::GetPerfInfoCallback got_info_cb) {
    perf_history_cb_ = std::move(got_info_cb);
  }

  void SaveGpuFactoriesNotifyCallback(base::OnceClosure cb) {
    gpu_factories_notify_cb_ = std::move(cb);
  }

  MockPerfHistoryService::GetPerfInfoCallback& perf_history_cb() {
    return perf_history_cb_;
  }

  base::OnceClosure& gpu_factories_notify_cb() {
    return gpu_factories_notify_cb_;
  }

  MockWebrtcPerfHistoryService::GetPerfInfoCallback& webrtc_perf_history_cb() {
    return webrtc_perf_history_cb_;
  }

  void SaveWebrtcPerfHistoryCallback(
      media::mojom::blink::WebrtcPredictionFeaturesPtr features,
      int frames_per_second,
      MockWebrtcPerfHistoryService::GetPerfInfoCallback got_info_cb) {
    webrtc_perf_history_cb_ = std::move(got_info_cb);
  }

 private:
  MockPerfHistoryService::GetPerfInfoCallback perf_history_cb_;
  MockWebrtcPerfHistoryService::GetPerfInfoCallback webrtc_perf_history_cb_;
  base::OnceClosure gpu_factories_notify_cb_;
};

class MockPlatform : public TestingPlatformSupport {
 public:
  MockPlatform() = default;
  ~MockPlatform() override = default;

  MOCK_METHOD0(GetGpuFactories, media::GpuVideoAcceleratorFactories*());
};

// This would typically be a test fixture, but we need it to be
// STACK_ALLOCATED() in order to use V8TestingScope, and we can't force that on
// whatever gtest class instantiates the fixture.
class MediaCapabilitiesTestContext {
  STACK_ALLOCATED();

 public:
  MediaCapabilitiesTestContext() {
    perf_history_service_ = std::make_unique<MockPerfHistoryService>();
    webrtc_perf_history_service_ =
        std::make_unique<MockWebrtcPerfHistoryService>();
    fake_metrics_provider_ = std::make_unique<FakeMediaMetricsProvider>();

    CHECK(v8_scope_.GetExecutionContext()
              ->GetBrowserInterfaceBroker()
              .SetBinderForTesting(
                  media::mojom::blink::MediaMetricsProvider::Name_,
                  base::BindRepeating(
                      &FakeMediaMetricsProvider::BindRequest,
                      base::Unretained(fake_metrics_provider_.get()))));

    CHECK(v8_scope_.GetExecutionContext()
              ->GetBrowserInterfaceBroker()
              .SetBinderForTesting(
                  media::mojom::blink::VideoDecodePerfHistory::Name_,
                  base::BindRepeating(
                      &MockPerfHistoryService::BindRequest,
                      base::Unretained(perf_history_service_.get()))));

    CHECK(v8_scope_.GetExecutionContext()
              ->GetBrowserInterfaceBroker()
              .SetBinderForTesting(
                  media::mojom::blink::WebrtcVideoPerfHistory::Name_,
                  base::BindRepeating(
                      &MockWebrtcPerfHistoryService::BindRequest,
                      base::Unretained(webrtc_perf_history_service_.get()))));

    media_capabilities_ = MediaCapabilities::mediaCapabilities(
        *v8_scope_.GetWindow().navigator());
  }

  ~MediaCapabilitiesTestContext() {
    CHECK(v8_scope_.GetExecutionContext()
              ->GetBrowserInterfaceBroker()
              .SetBinderForTesting(
                  media::mojom::blink::MediaMetricsProvider::Name_, {}));

    CHECK(v8_scope_.GetExecutionContext()
              ->GetBrowserInterfaceBroker()
              .SetBinderForTesting(
                  media::mojom::blink::VideoDecodePerfHistory::Name_, {}));

    CHECK(v8_scope_.GetExecutionContext()
              ->GetBrowserInterfaceBroker()
              .SetBinderForTesting(
                  media::mojom::blink::WebrtcVideoPerfHistory::Name_, {}));
  }

  ExceptionState& GetExceptionState() { return v8_scope_.GetExceptionState(); }

  ScriptState* GetScriptState() const { return v8_scope_.GetScriptState(); }

  v8::Isolate* GetIsolate() const { return GetScriptState()->GetIsolate(); }

  MediaCapabilities* GetMediaCapabilities() const {
    return media_capabilities_.Get();
  }

  MockPerfHistoryService* GetPerfHistoryService() const {
    return perf_history_service_.get();
  }

  MockWebrtcPerfHistoryService* GetWebrtcPerfHistoryService() const {
    return webrtc_perf_history_service_.get();
  }

  MockPlatform& GetMockPlatform() { return *mock_platform_; }

  void VerifyAndClearMockExpectations() {
    testing::Mock::VerifyAndClearExpectations(GetPerfHistoryService());
    testing::Mock::VerifyAndClearExpectations(GetWebrtcPerfHistoryService());
    testing::Mock::VerifyAndClearExpectations(&GetMockPlatform());
  }

 private:
  V8TestingScope v8_scope_;
  ScopedTestingPlatformSupport<MockPlatform> mock_platform_;
  std::unique_ptr<MockPerfHistoryService> perf_history_service_;
  std::unique_ptr<MockWebrtcPerfHistoryService> webrtc_perf_history_service_;
  std::unique_ptr<FakeMediaMetricsProvider> fake_metrics_provider_;
  Persistent<MediaCapabilities> media_capabilities_;
};

// `kVideoContentType`, and `kCodecProfile` must match.
const char kVideoContentType[] = "video/webm; codecs=\"vp09.00.10.08\"";
const char kAudioContentType[] = "audio/webm; codecs=\"opus\"";
const media::VideoCodecProfile kCodecProfile = media::VP9PROFILE_PROFILE0;
const double kFramerate = 20.5;
const int kWidth = 3840;
const int kHeight = 2160;
const int kBitrate = 2391000;
const char kWebrtcVideoContentType[] = "video/VP9; profile-id=\"0\"";
const char kWebrtcAudioContentType[] = "audio/opus";

// Construct AudioConfig using the constants above.
template <class T, class Enum>
T* CreateAudioConfig(const char content_type[], Enum type) {
  auto* audio_config = MakeGarbageCollected<AudioConfiguration>();
  audio_config->setContentType(content_type);
  auto* decoding_config = MakeGarbageCollected<T>();
  decoding_config->setType(type);
  decoding_config->setAudio(audio_config);
  return decoding_config;
}

// Construct media-source AudioConfig using the constants above.
MediaDecodingConfiguration* CreateAudioDecodingConfig() {
  return CreateAudioConfig<MediaDecodingConfiguration>(
      kAudioContentType, V8MediaDecodingType::Enum::kMediaSource);
}

// Construct webrtc decoding AudioConfig using the constants above.
MediaDecodingConfiguration* CreateWebrtcAudioDecodingConfig() {
  return CreateAudioConfig<MediaDecodingConfiguration>(
      kWebrtcAudioContentType, V8MediaDecodingType::Enum::kWebrtc);
}

// Construct webrtc decoding AudioConfig using the constants above.
MediaEncodingConfiguration* CreateWebrtcAudioEncodingConfig() {
  return CreateAudioConfig<MediaEncodingConfiguration>(
      kWebrtcAudioContentType, V8MediaEncodingType::Enum::kWebrtc);
}

// Construct VideoConfig using the constants above.
template <class T, class Enum>
T* CreateVideoConfig(const char content_type[], Enum type) {
  auto* video_config = MakeGarbageCollected<VideoConfiguration>();
  video_config->setFramerate(kFramerate);
  video_config->setContentType(content_type);
  video_config->setWidth(kWidth);
  video_config->setHeight(kHeight);
  video_config->setBitrate(kBitrate);
  auto* decoding_config = MakeGarbageCollected<T>();
  decoding_config->setType(type);
  decoding_config->setVideo(video_config);
  return decoding_config;
}

// Construct media-source VideoConfig using the constants above.
MediaDecodingConfiguration* CreateDecodingConfig() {
  return CreateVideoConfig<MediaDecodingConfiguration>(
      kVideoContentType, V8MediaDecodingType::Enum::kMediaSource);
}

// Construct webrtc decoding VideoConfig using the constants above.
MediaDecodingConfiguration* CreateWebrtcDecodingConfig() {
  return CreateVideoConfig<MediaDecodingConfiguration>(
      kWebrtcVideoContentType, V8MediaDecodingType::Enum::kWebrtc);
}

// Construct webrtc encoding VideoConfig using the constants above.
MediaEncodingConfiguration* CreateWebrtcEncodingConfig() {
  return CreateVideoConfig<MediaEncodingConfiguration>(
      kWebrtcVideoContentType, V8MediaEncodingType::Enum::kWebrtc);
}

// Construct PredicitonFeatures matching the CreateDecodingConfig, using the
// constants above.
media::mojom::blink::PredictionFeatures CreateFeatures() {
  media::mojom::blink::PredictionFeatures features;
  features.profile = kCodecProfile;
  features.video_size = gfx::Size(kWidth, kHeight);
  features.frames_per_sec = kFramerate;

  // Not set by any tests so far. Choosing sane defaults to mirror production
  // code.
  features.key_system = "";
  features.use_hw_secure_codecs = false;

  return features;
}

// Construct WebrtcPredicitonFeatures matching the CreateWebrtc{Decoding,
// Encoding}Config, using the constants above.
media::mojom::blink::WebrtcPredictionFeatures CreateWebrtcFeatures(
    bool is_decode) {
  media::mojom::blink::WebrtcPredictionFeatures features;
  features.is_decode_stats = is_decode;
  features.profile = kCodecProfile;
  features.video_pixels = kWidth * kHeight;
  return features;
}

// Types of smoothness predictions.
enum class PredictionType {
  kDB,
  kGpuFactories,
};

// Makes DB (PerfHistoryService) callback for use with gtest WillOnce().
// Callback will verify |features| matches |expected_features| and run with
// provided values for |is_smooth| and |is_power_efficient|.
testing::Action<void(media::mojom::blink::PredictionFeaturesPtr,
                     MockPerfHistoryService::GetPerfInfoCallback)>
DbCallback(const media::mojom::blink::PredictionFeatures& expected_features,
           bool is_smooth,
           bool is_power_efficient) {
  return [=](media::mojom::blink::PredictionFeaturesPtr features,
             MockPerfHistoryService::GetPerfInfoCallback got_info_cb) {
    EXPECT_TRUE(features->Equals(expected_features));
    std::move(got_info_cb).Run(is_smooth, is_power_efficient);
  };
}

// Makes DB (WebrtcPerfHistoryService) callback for use with gtest WillOnce().
// Callback will verify |features| and |framerate| matches |expected_features|
// and |expected_framreate| and run with provided values for |is_smooth|.
testing::Action<void(media::mojom::blink::WebrtcPredictionFeaturesPtr,
                     int32_t,
                     MockWebrtcPerfHistoryService::GetPerfInfoCallback)>
WebrtcDbCallback(
    const media::mojom::blink::WebrtcPredictionFeatures& expected_features,
    double expected_framerate,
    bool is_smooth) {
  return [=](media::mojom::blink::WebrtcPredictionFeaturesPtr features,
             int framerate,
             MockWebrtcPerfHistoryService::GetPerfInfoCallback got_info_cb) {
    EXPECT_TRUE(features->Equals(expected_features));
    EXPECT_EQ(framerate, base::ClampRound(expected_framerate));
    std::move(got_info_cb).Run(is_smooth);
  };
}

testing::Action<void(base::OnceClosure)> GpuFactoriesNotifyCallback() {
  return [](base::OnceClosure cb) { std::move(cb).Run(); };
}

// Wrapping decodingInfo() call for readability. Await resolution of the promise
// and return its info.
MediaCapabilitiesInfo* DecodingInfo(
    const MediaDecodingConfiguration* decoding_config,
    MediaCapabilitiesTestContext* context) {
  auto promise = context->GetMediaCapabilities()->decodingInfo(
      context->GetScriptState(), decoding_config, context->GetExceptionState());

  ScriptPromiseTester tester(context->GetScriptState(), promise);
  tester.WaitUntilSettled();

  CHECK(!tester.IsRejected()) << " Cant get info from rejected promise.";

  return NativeValueTraits<MediaCapabilitiesInfo>::NativeValue(
      v8::Isolate::GetCurrent(), tester.Value().V8Value(),
      context->GetExceptionState());
}

MediaCapabilitiesDecodingInfo* GetDecodingInfo(
    const ScriptPromiseTester& tester,
    MediaCapabilitiesTestContext& context) {
  return NativeValueTraits<MediaCapabilitiesDecodingInfo>::NativeValue(
      context.GetIsolate(), tester.Value().V8Value(),
      context.GetExceptionState());
}

// Wrapping encodingInfo() call for readability. Await resolution of the promise
// and return its info.
MediaCapabilitiesInfo* EncodingInfo(
    const MediaEncodingConfiguration* encoding_config,
    MediaCapabilitiesTestContext* context) {
  auto promise = context->GetMediaCapabilities()->encodingInfo(
      context->GetScriptState(), encoding_config, context->GetExceptionState());

  ScriptPromiseTester tester(context->GetScriptState(), promise);
  tester.WaitUntilSettled();

  CHECK(!tester.IsRejected()) << " Cant get info from rejected promise.";

  return NativeValueTraits<MediaCapabilitiesInfo>::NativeValue(
      v8::Isolate::GetCurrent(), tester.Value().V8Value(),
      context->GetExceptionState());
}

// Mock Mojo struct and conversion helper for testing
// KeySystemTrackConfiguration.
struct MockMojoKeySystemTrackConfiguration {
  String robustness;
  std::optional<String> encryption_scheme;
};

std::unique_ptr<MockMojoKeySystemTrackConfiguration> ConvertToMojo(
    const KeySystemTrackConfiguration* idl_config) {
  auto mojo_config = std::make_unique<MockMojoKeySystemTrackConfiguration>();
  if (idl_config->hasRobustness()) {
    mojo_config->robustness = idl_config->robustness();
  }
  if (idl_config->hasEncryptionScheme()) {
    mojo_config->encryption_scheme = idl_config->encryptionScheme();
  }
  return mojo_config;
}

}  // namespace

TEST(MediaCapabilitiesTests, BasicAudio) {
  test::TaskEnvironment task_environment;
  MediaCapabilitiesTestContext context;
  const MediaDecodingConfiguration* kDecodingConfig =
      CreateAudioDecodingConfig();
  MediaCapabilitiesInfo* info = DecodingInfo(kDecodingConfig, &context);
  EXPECT_TRUE(info->supported());
  EXPECT_TRUE(info->smooth());
  EXPECT_TRUE(info->powerEfficient());
}

TEST(MediaCapabilitiesTests, BasicAudioWithProfile) {
  test::TaskEnvironment task_environment;
  MediaCapabilitiesTestContext context;
  auto* decoding_config = CreateAudioConfig<MediaDecodingConfiguration>(
      "audio/mp4; codecs=mp4a.40.42", V8MediaDecodingType::Enum::kMediaSource);
  MediaCapabilitiesInfo* info = DecodingInfo(decoding_config, &context);

  EXPECT_EQ(info->supported(),
            media::IsDecoderSupportedAudioType(
                {media::AudioCodec::kAAC, media::AudioCodecProfile::kXHE_AAC}));
  if (info->supported()) {
    EXPECT_TRUE(info->smooth());
    EXPECT_TRUE(info->powerEfficient());
  }
}

// Other tests will assume these match. Test to be sure they stay in sync.
TEST(MediaCapabilitiesTests, ConfigMatchesFeatures) {
  test::TaskEnvironment task_environment;
  const MediaDecodingConfiguration* kDecodingConfig = CreateDecodingConfig();
  const media::mojom::blink::PredictionFeatures kFeatures = CreateFeatures();

  EXPECT_TRUE(kDecodingConfig->video()->contentType().contains("vp09.00"));
  EXPECT_EQ(static_cast<media::VideoCodecProfile>(kFeatures.profile),
            media::VP9PROFILE_PROFILE0);
  EXPECT_EQ(kCodecProfile, media::VP9PROFILE_PROFILE0);

  EXPECT_EQ(kDecodingConfig->video()->framerate(), kFeatures.frames_per_sec);
  EXPECT_EQ(kDecodingConfig->video()->width(),
            static_cast<uint32_t>(kFeatures.video_size.width()));
  EXPECT_EQ(kDecodingConfig->video()->height(),
            static_cast<uint32_t>(kFeatures.video_size.height()));
}

// Test that non-integer framerate isn't truncated by IPC.
// https://crbug.com/1024399
TEST(MediaCapabilitiesTests, NonIntegerFramerate) {
  test::TaskEnvironment task_environment;
  MediaCapabilitiesTestContext context;

  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      // Enabled features.
      {},
      // Disabled GpuFactories (just use DB).
      {media::kMediaCapabilitiesQueryGpuFactories});

  const auto* kDecodingConfig = CreateDecodingConfig();
  const media::mojom::blink::PredictionFeatures kFeatures = CreateFeatures();

  // FPS for this test must not be a whole number. Assert to ensure the default
  // config meets that condition.
  ASSERT_NE(fmod(kDecodingConfig->video()->framerate(), 1), 0);

  EXPECT_CALL(*context.GetPerfHistoryService(), GetPerfInfo(_, _))
      .WillOnce([&](media::mojom::blink::PredictionFeaturesPtr features,
                    MockPerfHistoryService::GetPerfInfoCallback got_info_cb) {
        // Explicitly check for frames_per_sec equality.
        // PredictionFeatures::Equals() will not catch loss of precision if
        // frames_per_sec is made to be int (currently a double).
        EXPECT_EQ(features->frames_per_sec, kFramerate);

        // Check that other things match as well.
        EXPECT_TRUE(features->Equals(kFeatures));

        std::move(got_info_cb).Run(/*smooth*/ true, /*power_efficient*/ true);
      });

  MediaCapabilitiesInfo* info = DecodingInfo(kDecodingConfig, &context);
  EXPECT_TRUE(info->smooth());
  EXPECT_TRUE(info->powerEfficient());
}

// Test smoothness predictions from DB (PerfHistoryService).
TEST(MediaCapabilitiesTests, PredictWithJustDB) {
  test::TaskEnvironment task_environment;
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      // Enabled features.
      {},
      // Disabled GpuFactories (just use DB).
      {media::kMediaCapabilitiesQueryGpuFactories});

  MediaCapabilitiesTestContext context;
  const auto* kDecodingConfig = CreateDecodingConfig();
  const media::mojom::blink::PredictionFeatures kFeatures = CreateFeatures();

  // DB alone (PerfHistoryService) should be called. Signal smooth=true and
  // power_efficient = false.
  EXPECT_CALL(*context.GetPerfHistoryService(), GetPerfInfo(_, _))
      .WillOnce(DbCallback(kFeatures, /*smooth*/ true, /*power_eff*/ false));
  MediaCapabilitiesInfo* info = DecodingInfo(kDecodingConfig, &context);
  EXPECT_TRUE(info->smooth());
  EXPECT_FALSE(info->powerEfficient());

  // Verify DB call was made.
  testing::Mock::VerifyAndClearExpectations(context.GetPerfHistoryService());

  // Repeat test with inverted smooth and power_efficient results.
  EXPECT_CALL(*context.GetPerfHistoryService(), GetPerfInfo(_, _))
      .WillOnce(DbCallback(kFeatures, /*smooth*/ false, /*power_eff*/ true));
  info = DecodingInfo(kDecodingConfig, &context);
  EXPECT_FALSE(info->smooth());
  EXPECT_TRUE(info->powerEfficient());
}

TEST(MediaCapabilitiesTests, PredictPowerEfficientWithGpuFactories) {
  test::TaskEnvironment task_environment;
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      // Enable GpuFactories for power predictions.
      {media::kMediaCapabilitiesQueryGpuFactories}, {});

  MediaCapabilitiesTestContext context;
  const auto* kDecodingConfig = CreateDecodingConfig();
  const media::mojom::blink::PredictionFeatures kFeatures = CreateFeatures();

  // Setup DB to return powerEfficient = false. We later verify that opposite
  // response from GpuFactories overrides the DB.
  EXPECT_CALL(*context.GetPerfHistoryService(), GetPerfInfo(_, _))
      .WillOnce(DbCallback(kFeatures, /*smooth*/ false, /*power_eff*/ false));

  auto mock_gpu_factories =
      std::make_unique<media::MockGpuVideoAcceleratorFactories>(nullptr);
  ON_CALL(context.GetMockPlatform(), GetGpuFactories())
      .WillByDefault(Return(mock_gpu_factories.get()));

  // First, lets simulate the scenario where we ask before support is known. The
  // async path should notify us when the info arrives. We then get GpuFactroies
  // again and learn the config is supported.
  EXPECT_CALL(context.GetMockPlatform(), GetGpuFactories()).Times(2);
  {
    // InSequence because we EXPECT two calls to IsDecoderSupportKnown with
    // different return values.
    InSequence s;
    EXPECT_CALL(*mock_gpu_factories, IsDecoderSupportKnown())
        .WillOnce(Return(false));
    EXPECT_CALL(*mock_gpu_factories, NotifyDecoderSupportKnown(_))
        .WillOnce(GpuFactoriesNotifyCallback());

    // MediaCapabilities calls IsDecoderSupportKnown() once, and
    // GpuVideoAcceleratorFactories::IsDecoderConfigSupported() also calls it
    // once internally.
    EXPECT_CALL(*mock_gpu_factories, IsDecoderSupportKnown())
        .Times(2)
        .WillRepeatedly(Return(true));
    EXPECT_CALL(*mock_gpu_factories, IsDecoderConfigSupported(_))
        .WillOnce(
            Return(media::GpuVideoAcceleratorFactories::Supported::kTrue));
  }

  base::HistogramTester histogram_tester;

  // Info should be powerEfficient, preferring response of GpuFactories over
  // the DB.
  MediaCapabilitiesInfo* info = DecodingInfo(kDecodingConfig, &context);
  EXPECT_TRUE(info->powerEfficient());
  EXPECT_FALSE(info->smooth());
  histogram_tester.ExpectBucketCount("Media.Capabilities.TimedOut.Decoding",
                                     /*sample=*/false, /*expected_count=*/1);
  context.VerifyAndClearMockExpectations();
  testing::Mock::VerifyAndClearExpectations(mock_gpu_factories.get());

  // Now expect a second query with support is already known to be false. Set
  // DB to respond with the opposite answer.
  EXPECT_CALL(*context.GetPerfHistoryService(), GetPerfInfo(_, _))
      .WillOnce(DbCallback(kFeatures, /*smooth*/ false, /*power_eff*/ true));
  EXPECT_CALL(context.GetMockPlatform(), GetGpuFactories());
  EXPECT_CALL(*mock_gpu_factories, IsDecoderSupportKnown())
      .Times(2)
      .WillRepeatedly(Return(true));
  EXPECT_CALL(*mock_gpu_factories, IsDecoderConfigSupported(_))
      .WillRepeatedly(
          Return(media::GpuVideoAcceleratorFactories::Supported::kFalse));

  // Info should be NOT powerEfficient, preferring response of GpuFactories over
  // the DB.
  info = DecodingInfo(kDecodingConfig, &context);
  EXPECT_FALSE(info->powerEfficient());
  EXPECT_FALSE(info->smooth());
  context.VerifyAndClearMockExpectations();
  testing::Mock::VerifyAndClearExpectations(mock_gpu_factories.get());
}

// Simulate a call to DecodingInfo with smoothness predictions arriving in the
// specified |callback_order|. Ensure that promise resolves correctly only after
// all callbacks have arrived.
void RunCallbackPermutationTest(std::vector<PredictionType> callback_order) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      // Enabled features.
      {media::kMediaCapabilitiesQueryGpuFactories},
      // Disabled features.
      {});

  MediaCapabilitiesTestContext context;
  const auto* kDecodingConfig = CreateDecodingConfig();
  auto mock_gpu_factories =
      std::make_unique<media::MockGpuVideoAcceleratorFactories>(nullptr);

  // DB should be called. Save its callbacks.
  CallbackSaver cb_saver;
  EXPECT_CALL(*context.GetPerfHistoryService(), GetPerfInfo(_, _))
      .WillOnce(Invoke(&cb_saver, &CallbackSaver::SavePerfHistoryCallback));

  // GpuFactories should also be called. Set it up to be async with arrival of
  // support info. Save the "notify" callback.
  EXPECT_CALL(context.GetMockPlatform(), GetGpuFactories())
      .WillRepeatedly(Return(mock_gpu_factories.get()));
  {
    // InSequence because we EXPECT two calls to IsDecoderSupportKnown with
    // different return values.
    InSequence s;
    EXPECT_CALL(*mock_gpu_factories, IsDecoderSupportKnown())
        .WillOnce(Return(false));
    EXPECT_CALL(*mock_gpu_factories, NotifyDecoderSupportKnown(_))
        .WillOnce(
            Invoke(&cb_saver, &CallbackSaver::SaveGpuFactoriesNotifyCallback));
    // MediaCapabilities calls IsDecoderSupportKnown() once, and
    // GpuVideoAcceleratorFactories::IsDecoderConfigSupported() also calls it
    // once internally.
    EXPECT_CALL(*mock_gpu_factories, IsDecoderSupportKnown())
        .Times(2)
        .WillRepeatedly(Return(true));
    EXPECT_CALL(*mock_gpu_factories, IsDecoderConfigSupported(_))
        .WillRepeatedly(
            Return(media::GpuVideoAcceleratorFactories::Supported::kFalse));
  }

  // Call decodingInfo() to kick off the calls to prediction services.
  auto promise = context.GetMediaCapabilities()->decodingInfo(
      context.GetScriptState(), kDecodingConfig, context.GetExceptionState());
  ScriptPromiseTester tester(context.GetScriptState(), promise);

  // Callbacks should all be saved after mojo's pending tasks have run.
  test::RunPendingTasks();
  ASSERT_TRUE(cb_saver.perf_history_cb() && cb_saver.gpu_factories_notify_cb());

  // Complete callbacks in whatever order.
  for (size_t i = 0; i < callback_order.size(); ++i) {
    switch (callback_order[i]) {
      case PredictionType::kDB:
        std::move(cb_saver.perf_history_cb()).Run(true, true);
        break;
      case PredictionType::kGpuFactories:
        std::move(cb_saver.gpu_factories_notify_cb()).Run();
        break;
    }

    // Give callbacks/tasks a chance to run.
    test::RunPendingTasks();

    // Promise should only be resolved once the final callback has run.
    if (i < callback_order.size() - 1) {
      ASSERT_FALSE(tester.IsFulfilled());
    } else {
      ASSERT_TRUE(tester.IsFulfilled());
    }
  }

  ASSERT_FALSE(tester.IsRejected()) << " Cant get info from rejected promise.";
  MediaCapabilitiesInfo* info =
      NativeValueTraits<MediaCapabilitiesInfo>::NativeValue(
          context.GetIsolate(), tester.Value().V8Value(),
          context.GetExceptionState());

  EXPECT_TRUE(info->smooth());
  // DB predicted power_efficient = true, but GpuFactories overrides w/ false.
  EXPECT_FALSE(info->powerEfficient());
}

// Test that decodingInfo() behaves correctly for all orderings/timings of the
// underlying prediction services.
TEST(MediaCapabilitiesTests, PredictionCallbackPermutations) {
  test::TaskEnvironment task_environment;
  std::vector<PredictionType> callback_order(
      {PredictionType::kDB, PredictionType::kGpuFactories});
  do {
    RunCallbackPermutationTest(callback_order);
  } while (std::next_permutation(callback_order.begin(), callback_order.end()));
}

// WebRTC decodingInfo tests.
TEST(MediaCapabilitiesTests, WebrtcDecodingBasicAudio) {
  test::TaskEnvironment task_environment;
  MediaCapabilitiesTestContext context;
  EXPECT_CALL(context.GetMockPlatform(), GetGpuFactories())
      .Times(testing::AtMost(1));

  const MediaDecodingConfiguration* kDecodingConfig =
      CreateWebrtcAudioDecodingConfig();
  MediaCapabilitiesInfo* info = DecodingInfo(kDecodingConfig, &context);
  EXPECT_TRUE(info->supported());
  EXPECT_TRUE(info->smooth());
  EXPECT_TRUE(info->powerEfficient());
}

TEST(MediaCapabilitiesTests, WebrtcDecodingUnsupportedAudio) {
  test::TaskEnvironment task_environment;
  MediaCapabilitiesTestContext context;
  EXPECT_CALL(context.GetMockPlatform(), GetGpuFactories())
      .Times(testing::AtMost(1));

  const MediaDecodingConfiguration* kDecodingConfig =
      CreateAudioConfig<MediaDecodingConfiguration>(
          "audio/FooCodec", V8MediaDecodingType::Enum::kWebrtc);
  MediaCapabilitiesInfo* info = DecodingInfo(kDecodingConfig, &context);
  EXPECT_FALSE(info->supported());
  EXPECT_FALSE(info->smooth());
  EXPECT_FALSE(info->powerEfficient());
}

// Other tests will assume these match. Test to be sure they stay in sync.
TEST(MediaCapabilitiesTests, WebrtcConfigMatchesFeatures) {
  test::TaskEnvironment task_environment;
  const MediaDecodingConfiguration* kDecodingConfig =
      CreateWebrtcDecodingConfig();
  const MediaEncodingConfiguration* kEncodingConfig =
      CreateWebrtcEncodingConfig();
  const media::mojom::blink::WebrtcPredictionFeatures kDecodeFeatures =
      CreateWebrtcFeatures(/*is_decode=*/true);
  const media::mojom::blink::WebrtcPredictionFeatures kEncodeFeatures =
      CreateWebrtcFeatures(/*is_decode=*/false);

  EXPECT_TRUE(kDecodeFeatures.is_decode_stats);
  EXPECT_FALSE(kEncodeFeatures.is_decode_stats);

  EXPECT_TRUE(kDecodingConfig->video()->contentType().contains("video/VP9"));
  EXPECT_TRUE(kEncodingConfig->video()->contentType().contains("video/VP9"));
  EXPECT_EQ(static_cast<media::VideoCodecProfile>(kDecodeFeatures.profile),
            media::VP9PROFILE_PROFILE0);
  EXPECT_EQ(static_cast<media::VideoCodecProfile>(kEncodeFeatures.profile),
            media::VP9PROFILE_PROFILE0);
  EXPECT_EQ(kCodecProfile, media::VP9PROFILE_PROFILE0);

  EXPECT_EQ(
      kDecodingConfig->video()->width() * kDecodingConfig->video()->height(),
      static_cast<uint32_t>(kDecodeFeatures.video_pixels));
  EXPECT_EQ(
      kEncodingConfig->video()->width() * kEncodingConfig->video()->height(),
      static_cast<uint32_t>(kEncodeFeatures.video_pixels));
}

// Test smoothness predictions from DB (WebrtcPerfHistoryService).
TEST(MediaCapabilitiesTests, WebrtcDecodingBasicVideo) {
  test::TaskEnvironment task_environment;
  MediaCapabilitiesTestContext context;
  EXPECT_CALL(context.GetMockPlatform(), GetGpuFactories())
      .Times(testing::AtMost(1));
  const auto* kDecodingConfig = CreateWebrtcDecodingConfig();
  const media::mojom::blink::WebrtcPredictionFeatures kFeatures =
      CreateWebrtcFeatures(/*is_decode=*/true);

  // WebrtcPerfHistoryService should be queried for smoothness. Signal
  // smooth=true.
  EXPECT_CALL(*context.GetWebrtcPerfHistoryService(), GetPerfInfo(_, _, _))
      .WillOnce(WebrtcDbCallback(kFeatures, kFramerate, /*is_smooth=*/true));
  MediaCapabilitiesInfo* info = DecodingInfo(kDecodingConfig, &context);
  EXPECT_TRUE(info->supported());
  EXPECT_TRUE(info->smooth());
  EXPECT_FALSE(info->powerEfficient());

  // Verify DB call was made.
  testing::Mock::VerifyAndClearExpectations(
      context.GetWebrtcPerfHistoryService());

  // Repeat test with smooth=false.
  EXPECT_CALL(*context.GetWebrtcPerfHistoryService(), GetPerfInfo(_, _, _))
      .WillOnce(WebrtcDbCallback(kFeatures, kFramerate, /*is_smooth=*/false));
  info = DecodingInfo(kDecodingConfig, &context);
  EXPECT_TRUE(info->supported());
  EXPECT_FALSE(info->smooth());
  EXPECT_FALSE(info->powerEfficient());
}

TEST(MediaCapabilitiesTests, WebrtcDecodingUnsupportedVideo) {
  test::TaskEnvironment task_environment;
  MediaCapabilitiesTestContext context;
  EXPECT_CALL(context.GetMockPlatform(), GetGpuFactories())
      .Times(testing::AtMost(1));

  const MediaDecodingConfiguration* kDecodingConfig =
      CreateVideoConfig<MediaDecodingConfiguration>(
          "video/FooCodec", V8MediaDecodingType::Enum::kWebrtc);

  MediaCapabilitiesInfo* info = DecodingInfo(kDecodingConfig, &context);
  EXPECT_FALSE(info->supported());
  EXPECT_FALSE(info->smooth());
  EXPECT_FALSE(info->powerEfficient());
}

TEST(MediaCapabilitiesTests, WebrtcDecodingSpatialScalability) {
  test::TaskEnvironment task_environment;
  MediaCapabilitiesTestContext context;
  EXPECT_CALL(context.GetMockPlatform(), GetGpuFactories())
      .Times(testing::AtMost(1));

  auto* decoding_config = CreateWebrtcDecodingConfig();
  auto* video_config = decoding_config->getVideoOr(nullptr);
  video_config->setSpatialScalability(false);
  const media::mojom::blink::WebrtcPredictionFeatures kFeatures =
      CreateWebrtcFeatures(/*is_decode=*/true);

  // WebrtcPerfHistoryService should be queried for smoothness. Signal
  // smooth=true.
  EXPECT_CALL(*context.GetWebrtcPerfHistoryService(), GetPerfInfo(_, _, _))
      .WillOnce(WebrtcDbCallback(kFeatures, kFramerate, /*is_smooth=*/true));
  MediaCapabilitiesInfo* info = DecodingInfo(decoding_config, &context);
  EXPECT_TRUE(info->supported());
  EXPECT_TRUE(info->smooth());
  EXPECT_FALSE(info->powerEfficient());

  // Verify DB call was made.
  testing::Mock::VerifyAndClearExpectations(
      context.GetWebrtcPerfHistoryService());

  // Repeat test with spatialScalability=true.
  video_config->setSpatialScalability(true);
  EXPECT_CALL(*context.GetWebrtcPerfHistoryService(), GetPerfInfo(_, _, _))
      .WillOnce(WebrtcDbCallback(kFeatures, kFramerate, /*is_smooth=*/false));
  info = DecodingInfo(decoding_config, &context);
  EXPECT_TRUE(info->supported());
  EXPECT_FALSE(info->smooth());
  EXPECT_FALSE(info->powerEfficient());
}

class MediaCapabilitiesWebrtcTests : public ::testing::Test {
 public:
  static std::unique_ptr<WebrtcDecodingInfoHandler>
  CreateWebrtcDecodingInfoHandler(
      std::unique_ptr<webrtc::VideoDecoderFactory> video_decoder_factory,
      webrtc::scoped_refptr<webrtc::AudioDecoderFactory> audio_decoder_factory,
      media::GpuVideoAcceleratorFactories* gpu_factories) {
    return base::WrapUnique(new WebrtcDecodingInfoHandler(
        std::move(video_decoder_factory), std::move(audio_decoder_factory),
        gpu_factories));
  }

  static std::unique_ptr<WebrtcEncodingInfoHandler>
  CreateWebrtcEncodingInfoHandler(
      std::unique_ptr<webrtc::VideoEncoderFactory> video_encoder_factory,
      webrtc::scoped_refptr<webrtc::AudioEncoderFactory> audio_encoder_factory,
      media::GpuVideoAcceleratorFactories* gpu_factories) {
    return base::WrapUnique(new WebrtcEncodingInfoHandler(
        std::move(video_encoder_factory), std::move(audio_encoder_factory),
        gpu_factories));
  }
};

// WebRTC encodingInfo tests.
TEST_F(MediaCapabilitiesWebrtcTests, EncodingBasicAudio) {
  test::TaskEnvironment task_environment;
  MediaCapabilitiesTestContext context;
  EXPECT_CALL(context.GetMockPlatform(), GetGpuFactories())
      .Times(testing::AtMost(1));

  const MediaEncodingConfiguration* kEncodingConfig =
      CreateWebrtcAudioEncodingConfig();
  MediaCapabilitiesInfo* info = EncodingInfo(kEncodingConfig, &context);
  EXPECT_TRUE(info->supported());
  EXPECT_TRUE(info->smooth());
  EXPECT_TRUE(info->powerEfficient());
}

TEST_F(MediaCapabilitiesWebrtcTests, EncodingUnsupportedAudio) {
  test::TaskEnvironment task_environment;
  MediaCapabilitiesTestContext context;
  EXPECT_CALL(context.GetMockPlatform(), GetGpuFactories())
      .Times(testing::AtMost(1));
  const MediaEncodingConfiguration* kEncodingConfig =
      CreateAudioConfig<MediaEncodingConfiguration>(
          "audio/FooCodec", V8MediaEncodingType::Enum::kWebrtc);
  MediaCapabilitiesInfo* info = EncodingInfo(kEncodingConfig, &context);
  EXPECT_FALSE(info->supported());
  EXPECT_FALSE(info->smooth());
  EXPECT_FALSE(info->powerEfficient());
}

// Test smoothness predictions from DB (WebrtcPerfHistoryService).
TEST_F(MediaCapabilitiesWebrtcTests, EncodingBasicVideo) {
  test::TaskEnvironment task_environment;
  MediaCapabilitiesTestContext context;
  EXPECT_CALL(context.GetMockPlatform(), GetGpuFactories())
      .Times(testing::AtMost(1));
  const auto* kEncodingConfig = CreateWebrtcEncodingConfig();
  const media::mojom::blink::WebrtcPredictionFeatures kFeatures =
      CreateWebrtcFeatures(/*is_decode=*/false);

  // WebrtcPerfHistoryService should be queried for smoothness. Signal
  // smooth=true.
  EXPECT_CALL(*context.GetWebrtcPerfHistoryService(), GetPerfInfo(_, _, _))
      .WillOnce(WebrtcDbCallback(kFeatures, kFramerate, /*is_smooth=*/true));
  MediaCapabilitiesInfo* info = EncodingInfo(kEncodingConfig, &context);
  EXPECT_TRUE(info->supported());
  EXPECT_TRUE(info->smooth());
  EXPECT_FALSE(info->powerEfficient());

  // Verify DB call was made.
  testing::Mock::VerifyAndClearExpectations(
      context.GetWebrtcPerfHistoryService());

  // Repeat test with smooth=false.
  EXPECT_CALL(*context.GetWebrtcPerfHistoryService(), GetPerfInfo(_, _, _))
      .WillOnce(WebrtcDbCallback(kFeatures, kFramerate, /*is_smooth=*/false));
  info = EncodingInfo(kEncodingConfig, &context);
  EXPECT_TRUE(info->supported());
  EXPECT_FALSE(info->smooth());
  EXPECT_FALSE(info->powerEfficient());
}

TEST_F(MediaCapabilitiesWebrtcTests, EncodingUnsupportedVideo) {
  test::TaskEnvironment task_environment;
  MediaCapabilitiesTestContext context;
  EXPECT_CALL(context.GetMockPlatform(), GetGpuFactories())
      .Times(testing::AtMost(1));

  const MediaEncodingConfiguration* kEncodingConfig =
      CreateVideoConfig<MediaEncodingConfiguration>(
          "video/FooCodec", V8MediaEncodingType::Enum::kWebrtc);

  MediaCapabilitiesInfo* info = EncodingInfo(kEncodingConfig, &context);
  EXPECT_FALSE(info->supported());
  EXPECT_FALSE(info->smooth());
  EXPECT_FALSE(info->powerEfficient());
}

TEST_F(MediaCapabilitiesWebrtcTests, EncodingScalabilityMode) {
  test::TaskEnvironment task_environment;
  MediaCapabilitiesTestContext context;
  EXPECT_CALL(context.GetMockPlatform(), GetGpuFactories())
      .Times(testing::AtMost(1));
  auto* encoding_config = CreateWebrtcEncodingConfig();
  auto* video_config = encoding_config->getVideoOr(nullptr);
  video_config->setScalabilityMode("L3T3_KEY");
  const media::mojom::blink::WebrtcPredictionFeatures kFeatures =
      CreateWebrtcFeatures(/*is_decode=*/false);

  // WebrtcPerfHistoryService should be queried for smoothness. Signal
  // smooth=true.
  EXPECT_CALL(*context.GetWebrtcPerfHistoryService(), GetPerfInfo(_, _, _))
      .WillOnce(WebrtcDbCallback(kFeatures, kFramerate, /*is_smooth=*/true));
  MediaCapabilitiesInfo* info = EncodingInfo(encoding_config, &context);
  EXPECT_TRUE(info->supported());
  EXPECT_TRUE(info->smooth());
  EXPECT_FALSE(info->powerEfficient());

  // Verify DB call was made.
  testing::Mock::VerifyAndClearExpectations(
      context.GetWebrtcPerfHistoryService());

  // Repeat with unsupported mode.
  video_config->setScalabilityMode("L3T2_Foo");
  info = EncodingInfo(encoding_config, &context);
  EXPECT_FALSE(info->supported());
  EXPECT_FALSE(info->smooth());
  EXPECT_FALSE(info->powerEfficient());
}

TEST_F(MediaCapabilitiesWebrtcTests, DecodePowerEfficientIsSmooth) {
  test::TaskEnvironment task_environment;
  // Set up a custom decoding info handler with a GPU factory that returns
  // supported and powerEfficient.
  MediaCapabilitiesTestContext context;
  media::MockGpuVideoAcceleratorFactories mock_gpu_factories(nullptr);
  auto video_decoder_factory = blink::CreateWebrtcVideoDecoderFactory(
      &mock_gpu_factories, Platform::Current()->GetRenderingColorSpace(),
      base::DoNothing());

  std::unique_ptr<WebrtcDecodingInfoHandler> decoding_info_handler =
      CreateWebrtcDecodingInfoHandler(std::move(video_decoder_factory),
                                      blink::CreateWebrtcAudioDecoderFactory(),
                                      &mock_gpu_factories);

  context.GetMediaCapabilities()->set_webrtc_decoding_info_handler_for_test(
      decoding_info_handler.get());

  EXPECT_CALL(mock_gpu_factories, IsDecoderSupportKnown())
      .WillRepeatedly(Return(true));
  EXPECT_CALL(mock_gpu_factories, IsDecoderConfigSupported(_))
      .WillOnce(Return(media::GpuVideoAcceleratorFactories::Supported::kTrue));

  const auto* kDecodingConfig = CreateWebrtcDecodingConfig();
  MediaCapabilitiesInfo* info = DecodingInfo(kDecodingConfig, &context);
  // Expect that powerEfficient==true implies that smooth==true without querying
  // perf history.
  EXPECT_TRUE(info->supported());
  EXPECT_TRUE(info->smooth());
  EXPECT_TRUE(info->powerEfficient());

  context.GetMediaCapabilities()->set_webrtc_decoding_info_handler_for_test(
      nullptr);
}

TEST_F(MediaCapabilitiesWebrtcTests, DecodeOverridePowerEfficientIsSmooth) {
  test::TaskEnvironment task_environment;
  // Override the default behavior using a field trial. Query smooth from perf
  // history regardless the value of powerEfficient.
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeaturesAndParameters(
      // Enabled features w/ parameters
      {{media::kWebrtcMediaCapabilitiesParameters,
        {{MediaCapabilities::kWebrtcDecodeSmoothIfPowerEfficientParamName,
          "false"}}}},
      // Disabled features.
      {});

  // Set up a custom decoding info handler with a GPU factory that returns
  // supported and powerEfficient.
  MediaCapabilitiesTestContext context;
  media::MockGpuVideoAcceleratorFactories mock_gpu_factories(nullptr);
  auto video_decoder_factory = blink::CreateWebrtcVideoDecoderFactory(
      &mock_gpu_factories, Platform::Current()->GetRenderingColorSpace(),
      base::DoNothing());
  std::unique_ptr<WebrtcDecodingInfoHandler> decoding_info_handler =
      CreateWebrtcDecodingInfoHandler(std::move(video_decoder_factory),
                                      blink::CreateWebrtcAudioDecoderFactory(),
                                      &mock_gpu_factories);
  context.GetMediaCapabilities()->set_webrtc_decoding_info_handler_for_test(
      decoding_info_handler.get());

  EXPECT_CALL(mock_gpu_factories, IsDecoderSupportKnown())
      .WillRepeatedly(Return(true));
  EXPECT_CALL(mock_gpu_factories, IsDecoderConfigSupported(_))
      .WillOnce(Return(media::GpuVideoAcceleratorFactories::Supported::kTrue));

  const auto* kDecodingConfig = CreateWebrtcDecodingConfig();
  media::mojom::blink::WebrtcPredictionFeatures expected_features =
      CreateWebrtcFeatures(/*is_decode=*/true);
  expected_features.hardware_accelerated = true;

  base::HistogramTester histogram_tester;
  EXPECT_CALL(*context.GetWebrtcPerfHistoryService(), GetPerfInfo(_, _, _))
      .WillOnce(
          WebrtcDbCallback(expected_features, kFramerate, /*is_smooth=*/false));
  MediaCapabilitiesInfo* info = DecodingInfo(kDecodingConfig, &context);
  // Expect powerEfficient is true but smooth returned from perf history is
  // false.
  EXPECT_TRUE(info->supported());
  EXPECT_FALSE(info->smooth());
  EXPECT_TRUE(info->powerEfficient());
  histogram_tester.ExpectBucketCount(
      "Media.Capabilities.TimedOut.WebrtcDecoding",
      /*sample=*/false, /*expected_count=*/1);

  context.GetMediaCapabilities()->set_webrtc_decoding_info_handler_for_test(
      nullptr);
}

TEST_F(MediaCapabilitiesWebrtcTests, EncodePowerEfficientIsSmooth) {
  test::TaskEnvironment task_environment;
  base::HistogramTester histogram_tester;
  // Set up a custom decoding info handler with a GPU factory that returns
  // supported and powerEfficient.
  MediaCapabilitiesTestContext context;
  media::MockGpuVideoAcceleratorFactories mock_gpu_factories(nullptr);

  auto video_encoder_factory =
      std::make_unique<RTCVideoEncoderFactory>(&mock_gpu_factories, nullptr);
  // Ensure all the profiles in our mock GPU factory are allowed.
  video_encoder_factory->clear_disabled_profiles_for_testing();

  std::unique_ptr<WebrtcEncodingInfoHandler> encoding_info_handler =
      CreateWebrtcEncodingInfoHandler(std::move(video_encoder_factory),
                                      blink::CreateWebrtcAudioEncoderFactory(),
                                      &mock_gpu_factories);
  context.GetMediaCapabilities()->set_webrtc_encoding_info_handler_for_test(
      encoding_info_handler.get());

  EXPECT_CALL(mock_gpu_factories, IsEncoderSupportKnown())
      .WillRepeatedly(Return(true));
  EXPECT_CALL(mock_gpu_factories, GetVideoEncodeAcceleratorSupportedProfiles())
      .WillOnce(Return(media::VideoEncodeAccelerator::SupportedProfiles{
          {media::VP9PROFILE_PROFILE0, gfx::Size(kWidth, kHeight)}}));

  const auto* kEncodingConfig = CreateWebrtcEncodingConfig();
  MediaCapabilitiesInfo* info = EncodingInfo(kEncodingConfig, &context);
  // Expect that powerEfficient==true implies that smooth==true without querying
  // perf history.
  EXPECT_TRUE(info->supported());
  EXPECT_TRUE(info->smooth());
  EXPECT_TRUE(info->powerEfficient());
  histogram_tester.ExpectBucketCount(
      "Media.Capabilities.TimedOut.WebrtcEncoding",
      /*sample=*/false, /*expected_count=*/1);

  // RTCVideoEncoderFactory destroys MojoVideoEncoderMetricsProvider on the
  // task runner of GpuVideoAcceleratorFactories.
  EXPECT_CALL(mock_gpu_factories, GetTaskRunner())
      .WillOnce(Return(base::SequencedTaskRunner::GetCurrentDefault()));
  context.GetMediaCapabilities()->set_webrtc_encoding_info_handler_for_test(
      nullptr);
}

TEST_F(MediaCapabilitiesWebrtcTests, EncodeOverridePowerEfficientIsSmooth) {
  test::TaskEnvironment task_environment;
  // Override the default behavior using a field trial. Query smooth from perf
  // history regardless the value of powerEfficient.
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeaturesAndParameters(
      // Enabled features w/ parameters
      {{media::kWebrtcMediaCapabilitiesParameters,
        {{MediaCapabilities::kWebrtcEncodeSmoothIfPowerEfficientParamName,
          "false"}}}},
      // Disabled features.
      {});

  // Set up a custom decoding info handler with a GPU factory that returns
  // supported and powerEfficient.
  MediaCapabilitiesTestContext context;
  media::MockGpuVideoAcceleratorFactories mock_gpu_factories(nullptr);

  auto video_encoder_factory =
      std::make_unique<RTCVideoEncoderFactory>(&mock_gpu_factories, nullptr);
  // Ensure all the profiles in our mock GPU factory are allowed.
  video_encoder_factory->clear_disabled_profiles_for_testing();

  std::unique_ptr<WebrtcEncodingInfoHandler> encoding_info_handler =
      CreateWebrtcEncodingInfoHandler(std::move(video_encoder_factory),
                                      blink::CreateWebrtcAudioEncoderFactory(),
                                      &mock_gpu_factories);
  context.GetMediaCapabilities()->set_webrtc_encoding_info_handler_for_test(
      encoding_info_handler.get());

  EXPECT_CALL(mock_gpu_factories, IsEncoderSupportKnown())
      .WillRepeatedly(Return(true));
  EXPECT_CALL(mock_gpu_factories, GetVideoEncodeAcceleratorSupportedProfiles())
      .WillOnce(Return(media::VideoEncodeAccelerator::SupportedProfiles{
          {media::VP9PROFILE_PROFILE0, gfx::Size(kWidth, kHeight)}}));

  const auto* kEncodingConfig = CreateWebrtcEncodingConfig();
  media::mojom::blink::WebrtcPredictionFeatures expected_features =
      CreateWebrtcFeatures(/*is_decode=*/false);
  expected_features.hardware_accelerated = true;

  EXPECT_CALL(*context.GetWebrtcPerfHistoryService(), GetPerfInfo(_, _, _))
      .WillOnce(
          WebrtcDbCallback(expected_features, kFramerate, /*is_smooth=*/false));
  MediaCapabilitiesInfo* info = EncodingInfo(kEncodingConfig, &context);
  // Expect powerEfficient is true but smooth returned from perf history is
  // false.
  EXPECT_TRUE(info->supported());
  EXPECT_FALSE(info->smooth());
  EXPECT_TRUE(info->powerEfficient());

  // RTCVideoEncoderFactory destroys MojoVideoEncoderMetricsProvider on the
  // task runner of GpuVideoAcceleratorFactories.
  EXPECT_CALL(mock_gpu_factories, GetTaskRunner())
      .WillOnce(Return(base::SequencedTaskRunner::GetCurrentDefault()));
  context.GetMediaCapabilities()->set_webrtc_encoding_info_handler_for_test(
      nullptr);
}

TEST(MediaCapabilitiesTests, KeySystemTrackConfiguration_EncryptionScheme) {
  ScopedKeySystemTrackConfigurationEncryptionSchemeForTest
      scoped_encryption_scheme_for_test(true);
  test::TaskEnvironment task_environment;
  V8TestingScope scope;

  auto* idl_config = KeySystemTrackConfiguration::Create(scope.GetIsolate());
  idl_config->setRobustness("SW_SECURE_CRYPTO");
  idl_config->setEncryptionScheme("cbcs");  // Test the new field

  auto mojo_config = ConvertToMojo(idl_config);

  EXPECT_EQ(mojo_config->robustness, "SW_SECURE_CRYPTO");
  ASSERT_TRUE(mojo_config->encryption_scheme.has_value());
  EXPECT_EQ(mojo_config->encryption_scheme.value(), "cbcs");
}

TEST(MediaCapabilitiesTests, VideoDecodingInfoGpuFactoryTimeoutTest) {
  test::TaskEnvironment task_environment(
      test::TaskEnvironment::TimeSource::MOCK_TIME);
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      {media::kMediaCapabilitiesQueryGpuFactories}, {});

  MediaCapabilitiesTestContext context;
  const auto* kDecodingConfig = CreateDecodingConfig();
  const media::mojom::blink::PredictionFeatures kFeatures = CreateFeatures();

  // DB returns smooth=true and power_eff=false, but GpuFactories query hangs.
  EXPECT_CALL(*context.GetPerfHistoryService(), GetPerfInfo(_, _))
      .WillOnce(DbCallback(kFeatures, /*smooth*/ true, /*power_eff*/ false));

  auto mock_gpu_factories =
      std::make_unique<media::MockGpuVideoAcceleratorFactories>(nullptr);
  EXPECT_CALL(context.GetMockPlatform(), GetGpuFactories())
      .WillRepeatedly(Return(mock_gpu_factories.get()));
  CallbackSaver cb_saver;
  EXPECT_CALL(*mock_gpu_factories, IsDecoderSupportKnown())
      .WillOnce(Return(false));
  EXPECT_CALL(*mock_gpu_factories, NotifyDecoderSupportKnown(_))
      .WillOnce(
          Invoke(&cb_saver, &CallbackSaver::SaveGpuFactoriesNotifyCallback));

  base::HistogramTester histogram_tester;

  auto promise = context.GetMediaCapabilities()->decodingInfo(
      context.GetScriptState(), kDecodingConfig, context.GetExceptionState());
  ScriptPromiseTester tester(context.GetScriptState(), promise);

  // Fast-forward to trigger the query timeout.
  task_environment.FastForwardBy(
      MediaCapabilities::kMediaCapabilitiesQueryTimeout);
  tester.WaitUntilSettled();

  EXPECT_TRUE(tester.IsFulfilled());
  auto* info = GetDecodingInfo(tester, context);
  // When GPU factory times out, supported falls back to true for built-in VP9,
  // smooth comes from the DB, and powerEfficient is false.
  EXPECT_TRUE(info->supported());
  EXPECT_TRUE(info->smooth());
  EXPECT_FALSE(info->powerEfficient());
  histogram_tester.ExpectBucketCount("Media.Capabilities.TimedOut.Decoding",
                                     /*sample=*/true, /*expected_count=*/1);

  // Verify late callback from GpuFactories does not crash after resolution.
  ASSERT_TRUE(cb_saver.gpu_factories_notify_cb());
  std::move(cb_saver.gpu_factories_notify_cb()).Run();
}

TEST(MediaCapabilitiesTests,
     VideoDecodingInfoBuiltinCodecGpuFalseDbTimeoutTest) {
  test::TaskEnvironment task_environment(
      test::TaskEnvironment::TimeSource::MOCK_TIME);
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      {media::kMediaCapabilitiesQueryGpuFactories}, {});

  MediaCapabilitiesTestContext context;
  const auto* kDecodingConfig = CreateDecodingConfig();

  CallbackSaver cb_saver;
  EXPECT_CALL(*context.GetPerfHistoryService(), GetPerfInfo(_, _))
      .WillOnce(Invoke(&cb_saver, &CallbackSaver::SavePerfHistoryCallback));

  // GpuFactories reports unsupported for hardware decoding, and DB query hangs.
  auto mock_gpu_factories =
      std::make_unique<media::MockGpuVideoAcceleratorFactories>(nullptr);
  EXPECT_CALL(context.GetMockPlatform(), GetGpuFactories())
      .WillRepeatedly(Return(mock_gpu_factories.get()));
  EXPECT_CALL(*mock_gpu_factories, IsDecoderSupportKnown())
      .WillRepeatedly(Return(true));
  EXPECT_CALL(*mock_gpu_factories, IsDecoderConfigSupported(_))
      .WillRepeatedly(
          Return(media::GpuVideoAcceleratorFactories::Supported::kFalse));

  auto promise = context.GetMediaCapabilities()->decodingInfo(
      context.GetScriptState(), kDecodingConfig, context.GetExceptionState());
  ScriptPromiseTester tester(context.GetScriptState(), promise);

  test::RunPendingTasks();
  ASSERT_TRUE(cb_saver.perf_history_cb());
  EXPECT_FALSE(tester.IsFulfilled());
  EXPECT_FALSE(tester.IsRejected());

  // Fast-forward to trigger DB timeout.
  task_environment.FastForwardBy(
      MediaCapabilities::kMediaCapabilitiesQueryTimeout);
  tester.WaitUntilSettled();

  EXPECT_TRUE(tester.IsFulfilled());
  auto* info = GetDecodingInfo(tester, context);
  // Software decoder fallback ensures supported remains true for built-in VP9
  // even though GPU factory returned false and DB timed out.
  EXPECT_TRUE(info->supported());
  EXPECT_FALSE(info->powerEfficient());
  EXPECT_FALSE(info->smooth());

  // Run the saved DB callback to verify late callback handling.
  ASSERT_TRUE(cb_saver.perf_history_cb());
  std::move(cb_saver.perf_history_cb()).Run(true, false);
}

TEST(MediaCapabilitiesTests, VideoDecodingInfoMojoDisconnectTest) {
  test::TaskEnvironment task_environment;
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      {}, {media::kMediaCapabilitiesQueryGpuFactories});

  MediaCapabilitiesTestContext context;
  const auto* kDecodingConfig = CreateDecodingConfig();

  CallbackSaver cb_saver;
  EXPECT_CALL(*context.GetPerfHistoryService(), GetPerfInfo(_, _))
      .WillOnce(Invoke(&cb_saver, &CallbackSaver::SavePerfHistoryCallback));

  auto promise = context.GetMediaCapabilities()->decodingInfo(
      context.GetScriptState(), kDecodingConfig, context.GetExceptionState());
  ScriptPromiseTester tester(context.GetScriptState(), promise);

  test::RunPendingTasks();
  ASSERT_TRUE(cb_saver.perf_history_cb());
  EXPECT_FALSE(tester.IsFulfilled());
  EXPECT_FALSE(tester.IsRejected());

  // Disconnecting the service should immediately trigger fallback resolution.
  context.GetPerfHistoryService()->Disconnect();
  test::RunPendingTasks();

  EXPECT_TRUE(tester.IsFulfilled());
  auto* info = GetDecodingInfo(tester, context);
  // With GpuFactories disabled and DB disconnected, supported defaults to true.
  EXPECT_TRUE(info->supported());
  EXPECT_FALSE(info->smooth());
  EXPECT_FALSE(info->powerEfficient());
}

TEST(MediaCapabilitiesTests, WebrtcDecodingInfoMojoDisconnectTest) {
  test::TaskEnvironment task_environment;
  MediaCapabilitiesTestContext context;
  const auto* kDecodingConfig = CreateWebrtcDecodingConfig();

  CallbackSaver cb_saver;
  EXPECT_CALL(*context.GetWebrtcPerfHistoryService(), GetPerfInfo(_, _, _))
      .WillOnce(
          Invoke(&cb_saver, &CallbackSaver::SaveWebrtcPerfHistoryCallback));

  auto promise = context.GetMediaCapabilities()->decodingInfo(
      context.GetScriptState(), kDecodingConfig, context.GetExceptionState());
  ScriptPromiseTester tester(context.GetScriptState(), promise);

  test::RunPendingTasks();
  ASSERT_TRUE(cb_saver.webrtc_perf_history_cb());
  EXPECT_FALSE(tester.IsFulfilled());
  EXPECT_FALSE(tester.IsRejected());

  // Disconnecting decode_history_service_ should NOT trigger timeout/fallback
  // for this WebRTC query.
  context.GetPerfHistoryService()->Disconnect();
  test::RunPendingTasks();
  EXPECT_FALSE(tester.IsFulfilled());
  EXPECT_FALSE(tester.IsRejected());

  // Disconnecting webrtc_history_service_ should immediately trigger fallback.
  context.GetWebrtcPerfHistoryService()->Disconnect();
  test::RunPendingTasks();
  EXPECT_TRUE(tester.IsFulfilled());

  auto* info = GetDecodingInfo(tester, context);
  // For WebRTC queries, supported is preserved from the WebRTC handler.
  EXPECT_TRUE(info->supported());
  EXPECT_FALSE(info->smooth());
  EXPECT_FALSE(info->powerEfficient());
}

TEST(MediaCapabilitiesTests, WebrtcDecodingInfoTimeoutTest) {
  test::TaskEnvironment task_environment(
      test::TaskEnvironment::TimeSource::MOCK_TIME);
  MediaCapabilitiesTestContext context;
  const auto* kDecodingConfig = CreateWebrtcDecodingConfig();

  CallbackSaver cb_saver;
  EXPECT_CALL(*context.GetWebrtcPerfHistoryService(), GetPerfInfo(_, _, _))
      .WillOnce(
          Invoke(&cb_saver, &CallbackSaver::SaveWebrtcPerfHistoryCallback));

  base::HistogramTester histogram_tester;

  auto promise = context.GetMediaCapabilities()->decodingInfo(
      context.GetScriptState(), kDecodingConfig, context.GetExceptionState());
  ScriptPromiseTester tester(context.GetScriptState(), promise);

  test::RunPendingTasks();
  ASSERT_TRUE(cb_saver.webrtc_perf_history_cb());
  EXPECT_FALSE(tester.IsFulfilled());
  EXPECT_FALSE(tester.IsRejected());

  // Fast-forward to trigger query timeout when DB query hangs.
  task_environment.FastForwardBy(
      MediaCapabilities::kMediaCapabilitiesQueryTimeout);
  tester.WaitUntilSettled();

  EXPECT_TRUE(tester.IsFulfilled());
  auto* info = GetDecodingInfo(tester, context);
  // Supported is preserved from the WebRTC handler, while smooth defaults to
  // false.
  EXPECT_TRUE(info->supported());
  EXPECT_FALSE(info->smooth());
  EXPECT_FALSE(info->powerEfficient());
  histogram_tester.ExpectBucketCount(
      "Media.Capabilities.TimedOut.WebrtcDecoding",
      /*sample=*/true, /*expected_count=*/1);

  // Run the saved callback to verify late callback handling.
  ASSERT_TRUE(cb_saver.webrtc_perf_history_cb());
  std::move(cb_saver.webrtc_perf_history_cb()).Run(true);
}

}  // namespace blink
