| // Copyright 2016 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 <memory> |
| #include <string> |
| #include <tuple> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/files/file_enumerator.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/run_loop.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "content/public/browser/navigation_controller.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/permission_controller_delegate.h" |
| #include "content/public/browser/ssl_status.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/common/url_constants.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/no_renderer_crashes_assertion.h" |
| #include "headless/app/headless_shell_switches.h" |
| #include "headless/lib/browser/headless_browser_context_impl.h" |
| #include "headless/lib/browser/headless_browser_impl.h" |
| #include "headless/lib/browser/headless_select_file_dialog_factory.h" |
| #include "headless/lib/browser/headless_web_contents_impl.h" |
| #include "headless/public/devtools/domains/inspector.h" |
| #include "headless/public/devtools/domains/network.h" |
| #include "headless/public/devtools/domains/page.h" |
| #include "headless/public/devtools/domains/tracing.h" |
| #include "headless/public/headless_browser.h" |
| #include "headless/public/headless_devtools_client.h" |
| #include "headless/public/headless_devtools_target.h" |
| #include "headless/public/headless_web_contents.h" |
| #include "headless/test/headless_browser_test.h" |
| #include "net/base/net_errors.h" |
| #include "net/cert/cert_status_flags.h" |
| #include "net/ssl/ssl_server_config.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "storage/browser/file_system/external_mount_points.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/chrome_debug_urls.h" |
| #include "third_party/blink/public/common/permissions/permission_utils.h" |
| #include "third_party/blink/public/mojom/frame/user_activation_notification_type.mojom.h" |
| #include "third_party/blink/public/resources/grit/blink_resources.h" |
| #include "third_party/blink/public/strings/grit/blink_strings.h" |
| #include "ui/base/clipboard/clipboard.h" |
| #include "ui/base/clipboard/scoped_clipboard_writer.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/gfx/geometry/size.h" |
| #include "ui/shell_dialogs/select_file_dialog.h" |
| |
| #if !BUILDFLAG(IS_FUCHSIA) |
| #include "third_party/crashpad/crashpad/client/crash_report_database.h" // nogncheck |
| #endif |
| |
| using testing::UnorderedElementsAre; |
| |
| namespace headless { |
| |
| IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, CreateAndDestroyBrowserContext) { |
| HeadlessBrowserContext* browser_context = |
| browser()->CreateBrowserContextBuilder().Build(); |
| |
| EXPECT_THAT(browser()->GetAllBrowserContexts(), |
| UnorderedElementsAre(browser_context)); |
| |
| browser_context->Close(); |
| |
| EXPECT_TRUE(browser()->GetAllBrowserContexts().empty()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, |
| CreateAndDoNotDestroyBrowserContext) { |
| HeadlessBrowserContext* browser_context = |
| browser()->CreateBrowserContextBuilder().Build(); |
| |
| EXPECT_THAT(browser()->GetAllBrowserContexts(), |
| UnorderedElementsAre(browser_context)); |
| |
| // We check that HeadlessBrowser correctly handles non-closed BrowserContexts. |
| // We can rely on Chromium DCHECKs to capture this. |
| } |
| |
| IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, CreateAndDestroyWebContents) { |
| HeadlessBrowserContext* browser_context = |
| browser()->CreateBrowserContextBuilder().Build(); |
| |
| HeadlessWebContents* web_contents = |
| browser_context->CreateWebContentsBuilder().Build(); |
| EXPECT_TRUE(web_contents); |
| |
| EXPECT_THAT(browser()->GetAllBrowserContexts(), |
| UnorderedElementsAre(browser_context)); |
| EXPECT_THAT(browser_context->GetAllWebContents(), |
| UnorderedElementsAre(web_contents)); |
| |
| // TODO(skyostil): Verify viewport dimensions once we can. |
| |
| web_contents->Close(); |
| |
| EXPECT_TRUE(browser_context->GetAllWebContents().empty()); |
| |
| browser_context->Close(); |
| |
| EXPECT_TRUE(browser()->GetAllBrowserContexts().empty()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, |
| WebContentsAreDestroyedWithContext) { |
| HeadlessBrowserContext* browser_context = |
| browser()->CreateBrowserContextBuilder().Build(); |
| |
| HeadlessWebContents* web_contents = |
| browser_context->CreateWebContentsBuilder().Build(); |
| EXPECT_TRUE(web_contents); |
| |
| EXPECT_THAT(browser()->GetAllBrowserContexts(), |
| UnorderedElementsAre(browser_context)); |
| EXPECT_THAT(browser_context->GetAllWebContents(), |
| UnorderedElementsAre(web_contents)); |
| |
| browser_context->Close(); |
| |
| EXPECT_TRUE(browser()->GetAllBrowserContexts().empty()); |
| |
| // If WebContents are not destroyed, Chromium DCHECKs will capture this. |
| } |
| |
| IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, CreateAndDoNotDestroyWebContents) { |
| HeadlessBrowserContext* browser_context = |
| browser()->CreateBrowserContextBuilder().Build(); |
| |
| HeadlessWebContents* web_contents = |
| browser_context->CreateWebContentsBuilder().Build(); |
| EXPECT_TRUE(web_contents); |
| |
| EXPECT_THAT(browser()->GetAllBrowserContexts(), |
| UnorderedElementsAre(browser_context)); |
| EXPECT_THAT(browser_context->GetAllWebContents(), |
| UnorderedElementsAre(web_contents)); |
| |
| // If WebContents are not destroyed, Chromium DCHECKs will capture this. |
| } |
| |
| IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, DestroyAndCreateTwoWebContents) { |
| HeadlessBrowserContext* browser_context1 = |
| browser()->CreateBrowserContextBuilder().Build(); |
| EXPECT_TRUE(browser_context1); |
| HeadlessWebContents* web_contents1 = |
| browser_context1->CreateWebContentsBuilder().Build(); |
| EXPECT_TRUE(web_contents1); |
| |
| EXPECT_THAT(browser()->GetAllBrowserContexts(), |
| UnorderedElementsAre(browser_context1)); |
| EXPECT_THAT(browser_context1->GetAllWebContents(), |
| UnorderedElementsAre(web_contents1)); |
| |
| HeadlessBrowserContext* browser_context2 = |
| browser()->CreateBrowserContextBuilder().Build(); |
| EXPECT_TRUE(browser_context2); |
| HeadlessWebContents* web_contents2 = |
| browser_context2->CreateWebContentsBuilder().Build(); |
| EXPECT_TRUE(web_contents2); |
| |
| EXPECT_THAT(browser()->GetAllBrowserContexts(), |
| UnorderedElementsAre(browser_context1, browser_context2)); |
| EXPECT_THAT(browser_context1->GetAllWebContents(), |
| UnorderedElementsAre(web_contents1)); |
| EXPECT_THAT(browser_context2->GetAllWebContents(), |
| UnorderedElementsAre(web_contents2)); |
| |
| browser_context1->Close(); |
| |
| EXPECT_THAT(browser()->GetAllBrowserContexts(), |
| UnorderedElementsAre(browser_context2)); |
| EXPECT_THAT(browser_context2->GetAllWebContents(), |
| UnorderedElementsAre(web_contents2)); |
| |
| browser_context2->Close(); |
| |
| EXPECT_TRUE(browser()->GetAllBrowserContexts().empty()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, CreateWithBadURL) { |
| GURL bad_url("not_valid"); |
| |
| HeadlessBrowserContext* browser_context = |
| browser()->CreateBrowserContextBuilder().Build(); |
| |
| HeadlessWebContents* web_contents = |
| browser_context->CreateWebContentsBuilder() |
| .SetInitialURL(bad_url) |
| .Build(); |
| |
| EXPECT_FALSE(web_contents); |
| EXPECT_TRUE(browser_context->GetAllWebContents().empty()); |
| } |
| |
| class HeadlessBrowserTestWithProxy : public HeadlessBrowserTest { |
| public: |
| HeadlessBrowserTestWithProxy() |
| : proxy_server_(net::EmbeddedTestServer::TYPE_HTTP) { |
| proxy_server_.AddDefaultHandlers( |
| base::FilePath(FILE_PATH_LITERAL("headless/test/data"))); |
| } |
| |
| void SetUp() override { |
| ASSERT_TRUE(proxy_server_.Start()); |
| HeadlessBrowserTest::SetUp(); |
| } |
| |
| void TearDown() override { |
| EXPECT_TRUE(proxy_server_.ShutdownAndWaitUntilComplete()); |
| HeadlessBrowserTest::TearDown(); |
| } |
| |
| net::EmbeddedTestServer* proxy_server() { return &proxy_server_; } |
| |
| private: |
| net::EmbeddedTestServer proxy_server_; |
| }; |
| |
| #if (BUILDFLAG(IS_MAC) && defined(ADDRESS_SANITIZER)) || BUILDFLAG(IS_FUCHSIA) |
| // TODO(crbug.com/1086872): Disabled due to flakiness on Mac ASAN. |
| // TODO(crbug.com/1090933): Fix this test on Fuchsia and re-enable. |
| #define MAYBE_SetProxyConfig DISABLED_SetProxyConfig |
| #else |
| #define MAYBE_SetProxyConfig SetProxyConfig |
| #endif |
| IN_PROC_BROWSER_TEST_F(HeadlessBrowserTestWithProxy, MAYBE_SetProxyConfig) { |
| std::unique_ptr<net::ProxyConfig> proxy_config(new net::ProxyConfig); |
| proxy_config->proxy_rules().ParseFromString( |
| proxy_server()->host_port_pair().ToString()); |
| HeadlessBrowserContext* browser_context = |
| browser() |
| ->CreateBrowserContextBuilder() |
| .SetProxyConfig(std::move(proxy_config)) |
| .Build(); |
| |
| // Load a page which doesn't actually exist, but for which the our proxy |
| // returns valid content anyway. |
| HeadlessWebContents* web_contents = |
| browser_context->CreateWebContentsBuilder() |
| .SetInitialURL(GURL("http://not-an-actual-domain.tld/hello.html")) |
| .Build(); |
| EXPECT_TRUE(WaitForLoad(web_contents)); |
| EXPECT_THAT(browser()->GetAllBrowserContexts(), |
| UnorderedElementsAre(browser_context)); |
| EXPECT_THAT(browser_context->GetAllWebContents(), |
| UnorderedElementsAre(web_contents)); |
| web_contents->Close(); |
| EXPECT_TRUE(browser_context->GetAllWebContents().empty()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, WebGLSupported) { |
| HeadlessBrowserContext* browser_context = |
| browser()->CreateBrowserContextBuilder().Build(); |
| |
| HeadlessWebContents* web_contents = |
| browser_context->CreateWebContentsBuilder().Build(); |
| |
| EXPECT_TRUE( |
| EvaluateScript(web_contents, |
| "(document.createElement('canvas').getContext('webgl')" |
| " instanceof WebGLRenderingContext)") |
| ->GetResult() |
| ->GetValue() |
| ->GetBool()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, ClipboardCopyPasteText) { |
| // Tests copy-pasting text with the clipboard in headless mode. |
| ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread(); |
| ASSERT_TRUE(clipboard); |
| std::u16string paste_text = u"Clippy!"; |
| for (ui::ClipboardBuffer buffer : |
| {ui::ClipboardBuffer::kCopyPaste, ui::ClipboardBuffer::kSelection, |
| ui::ClipboardBuffer::kDrag}) { |
| if (!ui::Clipboard::IsSupportedClipboardBuffer(buffer)) |
| continue; |
| { |
| ui::ScopedClipboardWriter writer(buffer); |
| writer.WriteText(paste_text); |
| } |
| std::u16string copy_text; |
| clipboard->ReadText(buffer, /* data_dst = */ nullptr, ©_text); |
| EXPECT_EQ(paste_text, copy_text); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, DefaultSizes) { |
| HeadlessBrowserContext* browser_context = |
| browser()->CreateBrowserContextBuilder().Build(); |
| |
| HeadlessWebContents* web_contents = |
| browser_context->CreateWebContentsBuilder().Build(); |
| |
| HeadlessBrowser::Options::Builder builder; |
| const HeadlessBrowser::Options kDefaultOptions = builder.Build(); |
| |
| #if !BUILDFLAG(IS_MAC) |
| // On Mac headless does not override the screen dimensions, so they are |
| // left with the actual screen values. |
| EXPECT_EQ(kDefaultOptions.window_size.width(), |
| EvaluateScript(web_contents, "screen.width") |
| ->GetResult() |
| ->GetValue() |
| ->GetInt()); |
| EXPECT_EQ(kDefaultOptions.window_size.height(), |
| EvaluateScript(web_contents, "screen.height") |
| ->GetResult() |
| ->GetValue() |
| ->GetInt()); |
| #endif // !BUILDFLAG(IS_MAC) |
| EXPECT_EQ(kDefaultOptions.window_size.width(), |
| EvaluateScript(web_contents, "window.innerWidth") |
| ->GetResult() |
| ->GetValue() |
| ->GetInt()); |
| EXPECT_EQ(kDefaultOptions.window_size.height(), |
| EvaluateScript(web_contents, "window.innerHeight") |
| ->GetResult() |
| ->GetValue() |
| ->GetInt()); |
| } |
| |
| namespace { |
| |
| class CookieSetter { |
| public: |
| CookieSetter(HeadlessBrowserTest* browser_test, |
| HeadlessWebContents* web_contents, |
| std::unique_ptr<network::SetCookieParams> set_cookie_params) |
| : browser_test_(browser_test), |
| web_contents_(web_contents), |
| devtools_client_(HeadlessDevToolsClient::Create()) { |
| web_contents_->GetDevToolsTarget()->AttachClient(devtools_client_.get()); |
| devtools_client_->GetNetwork()->GetExperimental()->SetCookie( |
| std::move(set_cookie_params), |
| base::BindOnce(&CookieSetter::OnSetCookieResult, |
| base::Unretained(this))); |
| } |
| |
| CookieSetter(const CookieSetter&) = delete; |
| CookieSetter& operator=(const CookieSetter&) = delete; |
| |
| ~CookieSetter() { |
| web_contents_->GetDevToolsTarget()->DetachClient(devtools_client_.get()); |
| } |
| |
| void OnSetCookieResult(std::unique_ptr<network::SetCookieResult> result) { |
| result_ = std::move(result); |
| browser_test_->FinishAsynchronousTest(); |
| } |
| |
| std::unique_ptr<network::SetCookieResult> TakeResult() { |
| return std::move(result_); |
| } |
| |
| private: |
| raw_ptr<HeadlessBrowserTest> browser_test_; // Not owned. |
| raw_ptr<HeadlessWebContents> web_contents_; // Not owned. |
| std::unique_ptr<HeadlessDevToolsClient> devtools_client_; |
| |
| std::unique_ptr<network::SetCookieResult> result_; |
| }; |
| |
| } // namespace |
| |
| // TODO(skyostil): This test currently relies on being able to run a shell |
| // script. |
| #if BUILDFLAG(IS_POSIX) |
| class HeadlessBrowserRendererCommandPrefixTest : public HeadlessBrowserTest { |
| public: |
| const base::FilePath& launcher_stamp() const { return launcher_stamp_; } |
| |
| void SetUp() override { |
| base::CreateTemporaryFile(&launcher_stamp_); |
| |
| base::ScopedFILE launcher_file = |
| base::CreateAndOpenTemporaryStream(&launcher_script_); |
| fprintf(launcher_file.get(), "#!/bin/sh\n"); |
| fprintf(launcher_file.get(), "echo $@ > %s\n", |
| launcher_stamp_.value().c_str()); |
| fprintf(launcher_file.get(), "exec $@\n"); |
| launcher_file.reset(); |
| #if !BUILDFLAG(IS_FUCHSIA) |
| base::SetPosixFilePermissions(launcher_script_, |
| base::FILE_PERMISSION_READ_BY_USER | |
| base::FILE_PERMISSION_EXECUTE_BY_USER); |
| #endif // !BUILDFLAG(IS_FUCHSIA) |
| |
| HeadlessBrowserTest::SetUp(); |
| } |
| |
| void TearDown() override { |
| if (!launcher_script_.empty()) |
| base::DeleteFile(launcher_script_); |
| if (!launcher_stamp_.empty()) |
| base::DeleteFile(launcher_stamp_); |
| } |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| command_line->AppendSwitch("--no-sandbox"); |
| command_line->AppendSwitchASCII("--renderer-cmd-prefix", |
| launcher_script_.value()); |
| } |
| |
| private: |
| base::FilePath launcher_stamp_; |
| base::FilePath launcher_script_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(HeadlessBrowserRendererCommandPrefixTest, Prefix) { |
| base::ScopedAllowBlockingForTesting allow_blocking; |
| 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)); |
| |
| // Make sure the launcher was invoked when starting the renderer. |
| std::string stamp; |
| EXPECT_TRUE(base::ReadFileToString(launcher_stamp(), &stamp)); |
| EXPECT_GE(stamp.find("--type=renderer"), 0u); |
| } |
| #endif // BUILDFLAG(IS_POSIX) |
| |
| class CrashReporterTest : public HeadlessBrowserTest, |
| public HeadlessWebContents::Observer, |
| inspector::ExperimentalObserver { |
| public: |
| CrashReporterTest() {} |
| ~CrashReporterTest() override = default; |
| |
| void SetUp() override { |
| base::CreateNewTempDirectory(FILE_PATH_LITERAL("CrashReporterTest"), |
| &crash_dumps_dir_); |
| EXPECT_FALSE(options()->enable_crash_reporter); |
| options()->enable_crash_reporter = true; |
| options()->crash_dumps_dir = crash_dumps_dir_; |
| HeadlessBrowserTest::SetUp(); |
| } |
| |
| void TearDown() override { |
| base::DeleteFile(crash_dumps_dir_); |
| } |
| |
| // HeadlessWebContents::Observer implementation: |
| void DevToolsTargetReady() override { |
| EXPECT_TRUE(web_contents_->GetDevToolsTarget()); |
| devtools_client_ = HeadlessDevToolsClient::Create(); |
| web_contents_->GetDevToolsTarget()->AttachClient(devtools_client_.get()); |
| devtools_client_->GetInspector()->GetExperimental()->AddObserver(this); |
| } |
| |
| // inspector::ExperimentalObserver implementation: |
| void OnTargetCrashed(const inspector::TargetCrashedParams&) override { |
| FinishAsynchronousTest(); |
| } |
| |
| protected: |
| raw_ptr<HeadlessBrowserContext> browser_context_ = nullptr; |
| raw_ptr<HeadlessWebContents> web_contents_ = nullptr; |
| std::unique_ptr<HeadlessDevToolsClient> devtools_client_; |
| base::FilePath crash_dumps_dir_; |
| }; |
| |
| #if !BUILDFLAG(IS_FUCHSIA) && !BUILDFLAG(IS_WIN) |
| IN_PROC_BROWSER_TEST_F(CrashReporterTest, GenerateMinidump) { |
| content::ScopedAllowRendererCrashes scoped_allow_renderer_crashes; |
| |
| // Navigates a tab to chrome://crash and checks that a minidump is generated. |
| // Note that we only test renderer crashes here -- browser crashes need to be |
| // tested with a separate harness. |
| // |
| // The case where crash reporting is disabled is covered by |
| // HeadlessCrashObserverTest. |
| browser_context_ = browser()->CreateBrowserContextBuilder().Build(); |
| |
| web_contents_ = browser_context_->CreateWebContentsBuilder() |
| .SetInitialURL(GURL(blink::kChromeUICrashURL)) |
| .Build(); |
| |
| web_contents_->AddObserver(this); |
| RunAsynchronousTest(); |
| |
| // The target has crashed and should no longer be there. |
| EXPECT_FALSE(web_contents_->GetDevToolsTarget()); |
| |
| // Check that one minidump got created. |
| { |
| base::ScopedAllowBlockingForTesting allow_blocking; |
| |
| auto database = crashpad::CrashReportDatabase::Initialize(crash_dumps_dir_); |
| std::vector<crashpad::CrashReportDatabase::Report> reports; |
| ASSERT_EQ(database->GetPendingReports(&reports), |
| crashpad::CrashReportDatabase::kNoError); |
| EXPECT_EQ(reports.size(), 1u); |
| } |
| |
| web_contents_->RemoveObserver(this); |
| web_contents_->Close(); |
| web_contents_ = nullptr; |
| |
| browser_context_->Close(); |
| browser_context_ = nullptr; |
| } |
| #endif // !BUILDFLAG(IS_FUCHSIA) && !BUILDFLAG(IS_WIN) |
| |
| IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, PermissionManagerAlwaysASK) { |
| GURL url("https://example.com"); |
| |
| HeadlessBrowserContext* browser_context = |
| browser()->CreateBrowserContextBuilder().Build(); |
| |
| HeadlessWebContents* headless_web_contents = |
| browser_context->CreateWebContentsBuilder().Build(); |
| EXPECT_TRUE(headless_web_contents); |
| |
| HeadlessWebContentsImpl* web_contents = |
| HeadlessWebContentsImpl::From(headless_web_contents); |
| |
| content::PermissionControllerDelegate* permission_controller_delegate = |
| web_contents->browser_context()->GetPermissionControllerDelegate(); |
| EXPECT_NE(nullptr, permission_controller_delegate); |
| |
| // Check that the permission manager returns ASK for a given permission type. |
| EXPECT_EQ(blink::mojom::PermissionStatus::ASK, |
| permission_controller_delegate->GetPermissionStatus( |
| blink::PermissionType::NOTIFICATIONS, url, url)); |
| } |
| |
| namespace { |
| |
| class TraceHelper : public tracing::ExperimentalObserver { |
| public: |
| TraceHelper(HeadlessBrowserTest* browser_test, HeadlessDevToolsTarget* target) |
| : browser_test_(browser_test), |
| target_(target), |
| client_(HeadlessDevToolsClient::Create()), |
| tracing_data_(std::make_unique<base::ListValue>()) { |
| EXPECT_FALSE(target_->IsAttached()); |
| target_->AttachClient(client_.get()); |
| EXPECT_TRUE(target_->IsAttached()); |
| |
| client_->GetTracing()->GetExperimental()->AddObserver(this); |
| |
| client_->GetTracing()->GetExperimental()->Start( |
| tracing::StartParams::Builder().Build(), |
| base::BindOnce(&TraceHelper::OnTracingStarted, base::Unretained(this))); |
| } |
| |
| TraceHelper(const TraceHelper&) = delete; |
| TraceHelper& operator=(const TraceHelper&) = delete; |
| |
| ~TraceHelper() override { |
| target_->DetachClient(client_.get()); |
| EXPECT_FALSE(target_->IsAttached()); |
| } |
| |
| std::unique_ptr<base::ListValue> TakeTracingData() { |
| return std::move(tracing_data_); |
| } |
| |
| private: |
| void OnTracingStarted(std::unique_ptr<tracing::StartResult>) { |
| // We don't need the callback from End, but the OnTracingComplete event. |
| client_->GetTracing()->GetExperimental()->End( |
| tracing::EndParams::Builder().Build()); |
| } |
| |
| // tracing::ExperimentalObserver implementation: |
| void OnDataCollected(const tracing::DataCollectedParams& params) override { |
| for (const auto& value : *params.GetValue()) { |
| tracing_data_->Append(value->Clone()); |
| } |
| } |
| |
| void OnTracingComplete( |
| const tracing::TracingCompleteParams& params) override { |
| browser_test_->FinishAsynchronousTest(); |
| } |
| |
| raw_ptr<HeadlessBrowserTest> browser_test_; // Not owned. |
| raw_ptr<HeadlessDevToolsTarget> target_; // Not owned. |
| std::unique_ptr<HeadlessDevToolsClient> client_; |
| |
| std::unique_ptr<base::ListValue> tracing_data_; |
| }; |
| |
| } // namespace |
| |
| // Flaky, http://crbug.com/1269261. |
| #if BUILDFLAG(IS_WIN) |
| #define MAYBE_TraceUsingBrowserDevToolsTarget \ |
| DISABLED_TraceUsingBrowserDevToolsTarget |
| #else |
| #define MAYBE_TraceUsingBrowserDevToolsTarget TraceUsingBrowserDevToolsTarget |
| #endif // BUILDFLAG(IS_WIN) |
| IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, |
| MAYBE_TraceUsingBrowserDevToolsTarget) { |
| HeadlessDevToolsTarget* target = browser()->GetDevToolsTarget(); |
| EXPECT_NE(nullptr, target); |
| |
| TraceHelper helper(this, target); |
| RunAsynchronousTest(); |
| |
| std::unique_ptr<base::ListValue> tracing_data = helper.TakeTracingData(); |
| EXPECT_TRUE(tracing_data); |
| EXPECT_LT(0u, tracing_data->GetListDeprecated().size()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, WindowPrint) { |
| 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_FALSE( |
| EvaluateScript(web_contents, "window.print()")->HasExceptionDetails()); |
| } |
| |
| class HeadlessBrowserAllowInsecureLocalhostTest : public HeadlessBrowserTest { |
| public: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| command_line->AppendSwitch(::switches::kAllowInsecureLocalhost); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(HeadlessBrowserAllowInsecureLocalhostTest, |
| AllowInsecureLocalhostFlag) { |
| net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); |
| https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_EXPIRED); |
| https_server.ServeFilesFromSourceDirectory("headless/test/data"); |
| ASSERT_TRUE(https_server.Start()); |
| GURL test_url = https_server.GetURL("/hello.html"); |
| |
| HeadlessBrowserContext* browser_context = |
| browser()->CreateBrowserContextBuilder().Build(); |
| |
| HeadlessWebContentsImpl* web_contents = |
| HeadlessWebContentsImpl::From(browser_context->CreateWebContentsBuilder() |
| .SetInitialURL(test_url) |
| .Build()); |
| |
| // If the certificate fails to validate, this should fail. |
| EXPECT_TRUE(WaitForLoad(web_contents)); |
| } |
| |
| class HeadlessBrowserTestAppendCommandLineFlags : public HeadlessBrowserTest { |
| public: |
| HeadlessBrowserTestAppendCommandLineFlags() { |
| options()->append_command_line_flags_callback = base::BindRepeating( |
| &HeadlessBrowserTestAppendCommandLineFlags::AppendCommandLineFlags, |
| base::Unretained(this)); |
| } |
| |
| void AppendCommandLineFlags(base::CommandLine* command_line, |
| HeadlessBrowserContext* child_browser_context, |
| const std::string& child_process_type, |
| int child_process_id) { |
| if (child_process_type != "renderer") |
| return; |
| |
| callback_was_run_ = true; |
| EXPECT_LE(0, child_process_id); |
| EXPECT_NE(nullptr, command_line); |
| EXPECT_NE(nullptr, child_browser_context); |
| } |
| |
| protected: |
| bool callback_was_run_ = false; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(HeadlessBrowserTestAppendCommandLineFlags, |
| AppendChildProcessCommandLineFlags) { |
| // Create a new renderer process, and verify that callback was executed. |
| HeadlessBrowserContext* browser_context = |
| browser()->CreateBrowserContextBuilder().Build(); |
| // Used only for lifetime, thus std::ignore. |
| std::ignore = browser_context->CreateWebContentsBuilder() |
| .SetInitialURL(GURL("about:blank")) |
| .Build(); |
| |
| EXPECT_TRUE(callback_was_run_); |
| } |
| |
| #if BUILDFLAG(IS_FUCHSIA) |
| // TODO(crbug.com/1090933): Fix this test on Fuchsia and re-enable. |
| #define MAYBE_ServerWantsClientCertificate DISABLED_ServerWantsClientCertificate |
| #else |
| #define MAYBE_ServerWantsClientCertificate ServerWantsClientCertificate |
| #endif |
| IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, |
| MAYBE_ServerWantsClientCertificate) { |
| net::SSLServerConfig server_config; |
| server_config.client_cert_type = net::SSLServerConfig::OPTIONAL_CLIENT_CERT; |
| net::EmbeddedTestServer server(net::EmbeddedTestServer::TYPE_HTTPS); |
| server.SetSSLConfig(net::EmbeddedTestServer::CERT_AUTO, server_config); |
| server.ServeFilesFromSourceDirectory("headless/test/data"); |
| EXPECT_TRUE(server.Start()); |
| |
| HeadlessBrowserContext* browser_context = |
| browser()->CreateBrowserContextBuilder().Build(); |
| |
| HeadlessWebContents* web_contents = |
| browser_context->CreateWebContentsBuilder() |
| .SetInitialURL(server.GetURL("/hello.html")) |
| .Build(); |
| EXPECT_TRUE(WaitForLoad(web_contents)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, AIAFetching) { |
| net::EmbeddedTestServer server(net::EmbeddedTestServer::TYPE_HTTPS); |
| net::EmbeddedTestServer::ServerCertificateConfig cert_config; |
| cert_config.intermediate = net::EmbeddedTestServer::IntermediateType::kByAIA; |
| server.SetSSLConfig(cert_config); |
| server.AddDefaultHandlers(base::FilePath(FILE_PATH_LITERAL("net/data/ssl"))); |
| ASSERT_TRUE(server.Start()); |
| |
| HeadlessBrowserContext* browser_context = |
| browser()->CreateBrowserContextBuilder().Build(); |
| browser()->SetDefaultBrowserContext(browser_context); |
| |
| GURL url = server.GetURL("/defaultresponse"); |
| HeadlessWebContents* web_contents = |
| browser_context->CreateWebContentsBuilder().SetInitialURL(url).Build(); |
| EXPECT_TRUE(WaitForLoad(web_contents)); |
| content::NavigationEntry* last_entry = |
| HeadlessWebContentsImpl::From(web_contents) |
| ->web_contents() |
| ->GetController() |
| .GetLastCommittedEntry(); |
| EXPECT_FALSE(net::IsCertStatusError(last_entry->GetSSL().cert_status)); |
| EXPECT_EQ(url, last_entry->GetURL()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, BadgingAPI) { |
| EXPECT_TRUE(embedded_test_server()->Start()); |
| |
| HeadlessBrowserContext* browser_context = |
| browser()->CreateBrowserContextBuilder().Build(); |
| |
| GURL url = embedded_test_server()->GetURL("/badging_api.html"); |
| HeadlessWebContents* web_contents = |
| browser_context->CreateWebContentsBuilder().SetInitialURL(url).Build(); |
| |
| EXPECT_TRUE(WaitForLoad(web_contents)); |
| } |
| |
| class HeadlessBrowserTestWithExplicitlyAllowedPorts |
| : public HeadlessBrowserTest, |
| public testing::WithParamInterface<bool> { |
| protected: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| HeadlessBrowserTest::SetUpCommandLine(command_line); |
| if (is_port_allowed()) { |
| command_line->AppendSwitchASCII(switches::kExplicitlyAllowedPorts, |
| "10080"); |
| } |
| } |
| |
| bool is_port_allowed() { return GetParam(); } |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(HeadlessBrowserTestWithExplicitlyAllowedPorts, |
| HeadlessBrowserTestWithExplicitlyAllowedPorts, |
| testing::Values(false, true)); |
| |
| IN_PROC_BROWSER_TEST_P(HeadlessBrowserTestWithExplicitlyAllowedPorts, |
| AllowedPort) { |
| HeadlessBrowserContext* browser_context = |
| browser()->CreateBrowserContextBuilder().Build(); |
| |
| HeadlessWebContents* web_contents = |
| browser_context->CreateWebContentsBuilder() |
| .SetInitialURL(GURL("http://127.0.0.1:10080")) |
| .Build(); |
| |
| // If the port is allowed, the request is expected to fail for |
| // reasons other than ERR_UNSAFE_PORT. |
| net::Error error = net::OK; |
| EXPECT_FALSE(WaitForLoad(web_contents, &error)); |
| if (is_port_allowed()) |
| EXPECT_NE(error, net::ERR_UNSAFE_PORT); |
| else |
| EXPECT_EQ(error, net::ERR_UNSAFE_PORT); |
| } |
| |
| // This assures that both string and data blink resources are |
| // present. These are essential for correct rendering. |
| IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, LocalizedResources) { |
| EXPECT_THAT( |
| ui::ResourceBundle::GetSharedInstance().LoadLocalizedResourceString( |
| IDS_FORM_SUBMIT_LABEL), |
| testing::Eq("Submit")); |
| EXPECT_THAT(ui::ResourceBundle::GetSharedInstance().LoadDataResourceString( |
| IDR_UASTYLE_HTML_CSS), |
| testing::Ne("")); |
| } |
| |
| class SelectFileDialogHeadlessBrowserTest |
| : public HeadlessBrowserTest, |
| public testing::WithParamInterface< |
| std::tuple<const char*, ui::SelectFileDialog::Type>> { |
| public: |
| static constexpr char kTestMountPoint[] = "testfs"; |
| |
| void SetUp() override { |
| ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); |
| |
| // Register an external mount point to test support for virtual paths. |
| // This maps the virtual path a native local path to make these tests work |
| // on all platforms. |
| storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( |
| kTestMountPoint, storage::kFileSystemTypeLocal, |
| storage::FileSystemMountOption(), temp_dir_.GetPath()); |
| |
| HeadlessBrowserTest::SetUp(); |
| } |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| // Enable experimental web platform features to enable write access. |
| command_line->AppendSwitch( |
| ::switches::kEnableExperimentalWebPlatformFeatures); |
| } |
| |
| void TearDown() override { |
| HeadlessBrowserTest::TearDown(); |
| storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem( |
| kTestMountPoint); |
| ASSERT_TRUE(temp_dir_.Delete()); |
| } |
| |
| void WaitForSelectFileDialogCallback() { |
| if (select_file_dialog_type_ != ui::SelectFileDialog::SELECT_NONE) |
| return; |
| |
| ASSERT_FALSE(run_loop_); |
| run_loop_ = std::make_unique<base::RunLoop>( |
| base::RunLoop::Type::kNestableTasksAllowed); |
| run_loop_->Run(); |
| run_loop_ = nullptr; |
| } |
| |
| void OnSelectFileDialogCallback(ui::SelectFileDialog::Type type) { |
| select_file_dialog_type_ = type; |
| |
| if (run_loop_) |
| run_loop_->Quit(); |
| } |
| |
| const char* file_dialog_script() { return std::get<0>(GetParam()); } |
| ui::SelectFileDialog::Type expected_type() { return std::get<1>(GetParam()); } |
| |
| protected: |
| std::unique_ptr<base::RunLoop> run_loop_; |
| base::ScopedTempDir temp_dir_; |
| ui::SelectFileDialog::Type select_file_dialog_type_ = |
| ui::SelectFileDialog::SELECT_NONE; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| SelectFileDialogHeadlessBrowserTest, |
| SelectFileDialogHeadlessBrowserTest, |
| testing::Values(std::make_tuple("window.showOpenFilePicker()", |
| ui::SelectFileDialog::SELECT_OPEN_FILE), |
| std::make_tuple("window.showSaveFilePicker()", |
| ui::SelectFileDialog::SELECT_SAVEAS_FILE), |
| std::make_tuple("window.showDirectoryPicker()", |
| ui::SelectFileDialog::SELECT_FOLDER))); |
| |
| IN_PROC_BROWSER_TEST_P(SelectFileDialogHeadlessBrowserTest, SelectFileDialog) { |
| base::ScopedAllowBlockingForTesting allow_blocking; |
| ASSERT_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(); |
| ASSERT_TRUE(WaitForLoad(web_contents)); |
| |
| // Select file dialog will not be shown if the owning frame does not |
| // have user activation, see VerifyIsAllowedToShowFilePicker in |
| // third_party/blink/renderer/.../global_file_system_access.cc |
| content::WebContents* content = |
| HeadlessWebContentsImpl::From(web_contents)->web_contents(); |
| content::RenderFrameHost* main_frame = content->GetPrimaryMainFrame(); |
| main_frame->NotifyUserActivation( |
| blink::mojom::UserActivationNotificationType::kTest); |
| |
| HeadlessSelectFileDialogFactory::SetSelectFileDialogOnceCallbackForTests( |
| base::BindOnce( |
| &SelectFileDialogHeadlessBrowserTest::OnSelectFileDialogCallback, |
| base::Unretained(this))); |
| |
| EvaluateScript(web_contents, file_dialog_script()); |
| WaitForSelectFileDialogCallback(); |
| |
| EXPECT_EQ(select_file_dialog_type_, expected_type()); |
| } |
| |
| } // namespace headless |