| // Copyright 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 <stddef.h> |
| |
| #include <utility> |
| |
| #include "base/base64.h" |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/command_line.h" |
| #include "base/json/json_reader.h" |
| #include "base/json/json_writer.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/run_loop.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/sys_info.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "content/browser/frame_host/interstitial_page_impl.h" |
| #include "content/browser/renderer_host/render_widget_host_view_base.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/public/browser/devtools_agent_host.h" |
| #include "content/public/browser/interstitial_page_delegate.h" |
| #include "content/public/browser/javascript_dialog_manager.h" |
| #include "content/public/browser/navigation_controller.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_view_host.h" |
| #include "content/public/browser/render_widget_host_view.h" |
| #include "content/public/browser/ssl_status.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/url_constants.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/content_browser_test.h" |
| #include "content/public/test/content_browser_test_utils.h" |
| #include "content/public/test/test_navigation_observer.h" |
| #include "content/shell/browser/shell.h" |
| #include "content/test/content_browser_test_utils_internal.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/test/cert_test_util.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "net/test/test_data_directory.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| #include "third_party/skia/include/core/SkColor.h" |
| #include "ui/base/layout.h" |
| #include "ui/compositor/compositor_switches.h" |
| #include "ui/gfx/codec/jpeg_codec.h" |
| #include "ui/gfx/codec/png_codec.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/geometry/size.h" |
| #include "ui/gfx/skia_util.h" |
| #include "ui/snapshot/snapshot.h" |
| |
| #define EXPECT_SIZE_EQ(expected, actual) \ |
| do { \ |
| EXPECT_EQ((expected).width(), (actual).width()); \ |
| EXPECT_EQ((expected).height(), (actual).height()); \ |
| } while (false) |
| |
| using testing::ElementsAre; |
| |
| namespace content { |
| |
| namespace { |
| |
| const char kIdParam[] = "id"; |
| const char kMethodParam[] = "method"; |
| const char kParamsParam[] = "params"; |
| |
| class TestJavaScriptDialogManager : public JavaScriptDialogManager, |
| public WebContentsDelegate { |
| public: |
| TestJavaScriptDialogManager() : handle_(false) {} |
| ~TestJavaScriptDialogManager() override {} |
| |
| void Handle() |
| { |
| if (!callback_.is_null()) { |
| callback_.Run(true, base::string16()); |
| callback_.Reset(); |
| } else { |
| handle_ = true; |
| } |
| } |
| |
| // WebContentsDelegate |
| JavaScriptDialogManager* GetJavaScriptDialogManager( |
| WebContents* source) override { |
| return this; |
| } |
| |
| // JavaScriptDialogManager |
| void RunJavaScriptDialog(WebContents* web_contents, |
| const GURL& origin_url, |
| JavaScriptDialogType dialog_type, |
| const base::string16& message_text, |
| const base::string16& default_prompt_text, |
| const DialogClosedCallback& callback, |
| bool* did_suppress_message) override { |
| if (handle_) { |
| handle_ = false; |
| callback.Run(true, base::string16()); |
| } else { |
| callback_ = callback; |
| } |
| }; |
| |
| void RunBeforeUnloadDialog(WebContents* web_contents, |
| bool is_reload, |
| const DialogClosedCallback& callback) override {} |
| |
| bool HandleJavaScriptDialog(WebContents* web_contents, |
| bool accept, |
| const base::string16* prompt_override) override { |
| return true; |
| } |
| |
| void CancelDialogs(WebContents* web_contents, |
| bool reset_state) override {} |
| |
| private: |
| DialogClosedCallback callback_; |
| bool handle_; |
| DISALLOW_COPY_AND_ASSIGN(TestJavaScriptDialogManager); |
| }; |
| |
| } |
| |
| class DevToolsProtocolTest : public ContentBrowserTest, |
| public DevToolsAgentHostClient, |
| public WebContentsDelegate { |
| public: |
| DevToolsProtocolTest() |
| : last_sent_id_(0), |
| waiting_for_command_result_id_(0), |
| in_dispatch_(false), |
| agent_host_can_close_(false) {} |
| |
| void SetUpOnMainThread() override { |
| ok_cert_ = |
| net::ImportCertFromFile(net::GetTestCertsDirectory(), "ok_cert.pem"); |
| expired_cert_ = net::ImportCertFromFile(net::GetTestCertsDirectory(), |
| "expired_cert.pem"); |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| } |
| |
| protected: |
| // WebContentsDelegate method: |
| bool DidAddMessageToConsole(WebContents* source, |
| int32_t level, |
| const base::string16& message, |
| int32_t line_no, |
| const base::string16& source_id) override { |
| console_messages_.push_back(base::UTF16ToUTF8(message)); |
| return true; |
| } |
| |
| void ShowCertificateViewerInDevTools( |
| WebContents* web_contents, |
| scoped_refptr<net::X509Certificate> certificate) override { |
| last_shown_certificate_ = certificate; |
| } |
| |
| void SendCommand(const std::string& method, |
| std::unique_ptr<base::DictionaryValue> params) { |
| SendCommand(method, std::move(params), true); |
| } |
| |
| void SendCommand(const std::string& method, |
| std::unique_ptr<base::DictionaryValue> params, |
| bool wait) { |
| in_dispatch_ = true; |
| base::DictionaryValue command; |
| command.SetInteger(kIdParam, ++last_sent_id_); |
| command.SetString(kMethodParam, method); |
| if (params) |
| command.Set(kParamsParam, std::move(params)); |
| |
| std::string json_command; |
| base::JSONWriter::Write(command, &json_command); |
| agent_host_->DispatchProtocolMessage(this, json_command); |
| // Some messages are dispatched synchronously. |
| // Only run loop if we are not finished yet. |
| if (in_dispatch_ && wait) |
| WaitForResponse(); |
| in_dispatch_ = false; |
| } |
| |
| void WaitForResponse() { |
| waiting_for_command_result_id_ = last_sent_id_; |
| base::RunLoop().Run(); |
| } |
| |
| bool HasValue(const std::string& path) { |
| base::Value* value = 0; |
| return result_->Get(path, &value); |
| } |
| |
| bool HasListItem(const std::string& path_to_list, |
| const std::string& name, |
| const std::string& value) { |
| base::ListValue* list; |
| if (!result_->GetList(path_to_list, &list)) |
| return false; |
| |
| for (size_t i = 0; i != list->GetSize(); i++) { |
| base::DictionaryValue* item; |
| if (!list->GetDictionary(i, &item)) |
| return false; |
| std::string id; |
| if (!item->GetString(name, &id)) |
| return false; |
| if (id == value) |
| return true; |
| } |
| return false; |
| } |
| |
| void Attach() { |
| agent_host_ = DevToolsAgentHost::GetOrCreateFor(shell()->web_contents()); |
| agent_host_->AttachClient(this); |
| shell()->web_contents()->SetDelegate(this); |
| } |
| |
| void TearDownOnMainThread() override { |
| if (agent_host_) { |
| agent_host_->DetachClient(this); |
| agent_host_ = nullptr; |
| } |
| } |
| |
| std::unique_ptr<base::DictionaryValue> WaitForNotification( |
| const std::string& notification) { |
| return WaitForNotification(notification, false); |
| } |
| |
| std::unique_ptr<base::DictionaryValue> WaitForNotification( |
| const std::string& notification, |
| bool allow_existing) { |
| if (allow_existing) { |
| for (size_t i = 0; i < notifications_.size(); i++) { |
| if (notifications_[i] == notification) { |
| std::unique_ptr<base::DictionaryValue> result = |
| std::move(notification_params_[i]); |
| notifications_.erase(notifications_.begin() + i); |
| notification_params_.erase(notification_params_.begin() + i); |
| return result; |
| } |
| } |
| } |
| |
| waiting_for_notification_ = notification; |
| RunMessageLoop(); |
| return std::move(waiting_for_notification_params_); |
| } |
| |
| void ClearNotifications() { |
| notifications_.clear(); |
| notification_params_.clear(); |
| } |
| |
| struct ExpectedNavigation { |
| std::string url; |
| bool is_in_main_frame; |
| bool is_redirect; |
| std::string navigation_response; |
| }; |
| |
| std::string RemovePort(const GURL& url) { |
| GURL::Replacements remove_port; |
| remove_port.ClearPort(); |
| return url.ReplaceComponents(remove_port).spec(); |
| } |
| |
| // 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 ProcessNavigationsAnyOrder( |
| std::vector<ExpectedNavigation> expected_navigations) { |
| while (!expected_navigations.empty()) { |
| std::unique_ptr<base::DictionaryValue> params = |
| WaitForNotification("Page.navigationRequested"); |
| |
| std::string url; |
| ASSERT_TRUE(params->GetString("url", &url)); |
| |
| // The url will typically have a random port which we want to remove. |
| url = RemovePort(GURL(url)); |
| |
| int navigation_id; |
| ASSERT_TRUE(params->GetInteger("navigationId", &navigation_id)); |
| bool is_in_main_frame; |
| ASSERT_TRUE(params->GetBoolean( "isInMainFrame", &is_in_main_frame)); |
| bool is_redirect; |
| ASSERT_TRUE(params->GetBoolean("isRedirect", &is_redirect)); |
| |
| bool navigation_was_expected; |
| for (auto it = expected_navigations.begin(); |
| it != expected_navigations.end(); it++) { |
| if (url != it->url || is_in_main_frame != it->is_in_main_frame || |
| is_redirect != it->is_redirect) { |
| continue; |
| } |
| |
| std::unique_ptr<base::DictionaryValue> process_params( |
| new base::DictionaryValue()); |
| process_params->SetString("response", it->navigation_response); |
| process_params->SetInteger("navigationId", navigation_id); |
| SendCommand("Page.processNavigation", std::move(process_params), false); |
| |
| navigation_was_expected = true; |
| expected_navigations.erase(it); |
| break; |
| } |
| EXPECT_TRUE(navigation_was_expected) |
| << "url = " << url << "is_in_main_frame = " << is_in_main_frame |
| << "is_redirect = " << is_redirect; |
| } |
| } |
| |
| std::vector<std::string> GetAllFrameUrls() { |
| std::vector<std::string> urls; |
| for (RenderFrameHost* render_frame_host : |
| shell()->web_contents()->GetAllFrames()) { |
| urls.push_back(RemovePort(render_frame_host->GetLastCommittedURL())); |
| } |
| return urls; |
| } |
| |
| const scoped_refptr<net::X509Certificate>& last_shown_certificate() { |
| return last_shown_certificate_; |
| } |
| |
| const scoped_refptr<net::X509Certificate>& ok_cert() { return ok_cert_; } |
| |
| const scoped_refptr<net::X509Certificate>& expired_cert() { |
| return expired_cert_; |
| } |
| |
| void set_agent_host_can_close() { agent_host_can_close_ = true; } |
| |
| 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::vector<std::string> console_messages_; |
| std::vector<std::unique_ptr<base::DictionaryValue>> notification_params_; |
| |
| private: |
| void DispatchProtocolMessage(DevToolsAgentHost* agent_host, |
| const std::string& message) override { |
| std::unique_ptr<base::DictionaryValue> root( |
| static_cast<base::DictionaryValue*>( |
| base::JSONReader::Read(message).release())); |
| int id; |
| if (root->GetInteger("id", &id)) { |
| result_ids_.push_back(id); |
| base::DictionaryValue* result; |
| ASSERT_TRUE(root->GetDictionary("result", &result)); |
| result_.reset(result->DeepCopy()); |
| in_dispatch_ = false; |
| if (id && id == waiting_for_command_result_id_) { |
| waiting_for_command_result_id_ = 0; |
| base::MessageLoop::current()->QuitNow(); |
| } |
| } else { |
| std::string notification; |
| EXPECT_TRUE(root->GetString("method", ¬ification)); |
| notifications_.push_back(notification); |
| base::DictionaryValue* params; |
| if (root->GetDictionary("params", ¶ms)) { |
| notification_params_.push_back(params->CreateDeepCopy()); |
| } else { |
| notification_params_.push_back( |
| base::WrapUnique(new base::DictionaryValue())); |
| } |
| if (waiting_for_notification_ == notification) { |
| waiting_for_notification_ = std::string(); |
| waiting_for_notification_params_ = base::WrapUnique( |
| notification_params_[notification_params_.size() - 1]->DeepCopy()); |
| base::MessageLoop::current()->QuitNow(); |
| } |
| } |
| } |
| |
| void AgentHostClosed(DevToolsAgentHost* agent_host, bool replaced) override { |
| if (!agent_host_can_close_) |
| NOTREACHED(); |
| } |
| |
| std::string waiting_for_notification_; |
| std::unique_ptr<base::DictionaryValue> waiting_for_notification_params_; |
| int waiting_for_command_result_id_; |
| bool in_dispatch_; |
| scoped_refptr<net::X509Certificate> last_shown_certificate_; |
| scoped_refptr<net::X509Certificate> ok_cert_; |
| scoped_refptr<net::X509Certificate> expired_cert_; |
| bool agent_host_can_close_; |
| }; |
| |
| class TestInterstitialDelegate : public InterstitialPageDelegate { |
| private: |
| // InterstitialPageDelegate: |
| std::string GetHTMLContents() override { return "<p>Interstitial</p>"; } |
| }; |
| |
| class SyntheticKeyEventTest : public DevToolsProtocolTest { |
| protected: |
| void SendKeyEvent(const std::string& type, |
| int modifier, |
| int windowsKeyCode, |
| int nativeKeyCode, |
| const std::string& key, |
| bool wait) { |
| std::unique_ptr<base::DictionaryValue> params(new base::DictionaryValue()); |
| params->SetString("type", type); |
| params->SetInteger("modifiers", modifier); |
| params->SetInteger("windowsVirtualKeyCode", windowsKeyCode); |
| params->SetInteger("nativeVirtualKeyCode", nativeKeyCode); |
| params->SetString("key", key); |
| SendCommand("Input.dispatchKeyEvent", std::move(params), wait); |
| } |
| }; |
| |
| class SyntheticMouseEventTest : public DevToolsProtocolTest { |
| protected: |
| void SendMouseEvent(const std::string& type, int x, int y, bool wait) { |
| std::unique_ptr<base::DictionaryValue> params(new base::DictionaryValue()); |
| params->SetString("type", type); |
| params->SetInteger("x", x); |
| params->SetInteger("y", y); |
| SendCommand("Input.dispatchMouseEvent", std::move(params), wait); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(SyntheticKeyEventTest, KeyEventSynthesizeKey) { |
| NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1); |
| Attach(); |
| ASSERT_TRUE(content::ExecuteScript( |
| shell()->web_contents()->GetRenderViewHost(), |
| "function handleKeyEvent(event) {" |
| "domAutomationController.setAutomationId(0);" |
| "domAutomationController.send(event.key);" |
| "}" |
| "document.body.addEventListener('keydown', handleKeyEvent);" |
| "document.body.addEventListener('keyup', handleKeyEvent);")); |
| |
| DOMMessageQueue dom_message_queue; |
| |
| // Send enter (keycode 13). |
| SendKeyEvent("rawKeyDown", 0, 13, 13, "Enter", true); |
| SendKeyEvent("keyUp", 0, 13, 13, "Enter", true); |
| |
| std::string key; |
| ASSERT_TRUE(dom_message_queue.WaitForMessage(&key)); |
| EXPECT_EQ("\"Enter\"", key); |
| ASSERT_TRUE(dom_message_queue.WaitForMessage(&key)); |
| EXPECT_EQ("\"Enter\"", key); |
| |
| // Send escape (keycode 27). |
| SendKeyEvent("rawKeyDown", 0, 27, 27, "Escape", true); |
| SendKeyEvent("keyUp", 0, 27, 27, "Escape", true); |
| |
| ASSERT_TRUE(dom_message_queue.WaitForMessage(&key)); |
| EXPECT_EQ("\"Escape\"", key); |
| ASSERT_TRUE(dom_message_queue.WaitForMessage(&key)); |
| EXPECT_EQ("\"Escape\"", key); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SyntheticKeyEventTest, KeyboardEventAck) { |
| NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1); |
| Attach(); |
| ASSERT_TRUE(content::ExecuteScript( |
| shell()->web_contents()->GetRenderViewHost(), |
| "document.body.addEventListener('keydown', () => console.log('x'));")); |
| |
| scoped_refptr<InputMsgWatcher> filter = new InputMsgWatcher( |
| RenderWidgetHostImpl::From( |
| shell()->web_contents()->GetRenderViewHost()->GetWidget()), |
| blink::WebInputEvent::kMouseMove); |
| |
| SendCommand("Runtime.enable", nullptr); |
| SendKeyEvent("rawKeyDown", 0, 13, 13, "Enter", false); |
| |
| // We expect that the console log message event arrives *before* the input |
| // event ack, and the subsequent command response for Input.dispatchKeyEvent. |
| WaitForNotification("Runtime.consoleAPICalled"); |
| EXPECT_THAT(console_messages_, ElementsAre("x")); |
| EXPECT_FALSE(filter->HasReceivedAck()); |
| EXPECT_EQ(1u, result_ids_.size()); |
| |
| WaitForResponse(); |
| EXPECT_EQ(2u, result_ids_.size()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SyntheticMouseEventTest, MouseEventAck) { |
| NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1); |
| Attach(); |
| ASSERT_TRUE(content::ExecuteScript( |
| shell()->web_contents()->GetRenderViewHost(), |
| "document.body.addEventListener('mousemove', () => console.log('x'));")); |
| |
| scoped_refptr<InputMsgWatcher> filter = new InputMsgWatcher( |
| RenderWidgetHostImpl::From( |
| shell()->web_contents()->GetRenderViewHost()->GetWidget()), |
| blink::WebInputEvent::kMouseMove); |
| |
| SendCommand("Runtime.enable", nullptr); |
| SendMouseEvent("mouseMoved", 15, 15, false); |
| |
| // We expect that the console log message event arrives *before* the input |
| // event ack, and the subsequent command response for |
| // Input.dispatchMouseEvent. |
| WaitForNotification("Runtime.consoleAPICalled"); |
| EXPECT_THAT(console_messages_, ElementsAre("x")); |
| EXPECT_FALSE(filter->HasReceivedAck()); |
| EXPECT_EQ(1u, result_ids_.size()); |
| |
| WaitForResponse(); |
| EXPECT_EQ(2u, result_ids_.size()); |
| } |
| |
| namespace { |
| bool DecodePNG(std::string base64_data, SkBitmap* bitmap) { |
| std::string png_data; |
| if (!base::Base64Decode(base64_data, &png_data)) |
| return false; |
| return gfx::PNGCodec::Decode( |
| reinterpret_cast<unsigned const char*>(png_data.data()), png_data.size(), |
| bitmap); |
| } |
| |
| std::unique_ptr<SkBitmap> DecodeJPEG(std::string base64_data) { |
| std::string jpeg_data; |
| if (!base::Base64Decode(base64_data, &jpeg_data)) |
| return nullptr; |
| return gfx::JPEGCodec::Decode( |
| reinterpret_cast<unsigned const char*>(jpeg_data.data()), |
| jpeg_data.size()); |
| } |
| |
| bool ColorsMatchWithinLimit(SkColor color1, |
| SkColor color2, |
| int32_t error_limit) { |
| auto a_distance = std::abs(static_cast<int32_t>(SkColorGetA(color1)) - |
| static_cast<int32_t>(SkColorGetA(color2))); |
| auto r_distance = std::abs(static_cast<int32_t>(SkColorGetR(color1)) - |
| static_cast<int32_t>(SkColorGetR(color2))); |
| auto g_distance = std::abs(static_cast<int32_t>(SkColorGetG(color1)) - |
| static_cast<int32_t>(SkColorGetG(color2))); |
| auto b_distance = std::abs(static_cast<int32_t>(SkColorGetB(color1)) - |
| static_cast<int32_t>(SkColorGetB(color2))); |
| |
| return a_distance * a_distance + r_distance * r_distance + |
| g_distance * g_distance + b_distance * b_distance <= |
| error_limit * error_limit; |
| } |
| |
| // Adapted from cc::ExactPixelComparator. |
| bool MatchesBitmap(const SkBitmap& expected_bmp, |
| const SkBitmap& actual_bmp, |
| const gfx::Rect& matching_mask, |
| int error_limit) { |
| // Number of pixels with an error |
| int error_pixels_count = 0; |
| |
| gfx::Rect error_bounding_rect = gfx::Rect(); |
| |
| // Check that bitmaps have identical dimensions. |
| EXPECT_EQ(expected_bmp.width(), actual_bmp.width()); |
| EXPECT_EQ(expected_bmp.height(), actual_bmp.height()); |
| if (expected_bmp.width() != actual_bmp.width() || |
| expected_bmp.height() != actual_bmp.height()) { |
| return false; |
| } |
| |
| DCHECK(gfx::SkIRectToRect(actual_bmp.bounds()).Contains(matching_mask)); |
| |
| for (int x = matching_mask.x(); x < matching_mask.right(); ++x) { |
| for (int y = matching_mask.y(); y < matching_mask.bottom(); ++y) { |
| SkColor actual_color = actual_bmp.getColor(x, y); |
| SkColor expected_color = expected_bmp.getColor(x, y); |
| if (!ColorsMatchWithinLimit(actual_color, expected_color, error_limit)) { |
| if (error_pixels_count < 10) { |
| LOG(ERROR) << "Pixel (" << x << "," << y << "): expected " |
| << expected_color << " actual " << actual_color; |
| } |
| error_pixels_count++; |
| error_bounding_rect.Union(gfx::Rect(x, y, 1, 1)); |
| } |
| } |
| } |
| |
| if (error_pixels_count != 0) { |
| LOG(ERROR) << "Number of pixel with an error: " << error_pixels_count; |
| LOG(ERROR) << "Error Bounding Box : " << error_bounding_rect.ToString(); |
| return false; |
| } |
| |
| return true; |
| } |
| } // namespace |
| |
| class CaptureScreenshotTest : public DevToolsProtocolTest { |
| protected: |
| enum ScreenshotEncoding { ENCODING_PNG, ENCODING_JPEG }; |
| void CaptureScreenshotAndCompareTo(const SkBitmap& expected_bitmap, |
| ScreenshotEncoding encoding, |
| bool fromSurface) { |
| std::unique_ptr<base::DictionaryValue> params(new base::DictionaryValue()); |
| params->SetString("format", encoding == ENCODING_PNG ? "png" : "jpeg"); |
| params->SetInteger("quality", 100); |
| params->SetBoolean("fromSurface", fromSurface); |
| SendCommand("Page.captureScreenshot", std::move(params)); |
| |
| std::string base64; |
| EXPECT_TRUE(result_->GetString("data", &base64)); |
| std::unique_ptr<SkBitmap> result_bitmap; |
| int error_limit = 0; |
| if (encoding == ENCODING_PNG) { |
| result_bitmap.reset(new SkBitmap()); |
| EXPECT_TRUE(DecodePNG(base64, result_bitmap.get())); |
| } else { |
| result_bitmap = DecodeJPEG(base64); |
| // Even with quality 100, jpeg isn't lossless. So, we allow some skew in |
| // pixel values. Not that this assumes that there is no skew in pixel |
| // positions, so will only work reliably if all pixels have equal values. |
| error_limit = 3; |
| } |
| EXPECT_TRUE(result_bitmap); |
| |
| gfx::Rect matching_mask(gfx::SkIRectToRect(expected_bitmap.bounds())); |
| #if defined(OS_MACOSX) |
| // Mask out the corners, which may be drawn differently on Mac because of |
| // rounded corners. |
| matching_mask.Inset(4, 4, 4, 4); |
| #endif |
| EXPECT_TRUE(MatchesBitmap(expected_bitmap, *result_bitmap, matching_mask, |
| error_limit)); |
| } |
| |
| // Takes a screenshot of a colored box that is positioned inside the frame. |
| void PlaceAndCaptureBox(const gfx::Size& frame_size, |
| const gfx::Size& box_size, |
| float screenshot_scale) { |
| static const int kBoxOffsetHeight = 100; |
| const gfx::Size scaled_box_size = |
| ScaleToFlooredSize(box_size, screenshot_scale); |
| std::unique_ptr<base::DictionaryValue> params; |
| |
| VLOG(1) << "Testing screenshot of box with size " << box_size.width() << "x" |
| << box_size.height() << "px at scale " << screenshot_scale |
| << " ..."; |
| |
| // Draw a blue box of provided size in the horizontal center of the page. |
| EXPECT_TRUE(content::ExecuteScript( |
| shell()->web_contents()->GetRenderViewHost(), |
| base::StringPrintf( |
| "var style = document.body.style; " |
| "style.overflow = 'hidden'; " |
| "style.minHeight = '%dpx'; " |
| "style.backgroundImage = 'linear-gradient(#0000ff, #0000ff)'; " |
| "style.backgroundSize = '%dpx %dpx'; " |
| "style.backgroundPosition = '50%% %dpx'; " |
| "style.backgroundRepeat = 'no-repeat'; ", |
| box_size.height() + kBoxOffsetHeight, box_size.width(), |
| box_size.height(), kBoxOffsetHeight))); |
| |
| // Force frame size: The offset of the blue box within the frame shouldn't |
| // change during screenshotting. This verifies that the page doesn't observe |
| // a change in frame size as a side effect of screenshotting. |
| params.reset(new base::DictionaryValue()); |
| params->SetInteger("width", frame_size.width()); |
| params->SetInteger("height", frame_size.height()); |
| params->SetDouble("deviceScaleFactor", 0); |
| params->SetBoolean("mobile", false); |
| params->SetBoolean("fitWindow", false); |
| SendCommand("Emulation.setDeviceMetricsOverride", std::move(params)); |
| |
| // Resize frame to scaled blue box size. |
| params.reset(new base::DictionaryValue()); |
| params->SetInteger("width", scaled_box_size.width()); |
| params->SetInteger("height", scaled_box_size.height()); |
| SendCommand("Emulation.setVisibleSize", std::move(params)); |
| |
| // Force viewport to match scaled blue box. |
| params.reset(new base::DictionaryValue()); |
| params->SetDouble("x", (frame_size.width() - box_size.width()) / 2.); |
| params->SetDouble("y", kBoxOffsetHeight); |
| params->SetDouble("scale", screenshot_scale); |
| SendCommand("Emulation.forceViewport", std::move(params)); |
| |
| // Capture screenshot and verify that it is indeed blue. |
| SkBitmap expected_bitmap; |
| expected_bitmap.allocN32Pixels(scaled_box_size.width(), |
| scaled_box_size.height()); |
| expected_bitmap.eraseColor(SkColorSetRGB(0x00, 0x00, 0xff)); |
| CaptureScreenshotAndCompareTo(expected_bitmap, ENCODING_PNG, true); |
| |
| // Reset for next screenshot. |
| SendCommand("Emulation.resetViewport", nullptr); |
| SendCommand("Emulation.clearDeviceMetricsOverride", nullptr); |
| } |
| |
| private: |
| #if !defined(OS_ANDROID) |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| command_line->AppendSwitch(switches::kEnablePixelOutputInTests); |
| } |
| #endif |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(CaptureScreenshotTest, CaptureScreenshot) { |
| // This test fails consistently on low-end Android devices. |
| // See crbug.com/653637. |
| // TODO(eseckler): Reenable with error limit if necessary. |
| if (base::SysInfo::IsLowEndDevice()) return; |
| |
| shell()->LoadURL( |
| GURL("data:text/html,<body style='background:#123456'></body>")); |
| WaitForLoadStop(shell()->web_contents()); |
| Attach(); |
| SkBitmap expected_bitmap; |
| // We compare against the actual physical backing size rather than the |
| // view size, because the view size is stored adjusted for DPI and only in |
| // integer precision. |
| gfx::Size view_size = static_cast<RenderWidgetHostViewBase*>( |
| shell()->web_contents()->GetRenderWidgetHostView()) |
| ->GetPhysicalBackingSize(); |
| expected_bitmap.allocN32Pixels(view_size.width(), view_size.height()); |
| expected_bitmap.eraseColor(SkColorSetRGB(0x12, 0x34, 0x56)); |
| CaptureScreenshotAndCompareTo(expected_bitmap, ENCODING_PNG, false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CaptureScreenshotTest, CaptureScreenshotJpeg) { |
| // This test fails consistently on low-end Android devices. |
| // See crbug.com/653637. |
| // TODO(eseckler): Reenable with error limit if necessary. |
| if (base::SysInfo::IsLowEndDevice()) |
| return; |
| |
| shell()->LoadURL( |
| GURL("data:text/html,<body style='background:#123456'></body>")); |
| WaitForLoadStop(shell()->web_contents()); |
| Attach(); |
| SkBitmap expected_bitmap; |
| // We compare against the actual physical backing size rather than the |
| // view size, because the view size is stored adjusted for DPI and only in |
| // integer precision. |
| gfx::Size view_size = static_cast<RenderWidgetHostViewBase*>( |
| shell()->web_contents()->GetRenderWidgetHostView()) |
| ->GetPhysicalBackingSize(); |
| expected_bitmap.allocN32Pixels(view_size.width(), view_size.height()); |
| expected_bitmap.eraseColor(SkColorSetRGB(0x12, 0x34, 0x56)); |
| CaptureScreenshotAndCompareTo(expected_bitmap, ENCODING_JPEG, false); |
| } |
| |
| // Setting frame size (through RWHV) is not supported on Android. |
| #if defined(OS_ANDROID) |
| #define MAYBE_CaptureScreenshotArea DISABLED_CaptureScreenshotArea |
| #else |
| #define MAYBE_CaptureScreenshotArea CaptureScreenshotArea |
| #endif |
| IN_PROC_BROWSER_TEST_F(CaptureScreenshotTest, |
| MAYBE_CaptureScreenshotArea) { |
| static const gfx::Size kFrameSize(800, 600); |
| |
| shell()->LoadURL(GURL("about:blank")); |
| Attach(); |
| |
| // Test capturing a subarea inside the emulated frame at different scales. |
| PlaceAndCaptureBox(kFrameSize, gfx::Size(100, 200), 1.0); |
| PlaceAndCaptureBox(kFrameSize, gfx::Size(100, 200), 2.0); |
| PlaceAndCaptureBox(kFrameSize, gfx::Size(100, 200), 0.5); |
| |
| // Ensure that content outside the emulated frame is painted, too. |
| PlaceAndCaptureBox(kFrameSize, gfx::Size(10, 8192), 1.0); |
| } |
| |
| // Verifies that setDefaultBackgroundColor and captureScreenshot support a |
| // transparent background, and that setDeviceMetricsOverride doesn't affect it. |
| IN_PROC_BROWSER_TEST_F(CaptureScreenshotTest, TransparentScreenshots) { |
| if (base::SysInfo::IsLowEndDevice()) |
| return; |
| |
| shell()->LoadURL( |
| GURL("data:text/html,<body style='background:transparent'></body>")); |
| WaitForLoadStop(shell()->web_contents()); |
| Attach(); |
| |
| // Override background to transparent. |
| std::unique_ptr<base::DictionaryValue> color(new base::DictionaryValue()); |
| color->SetInteger("r", 0); |
| color->SetInteger("g", 0); |
| color->SetInteger("b", 0); |
| color->SetDouble("a", 0); |
| std::unique_ptr<base::DictionaryValue> params(new base::DictionaryValue()); |
| params->Set("color", std::move(color)); |
| SendCommand("Emulation.setDefaultBackgroundColorOverride", std::move(params)); |
| |
| SkBitmap expected_bitmap; |
| // We compare against the actual physical backing size rather than the |
| // view size, because the view size is stored adjusted for DPI and only in |
| // integer precision. |
| gfx::Size view_size = static_cast<RenderWidgetHostViewBase*>( |
| shell()->web_contents()->GetRenderWidgetHostView()) |
| ->GetPhysicalBackingSize(); |
| expected_bitmap.allocN32Pixels(view_size.width(), view_size.height()); |
| expected_bitmap.eraseColor(SK_ColorTRANSPARENT); |
| CaptureScreenshotAndCompareTo(expected_bitmap, ENCODING_PNG, true); |
| |
| // Check that device emulation does not affect the transparency. |
| params.reset(new base::DictionaryValue()); |
| params->SetInteger("width", view_size.width()); |
| params->SetInteger("height", view_size.height()); |
| params->SetDouble("deviceScaleFactor", 0); |
| params->SetBoolean("mobile", false); |
| params->SetBoolean("fitWindow", false); |
| SendCommand("Emulation.setDeviceMetricsOverride", std::move(params)); |
| CaptureScreenshotAndCompareTo(expected_bitmap, ENCODING_PNG, true); |
| } |
| |
| #if defined(OS_ANDROID) |
| // Disabled, see http://crbug.com/469947. |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, DISABLED_SynthesizePinchGesture) { |
| GURL test_url = GetTestUrl("devtools", "synthetic_gesture_tests.html"); |
| NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 1); |
| Attach(); |
| |
| int old_width; |
| ASSERT_TRUE(content::ExecuteScriptAndExtractInt( |
| shell(), "domAutomationController.send(window.innerWidth)", &old_width)); |
| |
| int old_height; |
| ASSERT_TRUE(content::ExecuteScriptAndExtractInt( |
| shell(), "domAutomationController.send(window.innerHeight)", |
| &old_height)); |
| |
| std::unique_ptr<base::DictionaryValue> params(new base::DictionaryValue()); |
| params->SetInteger("x", old_width / 2); |
| params->SetInteger("y", old_height / 2); |
| params->SetDouble("scaleFactor", 2.0); |
| SendCommand("Input.synthesizePinchGesture", std::move(params)); |
| |
| int new_width; |
| ASSERT_TRUE(content::ExecuteScriptAndExtractInt( |
| shell(), "domAutomationController.send(window.innerWidth)", &new_width)); |
| ASSERT_DOUBLE_EQ(2.0, static_cast<double>(old_width) / new_width); |
| |
| int new_height; |
| ASSERT_TRUE(content::ExecuteScriptAndExtractInt( |
| shell(), "domAutomationController.send(window.innerHeight)", |
| &new_height)); |
| ASSERT_DOUBLE_EQ(2.0, static_cast<double>(old_height) / new_height); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, DISABLED_SynthesizeScrollGesture) { |
| GURL test_url = GetTestUrl("devtools", "synthetic_gesture_tests.html"); |
| NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 1); |
| Attach(); |
| |
| int scroll_top; |
| ASSERT_TRUE(content::ExecuteScriptAndExtractInt( |
| shell(), "domAutomationController.send(document.body.scrollTop)", |
| &scroll_top)); |
| ASSERT_EQ(0, scroll_top); |
| |
| std::unique_ptr<base::DictionaryValue> params(new base::DictionaryValue()); |
| params->SetInteger("x", 0); |
| params->SetInteger("y", 0); |
| params->SetInteger("xDistance", 0); |
| params->SetInteger("yDistance", -100); |
| SendCommand("Input.synthesizeScrollGesture", std::move(params)); |
| |
| ASSERT_TRUE(content::ExecuteScriptAndExtractInt( |
| shell(), "domAutomationController.send(document.body.scrollTop)", |
| &scroll_top)); |
| ASSERT_EQ(100, scroll_top); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, DISABLED_SynthesizeTapGesture) { |
| GURL test_url = GetTestUrl("devtools", "synthetic_gesture_tests.html"); |
| NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 1); |
| Attach(); |
| |
| int scroll_top; |
| ASSERT_TRUE(content::ExecuteScriptAndExtractInt( |
| shell(), "domAutomationController.send(document.body.scrollTop)", |
| &scroll_top)); |
| ASSERT_EQ(0, scroll_top); |
| |
| std::unique_ptr<base::DictionaryValue> params(new base::DictionaryValue()); |
| params->SetInteger("x", 16); |
| params->SetInteger("y", 16); |
| params->SetString("gestureSourceType", "touch"); |
| SendCommand("Input.synthesizeTapGesture", std::move(params)); |
| |
| // The link that we just tapped should take us to the bottom of the page. The |
| // new value of |document.body.scrollTop| will depend on the screen dimensions |
| // of the device that we're testing on, but in any case it should be greater |
| // than 0. |
| ASSERT_TRUE(content::ExecuteScriptAndExtractInt( |
| shell(), "domAutomationController.send(document.body.scrollTop)", |
| &scroll_top)); |
| ASSERT_GT(scroll_top, 0); |
| } |
| #endif // defined(OS_ANDROID) |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, NavigationPreservesMessages) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL test_url = embedded_test_server()->GetURL("/devtools/navigation.html"); |
| NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 1); |
| Attach(); |
| SendCommand("Page.enable", nullptr, false); |
| |
| std::unique_ptr<base::DictionaryValue> params(new base::DictionaryValue()); |
| test_url = GetTestUrl("devtools", "navigation.html"); |
| params->SetString("url", test_url.spec()); |
| TestNavigationObserver navigation_observer(shell()->web_contents()); |
| SendCommand("Page.navigate", std::move(params), true); |
| navigation_observer.Wait(); |
| |
| bool enough_results = result_ids_.size() >= 2u; |
| EXPECT_TRUE(enough_results); |
| if (enough_results) { |
| EXPECT_EQ(1, result_ids_[0]); // Page.enable |
| 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); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, CrossSiteNoDetach) { |
| content::SetupCrossSiteRedirector(embedded_test_server()); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| GURL test_url1 = embedded_test_server()->GetURL( |
| "A.com", "/devtools/navigation.html"); |
| NavigateToURLBlockUntilNavigationsComplete(shell(), test_url1, 1); |
| Attach(); |
| |
| GURL test_url2 = embedded_test_server()->GetURL( |
| "B.com", "/devtools/navigation.html"); |
| NavigateToURLBlockUntilNavigationsComplete(shell(), test_url2, 1); |
| |
| EXPECT_EQ(0u, notifications_.size()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, CrossSiteCrash) { |
| set_agent_host_can_close(); |
| content::SetupCrossSiteRedirector(embedded_test_server()); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| GURL test_url1 = |
| embedded_test_server()->GetURL("A.com", "/devtools/navigation.html"); |
| NavigateToURLBlockUntilNavigationsComplete(shell(), test_url1, 1); |
| Attach(); |
| CrashTab(shell()->web_contents()); |
| |
| GURL test_url2 = |
| embedded_test_server()->GetURL("B.com", "/devtools/navigation.html"); |
| NavigateToURLBlockUntilNavigationsComplete(shell(), test_url2, 1); |
| |
| // Should not crash at this point. |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, ReconnectPreservesState) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL test_url = embedded_test_server()->GetURL("/devtools/navigation.html"); |
| NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 1); |
| |
| Shell* second = CreateBrowser(); |
| NavigateToURLBlockUntilNavigationsComplete(second, test_url, 1); |
| |
| Attach(); |
| SendCommand("Runtime.enable", nullptr); |
| |
| agent_host_->DisconnectWebContents(); |
| agent_host_->ConnectWebContents(second->web_contents()); |
| WaitForNotification("Runtime.executionContextsCleared"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, CrossSitePauseInBeforeUnload) { |
| content::SetupCrossSiteRedirector(embedded_test_server()); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| NavigateToURLBlockUntilNavigationsComplete(shell(), |
| embedded_test_server()->GetURL("A.com", "/devtools/navigation.html"), 1); |
| Attach(); |
| SendCommand("Debugger.enable", nullptr); |
| |
| ASSERT_TRUE(content::ExecuteScript( |
| shell(), |
| "window.onbeforeunload = function() { debugger; return null; }")); |
| |
| shell()->LoadURL( |
| embedded_test_server()->GetURL("B.com", "/devtools/navigation.html")); |
| WaitForNotification("Debugger.paused"); |
| TestNavigationObserver observer(shell()->web_contents(), 1); |
| SendCommand("Debugger.resume", nullptr); |
| observer.Wait(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, InspectDuringFrameSwap) { |
| content::SetupCrossSiteRedirector(embedded_test_server()); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| GURL test_url1 = |
| embedded_test_server()->GetURL("A.com", "/devtools/navigation.html"); |
| NavigateToURLBlockUntilNavigationsComplete(shell(), test_url1, 1); |
| |
| ShellAddedObserver new_shell_observer; |
| EXPECT_TRUE(ExecuteScript(shell(), "window.open('about:blank','foo');")); |
| Shell* new_shell = new_shell_observer.GetShell(); |
| EXPECT_TRUE(new_shell->web_contents()->HasOpener()); |
| |
| agent_host_ = DevToolsAgentHost::GetOrCreateFor(new_shell->web_contents()); |
| agent_host_->AttachClient(this); |
| |
| GURL test_url2 = |
| embedded_test_server()->GetURL("B.com", "/devtools/navigation.html"); |
| |
| // After this navigation, if the bug exists, the process will crash. |
| NavigateToURLBlockUntilNavigationsComplete(new_shell, test_url2, 1); |
| |
| // Ensure that the A.com process is still alive by executing a script in the |
| // original tab. |
| // |
| // TODO(alexmos, nasko): A better way to do this is to navigate the original |
| // tab to another site, watch for process exit, and check whether there was a |
| // crash. However, currently there's no way to wait for process exit |
| // regardless of whether it's a crash or not. RenderProcessHostWatcher |
| // should be fixed to support waiting on both WATCH_FOR_PROCESS_EXIT and |
| // WATCH_FOR_HOST_DESTRUCTION, and then used here. |
| bool success = false; |
| EXPECT_TRUE(ExecuteScriptAndExtractBool(shell(), |
| "window.domAutomationController.send(" |
| " !!window.open('', 'foo'));", |
| &success)); |
| EXPECT_TRUE(success); |
| |
| GURL test_url3 = |
| embedded_test_server()->GetURL("A.com", "/devtools/navigation.html"); |
| |
| // After this navigation, if the bug exists, the process will crash. |
| NavigateToURLBlockUntilNavigationsComplete(new_shell, test_url3, 1); |
| |
| // Ensure that the A.com process is still alive by executing a script in the |
| // original tab. |
| success = false; |
| EXPECT_TRUE(ExecuteScriptAndExtractBool(shell(), |
| "window.domAutomationController.send(" |
| " !!window.open('', 'foo'));", |
| &success)); |
| EXPECT_TRUE(success); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, DoubleCrash) { |
| set_agent_host_can_close(); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL test_url = embedded_test_server()->GetURL("/devtools/navigation.html"); |
| NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1); |
| Attach(); |
| SendCommand("ServiceWorker.enable", nullptr); |
| NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 1); |
| CrashTab(shell()->web_contents()); |
| NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 1); |
| CrashTab(shell()->web_contents()); |
| NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1); |
| // Should not crash at this point. |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, ReloadBlankPage) { |
| Shell* window = Shell::CreateNewWindow( |
| shell()->web_contents()->GetBrowserContext(), |
| GURL("javascript:x=1"), |
| nullptr, |
| gfx::Size()); |
| WaitForLoadStop(window->web_contents()); |
| Attach(); |
| SendCommand("Page.reload", nullptr, false); |
| // Should not crash at this point. |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, EvaluateInBlankPage) { |
| NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1); |
| Attach(); |
| std::unique_ptr<base::DictionaryValue> params(new base::DictionaryValue()); |
| params->SetString("expression", "window"); |
| SendCommand("Runtime.evaluate", std::move(params), true); |
| EXPECT_FALSE(result_->HasKey("exceptionDetails")); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, |
| EvaluateInBlankPageAfterNavigation) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL test_url = embedded_test_server()->GetURL("/devtools/navigation.html"); |
| NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 1); |
| Attach(); |
| NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1); |
| std::unique_ptr<base::DictionaryValue> params(new base::DictionaryValue()); |
| params->SetString("expression", "window"); |
| SendCommand("Runtime.evaluate", std::move(params), true); |
| EXPECT_FALSE(result_->HasKey("exceptionDetails")); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, JavaScriptDialogNotifications) { |
| NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1); |
| Attach(); |
| TestJavaScriptDialogManager dialog_manager; |
| shell()->web_contents()->SetDelegate(&dialog_manager); |
| SendCommand("Page.enable", nullptr, true); |
| std::unique_ptr<base::DictionaryValue> params(new base::DictionaryValue()); |
| params->SetString("expression", "alert('alert')"); |
| SendCommand("Runtime.evaluate", std::move(params), false); |
| WaitForNotification("Page.javascriptDialogOpening"); |
| dialog_manager.Handle(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, BrowserCreateAndCloseTarget) { |
| NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1); |
| Attach(); |
| EXPECT_EQ(1u, shell()->windows().size()); |
| std::unique_ptr<base::DictionaryValue> params(new base::DictionaryValue()); |
| params->SetString("url", "about:blank"); |
| SendCommand("Target.createTarget", std::move(params), true); |
| std::string target_id; |
| EXPECT_TRUE(result_->GetString("targetId", &target_id)); |
| EXPECT_EQ(2u, shell()->windows().size()); |
| |
| // TODO(eseckler): Since the RenderView is closed asynchronously, we currently |
| // don't verify that the command actually closes the shell. |
| bool success; |
| params.reset(new base::DictionaryValue()); |
| params->SetString("targetId", target_id); |
| SendCommand("Target.closeTarget", std::move(params), true); |
| EXPECT_TRUE(result_->GetBoolean("success", &success)); |
| EXPECT_TRUE(success); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, BrowserGetTargets) { |
| NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1); |
| Attach(); |
| SendCommand("Target.getTargets", nullptr, true); |
| base::ListValue* target_infos; |
| EXPECT_TRUE(result_->GetList("targetInfos", &target_infos)); |
| EXPECT_EQ(1u, target_infos->GetSize()); |
| base::DictionaryValue* target_info; |
| EXPECT_TRUE(target_infos->GetDictionary(0u, &target_info)); |
| std::string target_id, type, title, url; |
| EXPECT_TRUE(target_info->GetString("targetId", &target_id)); |
| EXPECT_TRUE(target_info->GetString("type", &type)); |
| EXPECT_TRUE(target_info->GetString("title", &title)); |
| EXPECT_TRUE(target_info->GetString("url", &url)); |
| EXPECT_EQ("page", type); |
| EXPECT_EQ("about:blank", title); |
| EXPECT_EQ("about:blank", url); |
| } |
| |
| namespace { |
| class NavigationFinishedObserver : public content::WebContentsObserver { |
| public: |
| explicit NavigationFinishedObserver(WebContents* web_contents) |
| : WebContentsObserver(web_contents), |
| num_finished_(0), |
| num_to_wait_for_(0) {} |
| |
| ~NavigationFinishedObserver() override {} |
| |
| void DidFinishNavigation( |
| content::NavigationHandle* navigation_handle) override { |
| if (navigation_handle->WasServerRedirect()) |
| return; |
| |
| num_finished_++; |
| if (num_finished_ >= num_to_wait_for_ && num_to_wait_for_ != 0) { |
| base::MessageLoop::current()->QuitNow(); |
| } |
| } |
| |
| void WaitForNavigationsToFinish(int num_to_wait_for) { |
| if (num_finished_ < num_to_wait_for) { |
| num_to_wait_for_ = num_to_wait_for; |
| RunMessageLoop(); |
| } |
| num_to_wait_for_ = 0; |
| } |
| |
| private: |
| int num_finished_; |
| int num_to_wait_for_; |
| }; |
| |
| class LoadFinishedObserver : public content::WebContentsObserver { |
| public: |
| explicit LoadFinishedObserver(WebContents* web_contents) |
| : WebContentsObserver(web_contents), num_finished_(0) {} |
| |
| ~LoadFinishedObserver() override {} |
| |
| void DidStopLoading() override { |
| num_finished_++; |
| if (run_loop_.running()) |
| run_loop_.Quit(); |
| } |
| |
| void WaitForLoadToFinish() { |
| if (num_finished_ == 0) |
| run_loop_.Run(); |
| } |
| |
| private: |
| int num_finished_; |
| base::RunLoop run_loop_; |
| }; |
| |
| } // namespace |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, PageStopLoading) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // Navigate to about:blank first so we can make sure there is a target page we |
| // can attach to, and have Page.setControlNavigations complete before we start |
| // the navigations we're interested in. |
| NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1); |
| Attach(); |
| |
| std::unique_ptr<base::DictionaryValue> params(new base::DictionaryValue()); |
| params->SetBoolean("enabled", true); |
| SendCommand("Page.setControlNavigations", std::move(params), true); |
| |
| LoadFinishedObserver load_finished_observer(shell()->web_contents()); |
| |
| // The page will try to navigate twice, however since |
| // Page.setControlNavigations is true, it'll wait for confirmation before |
| // committing to the navigation. |
| GURL test_url = embedded_test_server()->GetURL( |
| "/devtools/control_navigations/meta_tag.html"); |
| shell()->LoadURL(test_url); |
| |
| // Stop all navigations. |
| SendCommand("Page.stopLoading", nullptr); |
| |
| // Wait for the initial navigation to finish. |
| load_finished_observer.WaitForLoadToFinish(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, ControlNavigationsMainFrame) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // Navigate to about:blank first so we can make sure there is a target page we |
| // can attach to, and have Page.setControlNavigations complete before we start |
| // the navigations we're interested in. |
| NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1); |
| Attach(); |
| |
| std::unique_ptr<base::DictionaryValue> params(new base::DictionaryValue()); |
| params->SetBoolean("enabled", true); |
| SendCommand("Page.setControlNavigations", std::move(params), true); |
| |
| NavigationFinishedObserver navigation_finished_observer( |
| shell()->web_contents()); |
| |
| 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/devtools/control_navigations/meta_tag.html", |
| true /* expected_is_in_main_frame */, false /* expected_is_redirect */, |
| "Proceed"}, |
| {"http://127.0.0.1/devtools/navigation.html", |
| true /* expected_is_in_main_frame */, false /* expected_is_redirect */, |
| "Cancel"}}; |
| |
| ProcessNavigationsAnyOrder(std::move(expected_navigations)); |
| |
| // Wait for the initial navigation and the cancelled meta refresh navigation |
| // to finish. |
| navigation_finished_observer.WaitForNavigationsToFinish(2); |
| |
| // Check main frame has the expected url. |
| EXPECT_EQ( |
| "http://127.0.0.1/devtools/control_navigations/meta_tag.html", |
| RemovePort( |
| shell()->web_contents()->GetMainFrame()->GetLastCommittedURL())); |
| } |
| |
| class IsolatedDevToolsProtocolTest : public DevToolsProtocolTest { |
| public: |
| ~IsolatedDevToolsProtocolTest() override {} |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| IsolateAllSitesForTesting(command_line); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(IsolatedDevToolsProtocolTest, |
| ControlNavigationsChildFrames) { |
| content::SetupCrossSiteRedirector(embedded_test_server()); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // Navigate to about:blank first so we can make sure there is a target page we |
| // can attach to, and have Page.setControlNavigations complete before we start |
| // the navigations we're interested in. |
| NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1); |
| Attach(); |
| |
| std::unique_ptr<base::DictionaryValue> params(new base::DictionaryValue()); |
| params->SetBoolean("enabled", true); |
| SendCommand("Page.setControlNavigations", std::move(params), true); |
| |
| NavigationFinishedObserver navigation_finished_observer( |
| shell()->web_contents()); |
| |
| 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/devtools/control_navigations/" |
| "iframe_navigation.html", |
| /* expected_is_in_main_frame */ true, |
| /* expected_is_redirect */ false, "Proceed"}, |
| {"http://127.0.0.1/cross-site/a.com/devtools/control_navigations/" |
| "meta_tag.html", |
| /* expected_is_in_main_frame */ false, |
| /* expected_is_redirect */ false, "Proceed"}, |
| {"http://127.0.0.1/cross-site/b.com/devtools/control_navigations/" |
| "meta_tag.html", |
| /* expected_is_in_main_frame */ false, |
| /* expected_is_redirect */ false, "Proceed"}, |
| {"http://a.com/devtools/control_navigations/meta_tag.html", |
| /* expected_is_in_main_frame */ false, |
| /* expected_is_redirect */ true, "Proceed"}, |
| {"http://b.com/devtools/control_navigations/meta_tag.html", |
| /* expected_is_in_main_frame */ false, |
| /* expected_is_redirect */ true, "Proceed"}, |
| {"http://a.com/devtools/navigation.html", |
| /* expected_is_in_main_frame */ false, |
| /* expected_is_redirect */ false, "Proceed"}, |
| {"http://b.com/devtools/navigation.html", |
| /* expected_is_in_main_frame */ false, |
| /* expected_is_redirect */ false, "Cancel"}}; |
| |
| ProcessNavigationsAnyOrder(std::move(expected_navigations)); |
| |
| // Wait for each frame's navigation to finish, ignoring redirects. |
| navigation_finished_observer.WaitForNavigationsToFinish(3); |
| |
| // Make sure each frame has the expected url. |
| EXPECT_THAT( |
| GetAllFrameUrls(), |
| ElementsAre("http://127.0.0.1/devtools/control_navigations/" |
| "iframe_navigation.html", |
| "http://a.com/devtools/navigation.html", |
| "http://b.com/devtools/control_navigations/meta_tag.html")); |
| } |
| |
| // Setting RWHV size is not supported on Android. |
| #if defined(OS_ANDROID) |
| #define MAYBE_EmulationSetVisibleSize DISABLED_EmulationSetVisibleSize |
| #else |
| #define MAYBE_EmulationSetVisibleSize EmulationSetVisibleSize |
| #endif |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, |
| MAYBE_EmulationSetVisibleSize) { |
| NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1); |
| Attach(); |
| gfx::Size new_size(200, 400); |
| std::unique_ptr<base::DictionaryValue> params(new base::DictionaryValue()); |
| params->SetInteger("width", new_size.width()); |
| params->SetInteger("height", new_size.height()); |
| SendCommand("Emulation.setVisibleSize", std::move(params), true); |
| EXPECT_SIZE_EQ(new_size, (shell()->web_contents()) |
| ->GetRenderWidgetHostView() |
| ->GetViewBounds() |
| .size()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, VirtualTimeTest) { |
| NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1); |
| Attach(); |
| |
| std::unique_ptr<base::DictionaryValue> params(new base::DictionaryValue()); |
| params->SetString("policy", "pause"); |
| SendCommand("Emulation.setVirtualTimePolicy", std::move(params), true); |
| |
| params.reset(new base::DictionaryValue()); |
| params->SetString("expression", |
| "setTimeout(function(){console.log('before')}, 999);" |
| "setTimeout(function(){console.log('at')}, 1000);" |
| "setTimeout(function(){console.log('after')}, 1001);"); |
| SendCommand("Runtime.evaluate", std::move(params), true); |
| |
| // Let virtual time advance for one second. |
| params.reset(new base::DictionaryValue()); |
| params->SetString("policy", "advance"); |
| params->SetInteger("budget", 1000); |
| SendCommand("Emulation.setVirtualTimePolicy", std::move(params), true); |
| |
| WaitForNotification("Emulation.virtualTimeBudgetExpired"); |
| |
| params.reset(new base::DictionaryValue()); |
| params->SetString("expression", "console.log('done')"); |
| SendCommand("Runtime.evaluate", std::move(params), true); |
| |
| // The third timer should not fire. |
| EXPECT_THAT(console_messages_, ElementsAre("before", "at", "done")); |
| |
| // Let virtual time advance for another second, which should make the third |
| // timer fire. |
| params.reset(new base::DictionaryValue()); |
| params->SetString("policy", "advance"); |
| params->SetInteger("budget", 1000); |
| SendCommand("Emulation.setVirtualTimePolicy", std::move(params), true); |
| |
| WaitForNotification("Emulation.virtualTimeBudgetExpired"); |
| |
| EXPECT_THAT(console_messages_, ElementsAre("before", "at", "done", "after")); |
| } |
| |
| // Tests that the Security.showCertificateViewer command shows the |
| // certificate corresponding to the visible navigation entry, even when |
| // an interstitial is showing. Regression test for |
| // https://crbug.com/647759. |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, ShowCertificateViewer) { |
| // First test that the correct certificate is shown for a normal |
| // (non-interstitial) page. |
| NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1); |
| Attach(); |
| |
| // Set a dummy certificate on the NavigationEntry. |
| shell() |
| ->web_contents() |
| ->GetController() |
| .GetVisibleEntry() |
| ->GetSSL() |
| .certificate = ok_cert(); |
| |
| std::unique_ptr<base::DictionaryValue> params1(new base::DictionaryValue()); |
| SendCommand("Security.showCertificateViewer", std::move(params1), true); |
| |
| scoped_refptr<net::X509Certificate> normal_page_cert = shell() |
| ->web_contents() |
| ->GetController() |
| .GetVisibleEntry() |
| ->GetSSL() |
| .certificate; |
| ASSERT_TRUE(normal_page_cert); |
| EXPECT_EQ(normal_page_cert, last_shown_certificate()); |
| |
| // Now test that the correct certificate is shown on an interstitial. |
| TestInterstitialDelegate* delegate = new TestInterstitialDelegate; |
| WebContentsImpl* web_contents = |
| static_cast<WebContentsImpl*>(shell()->web_contents()); |
| GURL interstitial_url("https://example.test"); |
| InterstitialPageImpl* interstitial = new InterstitialPageImpl( |
| web_contents, static_cast<RenderWidgetHostDelegate*>(web_contents), true, |
| interstitial_url, delegate); |
| interstitial->Show(); |
| WaitForInterstitialAttach(web_contents); |
| |
| // Set the transient navigation entry certificate. |
| NavigationEntry* transient_entry = |
| web_contents->GetController().GetTransientEntry(); |
| ASSERT_TRUE(transient_entry); |
| transient_entry->GetSSL().certificate = expired_cert(); |
| ASSERT_TRUE(transient_entry->GetSSL().certificate); |
| |
| std::unique_ptr<base::DictionaryValue> params2(new base::DictionaryValue()); |
| SendCommand("Security.showCertificateViewer", std::move(params2), true); |
| EXPECT_EQ(transient_entry->GetSSL().certificate, last_shown_certificate()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, CertificateError) { |
| net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); |
| https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_EXPIRED); |
| https_server.ServeFilesFromSourceDirectory("content/test/data"); |
| ASSERT_TRUE(https_server.Start()); |
| GURL test_url = https_server.GetURL("/devtools/navigation.html"); |
| std::unique_ptr<base::DictionaryValue> params; |
| std::unique_ptr<base::DictionaryValue> command_params; |
| int eventId; |
| |
| shell()->LoadURL(GURL("about:blank")); |
| WaitForLoadStop(shell()->web_contents()); |
| |
| Attach(); |
| SendCommand("Network.enable", nullptr, true); |
| SendCommand("Security.enable", nullptr, false); |
| command_params.reset(new base::DictionaryValue()); |
| command_params->SetBoolean("override", true); |
| SendCommand("Security.setOverrideCertificateErrors", |
| std::move(command_params), true); |
| |
| // Test cancel. |
| SendCommand("Network.clearBrowserCache", nullptr, true); |
| SendCommand("Network.clearBrowserCookies", nullptr, true); |
| TestNavigationObserver cancel_observer(shell()->web_contents(), 1); |
| shell()->LoadURL(test_url); |
| params = WaitForNotification("Security.certificateError", false); |
| EXPECT_TRUE(shell()->web_contents()->GetController().GetPendingEntry()); |
| EXPECT_EQ( |
| test_url, |
| shell()->web_contents()->GetController().GetPendingEntry()->GetURL()); |
| EXPECT_TRUE(params->GetInteger("eventId", &eventId)); |
| command_params.reset(new base::DictionaryValue()); |
| command_params->SetInteger("eventId", eventId); |
| command_params->SetString("action", "cancel"); |
| SendCommand("Security.handleCertificateError", std::move(command_params), |
| false); |
| cancel_observer.Wait(); |
| EXPECT_FALSE(shell()->web_contents()->GetController().GetPendingEntry()); |
| EXPECT_EQ(GURL("about:blank"), shell() |
| ->web_contents() |
| ->GetController() |
| .GetLastCommittedEntry() |
| ->GetURL()); |
| |
| // Test continue. |
| SendCommand("Network.clearBrowserCache", nullptr, true); |
| SendCommand("Network.clearBrowserCookies", nullptr, true); |
| TestNavigationObserver continue_observer(shell()->web_contents(), 1); |
| shell()->LoadURL(test_url); |
| params = WaitForNotification("Security.certificateError", false); |
| EXPECT_TRUE(params->GetInteger("eventId", &eventId)); |
| command_params.reset(new base::DictionaryValue()); |
| command_params->SetInteger("eventId", eventId); |
| command_params->SetString("action", "continue"); |
| SendCommand("Security.handleCertificateError", std::move(command_params), |
| false); |
| WaitForNotification("Network.loadingFinished", true); |
| continue_observer.Wait(); |
| EXPECT_EQ(test_url, shell() |
| ->web_contents() |
| ->GetController() |
| .GetLastCommittedEntry() |
| ->GetURL()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, SubresourceWithCertificateError) { |
| net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); |
| https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_EXPIRED); |
| https_server.ServeFilesFromSourceDirectory("content/test/data/devtools"); |
| ASSERT_TRUE(https_server.Start()); |
| GURL test_url = https_server.GetURL("/image.html"); |
| std::unique_ptr<base::DictionaryValue> params; |
| std::unique_ptr<base::DictionaryValue> command_params; |
| int eventId; |
| |
| shell()->LoadURL(GURL("about:blank")); |
| WaitForLoadStop(shell()->web_contents()); |
| |
| Attach(); |
| SendCommand("Security.enable", nullptr, false); |
| command_params.reset(new base::DictionaryValue()); |
| command_params->SetBoolean("override", true); |
| SendCommand("Security.setOverrideCertificateErrors", |
| std::move(command_params), true); |
| |
| TestNavigationObserver observer(shell()->web_contents(), 1); |
| shell()->LoadURL(test_url); |
| |
| // Expect certificateError event for main frame. |
| params = WaitForNotification("Security.certificateError", false); |
| EXPECT_TRUE(params->GetInteger("eventId", &eventId)); |
| command_params.reset(new base::DictionaryValue()); |
| command_params->SetInteger("eventId", eventId); |
| command_params->SetString("action", "continue"); |
| SendCommand("Security.handleCertificateError", std::move(command_params), |
| false); |
| |
| // Expect certificateError event for image. |
| params = WaitForNotification("Security.certificateError", false); |
| EXPECT_TRUE(params->GetInteger("eventId", &eventId)); |
| command_params.reset(new base::DictionaryValue()); |
| command_params->SetInteger("eventId", eventId); |
| command_params->SetString("action", "continue"); |
| SendCommand("Security.handleCertificateError", std::move(command_params), |
| false); |
| |
| observer.Wait(); |
| EXPECT_EQ(test_url, shell() |
| ->web_contents() |
| ->GetController() |
| .GetLastCommittedEntry() |
| ->GetURL()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, TargetDiscovery) { |
| std::string temp; |
| std::set<std::string> ids; |
| std::unique_ptr<base::DictionaryValue> command_params; |
| std::unique_ptr<base::DictionaryValue> params; |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL first_url = embedded_test_server()->GetURL("/devtools/navigation.html"); |
| NavigateToURLBlockUntilNavigationsComplete(shell(), first_url, 1); |
| |
| GURL second_url = embedded_test_server()->GetURL("/devtools/navigation.html"); |
| Shell* second = CreateBrowser(); |
| NavigateToURLBlockUntilNavigationsComplete(second, second_url, 1); |
| |
| Attach(); |
| command_params.reset(new base::DictionaryValue()); |
| command_params->SetBoolean("discover", true); |
| SendCommand("Target.setDiscoverTargets", std::move(command_params), true); |
| params = WaitForNotification("Target.targetCreated", true); |
| EXPECT_TRUE(params->GetString("targetInfo.type", &temp)); |
| EXPECT_EQ("page", temp); |
| EXPECT_TRUE(params->GetString("targetInfo.targetId", &temp)); |
| EXPECT_TRUE(ids.find(temp) == ids.end()); |
| ids.insert(temp); |
| params = WaitForNotification("Target.targetCreated", true); |
| EXPECT_TRUE(params->GetString("targetInfo.type", &temp)); |
| EXPECT_EQ("page", temp); |
| EXPECT_TRUE(params->GetString("targetInfo.targetId", &temp)); |
| EXPECT_TRUE(ids.find(temp) == ids.end()); |
| ids.insert(temp); |
| EXPECT_TRUE(notifications_.empty()); |
| |
| GURL third_url = embedded_test_server()->GetURL("/devtools/navigation.html"); |
| Shell* third = CreateBrowser(); |
| NavigateToURLBlockUntilNavigationsComplete(third, third_url, 1); |
| params = WaitForNotification("Target.targetCreated", true); |
| EXPECT_TRUE(params->GetString("targetInfo.type", &temp)); |
| EXPECT_EQ("page", temp); |
| EXPECT_TRUE(params->GetString("targetInfo.targetId", &temp)); |
| EXPECT_TRUE(ids.find(temp) == ids.end()); |
| std::string attached_id = temp; |
| ids.insert(temp); |
| EXPECT_TRUE(notifications_.empty()); |
| |
| second->Close(); |
| second = nullptr; |
| params = WaitForNotification("Target.targetDestroyed", true); |
| EXPECT_TRUE(params->GetString("targetId", &temp)); |
| EXPECT_TRUE(ids.find(temp) != ids.end()); |
| ids.erase(temp); |
| EXPECT_TRUE(notifications_.empty()); |
| |
| command_params.reset(new base::DictionaryValue()); |
| command_params->SetString("targetId", attached_id); |
| SendCommand("Target.attachToTarget", std::move(command_params), true); |
| params = WaitForNotification("Target.attachedToTarget", true); |
| EXPECT_TRUE(params->GetString("targetInfo.targetId", &temp)); |
| EXPECT_EQ(attached_id, temp); |
| EXPECT_TRUE(notifications_.empty()); |
| |
| command_params.reset(new base::DictionaryValue()); |
| command_params->SetBoolean("discover", false); |
| SendCommand("Target.setDiscoverTargets", std::move(command_params), true); |
| params = WaitForNotification("Target.targetDestroyed", true); |
| EXPECT_TRUE(params->GetString("targetId", &temp)); |
| EXPECT_TRUE(ids.find(temp) != ids.end()); |
| ids.erase(temp); |
| params = WaitForNotification("Target.targetDestroyed", true); |
| EXPECT_TRUE(params->GetString("targetId", &temp)); |
| EXPECT_TRUE(ids.find(temp) != ids.end()); |
| ids.erase(temp); |
| EXPECT_TRUE(notifications_.empty()); |
| |
| command_params.reset(new base::DictionaryValue()); |
| command_params->SetString("targetId", attached_id); |
| SendCommand("Target.detachFromTarget", std::move(command_params), true); |
| params = WaitForNotification("Target.detachedFromTarget", true); |
| EXPECT_TRUE(params->GetString("targetId", &temp)); |
| EXPECT_EQ(attached_id, temp); |
| EXPECT_TRUE(notifications_.empty()); |
| } |
| |
| // Tests that an interstitialShown event is sent when an interstitial is showing |
| // on attach. |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, InterstitialShownOnAttach) { |
| TestInterstitialDelegate* delegate = new TestInterstitialDelegate; |
| WebContentsImpl* web_contents = |
| static_cast<WebContentsImpl*>(shell()->web_contents()); |
| GURL interstitial_url("https://example.test"); |
| InterstitialPageImpl* interstitial = new InterstitialPageImpl( |
| web_contents, static_cast<RenderWidgetHostDelegate*>(web_contents), true, |
| interstitial_url, delegate); |
| interstitial->Show(); |
| WaitForInterstitialAttach(web_contents); |
| Attach(); |
| SendCommand("Page.enable", nullptr, false); |
| WaitForNotification("Page.interstitialShown", true); |
| } |
| |
| class SitePerProcessDevToolsProtocolTest : public DevToolsProtocolTest { |
| public: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| DevToolsProtocolTest::SetUpCommandLine(command_line); |
| IsolateAllSitesForTesting(command_line); |
| }; |
| |
| void SetUpOnMainThread() override { |
| DevToolsProtocolTest::SetUpOnMainThread(); |
| content::SetupCrossSiteRedirector(embedded_test_server()); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(SitePerProcessDevToolsProtocolTest, TargetNoDiscovery) { |
| std::string temp; |
| std::string target_id; |
| std::unique_ptr<base::DictionaryValue> command_params; |
| std::unique_ptr<base::DictionaryValue> params; |
| |
| GURL main_url(embedded_test_server()->GetURL("/site_per_process_main.html")); |
| NavigateToURLBlockUntilNavigationsComplete(shell(), main_url, 1); |
| // It is safe to obtain the root frame tree node here, as it doesn't change. |
| FrameTreeNode* root = |
| static_cast<WebContentsImpl*>(shell()->web_contents())-> |
| GetFrameTree()->root(); |
| |
| // Load cross-site page into iframe. |
| GURL::Replacements replace_host; |
| GURL cross_site_url(embedded_test_server()->GetURL("/title1.html")); |
| replace_host.SetHostStr("foo.com"); |
| cross_site_url = cross_site_url.ReplaceComponents(replace_host); |
| NavigateFrameToURL(root->child_at(0), cross_site_url); |
| |
| // Enable auto-attach. |
| Attach(); |
| command_params.reset(new base::DictionaryValue()); |
| command_params->SetBoolean("autoAttach", true); |
| command_params->SetBoolean("waitForDebuggerOnStart", true); |
| SendCommand("Target.setAutoAttach", std::move(command_params), true); |
| EXPECT_TRUE(notifications_.empty()); |
| command_params.reset(new base::DictionaryValue()); |
| command_params->SetBoolean("value", true); |
| SendCommand("Target.setAttachToFrames", std::move(command_params), false); |
| params = WaitForNotification("Target.attachedToTarget", true); |
| EXPECT_TRUE(params->GetString("targetInfo.targetId", &target_id)); |
| EXPECT_TRUE(params->GetString("targetInfo.type", &temp)); |
| EXPECT_EQ("iframe", temp); |
| |
| // Load same-site page into iframe. |
| FrameTreeNode* child = root->child_at(0); |
| GURL http_url(embedded_test_server()->GetURL("/title1.html")); |
| NavigateFrameToURL(child, http_url); |
| params = WaitForNotification("Target.detachedFromTarget", true); |
| EXPECT_TRUE(params->GetString("targetId", &temp)); |
| EXPECT_EQ(target_id, temp); |
| |
| // Navigate back to cross-site iframe. |
| NavigateFrameToURL(root->child_at(0), cross_site_url); |
| params = WaitForNotification("Target.attachedToTarget", true); |
| EXPECT_TRUE(params->GetString("targetInfo.targetId", &target_id)); |
| EXPECT_TRUE(params->GetString("targetInfo.type", &temp)); |
| EXPECT_EQ("iframe", temp); |
| |
| // Disable auto-attach. |
| command_params.reset(new base::DictionaryValue()); |
| command_params->SetBoolean("autoAttach", false); |
| command_params->SetBoolean("waitForDebuggerOnStart", false); |
| SendCommand("Target.setAutoAttach", std::move(command_params), false); |
| params = WaitForNotification("Target.detachedFromTarget", true); |
| EXPECT_TRUE(params->GetString("targetId", &temp)); |
| EXPECT_EQ(target_id, temp); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, SetAndGetCookies) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL test_url = embedded_test_server()->GetURL("/title1.html"); |
| NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 1); |
| Attach(); |
| |
| // Set two cookies, one of which matches the loaded URL and another that |
| // doesn't. |
| std::unique_ptr<base::DictionaryValue> command_params; |
| command_params.reset(new base::DictionaryValue()); |
| command_params->SetString("url", test_url.spec()); |
| command_params->SetString("name", "cookie_for_this_url"); |
| command_params->SetString("value", "mendacious"); |
| SendCommand("Network.setCookie", std::move(command_params), false); |
| |
| command_params.reset(new base::DictionaryValue()); |
| command_params->SetString("url", "https://www.chromium.org"); |
| command_params->SetString("name", "cookie_for_another_url"); |
| command_params->SetString("value", "polyglottal"); |
| SendCommand("Network.setCookie", std::move(command_params), false); |
| |
| // First get the cookies for just the loaded URL. |
| SendCommand("Network.getCookies", nullptr, true); |
| |
| base::ListValue* cookies; |
| EXPECT_TRUE(result_->HasKey("cookies")); |
| EXPECT_TRUE(result_->GetList("cookies", &cookies)); |
| EXPECT_EQ(1u, cookies->GetSize()); |
| |
| base::DictionaryValue* cookie; |
| std::string name; |
| std::string value; |
| EXPECT_TRUE(cookies->GetDictionary(0, &cookie)); |
| EXPECT_TRUE(cookie->GetString("name", &name)); |
| EXPECT_TRUE(cookie->GetString("value", &value)); |
| EXPECT_EQ("cookie_for_this_url", name); |
| EXPECT_EQ("mendacious", value); |
| |
| // Then get all the cookies in the cookie jar. |
| SendCommand("Network.getAllCookies", nullptr, true); |
| |
| EXPECT_TRUE(result_->HasKey("cookies")); |
| EXPECT_TRUE(result_->GetList("cookies", &cookies)); |
| EXPECT_EQ(2u, cookies->GetSize()); |
| |
| // Note: the cookies will be returned in unspecified order. |
| size_t found = 0; |
| for (size_t i = 0; i < cookies->GetSize(); i++) { |
| EXPECT_TRUE(cookies->GetDictionary(i, &cookie)); |
| EXPECT_TRUE(cookie->GetString("name", &name)); |
| if (name == "cookie_for_this_url") { |
| EXPECT_TRUE(cookie->GetString("value", &value)); |
| EXPECT_EQ("mendacious", value); |
| found++; |
| } else if (name == "cookie_for_another_url") { |
| EXPECT_TRUE(cookie->GetString("value", &value)); |
| EXPECT_EQ("polyglottal", value); |
| found++; |
| } else { |
| FAIL(); |
| } |
| } |
| EXPECT_EQ(2u, found); |
| } |
| |
| } // namespace content |