| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| |
| #include "remoting/host/client_session.h" |
| |
| #include <array> |
| #include <cstdint> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/check.h" |
| #include "base/containers/contains.h" |
| #include "base/functional/bind.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_split.h" |
| #include "base/test/task_environment.h" |
| #include "base/test/test_future.h" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| #include "remoting/base/auto_thread_task_runner.h" |
| #include "remoting/base/constants.h" |
| #include "remoting/base/errors.h" |
| #include "remoting/base/local_session_policies_provider.h" |
| #include "remoting/base/session_policies.h" |
| #include "remoting/host/base/desktop_environment_options.h" |
| #include "remoting/host/desktop_display_info.h" |
| #include "remoting/host/fake_desktop_environment.h" |
| #include "remoting/host/fake_host_extension.h" |
| #include "remoting/host/host_extension.h" |
| #include "remoting/host/host_extension_session.h" |
| #include "remoting/host/host_mock_objects.h" |
| #include "remoting/proto/control.pb.h" |
| #include "remoting/proto/event.pb.h" |
| #include "remoting/protocol/capability_names.h" |
| #include "remoting/protocol/fake_connection_to_client.h" |
| #include "remoting/protocol/fake_desktop_capturer.h" |
| #include "remoting/protocol/fake_message_pipe.h" |
| #include "remoting/protocol/fake_session.h" |
| #include "remoting/protocol/message_pipe.h" |
| #include "remoting/protocol/protocol_mock_objects.h" |
| #include "remoting/protocol/session_config.h" |
| #include "remoting/protocol/test_event_matchers.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/libjingle_xmpp/xmllite/qname.h" |
| #include "third_party/libjingle_xmpp/xmllite/xmlelement.h" |
| #include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h" |
| #include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h" |
| #include "ui/events/types/event_type.h" |
| |
| namespace remoting { |
| |
| using protocol::FakeSession; |
| using protocol::MockClientStub; |
| using protocol::MockHostStub; |
| using protocol::MockInputStub; |
| using protocol::MockVideoStub; |
| using protocol::SessionConfig; |
| using protocol::test::EqualsClipboardEvent; |
| using protocol::test::EqualsKeyEvent; |
| using protocol::test::EqualsMouseButtonEvent; |
| using protocol::test::EqualsMouseMoveEvent; |
| |
| using testing::_; |
| using testing::AtLeast; |
| using testing::Eq; |
| using testing::Not; |
| using testing::Return; |
| using testing::ReturnRef; |
| using testing::StrictMock; |
| |
| namespace { |
| |
| constexpr char kTestDataChannelCallbackName[] = "test_channel_name"; |
| |
| // Use large fake screen-ids on 64-bit systems, to detect errors caused by |
| // inadvertent casts to 32-bits. |
| constexpr bool kUse64BitDisplayId = (sizeof(webrtc::ScreenId) >= 8); |
| |
| // Matches a |protocol::Capabilities| argument against a list of capabilities |
| // formatted as a space-separated string. |
| MATCHER_P(IncludesCapabilities, expected_capabilities, "") { |
| if (!arg.has_capabilities()) { |
| return false; |
| } |
| |
| std::vector<std::string> words_args = |
| base::SplitString(arg.capabilities(), " ", base::KEEP_WHITESPACE, |
| base::SPLIT_WANT_NONEMPTY); |
| std::vector<std::string> words_expected = |
| base::SplitString(expected_capabilities, " ", base::KEEP_WHITESPACE, |
| base::SPLIT_WANT_NONEMPTY); |
| |
| for (const auto& word : words_expected) { |
| if (!base::Contains(words_args, word)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| MATCHER_P(ScreenIdMatches, expected_id, "") { |
| return arg.screen_id() == expected_id; |
| } |
| |
| protocol::MouseEvent MakeMouseMoveEvent(int x, int y) { |
| protocol::MouseEvent result; |
| result.set_x(x); |
| result.set_y(y); |
| return result; |
| } |
| |
| protocol::KeyEvent MakeKeyEvent(bool pressed, std::uint32_t keycode) { |
| protocol::KeyEvent result; |
| result.set_pressed(pressed); |
| result.set_usb_keycode(keycode); |
| return result; |
| } |
| |
| protocol::ClipboardEvent MakeClipboardEvent(const std::string& text) { |
| protocol::ClipboardEvent result; |
| result.set_mime_type(kMimeTypeTextUtf8); |
| result.set_data(text); |
| return result; |
| } |
| |
| } // namespace |
| |
| class ClientSessionTest : public testing::Test { |
| public: |
| ClientSessionTest() = default; |
| |
| void SetUp() override; |
| void TearDown() override; |
| |
| protected: |
| // Fake multi-monitor setup. |
| static const int kDisplay1Width = |
| protocol::FakeDesktopCapturer::kWidth; // 800 |
| static const int kDisplay1Height = |
| protocol::FakeDesktopCapturer::kHeight; // 600 |
| static const std::int64_t kDisplay1Id = |
| kUse64BitDisplayId ? 1111111111111111 : 11111111; |
| static const int kDisplay2Width = 1024; |
| static const int kDisplay2Height = 768; |
| static const int kDisplay2YOffset = 35; |
| static const std::int64_t kDisplay2Id = |
| kUse64BitDisplayId ? 2222222222222222 : 22222222; |
| |
| // Creates the client session from a FakeSession instance. |
| void CreateClientSession(std::unique_ptr<protocol::FakeSession> session); |
| |
| // Creates the client session. |
| void CreateClientSession(); |
| |
| // Notifies the client session that the client connection has been |
| // authenticated and channels have been connected. This effectively enables |
| // the input pipe line and starts video capturing. |
| void ConnectClientSession(const SessionPolicies* session_policies = nullptr); |
| |
| // Add a fake display to the layout list. Used in conjunction with |
| // NotifyDesktopDisplaySize. |
| void AddDisplayToLayout(protocol::VideoLayout* displays, |
| int x, |
| int y, |
| int width, |
| int height, |
| int dpi_x, |
| int dpi_y, |
| std::int64_t display_id); |
| |
| // Fakes desktop display size notification from Webrtc. |
| void NotifyDesktopDisplaySize( |
| std::unique_ptr<protocol::VideoLayout> displays); |
| |
| // Fakes display select request from user. |
| void NotifySelectDesktopDisplay(std::string id); |
| |
| // Convenience methods to setup a single- or double-monitor setup. |
| void ResetDisplayInfo(); |
| void SetupSingleDisplay(); |
| void SetupMultiDisplay(); |
| void SetupMultiDisplay_SameSize(); |
| |
| // When using a multi-mon setup, this fakes the user selecting which display |
| // to show. |
| void MultiMon_SelectFirstDisplay(); |
| void MultiMon_SelectSecondDisplay(); |
| void MultiMon_SelectAllDisplays(); |
| void MultiMon_SelectDisplay(std::string display_id); |
| |
| // Return the identifier of the display that's currently selected. |
| webrtc::ScreenId GetSelectedSourceDisplayId(); |
| |
| // Geometry info for displays being tested. |
| DesktopDisplayInfo displays_; |
| int curr_display_; |
| |
| // Message loop that will process all ClientSession tasks. |
| base::test::TaskEnvironment task_environment_{ |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME}; |
| |
| // AutoThreadTaskRunner on which |client_session_| will be run. |
| scoped_refptr<AutoThreadTaskRunner> task_runner_; |
| |
| // Used to run |message_loop_| after each test, until no objects remain that |
| // require it. |
| base::RunLoop run_loop_; |
| |
| // HostExtensions to pass when creating the ClientSession. Caller retains |
| // ownership of the HostExtensions themselves. |
| std::vector<raw_ptr<HostExtension, VectorExperimental>> extensions_; |
| |
| // Vectors of events to bind to `client_sessions_`, must outlive it. |
| std::vector<protocol::KeyEvent> key_events_; |
| std::vector<protocol::MouseEvent> mouse_events_; |
| std::vector<protocol::ClipboardEvent> clipboard_events_; |
| |
| SessionPolicies initial_local_policies_; |
| |
| LocalSessionPoliciesProvider local_session_policies_provider_; |
| |
| // ClientSession instance under test. |
| std::unique_ptr<ClientSession> client_session_; |
| |
| // ClientSession::EventHandler mock for use in tests. |
| MockClientSessionEventHandler session_event_handler_; |
| |
| // Stubs returned to |client_session_| components by |connection_|. |
| MockClientStub client_stub_; |
| |
| // ClientSession owns |connection_| but tests need it to inject fake events. |
| raw_ptr<protocol::FakeConnectionToClient, DanglingUntriaged> connection_; |
| |
| std::unique_ptr<FakeDesktopEnvironmentFactory> desktop_environment_factory_; |
| |
| DesktopEnvironmentOptions desktop_environment_options_; |
| }; |
| |
| void ClientSessionTest::SetUp() { |
| // Arrange to run |task_environment_| until no components depend on it. |
| task_runner_ = new AutoThreadTaskRunner( |
| task_environment_.GetMainThreadTaskRunner(), run_loop_.QuitClosure()); |
| |
| desktop_environment_factory_ = |
| std::make_unique<FakeDesktopEnvironmentFactory>( |
| task_environment_.GetMainThreadTaskRunner()); |
| desktop_environment_options_ = DesktopEnvironmentOptions::CreateDefault(); |
| |
| initial_local_policies_.maximum_session_duration = base::Hours(10); |
| local_session_policies_provider_.set_local_policies(initial_local_policies_); |
| |
| // Suppress spammy "uninteresting call" logs. |
| EXPECT_CALL(client_stub_, SetCursorShape(_)).Times(testing::AnyNumber()); |
| } |
| |
| void ClientSessionTest::TearDown() { |
| if (client_session_) { |
| if (connection_->is_connected()) { |
| client_session_->DisconnectSession(ErrorCode::OK, {}, FROM_HERE); |
| } |
| client_session_.reset(); |
| desktop_environment_factory_.reset(); |
| } |
| |
| // Clear out |task_runner_| reference so the loop can quit, and run it until |
| // it does. |
| task_runner_ = nullptr; |
| run_loop_.Run(); |
| } |
| |
| void ClientSessionTest::CreateClientSession( |
| std::unique_ptr<protocol::FakeSession> session) { |
| DCHECK(session); |
| |
| // Mock protocol::ConnectionToClient APIs called directly by ClientSession. |
| // HostStub is not touched by ClientSession, so we can safely pass nullptr. |
| std::unique_ptr<protocol::FakeConnectionToClient> connection( |
| new protocol::FakeConnectionToClient(std::move(session))); |
| connection->set_client_stub(&client_stub_); |
| connection_ = connection.get(); |
| |
| client_session_ = std::make_unique<ClientSession>( |
| &session_event_handler_, std::move(connection), |
| desktop_environment_factory_.get(), desktop_environment_options_, nullptr, |
| extensions_, &local_session_policies_provider_); |
| } |
| |
| void ClientSessionTest::CreateClientSession() { |
| CreateClientSession(std::make_unique<protocol::FakeSession>()); |
| } |
| |
| void ClientSessionTest::ConnectClientSession( |
| const SessionPolicies* session_policies) { |
| EXPECT_CALL(session_event_handler_, OnSessionPoliciesReceived(_)) |
| .WillOnce(Return(std::nullopt)); |
| EXPECT_CALL(session_event_handler_, OnSessionAuthenticated(_)); |
| |
| base::test::TestFuture<void> future; |
| EXPECT_CALL(session_event_handler_, OnSessionChannelsConnected(_)) |
| .WillOnce([&future] { future.SetValue(); }); |
| |
| // Stubs should be set only after connection is authenticated. |
| EXPECT_FALSE(connection_->clipboard_stub()); |
| EXPECT_FALSE(connection_->input_stub()); |
| |
| client_session_->OnConnectionAuthenticated(session_policies); |
| client_session_->CreateMediaStreams(); |
| client_session_->OnConnectionChannelsConnected(); |
| future.Get(); |
| |
| EXPECT_TRUE(connection_->clipboard_stub()); |
| EXPECT_TRUE(connection_->input_stub()); |
| } |
| |
| void ClientSessionTest::AddDisplayToLayout(protocol::VideoLayout* displays, |
| int x, |
| int y, |
| int width, |
| int height, |
| int dpi_x, |
| int dpi_y, |
| std::int64_t display_id) { |
| protocol::VideoTrackLayout* video_track = displays->add_video_track(); |
| video_track->set_position_x(x); |
| video_track->set_position_y(y); |
| video_track->set_width(width); |
| video_track->set_height(height); |
| video_track->set_x_dpi(dpi_x); |
| video_track->set_y_dpi(dpi_y); |
| video_track->set_screen_id(display_id); |
| displays_.AddDisplayFrom(*video_track); |
| } |
| |
| void ClientSessionTest::NotifyDesktopDisplaySize( |
| std::unique_ptr<protocol::VideoLayout> displays) { |
| client_session_->OnDesktopDisplayChanged(std::move(displays)); |
| } |
| |
| void ClientSessionTest::NotifySelectDesktopDisplay(std::string id) { |
| protocol::SelectDesktopDisplayRequest req; |
| req.set_id(id); |
| client_session_->SelectDesktopDisplay(req); |
| } |
| |
| void ClientSessionTest::ResetDisplayInfo() { |
| displays_.Reset(); |
| curr_display_ = webrtc::kInvalidScreenId; |
| } |
| |
| // Set up a single display (default size). |
| void ClientSessionTest::SetupSingleDisplay() { |
| ResetDisplayInfo(); |
| auto displays = std::make_unique<protocol::VideoLayout>(); |
| AddDisplayToLayout(displays.get(), 0, 0, kDisplay1Width, kDisplay1Height, |
| kDefaultDpi, kDefaultDpi, kDisplay1Id); |
| NotifyDesktopDisplaySize(std::move(displays)); |
| } |
| |
| // Set up multiple displays: |
| // +-----------+ |
| // | 800x600 |---------------+ |
| // | 0 | 1024x768 | |
| // +-----------+ 1 | |
| // | | |
| // +---------------+ |
| void ClientSessionTest::SetupMultiDisplay() { |
| ResetDisplayInfo(); |
| auto displays = std::make_unique<protocol::VideoLayout>(); |
| AddDisplayToLayout(displays.get(), 0, 0, kDisplay1Width, kDisplay1Height, |
| kDefaultDpi, kDefaultDpi, kDisplay1Id); |
| AddDisplayToLayout(displays.get(), kDisplay1Width, kDisplay2YOffset, |
| kDisplay2Width, kDisplay2Height, kDefaultDpi, kDefaultDpi, |
| kDisplay2Id); |
| NotifyDesktopDisplaySize(std::move(displays)); |
| } |
| |
| // Set up multiple displays that are the same size: |
| // +-----------+ |
| // | 800x600 |-----------+ |
| // | 0 | 800x600 | |
| // +-----------+ 1 | |
| // +-----------+ |
| void ClientSessionTest::SetupMultiDisplay_SameSize() { |
| ResetDisplayInfo(); |
| auto displays = std::make_unique<protocol::VideoLayout>(); |
| AddDisplayToLayout(displays.get(), 0, 0, kDisplay1Width, kDisplay1Height, |
| kDefaultDpi, kDefaultDpi, kDisplay1Id); |
| AddDisplayToLayout(displays.get(), kDisplay1Width, kDisplay2YOffset, |
| kDisplay1Width, kDisplay1Height, kDefaultDpi, kDefaultDpi, |
| kDisplay2Id); |
| NotifyDesktopDisplaySize(std::move(displays)); |
| } |
| |
| void ClientSessionTest::MultiMon_SelectFirstDisplay() { |
| NotifySelectDesktopDisplay("0"); |
| } |
| |
| void ClientSessionTest::MultiMon_SelectSecondDisplay() { |
| NotifySelectDesktopDisplay("1"); |
| } |
| |
| void ClientSessionTest::MultiMon_SelectAllDisplays() { |
| NotifySelectDesktopDisplay("all"); |
| } |
| |
| void ClientSessionTest::MultiMon_SelectDisplay(std::string display_id) { |
| NotifySelectDesktopDisplay(display_id); |
| } |
| |
| webrtc::ScreenId ClientSessionTest::GetSelectedSourceDisplayId() { |
| return connection_->last_video_stream()->selected_source(); |
| } |
| |
| TEST_F( |
| ClientSessionTest, |
| OnLocalPoliciesChanged_DoesNotDisconnectIfEffectivePoliciesComeFromRemotePolicies) { |
| SessionPolicies remote_policies; |
| remote_policies.maximum_session_duration = base::Hours(8); |
| CreateClientSession(); |
| ConnectClientSession(&remote_policies); |
| |
| EXPECT_TRUE(connection_->is_connected()); |
| SessionPolicies new_policies; |
| new_policies.maximum_session_duration = base::Hours(23); |
| local_session_policies_provider_.set_local_policies(new_policies); |
| EXPECT_TRUE(connection_->is_connected()); |
| } |
| |
| TEST_F(ClientSessionTest, |
| OnLocalPoliciesChanged_DoesNotDisconnectIfEffectivePoliciesNotChanged) { |
| CreateClientSession(); |
| ConnectClientSession(); |
| |
| EXPECT_TRUE(connection_->is_connected()); |
| local_session_policies_provider_.set_local_policies(initial_local_policies_); |
| EXPECT_TRUE(connection_->is_connected()); |
| } |
| |
| TEST_F(ClientSessionTest, |
| OnLocalPoliciesChanged_DisconnectsIfEffectivePoliciesChanged) { |
| CreateClientSession(); |
| ConnectClientSession(); |
| |
| EXPECT_TRUE(connection_->is_connected()); |
| SessionPolicies local_policies; |
| local_policies.maximum_session_duration = base::Hours(23); |
| local_session_policies_provider_.set_local_policies(local_policies); |
| EXPECT_FALSE(connection_->is_connected()); |
| } |
| |
| TEST_F(ClientSessionTest, DisconnectsAfterMaxSessionDurationIsReached) { |
| CreateClientSession(); |
| ConnectClientSession(); |
| |
| EXPECT_TRUE(connection_->is_connected()); |
| // Calling FastForwardBy() would result in a livelock, so we just advance the |
| // clock and run all the scheduled tasks, which includes the max duration |
| // timer. |
| task_environment_.AdvanceClock( |
| *initial_local_policies_.maximum_session_duration + base::Minutes(1)); |
| task_environment_.RunUntilIdle(); |
| EXPECT_FALSE(connection_->is_connected()); |
| } |
| |
| TEST_F(ClientSessionTest, DisconnectsIfOnSessionPoliciesReceivedReturnsError) { |
| EXPECT_CALL(session_event_handler_, |
| OnSessionPoliciesReceived(initial_local_policies_)) |
| .WillOnce(Return(ErrorCode::DISALLOWED_BY_POLICY)); |
| |
| CreateClientSession(); |
| client_session_->OnConnectionAuthenticated(nullptr); |
| |
| EXPECT_FALSE(connection_->is_connected()); |
| EXPECT_EQ(connection_->disconnect_error(), ErrorCode::DISALLOWED_BY_POLICY); |
| } |
| |
| TEST_F(ClientSessionTest, |
| EffectivePoliciesImplicitlyAllowFileTransfer_HasCapability) { |
| local_session_policies_provider_.set_local_policies({}); |
| EXPECT_CALL( |
| client_stub_, |
| SetCapabilities(IncludesCapabilities(protocol::kFileTransferCapability))); |
| |
| CreateClientSession(); |
| ConnectClientSession(); |
| } |
| |
| TEST_F(ClientSessionTest, |
| EffectivePoliciesExplicitlyAllowFileTransfer_HasCapability) { |
| SessionPolicies local_policies; |
| local_policies.allow_file_transfer = true; |
| local_session_policies_provider_.set_local_policies(local_policies); |
| EXPECT_CALL( |
| client_stub_, |
| SetCapabilities(IncludesCapabilities(protocol::kFileTransferCapability))); |
| |
| CreateClientSession(); |
| ConnectClientSession(); |
| } |
| |
| TEST_F(ClientSessionTest, |
| EffectivePoliciesDisallowFileTransfer_DoesNotHaveCapability) { |
| SessionPolicies local_policies; |
| local_policies.allow_file_transfer = false; |
| local_session_policies_provider_.set_local_policies(local_policies); |
| EXPECT_CALL(client_stub_, SetCapabilities(Not(IncludesCapabilities( |
| protocol::kFileTransferCapability)))); |
| |
| CreateClientSession(); |
| ConnectClientSession(); |
| } |
| |
| TEST_F(ClientSessionTest, ApplyPoliciesFromRemotePolicies) { |
| SessionPolicies local_policies; |
| local_policies.allow_file_transfer = true; |
| local_policies.allow_uri_forwarding = true; |
| local_session_policies_provider_.set_local_policies(local_policies); |
| SessionPolicies remote_policies; |
| remote_policies.allow_file_transfer = false; |
| remote_policies.allow_uri_forwarding = false; |
| EXPECT_CALL(client_stub_, |
| SetCapabilities(Not(IncludesCapabilities( |
| std::string() + protocol::kFileTransferCapability + " " + |
| protocol::kRemoteOpenUrlCapability)))); |
| |
| CreateClientSession(); |
| ConnectClientSession(&remote_policies); |
| } |
| |
| // TODO(lambroslambrou): Re-implement the deleted MultiMonMouseMove |
| // and MultiMonMouseMove_SameSize tests in a way that makes sense for |
| // multi-stream mode. |
| |
| TEST_F(ClientSessionTest, DisableInputs) { |
| CreateClientSession(); |
| ConnectClientSession(); |
| SetupSingleDisplay(); |
| |
| FakeInputInjector* input_injector = |
| desktop_environment_factory_->last_desktop_environment() |
| ->last_input_injector() |
| .get(); |
| input_injector->set_key_events(&key_events_); |
| input_injector->set_mouse_events(&mouse_events_); |
| input_injector->set_clipboard_events(&clipboard_events_); |
| |
| // Inject test events that are expected to be injected. |
| connection_->clipboard_stub()->InjectClipboardEvent(MakeClipboardEvent("a")); |
| connection_->input_stub()->InjectKeyEvent(MakeKeyEvent(true, 1)); |
| connection_->input_stub()->InjectMouseEvent(MakeMouseMoveEvent(100, 101)); |
| |
| // Disable input. |
| client_session_->SetDisableInputs(true); |
| |
| // These events shouldn't get though to the input injector. |
| connection_->clipboard_stub()->InjectClipboardEvent(MakeClipboardEvent("b")); |
| connection_->input_stub()->InjectKeyEvent(MakeKeyEvent(true, 2)); |
| connection_->input_stub()->InjectKeyEvent(MakeKeyEvent(false, 2)); |
| connection_->input_stub()->InjectMouseEvent(MakeMouseMoveEvent(200, 201)); |
| |
| // Enable input again. |
| client_session_->SetDisableInputs(false); |
| connection_->clipboard_stub()->InjectClipboardEvent(MakeClipboardEvent("c")); |
| connection_->input_stub()->InjectKeyEvent(MakeKeyEvent(true, 3)); |
| connection_->input_stub()->InjectMouseEvent(MakeMouseMoveEvent(300, 301)); |
| |
| client_session_->DisconnectSession(ErrorCode::OK, {}, FROM_HERE); |
| client_session_.reset(); |
| |
| EXPECT_EQ(2U, mouse_events_.size()); |
| EXPECT_THAT(mouse_events_[0], EqualsMouseMoveEvent(100, 101)); |
| EXPECT_THAT(mouse_events_[1], EqualsMouseMoveEvent(300, 301)); |
| |
| EXPECT_EQ(4U, key_events_.size()); |
| EXPECT_THAT(key_events_[0], EqualsKeyEvent(1, true)); |
| EXPECT_THAT(key_events_[1], EqualsKeyEvent(1, false)); |
| EXPECT_THAT(key_events_[2], EqualsKeyEvent(3, true)); |
| EXPECT_THAT(key_events_[3], EqualsKeyEvent(3, false)); |
| |
| EXPECT_EQ(2U, clipboard_events_.size()); |
| EXPECT_THAT(clipboard_events_[0], |
| EqualsClipboardEvent(kMimeTypeTextUtf8, "a")); |
| EXPECT_THAT(clipboard_events_[1], |
| EqualsClipboardEvent(kMimeTypeTextUtf8, "c")); |
| } |
| |
| TEST_F(ClientSessionTest, InputAllowedFromRemotePolicy) { |
| SessionPolicies remote_policies; |
| remote_policies.allow_remote_input = true; |
| CreateClientSession(); |
| ConnectClientSession(&remote_policies); |
| SetupSingleDisplay(); |
| |
| FakeInputInjector* input_injector = |
| desktop_environment_factory_->last_desktop_environment() |
| ->last_input_injector() |
| .get(); |
| input_injector->set_key_events(&key_events_); |
| input_injector->set_mouse_events(&mouse_events_); |
| input_injector->set_clipboard_events(&clipboard_events_); |
| |
| connection_->clipboard_stub()->InjectClipboardEvent(MakeClipboardEvent("a")); |
| connection_->input_stub()->InjectKeyEvent(MakeKeyEvent(true, 1)); |
| connection_->input_stub()->InjectMouseEvent(MakeMouseMoveEvent(100, 101)); |
| |
| client_session_->DisconnectSession(ErrorCode::OK, {}, FROM_HERE); |
| client_session_.reset(); |
| |
| EXPECT_EQ(1U, mouse_events_.size()); |
| EXPECT_THAT(mouse_events_[0], EqualsMouseMoveEvent(100, 101)); |
| |
| EXPECT_EQ(2U, key_events_.size()); |
| EXPECT_THAT(key_events_[0], EqualsKeyEvent(1, true)); |
| EXPECT_THAT(key_events_[1], EqualsKeyEvent(1, false)); |
| |
| EXPECT_EQ(1U, clipboard_events_.size()); |
| EXPECT_THAT(clipboard_events_[0], |
| EqualsClipboardEvent(kMimeTypeTextUtf8, "a")); |
| } |
| |
| TEST_F(ClientSessionTest, InputDisabledFromRemotePolicy) { |
| SessionPolicies remote_policies; |
| remote_policies.allow_remote_input = false; |
| CreateClientSession(); |
| ConnectClientSession(&remote_policies); |
| SetupSingleDisplay(); |
| |
| FakeInputInjector* input_injector = |
| desktop_environment_factory_->last_desktop_environment() |
| ->last_input_injector() |
| .get(); |
| input_injector->set_key_events(&key_events_); |
| input_injector->set_mouse_events(&mouse_events_); |
| input_injector->set_clipboard_events(&clipboard_events_); |
| |
| connection_->clipboard_stub()->InjectClipboardEvent(MakeClipboardEvent("a")); |
| connection_->input_stub()->InjectKeyEvent(MakeKeyEvent(true, 1)); |
| connection_->input_stub()->InjectMouseEvent(MakeMouseMoveEvent(100, 101)); |
| |
| client_session_->DisconnectSession(ErrorCode::OK, {}, FROM_HERE); |
| client_session_.reset(); |
| |
| EXPECT_EQ(0U, mouse_events_.size()); |
| EXPECT_EQ(0U, key_events_.size()); |
| EXPECT_EQ(0U, clipboard_events_.size()); |
| } |
| |
| TEST_F(ClientSessionTest, LocalInputTest) { |
| CreateClientSession(); |
| ConnectClientSession(); |
| SetupSingleDisplay(); |
| |
| desktop_environment_factory_->last_desktop_environment() |
| ->last_input_injector() |
| ->set_mouse_events(&mouse_events_); |
| |
| connection_->input_stub()->InjectMouseEvent(MakeMouseMoveEvent(100, 101)); |
| |
| #if !BUILDFLAG(IS_WIN) && !BUILDFLAG(IS_CHROMEOS) |
| // The OS echoes the injected event back. |
| client_session_->OnLocalPointerMoved(webrtc::DesktopVector(100, 101), |
| ui::EventType::kMouseMoved); |
| #endif // !BUILDFLAG(IS_WIN) |
| |
| // This one should get throught as well. |
| connection_->input_stub()->InjectMouseEvent(MakeMouseMoveEvent(200, 201)); |
| |
| // Now this is a genuine local event. |
| client_session_->OnLocalPointerMoved(webrtc::DesktopVector(100, 101), |
| ui::EventType::kMouseMoved); |
| |
| // This one should be blocked because of the previous local input event. |
| connection_->input_stub()->InjectMouseEvent(MakeMouseMoveEvent(300, 301)); |
| |
| // Verify that we've received correct set of mouse events. |
| ASSERT_EQ(2U, mouse_events_.size()); |
| EXPECT_THAT(mouse_events_[0], EqualsMouseMoveEvent(100, 101)); |
| EXPECT_THAT(mouse_events_[1], EqualsMouseMoveEvent(200, 201)); |
| |
| // Verify that we're still connected. |
| EXPECT_TRUE(connection_->is_connected()); |
| |
| // TODO(jamiewalch): Verify that remote inputs are re-enabled |
| // eventually (via dependency injection, not sleep!) |
| } |
| |
| TEST_F(ClientSessionTest, DisconnectOnLocalInputTest) { |
| desktop_environment_options_.set_terminate_upon_input(true); |
| CreateClientSession(); |
| ConnectClientSession(); |
| SetupSingleDisplay(); |
| |
| client_session_->OnLocalPointerMoved(webrtc::DesktopVector(100, 101), |
| ui::EventType::kMouseMoved); |
| EXPECT_FALSE(connection_->is_connected()); |
| } |
| |
| TEST_F(ClientSessionTest, RestoreEventState) { |
| CreateClientSession(); |
| ConnectClientSession(); |
| SetupSingleDisplay(); |
| |
| FakeInputInjector* input_injector = |
| desktop_environment_factory_->last_desktop_environment() |
| ->last_input_injector() |
| .get(); |
| input_injector->set_key_events(&key_events_); |
| input_injector->set_mouse_events(&mouse_events_); |
| |
| connection_->input_stub()->InjectKeyEvent(MakeKeyEvent(true, 1)); |
| connection_->input_stub()->InjectKeyEvent(MakeKeyEvent(true, 2)); |
| |
| protocol::MouseEvent mousedown; |
| mousedown.set_button(protocol::MouseEvent::BUTTON_LEFT); |
| mousedown.set_button_down(true); |
| connection_->input_stub()->InjectMouseEvent(mousedown); |
| |
| client_session_->DisconnectSession(ErrorCode::OK, {}, FROM_HERE); |
| client_session_.reset(); |
| |
| EXPECT_EQ(2U, mouse_events_.size()); |
| EXPECT_THAT(mouse_events_[0], |
| EqualsMouseButtonEvent(protocol::MouseEvent::BUTTON_LEFT, true)); |
| EXPECT_THAT(mouse_events_[1], |
| EqualsMouseButtonEvent(protocol::MouseEvent::BUTTON_LEFT, false)); |
| |
| EXPECT_EQ(4U, key_events_.size()); |
| EXPECT_THAT(key_events_[0], EqualsKeyEvent(1, true)); |
| EXPECT_THAT(key_events_[1], EqualsKeyEvent(2, true)); |
| EXPECT_THAT(key_events_[2], EqualsKeyEvent(1, false)); |
| EXPECT_THAT(key_events_[3], EqualsKeyEvent(2, false)); |
| } |
| |
| TEST_F(ClientSessionTest, ClampMouseEvents) { |
| CreateClientSession(); |
| ConnectClientSession(); |
| SetupSingleDisplay(); |
| |
| desktop_environment_factory_->last_desktop_environment() |
| ->last_input_injector() |
| ->set_mouse_events(&mouse_events_); |
| |
| std::array<int, 3> input_x = {-999, 100, 999}; |
| std::array<int, 3> expected_x = { |
| 0, |
| 100, |
| protocol::FakeDesktopCapturer::kWidth - 1, |
| }; |
| std::array<int, 3> input_y = {-999, 50, 999}; |
| std::array<int, 3> expected_y = { |
| 0, |
| 50, |
| protocol::FakeDesktopCapturer::kHeight - 1, |
| }; |
| |
| protocol::MouseEvent expected_event; |
| for (int j = 0; j < 3; j++) { |
| for (int i = 0; i < 3; i++) { |
| mouse_events_.clear(); |
| connection_->input_stub()->InjectMouseEvent( |
| MakeMouseMoveEvent(input_x[i], input_y[j])); |
| |
| EXPECT_EQ(1U, mouse_events_.size()); |
| EXPECT_THAT(mouse_events_[0], |
| EqualsMouseMoveEvent(expected_x[i], expected_y[j])); |
| } |
| } |
| } |
| |
| // Verifies that clients can have extensions registered, resulting in the |
| // correct capabilities being reported, and messages delivered correctly. |
| // The extension system is tested more extensively in the |
| // HostExtensionSessionManager unit-tests. |
| TEST_F(ClientSessionTest, Extensions) { |
| // Configure fake extensions for testing. |
| FakeExtension extension1("ext1", "cap1"); |
| extensions_.push_back(&extension1); |
| FakeExtension extension2("ext2", ""); |
| extensions_.push_back(&extension2); |
| FakeExtension extension3("ext3", "cap3"); |
| extensions_.push_back(&extension3); |
| |
| // Verify that the ClientSession reports the correct capabilities. |
| EXPECT_CALL(client_stub_, SetCapabilities(IncludesCapabilities("cap1 cap3"))); |
| |
| CreateClientSession(); |
| ConnectClientSession(); |
| |
| testing::Mock::VerifyAndClearExpectations(&client_stub_); |
| |
| // Mimic the client reporting an overlapping set of capabilities. |
| protocol::Capabilities capabilities_message; |
| capabilities_message.set_capabilities("cap1 cap4 default"); |
| client_session_->SetCapabilities(capabilities_message); |
| |
| // Verify that the correct extension messages are delivered, and dropped. |
| protocol::ExtensionMessage message1; |
| message1.set_type("ext1"); |
| message1.set_data("data"); |
| client_session_->DeliverClientMessage(message1); |
| protocol::ExtensionMessage message3; |
| message3.set_type("ext3"); |
| message3.set_data("data"); |
| client_session_->DeliverClientMessage(message3); |
| protocol::ExtensionMessage message4; |
| message4.set_type("ext4"); |
| message4.set_data("data"); |
| client_session_->DeliverClientMessage(message4); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| // ext1 was instantiated and sent a message, and did not wrap anything. |
| EXPECT_TRUE(extension1.was_instantiated()); |
| EXPECT_TRUE(extension1.has_handled_message()); |
| |
| // ext2 was instantiated but not sent a message, and wrapped video encoder. |
| EXPECT_TRUE(extension2.was_instantiated()); |
| EXPECT_FALSE(extension2.has_handled_message()); |
| |
| // ext3 was sent a message but not instantiated. |
| EXPECT_FALSE(extension3.was_instantiated()); |
| |
| // Drop references to locals before they go out of scope. |
| extensions_.clear(); |
| } |
| |
| TEST_F(ClientSessionTest, DataChannelCallbackIsCalled) { |
| bool callback_called = false; |
| |
| CreateClientSession(); |
| client_session_->RegisterCreateHandlerCallbackForTesting( |
| kTestDataChannelCallbackName, |
| base::BindRepeating([](bool* callback_was_called, const std::string& name, |
| std::unique_ptr<protocol::MessagePipe> pipe) |
| -> void { *callback_was_called = true; }, |
| &callback_called)); |
| ConnectClientSession(); |
| |
| std::unique_ptr<protocol::MessagePipe> pipe = |
| base::WrapUnique(new protocol::FakeMessagePipe(false)); |
| |
| client_session_->OnIncomingDataChannel(kTestDataChannelCallbackName, |
| std::move(pipe)); |
| |
| ASSERT_TRUE(callback_called); |
| } |
| |
| TEST_F(ClientSessionTest, ForwardHostSessionOptions1) { |
| auto session = std::make_unique<protocol::FakeSession>(); |
| auto configuration = std::make_unique<jingle_xmpp::XmlElement>( |
| jingle_xmpp::QName(kChromotingXmlNamespace, "host-configuration")); |
| configuration->SetBodyText("Detect-Updated-Region:true"); |
| session->SetAttachment(0, std::move(configuration)); |
| CreateClientSession(std::move(session)); |
| ConnectClientSession(); |
| ASSERT_TRUE(desktop_environment_factory_->last_desktop_environment() |
| ->options() |
| .desktop_capture_options() |
| ->detect_updated_region()); |
| } |
| |
| TEST_F(ClientSessionTest, ForwardHostSessionOptions2) { |
| auto session = std::make_unique<protocol::FakeSession>(); |
| auto configuration = std::make_unique<jingle_xmpp::XmlElement>( |
| jingle_xmpp::QName(kChromotingXmlNamespace, "host-configuration")); |
| configuration->SetBodyText("Detect-Updated-Region:false"); |
| session->SetAttachment(0, std::move(configuration)); |
| CreateClientSession(std::move(session)); |
| ConnectClientSession(); |
| ASSERT_FALSE(desktop_environment_factory_->last_desktop_environment() |
| ->options() |
| .desktop_capture_options() |
| ->detect_updated_region()); |
| } |
| |
| TEST_F(ClientSessionTest, ActiveDisplayMessageSent) { |
| EXPECT_CALL(client_stub_, SetActiveDisplay(ScreenIdMatches(kDisplay1Id))); |
| |
| // The ActiveDisplayMonitor only gets created after negotiating this |
| // capability with the client. |
| desktop_environment_factory_->set_capabilities( |
| protocol::kMultiStreamCapability); |
| CreateClientSession(); |
| ConnectClientSession(); |
| |
| protocol::Capabilities client_capabilities; |
| client_capabilities.set_capabilities(protocol::kMultiStreamCapability); |
| client_session_->SetCapabilities(client_capabilities); |
| |
| auto monitor = desktop_environment_factory_->last_desktop_environment() |
| ->last_active_display_monitor(); |
| ASSERT_TRUE(monitor); |
| monitor->SetActiveDisplay(static_cast<webrtc::ScreenId>(kDisplay1Id)); |
| } |
| |
| // Display selection behaves quite differently if capturing of the full desktop |
| // is enabled or not. To simplify things these tests only handle the ChromeOS |
| // situation, where full desktop capturing is disabled. |
| #if BUILDFLAG(IS_CHROMEOS) |
| TEST_F(ClientSessionTest, ShouldSelectFirstDesktopByDefault) { |
| CreateClientSession(); |
| ConnectClientSession(); |
| |
| SetupMultiDisplay(); |
| |
| EXPECT_THAT(GetSelectedSourceDisplayId(), Eq(kDisplay1Id)); |
| } |
| |
| TEST_F(ClientSessionTest, |
| ShouldChangeSelectedSourceDisplayWhenSwitchingDisplay) { |
| CreateClientSession(); |
| ConnectClientSession(); |
| SetupMultiDisplay(); |
| |
| MultiMon_SelectSecondDisplay(); |
| EXPECT_THAT(GetSelectedSourceDisplayId(), Eq(kDisplay2Id)); |
| |
| MultiMon_SelectFirstDisplay(); |
| EXPECT_THAT(GetSelectedSourceDisplayId(), Eq(kDisplay1Id)); |
| } |
| |
| TEST_F(ClientSessionTest, |
| ShouldFallBackToPrimaryDisplayWhenSwitchingToInvalidDisplay) { |
| CreateClientSession(); |
| ConnectClientSession(); |
| SetupMultiDisplay(); |
| |
| MultiMon_SelectDisplay("Not an integer"); |
| EXPECT_THAT(GetSelectedSourceDisplayId(), Eq(kDisplay1Id)); |
| |
| MultiMon_SelectDisplay("123456"); // There is no display with this id. |
| EXPECT_THAT(GetSelectedSourceDisplayId(), Eq(kDisplay1Id)); |
| |
| // Full desktop capturing is not supported on ChromeOS. |
| MultiMon_SelectDisplay("all"); |
| EXPECT_THAT(GetSelectedSourceDisplayId(), Eq(kDisplay1Id)); |
| } |
| |
| TEST_F(ClientSessionTest, |
| ShouldFallBackToPrimaryDisplayWhenSelectedDisplayIsDisconnected) { |
| CreateClientSession(); |
| ConnectClientSession(); |
| SetupMultiDisplay(); |
| MultiMon_SelectSecondDisplay(); |
| |
| SetupSingleDisplay(); |
| EXPECT_THAT(GetSelectedSourceDisplayId(), Eq(kDisplay1Id)); |
| } |
| #endif // if BUILDFLAG(IS_CHROMEOS) |
| |
| } // namespace remoting |