summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/modules/video_rvfc/video_frame_callback_requester_impl_test.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/blink/renderer/modules/video_rvfc/video_frame_callback_requester_impl_test.cc')
-rw-r--r--chromium/third_party/blink/renderer/modules/video_rvfc/video_frame_callback_requester_impl_test.cc383
1 files changed, 383 insertions, 0 deletions
diff --git a/chromium/third_party/blink/renderer/modules/video_rvfc/video_frame_callback_requester_impl_test.cc b/chromium/third_party/blink/renderer/modules/video_rvfc/video_frame_callback_requester_impl_test.cc
new file mode 100644
index 00000000000..e7c3fe022ac
--- /dev/null
+++ b/chromium/third_party/blink/renderer/modules/video_rvfc/video_frame_callback_requester_impl_test.cc
@@ -0,0 +1,383 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// 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/video_rvfc/video_frame_callback_requester_impl.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/bindings/core/v8/script_function.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
+#include "third_party/blink/renderer/core/dom/scripted_animation_controller.h"
+#include "third_party/blink/renderer/core/html/media/html_media_test_helper.h"
+#include "third_party/blink/renderer/core/html/media/html_video_element.h"
+#include "third_party/blink/renderer/core/loader/empty_clients.h"
+#include "third_party/blink/renderer/core/testing/page_test_base.h"
+#include "third_party/blink/renderer/core/timing/performance.h"
+#include "third_party/blink/renderer/platform/testing/empty_web_media_player.h"
+#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
+#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
+
+using testing::_;
+using testing::ByMove;
+using testing::Invoke;
+using testing::Return;
+
+namespace blink {
+
+using VideoFramePresentationMetadata =
+ WebMediaPlayer::VideoFramePresentationMetadata;
+
+namespace {
+
+class MockWebMediaPlayer : public EmptyWebMediaPlayer {
+ public:
+ MOCK_METHOD0(RequestVideoFrameCallback, void());
+ MOCK_METHOD0(GetVideoFramePresentationMetadata,
+ std::unique_ptr<VideoFramePresentationMetadata>());
+};
+
+class MockFunction : public ScriptFunction {
+ public:
+ static testing::StrictMock<MockFunction>* Create(ScriptState* script_state) {
+ return MakeGarbageCollected<testing::StrictMock<MockFunction>>(
+ script_state);
+ }
+
+ v8::Local<v8::Function> Bind() { return BindToV8Function(); }
+
+ MOCK_METHOD1(Call, ScriptValue(ScriptValue));
+
+ protected:
+ explicit MockFunction(ScriptState* script_state)
+ : ScriptFunction(script_state) {}
+};
+
+// Helper class to wrap a VideoFramePresentationData, which can't have a copy
+// constructor, due to it having a media::VideoFrameMetadata instance.
+class MetadataHelper {
+ public:
+ static VideoFramePresentationMetadata* GetDefaultMedatada() {
+ DCHECK(initialized);
+ return &metadata_;
+ }
+
+ static std::unique_ptr<VideoFramePresentationMetadata> CopyDefaultMedatada() {
+ DCHECK(initialized);
+ auto copy = std::make_unique<VideoFramePresentationMetadata>();
+
+ copy->presented_frames = metadata_.presented_frames;
+ copy->presentation_time = metadata_.presentation_time;
+ copy->expected_display_time = metadata_.expected_display_time;
+ copy->width = metadata_.width;
+ copy->height = metadata_.height;
+ copy->media_time = metadata_.media_time;
+ copy->metadata.MergeMetadataFrom(&(metadata_.metadata));
+
+ return copy;
+ }
+
+ static void InitializeFields(base::TimeTicks now) {
+ if (initialized)
+ return;
+
+ // We don't want any time ticks be a multiple of 5us, otherwise, we couldn't
+ // tell whether or not the implementation clamped their values. Therefore,
+ // we manually set the values for a deterministic test, and make sure we
+ // have sub-microsecond resolution for those values.
+
+ metadata_.presented_frames = 42;
+ metadata_.presentation_time =
+ now + base::TimeDelta::FromMillisecondsD(10.1234);
+ metadata_.expected_display_time =
+ now + base::TimeDelta::FromMillisecondsD(26.3467);
+ metadata_.width = 320;
+ metadata_.height = 480;
+ metadata_.media_time = base::TimeDelta::FromSecondsD(3.14);
+ metadata_.metadata.SetTimeDelta(media::VideoFrameMetadata::PROCESSING_TIME,
+ base::TimeDelta::FromMillisecondsD(60.982));
+ metadata_.metadata.SetTimeTicks(
+ media::VideoFrameMetadata::CAPTURE_BEGIN_TIME,
+ now + base::TimeDelta::FromMillisecondsD(5.6785));
+ metadata_.metadata.SetTimeTicks(
+ media::VideoFrameMetadata::RECEIVE_TIME,
+ now + base::TimeDelta::FromMillisecondsD(17.1234));
+ metadata_.metadata.SetDouble(media::VideoFrameMetadata::RTP_TIMESTAMP,
+ 12345);
+
+ initialized = true;
+ }
+
+ private:
+ static bool initialized;
+ static VideoFramePresentationMetadata metadata_;
+};
+
+bool MetadataHelper::initialized = false;
+VideoFramePresentationMetadata MetadataHelper::metadata_;
+
+// Helper class that compares the parameters used when invoking a callback, with
+// the reference parameters we expect.
+class VfcRequesterParameterVerifierCallback
+ : public VideoFrameRequestCallbackCollection::VideoFrameCallback {
+ public:
+ explicit VfcRequesterParameterVerifierCallback(DocumentLoadTiming& timing)
+ : timing_(timing) {}
+ ~VfcRequesterParameterVerifierCallback() override = default;
+
+ void Invoke(double now, const VideoFrameMetadata* metadata) override {
+ was_invoked_ = true;
+ now_ = now;
+
+ auto* expected = MetadataHelper::GetDefaultMedatada();
+ EXPECT_EQ(expected->presented_frames, metadata->presentedFrames());
+ EXPECT_EQ((unsigned int)expected->width, metadata->width());
+ EXPECT_EQ((unsigned int)expected->height, metadata->height());
+ EXPECT_EQ(expected->media_time.InSecondsF(), metadata->mediaTime());
+
+ double rtp_timestamp;
+ EXPECT_TRUE(expected->metadata.GetDouble(
+ media::VideoFrameMetadata::RTP_TIMESTAMP, &rtp_timestamp));
+ EXPECT_EQ(rtp_timestamp, metadata->rtpTimestamp());
+
+ // Verify that values were correctly clamped.
+ VerifyTicksClamping(expected->presentation_time,
+ metadata->presentationTime(), "presentation_time");
+ VerifyTicksClamping(expected->expected_display_time,
+ metadata->expectedDisplayTime(),
+ "expected_display_time");
+
+ base::TimeTicks capture_time;
+ EXPECT_TRUE(expected->metadata.GetTimeTicks(
+ media::VideoFrameMetadata::CAPTURE_BEGIN_TIME, &capture_time));
+ VerifyTicksClamping(capture_time, metadata->captureTime(), "capture_time");
+
+ base::TimeTicks receive_time;
+ EXPECT_TRUE(expected->metadata.GetTimeTicks(
+ media::VideoFrameMetadata::RECEIVE_TIME, &receive_time));
+ VerifyTicksClamping(receive_time, metadata->receiveTime(), "receive_time");
+
+ base::TimeDelta processing_time;
+ EXPECT_TRUE(expected->metadata.GetTimeDelta(
+ media::VideoFrameMetadata::PROCESSING_TIME, &processing_time));
+ EXPECT_EQ(ClampElapsedProcessingTime(processing_time),
+ metadata->processingDuration());
+ EXPECT_NE(processing_time.InSecondsF(), metadata->processingDuration());
+ }
+
+ double last_now() { return now_; }
+ bool was_invoked() { return was_invoked_; }
+
+ private:
+ void VerifyTicksClamping(base::TimeTicks reference,
+ double actual,
+ std::string name) {
+ EXPECT_EQ(TicksToClampedMillisecondsF(reference), actual)
+ << name << " was not clamped properly.";
+ EXPECT_NE(TicksToMillisecondsF(reference), actual)
+ << "Did not successfully test clamping for " << name;
+ }
+
+ double TicksToClampedMillisecondsF(base::TimeTicks ticks) {
+ constexpr double kSecondsToMillis = 1000.0;
+ return Performance::ClampTimeResolution(
+ timing_.MonotonicTimeToZeroBasedDocumentTime(ticks)
+ .InSecondsF()) *
+ kSecondsToMillis;
+ }
+
+ double TicksToMillisecondsF(base::TimeTicks ticks) {
+ return timing_.MonotonicTimeToZeroBasedDocumentTime(ticks)
+ .InMillisecondsF();
+ }
+
+ static double ClampElapsedProcessingTime(base::TimeDelta time) {
+ constexpr double kProcessingTimeResolution = 100e-6;
+ double interval = floor(time.InSecondsF() / kProcessingTimeResolution);
+ double clamped_time = interval * kProcessingTimeResolution;
+
+ return clamped_time;
+ }
+
+ double now_;
+ bool was_invoked_ = false;
+ DocumentLoadTiming& timing_;
+};
+
+} // namespace
+
+class VideoFrameCallbackRequesterImplTest
+ : public PageTestBase,
+ private ScopedRequestVideoFrameCallbackForTest {
+ public:
+ VideoFrameCallbackRequesterImplTest()
+ : ScopedRequestVideoFrameCallbackForTest(true) {}
+
+ virtual void SetUpWebMediaPlayer() {
+ auto mock_media_player = std::make_unique<MockWebMediaPlayer>();
+ media_player_ = mock_media_player.get();
+ SetupPageWithClients(nullptr,
+ MakeGarbageCollected<test::MediaStubLocalFrameClient>(
+ std::move(mock_media_player)),
+ nullptr);
+ }
+
+ void SetUp() override {
+ SetUpWebMediaPlayer();
+
+ video_ = MakeGarbageCollected<HTMLVideoElement>(GetDocument());
+ GetDocument().body()->appendChild(video_);
+
+ video()->SetSrc("http://example.com/foo.mp4");
+ test::RunPendingTasks();
+ UpdateAllLifecyclePhasesForTest();
+ }
+
+ HTMLVideoElement* video() { return video_.Get(); }
+
+ MockWebMediaPlayer* media_player() { return media_player_; }
+
+ VideoFrameCallbackRequesterImpl& vfc_requester() {
+ return VideoFrameCallbackRequesterImpl::From(*video());
+ }
+
+ void SimulateFramePresented() { video_->OnRequestVideoFrameCallback(); }
+
+ void SimulateVideoFrameCallback(base::TimeTicks now) {
+ GetDocument().GetScriptedAnimationController().ServiceScriptedAnimations(
+ now);
+ }
+
+ V8VideoFrameRequestCallback* GetCallback(MockFunction* function) {
+ return V8VideoFrameRequestCallback::Create(function->Bind());
+ }
+
+ void RegisterCallbackDirectly(
+ VfcRequesterParameterVerifierCallback* callback) {
+ vfc_requester().RegisterCallbackForTest(callback);
+ }
+
+ private:
+ Persistent<HTMLVideoElement> video_;
+
+ // Owned by HTMLVideoElementFrameClient.
+ MockWebMediaPlayer* media_player_;
+};
+
+class VideoFrameCallbackRequesterImplNullMediaPlayerTest
+ : public VideoFrameCallbackRequesterImplTest {
+ public:
+ void SetUpWebMediaPlayer() override {
+ SetupPageWithClients(nullptr,
+ MakeGarbageCollected<test::MediaStubLocalFrameClient>(
+ std::unique_ptr<MockWebMediaPlayer>(),
+ /* allow_empty_client */ true),
+ nullptr);
+ }
+};
+
+TEST_F(VideoFrameCallbackRequesterImplTest, VerifyRequestVideoFrameCallback) {
+ V8TestingScope scope;
+
+ auto* function = MockFunction::Create(scope.GetScriptState());
+
+ // Queuing up a video.rAF call should propagate to the WebMediaPlayer.
+ EXPECT_CALL(*media_player(), RequestVideoFrameCallback()).Times(1);
+ vfc_requester().requestVideoFrameCallback(GetCallback(function));
+
+ testing::Mock::VerifyAndClear(media_player());
+
+ // Callbacks should not be run immediately when a frame is presented.
+ EXPECT_CALL(*function, Call(_)).Times(0);
+ SimulateFramePresented();
+
+ testing::Mock::VerifyAndClear(function);
+
+ // Callbacks should be called during the rendering steps.
+ auto metadata = std::make_unique<VideoFramePresentationMetadata>();
+ metadata->presented_frames = 1;
+
+ EXPECT_CALL(*function, Call(_)).Times(1);
+ EXPECT_CALL(*media_player(), GetVideoFramePresentationMetadata())
+ .WillOnce(Return(ByMove(std::move(metadata))));
+ SimulateVideoFrameCallback(base::TimeTicks::Now());
+
+ testing::Mock::VerifyAndClear(function);
+}
+
+TEST_F(VideoFrameCallbackRequesterImplTest,
+ VerifyCancelVideoFrameCallback_BeforePresentedFrame) {
+ V8TestingScope scope;
+
+ auto* function = MockFunction::Create(scope.GetScriptState());
+
+ // Queue and cancel a request before a frame is presented.
+ int callback_id =
+ vfc_requester().requestVideoFrameCallback(GetCallback(function));
+ vfc_requester().cancelVideoFrameCallback(callback_id);
+
+ EXPECT_CALL(*function, Call(_)).Times(0);
+ SimulateFramePresented();
+ SimulateVideoFrameCallback(base::TimeTicks::Now());
+
+ testing::Mock::VerifyAndClear(function);
+}
+
+TEST_F(VideoFrameCallbackRequesterImplTest,
+ VerifyCancelVideoFrameCallback_AfterPresentedFrame) {
+ V8TestingScope scope;
+
+ auto* function = MockFunction::Create(scope.GetScriptState());
+
+ // Queue a request.
+ int callback_id =
+ vfc_requester().requestVideoFrameCallback(GetCallback(function));
+ SimulateFramePresented();
+
+ // The callback should be scheduled for execution, but not yet run.
+ EXPECT_CALL(*function, Call(_)).Times(0);
+ vfc_requester().cancelVideoFrameCallback(callback_id);
+ SimulateVideoFrameCallback(base::TimeTicks::Now());
+
+ testing::Mock::VerifyAndClear(function);
+}
+
+TEST_F(VideoFrameCallbackRequesterImplTest, VerifyParameters) {
+ auto timing = GetDocument().Loader()->GetTiming();
+ MetadataHelper::InitializeFields(timing.ReferenceMonotonicTime());
+
+ auto* callback =
+ MakeGarbageCollected<VfcRequesterParameterVerifierCallback>(timing);
+
+ // Register the non-V8 callback.
+ RegisterCallbackDirectly(callback);
+
+ EXPECT_CALL(*media_player(), GetVideoFramePresentationMetadata())
+ .WillOnce(Return(ByMove(MetadataHelper::CopyDefaultMedatada())));
+
+ double now_ms =
+ timing.MonotonicTimeToZeroBasedDocumentTime(base::TimeTicks::Now())
+ .InMillisecondsF();
+
+ // Run the callbacks directly, since they weren't scheduled to be run by the
+ // ScriptedAnimationController.
+ vfc_requester().OnRenderingSteps(now_ms);
+
+ EXPECT_EQ(callback->last_now(), now_ms);
+ EXPECT_TRUE(callback->was_invoked());
+
+ testing::Mock::VerifyAndClear(media_player());
+}
+
+TEST_F(VideoFrameCallbackRequesterImplNullMediaPlayerTest, VerifyNoCrash) {
+ V8TestingScope scope;
+
+ auto* function = MockFunction::Create(scope.GetScriptState());
+
+ vfc_requester().requestVideoFrameCallback(GetCallback(function));
+
+ SimulateFramePresented();
+ SimulateVideoFrameCallback(base::TimeTicks::Now());
+}
+
+} // namespace blink