blob: 6e187b3c5aba5e444407c77399d859498e7d0095 [file] [log] [blame]
Avi Drissman4a8573c2022-09-09 19:35:541// Copyright 2021 The Chromium Authors
Peter Kvitek6e5bfc222021-11-12 23:33:292// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Peter Kvitek16f010c2022-04-29 05:02:395#include "chrome/browser/headless/headless_mode_browsertest.h"
Peter Kvitek6e5bfc222021-11-12 23:33:296
Peter Kvitek2754d7e2021-12-13 21:03:537#include "build/build_config.h"
8
Peter Kvitekd64628e2022-02-04 22:46:329// Native headless is currently available on Linux, Windows and Mac platforms.
Peter Kvitek2754d7e2021-12-13 21:03:5310// More platforms will be added later, so avoid function level clutter by
11// providing a compile time condition over the entire file.
Peter Kvitekd64628e2022-02-04 22:46:3212#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
Peter Kvitek2754d7e2021-12-13 21:03:5313
Peter Kvitek6e5bfc222021-11-12 23:33:2914#include <memory>
15#include <string>
16
17#include "base/command_line.h"
Peter Kvitekb95e2232021-12-10 00:40:0818#include "base/files/file.h"
Peter Kvitekb95e2232021-12-10 00:40:0819#include "base/files/file_util.h"
Peter Kvitek5f51f5a2023-07-19 00:45:3820#include "base/functional/bind.h"
21#include "base/logging.h"
22#include "base/run_loop.h"
Peter Kvitekb95e2232021-12-10 00:40:0823#include "base/test/task_environment.h"
24#include "base/test/test_timeouts.h"
Peter Kvitek2cd1a9b2023-01-31 19:34:4525#include "base/threading/thread_restrictions.h"
Peter Kvitek16f010c2022-04-29 05:02:3926#include "chrome/browser/headless/headless_mode_util.h"
Peter Kvitekb95e2232021-12-10 00:40:0827#include "chrome/browser/process_singleton.h"
Peter Kvitek2cd1a9b2023-01-31 19:34:4528#include "chrome/browser/profiles/profile.h"
Peter Kvitek6e5bfc222021-11-12 23:33:2929#include "chrome/browser/ui/browser.h"
Peter Kvitekdbde9eb2022-05-21 00:29:3630#include "chrome/browser/ui/browser_commands.h"
Peter Kvitek69907fc2023-01-31 18:07:4931#include "chrome/browser/ui/browser_window.h"
Peter Kvitekdbde9eb2022-05-21 00:29:3632#include "chrome/browser/ui/exclusive_access/exclusive_access_test.h"
Peter Kvitek49a1e142023-11-30 18:32:2333#include "chrome/browser/ui/page_info/page_info_infobar_delegate.h"
Peter Kvitekdbdb4d22023-12-01 00:39:5334#include "chrome/browser/ui/views/frame/app_menu_button.h"
35#include "chrome/browser/ui/views/frame/browser_view.h"
36#include "chrome/browser/ui/views/frame/toolbar_button_provider.h"
Peter Kvitekb95e2232021-12-10 00:40:0837#include "chrome/common/chrome_switches.h"
Peter Kvitek49a1e142023-11-30 18:32:2338#include "components/headless/clipboard/headless_clipboard.h" // nogncheck
39#include "components/infobars/content/content_infobar_manager.h" // nogncheck
Peter Kvitek5f51f5a2023-07-19 00:45:3840#include "content/public/browser/browser_context.h"
41#include "content/public/browser/browser_task_traits.h"
42#include "content/public/browser/browser_thread.h"
43#include "content/public/browser/navigation_controller.h"
44#include "content/public/browser/web_contents.h"
Peter Kvitek7dd69b9c2022-05-13 19:24:0845#include "content/public/common/content_switches.h"
Peter Kvitekb95e2232021-12-10 00:40:0846#include "content/public/test/browser_task_environment.h"
Peter Kvitek6e5bfc222021-11-12 23:33:2947#include "content/public/test/browser_test_utils.h"
Peter Kvitek17836682023-01-18 07:24:3748#include "net/test/embedded_test_server/embedded_test_server.h"
Peter Kvitek6e5bfc222021-11-12 23:33:2949#include "testing/gmock/include/gmock/gmock.h"
Peter Kvitekf6a06c62023-02-07 18:06:0050#include "ui/base/clipboard/clipboard.h"
Peter Kvitek22202802023-09-22 21:07:3451#include "ui/base/clipboard/clipboard_non_backed.h"
Peter Kvitekf6a06c62023-02-07 18:06:0052#include "ui/base/clipboard/clipboard_sequence_number_token.h"
53#include "ui/base/clipboard/scoped_clipboard_writer.h"
Peter Kvitekdbdb4d22023-12-01 00:39:5354#include "ui/base/models/dialog_model.h"
Peter Kvitek2a2b92a2023-03-01 23:22:3955#include "ui/display/display_switches.h"
Peter Kvitekf3c187af72023-11-28 00:53:2256#include "ui/gfx/native_widget_types.h"
Peter Kvitek6e5bfc222021-11-12 23:33:2957#include "ui/gfx/switches.h"
Peter Kvitekdbdb4d22023-12-01 00:39:5358#include "ui/views/bubble/bubble_dialog_model_host.h"
59#include "ui/views/view.h"
Peter Kvitekf3c187af72023-11-28 00:53:2260#include "ui/views/widget/widget.h"
Peter Kvitek5f51f5a2023-07-19 00:45:3861#include "url/gurl.h"
Peter Kvitek6e5bfc222021-11-12 23:33:2962
Peter Kvitekf3c187af72023-11-28 00:53:2263using testing::Ge;
64using testing::SizeIs;
Peter Kvitekdbdb4d22023-12-01 00:39:5365using views::Widget;
Peter Kvitekf3c187af72023-11-28 00:53:2266
Peter Kvitek1034c08d2023-01-24 17:54:0767namespace headless {
68
Peter Kvitek3a91cb82023-01-24 01:41:2669namespace switches {
70// This switch runs tests in headful mode, intended for experiments only because
71// not all tests are expected to pass in headful mode.
72static const char kHeadfulMode[] = "headful-mode";
73} // namespace switches
74
Peter Kvitek17836682023-01-18 07:24:3775HeadlessModeBrowserTest::HeadlessModeBrowserTest() {
76 base::FilePath test_data(
77 FILE_PATH_LITERAL("chrome/browser/headless/test/data"));
78 embedded_test_server()->AddDefaultHandlers(test_data);
79}
80
Peter Kvitek16f010c2022-04-29 05:02:3981void HeadlessModeBrowserTest::SetUpCommandLine(
82 base::CommandLine* command_line) {
Peter Kvitek2cd1a9b2023-01-31 19:34:4583 AppendHeadlessCommandLineSwitches(command_line);
84}
Peter Kvitek6e5bfc222021-11-12 23:33:2985
Peter Kvitek2cd1a9b2023-01-31 19:34:4586void HeadlessModeBrowserTest::SetUpOnMainThread() {
87 InProcessBrowserTest::SetUpOnMainThread();
88
89 ASSERT_TRUE(headless::IsHeadlessMode() || headful_mode());
90}
91
92void HeadlessModeBrowserTest::AppendHeadlessCommandLineSwitches(
93 base::CommandLine* command_line) {
Peter Kvitek3a91cb82023-01-24 01:41:2694 if (command_line->HasSwitch(switches::kHeadfulMode)) {
95 headful_mode_ = true;
96 } else {
Peter Kvitek1034c08d2023-01-24 17:54:0797 command_line->AppendSwitchASCII(::switches::kHeadless,
98 kHeadlessSwitchValue);
Peter Kvitek099ad6ea2023-09-06 19:53:3999 headless::InitHeadlessMode();
Peter Kvitek3a91cb82023-01-24 01:41:26100 }
Peter Kvitek16f010c2022-04-29 05:02:39101}
Peter Kvitek6e5bfc222021-11-12 23:33:29102
Peter Kvitek49a1e142023-11-30 18:32:23103content::WebContents* HeadlessModeBrowserTest::GetActiveWebContents() {
104 return browser()->tab_strip_model()->GetActiveWebContents();
105}
106
Peter Kvitek2cd1a9b2023-01-31 19:34:45107void HeadlessModeBrowserTestWithUserDataDir::SetUpCommandLine(
108 base::CommandLine* command_line) {
109 ASSERT_TRUE(user_data_dir_.CreateUniqueTempDir());
110 ASSERT_TRUE(base::IsDirectoryEmpty(user_data_dir()));
111 command_line->AppendSwitchPath(::switches::kUserDataDir, user_data_dir());
Peter Kvitek6e5bfc222021-11-12 23:33:29112
Peter Kvitek2cd1a9b2023-01-31 19:34:45113 AppendHeadlessCommandLineSwitches(command_line);
Peter Kvitek16f010c2022-04-29 05:02:39114}
Peter Kvitek6e5bfc222021-11-12 23:33:29115
Peter Kvitek7dd69b9c2022-05-13 19:24:08116void HeadlessModeBrowserTestWithStartWindowMode::SetUpCommandLine(
117 base::CommandLine* command_line) {
118 HeadlessModeBrowserTest::SetUpCommandLine(command_line);
119
120 switch (start_window_mode()) {
121 case kStartWindowNormal:
122 break;
123 case kStartWindowMaximized:
Peter Kvitek1034c08d2023-01-24 17:54:07124 command_line->AppendSwitch(::switches::kStartMaximized);
Peter Kvitek7dd69b9c2022-05-13 19:24:08125 break;
126 case kStartWindowFullscreen:
Peter Kvitek1034c08d2023-01-24 17:54:07127 command_line->AppendSwitch(::switches::kStartFullscreen);
Peter Kvitek7dd69b9c2022-05-13 19:24:08128 break;
129 }
130}
131
Peter Kvitekdbde9eb2022-05-21 00:29:36132void ToggleFullscreenModeSync(Browser* browser) {
133 FullscreenNotificationObserver observer(browser);
134 chrome::ToggleFullscreenMode(browser);
135 observer.Wait();
136}
137
Peter Kvitek2a2b92a2023-03-01 23:22:39138void HeadlessModeBrowserTestWithWindowSize::SetUpCommandLine(
139 base::CommandLine* command_line) {
140 HeadlessModeBrowserTest::SetUpCommandLine(command_line);
141 command_line->AppendSwitchASCII(
142 ::switches::kWindowSize,
143 base::StringPrintf("%u,%u", kWindowSize.width(), kWindowSize.height()));
144}
145
146void HeadlessModeBrowserTestWithWindowSizeAndScale::SetUpCommandLine(
147 base::CommandLine* command_line) {
148 HeadlessModeBrowserTest::SetUpCommandLine(command_line);
149 command_line->AppendSwitchASCII(
150 ::switches::kWindowSize,
151 base::StringPrintf("%u,%u", kWindowSize.width(), kWindowSize.height()));
152 command_line->AppendSwitchASCII(::switches::kForceDeviceScaleFactor, "1.5");
153}
154
Peter Kvitek1034c08d2023-01-24 17:54:07155namespace {
156
Peter Kvitek2cd1a9b2023-01-31 19:34:45157// Miscellaneous tests -------------------------------------------------------
158
Peter Kvitek69907fc2023-01-31 18:07:49159IN_PROC_BROWSER_TEST_F(HeadlessModeBrowserTest, BrowserWindowIsActive) {
160 EXPECT_TRUE(browser()->window()->IsActive());
161}
162
Peter Kvitek49a1e142023-11-30 18:32:23163IN_PROC_BROWSER_TEST_F(HeadlessModeBrowserTest, NoInfoBarInHeadless) {
164 content::WebContents* web_contents = GetActiveWebContents();
165 ASSERT_TRUE(web_contents);
166
167 infobars::ContentInfoBarManager* infobar_manager =
168 infobars::ContentInfoBarManager::FromWebContents(web_contents);
169 ASSERT_TRUE(infobar_manager);
170
171 PageInfoInfoBarDelegate::Create(infobar_manager);
172
173 EXPECT_THAT(infobar_manager->infobars(), testing::IsEmpty());
174}
175
176// UserAgent tests -----------------------------------------------------------
177
Peter Kvitek5f51f5a2023-07-19 00:45:38178class HeadlessModeUserAgentBrowserTest : public HeadlessModeBrowserTest {
179 public:
180 HeadlessModeUserAgentBrowserTest() = default;
181 ~HeadlessModeUserAgentBrowserTest() override = default;
182
183 void SetUp() override {
184 embedded_test_server()->RegisterRequestHandler(
185 base::BindRepeating(&HeadlessModeUserAgentBrowserTest::RequestHandler,
186 base::Unretained(this)));
187
188 ASSERT_TRUE(embedded_test_server()->Start());
189
190 HeadlessModeBrowserTest::SetUp();
191 }
192
193 protected:
194 std::unique_ptr<net::test_server::HttpResponse> RequestHandler(
195 const net::test_server::HttpRequest& request) {
196 if (request.relative_url == "/page.html") {
197 headers_ = request.headers;
198
199 content::GetUIThreadTaskRunner({})->PostTask(
200 FROM_HERE,
201 base::BindOnce(&HeadlessModeUserAgentBrowserTest::FinishTest,
202 base::Unretained(this)));
203
204 auto response = std::make_unique<net::test_server::BasicHttpResponse>();
205 response->set_code(net::HTTP_OK);
206 response->set_content_type("text/html");
207 response->set_content(R"(<div>Hi, I'm headless!</div>)");
208
209 return response;
210 }
211
212 return nullptr;
213 }
214
215 void RunLoop() {
216 if (!test_complete_) {
217 run_loop_ = std::make_unique<base::RunLoop>();
218 run_loop_->Run();
219 run_loop_.reset();
220 }
221 }
222
223 void FinishTest() {
224 test_complete_ = true;
225 if (run_loop_) {
226 run_loop_->Quit();
227 }
228 }
229
230 bool test_complete_ = false;
231 std::unique_ptr<base::RunLoop> run_loop_;
232 net::test_server::HttpRequest::HeaderMap headers_;
233};
234
235IN_PROC_BROWSER_TEST_F(HeadlessModeUserAgentBrowserTest, UserAgentHasHeadless) {
236 content::BrowserContext* browser_context = browser()->profile();
237 DCHECK(browser_context);
238
239 content::WebContents::CreateParams create_params(browser_context);
240 std::unique_ptr<content::WebContents> web_contents =
241 content::WebContents::Create(create_params);
242 DCHECK(web_contents);
243
244 GURL url = embedded_test_server()->GetURL("/page.html");
245 content::NavigationController::LoadURLParams params(url);
246 web_contents->GetController().LoadURLWithParams(params);
247
248 RunLoop();
249
250 web_contents->Close();
251 web_contents.reset();
252
253 base::RunLoop().RunUntilIdle();
254
255 EXPECT_THAT(headers_.at("User-Agent"), testing::HasSubstr("HeadlessChrome/"));
256}
257
Peter Kvitek2cd1a9b2023-01-31 19:34:45258// Incognito mode tests ------------------------------------------------------
259
Peter Kvitek2cd1a9b2023-01-31 19:34:45260IN_PROC_BROWSER_TEST_F(HeadlessModeBrowserTestWithUserDataDir,
261 StartWithUserDataDir) {
262 // With user data dir expect to start in non incognito mode.
263 EXPECT_FALSE(browser()->profile()->IsOffTheRecord());
264}
265
266class HeadlessModeBrowserTestWithUserDataDirAndIncognito
267 : public HeadlessModeBrowserTestWithUserDataDir {
268 public:
269 HeadlessModeBrowserTestWithUserDataDirAndIncognito() = default;
270 ~HeadlessModeBrowserTestWithUserDataDirAndIncognito() override = default;
271
272 void SetUpCommandLine(base::CommandLine* command_line) override {
273 HeadlessModeBrowserTestWithUserDataDir::SetUpCommandLine(command_line);
274 command_line->AppendSwitch(::switches::kIncognito);
275 }
276};
277
278IN_PROC_BROWSER_TEST_F(HeadlessModeBrowserTestWithUserDataDirAndIncognito,
279 StartWithUserDataDirAndIncognito) {
280 // With user data dir and incognito expect to start in incognito mode.
281 EXPECT_TRUE(browser()->profile()->IsOffTheRecord());
282}
283
Peter Kvitekf6a06c62023-02-07 18:06:00284// Clipboard tests -----------------------------------------------------------
285
286IN_PROC_BROWSER_TEST_F(HeadlessModeBrowserTest, HeadlessClipboardInstalled) {
Peter Kvitek22202802023-09-22 21:07:34287 ui::Clipboard* clipboard = ui::ClipboardNonBacked::GetForCurrentThread();
Peter Kvitekf6a06c62023-02-07 18:06:00288 ASSERT_TRUE(clipboard);
289
290 ui::ClipboardBuffer buffer = ui::ClipboardBuffer::kCopyPaste;
291 ASSERT_TRUE(ui::Clipboard::IsSupportedClipboardBuffer(buffer));
292
293 // Expect sequence number to be incremented. This confirms that the headless
294 // clipboard implementation is being used.
295 int request_counter = GetSequenceNumberRequestCounterForTesting();
296 clipboard->GetSequenceNumber(buffer);
297 EXPECT_GT(GetSequenceNumberRequestCounterForTesting(), request_counter);
298}
299
300IN_PROC_BROWSER_TEST_F(HeadlessModeBrowserTest, HeadlessClipboardCopyPaste) {
301 ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread();
302 ASSERT_TRUE(clipboard);
303
Peter Kvitek22202802023-09-22 21:07:34304 static const struct ClipboardBufferInfo {
305 ui::ClipboardBuffer buffer;
306 std::u16string paste_text;
307 } clipboard_buffers[] = {
308 {ui::ClipboardBuffer::kCopyPaste, u"kCopyPaste"},
309 {ui::ClipboardBuffer::kSelection, u"kSelection"},
310 {ui::ClipboardBuffer::kDrag, u"kDrag"},
311 };
Peter Kvitekf6a06c62023-02-07 18:06:00312
Peter Kvitek22202802023-09-22 21:07:34313 // Check basic write/read ops into each buffer type.
314 for (const auto& [buffer, paste_text] : clipboard_buffers) {
315 if (!ui::Clipboard::IsSupportedClipboardBuffer(buffer)) {
316 continue;
317 }
318 {
319 ui::ScopedClipboardWriter writer(buffer);
320 writer.WriteText(paste_text);
321 }
322 std::u16string copy_text;
323 clipboard->ReadText(buffer, /* data_dst = */ nullptr, &copy_text);
324 EXPECT_EQ(paste_text, copy_text);
325 }
Peter Kvitekf6a06c62023-02-07 18:06:00326
Peter Kvitek22202802023-09-22 21:07:34327 // Verify that different clipboard buffer data is independent.
328 for (const auto& [buffer, paste_text] : clipboard_buffers) {
329 if (!ui::Clipboard::IsSupportedClipboardBuffer(buffer)) {
330 continue;
331 }
332 std::u16string copy_text;
333 clipboard->ReadText(buffer, /* data_dst = */ nullptr, &copy_text);
334 EXPECT_EQ(paste_text, copy_text);
335 }
Peter Kvitekf6a06c62023-02-07 18:06:00336}
337
Peter Kvitekdbdb4d22023-12-01 00:39:53338// Bubble tests --------------------------------------------------------------
339
340class TestBubbleDelegate : public ui::DialogModelDelegate {
341 public:
342 TestBubbleDelegate() = default;
343 ~TestBubbleDelegate() override = default;
344
345 void OnOkButton() { dialog_model()->host()->Close(); }
346};
347
348Widget* ShowTestBubble(Browser* browser) {
349 views::View* anchor_view = BrowserView::GetBrowserViewForBrowser(browser)
350 ->toolbar_button_provider()
351 ->GetAppMenuButton();
352
353 auto bubble_delegate = std::make_unique<TestBubbleDelegate>();
354 TestBubbleDelegate* bubble_delegate_ptr = bubble_delegate.get();
355
356 ui::DialogModel::Builder dialog_builder(std::move(bubble_delegate));
Peter Boström8c07ba02023-12-19 21:02:43357 dialog_builder.SetTitle(u"Test bubble")
358 .AddParagraph(ui::DialogModelLabel(u"Test bubble text"))
359 .AddOkButton(base::BindOnce(&TestBubbleDelegate::OnOkButton,
360 base::Unretained(bubble_delegate_ptr)),
361 ui::DialogModel::Button::Params().SetLabel(u"OK"));
Peter Kvitekdbdb4d22023-12-01 00:39:53362
363 auto bubble = std::make_unique<views::BubbleDialogModelHost>(
364 dialog_builder.Build(), anchor_view, views::BubbleBorder::TOP_RIGHT);
365
366 Widget* widget = views::BubbleDialogDelegate::CreateBubble(std::move(bubble));
367 widget->Show();
368
369 return widget;
370}
371
372IN_PROC_BROWSER_TEST_F(HeadlessModeBrowserTest, HeadlessBubbleVisibility) {
373 // Desktop window widget should be headless.
374 gfx::NativeWindow desktop_window = browser()->window()->GetNativeWindow();
375 Widget* desktop_widget = Widget::GetWidgetForNativeWindow(desktop_window);
376 ASSERT_TRUE(desktop_widget);
377 ASSERT_TRUE(desktop_widget->is_headless());
378
379 // Bubble widget is expected to be headless.
380 Widget* bubble_widget = ShowTestBubble(browser());
381 EXPECT_TRUE(bubble_widget->is_headless());
382
383#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
384 // On Windows and Mac in headless mode we still have actual platform
385 // windows which are always hidden, so verify that they are not visible.
386 EXPECT_FALSE(IsPlatformWindowVisible(bubble_widget));
387#elif BUILDFLAG(IS_LINUX)
388 // On Linux headless mode uses Ozone/Headless where platform windows are not
389 // backed up by any real windows, so verify that their visibility state
390 // matches the widget's visibility state.
391 EXPECT_EQ(IsPlatformWindowVisible(bubble_widget), bubble_widget->IsVisible());
392#else
393#error Unsupported platform
394#endif
395}
396
Peter Kvitek1034c08d2023-01-24 17:54:07397} // namespace
398
399} // namespace headless
400
Peter Kvitekd64628e2022-02-04 22:46:32401#endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)