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