blob: 0124062b569b28788e6d4215f05828428af534e8 [file] [log] [blame]
// 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/mediastream/media_stream_video_track_underlying_sink.h"
#include "base/memory/scoped_refptr.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_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_video_frame.h"
#include "third_party/blink/renderer/core/streams/writable_stream.h"
#include "third_party/blink/renderer/core/streams/writable_stream_default_writer.h"
#include "third_party/blink/renderer/modules/mediastream/media_stream_video_track.h"
#include "third_party/blink/renderer/modules/mediastream/mock_media_stream_video_sink.h"
#include "third_party/blink/renderer/modules/mediastream/pushable_media_stream_video_source.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/mediastream/media_stream_source.h"
#include "third_party/blink/renderer/platform/testing/io_task_runner_testing_platform_support.h"
using testing::_;
namespace blink {
class MediaStreamVideoTrackUnderlyingSinkTest : public testing::Test {
public:
MediaStreamVideoTrackUnderlyingSinkTest() {
pushable_video_source_ = new PushableMediaStreamVideoSource();
media_stream_source_ = MakeGarbageCollected<MediaStreamSource>(
"dummy_source_id", MediaStreamSource::kTypeVideo, "dummy_source_name",
/*remote=*/false);
media_stream_source_->SetPlatformSource(
base::WrapUnique(pushable_video_source_));
}
~MediaStreamVideoTrackUnderlyingSinkTest() override {
platform_->RunUntilIdle();
WebHeap::CollectAllGarbageForTesting();
}
MediaStreamVideoTrackUnderlyingSink* CreateUnderlyingSink(
ScriptState* script_state) {
return MakeGarbageCollected<MediaStreamVideoTrackUnderlyingSink>(
pushable_video_source_);
}
WebMediaStreamTrack CreateTrack() {
return MediaStreamVideoTrack::CreateVideoTrack(
pushable_video_source_,
MediaStreamVideoSource::ConstraintsOnceCallback(),
/*enabled=*/true);
}
ScriptValue CreateVideoFrameChunk(ScriptState* script_state,
VideoFrame** video_frame_out = nullptr) {
const scoped_refptr<media::VideoFrame> media_frame =
media::VideoFrame::CreateBlackFrame(gfx::Size(100, 50));
VideoFrame* video_frame = MakeGarbageCollected<VideoFrame>(
std::move(media_frame), ExecutionContext::From(script_state));
if (video_frame_out)
*video_frame_out = video_frame;
return ScriptValue(script_state->GetIsolate(),
ToV8(video_frame, script_state->GetContext()->Global(),
script_state->GetIsolate()));
}
protected:
ScopedTestingPlatformSupport<IOTaskRunnerTestingPlatformSupport> platform_;
Persistent<MediaStreamSource> media_stream_source_;
PushableMediaStreamVideoSource* pushable_video_source_;
};
// TODO(1153092): Test flakes, likely due to completing before background
// thread has had chance to call OnVideoFrame().
TEST_F(MediaStreamVideoTrackUnderlyingSinkTest,
DISABLED_WriteToStreamForwardsToMediaStreamSink) {
V8TestingScope v8_scope;
ScriptState* script_state = v8_scope.GetScriptState();
auto* underlying_sink = CreateUnderlyingSink(script_state);
auto* writable_stream = WritableStream::CreateWithCountQueueingStrategy(
script_state, underlying_sink, 1u);
auto track = CreateTrack();
MockMediaStreamVideoSink media_stream_video_sink;
media_stream_video_sink.ConnectToTrack(track);
NonThrowableExceptionState exception_state;
auto* writer = writable_stream->getWriter(script_state, exception_state);
VideoFrame* video_frame = nullptr;
auto video_frame_chunk = CreateVideoFrameChunk(script_state, &video_frame);
EXPECT_NE(video_frame, nullptr);
EXPECT_NE(video_frame->frame(), nullptr);
EXPECT_CALL(media_stream_video_sink, OnVideoFrame(_));
ScriptPromiseTester write_tester(
script_state,
writer->write(script_state, video_frame_chunk, exception_state));
write_tester.WaitUntilSettled();
// |video_frame| should be invalidated after sending it to the sink.
EXPECT_EQ(video_frame->frame(), nullptr);
writer->releaseLock(script_state);
ScriptPromiseTester close_tester(
script_state, writable_stream->close(script_state, exception_state));
close_tester.WaitUntilSettled();
// Writing to the sink after the stream closes should fail.
DummyExceptionStateForTesting dummy_exception_state;
underlying_sink->write(script_state, CreateVideoFrameChunk(script_state),
nullptr, dummy_exception_state);
EXPECT_TRUE(dummy_exception_state.HadException());
EXPECT_EQ(dummy_exception_state.Code(),
static_cast<ExceptionCode>(DOMExceptionCode::kInvalidStateError));
}
TEST_F(MediaStreamVideoTrackUnderlyingSinkTest, WriteInvalidDataFails) {
V8TestingScope v8_scope;
ScriptState* script_state = v8_scope.GetScriptState();
auto* sink = CreateUnderlyingSink(script_state);
ScriptValue v8_integer = ScriptValue::From(script_state, 0);
// Writing something that is not a VideoFrame to the sink should fail.
DummyExceptionStateForTesting dummy_exception_state;
sink->write(script_state, v8_integer, nullptr, dummy_exception_state);
EXPECT_TRUE(dummy_exception_state.HadException());
// Writing a null value to the sink should fail.
dummy_exception_state.ClearException();
EXPECT_FALSE(dummy_exception_state.HadException());
sink->write(script_state, ScriptValue::CreateNull(v8_scope.GetIsolate()),
nullptr, dummy_exception_state);
EXPECT_TRUE(dummy_exception_state.HadException());
// Writing a destroyed VideoFrame to the sink should fail.
dummy_exception_state.ClearException();
VideoFrame* video_frame = nullptr;
auto chunk = CreateVideoFrameChunk(script_state, &video_frame);
video_frame->close();
EXPECT_FALSE(dummy_exception_state.HadException());
sink->write(script_state, chunk, nullptr, dummy_exception_state);
EXPECT_TRUE(dummy_exception_state.HadException());
}
TEST_F(MediaStreamVideoTrackUnderlyingSinkTest, WriteToAbortedSinkFails) {
V8TestingScope v8_scope;
ScriptState* script_state = v8_scope.GetScriptState();
auto* underlying_sink = CreateUnderlyingSink(script_state);
auto* writable_stream = WritableStream::CreateWithCountQueueingStrategy(
script_state, underlying_sink, 1u);
NonThrowableExceptionState exception_state;
ScriptPromiseTester abort_tester(
script_state, writable_stream->abort(script_state, exception_state));
abort_tester.WaitUntilSettled();
// Writing to the sink after the stream closes should fail.
DummyExceptionStateForTesting dummy_exception_state;
underlying_sink->write(script_state, CreateVideoFrameChunk(script_state),
nullptr, dummy_exception_state);
EXPECT_TRUE(dummy_exception_state.HadException());
EXPECT_EQ(dummy_exception_state.Code(),
static_cast<ExceptionCode>(DOMExceptionCode::kInvalidStateError));
}
} // namespace blink