OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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 #import "chrome/browser/ui/cocoa/panels/panel_cocoa.h" | |
6 | |
7 #include <Carbon/Carbon.h> | |
8 #import <Cocoa/Cocoa.h> | |
9 | |
10 #include <memory> | |
11 | |
12 #include "base/command_line.h" | |
13 #include "base/debug/debugger.h" | |
14 #include "base/mac/scoped_nsautorelease_pool.h" | |
15 #include "base/strings/sys_string_conversions.h" | |
16 #include "chrome/app/chrome_command_ids.h" // IDC_* | |
17 #include "chrome/browser/chrome_notification_types.h" | |
18 #import "chrome/browser/ui/cocoa/browser_window_utils.h" | |
19 #import "chrome/browser/ui/cocoa/cocoa_profile_test.h" | |
20 #import "chrome/browser/ui/cocoa/panels/panel_titlebar_view_cocoa.h" | |
21 #import "chrome/browser/ui/cocoa/panels/panel_window_controller_cocoa.h" | |
22 #include "chrome/browser/ui/cocoa/run_loop_testing.h" | |
23 #include "chrome/browser/ui/panels/panel.h" | |
24 #include "chrome/browser/ui/panels/panel_manager.h" | |
25 #include "chrome/common/chrome_switches.h" | |
26 #include "chrome/test/base/testing_profile.h" | |
27 #include "content/public/test/test_utils.h" | |
28 #include "testing/gtest/include/gtest/gtest.h" | |
29 #include "testing/gtest_mac.h" | |
30 | |
31 class PanelAnimatedBoundsObserver : | |
32 public content::WindowedNotificationObserver { | |
33 public: | |
34 PanelAnimatedBoundsObserver(Panel* panel) | |
35 : content::WindowedNotificationObserver( | |
36 chrome::NOTIFICATION_PANEL_BOUNDS_ANIMATIONS_FINISHED, | |
37 content::Source<Panel>(panel)) { } | |
38 ~PanelAnimatedBoundsObserver() override {} | |
39 }; | |
40 | |
41 // Main test class. | |
42 class PanelCocoaTest : public CocoaProfileTest { | |
43 public: | |
44 void SetUp() override { CocoaProfileTest::SetUp(); } | |
45 | |
46 Panel* CreateTestPanel(const std::string& panel_name) { | |
47 // Opening panels on a Mac causes NSWindowController of the Panel window | |
48 // to be autoreleased. We need a pool drained after it's done so the test | |
49 // can close correctly. | |
50 base::mac::ScopedNSAutoreleasePool autorelease_pool; | |
51 | |
52 PanelManager* manager = PanelManager::GetInstance(); | |
53 int panels_count = manager->num_panels(); | |
54 | |
55 Panel* panel = manager->CreatePanel(panel_name, profile(), | |
56 GURL(), nullptr, gfx::Rect(), | |
57 PanelManager::CREATE_AS_DOCKED); | |
58 EXPECT_EQ(panels_count + 1, manager->num_panels()); | |
59 | |
60 EXPECT_TRUE(panel); | |
61 EXPECT_TRUE(panel->native_panel()); // Native panel is created right away. | |
62 PanelCocoa* native_window = | |
63 static_cast<PanelCocoa*>(panel->native_panel()); | |
64 EXPECT_EQ(panel, native_window->panel_.get()); // Back pointer initialized. | |
65 | |
66 PanelAnimatedBoundsObserver bounds_observer(panel); | |
67 | |
68 // Window should not load before Show(). | |
69 // Note: Loading the wnidow causes Cocoa to autorelease a few objects. | |
70 // This is the reason we do this within the scope of the | |
71 // ScopedNSAutoreleasePool. | |
72 EXPECT_FALSE([native_window->controller_ isWindowLoaded]); | |
73 panel->Show(); | |
74 EXPECT_TRUE([native_window->controller_ isWindowLoaded]); | |
75 EXPECT_TRUE([native_window->controller_ window]); | |
76 | |
77 // Wait until bounds animate to their specified values. | |
78 bounds_observer.Wait(); | |
79 | |
80 return panel; | |
81 } | |
82 | |
83 void VerifyTitlebarLocation(NSView* contentView, NSView* titlebar) { | |
84 NSRect content_frame = [contentView frame]; | |
85 NSRect titlebar_frame = [titlebar frame]; | |
86 // Since contentView and titlebar are both children of window's root view, | |
87 // we can compare their frames since they are in the same coordinate system. | |
88 EXPECT_EQ(NSMinX(content_frame), NSMinX(titlebar_frame)); | |
89 EXPECT_EQ(NSWidth(content_frame), NSWidth(titlebar_frame)); | |
90 EXPECT_EQ(NSHeight([[titlebar superview] bounds]), NSMaxY(titlebar_frame)); | |
91 } | |
92 | |
93 void ClosePanelAndWait(Panel* panel) { | |
94 EXPECT_TRUE(panel); | |
95 // Closing a panel may involve several async tasks. Need to use | |
96 // message pump and wait for the notification. | |
97 PanelManager* manager = PanelManager::GetInstance(); | |
98 int panel_count = manager->num_panels(); | |
99 content::WindowedNotificationObserver signal( | |
100 chrome::NOTIFICATION_PANEL_CLOSED, | |
101 content::Source<Panel>(panel)); | |
102 panel->Close(); | |
103 signal.Wait(); | |
104 // Now we have one less panel. | |
105 EXPECT_EQ(panel_count - 1, manager->num_panels()); | |
106 } | |
107 | |
108 NSMenuItem* CreateMenuItem(NSMenu* menu, int command_id) { | |
109 NSMenuItem* item = | |
110 [menu addItemWithTitle:@"" | |
111 action:@selector(commandDispatch:) | |
112 keyEquivalent:@""]; | |
113 [item setTag:command_id]; | |
114 return item; | |
115 } | |
116 }; | |
117 | |
118 TEST_F(PanelCocoaTest, CreateClose) { | |
119 PanelManager* manager = PanelManager::GetInstance(); | |
120 EXPECT_EQ(0, manager->num_panels()); // No panels initially. | |
121 | |
122 Panel* panel = CreateTestPanel("Test Panel"); | |
123 ASSERT_TRUE(panel); | |
124 | |
125 gfx::Rect bounds = panel->GetBounds(); | |
126 EXPECT_TRUE(bounds.width() > 0); | |
127 EXPECT_TRUE(bounds.height() > 0); | |
128 | |
129 PanelCocoa* native_window = static_cast<PanelCocoa*>(panel->native_panel()); | |
130 ASSERT_TRUE(native_window); | |
131 // NSWindows created by NSWindowControllers don't have this bit even if | |
132 // their NIB has it. The controller's lifetime is the window's lifetime. | |
133 EXPECT_EQ(NO, [[native_window->controller_ window] isReleasedWhenClosed]); | |
134 | |
135 ClosePanelAndWait(panel); | |
136 EXPECT_EQ(0, manager->num_panels()); | |
137 } | |
138 | |
139 TEST_F(PanelCocoaTest, AssignedBounds) { | |
140 Panel* panel1 = CreateTestPanel("Test Panel 1"); | |
141 Panel* panel2 = CreateTestPanel("Test Panel 2"); | |
142 Panel* panel3 = CreateTestPanel("Test Panel 3"); | |
143 | |
144 gfx::Rect bounds1 = panel1->GetBounds(); | |
145 gfx::Rect bounds2 = panel2->GetBounds(); | |
146 gfx::Rect bounds3 = panel3->GetBounds(); | |
147 | |
148 // This checks panelManager calculating and assigning bounds right. | |
149 // Panels should stack on the bottom right to left. | |
150 EXPECT_LT(bounds3.x() + bounds3.width(), bounds2.x()); | |
151 EXPECT_LT(bounds2.x() + bounds2.width(), bounds1.x()); | |
152 EXPECT_EQ(bounds1.y(), bounds2.y()); | |
153 EXPECT_EQ(bounds2.y(), bounds3.y()); | |
154 | |
155 // After panel2 is closed, panel3 should take its place. | |
156 ClosePanelAndWait(panel2); | |
157 bounds3 = panel3->GetBounds(); | |
158 EXPECT_EQ(bounds2, bounds3); | |
159 | |
160 // After panel1 is closed, panel3 should take its place. | |
161 ClosePanelAndWait(panel1); | |
162 EXPECT_EQ(bounds1, panel3->GetBounds()); | |
163 | |
164 ClosePanelAndWait(panel3); | |
165 } | |
166 | |
167 // Same test as AssignedBounds, but checks actual bounds on native OS windows. | |
168 TEST_F(PanelCocoaTest, NativeBounds) { | |
169 Panel* panel1 = CreateTestPanel("Test Panel 1"); | |
170 Panel* panel2 = CreateTestPanel("Test Panel 2"); | |
171 Panel* panel3 = CreateTestPanel("Test Panel 3"); | |
172 | |
173 PanelCocoa* native_window1 = static_cast<PanelCocoa*>(panel1->native_panel()); | |
174 PanelCocoa* native_window2 = static_cast<PanelCocoa*>(panel2->native_panel()); | |
175 PanelCocoa* native_window3 = static_cast<PanelCocoa*>(panel3->native_panel()); | |
176 | |
177 NSRect bounds1 = [[native_window1->controller_ window] frame]; | |
178 NSRect bounds2 = [[native_window2->controller_ window] frame]; | |
179 NSRect bounds3 = [[native_window3->controller_ window] frame]; | |
180 | |
181 EXPECT_LT(bounds3.origin.x + bounds3.size.width, bounds2.origin.x); | |
182 EXPECT_LT(bounds2.origin.x + bounds2.size.width, bounds1.origin.x); | |
183 EXPECT_EQ(bounds1.origin.y, bounds2.origin.y); | |
184 EXPECT_EQ(bounds2.origin.y, bounds3.origin.y); | |
185 | |
186 { | |
187 // After panel2 is closed, panel3 should take its place. | |
188 PanelAnimatedBoundsObserver bounds_observer(panel3); | |
189 ClosePanelAndWait(panel2); | |
190 bounds_observer.Wait(); | |
191 bounds3 = [[native_window3->controller_ window] frame]; | |
192 EXPECT_EQ(bounds2.origin.x, bounds3.origin.x); | |
193 EXPECT_EQ(bounds2.origin.y, bounds3.origin.y); | |
194 EXPECT_EQ(bounds2.size.width, bounds3.size.width); | |
195 EXPECT_EQ(bounds2.size.height, bounds3.size.height); | |
196 } | |
197 | |
198 { | |
199 // After panel1 is closed, panel3 should take its place. | |
200 PanelAnimatedBoundsObserver bounds_observer(panel3); | |
201 ClosePanelAndWait(panel1); | |
202 bounds_observer.Wait(); | |
203 bounds3 = [[native_window3->controller_ window] frame]; | |
204 EXPECT_EQ(bounds1.origin.x, bounds3.origin.x); | |
205 EXPECT_EQ(bounds1.origin.y, bounds3.origin.y); | |
206 EXPECT_EQ(bounds1.size.width, bounds3.size.width); | |
207 EXPECT_EQ(bounds1.size.height, bounds3.size.height); | |
208 } | |
209 | |
210 ClosePanelAndWait(panel3); | |
211 } | |
212 | |
213 // Verify the titlebar is being created. | |
214 TEST_F(PanelCocoaTest, TitlebarViewCreate) { | |
215 Panel* panel = CreateTestPanel("Test Panel"); | |
216 | |
217 PanelCocoa* native_window = static_cast<PanelCocoa*>(panel->native_panel()); | |
218 | |
219 PanelTitlebarViewCocoa* titlebar = [native_window->controller_ titlebarView]; | |
220 EXPECT_TRUE(titlebar); | |
221 EXPECT_EQ(native_window->controller_, [titlebar controller]); | |
222 | |
223 ClosePanelAndWait(panel); | |
224 } | |
225 | |
226 // Verify the sizing of titlebar - should be affixed on top of regular titlebar. | |
227 TEST_F(PanelCocoaTest, TitlebarViewSizing) { | |
228 Panel* panel = CreateTestPanel("Test Panel"); | |
229 | |
230 PanelCocoa* native_window = static_cast<PanelCocoa*>(panel->native_panel()); | |
231 PanelTitlebarViewCocoa* titlebar = [native_window->controller_ titlebarView]; | |
232 | |
233 NSView* contentView = [[native_window->controller_ window] contentView]; | |
234 VerifyTitlebarLocation(contentView, titlebar); | |
235 | |
236 // In local coordinate system, width of titlebar should match width of | |
237 // content view of the window. They both use the same scale factor. | |
238 EXPECT_EQ(NSWidth([contentView bounds]), NSWidth([titlebar bounds])); | |
239 | |
240 NSRect oldTitleFrame = [[titlebar title] frame]; | |
241 NSRect oldIconFrame = [[titlebar icon] frame]; | |
242 | |
243 // Now resize the Panel, see that titlebar follows. | |
244 const int kDelta = 153; // random number | |
245 gfx::Rect bounds = panel->GetBounds(); | |
246 // Grow panel in a way so that its titlebar moves and grows. | |
247 bounds.set_x(bounds.x() - kDelta); | |
248 bounds.set_y(bounds.y() - kDelta); | |
249 bounds.set_width(bounds.width() + kDelta); | |
250 bounds.set_height(bounds.height() + kDelta); | |
251 | |
252 PanelAnimatedBoundsObserver bounds_observer(panel); | |
253 native_window->SetPanelBounds(bounds); | |
254 bounds_observer.Wait(); | |
255 | |
256 // Verify the panel resized. | |
257 NSRect window_frame = [[native_window->controller_ window] frame]; | |
258 EXPECT_EQ(NSWidth(window_frame), bounds.width()); | |
259 EXPECT_EQ(NSHeight(window_frame), bounds.height()); | |
260 | |
261 // Verify the titlebar is still on top of regular titlebar. | |
262 VerifyTitlebarLocation(contentView, titlebar); | |
263 | |
264 // Verify that the title/icon frames were updated. | |
265 NSRect newTitleFrame = [[titlebar title] frame]; | |
266 NSRect newIconFrame = [[titlebar icon] frame]; | |
267 | |
268 EXPECT_EQ(newTitleFrame.origin.x - newIconFrame.origin.x, | |
269 oldTitleFrame.origin.x - oldIconFrame.origin.x); | |
270 // Icon and Text should remain at the same left-aligned position. | |
271 EXPECT_EQ(newTitleFrame.origin.x, oldTitleFrame.origin.x); | |
272 EXPECT_EQ(newIconFrame.origin.x, oldIconFrame.origin.x); | |
273 | |
274 ClosePanelAndWait(panel); | |
275 } | |
276 | |
277 // Verify closing behavior of titlebar close button. | |
278 TEST_F(PanelCocoaTest, TitlebarViewClose) { | |
279 Panel* panel = CreateTestPanel("Test Panel"); | |
280 PanelCocoa* native_window = static_cast<PanelCocoa*>(panel->native_panel()); | |
281 | |
282 PanelTitlebarViewCocoa* titlebar = [native_window->controller_ titlebarView]; | |
283 EXPECT_TRUE(titlebar); | |
284 | |
285 PanelManager* manager = PanelManager::GetInstance(); | |
286 EXPECT_EQ(1, manager->num_panels()); | |
287 // Simulate clicking Close Button and wait until the Panel closes. | |
288 content::WindowedNotificationObserver signal( | |
289 chrome::NOTIFICATION_PANEL_CLOSED, | |
290 content::Source<Panel>(panel)); | |
291 [titlebar simulateCloseButtonClick]; | |
292 signal.Wait(); | |
293 EXPECT_EQ(0, manager->num_panels()); | |
294 } | |
295 | |
296 // Verify some menu items being properly enabled/disabled for panels. | |
297 TEST_F(PanelCocoaTest, MenuItems) { | |
298 Panel* panel = CreateTestPanel("Test Panel"); | |
299 | |
300 base::scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle:@""]); | |
301 NSMenuItem* close_tab_menu_item = CreateMenuItem(menu, IDC_CLOSE_TAB); | |
302 NSMenuItem* new_tab_menu_item = CreateMenuItem(menu, IDC_NEW_TAB); | |
303 NSMenuItem* new_tab_window_item = CreateMenuItem(menu, IDC_NEW_WINDOW); | |
304 NSMenuItem* new_tab_incognito_window_item = | |
305 CreateMenuItem(menu, IDC_NEW_INCOGNITO_WINDOW); | |
306 NSMenuItem* close_window_menu_item = CreateMenuItem(menu, IDC_CLOSE_WINDOW); | |
307 NSMenuItem* find_menu_item = CreateMenuItem(menu, IDC_FIND); | |
308 NSMenuItem* find_previous_menu_item = CreateMenuItem(menu, IDC_FIND_PREVIOUS); | |
309 NSMenuItem* find_next_menu_item = CreateMenuItem(menu, IDC_FIND_NEXT); | |
310 NSMenuItem* fullscreen_menu_item = CreateMenuItem(menu, IDC_FULLSCREEN); | |
311 NSMenuItem* sync_menu_item = CreateMenuItem(menu, IDC_SHOW_SYNC_SETUP); | |
312 NSMenuItem* dev_tools_item = CreateMenuItem(menu, IDC_DEV_TOOLS); | |
313 NSMenuItem* dev_tools_console_item = | |
314 CreateMenuItem(menu, IDC_DEV_TOOLS_CONSOLE); | |
315 | |
316 PanelCocoa* native_window = static_cast<PanelCocoa*>(panel->native_panel()); | |
317 PanelWindowControllerCocoa* panel_controller = native_window->controller_; | |
318 for (NSMenuItem *item in [menu itemArray]) | |
319 [item setTarget:panel_controller]; | |
320 | |
321 [menu update]; // Trigger validation of menu items. | |
322 EXPECT_FALSE([close_tab_menu_item isEnabled]); | |
323 EXPECT_TRUE([close_window_menu_item isEnabled]); | |
324 // No find support. Panels don't have a find bar. | |
325 EXPECT_FALSE([find_menu_item isEnabled]); | |
326 EXPECT_FALSE([find_previous_menu_item isEnabled]); | |
327 EXPECT_FALSE([find_next_menu_item isEnabled]); | |
328 EXPECT_FALSE([fullscreen_menu_item isEnabled]); | |
329 EXPECT_FALSE([sync_menu_item isEnabled]); | |
330 // These are not enabled by Panel, so they are expected to be disabled for | |
331 // this unit_test. In real Chrome app, they are enabled by Chrome NSApp | |
332 // controller. PanelCocoaBrowsertest.MenuItems verifies that. | |
333 EXPECT_FALSE([new_tab_menu_item isEnabled]); | |
334 EXPECT_FALSE([new_tab_window_item isEnabled]); | |
335 EXPECT_FALSE([new_tab_incognito_window_item isEnabled]); | |
336 | |
337 EXPECT_TRUE([dev_tools_item isEnabled]); | |
338 EXPECT_TRUE([dev_tools_console_item isEnabled]); | |
339 | |
340 // Verify that commandDispatch on an invalid menu item does not crash. | |
341 [NSApp sendAction:[sync_menu_item action] | |
342 to:[sync_menu_item target] | |
343 from:sync_menu_item]; | |
344 | |
345 ClosePanelAndWait(panel); | |
346 } | |
347 | |
348 TEST_F(PanelCocoaTest, KeyEvent) { | |
349 Panel* panel = CreateTestPanel("Test Panel"); | |
350 NSEvent* event = [NSEvent keyEventWithType:NSKeyDown | |
351 location:NSZeroPoint | |
352 modifierFlags:NSControlKeyMask | |
353 timestamp:0.0 | |
354 windowNumber:0 | |
355 context:nil | |
356 characters:@"" | |
357 charactersIgnoringModifiers:@"" | |
358 isARepeat:NO | |
359 keyCode:kVK_Tab]; | |
360 PanelCocoa* native_window = static_cast<PanelCocoa*>(panel->native_panel()); | |
361 [BrowserWindowUtils handleKeyboardEvent:event | |
362 inWindow:[native_window->controller_ window]]; | |
363 ClosePanelAndWait(panel); | |
364 } | |
365 | |
366 TEST_F(PanelCocoaTest, SetTitle) { | |
367 NSString *appName = @"Test Panel"; | |
368 Panel* panel = CreateTestPanel(base::SysNSStringToUTF8(appName)); | |
369 ASSERT_TRUE(panel); | |
370 | |
371 PanelCocoa* native_window = static_cast<PanelCocoa*>(panel->native_panel()); | |
372 ASSERT_TRUE(native_window); | |
373 NSString* previousTitle = [[native_window->controller_ window] title]; | |
374 EXPECT_NSNE(appName, previousTitle); | |
375 [native_window->controller_ updateTitleBar]; | |
376 chrome::testing::NSRunLoopRunAllPending(); | |
377 NSString* currentTitle = [[native_window->controller_ window] title]; | |
378 EXPECT_NSEQ(appName, currentTitle); | |
379 EXPECT_NSNE(currentTitle, previousTitle); | |
380 ClosePanelAndWait(panel); | |
381 } | |
382 | |
383 TEST_F(PanelCocoaTest, ActivatePanel) { | |
384 Panel* panel = CreateTestPanel("Test Panel"); | |
385 Panel* panel2 = CreateTestPanel("Test Panel 2"); | |
386 ASSERT_TRUE(panel); | |
387 ASSERT_TRUE(panel2); | |
388 | |
389 PanelCocoa* native_window = static_cast<PanelCocoa*>(panel->native_panel()); | |
390 ASSERT_TRUE(native_window); | |
391 PanelCocoa* native_window2 = static_cast<PanelCocoa*>(panel2->native_panel()); | |
392 ASSERT_TRUE(native_window2); | |
393 | |
394 // No one has a good answer why but apparently windows can't take keyboard | |
395 // focus outside of interactive UI tests. BrowserWindowController uses the | |
396 // same way of testing this. | |
397 native_window->ActivatePanel(); | |
398 chrome::testing::NSRunLoopRunAllPending(); | |
399 NSWindow* frontmostWindow = [[NSApp orderedWindows] objectAtIndex:0]; | |
400 EXPECT_NSEQ(frontmostWindow, [native_window->controller_ window]); | |
401 | |
402 native_window2->ActivatePanel(); | |
403 chrome::testing::NSRunLoopRunAllPending(); | |
404 frontmostWindow = [[NSApp orderedWindows] objectAtIndex:0]; | |
405 EXPECT_NSEQ(frontmostWindow, [native_window2->controller_ window]); | |
406 | |
407 ClosePanelAndWait(panel); | |
408 ClosePanelAndWait(panel2); | |
409 } | |
OLD | NEW |