blob: 9837fb0be84633c88fcf451cec8c276ca6e7c17c [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/peerconnection/rtc_encoded_video_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/platform/scheduler/test/renderer_scheduler_test_support.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/core/v8/v8_dom_exception.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_encoded_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/peerconnection/rtc_encoded_video_frame_delegate.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/peerconnection/rtc_encoded_video_stream_transformer.h"
#include "third_party/blink/renderer/platform/testing/testing_platform_support.h"
#include "third_party/webrtc/api/frame_transformer_interface.h"
#include "third_party/webrtc/api/scoped_refptr.h"
#include "third_party/webrtc/api/test/mock_transformable_video_frame.h"
#include "third_party/webrtc/rtc_base/ref_counted_object.h"
using testing::_;
using testing::NiceMock;
using testing::Return;
namespace blink {
namespace {
const uint32_t kSSRC = 1;
class MockWebRtcTransformedFrameCallback
: public webrtc::TransformedFrameCallback {
public:
MOCK_METHOD1(OnTransformedFrame,
void(std::unique_ptr<webrtc::TransformableFrameInterface>));
};
bool IsDOMException(ScriptState* script_state,
ScriptValue value,
DOMExceptionCode code) {
auto* dom_exception = V8DOMException::ToImplWithTypeCheck(
script_state->GetIsolate(), value.V8Value());
if (!dom_exception)
return false;
return dom_exception->code() == static_cast<uint16_t>(code);
}
} // namespace
class RTCEncodedVideoUnderlyingSinkTest : public testing::Test {
public:
RTCEncodedVideoUnderlyingSinkTest()
: main_task_runner_(
blink::scheduler::GetSingleThreadTaskRunnerForTesting()),
webrtc_callback_(
new rtc::RefCountedObject<MockWebRtcTransformedFrameCallback>()),
transformer_(main_task_runner_) {}
void SetUp() override {
EXPECT_FALSE(transformer_.HasTransformedFrameSinkCallback(kSSRC));
transformer_.RegisterTransformedFrameSinkCallback(webrtc_callback_, kSSRC);
EXPECT_TRUE(transformer_.HasTransformedFrameSinkCallback(kSSRC));
}
void TearDown() override {
platform_->RunUntilIdle();
transformer_.UnregisterTransformedFrameSinkCallback(kSSRC);
EXPECT_FALSE(transformer_.HasTransformedFrameSinkCallback(kSSRC));
}
RTCEncodedVideoUnderlyingSink* CreateSink(
ScriptState* script_state,
webrtc::TransformableFrameInterface::Direction expected_direction =
webrtc::TransformableFrameInterface::Direction::kSender) {
return MakeGarbageCollected<RTCEncodedVideoUnderlyingSink>(
script_state,
WTF::BindRepeating(&RTCEncodedVideoUnderlyingSinkTest::GetTransformer,
WTF::Unretained(this)),
expected_direction);
}
RTCEncodedVideoUnderlyingSink* CreateNullCallbackSink(
ScriptState* script_state) {
return MakeGarbageCollected<RTCEncodedVideoUnderlyingSink>(
script_state,
WTF::BindRepeating(
[]() -> RTCEncodedVideoStreamTransformer* { return nullptr; }),
webrtc::TransformableFrameInterface::Direction::kSender);
}
RTCEncodedVideoStreamTransformer* GetTransformer() { return &transformer_; }
ScriptValue CreateEncodedVideoFrameChunk(
ScriptState* script_state,
webrtc::TransformableFrameInterface::Direction direction =
webrtc::TransformableFrameInterface::Direction::kSender) {
auto mock_frame =
std::make_unique<NiceMock<webrtc::MockTransformableVideoFrame>>();
ON_CALL(*mock_frame.get(), GetSsrc).WillByDefault(Return(kSSRC));
ON_CALL(*mock_frame.get(), GetDirection).WillByDefault(Return(direction));
RTCEncodedVideoFrame* frame =
MakeGarbageCollected<RTCEncodedVideoFrame>(std::move(mock_frame));
return ScriptValue(script_state->GetIsolate(),
ToV8(frame, script_state->GetContext()->Global(),
script_state->GetIsolate()));
}
protected:
ScopedTestingPlatformSupport<TestingPlatformSupport> platform_;
scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
rtc::scoped_refptr<MockWebRtcTransformedFrameCallback> webrtc_callback_;
RTCEncodedVideoStreamTransformer transformer_;
};
TEST_F(RTCEncodedVideoUnderlyingSinkTest,
WriteToStreamForwardsToWebRtcCallback) {
V8TestingScope v8_scope;
ScriptState* script_state = v8_scope.GetScriptState();
auto* sink = CreateSink(script_state);
auto* stream =
WritableStream::CreateWithCountQueueingStrategy(script_state, sink, 1u);
NonThrowableExceptionState exception_state;
auto* writer = stream->getWriter(script_state, exception_state);
EXPECT_CALL(*webrtc_callback_, OnTransformedFrame(_));
ScriptPromiseTester write_tester(
script_state,
writer->write(script_state, CreateEncodedVideoFrameChunk(script_state),
exception_state));
EXPECT_FALSE(write_tester.IsFulfilled());
writer->releaseLock(script_state);
ScriptPromiseTester close_tester(
script_state, stream->close(script_state, exception_state));
close_tester.WaitUntilSettled();
// Writing to the sink after the stream closes should fail.
DummyExceptionStateForTesting dummy_exception_state;
sink->write(script_state, CreateEncodedVideoFrameChunk(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(RTCEncodedVideoUnderlyingSinkTest, WriteInvalidDataFails) {
V8TestingScope v8_scope;
ScriptState* script_state = v8_scope.GetScriptState();
auto* sink = CreateSink(script_state);
ScriptValue v8_integer = ScriptValue::From(script_state, 0);
// Writing something that is not an RTCEncodedVideoFrame integer 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());
}
TEST_F(RTCEncodedVideoUnderlyingSinkTest, WriteToNullCallbackSinkFails) {
V8TestingScope v8_scope;
ScriptState* script_state = v8_scope.GetScriptState();
auto* sink = CreateNullCallbackSink(script_state);
auto* stream =
WritableStream::CreateWithCountQueueingStrategy(script_state, sink, 1u);
NonThrowableExceptionState exception_state;
auto* writer = stream->getWriter(script_state, exception_state);
EXPECT_CALL(*webrtc_callback_, OnTransformedFrame(_)).Times(0);
ScriptPromiseTester write_tester(
script_state,
writer->write(script_state, CreateEncodedVideoFrameChunk(script_state),
exception_state));
write_tester.WaitUntilSettled();
EXPECT_TRUE(write_tester.IsRejected());
EXPECT_TRUE(IsDOMException(script_state, write_tester.Value(),
DOMExceptionCode::kInvalidStateError));
}
TEST_F(RTCEncodedVideoUnderlyingSinkTest, WriteInvalidDirectionFails) {
V8TestingScope v8_scope;
ScriptState* script_state = v8_scope.GetScriptState();
auto* sink = CreateSink(
script_state, webrtc::TransformableFrameInterface::Direction::kSender);
// Write an encoded chunk with direction set to Receiver should fail as it
// doesn't match the expected direction of our sink.
DummyExceptionStateForTesting dummy_exception_state;
sink->write(script_state,
CreateEncodedVideoFrameChunk(
script_state,
webrtc::TransformableFrameInterface::Direction::kReceiver),
nullptr, dummy_exception_state);
EXPECT_TRUE(dummy_exception_state.HadException());
}
} // namespace blink