1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <cmath>
6
7 #include "base/optional.h"
8 #include "base/run_loop.h"
9 #include "build/build_config.h"
10 #include "chrome/browser/ui/browser_commands.h"
11 #include "chrome/browser/ui/tabs/tab_strip_model.h"
12 #include "chrome/browser/ui/view_ids.h"
13 #include "chrome/browser/ui/views/frame/app_menu_button.h"
14 #include "chrome/browser/ui/views/frame/browser_non_client_frame_view.h"
15 #include "chrome/browser/ui/views/frame/browser_view.h"
16 #include "chrome/browser/ui/views/frame/toolbar_button_provider.h"
17 #include "chrome/browser/ui/views/page_action/page_action_icon_controller.h"
18 #include "chrome/browser/ui/views/web_apps/web_app_frame_toolbar_test_helper.h"
19 #include "chrome/browser/ui/views/web_apps/web_app_frame_toolbar_view.h"
20 #include "chrome/test/base/in_process_browser_test.h"
21 #include "content/public/browser/web_contents.h"
22 #include "content/public/browser/web_contents_observer.h"
23 #include "content/public/common/page_zoom.h"
24 #include "content/public/test/browser_test.h"
25 #include "content/public/test/browser_test_utils.h"
26 #include "content/public/test/test_utils.h"
27 #include "content/public/test/theme_change_waiter.h"
28 #include "net/test/embedded_test_server/embedded_test_server.h"
29 #include "third_party/skia/include/core/SkColor.h"
30 #include "ui/gfx/geometry/size.h"
31 #include "ui/views/controls/label.h"
32 #include "ui/views/view.h"
33 #include "url/gurl.h"
34
35 namespace {
36
37 #if defined(OS_MAC)
38 // Keep in sync with browser_non_client_frame_view_mac.mm
39 constexpr double kTitlePaddingWidthFraction = 0.1;
40 #endif
41
42 template <typename T>
GetLastVisible(const std::vector<T * > & views)43 T* GetLastVisible(const std::vector<T*>& views) {
44 T* visible = nullptr;
45 for (auto* view : views) {
46 if (view->GetVisible())
47 visible = view;
48 }
49 return visible;
50 }
51
52 } // namespace
53
54 class WebAppFrameToolbarBrowserTest : public InProcessBrowserTest {
55 public:
WebAppFrameToolbarBrowserTest()56 WebAppFrameToolbarBrowserTest()
57 : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
58
https_server()59 net::EmbeddedTestServer* https_server() { return &https_server_; }
60
61 // InProcessBrowserTest:
SetUp()62 void SetUp() override {
63 https_server_.AddDefaultHandlers(GetChromeTestDataDir());
64
65 InProcessBrowserTest::SetUp();
66 }
67
helper()68 WebAppFrameToolbarTestHelper* helper() {
69 return &web_app_frame_toolbar_helper_;
70 }
71
72 private:
73 net::EmbeddedTestServer https_server_;
74 WebAppFrameToolbarTestHelper web_app_frame_toolbar_helper_;
75 };
76
IN_PROC_BROWSER_TEST_F(WebAppFrameToolbarBrowserTest,SpaceConstrained)77 IN_PROC_BROWSER_TEST_F(WebAppFrameToolbarBrowserTest, SpaceConstrained) {
78 const GURL app_url("https://test.org");
79 helper()->InstallAndLaunchWebApp(browser(), app_url);
80
81 views::View* const toolbar_left_container =
82 helper()->web_app_frame_toolbar()->GetLeftContainerForTesting();
83 EXPECT_EQ(toolbar_left_container->parent(),
84 helper()->web_app_frame_toolbar());
85
86 views::View* const window_title =
87 helper()->frame_view()->GetViewByID(VIEW_ID_WINDOW_TITLE);
88 #if defined(OS_CHROMEOS)
89 EXPECT_FALSE(window_title);
90 #else
91 EXPECT_EQ(window_title->parent(), helper()->frame_view());
92 #endif
93
94 views::View* const toolbar_right_container =
95 helper()->web_app_frame_toolbar()->GetRightContainerForTesting();
96 EXPECT_EQ(toolbar_right_container->parent(),
97 helper()->web_app_frame_toolbar());
98
99 std::vector<const PageActionIconView*> page_actions =
100 helper()
101 ->web_app_frame_toolbar()
102 ->GetPageActionIconControllerForTesting()
103 ->GetPageActionIconViewsForTesting();
104 for (const PageActionIconView* action : page_actions)
105 EXPECT_EQ(action->parent(), toolbar_right_container);
106
107 views::View* const menu_button =
108 helper()->browser_view()->toolbar_button_provider()->GetAppMenuButton();
109 EXPECT_EQ(menu_button->parent(), toolbar_right_container);
110
111 // Ensure we initially have abundant space.
112 helper()->frame_view()->SetSize(gfx::Size(1000, 1000));
113
114 EXPECT_TRUE(toolbar_left_container->GetVisible());
115 const int original_left_container_width = toolbar_left_container->width();
116 EXPECT_GT(original_left_container_width, 0);
117
118 #if defined(OS_WIN) || (defined(OS_LINUX) && !defined(OS_CHROMEOS))
119 const int original_window_title_width = window_title->width();
120 EXPECT_GT(original_window_title_width, 0);
121 #endif
122
123 // Initially the page action icons are not visible.
124 EXPECT_EQ(GetLastVisible(page_actions), nullptr);
125 const int original_menu_button_width = menu_button->width();
126 EXPECT_GT(original_menu_button_width, 0);
127
128 // Cause the zoom page action icon to be visible.
129 chrome::Zoom(helper()->app_browser(), content::PAGE_ZOOM_IN);
130
131 // The layout should be invalidated, but since we don't have the benefit of
132 // the compositor to immediately kick a layout off, we have to do it manually.
133 helper()->frame_view()->Layout();
134
135 // The page action icons should now take up width, leaving less space on
136 // Windows and Linux for the window title. (On Mac, the window title remains
137 // centered - not tested here.)
138
139 EXPECT_TRUE(toolbar_left_container->GetVisible());
140 EXPECT_EQ(toolbar_left_container->width(), original_left_container_width);
141
142 #if defined(OS_WIN) || (defined(OS_LINUX) && !defined(OS_CHROMEOS))
143 EXPECT_GT(window_title->width(), 0);
144 EXPECT_LT(window_title->width(), original_window_title_width);
145 #endif
146
147 EXPECT_NE(GetLastVisible(page_actions), nullptr);
148 EXPECT_EQ(menu_button->width(), original_menu_button_width);
149
150 // Resize the WebAppFrameToolbarView just enough to clip out the page action
151 // icons (and toolbar contents left of them).
152 const int original_toolbar_width = helper()->web_app_frame_toolbar()->width();
153 const int new_toolbar_width = toolbar_right_container->width() -
154 GetLastVisible(page_actions)->bounds().right();
155 const int new_frame_width = helper()->frame_view()->width() -
156 original_toolbar_width + new_toolbar_width;
157
158 helper()->web_app_frame_toolbar()->SetSize(
159 {new_toolbar_width, helper()->web_app_frame_toolbar()->height()});
160 helper()->frame_view()->SetSize(
161 {new_frame_width, helper()->frame_view()->height()});
162
163 // The left container (containing Back and Reload) should be hidden.
164 EXPECT_FALSE(toolbar_left_container->GetVisible());
165
166 // The window title should be clipped to 0 width.
167 #if defined(OS_WIN) || (defined(OS_LINUX) && !defined(OS_CHROMEOS))
168 EXPECT_EQ(window_title->width(), 0);
169 #endif
170
171 // The page action icons should be hidden while the app menu button retains
172 // its full width.
173 EXPECT_EQ(GetLastVisible(page_actions), nullptr);
174 EXPECT_EQ(menu_button->width(), original_menu_button_width);
175 }
176
IN_PROC_BROWSER_TEST_F(WebAppFrameToolbarBrowserTest,ThemeChange)177 IN_PROC_BROWSER_TEST_F(WebAppFrameToolbarBrowserTest, ThemeChange) {
178 ASSERT_TRUE(https_server()->Start());
179 const GURL app_url = https_server()->GetURL("/banners/theme-color.html");
180 helper()->InstallAndLaunchWebApp(browser(), app_url);
181
182 content::WebContents* web_contents =
183 helper()->app_browser()->tab_strip_model()->GetActiveWebContents();
184 content::AwaitDocumentOnLoadCompleted(web_contents);
185
186 #if !defined(OS_LINUX) || defined(OS_CHROMEOS)
187 // Avoid dependence on Linux GTK+ Themes appearance setting.
188
189 ToolbarButtonProvider* const toolbar_button_provider =
190 helper()->browser_view()->toolbar_button_provider();
191 AppMenuButton* const app_menu_button =
192 toolbar_button_provider->GetAppMenuButton();
193
194 const SkColor original_ink_drop_color =
195 app_menu_button->GetInkDropBaseColor();
196
197 {
198 content::ThemeChangeWaiter theme_change_waiter(web_contents);
199 EXPECT_TRUE(content::ExecJs(web_contents,
200 "document.getElementById('theme-color')."
201 "setAttribute('content', '#246')"));
202 theme_change_waiter.Wait();
203
204 EXPECT_NE(app_menu_button->GetInkDropBaseColor(), original_ink_drop_color);
205 }
206
207 {
208 content::ThemeChangeWaiter theme_change_waiter(web_contents);
209 EXPECT_TRUE(content::ExecJs(
210 web_contents, "document.getElementById('theme-color').remove()"));
211 theme_change_waiter.Wait();
212
213 EXPECT_EQ(app_menu_button->GetInkDropBaseColor(), original_ink_drop_color);
214 }
215 #endif
216 }
217
218 // Test that a tooltip is shown when hovering over a truncated title.
IN_PROC_BROWSER_TEST_F(WebAppFrameToolbarBrowserTest,TitleHover)219 IN_PROC_BROWSER_TEST_F(WebAppFrameToolbarBrowserTest, TitleHover) {
220 const GURL app_url("https://test.org");
221 helper()->InstallAndLaunchWebApp(browser(), app_url);
222
223 views::View* const toolbar_left_container =
224 helper()->web_app_frame_toolbar()->GetLeftContainerForTesting();
225 views::View* const toolbar_right_container =
226 helper()->web_app_frame_toolbar()->GetRightContainerForTesting();
227
228 auto* const window_title = static_cast<views::Label*>(
229 helper()->frame_view()->GetViewByID(VIEW_ID_WINDOW_TITLE));
230 #if defined(OS_CHROMEOS)
231 // Chrome OS PWA windows do not display app titles.
232 EXPECT_EQ(nullptr, window_title);
233 return;
234 #endif
235 EXPECT_EQ(window_title->parent(), helper()->frame_view());
236
237 window_title->SetText(base::string16(30, 't'));
238
239 // Ensure we initially have abundant space.
240 helper()->frame_view()->SetSize(gfx::Size(1000, 1000));
241 helper()->frame_view()->Layout();
242 EXPECT_GT(window_title->width(), 0);
243 const int original_title_gap = toolbar_right_container->x() -
244 toolbar_left_container->x() -
245 toolbar_left_container->width();
246
247 // With a narrow window, we have insufficient space for the full title.
248 const int narrow_title_gap =
249 window_title->CalculatePreferredSize().width() * 3 / 4;
250 int narrow_frame_width =
251 helper()->frame_view()->width() - original_title_gap + narrow_title_gap;
252 #if defined(OS_MAC)
253 // Increase frame width to allow for title padding.
254 narrow_frame_width = base::checked_cast<int>(
255 std::ceil(narrow_frame_width / (1 - 2 * kTitlePaddingWidthFraction)));
256 #endif
257 helper()->frame_view()->SetSize(gfx::Size(narrow_frame_width, 1000));
258 helper()->frame_view()->Layout();
259
260 EXPECT_GT(window_title->width(), 0);
261 EXPECT_EQ(window_title->GetTooltipHandlerForPoint(gfx::Point(0, 0)),
262 window_title);
263
264 EXPECT_EQ(
265 helper()->frame_view()->GetTooltipHandlerForPoint(window_title->origin()),
266 window_title);
267 }
268