Index: content/browser/devtools/protocol/devtools_protocol_browsertest.cc |
diff --git a/content/browser/devtools/protocol/devtools_protocol_browsertest.cc b/content/browser/devtools/protocol/devtools_protocol_browsertest.cc |
index e6e83576d6626b1eab23df2ea201c6032e936fe0..d37728b0931643f8c75252894da81ea9f4c2790c 100644 |
--- a/content/browser/devtools/protocol/devtools_protocol_browsertest.cc |
+++ b/content/browser/devtools/protocol/devtools_protocol_browsertest.cc |
@@ -9,6 +9,7 @@ |
#include "base/command_line.h" |
#include "base/json/json_reader.h" |
#include "base/json/json_writer.h" |
+#include "base/memory/ptr_util.h" |
#include "base/run_loop.h" |
#include "base/values.h" |
#include "build/build_config.h" |
@@ -24,10 +25,14 @@ |
#include "content/shell/browser/shell.h" |
#include "net/dns/mock_host_resolver.h" |
#include "net/test/embedded_test_server/embedded_test_server.h" |
+#include "testing/gmock/include/gmock/gmock.h" |
+#include "third_party/re2/src/re2/re2.h" |
#include "third_party/skia/include/core/SkBitmap.h" |
#include "ui/compositor/compositor_switches.h" |
#include "ui/gfx/codec/png_codec.h" |
+using testing::MatchesRegex; |
+ |
namespace content { |
namespace { |
@@ -170,16 +175,90 @@ class DevToolsProtocolTest : public ContentBrowserTest, |
} |
} |
- void WaitForNotification(const std::string& notification) { |
- waiting_for_notification_ = notification; |
+ // Waits for an event of type |notification_name| to be sent. |
+ void WaitForNotification(const std::string& notification_name) { |
+ waiting_for_notification_ = notification_name; |
+ waiting_for_notification_count_ = |
+ notification_count_[notification_name] + 1; |
RunMessageLoop(); |
} |
+ // Waits until the count of events of type |notification_name| is >= count. |
+ void WaitForNotificationCount(const std::string& notification_name, |
+ int count) { |
+ if (notification_count_[notification_name] < count) { |
+ waiting_for_notification_ = notification_name; |
+ waiting_for_notification_count_ = count; |
+ RunMessageLoop(); |
+ } |
+ } |
+ |
+ struct ExpectedNavigation { |
+ std::string url_regex; |
+ bool renderer_initiated; |
+ bool is_in_main_frame; |
+ bool is_redirect; |
+ std::string navigation_response; |
+ }; |
+ |
+ // Waits for the expected navigations to occur in any order. If an expected |
+ // navigation occurs, Page.processNavigation is called with the specified |
+ // navigation_response to either allow it to proceed or to cancel it. |
+ void WaitForNavigationsInAnyOrder( |
+ std::vector<ExpectedNavigation> expected_navigations) { |
+ while (!expected_navigations.empty()) { |
+ WaitForNotification("Page.navigationRequested"); |
+ ASSERT_TRUE(requested_notification_params_.get()); |
+ |
+ std::string url; |
+ ASSERT_TRUE(requested_notification_params_->GetString("url", &url)); |
+ int navigation_id; |
+ ASSERT_TRUE(requested_notification_params_->GetInteger("navigationId", |
+ &navigation_id)); |
+ bool renderer_initiated; |
+ ASSERT_TRUE(requested_notification_params_->GetBoolean( |
+ "rendererInitiated", &renderer_initiated)); |
+ bool is_in_main_frame; |
+ ASSERT_TRUE(requested_notification_params_->GetBoolean( |
+ "isInMainFrame", &is_in_main_frame)); |
+ bool is_redirect; |
+ ASSERT_TRUE(requested_notification_params_->GetBoolean("isRedirect", |
+ &is_redirect)); |
+ |
+ bool navigation_was_expected; |
+ for (auto it = expected_navigations.begin(); |
+ it != expected_navigations.end(); it++) { |
+ if (!RE2::FullMatch(url, it->url_regex) || |
+ renderer_initiated != it->renderer_initiated || |
+ is_in_main_frame != it->is_in_main_frame || |
+ is_redirect != it->is_redirect) { |
+ continue; |
+ } |
+ |
+ std::unique_ptr<base::DictionaryValue> params( |
+ new base::DictionaryValue()); |
+ params->SetString("response", it->navigation_response); |
+ params->SetInteger("navigationId", navigation_id); |
+ SendCommand("Page.processNavigation", std::move(params), false); |
+ |
+ navigation_was_expected = true; |
+ expected_navigations.erase(it); |
+ break; |
+ } |
+ EXPECT_TRUE(navigation_was_expected) |
+ << "url = " << url << "renderer_initiated = " << renderer_initiated |
+ << "is_in_main_frame = " << is_in_main_frame |
+ << "is_redirect = " << is_redirect; |
+ } |
+ } |
+ |
std::unique_ptr<base::DictionaryValue> result_; |
scoped_refptr<DevToolsAgentHost> agent_host_; |
int last_sent_id_; |
std::vector<int> result_ids_; |
- std::vector<std::string> notifications_; |
+ std::unique_ptr<base::DictionaryValue> requested_notification_params_; |
+ |
+ bool NoEventsReceived() const { return notification_count_.empty(); } |
private: |
void DispatchProtocolMessage(DevToolsAgentHost* agent_host, |
@@ -201,8 +280,15 @@ class DevToolsProtocolTest : public ContentBrowserTest, |
} else { |
std::string notification; |
EXPECT_TRUE(root->GetString("method", ¬ification)); |
- notifications_.push_back(notification); |
- if (waiting_for_notification_ == notification) { |
+ int count = ++notification_count_[notification]; |
+ if (waiting_for_notification_ == notification && |
+ count >= waiting_for_notification_count_) { |
+ base::DictionaryValue* params; |
+ if (root->GetDictionary("params", ¶ms)) { |
+ requested_notification_params_ = params->CreateDeepCopy(); |
+ } else { |
+ requested_notification_params_.reset(); |
+ } |
waiting_for_notification_ = std::string(); |
base::MessageLoop::current()->QuitNow(); |
} |
@@ -213,7 +299,9 @@ class DevToolsProtocolTest : public ContentBrowserTest, |
EXPECT_TRUE(false); |
} |
+ std::map<std::string, int> notification_count_; |
std::string waiting_for_notification_; |
+ int waiting_for_notification_count_; |
int waiting_for_command_result_id_; |
bool in_dispatch_; |
}; |
@@ -416,14 +504,7 @@ IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, NavigationPreservesMessages) { |
EXPECT_EQ(2, result_ids_[1]); // Page.navigate |
} |
- enough_results = notifications_.size() >= 1u; |
- EXPECT_TRUE(enough_results); |
- bool found_frame_notification = false; |
- for (const std::string& notification : notifications_) { |
- if (notification == "Page.frameStartedLoading") |
- found_frame_notification = true; |
- } |
- EXPECT_TRUE(found_frame_notification); |
+ WaitForNotificationCount("Page.frameStartedLoading", 1); |
} |
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, CrossSiteNoDetach) { |
@@ -440,7 +521,7 @@ IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, CrossSiteNoDetach) { |
"B.com", "/devtools/navigation.html"); |
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url2, 1); |
- EXPECT_EQ(0u, notifications_.size()); |
+ EXPECT_TRUE(NoEventsReceived()); |
} |
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, ReconnectPreservesState) { |
@@ -597,4 +678,138 @@ IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, BrowserNewPage) { |
EXPECT_EQ(2u, shell()->windows().size()); |
} |
+IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, ControlNavigations_MainFrame) { |
nasko
2016/07/14 16:47:19
No underscores, just CamelCase.
alex clarke (OOO till 29th)
2016/07/14 17:38:34
Done.
|
+ ASSERT_TRUE(embedded_test_server()->Start()); |
+ |
+ NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1); |
+ Attach(); |
+ SendCommand("Page.enable", nullptr); |
+ |
+ std::unique_ptr<base::DictionaryValue> params(new base::DictionaryValue()); |
+ params->SetBoolean("enabled", true); |
+ SendCommand("Page.setControlNavigations", std::move(params), true); |
+ |
+ GURL test_url = embedded_test_server()->GetURL( |
+ "/devtools/control_navigations/meta_tag.html"); |
+ shell()->LoadURL(test_url); |
+ |
+ std::vector<ExpectedNavigation> expected_navigations = { |
+ {"http://127.0.0.1:\\d+/devtools/control_navigations/meta_tag.html", |
+ false /* expected_renderer_initiated */, |
+ true /* expected_is_in_main_frame */, false /* expected_is_redirect */, |
+ "Proceed"}, |
+ {"http://127.0.0.1:\\d+/devtools/navigation.html", |
+ true /* expected_renderer_initiated */, |
+ true /* expected_is_in_main_frame */, false /* expected_is_redirect */, |
+ "Cancel"}}; |
+ |
+ WaitForNavigationsInAnyOrder(std::move(expected_navigations)); |
+ |
+ WaitForNotificationCount("Page.frameStoppedLoading", 2); |
+ |
+ // Check main frame has the expected url. |
+ SendCommand("Page.getResourceTree", nullptr); |
+ |
+ const base::DictionaryValue* frame_tree; |
+ ASSERT_TRUE(result_->GetDictionary("frameTree", &frame_tree)); |
+ const base::DictionaryValue* frame; |
+ ASSERT_TRUE(frame_tree->GetDictionary("frame", &frame)); |
+ |
+ std::string frame_url; |
+ ASSERT_TRUE(frame->GetString("url", &frame_url)); |
+ EXPECT_THAT( |
+ frame_url, |
+ MatchesRegex( |
+ "http://127.0.0.1:\\d+/devtools/control_navigations/meta_tag.html")); |
nasko
2016/07/14 16:47:19
very minor nit: You could just strip the port numb
alex clarke (OOO till 29th)
2016/07/14 17:38:34
Seems like a good idea, are there any utilities fo
nasko
2016/07/14 17:55:51
You can use a GURL::Replacements. There are some e
alex clarke (OOO till 29th)
2016/07/15 13:21:21
Done.
|
+} |
+ |
+// TODO(alexclarke): Make this work with IsolateAllSitesForTesting. |
+IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, ControlNavigations_ChildFrames) { |
nasko
2016/07/14 16:47:20
Thanks for adding this test!
|
+ host_resolver()->AddRule("*", "127.0.0.1"); |
+ ASSERT_TRUE(embedded_test_server()->Start()); |
+ content::SetupCrossSiteRedirector(embedded_test_server()); |
+ |
+ NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1); |
nasko
2016/07/14 16:47:19
Why do you need to navigate to about:blank first?
alex clarke (OOO till 29th)
2016/07/14 17:38:34
For two reasons:
1. To make sure there's a page we
nasko
2016/07/14 17:55:51
In general, each frame starts with an initial blan
|
+ Attach(); |
+ SendCommand("Page.enable", nullptr); |
+ |
+ std::unique_ptr<base::DictionaryValue> params(new base::DictionaryValue()); |
+ params->SetBoolean("enabled", true); |
+ SendCommand("Page.setControlNavigations", std::move(params), true); |
+ |
+ GURL test_url = embedded_test_server()->GetURL( |
+ "/devtools/control_navigations/iframe_navigation.html"); |
+ shell()->LoadURL(test_url); |
+ |
+ // Allow main frame navigation, and all iframe navigations to http://a.com |
+ // Allow initial iframe navigation to http://b.com but dissallow it to |
+ // navigate to /devtools/navigation.html. |
+ std::vector<ExpectedNavigation> expected_navigations = { |
+ {"http://127.0.0.1:\\d+/devtools/control_navigations/" |
+ "iframe_navigation.html", |
+ /* expected_renderer_initiated */ false, |
+ /* expected_is_in_main_frame */ true, |
+ /* expected_is_redirect */ false, "Proceed"}, |
+ {"http://127.0.0.1:\\d+/cross-site/a.com/devtools/control_navigations/" |
+ "meta_tag.html", |
+ /* expected_renderer_initiated */ true, |
+ /* expected_is_in_main_frame */ false, |
+ /* expected_is_redirect */ false, "Proceed"}, |
+ {"http://127.0.0.1:\\d+/cross-site/b.com/devtools/control_navigations/" |
+ "meta_tag.html", |
+ /* expected_renderer_initiated */ true, |
+ /* expected_is_in_main_frame */ false, |
+ /* expected_is_redirect */ false, "Proceed"}, |
+ {"http://a.com:\\d+/devtools/control_navigations/meta_tag.html", |
+ /* expected_renderer_initiated */ true, |
+ /* expected_is_in_main_frame */ false, |
+ /* expected_is_redirect */ true, "Proceed"}, |
+ {"http://b.com:\\d+/devtools/control_navigations/meta_tag.html", |
+ /* expected_renderer_initiated */ true, |
+ /* expected_is_in_main_frame */ false, |
+ /* expected_is_redirect */ true, "Proceed"}, |
+ {"http://a.com:\\d+/devtools/navigation.html", |
+ /* expected_renderer_initiated */ true, |
+ /* expected_is_in_main_frame */ false, |
+ /* expected_is_redirect */ false, "Proceed"}, |
+ {"http://b.com:\\d+/devtools/navigation.html", |
+ /* expected_renderer_initiated */ true, |
+ /* expected_is_in_main_frame */ false, |
+ /* expected_is_redirect */ false, "Cancel"}}; |
+ |
+ WaitForNavigationsInAnyOrder(std::move(expected_navigations)); |
+ |
+ WaitForNotificationCount("Page.frameStoppedLoading", 5); |
+ |
+ // Check that the a.com iframe navigated to /devtools/navigation.html and that |
+ // the b.com iframe was stuck at /devtools/control_navigations/meta_tag.html. |
+ SendCommand("Page.getResourceTree", nullptr); |
+ |
+ const base::DictionaryValue* frame_tree; |
+ ASSERT_TRUE(result_->GetDictionary("frameTree", &frame_tree)); |
+ |
+ const base::ListValue* child_frames; |
+ ASSERT_TRUE(frame_tree->GetList("childFrames", &child_frames)); |
+ EXPECT_EQ(2u, child_frames->GetSize()); |
+ |
+ const base::DictionaryValue* child_frame0; |
+ ASSERT_TRUE(child_frames->GetDictionary(0, &child_frame0)); |
+ ASSERT_TRUE(child_frame0->GetDictionary("frame", &child_frame0)); |
+ |
+ std::string frame0_url; |
+ ASSERT_TRUE(child_frame0->GetString("url", &frame0_url)); |
+ EXPECT_THAT(frame0_url, |
+ MatchesRegex("http://a.com:\\d+/devtools/navigation.html")); |
+ |
+ const base::DictionaryValue* child_frame1; |
+ ASSERT_TRUE(child_frames->GetDictionary(1, &child_frame1)); |
+ ASSERT_TRUE(child_frame1->GetDictionary("frame", &child_frame1)); |
+ std::string frame1_url; |
+ ASSERT_TRUE(child_frame1->GetString("url", &frame1_url)); |
+ EXPECT_THAT( |
+ frame1_url, |
+ MatchesRegex( |
+ "http://b.com:\\d+/devtools/control_navigations/meta_tag.html")); |
+} |
+ |
} // namespace content |