| // Copyright 2016 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include "base/base64.h" |
| #include "base/check_op.h" |
| #include "base/command_line.h" |
| #include "base/functional/bind.h" |
| #include "base/json/json_writer.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/strings/string_util.h" |
| #include "build/build_config.h" |
| #include "cc/base/switches.h" |
| #include "cc/test/pixel_test_utils.h" |
| #include "components/devtools/simple_devtools_protocol_client/simple_devtools_protocol_client.h" |
| #include "components/viz/common/switches.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/test/browser_test.h" |
| #include "headless/app/headless_shell_switches.h" |
| #include "headless/lib/browser/headless_web_contents_impl.h" |
| #include "headless/public/headless_browser.h" |
| #include "headless/public/headless_web_contents.h" |
| #include "headless/test/headless_browser_test.h" |
| #include "headless/test/headless_browser_test_utils.h" |
| #include "headless/test/headless_devtooled_browsertest.h" |
| #include "headless/test/test_network_interceptor.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "third_party/blink/public/common/switches.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| #include "third_party/skia/include/core/SkColor.h" |
| #include "ui/gfx/codec/png_codec.h" |
| #include "ui/gfx/geometry/size.h" |
| #include "ui/gfx/geometry/size_f.h" |
| #include "url/gurl.h" |
| |
| using testing::ElementsAre; |
| using testing::ElementsAreArray; |
| using testing::Not; |
| using testing::UnorderedElementsAre; |
| using testing::UnorderedElementsAreArray; |
| |
| using simple_devtools_protocol_client::SimpleDevToolsProtocolClient; |
| |
| namespace headless { |
| |
| class HeadlessWebContentsTest : public HeadlessBrowserTest {}; |
| |
| IN_PROC_BROWSER_TEST_F(HeadlessWebContentsTest, Navigation) { |
| EXPECT_TRUE(embedded_test_server()->Start()); |
| |
| HeadlessBrowserContext* browser_context = |
| browser()->CreateBrowserContextBuilder().Build(); |
| |
| HeadlessWebContents* web_contents = |
| browser_context->CreateWebContentsBuilder() |
| .SetInitialURL(embedded_test_server()->GetURL("/hello.html")) |
| .Build(); |
| EXPECT_TRUE(WaitForLoad(web_contents)); |
| |
| EXPECT_THAT(browser_context->GetAllWebContents(), |
| UnorderedElementsAre(web_contents)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(HeadlessWebContentsTest, WindowOpen) { |
| EXPECT_TRUE(embedded_test_server()->Start()); |
| |
| HeadlessBrowserContext* browser_context = |
| browser()->CreateBrowserContextBuilder().Build(); |
| |
| HeadlessWebContents* web_contents = |
| browser_context->CreateWebContentsBuilder() |
| .SetInitialURL(embedded_test_server()->GetURL("/window_open.html")) |
| .Build(); |
| EXPECT_TRUE(WaitForLoad(web_contents)); |
| |
| EXPECT_EQ(2u, browser_context->GetAllWebContents().size()); |
| |
| HeadlessWebContentsImpl* child = nullptr; |
| HeadlessWebContentsImpl* parent = nullptr; |
| for (HeadlessWebContents* c : browser_context->GetAllWebContents()) { |
| HeadlessWebContentsImpl* impl = HeadlessWebContentsImpl::From(c); |
| if (impl->window_id() == 1) |
| parent = impl; |
| else if (impl->window_id() == 2) |
| child = impl; |
| } |
| |
| EXPECT_NE(nullptr, parent); |
| EXPECT_NE(nullptr, child); |
| EXPECT_NE(parent, child); |
| |
| // Mac doesn't have WindowTreeHosts. |
| if (parent && child && parent->window_tree_host()) |
| EXPECT_NE(parent->window_tree_host(), child->window_tree_host()); |
| |
| gfx::Rect expected_bounds(0, 0, 200, 100); |
| #if !BUILDFLAG(IS_MAC) |
| EXPECT_EQ(expected_bounds, child->web_contents()->GetViewBounds()); |
| EXPECT_EQ(expected_bounds, child->web_contents()->GetContainerBounds()); |
| #else // !BUILDFLAG(IS_MAC) |
| // Mac does not support GetViewBounds() and view positions are random. |
| EXPECT_EQ(expected_bounds.size(), |
| child->web_contents()->GetContainerBounds().size()); |
| #endif // !BUILDFLAG(IS_MAC) |
| } |
| |
| IN_PROC_BROWSER_TEST_F(HeadlessWebContentsTest, |
| FocusOfHeadlessWebContents_IsIndependent) { |
| EXPECT_TRUE(embedded_test_server()->Start()); |
| |
| HeadlessBrowserContext* browser_context = |
| browser()->CreateBrowserContextBuilder().Build(); |
| |
| HeadlessWebContents* web_contents = |
| browser_context->CreateWebContentsBuilder() |
| .SetInitialURL(embedded_test_server()->GetURL("/hello.html")) |
| .Build(); |
| WaitForLoadAndGainFocus(web_contents); |
| |
| EXPECT_THAT(EvaluateScript(web_contents, "document.hasFocus()"), |
| DictHasValue("result.result.value", true)); |
| |
| HeadlessWebContents* web_contents2 = |
| browser_context->CreateWebContentsBuilder() |
| .SetInitialURL(embedded_test_server()->GetURL("/hello.html")) |
| .Build(); |
| WaitForLoadAndGainFocus(web_contents2); |
| |
| // Focus of different WebContents is independent. |
| EXPECT_THAT(EvaluateScript(web_contents, "document.hasFocus()"), |
| DictHasValue("result.result.value", true)); |
| EXPECT_THAT(EvaluateScript(web_contents2, "document.hasFocus()"), |
| DictHasValue("result.result.value", true)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(HeadlessWebContentsTest, HandleSSLError) { |
| net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); |
| https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_EXPIRED); |
| ASSERT_TRUE(https_server.Start()); |
| |
| HeadlessBrowserContext* browser_context = |
| browser()->CreateBrowserContextBuilder().Build(); |
| |
| HeadlessWebContents* web_contents = |
| browser_context->CreateWebContentsBuilder() |
| .SetInitialURL(https_server.GetURL("/hello.html")) |
| .Build(); |
| |
| EXPECT_FALSE(WaitForLoad(web_contents)); |
| } |
| |
| namespace { |
| bool DecodePNG(const std::string& png_data, SkBitmap* bitmap) { |
| return gfx::PNGCodec::Decode( |
| reinterpret_cast<const unsigned char*>(png_data.data()), png_data.size(), |
| bitmap); |
| } |
| } // namespace |
| |
| // Parameter specifies whether --disable-gpu should be used. |
| class HeadlessWebContentsScreenshotTest |
| : public HeadlessDevTooledBrowserTest, |
| public ::testing::WithParamInterface<bool> { |
| public: |
| void SetUp() override { |
| EnablePixelOutput(); |
| if (GetParam()) { |
| UseSoftwareCompositing(); |
| SetUpWithoutGPU(); |
| } else { |
| HeadlessDevTooledBrowserTest::SetUp(); |
| } |
| } |
| |
| void RunDevTooledTest() override { |
| devtools_client_.SendCommand( |
| "Runtime.evaluate", |
| Param("expression", "document.body.style.background = '#0000ff'"), |
| base::BindOnce(&HeadlessWebContentsScreenshotTest::OnPageSetupCompleted, |
| base::Unretained(this))); |
| } |
| |
| void OnPageSetupCompleted(base::Value::Dict) { |
| devtools_client_.SendCommand( |
| "Page.captureScreenshot", |
| base::BindOnce(&HeadlessWebContentsScreenshotTest::OnScreenshotCaptured, |
| base::Unretained(this))); |
| } |
| |
| void OnScreenshotCaptured(base::Value::Dict result) { |
| std::string png_data_base64 = DictString(result, "result.data"); |
| ASSERT_FALSE(png_data_base64.empty()); |
| |
| std::string png_data; |
| ASSERT_TRUE(base::Base64Decode(png_data_base64, &png_data)); |
| EXPECT_GT(png_data.size(), 0U); |
| |
| SkBitmap result_bitmap; |
| EXPECT_TRUE(DecodePNG(png_data, &result_bitmap)); |
| |
| EXPECT_EQ(800, result_bitmap.width()); |
| EXPECT_EQ(600, result_bitmap.height()); |
| SkColor actual_color = result_bitmap.getColor(400, 300); |
| SkColor expected_color = SkColorSetRGB(0x00, 0x00, 0xff); |
| EXPECT_EQ(expected_color, actual_color); |
| |
| FinishAsynchronousTest(); |
| } |
| }; |
| |
| HEADLESS_DEVTOOLED_TEST_P(HeadlessWebContentsScreenshotTest); |
| |
| // Instantiate test case for both software and gpu compositing modes. |
| INSTANTIATE_TEST_SUITE_P(HeadlessWebContentsScreenshotTests, |
| HeadlessWebContentsScreenshotTest, |
| ::testing::Bool()); |
| |
| // Regression test for crbug.com/832138. |
| class HeadlessWebContentsScreenshotWindowPositionTest |
| : public HeadlessWebContentsScreenshotTest { |
| public: |
| void RunDevTooledTest() override { |
| base::Value::Dict params; |
| params.Set("windowId", |
| HeadlessWebContentsImpl::From(web_contents_)->window_id()); |
| params.SetByDottedPath("bounds.left", 600); |
| params.SetByDottedPath("bounds.top", 100); |
| params.SetByDottedPath("bounds.width", 800); |
| params.SetByDottedPath("bounds.height", 600); |
| |
| browser_devtools_client_.SendCommand( |
| "Browser.setWindowBounds", std::move(params), |
| base::BindOnce( |
| &HeadlessWebContentsScreenshotWindowPositionTest::OnWindowBoundsSet, |
| base::Unretained(this))); |
| } |
| |
| void OnWindowBoundsSet(base::Value::Dict result) { |
| EXPECT_NE(result.FindDict("result"), nullptr); |
| HeadlessWebContentsScreenshotTest::RunDevTooledTest(); |
| } |
| }; |
| |
| #if BUILDFLAG(IS_MAC) && defined(ADDRESS_SANITIZER) |
| // TODO(crbug.com/1086872): Disabled due to flakiness on Mac ASAN. |
| DISABLED_HEADLESS_DEVTOOLED_TEST_P( |
| HeadlessWebContentsScreenshotWindowPositionTest); |
| #else |
| HEADLESS_DEVTOOLED_TEST_P(HeadlessWebContentsScreenshotWindowPositionTest); |
| #endif |
| |
| // Instantiate test case for both software and gpu compositing modes. |
| INSTANTIATE_TEST_SUITE_P(HeadlessWebContentsScreenshotWindowPositionTests, |
| HeadlessWebContentsScreenshotWindowPositionTest, |
| ::testing::Bool()); |
| |
| // Regression test for https://crbug.com/733569. |
| class HeadlessWebContentsRequestStorageQuotaTest |
| : public HeadlessDevTooledBrowserTest { |
| public: |
| void RunDevTooledTest() override { |
| EXPECT_TRUE(embedded_test_server()->Start()); |
| |
| devtools_client_.AddEventHandler( |
| "Runtime.consoleAPICalled", |
| base::BindRepeating( |
| &HeadlessWebContentsRequestStorageQuotaTest::OnConsoleAPICalled, |
| base::Unretained(this))); |
| SendCommandSync(devtools_client_, "Runtime.enable"); |
| |
| // Should not crash and call console.log() if quota request succeeds. |
| devtools_client_.SendCommand( |
| "Page.navigate", |
| Param("url", embedded_test_server() |
| ->GetURL("/request_storage_quota.html") |
| .spec())); |
| } |
| |
| void OnConsoleAPICalled(const base::Value::Dict& params) { |
| const base::Value::List* args = params.FindListByDottedPath("params.args"); |
| ASSERT_NE(args, nullptr); |
| ASSERT_GT(args->size(), 0ul); |
| |
| const base::Value* value = args->front().GetDict().Find("value"); |
| ASSERT_NE(value, nullptr); |
| EXPECT_EQ(value->GetString(), "success"); |
| |
| FinishAsynchronousTest(); |
| } |
| }; |
| |
| HEADLESS_DEVTOOLED_TEST_F(HeadlessWebContentsRequestStorageQuotaTest); |
| |
| IN_PROC_BROWSER_TEST_F(HeadlessWebContentsTest, BrowserTabChangeContent) { |
| EXPECT_TRUE(embedded_test_server()->Start()); |
| |
| HeadlessBrowserContext* browser_context = |
| browser()->CreateBrowserContextBuilder().Build(); |
| |
| HeadlessWebContents* web_contents = |
| browser_context->CreateWebContentsBuilder().Build(); |
| EXPECT_TRUE(WaitForLoad(web_contents)); |
| |
| std::string script = "window.location = '" + |
| embedded_test_server()->GetURL("/hello.html").spec() + |
| "';"; |
| EXPECT_THAT(EvaluateScript(web_contents, script), |
| Not(DictHasKey("exceptionDetails"))); |
| |
| // This will time out if the previous script did not work. |
| EXPECT_TRUE(WaitForLoad(web_contents)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(HeadlessWebContentsTest, BrowserOpenInTab) { |
| EXPECT_TRUE(embedded_test_server()->Start()); |
| |
| HeadlessBrowserContext* browser_context = |
| browser()->CreateBrowserContextBuilder().Build(); |
| |
| HeadlessWebContents* web_contents = |
| browser_context->CreateWebContentsBuilder() |
| .SetInitialURL(embedded_test_server()->GetURL("/link.html")) |
| .Build(); |
| EXPECT_TRUE(WaitForLoad(web_contents)); |
| |
| EXPECT_EQ(1u, browser_context->GetAllWebContents().size()); |
| // Simulates a middle-button click on a link to ensure that the |
| // link is opened in a new tab by the browser and not by the renderer. |
| std::string script = |
| "var event = new MouseEvent('click', {'button': 1});" |
| "document.getElementsByTagName('a')[0].dispatchEvent(event);"; |
| EXPECT_THAT(EvaluateScript(web_contents, script), |
| Not(DictHasKey("exceptionDetails"))); |
| // Check that we have a new tab. |
| EXPECT_EQ(2u, browser_context->GetAllWebContents().size()); |
| } |
| |
| // BeginFrameControl is not supported on MacOS. |
| #if !BUILDFLAG(IS_MAC) |
| |
| // TODO(kvitekp): Check to see if this could be trimmed down by using |
| // Pre/PostRunAsynchronousTest(). |
| class HeadlessWebContentsBeginFrameControlTest : public HeadlessBrowserTest { |
| public: |
| HeadlessWebContentsBeginFrameControlTest() {} |
| |
| void SetUp() override { |
| EnablePixelOutput(); |
| HeadlessBrowserTest::SetUp(); |
| } |
| |
| protected: |
| virtual std::string GetTestHtmlFile() = 0; |
| virtual void StartFrames() {} |
| virtual void OnFrameFinished(base::Value::Dict result) {} |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| HeadlessBrowserTest::SetUpCommandLine(command_line); |
| // See bit.ly/headless-rendering for why we use these flags. |
| command_line->AppendSwitch(::switches::kRunAllCompositorStagesBeforeDraw); |
| command_line->AppendSwitch(::switches::kDisableNewContentRenderingTimeout); |
| command_line->AppendSwitch(cc::switches::kDisableCheckerImaging); |
| command_line->AppendSwitch(cc::switches::kDisableThreadedAnimation); |
| command_line->AppendSwitch(blink::switches::kDisableThreadedScrolling); |
| } |
| |
| void RunTest() { |
| browser_context_ = browser()->CreateBrowserContextBuilder().Build(); |
| browser()->SetDefaultBrowserContext(browser_context_); |
| browser_devtools_client_.AttachToBrowser(); |
| |
| EXPECT_TRUE(embedded_test_server()->Start()); |
| |
| base::Value::Dict params; |
| params.Set("url", "about:blank"); |
| params.Set("width", 200); |
| params.Set("height", 200); |
| params.Set("enableBeginFrameControl", true); |
| browser_devtools_client_.SendCommand( |
| "Target.createTarget", std::move(params), |
| base::BindOnce( |
| &HeadlessWebContentsBeginFrameControlTest::OnTargetCreated, |
| base::Unretained(this))); |
| |
| RunAsynchronousTest(); |
| |
| browser_devtools_client_.DetachClient(); |
| } |
| |
| void OnTargetCreated(base::Value::Dict result) { |
| const std::string targetId = DictString(result, "result.targetId"); |
| ASSERT_FALSE(targetId.empty()); |
| |
| web_contents_ = HeadlessWebContentsImpl::From( |
| browser()->GetWebContentsForDevToolsAgentHostId(targetId)); |
| |
| devtools_client_.AttachToWebContents(web_contents_->web_contents()); |
| |
| devtools_client_.SendCommand( |
| "Page.stopLoading", |
| base::BindOnce( |
| &HeadlessWebContentsBeginFrameControlTest::OnLoadingStopped, |
| base::Unretained(this))); |
| } |
| |
| void OnLoadingStopped(base::Value::Dict) { |
| devtools_client_.AddEventHandler("Page.loadEventFired", |
| on_load_event_fired_handler_); |
| |
| devtools_client_.SendCommand( |
| "Page.enable", |
| base::BindOnce( |
| &HeadlessWebContentsBeginFrameControlTest::OnPageDomainEnabled, |
| base::Unretained(this))); |
| } |
| |
| void OnPageDomainEnabled(base::Value::Dict) { |
| devtools_client_.SendCommand( |
| "Page.navigate", |
| Param("url", embedded_test_server()->GetURL(GetTestHtmlFile()).spec())); |
| } |
| |
| void OnLoadEventFired(const base::Value::Dict& params) { |
| TRACE_EVENT0("headless", |
| "HeadlessWebContentsBeginFrameControlTest::OnLoadEventFired"); |
| |
| devtools_client_.SendCommand("Page.disable"); |
| devtools_client_.RemoveEventHandler("Page.loadEventFired", |
| on_load_event_fired_handler_); |
| |
| page_ready_ = true; |
| StartFrames(); |
| } |
| |
| void BeginFrame(bool screenshot) { |
| frame_in_flight_ = true; |
| num_begin_frames_++; |
| |
| base::Value::Dict params; |
| if (screenshot) |
| params.Set("screenshot", base::Value::Dict()); |
| |
| devtools_client_.SendCommand( |
| "HeadlessExperimental.beginFrame", std::move(params), |
| base::BindOnce(&HeadlessWebContentsBeginFrameControlTest::FrameFinished, |
| base::Unretained(this))); |
| } |
| |
| void FrameFinished(base::Value::Dict result) { |
| TRACE_EVENT2( |
| "headless", "HeadlessWebContentsBeginFrameControlTest::FrameFinished", |
| "has_damage", DictBool(result, "result.hasDamage"), |
| "has_screenshot_data", DictString(result, "result.screenshotData")); |
| |
| // Post OnFrameFinished call so that any pending OnNeedsBeginFramesChanged |
| // call will be executed first. |
| browser()->BrowserMainThread()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &HeadlessWebContentsBeginFrameControlTest::NotifyOnFrameFinished, |
| base::Unretained(this), std::move(result))); |
| } |
| |
| void NotifyOnFrameFinished(base::Value::Dict result) { |
| frame_in_flight_ = false; |
| OnFrameFinished(std::move(result)); |
| } |
| |
| void PostFinishAsynchronousTest() { |
| browser()->BrowserMainThread()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &HeadlessWebContentsBeginFrameControlTest::FinishAsynchronousTest, |
| base::Unretained(this))); |
| } |
| |
| raw_ptr<HeadlessBrowserContext, DanglingUntriaged> browser_context_ = |
| nullptr; // Not owned. |
| raw_ptr<HeadlessWebContentsImpl, DanglingUntriaged> web_contents_ = |
| nullptr; // Not owned. |
| |
| bool page_ready_ = false; |
| bool frame_in_flight_ = false; |
| int num_begin_frames_ = 0; |
| |
| SimpleDevToolsProtocolClient browser_devtools_client_; |
| SimpleDevToolsProtocolClient devtools_client_; |
| |
| SimpleDevToolsProtocolClient::EventCallback on_load_event_fired_handler_ = |
| base::BindRepeating( |
| &HeadlessWebContentsBeginFrameControlTest::OnLoadEventFired, |
| base::Unretained(this)); |
| }; |
| |
| class HeadlessWebContentsBeginFrameControlBasicTest |
| : public HeadlessWebContentsBeginFrameControlTest { |
| public: |
| HeadlessWebContentsBeginFrameControlBasicTest() = default; |
| |
| protected: |
| std::string GetTestHtmlFile() override { |
| // Blue background. |
| return "/blue_page.html"; |
| } |
| |
| void StartFrames() override { BeginFrame(true); } |
| |
| void OnFrameFinished(base::Value::Dict result) override { |
| if (num_begin_frames_ == 1) { |
| // First BeginFrame should have caused damage and have a screenshot. |
| EXPECT_TRUE(DictBool(result, "result.hasDamage")); |
| |
| std::string png_data_base64 = DictString(result, "result.screenshotData"); |
| ASSERT_FALSE(png_data_base64.empty()); |
| |
| std::string png_data; |
| ASSERT_TRUE(base::Base64Decode(png_data_base64, &png_data)); |
| EXPECT_GT(png_data.size(), 0U); |
| |
| SkBitmap result_bitmap; |
| EXPECT_TRUE(DecodePNG(png_data, &result_bitmap)); |
| EXPECT_EQ(200, result_bitmap.width()); |
| EXPECT_EQ(200, result_bitmap.height()); |
| SkColor expected_color = SkColorSetRGB(0x00, 0x00, 0xff); |
| SkColor actual_color = result_bitmap.getColor(100, 100); |
| EXPECT_EQ(expected_color, actual_color); |
| } else { |
| DCHECK_EQ(2, num_begin_frames_); |
| // Can't guarantee that the second BeginFrame didn't have damage, but it |
| // should not have a screenshot. |
| EXPECT_FALSE(result.FindStringByDottedPath("result.screenshotData")); |
| } |
| |
| if (num_begin_frames_ < 2) { |
| // Don't capture a screenshot in the second BeginFrame. |
| BeginFrame(false); |
| } else { |
| // Post completion to avoid deleting the WebContents on the same callstack |
| // as frame finished callback. |
| PostFinishAsynchronousTest(); |
| } |
| } |
| }; |
| |
| HEADLESS_DEVTOOLED_TEST_F(HeadlessWebContentsBeginFrameControlBasicTest); |
| |
| class HeadlessWebContentsBeginFrameControlViewportTest |
| : public HeadlessWebContentsBeginFrameControlTest { |
| public: |
| HeadlessWebContentsBeginFrameControlViewportTest() = default; |
| |
| protected: |
| std::string GetTestHtmlFile() override { |
| // Draws a 100x100px blue box at 200x200px. |
| return "/blue_box.html"; |
| } |
| |
| void StartFrames() override { |
| // Send a first BeginFrame to initialize the surface. |
| BeginFrame(false); |
| } |
| |
| void SetUpViewport() { |
| base::Value::Dict params; |
| params.Set("width", 0); |
| params.Set("height", 0); |
| params.Set("deviceScaleFactor", 0); |
| params.Set("mobile", false); |
| params.SetByDottedPath("viewport.x", 200); |
| params.SetByDottedPath("viewport.y", 200); |
| params.SetByDottedPath("viewport.width", 100); |
| params.SetByDottedPath("viewport.height", 100); |
| params.SetByDottedPath("viewport.scale", 3); |
| |
| devtools_client_.SendCommand( |
| "Emulation.setDeviceMetricsOverride", std::move(params), |
| base::BindOnce(&HeadlessWebContentsBeginFrameControlViewportTest:: |
| OnSetDeviceMetricsOverrideDone, |
| base::Unretained(this))); |
| } |
| |
| void OnSetDeviceMetricsOverrideDone(base::Value::Dict result) { |
| EXPECT_THAT(result, DictHasKey("result")); |
| // Take a screenshot in the second BeginFrame. |
| BeginFrame(true); |
| } |
| |
| void OnFrameFinished(base::Value::Dict result) override { |
| if (num_begin_frames_ == 1) { |
| SetUpViewport(); |
| return; |
| } |
| |
| DCHECK_EQ(2, num_begin_frames_); |
| // Second BeginFrame should have a screenshot of the configured viewport and |
| // of the correct size. |
| EXPECT_TRUE(*result.FindBoolByDottedPath("result.hasDamage")); |
| |
| std::string png_data_base64 = DictString(result, "result.screenshotData"); |
| ASSERT_FALSE(png_data_base64.empty()); |
| |
| std::string png_data; |
| ASSERT_TRUE(base::Base64Decode(png_data_base64, &png_data)); |
| ASSERT_GT(png_data.size(), 0ul); |
| |
| SkBitmap result_bitmap; |
| EXPECT_TRUE(DecodePNG(png_data, &result_bitmap)); |
| |
| // Expect a 300x300 bitmap that is all blue. |
| SkBitmap expected_bitmap; |
| SkImageInfo info; |
| expected_bitmap.allocPixels( |
| SkImageInfo::MakeN32(300, 300, kOpaque_SkAlphaType), /*rowBytes=*/0); |
| expected_bitmap.eraseColor(SkColorSetRGB(0x00, 0x00, 0xff)); |
| |
| EXPECT_TRUE( |
| cc::MatchesBitmap(result_bitmap, expected_bitmap, |
| cc::ExactPixelComparator(/*discard_alpha=*/false))); |
| |
| // Post completion to avoid deleting the WebContents on the same callstack |
| // as frame finished callback. |
| PostFinishAsynchronousTest(); |
| } |
| }; |
| |
| HEADLESS_DEVTOOLED_TEST_F(HeadlessWebContentsBeginFrameControlViewportTest); |
| |
| #endif // !BUILDFLAG(IS_MAC) |
| |
| class CookiesEnabled : public HeadlessDevTooledBrowserTest { |
| public: |
| void RunDevTooledTest() override { |
| EXPECT_TRUE(embedded_test_server()->Start()); |
| |
| devtools_client_.AddEventHandler( |
| "Page.loadEventFired", |
| base::BindRepeating(&CookiesEnabled::OnLoadEventFired, |
| base::Unretained(this))); |
| devtools_client_.SendCommand("Page.enable"); |
| |
| devtools_client_.SendCommand( |
| "Page.navigate", |
| Param("url", embedded_test_server()->GetURL("/cookie.html").spec())); |
| } |
| |
| void OnLoadEventFired(const base::Value::Dict& params) { |
| devtools_client_.SendCommand( |
| "Runtime.evaluate", Param("expression", "window.test_result"), |
| base::BindOnce(&CookiesEnabled::OnEvaluateResult, |
| base::Unretained(this))); |
| } |
| |
| void OnEvaluateResult(base::Value::Dict result) { |
| EXPECT_EQ(DictString(result, "result.result.value"), "0"); |
| |
| FinishAsynchronousTest(); |
| } |
| }; |
| |
| HEADLESS_DEVTOOLED_TEST_F(CookiesEnabled); |
| |
| namespace { |
| const char* kPageWhichOpensAWindow = R"( |
| <html> |
| <body> |
| <script> |
| const win = window.open('/page2.html'); |
| if (!win) |
| console.error('ready'); |
| win.addEventListener('load', () => console.log('ready')); |
| </script> |
| </body> |
| </html> |
| )"; |
| |
| const char* kPage2 = R"( |
| <html> |
| <body> |
| Page 2. |
| </body> |
| </html> |
| )"; |
| } // namespace |
| |
| class WebContentsOpenTest : public HeadlessDevTooledBrowserTest { |
| public: |
| void PreRunAsynchronousTest() override { |
| interceptor_ = std::make_unique<TestNetworkInterceptor>(); |
| } |
| |
| void PostRunAsynchronousTest() override { interceptor_.reset(); } |
| |
| void RunDevTooledTest() override { |
| DCHECK(interceptor_); |
| |
| interceptor_->InsertResponse("http://foo.com/index.html", |
| {kPageWhichOpensAWindow, "text/html"}); |
| interceptor_->InsertResponse("http://foo.com/page2.html", |
| {kPage2, "text/html"}); |
| |
| devtools_client_.AddEventHandler( |
| "Runtime.consoleAPICalled", |
| base::BindRepeating(&WebContentsOpenTest::OnConsoleAPICalled, |
| base::Unretained(this))); |
| SendCommandSync(devtools_client_, "Runtime.enable"); |
| |
| devtools_client_.SendCommand("Page.navigate", |
| Param("url", "http://foo.com/index.html")); |
| } |
| |
| virtual void OnConsoleAPICalled(const base::Value::Dict& params) {} |
| |
| protected: |
| std::unique_ptr<TestNetworkInterceptor> interceptor_; |
| }; |
| |
| class DontBlockWebContentsOpenTest : public WebContentsOpenTest { |
| public: |
| void CustomizeHeadlessBrowserContext( |
| HeadlessBrowserContext::Builder& builder) override { |
| builder.SetBlockNewWebContents(false); |
| } |
| |
| void OnConsoleAPICalled(const base::Value::Dict& params) override { |
| EXPECT_THAT( |
| interceptor_->urls_requested(), |
| ElementsAre("http://foo.com/index.html", "http://foo.com/page2.html")); |
| FinishAsynchronousTest(); |
| } |
| }; |
| |
| HEADLESS_DEVTOOLED_TEST_F(DontBlockWebContentsOpenTest); |
| |
| class BlockWebContentsOpenTest : public WebContentsOpenTest { |
| public: |
| void CustomizeHeadlessBrowserContext( |
| HeadlessBrowserContext::Builder& builder) override { |
| builder.SetBlockNewWebContents(true); |
| } |
| |
| void OnConsoleAPICalled(const base::Value::Dict& params) override { |
| EXPECT_THAT(interceptor_->urls_requested(), |
| ElementsAre("http://foo.com/index.html")); |
| FinishAsynchronousTest(); |
| } |
| }; |
| |
| HEADLESS_DEVTOOLED_TEST_F(BlockWebContentsOpenTest); |
| |
| // Regression test for crbug.com/1385982. |
| class BlockDevToolsEmbedding : public HeadlessDevTooledBrowserTest { |
| protected: |
| void SetUpInProcessBrowserTestFixture() override { |
| HeadlessDevTooledBrowserTest::SetUpInProcessBrowserTestFixture(); |
| options()->devtools_endpoint = net::HostPortPair("localhost", port_); |
| } |
| |
| void RunDevTooledTest() override { |
| std::stringstream url; |
| url << "data:text/html,<iframe src='http://localhost:" << port_ |
| << "/json/version'></iframe>"; |
| |
| devtools_client_.AddEventHandler( |
| "Page.loadEventFired", |
| base::BindRepeating(&BlockDevToolsEmbedding::OnLoadEventFired, |
| base::Unretained(this))); |
| devtools_client_.SendCommand("Page.enable"); |
| devtools_client_.SendCommand("Page.navigate", Param("url", url.str())); |
| } |
| |
| void OnLoadEventFired(const base::Value::Dict& params) { |
| devtools_client_.SendCommand( |
| "Page.getFrameTree", |
| base::BindOnce(&BlockDevToolsEmbedding::OnFrameTreeResult, |
| base::Unretained(this))); |
| } |
| |
| void OnFrameTreeResult(base::Value::Dict result) { |
| // Make sure the iframe did not load successfully. |
| auto& child_frames = |
| *result.FindListByDottedPath("result.frameTree.childFrames"); |
| EXPECT_EQ(DictString(child_frames[0].GetDict(), "frame.url"), |
| "chrome-error://chromewebdata/"); |
| FinishAsynchronousTest(); |
| } |
| |
| private: |
| int port_ = 10000 + (rand() % 60000); |
| }; |
| |
| HEADLESS_DEVTOOLED_TEST_F(BlockDevToolsEmbedding); |
| |
| } // namespace headless |