Commit 21c899ea authored by sergeyu's avatar sergeyu Committed by Commit bot

Simplify It2Me host shutdown

Previously It2Me host was stopped asynchronoulsy, which makes the
shutdown more complicated. Now it's synchronous and is significantly
simpler.
Also removed ChromotingHost::RejectAuthenticatingClient() which is no
longer needed.

Review URL: https://codereview.chromium.org/1458323002

Cr-Commit-Position: refs/heads/master@{#361362}
parent cea07ed2
......@@ -79,8 +79,6 @@ ChromotingHost::ChromotingHost(
signal_strategy_(signal_strategy),
started_(false),
login_backoff_(&kDefaultBackoffPolicy),
authenticating_client_(false),
reject_authenticating_client_(false),
enable_curtaining_(false),
weak_factory_(this) {
DCHECK(network_task_runner_->BelongsToCurrentThread());
......@@ -133,11 +131,6 @@ void ChromotingHost::AddExtension(scoped_ptr<HostExtension> extension) {
extensions_.push_back(extension.release());
}
void ChromotingHost::RejectAuthenticatingClient() {
DCHECK(authenticating_client_);
reject_authenticating_client_ = true;
}
void ChromotingHost::SetAuthenticatorFactory(
scoped_ptr<protocol::AuthenticatorFactory> authenticator_factory) {
DCHECK(CalledOnValidThread());
......@@ -183,7 +176,7 @@ void ChromotingHost::OnSessionAuthenticating(ClientSession* client) {
login_backoff_.InformOfRequest(false);
}
bool ChromotingHost::OnSessionAuthenticated(ClientSession* client) {
void ChromotingHost::OnSessionAuthenticated(ClientSession* client) {
DCHECK(CalledOnValidThread());
login_backoff_.Reset();
......@@ -192,10 +185,16 @@ bool ChromotingHost::OnSessionAuthenticated(ClientSession* client) {
// is called to avoid it becoming invalid when the client is removed from
// the list.
ClientList::iterator it = clients_.begin();
base::WeakPtr<ChromotingHost> self = weak_factory_.GetWeakPtr();
while (it != clients_.end()) {
ClientSession* other_client = *it++;
if (other_client != client)
if (other_client != client) {
other_client->DisconnectSession(protocol::OK);
// Quit if the host was destroyed.
if (!self)
return;
}
}
// Disconnects above must have destroyed all other clients.
......@@ -204,14 +203,8 @@ bool ChromotingHost::OnSessionAuthenticated(ClientSession* client) {
// Notify observers that there is at least one authenticated client.
const std::string& jid = client->client_jid();
reject_authenticating_client_ = false;
authenticating_client_ = true;
FOR_EACH_OBSERVER(HostStatusObserver, status_observers_,
OnClientAuthenticated(jid));
authenticating_client_ = false;
return !reject_authenticating_client_;
}
void ChromotingHost::OnSessionChannelsConnected(ClientSession* client) {
......@@ -236,13 +229,15 @@ void ChromotingHost::OnSessionClosed(ClientSession* client) {
ClientList::iterator it = std::find(clients_.begin(), clients_.end(), client);
CHECK(it != clients_.end());
if (client->is_authenticated()) {
FOR_EACH_OBSERVER(HostStatusObserver, status_observers_,
OnClientDisconnected(client->client_jid()));
}
bool was_authenticated = client->is_authenticated();
std::string jid = client->client_jid();
clients_.erase(it);
delete client;
if (was_authenticated) {
FOR_EACH_OBSERVER(HostStatusObserver, status_observers_,
OnClientDisconnected(jid));
}
}
void ChromotingHost::OnSessionRouteChange(
......
......@@ -94,11 +94,6 @@ class ChromotingHost : public base::NonThreadSafe,
// Registers a host extension.
void AddExtension(scoped_ptr<HostExtension> extension);
// This method may be called only from
// HostStatusObserver::OnClientAuthenticated() to reject the new
// client.
void RejectAuthenticatingClient();
// Sets the authenticator factory to use for incoming
// connections. Incoming connections are rejected until
// authenticator factory is set. Must be called on the network
......@@ -119,7 +114,7 @@ class ChromotingHost : public base::NonThreadSafe,
////////////////////////////////////////////////////////////////////////////
// ClientSession::EventHandler implementation.
void OnSessionAuthenticating(ClientSession* client) override;
bool OnSessionAuthenticated(ClientSession* client) override;
void OnSessionAuthenticated(ClientSession* client) override;
void OnSessionChannelsConnected(ClientSession* client) override;
void OnSessionAuthenticationFailed(ClientSession* client) override;
void OnSessionClosed(ClientSession* session) override;
......@@ -186,10 +181,6 @@ class ChromotingHost : public base::NonThreadSafe,
// Login backoff state.
net::BackoffEntry login_backoff_;
// Flags used for RejectAuthenticatingClient().
bool authenticating_client_;
bool reject_authenticating_client_;
// True if the curtain mode is enabled.
bool enable_curtaining_;
......
......@@ -270,11 +270,6 @@ class ChromotingHostTest : public testing::Test {
NotifyClientSessionClosed(0);
}
// Notify |host_| that the authenticating client has been rejected.
void RejectAuthenticatingClient() {
host_->RejectAuthenticatingClient();
}
// Notify |host_| that a client session has closed.
void NotifyClientSessionClosed(int connection_index) {
get_client(connection_index)->OnConnectionClosed(
......@@ -468,21 +463,6 @@ TEST_F(ChromotingHostTest, Connect) {
message_loop_.Run();
}
TEST_F(ChromotingHostTest, RejectAuthenticatingClient) {
Expectation start = ExpectHostAndSessionManagerStart();
EXPECT_CALL(host_status_observer_, OnClientAuthenticated(session_jid1_))
.WillOnce(InvokeWithoutArgs(
this, &ChromotingHostTest::RejectAuthenticatingClient));
ExpectClientDisconnected(
0, true, start,
InvokeWithoutArgs(this, &ChromotingHostTest::ShutdownHost));
EXPECT_CALL(host_status_observer_, OnShutdown());
host_->Start(xmpp_login_);
SimulateClientConnection(0, true, true);
message_loop_.Run();
}
TEST_F(ChromotingHostTest, AuthenticationFailed) {
ExpectHostAndSessionManagerStart();
EXPECT_CALL(host_status_observer_, OnAccessDenied(session_jid1_))
......
......@@ -296,11 +296,8 @@ void ClientSession::OnConnectionAuthenticated(
protocol::MAX_SESSION_LENGTH));
}
// Disconnect the session if the connection was rejected by the host.
if (!event_handler_->OnSessionAuthenticated(this)) {
DisconnectSession(protocol::SESSION_REJECTED);
return;
}
// Notify EventHandler.
event_handler_->OnSessionAuthenticated(this);
// Create the desktop environment. Drop the connection if it could not be
// created for any reason (for instance the curtain could not initialize).
......
......@@ -60,9 +60,8 @@ class ClientSession
// Called after authentication has started.
virtual void OnSessionAuthenticating(ClientSession* client) = 0;
// Called after authentication has finished successfully. Returns true if
// the connection is allowed, or false otherwise.
virtual bool OnSessionAuthenticated(ClientSession* client) = 0;
// Called after authentication has finished successfully.
virtual void OnSessionAuthenticated(ClientSession* client) = 0;
// Called after we've finished connecting all channels.
virtual void OnSessionChannelsConnected(ClientSession* client) = 0;
......
......@@ -330,8 +330,7 @@ void ClientSessionTest::SetSendMessageAndDisconnectExpectation(
message.set_data("data");
Expectation authenticated =
EXPECT_CALL(session_event_handler_, OnSessionAuthenticated(_))
.WillOnce(Return(true));
EXPECT_CALL(session_event_handler_, OnSessionAuthenticated(_));
EXPECT_CALL(session_event_handler_, OnSessionChannelsConnected(_))
.After(authenticated)
.WillOnce(DoAll(
......@@ -343,8 +342,7 @@ void ClientSessionTest::SetSendMessageAndDisconnectExpectation(
TEST_F(ClientSessionTest, ClipboardStubFilter) {
CreateClientSession();
EXPECT_CALL(session_event_handler_, OnSessionAuthenticated(_))
.WillOnce(Return(true));
EXPECT_CALL(session_event_handler_, OnSessionAuthenticated(_));
EXPECT_CALL(*input_injector_, StartPtr(_));
EXPECT_CALL(session_event_handler_, OnSessionChannelsConnected(_));
......@@ -449,8 +447,7 @@ TEST_F(ClientSessionTest, LocalInputTest) {
mouse_event3.set_y(301);
Expectation authenticated =
EXPECT_CALL(session_event_handler_, OnSessionAuthenticated(_))
.WillOnce(Return(true));
EXPECT_CALL(session_event_handler_, OnSessionAuthenticated(_));
EXPECT_CALL(*input_injector_, StartPtr(_))
.After(authenticated);
EXPECT_CALL(session_event_handler_, OnSessionChannelsConnected(_))
......@@ -506,8 +503,7 @@ TEST_F(ClientSessionTest, RestoreEventState) {
mousedown.set_button_down(true);
Expectation authenticated =
EXPECT_CALL(session_event_handler_, OnSessionAuthenticated(_))
.WillOnce(Return(true));
EXPECT_CALL(session_event_handler_, OnSessionAuthenticated(_));
EXPECT_CALL(*input_injector_, StartPtr(_)).After(authenticated);
EXPECT_CALL(session_event_handler_, OnSessionChannelsConnected(_))
.After(authenticated);
......@@ -547,8 +543,7 @@ TEST_F(ClientSessionTest, ClampMouseEvents) {
CreateClientSession();
Expectation authenticated =
EXPECT_CALL(session_event_handler_, OnSessionAuthenticated(_))
.WillOnce(Return(true));
EXPECT_CALL(session_event_handler_, OnSessionAuthenticated(_));
EXPECT_CALL(*input_injector_, StartPtr(_)).After(authenticated);
EXPECT_CALL(session_event_handler_, OnSessionChannelsConnected(_))
.After(authenticated);
......@@ -609,8 +604,7 @@ TEST_F(ClientSessionTest, NoGnubbyAuth) {
message.set_data("test");
Expectation authenticated =
EXPECT_CALL(session_event_handler_, OnSessionAuthenticated(_))
.WillOnce(Return(true));
EXPECT_CALL(session_event_handler_, OnSessionAuthenticated(_));
EXPECT_CALL(*input_injector_, StartPtr(_)).After(authenticated);
EXPECT_CALL(session_event_handler_, OnSessionChannelsConnected(_))
.After(authenticated)
......@@ -634,8 +628,7 @@ TEST_F(ClientSessionTest, EnableGnubbyAuth) {
message.set_data("test");
Expectation authenticated =
EXPECT_CALL(session_event_handler_, OnSessionAuthenticated(_))
.WillOnce(Return(true));
EXPECT_CALL(session_event_handler_, OnSessionAuthenticated(_));
EXPECT_CALL(*input_injector_, StartPtr(_)).After(authenticated);
EXPECT_CALL(session_event_handler_, OnSessionChannelsConnected(_))
.After(authenticated)
......@@ -655,8 +648,7 @@ TEST_F(ClientSessionTest, EnableGnubbyAuth) {
TEST_F(ClientSessionTest, ResetVideoPipeline) {
CreateClientSession();
EXPECT_CALL(session_event_handler_, OnSessionAuthenticated(_))
.WillOnce(Return(true));
EXPECT_CALL(session_event_handler_, OnSessionAuthenticated(_));
EXPECT_CALL(video_stub_, ProcessVideoPacketPtr(_, _))
.WillOnce(DoAll(
......@@ -687,8 +679,7 @@ TEST_F(ClientSessionTest, Extensions) {
CreateClientSession();
Expectation authenticated =
EXPECT_CALL(session_event_handler_, OnSessionAuthenticated(_))
.WillOnce(Return(true));
EXPECT_CALL(session_event_handler_, OnSessionAuthenticated(_));
// Verify that the ClientSession reports the correct capabilities, and mimic
// the client reporting an overlapping set of capabilities.
......@@ -743,8 +734,7 @@ TEST_F(ClientSessionTest, StealVideoCapturer) {
SetMatchCapabilitiesExpectation();
EXPECT_CALL(session_event_handler_, OnSessionAuthenticated(_))
.WillOnce(Return(true));
EXPECT_CALL(session_event_handler_, OnSessionAuthenticated(_));
ConnectClientSession();
......
......@@ -76,7 +76,7 @@ class MockClientSessionEventHandler : public ClientSession::EventHandler {
~MockClientSessionEventHandler() override;
MOCK_METHOD1(OnSessionAuthenticating, void(ClientSession* client));
MOCK_METHOD1(OnSessionAuthenticated, bool(ClientSession* client));
MOCK_METHOD1(OnSessionAuthenticated, void(ClientSession* client));
MOCK_METHOD1(OnSessionChannelsConnected, void(ClientSession* client));
MOCK_METHOD1(OnSessionAuthenticationFailed, void(ClientSession* client));
MOCK_METHOD1(OnSessionClosed, void(ClientSession* client));
......
......@@ -82,43 +82,33 @@ void It2MeHost::Connect() {
}
void It2MeHost::Disconnect() {
if (!host_context_->network_task_runner()->BelongsToCurrentThread()) {
DCHECK(task_runner_->BelongsToCurrentThread());
host_context_->network_task_runner()->PostTask(
FROM_HERE, base::Bind(&It2MeHost::Disconnect, this));
return;
}
switch (state_) {
case kDisconnected:
ShutdownOnNetworkThread();
return;
DCHECK(task_runner_->BelongsToCurrentThread());
host_context_->network_task_runner()->PostTask(
FROM_HERE, base::Bind(&It2MeHost::Shutdown, this));
}
case kStarting:
SetState(kDisconnecting, "");
SetState(kDisconnected, "");
ShutdownOnNetworkThread();
return;
void It2MeHost::Shutdown() {
DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
case kDisconnecting:
return;
confirmation_dialog_proxy_.reset();
default:
SetState(kDisconnecting, "");
if (!host_) {
SetState(kDisconnected, "");
ShutdownOnNetworkThread();
return;
}
// Deleting the host destroys SignalStrategy synchronously, but
// SignalStrategy::Listener handlers are not allowed to destroy
// SignalStrategy, so post task to destroy the host later.
host_context_->network_task_runner()->PostTask(
FROM_HERE, base::Bind(&It2MeHost::ShutdownOnNetworkThread, this));
return;
host_event_logger_.reset();
if (host_) {
host_->RemoveStatusObserver(this);
host_.reset();
}
register_request_.reset();
host_status_logger_.reset();
signal_strategy_.reset();
// Post tasks to delete UI objects on the UI thread.
host_context_->ui_task_runner()->DeleteSoon(
FROM_HERE, desktop_environment_factory_.release());
host_context_->ui_task_runner()->DeleteSoon(FROM_HERE,
policy_watcher_.release());
SetState(kDisconnected, "");
}
void It2MeHost::RequestNatPolicy() {
......@@ -162,7 +152,7 @@ void It2MeHost::OnConfirmationResult(It2MeConfirmationDialog::Result result) {
break;
case It2MeConfirmationDialog::Result::CANCEL:
Disconnect();
Shutdown();
break;
default:
......@@ -272,61 +262,21 @@ void It2MeHost::FinishConnect() {
return;
}
void It2MeHost::ShutdownOnNetworkThread() {
DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
DCHECK(state_ == kDisconnecting || state_ == kDisconnected);
confirmation_dialog_proxy_.reset();
if (state_ == kDisconnecting) {
host_event_logger_.reset();
host_->RemoveStatusObserver(this);
host_.reset();
register_request_.reset();
host_status_logger_.reset();
signal_strategy_.reset();
SetState(kDisconnected, "");
}
host_context_->ui_task_runner()->PostTask(
FROM_HERE, base::Bind(&It2MeHost::ShutdownOnUiThread, this));
}
void It2MeHost::ShutdownOnUiThread() {
DCHECK(host_context_->ui_task_runner()->BelongsToCurrentThread());
// Destroy the DesktopEnvironmentFactory, to free thread references.
desktop_environment_factory_.reset();
// Stop listening for policy updates.
policy_watcher_.reset();
}
void It2MeHost::OnAccessDenied(const std::string& jid) {
DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
++failed_login_attempts_;
if (failed_login_attempts_ == kMaxLoginAttempts) {
Disconnect();
Shutdown();
}
}
void It2MeHost::OnClientAuthenticated(const std::string& jid) {
void It2MeHost::OnClientConnected(const std::string& jid) {
DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
if (state_ == kDisconnecting) {
// Ignore the new connection if we are disconnecting.
return;
}
if (state_ == kConnected) {
// If we already connected another client then one of the connections may be
// an attacker, so both are suspect and we have to reject the second
// connection and shutdown the host.
host_->RejectAuthenticatingClient();
Disconnect();
return;
}
// ChromotingHost doesn't allow multiple concurrent connection and the
// host is destroyed in OnClientDisconnected() after the first connection.
CHECK_NE(state_, kConnected);
std::string client_username = jid;
size_t pos = client_username.find('/');
......@@ -346,7 +296,7 @@ void It2MeHost::OnClientAuthenticated(const std::string& jid) {
void It2MeHost::OnClientDisconnected(const std::string& jid) {
DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
Disconnect();
Shutdown();
}
void It2MeHost::OnPolicyUpdate(scoped_ptr<base::DictionaryValue> policies) {
......@@ -388,7 +338,7 @@ void It2MeHost::UpdateNatPolicy(bool nat_traversal_enabled) {
// When transitioning from enabled to disabled, force disconnect any
// existing session.
if (nat_traversal_enabled_ && !nat_traversal_enabled && IsConnected()) {
Disconnect();
Shutdown();
}
nat_traversal_enabled_ = nat_traversal_enabled;
......@@ -406,7 +356,7 @@ void It2MeHost::UpdateHostDomainPolicy(const std::string& host_domain) {
// When setting a host domain policy, force disconnect any existing session.
if (!host_domain.empty() && IsConnected()) {
Disconnect();
Shutdown();
}
required_host_domain_ = host_domain;
......@@ -429,33 +379,29 @@ void It2MeHost::SetState(It2MeHostState state,
break;
case kStarting:
DCHECK(state == kRequestedAccessCode ||
state == kDisconnecting ||
state == kDisconnected ||
state == kError ||
state == kInvalidDomainError) << state;
break;
case kRequestedAccessCode:
DCHECK(state == kReceivedAccessCode ||
state == kDisconnecting ||
state == kDisconnected ||
state == kError) << state;
break;
case kReceivedAccessCode:
DCHECK(state == kConnected ||
state == kDisconnecting ||
state == kDisconnected ||
state == kError) << state;
break;
case kConnected:
DCHECK(state == kDisconnecting ||
state == kDisconnected ||
DCHECK(state == kDisconnected ||
state == kError) << state;
break;
case kDisconnecting:
DCHECK(state == kDisconnected) << state;
break;
case kError:
DCHECK(state == kDisconnecting) << state;
DCHECK(state == kDisconnected) << state;
break;
case kInvalidDomainError:
DCHECK(state == kDisconnecting) << state;
DCHECK(state == kDisconnected) << state;
break;
};
......@@ -480,7 +426,7 @@ void It2MeHost::OnReceivedSupportID(
if (!error_message.empty()) {
SetState(kError, error_message);
Disconnect();
Shutdown();
return;
}
......@@ -492,7 +438,7 @@ void It2MeHost::OnReceivedSupportID(
std::string error_message = "Failed to generate host certificate.";
LOG(ERROR) << error_message;
SetState(kError, error_message);
Disconnect();
Shutdown();
return;
}
......
......@@ -42,7 +42,6 @@ enum It2MeHostState {
kRequestedAccessCode,
kReceivedAccessCode,
kConnected,
kDisconnecting,
kError,
kInvalidDomainError
};
......@@ -74,8 +73,7 @@ class It2MeHost : public base::RefCountedThreadSafe<It2MeHost>,
// Creates It2Me host structures and starts the host.
virtual void Connect();
// Disconnects the host, ready for tear-down.
// Also called internally, from the network thread.
// Disconnects and shuts down the host.
virtual void Disconnect();
// TODO (weitaosu): Remove RequestNatPolicy from It2MeHost.
......@@ -84,7 +82,7 @@ class It2MeHost : public base::RefCountedThreadSafe<It2MeHost>,
// remoting::HostStatusObserver implementation.
void OnAccessDenied(const std::string& jid) override;
void OnClientAuthenticated(const std::string& jid) override;
void OnClientConnected(const std::string& jid) override;
void OnClientDisconnected(const std::string& jid) override;
void SetStateForTesting(It2MeHostState state,
......@@ -128,14 +126,6 @@ class It2MeHost : public base::RefCountedThreadSafe<It2MeHost>,
const base::TimeDelta& lifetime,
const std::string& error_message);
// Shuts down |host_| on the network thread and posts ShutdownOnUiThread()
// to shut down UI thread resources.
void ShutdownOnNetworkThread();
// Shuts down |desktop_environment_factory_| and |policy_watcher_| on
// the UI thread.
void ShutdownOnUiThread();
// Called when initial policies are read, and when they change.
void OnPolicyUpdate(scoped_ptr<base::DictionaryValue> policies);
......@@ -146,6 +136,8 @@ class It2MeHost : public base::RefCountedThreadSafe<It2MeHost>,
void UpdateNatPolicy(bool nat_traversal_enabled);
void UpdateHostDomainPolicy(const std::string& host_domain);
void Shutdown();
// Caller supplied fields.
scoped_ptr<ChromotingHostContext> host_context_;
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
......
......@@ -33,7 +33,6 @@ const remoting::protocol::NameMapElement<It2MeHostState> kIt2MeHostStates[] = {
{kRequestedAccessCode, "REQUESTED_ACCESS_CODE"},
{kReceivedAccessCode, "RECEIVED_ACCESS_CODE"},
{kConnected, "CONNECTED"},
{kDisconnecting, "DISCONNECTING"},
{kError, "ERROR"},
{kInvalidDomainError, "INVALID_DOMAIN_ERROR"},
};
......@@ -255,8 +254,7 @@ void It2MeNativeMessagingHost::OnStateChanged(
scoped_ptr<base::DictionaryValue> message(new base::DictionaryValue());
message->SetString("type", "hostStateChanged");
message->SetString("state",
It2MeNativeMessagingHost::HostStateToString(state));
message->SetString("state", HostStateToString(state));
switch (state_) {
case kReceivedAccessCode:
......
......@@ -23,6 +23,8 @@
#if defined(OS_LINUX)
#include <gtk/gtk.h>
#include <X11/Xlib.h>
#include "base/linux_util.h"
#endif // defined(OS_LINUX)
#if defined(OS_MACOSX)
......@@ -73,6 +75,10 @@ int StartIt2MeNativeMessagingHost() {
// Continue windows. Calling with nullptr arguments because we don't have
// any command line arguments for gtk to consume.
gtk_init(nullptr, nullptr);
// Need to prime the host OS version value for linux to prevent IO on the
// network thread. base::GetLinuxDistro() caches the result.
base::GetLinuxDistro();
#endif // OS_LINUX
// Enable support for SSL server sockets, which must be done while still
......
......@@ -134,7 +134,6 @@ void MockIt2MeHost::Disconnect() {
return;
}
RunSetState(kDisconnecting);
RunSetState(kDisconnected);
}
......@@ -379,11 +378,10 @@ void It2MeNativeMessagingHostTest::VerifyConnectResponses(int request_id) {
void It2MeNativeMessagingHostTest::VerifyDisconnectResponses(int request_id) {
bool disconnect_response_received = false;
bool disconnecting_received = false;
bool disconnected_received = false;
// We expect a total of 3 messages: 1 connectResponse and 2 hostStateChanged.
for (int i = 0; i < 3; ++i) {
// We expect a total of 3 messages: 1 connectResponse and 1 hostStateChanged.
for (int i = 0; i < 2; ++i) {
scoped_ptr<base::DictionaryValue> response = ReadMessageFromOutputPipe();
ASSERT_TRUE(response);
......@@ -397,12 +395,7 @@ void It2MeNativeMessagingHostTest::VerifyDisconnectResponses(int request_id) {
} else if (type == "hostStateChanged") {
std::string state;
ASSERT_TRUE(response->GetString("state", &state));
if (state ==
It2MeNativeMessagingHost::HostStateToString(kDisconnecting)) {
EXPECT_FALSE(disconnecting_received);
disconnecting_received = true;
} else if (state ==
It2MeNativeMessagingHost::HostStateToString(kDisconnected)) {
if (state == It2MeNativeMessagingHost::HostStateToString(kDisconnected)) {
EXPECT_FALSE(disconnected_received);
disconnected_received = true;
} else {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment