| // Copyright 2019 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 <string> |
| |
| #include "base/strings/utf_string_conversions.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_window.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "components/embedder_support/switches.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/test/browser_test.h" |
| #include "ui/display/display.h" |
| #include "ui/display/screen.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/views/widget/widget_observer.h" |
| #include "url/gurl.h" |
| |
| namespace { |
| |
| // Tests of window placement for popup browser windows. Test fixtures are run |
| // with and without the experimental WindowPlacement blink feature. |
| class PopupBrowserTest : public InProcessBrowserTest, |
| public ::testing::WithParamInterface<bool> { |
| protected: |
| PopupBrowserTest() = default; |
| ~PopupBrowserTest() override = default; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| InProcessBrowserTest::SetUpCommandLine(command_line); |
| base::CommandLine::ForCurrentProcess()->AppendSwitch( |
| embedder_support::kDisablePopupBlocking); |
| const bool enable_window_placement = GetParam(); |
| base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( |
| enable_window_placement ? switches::kEnableBlinkFeatures |
| : switches::kDisableBlinkFeatures, |
| "WindowPlacement"); |
| } |
| |
| display::Display GetDisplayNearestBrowser(const Browser* browser) const { |
| return display::Screen::GetScreen()->GetDisplayNearestWindow( |
| browser->window()->GetNativeWindow()); |
| } |
| |
| Browser* OpenPopup(Browser* browser, const std::string& script) const { |
| auto* contents = browser->tab_strip_model()->GetActiveWebContents(); |
| content::ExecuteScriptAsync(contents, script); |
| Browser* popup = ui_test_utils::WaitForBrowserToOpen(); |
| EXPECT_NE(popup, browser); |
| auto* popup_contents = popup->tab_strip_model()->GetActiveWebContents(); |
| EXPECT_TRUE(WaitForRenderFrameReady(popup_contents->GetMainFrame())); |
| return popup; |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(PopupBrowserTest); |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, PopupBrowserTest, ::testing::Bool()); |
| |
| // A helper class to wait for widget bounds changes beyond given thresholds. |
| class WidgetBoundsChangeWaiter : public views::WidgetObserver { |
| public: |
| WidgetBoundsChangeWaiter(views::Widget* widget, int move_by, int resize_by) |
| : widget_(widget), |
| move_by_(move_by), |
| resize_by_(resize_by), |
| initial_bounds_(widget->GetWindowBoundsInScreen()) { |
| widget_->AddObserver(this); |
| } |
| |
| WidgetBoundsChangeWaiter(const WidgetBoundsChangeWaiter&) = delete; |
| WidgetBoundsChangeWaiter& operator=(const WidgetBoundsChangeWaiter&) = delete; |
| ~WidgetBoundsChangeWaiter() final { widget_->RemoveObserver(this); } |
| |
| // views::WidgetObserver: |
| void OnWidgetBoundsChanged(views::Widget* widget, |
| const gfx::Rect& rect) final { |
| if (BoundsChangeMeetsThreshold(rect)) { |
| widget_->RemoveObserver(this); |
| run_loop_.Quit(); |
| } |
| } |
| |
| // Wait for changes to occur, or return immediately if they already have. |
| void Wait() { |
| if (!BoundsChangeMeetsThreshold(widget_->GetWindowBoundsInScreen())) |
| run_loop_.Run(); |
| } |
| |
| private: |
| bool BoundsChangeMeetsThreshold(const gfx::Rect& rect) const { |
| return (std::abs(rect.x() - initial_bounds_.x()) >= move_by_ || |
| std::abs(rect.y() - initial_bounds_.y()) >= move_by_) && |
| (std::abs(rect.width() - initial_bounds_.width()) >= resize_by_ || |
| std::abs(rect.height() - initial_bounds_.height()) >= resize_by_); |
| } |
| |
| views::Widget* const widget_; |
| const int move_by_, resize_by_; |
| const gfx::Rect initial_bounds_; |
| base::RunLoop run_loop_; |
| }; |
| |
| // Ensure popups are opened in the available space of the opener's display. |
| IN_PROC_BROWSER_TEST_P(PopupBrowserTest, OpenClampedToCurrentDisplay) { |
| const auto display = GetDisplayNearestBrowser(browser()); |
| EXPECT_TRUE(display.work_area().Contains(browser()->window()->GetBounds())) |
| << "The browser window should be contained by its display's work area"; |
| |
| // Attempt to open a popup outside the bounds of the opener's display. |
| const char* const open_scripts[] = { |
| "open('.', '', 'left=' + (screen.availLeft - 50));", |
| "open('.', '', 'left=' + (screen.availLeft + screen.availWidth + 50));", |
| "open('.', '', 'top=' + (screen.availTop - 50));", |
| "open('.', '', 'top=' + (screen.availTop + screen.availHeight + 50));", |
| "open('.', '', 'left=' + (screen.availLeft - 50) + " |
| "',top=' + (screen.availTop - 50));", |
| "open('.', '', 'left=' + (screen.availLeft - 50) + " |
| "',top=' + (screen.availTop - 50) + " |
| "',width=300,height=300');", |
| "open('.', '', 'left=' + (screen.availLeft + screen.availWidth + 50) + " |
| "',top=' + (screen.availTop + screen.availHeight + 50) + " |
| "',width=300,height=300');", |
| "open('.', '', 'left=' + screen.availLeft + ',top=' + screen.availTop + " |
| "',width=' + (screen.availWidth + 300) + ',height=300');", |
| "open('.', '', 'left=' + screen.availLeft + ',top=' + screen.availTop + " |
| "',width=300,height='+ (screen.availHeight + 300));", |
| "open('.', '', 'left=' + screen.availLeft + ',top=' + screen.availTop + " |
| "',width=' + (screen.availWidth + 300) + " |
| "',height='+ (screen.availHeight + 300));", |
| }; |
| for (auto* const script : open_scripts) { |
| Browser* popup = OpenPopup(browser(), script); |
| // The popup should be constrained to the opener's available display space. |
| // TODO(crbug.com/897300): Wait for the final window placement to occur; |
| // this is flakily checking initial or intermediate window placement bounds. |
| EXPECT_EQ(display, GetDisplayNearestBrowser(popup)); |
| EXPECT_TRUE(display.work_area().Contains(popup->window()->GetBounds())) |
| << " script: " << script |
| << " work_area: " << display.work_area().ToString() |
| << " popup: " << popup->window()->GetBounds().ToString(); |
| } |
| } |
| |
| // Ensure popups cannot be moved beyond the available display space by script. |
| IN_PROC_BROWSER_TEST_P(PopupBrowserTest, MoveClampedToCurrentDisplay) { |
| const auto display = GetDisplayNearestBrowser(browser()); |
| const char kOpenPopup[] = |
| "open('.', '', 'left=' + (screen.availLeft + 50) + " |
| "',top=' + (screen.availTop + 50) + " |
| "',width=300,height=300');"; |
| const char* const kMoveScripts[] = { |
| "moveBy(screen.availWidth * 2, 0);", |
| "moveBy(screen.availWidth * -2, 0);", |
| "moveBy(0, screen.availHeight * 2);", |
| "moveBy(0, screen.availHeight * -2);", |
| "moveBy(screen.availWidth * 2, screen.availHeight * 2);", |
| "moveBy(screen.availWidth * -2, screen.availHeight * -2);", |
| "moveTo(screen.availLeft + screen.availWidth + 50, screen.availTop);", |
| "moveTo(screen.availLeft - 50, screen.availTop);", |
| "moveTo(screen.availLeft, screen.availTop + screen.availHeight + 50);", |
| "moveTo(screen.availLeft, screen.availTop - 50);", |
| ("moveTo(screen.availLeft + screen.availWidth + 50, " |
| "screen.availTop + screen.availHeight + 50);"), |
| "moveTo(screen.availLeft - 50, screen.availTop - 50);", |
| }; |
| for (auto* const script : kMoveScripts) { |
| Browser* popup = OpenPopup(browser(), kOpenPopup); |
| auto popup_bounds = popup->window()->GetBounds(); |
| auto* popup_contents = popup->tab_strip_model()->GetActiveWebContents(); |
| auto* widget = views::Widget::GetWidgetForNativeWindow( |
| popup->window()->GetNativeWindow()); |
| |
| content::ExecuteScriptAsync(popup_contents, script); |
| // Wait for the substantial move, widgets may move during initialization. |
| WidgetBoundsChangeWaiter(widget, /*move_by=*/40, /*resize_by=*/0).Wait(); |
| EXPECT_NE(popup_bounds.origin(), popup->window()->GetBounds().origin()); |
| EXPECT_EQ(popup_bounds.size(), popup->window()->GetBounds().size()); |
| EXPECT_TRUE(display.work_area().Contains(popup->window()->GetBounds())) |
| << " script: " << script |
| << " work_area: " << display.work_area().ToString() |
| << " popup: " << popup_bounds.ToString(); |
| } |
| } |
| |
| // Ensure popups cannot be resized beyond the available display space by script. |
| IN_PROC_BROWSER_TEST_P(PopupBrowserTest, ResizeClampedToCurrentDisplay) { |
| const auto display = GetDisplayNearestBrowser(browser()); |
| const char kOpenPopup[] = |
| "open('.', '', 'left=' + (screen.availLeft + 50) + " |
| "',top=' + (screen.availTop + 50) + " |
| "',width=300,height=300');"; |
| // The popup cannot be resized beyond the current screen by script. |
| const char* const kResizeScripts[] = { |
| "resizeBy(screen.availWidth * 2, 0);", |
| "resizeBy(0, screen.availHeight * 2);", |
| "resizeTo(screen.availWidth + 200, 200);", |
| "resizeTo(200, screen.availHeight + 200);", |
| "resizeTo(screen.availWidth + 200, screen.availHeight + 200);", |
| }; |
| for (auto* const script : kResizeScripts) { |
| Browser* popup = OpenPopup(browser(), kOpenPopup); |
| auto popup_bounds = popup->window()->GetBounds(); |
| auto* popup_contents = popup->tab_strip_model()->GetActiveWebContents(); |
| auto* widget = views::Widget::GetWidgetForNativeWindow( |
| popup->window()->GetNativeWindow()); |
| |
| content::ExecuteScriptAsync(popup_contents, script); |
| // Wait for the substantial resize, widgets may move during initialization. |
| WidgetBoundsChangeWaiter(widget, /*move_by=*/0, /*resize_by=*/100).Wait(); |
| EXPECT_NE(popup_bounds.size(), popup->window()->GetBounds().size()); |
| EXPECT_TRUE(display.work_area().Contains(popup->window()->GetBounds())) |
| << " script: " << script |
| << " work_area: " << display.work_area().ToString() |
| << " popup: " << popup_bounds.ToString(); |
| } |
| } |
| |
| } // namespace |