blob: 2eb472de7b4a5641891ac534d844cd97ef9cff5f [file] [log] [blame]
// Copyright 2021 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/video_track_signal_underlying_sink.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/to_v8_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_media_stream_track_signal.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_track.h"
#include "third_party/blink/renderer/modules/mediastream/media_stream_video_track.h"
#include "third_party/blink/renderer/modules/mediastream/mock_media_stream_video_source.h"
#include "third_party/blink/renderer/platform/mediastream/media_stream_component.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 VideoTrackSignalUnderlyingSinkTest : public testing::Test {
public:
VideoTrackSignalUnderlyingSinkTest() {
mock_video_source_ = new MockMediaStreamVideoSource();
media_stream_source_ = MakeGarbageCollected<MediaStreamSource>(
"dummy_source_id", MediaStreamSource::kTypeVideo, "dummy_source_name",
/*remote=*/false);
media_stream_source_->SetPlatformSource(
base::WrapUnique(mock_video_source_));
web_track_ = MediaStreamVideoTrack::CreateVideoTrack(
mock_video_source_, MediaStreamVideoSource::ConstraintsOnceCallback(),
true);
mock_video_source_->StartMockedSource();
}
~VideoTrackSignalUnderlyingSinkTest() override {
platform_->RunUntilIdle();
mock_video_source_->StopSource();
base::RunLoop run_loop;
platform_->GetIOTaskRunner()->PostTask(FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
WebHeap::CollectAllGarbageForTesting();
}
MediaStreamTrack* CreateTrack(ExecutionContext* context) const {
return MakeGarbageCollected<MediaStreamTrack>(context, web_track_);
}
VideoTrackSignalUnderlyingSink* CreateUnderlyingSink(
MediaStreamTrack* track) {
return MakeGarbageCollected<VideoTrackSignalUnderlyingSink>(track);
}
ScriptValue CreateSignalChunk(ScriptState* script_state,
const String& signal_name) {
MediaStreamTrackSignal* signal = MediaStreamTrackSignal::Create();
signal->setSignalType(signal_name);
return ScriptValue(script_state->GetIsolate(),
ToV8(signal, script_state->GetContext()->Global(),
script_state->GetIsolate()));
}
ScriptValue CreateRequestFrameChunk(ScriptState* script_state) {
return CreateSignalChunk(script_state, "request-frame");
}
ScriptValue CreateSetMinFrameRateChunk(
ScriptState* script_state,
const base::Optional<double>& frame_rate = 10.0) {
MediaStreamTrackSignal* signal = MediaStreamTrackSignal::Create();
signal->setSignalType("set-min-frame-rate");
if (frame_rate)
signal->setFrameRate(*frame_rate);
return ScriptValue(script_state->GetIsolate(),
ToV8(signal, script_state->GetContext()->Global(),
script_state->GetIsolate()));
}
protected:
Persistent<MediaStreamSource> media_stream_source_;
WebMediaStreamTrack web_track_;
MockMediaStreamVideoSource* mock_video_source_;
ScopedTestingPlatformSupport<IOTaskRunnerTestingPlatformSupport> platform_;
};
TEST_F(VideoTrackSignalUnderlyingSinkTest,
WriteRequestFrameToStreamForwardsToVideoSource) {
V8TestingScope v8_scope;
ScriptState* script_state = v8_scope.GetScriptState();
auto* track = CreateTrack(v8_scope.GetExecutionContext());
auto* underlying_sink = CreateUnderlyingSink(track);
auto* writable_stream = WritableStream::CreateWithCountQueueingStrategy(
script_state, underlying_sink, 1u);
NonThrowableExceptionState exception_state;
auto* writer = writable_stream->getWriter(script_state, exception_state);
auto request_frame_chunk = CreateRequestFrameChunk(script_state);
EXPECT_CALL(*mock_video_source_, OnRequestRefreshFrame());
ScriptPromiseTester write_tester(
script_state,
writer->write(script_state, request_frame_chunk, exception_state));
write_tester.WaitUntilSettled();
const double frame_rate = 14.8;
auto set_min_frame_rate_chunk =
CreateSetMinFrameRateChunk(script_state, frame_rate);
MediaStreamVideoTrack* video_track =
MediaStreamVideoTrack::From(track->Component());
EXPECT_FALSE(video_track->min_frame_rate().has_value());
ScriptPromiseTester write_tester2(
script_state,
writer->write(script_state, set_min_frame_rate_chunk, exception_state));
write_tester2.WaitUntilSettled();
EXPECT_TRUE(video_track->min_frame_rate().has_value());
EXPECT_EQ(video_track->min_frame_rate().value(), frame_rate);
writer->releaseLock(script_state);
ScriptPromiseTester close_tester(
script_state, writable_stream->close(script_state, exception_state));
close_tester.WaitUntilSettled();
MediaStreamTrack* clone = track->clone(script_state);
track->stopTrack(v8_scope.GetExecutionContext());
// Writing to the sink after the track closes should fail, even if the source
// is active.
EXPECT_TRUE(mock_video_source_->IsRunning());
DummyExceptionStateForTesting dummy_exception_state;
underlying_sink->write(script_state, CreateRequestFrameChunk(script_state),
nullptr, dummy_exception_state);
EXPECT_TRUE(dummy_exception_state.HadException());
EXPECT_EQ(dummy_exception_state.Code(),
static_cast<ExceptionCode>(DOMExceptionCode::kInvalidStateError));
clone->stopTrack(v8_scope.GetExecutionContext());
EXPECT_FALSE(mock_video_source_->IsRunning());
// Writing to the sink after the source closes should fail.
dummy_exception_state.ClearException();
underlying_sink->write(script_state, CreateRequestFrameChunk(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(VideoTrackSignalUnderlyingSinkTest, WriteInvalidDataFails) {
V8TestingScope v8_scope;
ScriptState* script_state = v8_scope.GetScriptState();
auto* track = CreateTrack(v8_scope.GetExecutionContext());
auto* underlying_sink = CreateUnderlyingSink(track);
MediaStreamVideoTrack* video_track =
MediaStreamVideoTrack::From(track->Component());
EXPECT_FALSE(video_track->min_frame_rate().has_value());
DummyExceptionStateForTesting exception_state;
auto set_min_frame_rate_chunk =
CreateSetMinFrameRateChunk(script_state, base::nullopt);
underlying_sink->write(script_state, set_min_frame_rate_chunk, nullptr,
exception_state);
EXPECT_TRUE(exception_state.HadException());
EXPECT_FALSE(video_track->min_frame_rate().has_value());
exception_state.ClearException();
EXPECT_FALSE(exception_state.HadException());
underlying_sink->write(script_state,
CreateSignalChunk(script_state, "invalid-signal"),
nullptr, exception_state);
EXPECT_TRUE(exception_state.HadException());
// Writing null fails
exception_state.ClearException();
EXPECT_FALSE(exception_state.HadException());
underlying_sink->write(script_state,
ScriptValue::CreateNull(v8_scope.GetIsolate()),
nullptr, exception_state);
EXPECT_TRUE(exception_state.HadException());
// Writing an intenger fails
exception_state.ClearException();
EXPECT_FALSE(exception_state.HadException());
underlying_sink->write(script_state, ScriptValue::From(script_state, 5),
nullptr, exception_state);
EXPECT_TRUE(exception_state.HadException());
track->stopTrack(v8_scope.GetExecutionContext());
}
TEST_F(VideoTrackSignalUnderlyingSinkTest, WriteToClosedSinkFails) {
V8TestingScope v8_scope;
ScriptState* script_state = v8_scope.GetScriptState();
auto* track = CreateTrack(v8_scope.GetExecutionContext());
auto* underlying_sink = CreateUnderlyingSink(track);
auto* writable_stream = WritableStream::CreateWithCountQueueingStrategy(
script_state, underlying_sink, 1u);
NonThrowableExceptionState exception_state;
ScriptPromiseTester abort_tester(
script_state, writable_stream->close(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, CreateRequestFrameChunk(script_state),
nullptr, dummy_exception_state);
EXPECT_TRUE(dummy_exception_state.HadException());
EXPECT_EQ(dummy_exception_state.Code(),
static_cast<ExceptionCode>(DOMExceptionCode::kInvalidStateError));
track->stopTrack(v8_scope.GetExecutionContext());
}
TEST_F(VideoTrackSignalUnderlyingSinkTest, WriteToAbortedSinkFails) {
V8TestingScope v8_scope;
ScriptState* script_state = v8_scope.GetScriptState();
auto* track = CreateTrack(v8_scope.GetExecutionContext());
auto* underlying_sink = CreateUnderlyingSink(track);
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 aborts should fail.
DummyExceptionStateForTesting dummy_exception_state;
underlying_sink->write(script_state, CreateRequestFrameChunk(script_state),
nullptr, dummy_exception_state);
EXPECT_TRUE(dummy_exception_state.HadException());
EXPECT_EQ(dummy_exception_state.Code(),
static_cast<ExceptionCode>(DOMExceptionCode::kInvalidStateError));
track->stopTrack(v8_scope.GetExecutionContext());
}
} // namespace blink