[chromedriver] Fix race where we might not wait for page load after navigating.
Change the NavigationTracker to assume the page is loading after Page.navigate.
BUG=chromedriver:347
R=chrisgao@chromium.org, craigdh@chromium.org
Review URL: https://codereview.chromium.org/15772009
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@202651 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index a3a7976..a436d83b 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -814,6 +814,7 @@
'test/chromedriver/chrome/devtools_client.h',
'test/chromedriver/chrome/devtools_client_impl.cc',
'test/chromedriver/chrome/devtools_client_impl.h',
+ 'test/chromedriver/chrome/devtools_event_listener.cc',
'test/chromedriver/chrome/devtools_event_listener.h',
'test/chromedriver/chrome/devtools_http_client.cc',
'test/chromedriver/chrome/devtools_http_client.h',
diff --git a/chrome/test/chromedriver/chrome/devtools_client_impl.cc b/chrome/test/chromedriver/chrome/devtools_client_impl.cc
index 89906ef..4adedbf 100644
--- a/chrome/test/chromedriver/chrome/devtools_client_impl.cc
+++ b/chrome/test/chromedriver/chrome/devtools_client_impl.cc
@@ -178,7 +178,8 @@
return Status(kOk);
}
-DevToolsClientImpl::ResponseInfo::ResponseInfo() : state(kWaiting) {}
+DevToolsClientImpl::ResponseInfo::ResponseInfo(const std::string& method)
+ : state(kWaiting), method(method) {}
DevToolsClientImpl::ResponseInfo::~ResponseInfo() {}
@@ -200,7 +201,8 @@
if (!socket_->Send(message))
return Status(kDisconnected, "unable to send message to renderer");
- linked_ptr<ResponseInfo> response_info = make_linked_ptr(new ResponseInfo());
+ linked_ptr<ResponseInfo> response_info =
+ make_linked_ptr(new ResponseInfo(method));
response_info_map_[command_id] = response_info;
while (response_info->state == kWaiting) {
Status status = ProcessNextMessage(command_id);
@@ -231,6 +233,9 @@
status = EnsureListenersNotifiedOfEvent();
if (status.IsError())
return status;
+ status = EnsureListenersNotifiedOfCommandResponse();
+ if (status.IsError())
+ return status;
// The command response may have already been received or blocked while
// notifying listeners.
@@ -300,15 +305,25 @@
linked_ptr<ResponseInfo> response_info = response_info_map_[response.id];
if (response_info->state == kReceived)
return Status(kUnknownError, "received multiple command responses");
+
if (response_info->state == kIgnored) {
response_info_map_.erase(response.id);
- return Status(kOk);
+ } else {
+ response_info->state = kReceived;
+ response_info->response.id = response.id;
+ response_info->response.error = response.error;
+ if (response.result)
+ response_info->response.result.reset(response.result->DeepCopy());
}
- response_info->state = kReceived;
- response_info->response.id = response.id;
- response_info->response.error = response.error;
- if (response.result)
- response_info->response.result.reset(response.result->DeepCopy());
+
+ if (response.result) {
+ unnotified_cmd_response_listeners_ = listeners_;
+ unnotified_cmd_response_info_ = response_info;
+ Status status = EnsureListenersNotifiedOfCommandResponse();
+ unnotified_cmd_response_info_.reset();
+ if (status.IsError())
+ return status;
+ }
return Status(kOk);
}
@@ -333,6 +348,19 @@
return Status(kOk);
}
+Status DevToolsClientImpl::EnsureListenersNotifiedOfCommandResponse() {
+ while (unnotified_cmd_response_listeners_.size()) {
+ DevToolsEventListener* listener =
+ unnotified_cmd_response_listeners_.front();
+ unnotified_cmd_response_listeners_.pop_front();
+ Status status =
+ listener->OnCommandSuccess(this, unnotified_cmd_response_info_->method);
+ if (status.IsError())
+ return status;
+ }
+ return Status(kOk);
+}
+
namespace internal {
bool ParseInspectorMessage(
diff --git a/chrome/test/chromedriver/chrome/devtools_client_impl.h b/chrome/test/chromedriver/chrome/devtools_client_impl.h
index f46d6b2..e30410ba 100644
--- a/chrome/test/chromedriver/chrome/devtools_client_impl.h
+++ b/chrome/test/chromedriver/chrome/devtools_client_impl.h
@@ -103,10 +103,11 @@
kReceived
};
struct ResponseInfo {
- ResponseInfo();
+ explicit ResponseInfo(const std::string& method);
~ResponseInfo();
ResponseState state;
+ std::string method;
internal::InspectorCommandResponse response;
};
typedef std::map<int, linked_ptr<ResponseInfo> > ResponseInfoMap;
@@ -121,6 +122,7 @@
const internal::InspectorCommandResponse& response);
Status EnsureListenersNotifiedOfConnect();
Status EnsureListenersNotifiedOfEvent();
+ Status EnsureListenersNotifiedOfCommandResponse();
scoped_ptr<SyncWebSocket> socket_;
GURL url_;
@@ -132,6 +134,8 @@
std::list<DevToolsEventListener*> unnotified_connect_listeners_;
std::list<DevToolsEventListener*> unnotified_event_listeners_;
const internal::InspectorEvent* unnotified_event_;
+ std::list<DevToolsEventListener*> unnotified_cmd_response_listeners_;
+ linked_ptr<ResponseInfo> unnotified_cmd_response_info_;
ResponseInfoMap response_info_map_;
int next_id_;
int stack_count_;
diff --git a/chrome/test/chromedriver/chrome/devtools_client_impl_unittest.cc b/chrome/test/chromedriver/chrome/devtools_client_impl_unittest.cc
index 9d1f475..708ca50 100644
--- a/chrome/test/chromedriver/chrome/devtools_client_impl_unittest.cc
+++ b/chrome/test/chromedriver/chrome/devtools_client_impl_unittest.cc
@@ -1072,3 +1072,54 @@
msgs.push_back("{\"id\": 6, \"result\": {}}");
ASSERT_EQ(kOk, client.HandleReceivedEvents().code());
}
+
+namespace {
+
+class MockCommandListener : public DevToolsEventListener {
+ public:
+ MockCommandListener() {}
+ virtual ~MockCommandListener() {}
+
+ virtual void OnEvent(DevToolsClient* client,
+ const std::string& method,
+ const base::DictionaryValue& params) OVERRIDE {
+ msgs_.push_back(method);
+ }
+
+ virtual Status OnCommandSuccess(DevToolsClient* client,
+ const std::string& method) OVERRIDE {
+ msgs_.push_back(method);
+ if (!callback_.is_null())
+ callback_.Run(client);
+ return Status(kOk);
+ }
+
+ base::Callback<void(DevToolsClient*)> callback_;
+ std::list<std::string> msgs_;
+};
+
+void HandleReceivedEvents(DevToolsClient* client) {
+ EXPECT_EQ(kOk, client->HandleReceivedEvents().code());
+}
+
+} // namespace
+
+TEST(DevToolsClientImpl, ReceivesCommandResponse) {
+ std::list<std::string> msgs;
+ SyncWebSocketFactory factory = base::Bind(&CreateMockSyncWebSocket6, &msgs);
+ Logger logger;
+ DevToolsClientImpl client(
+ factory, "http://url", "id", base::Bind(&CloserFunc), &logger);
+ MockCommandListener listener1;
+ listener1.callback_ = base::Bind(&HandleReceivedEvents);
+ MockCommandListener listener2;
+ client.AddListener(&listener1);
+ client.AddListener(&listener2);
+ msgs.push_back("{\"id\": 1, \"result\": {}}");
+ msgs.push_back("{\"method\": \"event\", \"params\": {}}");
+ base::DictionaryValue params;
+ ASSERT_EQ(kOk, client.SendCommand("cmd", params).code());
+ ASSERT_EQ(2u, listener2.msgs_.size());
+ ASSERT_EQ("cmd", listener2.msgs_.front());
+ ASSERT_EQ("event", listener2.msgs_.back());
+}
diff --git a/chrome/test/chromedriver/chrome/devtools_event_listener.cc b/chrome/test/chromedriver/chrome/devtools_event_listener.cc
new file mode 100644
index 0000000..30a023e
--- /dev/null
+++ b/chrome/test/chromedriver/chrome/devtools_event_listener.cc
@@ -0,0 +1,23 @@
+// Copyright (c) 2013 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/chrome/devtools_event_listener.h"
+
+#include "chrome/test/chromedriver/chrome/status.h"
+
+DevToolsEventListener::~DevToolsEventListener() {}
+
+Status DevToolsEventListener::OnConnected(DevToolsClient* client) {
+ return Status(kOk);
+}
+
+void DevToolsEventListener::OnEvent(DevToolsClient* client,
+ const std::string& method,
+ const base::DictionaryValue& params) {}
+
+Status DevToolsEventListener::OnCommandSuccess(
+ DevToolsClient* client,
+ const std::string& method) {
+ return Status(kOk);
+}
diff --git a/chrome/test/chromedriver/chrome/devtools_event_listener.h b/chrome/test/chromedriver/chrome/devtools_event_listener.h
index 6ce0385..610b7f49 100644
--- a/chrome/test/chromedriver/chrome/devtools_event_listener.h
+++ b/chrome/test/chromedriver/chrome/devtools_event_listener.h
@@ -14,16 +14,23 @@
class DevToolsClient;
class Status;
-// Listens to WebKit Inspector events and DevTools debugger connection.
+// Receives notification of incoming Blink Inspector messages and connection
+// to the DevTools server.
class DevToolsEventListener {
public:
- virtual ~DevToolsEventListener() {}
+ virtual ~DevToolsEventListener();
- virtual Status OnConnected(DevToolsClient* client) = 0;
+ // Called when a connection is made to the DevTools server.
+ virtual Status OnConnected(DevToolsClient* client);
+ // Called when an event is received.
virtual void OnEvent(DevToolsClient* client,
const std::string& method,
- const base::DictionaryValue& params) = 0;
+ const base::DictionaryValue& params);
+
+ // Called when a command success response is received.
+ virtual Status OnCommandSuccess(DevToolsClient* client,
+ const std::string& method);
};
#endif // CHROME_TEST_CHROMEDRIVER_CHROME_DEVTOOLS_EVENT_LISTENER_H_
diff --git a/chrome/test/chromedriver/chrome/navigation_tracker.cc b/chrome/test/chromedriver/chrome/navigation_tracker.cc
index cd04f81..509b318c 100644
--- a/chrome/test/chromedriver/chrome/navigation_tracker.cc
+++ b/chrome/test/chromedriver/chrome/navigation_tracker.cc
@@ -126,3 +126,10 @@
scheduled_frame_set_.clear();
}
}
+
+Status NavigationTracker::OnCommandSuccess(DevToolsClient* client,
+ const std::string& method) {
+ if (method == "Page.navigate")
+ loading_state_ = kLoading;
+ return Status(kOk);
+}
diff --git a/chrome/test/chromedriver/chrome/navigation_tracker.h b/chrome/test/chromedriver/chrome/navigation_tracker.h
index 45c8b98..cd16206 100644
--- a/chrome/test/chromedriver/chrome/navigation_tracker.h
+++ b/chrome/test/chromedriver/chrome/navigation_tracker.h
@@ -42,6 +42,8 @@
virtual void OnEvent(DevToolsClient* client,
const std::string& method,
const base::DictionaryValue& params) OVERRIDE;
+ virtual Status OnCommandSuccess(DevToolsClient* client,
+ const std::string& method) OVERRIDE;
private:
DevToolsClient* client_;
diff --git a/chrome/test/chromedriver/chrome/web_view_impl.cc b/chrome/test/chromedriver/chrome/web_view_impl.cc
index 4a25e090..023e111 100644
--- a/chrome/test/chromedriver/chrome/web_view_impl.cc
+++ b/chrome/test/chromedriver/chrome/web_view_impl.cc
@@ -8,6 +8,7 @@
#include "base/files/file_path.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
+#include "base/string_util.h"
#include "base/stringprintf.h"
#include "base/threading/platform_thread.h"
#include "base/time.h"
@@ -111,6 +112,10 @@
}
Status WebViewImpl::Load(const std::string& url) {
+ // Javascript URLs will cause a hang while waiting for the page to stop
+ // loading, so just disallow.
+ if (StartsWithASCII(url, "javascript:", false))
+ return Status(kUnknownError, "unsupported protocol");
base::DictionaryValue params;
params.SetString("url", url);
return client_->SendCommand("Page.navigate", params);