blob: f6cdd1940b57df5a9533570bef8abe3ab8df0cac [file] [log] [blame]
// Copyright (c) 2012 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 "chrome/test/chromedriver/net/sync_websocket_impl.h"
#include <memory>
#include "base/bind.h"
#include "base/callback.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/location.h"
#include "base/single_thread_task_runner.h"
#include "base/synchronization/waitable_event.h"
#include "chrome/test/chromedriver/net/command_id.h"
#include "chrome/test/chromedriver/net/timeout.h"
#include "net/base/net_errors.h"
#include "net/url_request/url_request_context_getter.h"
#include "url/gurl.h"
SyncWebSocketImpl::SyncWebSocketImpl(
net::URLRequestContextGetter* context_getter)
: core_(new Core(context_getter)) {}
SyncWebSocketImpl::~SyncWebSocketImpl() {}
bool SyncWebSocketImpl::IsConnected() {
return core_->IsConnected();
}
bool SyncWebSocketImpl::Connect(const GURL& url) {
return core_->Connect(url);
}
bool SyncWebSocketImpl::Send(const std::string& message) {
return core_->Send(message);
}
SyncWebSocket::StatusCode SyncWebSocketImpl::ReceiveNextMessage(
std::string* message, const Timeout& timeout) {
return core_->ReceiveNextMessage(message, timeout);
}
bool SyncWebSocketImpl::HasNextMessage() {
return core_->HasNextMessage();
}
SyncWebSocketImpl::Core::Core(net::URLRequestContextGetter* context_getter)
: context_getter_(context_getter),
is_connected_(false),
on_update_event_(&lock_) {}
bool SyncWebSocketImpl::Core::IsConnected() {
base::AutoLock lock(lock_);
return is_connected_;
}
bool SyncWebSocketImpl::Core::Connect(const GURL& url) {
bool success = false;
base::WaitableEvent event(base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED);
// Connect with retries. The retry timeout starts at 4 seconds, with
// exponential backoff, up to 16 seconds. The maximum total wait time is
// about 30 seconds. (Normally, a successful connection takes only a few
// milliseconds on Linux and Mac, but around 2 seconds for Windows 10.)
// See https://crbug.com/chromedriver/3301 for Windows 10 startup times
const int kMaxTimeout = 16;
for (int timeout = 4; timeout <= kMaxTimeout; timeout *= 2) {
context_getter_->GetNetworkTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(&SyncWebSocketImpl::Core::ConnectOnIO, this,
url, &success, &event));
if (event.TimedWait(base::TimeDelta::FromSeconds(timeout)))
break;
LOG(WARNING) << "Timed out connecting to Chrome, "
<< (timeout < kMaxTimeout ? "retrying..." : "giving up.");
}
if (!success) {
// Make sure the underlying connection is closed before we return, otherwise
// it might try to set event or success flag after they have already gone
// out of scope.
context_getter_->GetNetworkTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(&SyncWebSocketImpl::Core::CloseOnIO, this, &event));
event.Wait();
return false;
}
return success;
}
bool SyncWebSocketImpl::Core::Send(const std::string& message) {
bool success = false;
base::WaitableEvent event(base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED);
context_getter_->GetNetworkTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(&SyncWebSocketImpl::Core::SendOnIO, this,
message, &success, &event));
event.Wait();
return success;
}
SyncWebSocket::StatusCode SyncWebSocketImpl::Core::ReceiveNextMessage(
std::string* message,
const Timeout& timeout) {
base::AutoLock lock(lock_);
while (received_queue_.empty() && is_connected_) {
base::TimeDelta next_wait = timeout.GetRemainingTime();
if (next_wait <= base::TimeDelta())
return SyncWebSocket::kTimeout;
on_update_event_.TimedWait(next_wait);
}
if (!is_connected_)
return SyncWebSocket::kDisconnected;
*message = received_queue_.front();
received_queue_.pop_front();
return SyncWebSocket::kOk;
}
bool SyncWebSocketImpl::Core::HasNextMessage() {
base::AutoLock lock(lock_);
return !received_queue_.empty();
}
// TODO(johnchen) : Send messages with negative command ids to client.
// https://crrev.com/c/1745493 is a pending CL that does this
void SyncWebSocketImpl::Core::OnMessageReceived(const std::string& message) {
base::AutoLock lock(lock_);
bool send_to_chromedriver;
DetermineRecipient(message, &send_to_chromedriver);
if (send_to_chromedriver)
received_queue_.push_back(message);
on_update_event_.Signal();
}
void SyncWebSocketImpl::Core::DetermineRecipient(const std::string& message,
bool* send_to_chromedriver) {
absl::optional<base::Value> message_value =
base::JSONReader::Read(message, base::JSON_REPLACE_INVALID_CHARACTERS);
base::DictionaryValue* message_dict;
if (!message_value || !message_value->GetAsDictionary(&message_dict)) {
*send_to_chromedriver = true;
return;
}
int id;
*send_to_chromedriver =
!message_dict->HasKey("id") || (message_dict->GetInteger("id", &id) &&
CommandId::IsChromeDriverCommandId(id));
}
void SyncWebSocketImpl::Core::OnClose() {
base::AutoLock lock(lock_);
is_connected_ = false;
on_update_event_.Signal();
}
SyncWebSocketImpl::Core::~Core() { }
void SyncWebSocketImpl::Core::ConnectOnIO(
const GURL& url,
bool* success,
base::WaitableEvent* event) {
{
base::AutoLock lock(lock_);
received_queue_.clear();
}
// If this is a retry to connect, there is a chance that the original attempt
// to connect has succeeded after the retry was initiated, so double check if
// we are already connected. The is_connected_ flag is only set on the I/O
// thread, so no additional synchronization is needed to check it here.
// Note: If is_connected_ is true, both |success| and |event| may point to
// stale memory, so don't use either parameters before returning.
if (socket_ && is_connected_)
return;
socket_ = std::make_unique<WebSocket>(url, this);
socket_->Connect(base::BindOnce(
&SyncWebSocketImpl::Core::OnConnectCompletedOnIO, this, success, event));
}
void SyncWebSocketImpl::Core::OnConnectCompletedOnIO(
bool* success,
base::WaitableEvent* event,
int error) {
*success = (error == net::OK);
if (*success) {
base::AutoLock lock(lock_);
is_connected_ = true;
}
event->Signal();
}
void SyncWebSocketImpl::Core::SendOnIO(
const std::string& message,
bool* success,
base::WaitableEvent* event) {
*success = socket_->Send(message);
event->Signal();
}
void SyncWebSocketImpl::Core::OnDestruct() const {
scoped_refptr<base::SingleThreadTaskRunner> network_task_runner =
context_getter_->GetNetworkTaskRunner();
if (network_task_runner->BelongsToCurrentThread())
delete this;
else
network_task_runner->DeleteSoon(FROM_HERE, this);
}
void SyncWebSocketImpl::Core::CloseOnIO(base::WaitableEvent* event) {
socket_.reset();
event->Signal();
}