Chromium Code Reviews| 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 |