1// Copyright 2013 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 "chrome/browser/ui/cocoa/apps/native_app_window_cocoa.h" 6 7#include "apps/app_shim/extension_app_shim_handler_mac.h" 8#include "base/command_line.h" 9#include "base/mac/foundation_util.h" 10#include "base/mac/mac_util.h" 11#include "base/mac/sdk_forward_declarations.h" 12#include "base/strings/sys_string_conversions.h" 13#include "chrome/browser/profiles/profile.h" 14#include "chrome/browser/ui/cocoa/browser_window_utils.h" 15#import "chrome/browser/ui/cocoa/chrome_event_processing_window.h" 16#import "chrome/browser/ui/cocoa/custom_frame_view.h" 17#include "chrome/browser/ui/cocoa/extensions/extension_keybinding_registry_cocoa.h" 18#include "chrome/browser/ui/cocoa/extensions/extension_view_mac.h" 19#import "chrome/browser/ui/cocoa/nsview_additions.h" 20#include "chrome/common/chrome_switches.h" 21#include "content/public/browser/native_web_keyboard_event.h" 22#include "content/public/browser/render_widget_host_view.h" 23#include "content/public/browser/web_contents.h" 24#include "extensions/common/extension.h" 25#include "skia/ext/skia_utils_mac.h" 26#include "third_party/skia/include/core/SkRegion.h" 27#include "ui/gfx/skia_util.h" 28 29// NOTE: State Before Update. 30// 31// Internal state, such as |is_maximized_|, must be set before the window 32// state is changed so that it is accurate when e.g. a resize results in a call 33// to |OnNativeWindowChanged|. 34 35// NOTE: Maximize and Zoom. 36// 37// Zooming is implemented manually in order to implement maximize functionality 38// and to support non resizable windows. The window will be resized explicitly 39// in the |WindowWillZoom| call. 40// 41// Attempting maximize and restore functionality with non resizable windows 42// using the native zoom method did not work, even with 43// windowWillUseStandardFrame, as the window would not restore back to the 44// desired size. 45 46using apps::AppWindow; 47 48@interface NSWindow (NSPrivateApis) 49- (void)setBottomCornerRounded:(BOOL)rounded; 50- (BOOL)_isTitleHidden; 51@end 52 53namespace { 54 55void SetFullScreenCollectionBehavior(NSWindow* window, bool allow_fullscreen) { 56 NSWindowCollectionBehavior behavior = [window collectionBehavior]; 57 if (allow_fullscreen) 58 behavior |= NSWindowCollectionBehaviorFullScreenPrimary; 59 else 60 behavior &= ~NSWindowCollectionBehaviorFullScreenPrimary; 61 [window setCollectionBehavior:behavior]; 62} 63 64void InitCollectionBehavior(NSWindow* window) { 65 // Since always-on-top windows have a higher window level 66 // than NSNormalWindowLevel, they will default to 67 // NSWindowCollectionBehaviorTransient. Set the value 68 // explicitly here to match normal windows. 69 NSWindowCollectionBehavior behavior = [window collectionBehavior]; 70 behavior |= NSWindowCollectionBehaviorManaged; 71 [window setCollectionBehavior:behavior]; 72} 73 74// Returns the level for windows that are configured to be always on top. 75// This is not a constant because NSFloatingWindowLevel is a macro defined 76// as a function call. 77NSInteger AlwaysOnTopWindowLevel() { 78 return NSFloatingWindowLevel; 79} 80 81NSRect GfxToCocoaBounds(gfx::Rect bounds) { 82 typedef apps::AppWindow::BoundsSpecification BoundsSpecification; 83 84 NSRect main_screen_rect = [[[NSScreen screens] objectAtIndex:0] frame]; 85 86 // If coordinates are unspecified, center window on primary screen. 87 if (bounds.x() == BoundsSpecification::kUnspecifiedPosition) 88 bounds.set_x(floor((NSWidth(main_screen_rect) - bounds.width()) / 2)); 89 if (bounds.y() == BoundsSpecification::kUnspecifiedPosition) 90 bounds.set_y(floor((NSHeight(main_screen_rect) - bounds.height()) / 2)); 91 92 // Convert to Mac coordinates. 93 NSRect cocoa_bounds = NSRectFromCGRect(bounds.ToCGRect()); 94 cocoa_bounds.origin.y = NSHeight(main_screen_rect) - NSMaxY(cocoa_bounds); 95 return cocoa_bounds; 96} 97 98// Return a vector of non-draggable regions that fill a window of size 99// |width| by |height|, but leave gaps where the window should be draggable. 100std::vector<gfx::Rect> CalculateNonDraggableRegions( 101 const std::vector<extensions::DraggableRegion>& regions, 102 int width, 103 int height) { 104 std::vector<gfx::Rect> result; 105 if (regions.empty()) { 106 result.push_back(gfx::Rect(0, 0, width, height)); 107 } else { 108 scoped_ptr<SkRegion> draggable( 109 AppWindow::RawDraggableRegionsToSkRegion(regions)); 110 scoped_ptr<SkRegion> non_draggable(new SkRegion); 111 non_draggable->op(0, 0, width, height, SkRegion::kUnion_Op); 112 non_draggable->op(*draggable, SkRegion::kDifference_Op); 113 for (SkRegion::Iterator it(*non_draggable); !it.done(); it.next()) { 114 result.push_back(gfx::SkIRectToRect(it.rect())); 115 } 116 } 117 return result; 118} 119 120} // namespace 121 122@implementation NativeAppWindowController 123 124@synthesize appWindow = appWindow_; 125 126- (void)windowWillClose:(NSNotification*)notification { 127 if (appWindow_) 128 appWindow_->WindowWillClose(); 129} 130 131- (void)windowDidBecomeKey:(NSNotification*)notification { 132 if (appWindow_) 133 appWindow_->WindowDidBecomeKey(); 134} 135 136- (void)windowDidResignKey:(NSNotification*)notification { 137 if (appWindow_) 138 appWindow_->WindowDidResignKey(); 139} 140 141- (void)windowDidResize:(NSNotification*)notification { 142 if (appWindow_) 143 appWindow_->WindowDidResize(); 144} 145 146- (void)windowDidEndLiveResize:(NSNotification*)notification { 147 if (appWindow_) 148 appWindow_->WindowDidFinishResize(); 149} 150 151- (void)windowDidEnterFullScreen:(NSNotification*)notification { 152 if (appWindow_) 153 appWindow_->WindowDidEnterFullscreen(); 154} 155 156- (void)windowDidExitFullScreen:(NSNotification*)notification { 157 if (appWindow_) 158 appWindow_->WindowDidExitFullscreen(); 159} 160 161- (void)windowDidMove:(NSNotification*)notification { 162 if (appWindow_) 163 appWindow_->WindowDidMove(); 164} 165 166- (void)windowDidMiniaturize:(NSNotification*)notification { 167 if (appWindow_) 168 appWindow_->WindowDidMiniaturize(); 169} 170 171- (void)windowDidDeminiaturize:(NSNotification*)notification { 172 if (appWindow_) 173 appWindow_->WindowDidDeminiaturize(); 174} 175 176- (BOOL)windowShouldZoom:(NSWindow*)window 177 toFrame:(NSRect)newFrame { 178 if (appWindow_) 179 appWindow_->WindowWillZoom(); 180 return NO; // See top of file NOTE: Maximize and Zoom. 181} 182 183// Allow non resizable windows (without NSResizableWindowMask) to enter 184// fullscreen by passing through the full size in willUseFullScreenContentSize. 185- (NSSize)window:(NSWindow *)window 186 willUseFullScreenContentSize:(NSSize)proposedSize { 187 return proposedSize; 188} 189 190- (void)executeCommand:(int)command { 191 // No-op, swallow the event. 192} 193 194- (BOOL)handledByExtensionCommand:(NSEvent*)event { 195 if (appWindow_) 196 return appWindow_->HandledByExtensionCommand(event); 197 return NO; 198} 199 200@end 201 202// This is really a method on NSGrayFrame, so it should only be called on the 203// view passed into -[NSWindow drawCustomFrameRect:forView:]. 204@interface NSView (PrivateMethods) 205- (CGFloat)roundedCornerRadius; 206@end 207 208// TODO(jamescook): Should these be AppNSWindow to match apps::AppWindow? 209// http://crbug.com/344082 210@interface ShellNSWindow : ChromeEventProcessingWindow 211@end 212@implementation ShellNSWindow 213 214- (instancetype)initWithContentRect:(NSRect)contentRect 215 styleMask:(NSUInteger)windowStyle 216 backing:(NSBackingStoreType)bufferingType 217 defer:(BOOL)deferCreation { 218 if ((self = [super initWithContentRect:contentRect 219 styleMask:windowStyle 220 backing:bufferingType 221 defer:deferCreation])) { 222 if ([self respondsToSelector:@selector(setTitleVisibility:)]) 223 self.titleVisibility = NSWindowTitleHidden; 224 } 225 226 return self; 227} 228 229// Similar to ChromeBrowserWindow, don't draw the title, but allow it to be seen 230// in menus, Expose, etc. 231- (BOOL)_isTitleHidden { 232 // Only intervene with 10.6-10.9. 233 if ([self respondsToSelector:@selector(setTitleVisibility:)]) 234 return [super _isTitleHidden]; 235 else 236 return YES; 237} 238 239- (void)drawCustomFrameRect:(NSRect)frameRect forView:(NSView*)view { 240 // Make the background color of the content area white. We can't just call 241 // -setBackgroundColor as that causes the title bar to be drawn in a solid 242 // color. 243 NSRect rect = [self contentRectForFrameRect:frameRect]; 244 [[NSColor whiteColor] set]; 245 NSRectFill(rect); 246 247 // Draw the native title bar. We remove the content area since the native 248 // implementation draws a gray background. 249 rect.origin.y = NSMaxY(rect); 250 rect.size.height = CGFLOAT_MAX; 251 rect = NSIntersectionRect(rect, frameRect); 252 253 [NSBezierPath clipRect:rect]; 254 [super drawCustomFrameRect:frameRect 255 forView:view]; 256} 257 258@end 259 260@interface ShellCustomFrameNSWindow : ShellNSWindow { 261 @private 262 base::scoped_nsobject<NSColor> color_; 263 base::scoped_nsobject<NSColor> inactiveColor_; 264} 265 266- (void)setColor:(NSColor*)color 267 inactiveColor:(NSColor*)inactiveColor; 268 269@end 270 271@implementation ShellCustomFrameNSWindow 272 273- (void)drawCustomFrameRect:(NSRect)rect forView:(NSView*)view { 274 [[NSBezierPath bezierPathWithRect:rect] addClip]; 275 [[NSColor clearColor] set]; 276 NSRectFill(rect); 277 278 // Set up our clip. 279 CGFloat cornerRadius = 4.0; 280 if ([view respondsToSelector:@selector(roundedCornerRadius)]) 281 cornerRadius = [view roundedCornerRadius]; 282 [[NSBezierPath bezierPathWithRoundedRect:[view bounds] 283 xRadius:cornerRadius 284 yRadius:cornerRadius] addClip]; 285 if ([self isMainWindow] || [self isKeyWindow]) 286 [color_ set]; 287 else 288 [inactiveColor_ set]; 289 NSRectFill(rect); 290} 291 292- (void)setColor:(NSColor*)color 293 inactiveColor:(NSColor*)inactiveColor { 294 color_.reset([color retain]); 295 inactiveColor_.reset([inactiveColor retain]); 296} 297 298@end 299 300@interface ShellFramelessNSWindow : ShellNSWindow 301@end 302 303@implementation ShellFramelessNSWindow 304 305- (void)drawCustomFrameRect:(NSRect)rect forView:(NSView*)view {} 306 307+ (NSRect)frameRectForContentRect:(NSRect)contentRect 308 styleMask:(NSUInteger)mask { 309 return contentRect; 310} 311 312+ (NSRect)contentRectForFrameRect:(NSRect)frameRect 313 styleMask:(NSUInteger)mask { 314 return frameRect; 315} 316 317- (NSRect)frameRectForContentRect:(NSRect)contentRect { 318 return contentRect; 319} 320 321- (NSRect)contentRectForFrameRect:(NSRect)frameRect { 322 return frameRect; 323} 324 325@end 326 327@interface ControlRegionView : NSView 328@end 329 330@implementation ControlRegionView 331 332- (BOOL)mouseDownCanMoveWindow { 333 return NO; 334} 335 336- (NSView*)hitTest:(NSPoint)aPoint { 337 return nil; 338} 339 340@end 341 342@interface NSView (WebContentsView) 343- (void)setMouseDownCanMoveWindow:(BOOL)can_move; 344@end 345 346NativeAppWindowCocoa::NativeAppWindowCocoa( 347 AppWindow* app_window, 348 const AppWindow::CreateParams& params) 349 : app_window_(app_window), 350 has_frame_(params.frame == AppWindow::FRAME_CHROME), 351 is_hidden_with_app_(false), 352 is_maximized_(false), 353 is_fullscreen_(false), 354 is_resizable_(params.resizable), 355 shows_resize_controls_(true), 356 shows_fullscreen_controls_(true), 357 has_frame_color_(params.has_frame_color), 358 active_frame_color_(params.active_frame_color), 359 inactive_frame_color_(params.inactive_frame_color), 360 attention_request_id_(0) { 361 Observe(WebContents()); 362 363 base::scoped_nsobject<NSWindow> window; 364 Class window_class; 365 if (has_frame_) { 366 window_class = has_frame_color_ ? 367 [ShellCustomFrameNSWindow class] : [ShellNSWindow class]; 368 } else { 369 window_class = [ShellFramelessNSWindow class]; 370 } 371 372 // Estimate the initial bounds of the window. Once the frame insets are known, 373 // the window bounds and constraints can be set precisely. 374 NSRect cocoa_bounds = GfxToCocoaBounds( 375 params.GetInitialWindowBounds(gfx::Insets())); 376 window.reset([[window_class alloc] 377 initWithContentRect:cocoa_bounds 378 styleMask:GetWindowStyleMask() 379 backing:NSBackingStoreBuffered 380 defer:NO]); 381 382 std::string name; 383 const extensions::Extension* extension = app_window_->GetExtension(); 384 if (extension) 385 name = extension->name(); 386 [window setTitle:base::SysUTF8ToNSString(name)]; 387 [[window contentView] cr_setWantsLayer:YES]; 388 if (has_frame_ && has_frame_color_) { 389 [base::mac::ObjCCastStrict<ShellCustomFrameNSWindow>(window) 390 setColor:gfx::SkColorToSRGBNSColor(active_frame_color_) 391 inactiveColor:gfx::SkColorToSRGBNSColor(inactive_frame_color_)]; 392 } 393 394 if (base::mac::IsOSSnowLeopard() && 395 [window respondsToSelector:@selector(setBottomCornerRounded:)]) 396 [window setBottomCornerRounded:NO]; 397 398 if (params.always_on_top) 399 [window setLevel:AlwaysOnTopWindowLevel()]; 400 InitCollectionBehavior(window); 401 402 window_controller_.reset( 403 [[NativeAppWindowController alloc] initWithWindow:window.release()]); 404 405 NSView* view = WebContents()->GetNativeView(); 406 [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; 407 408 InstallView(); 409 410 [[window_controller_ window] setDelegate:window_controller_]; 411 [window_controller_ setAppWindow:this]; 412 413 // We can now compute the precise window bounds and constraints. 414 gfx::Insets insets = GetFrameInsets(); 415 SetBounds(params.GetInitialWindowBounds(insets)); 416 SetContentSizeConstraints(params.GetContentMinimumSize(insets), 417 params.GetContentMaximumSize(insets)); 418 419 // Initialize |restored_bounds_|. 420 restored_bounds_ = [this->window() frame]; 421 422 extension_keybinding_registry_.reset(new ExtensionKeybindingRegistryCocoa( 423 Profile::FromBrowserContext(app_window_->browser_context()), 424 window, 425 extensions::ExtensionKeybindingRegistry::PLATFORM_APPS_ONLY, 426 NULL)); 427} 428 429NSUInteger NativeAppWindowCocoa::GetWindowStyleMask() const { 430 NSUInteger style_mask = NSTitledWindowMask | NSClosableWindowMask | 431 NSMiniaturizableWindowMask | 432 NSTexturedBackgroundWindowMask; 433 if (shows_resize_controls_) 434 style_mask |= NSResizableWindowMask; 435 return style_mask; 436} 437 438void NativeAppWindowCocoa::InstallView() { 439 NSView* view = WebContents()->GetNativeView(); 440 if (has_frame_) { 441 [view setFrame:[[window() contentView] bounds]]; 442 [[window() contentView] addSubview:view]; 443 if (!shows_fullscreen_controls_) 444 [[window() standardWindowButton:NSWindowZoomButton] setEnabled:NO]; 445 if (!shows_resize_controls_) 446 [window() setShowsResizeIndicator:NO]; 447 } else { 448 // TODO(jeremya): find a cleaner way to send this information to the 449 // WebContentsViewCocoa view. 450 DCHECK([view 451 respondsToSelector:@selector(setMouseDownCanMoveWindow:)]); 452 [view setMouseDownCanMoveWindow:YES]; 453 454 NSView* frameView = [[window() contentView] superview]; 455 [view setFrame:[frameView bounds]]; 456 [frameView addSubview:view]; 457 458 [[window() standardWindowButton:NSWindowZoomButton] setHidden:YES]; 459 [[window() standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES]; 460 [[window() standardWindowButton:NSWindowCloseButton] setHidden:YES]; 461 462 // Some third-party OS X utilities check the zoom button's enabled state to 463 // determine whether to show custom UI on hover, so we disable it here to 464 // prevent them from doing so in a frameless app window. 465 [[window() standardWindowButton:NSWindowZoomButton] setEnabled:NO]; 466 467 UpdateDraggableRegionViews(); 468 } 469} 470 471void NativeAppWindowCocoa::UninstallView() { 472 NSView* view = WebContents()->GetNativeView(); 473 [view removeFromSuperview]; 474} 475 476bool NativeAppWindowCocoa::IsActive() const { 477 return [window() isKeyWindow]; 478} 479 480bool NativeAppWindowCocoa::IsMaximized() const { 481 return is_maximized_; 482} 483 484bool NativeAppWindowCocoa::IsMinimized() const { 485 return [window() isMiniaturized]; 486} 487 488bool NativeAppWindowCocoa::IsFullscreen() const { 489 return is_fullscreen_; 490} 491 492void NativeAppWindowCocoa::SetFullscreen(int fullscreen_types) { 493 bool fullscreen = (fullscreen_types != AppWindow::FULLSCREEN_TYPE_NONE); 494 if (fullscreen == is_fullscreen_) 495 return; 496 is_fullscreen_ = fullscreen; 497 498 if (base::mac::IsOSLionOrLater()) { 499 // If going fullscreen, but the window is constrained (fullscreen UI control 500 // is disabled), temporarily enable it. It will be disabled again on leaving 501 // fullscreen. 502 if (fullscreen && !shows_fullscreen_controls_) 503 SetFullScreenCollectionBehavior(window(), true); 504 [window() toggleFullScreen:nil]; 505 return; 506 } 507 508 DCHECK(base::mac::IsOSSnowLeopard()); 509 510 // Fade to black. 511 const CGDisplayReservationInterval kFadeDurationSeconds = 0.6; 512 bool did_fade_out = false; 513 CGDisplayFadeReservationToken token; 514 if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token) == 515 kCGErrorSuccess) { 516 did_fade_out = true; 517 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal, 518 kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true); 519 } 520 521 // Since frameless windows insert the WebContentsView into the NSThemeFrame 522 // ([[window contentView] superview]), and since that NSThemeFrame is 523 // destroyed and recreated when we change the styleMask of the window, we 524 // need to remove the view from the window when we change the style, and 525 // add it back afterwards. 526 UninstallView(); 527 if (fullscreen) { 528 UpdateRestoredBounds(); 529 [window() setStyleMask:NSBorderlessWindowMask]; 530 [window() setFrame:[window() 531 frameRectForContentRect:[[window() screen] frame]] 532 display:YES]; 533 base::mac::RequestFullScreen(base::mac::kFullScreenModeAutoHideAll); 534 } else { 535 base::mac::ReleaseFullScreen(base::mac::kFullScreenModeAutoHideAll); 536 [window() setStyleMask:GetWindowStyleMask()]; 537 [window() setFrame:restored_bounds_ display:YES]; 538 } 539 InstallView(); 540 541 // Fade back in. 542 if (did_fade_out) { 543 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor, 544 kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false); 545 CGReleaseDisplayFadeReservation(token); 546 } 547} 548 549bool NativeAppWindowCocoa::IsFullscreenOrPending() const { 550 return is_fullscreen_; 551} 552 553bool NativeAppWindowCocoa::IsDetached() const { 554 return false; 555} 556 557gfx::NativeWindow NativeAppWindowCocoa::GetNativeWindow() { 558 return window(); 559} 560 561gfx::Rect NativeAppWindowCocoa::GetRestoredBounds() const { 562 // Flip coordinates based on the primary screen. 563 NSScreen* screen = [[NSScreen screens] objectAtIndex:0]; 564 NSRect frame = restored_bounds_; 565 gfx::Rect bounds(frame.origin.x, 0, NSWidth(frame), NSHeight(frame)); 566 bounds.set_y(NSHeight([screen frame]) - NSMaxY(frame)); 567 return bounds; 568} 569 570ui::WindowShowState NativeAppWindowCocoa::GetRestoredState() const { 571 if (IsMaximized()) 572 return ui::SHOW_STATE_MAXIMIZED; 573 if (IsFullscreen()) 574 return ui::SHOW_STATE_FULLSCREEN; 575 return ui::SHOW_STATE_NORMAL; 576} 577 578gfx::Rect NativeAppWindowCocoa::GetBounds() const { 579 // Flip coordinates based on the primary screen. 580 NSScreen* screen = [[NSScreen screens] objectAtIndex:0]; 581 NSRect frame = [window() frame]; 582 gfx::Rect bounds(frame.origin.x, 0, NSWidth(frame), NSHeight(frame)); 583 bounds.set_y(NSHeight([screen frame]) - NSMaxY(frame)); 584 return bounds; 585} 586 587void NativeAppWindowCocoa::Show() { 588 if (is_hidden_with_app_) { 589 // If there is a shim to gently request attention, return here. Otherwise 590 // show the window as usual. 591 if (apps::ExtensionAppShimHandler::RequestUserAttentionForWindow( 592 app_window_)) { 593 return; 594 } 595 } 596 597 [window_controller_ showWindow:nil]; 598 Activate(); 599} 600 601void NativeAppWindowCocoa::ShowInactive() { 602 [window() orderFront:window_controller_]; 603} 604 605void NativeAppWindowCocoa::Hide() { 606 HideWithoutMarkingHidden(); 607} 608 609void NativeAppWindowCocoa::Close() { 610 [window() performClose:nil]; 611} 612 613void NativeAppWindowCocoa::Activate() { 614 [BrowserWindowUtils activateWindowForController:window_controller_]; 615} 616 617void NativeAppWindowCocoa::Deactivate() { 618 // TODO(jcivelli): http://crbug.com/51364 Implement me. 619 NOTIMPLEMENTED(); 620} 621 622void NativeAppWindowCocoa::Maximize() { 623 UpdateRestoredBounds(); 624 is_maximized_ = true; // See top of file NOTE: State Before Update. 625 [window() setFrame:[[window() screen] visibleFrame] display:YES animate:YES]; 626} 627 628void NativeAppWindowCocoa::Minimize() { 629 [window() miniaturize:window_controller_]; 630} 631 632void NativeAppWindowCocoa::Restore() { 633 DCHECK(!IsFullscreenOrPending()); // SetFullscreen, not Restore, expected. 634 635 if (IsMaximized()) { 636 is_maximized_ = false; // See top of file NOTE: State Before Update. 637 [window() setFrame:restored_bounds() display:YES animate:YES]; 638 } else if (IsMinimized()) { 639 is_maximized_ = false; // See top of file NOTE: State Before Update. 640 [window() deminiaturize:window_controller_]; 641 } 642} 643 644void NativeAppWindowCocoa::SetBounds(const gfx::Rect& bounds) { 645 // Enforce minimum/maximum bounds. 646 gfx::Rect checked_bounds = bounds; 647 648 NSSize min_size = [window() minSize]; 649 if (bounds.width() < min_size.width) 650 checked_bounds.set_width(min_size.width); 651 if (bounds.height() < min_size.height) 652 checked_bounds.set_height(min_size.height); 653 NSSize max_size = [window() maxSize]; 654 if (checked_bounds.width() > max_size.width) 655 checked_bounds.set_width(max_size.width); 656 if (checked_bounds.height() > max_size.height) 657 checked_bounds.set_height(max_size.height); 658 659 NSRect cocoa_bounds = GfxToCocoaBounds(checked_bounds); 660 [window() setFrame:cocoa_bounds display:YES]; 661 // setFrame: without animate: does not trigger a windowDidEndLiveResize: so 662 // call it here. 663 WindowDidFinishResize(); 664} 665 666void NativeAppWindowCocoa::UpdateWindowIcon() { 667 // TODO(junmin): implement. 668} 669 670void NativeAppWindowCocoa::UpdateWindowTitle() { 671 base::string16 title = app_window_->GetTitle(); 672 [window() setTitle:base::SysUTF16ToNSString(title)]; 673} 674 675void NativeAppWindowCocoa::UpdateBadgeIcon() { 676 // TODO(benwells): implement. 677 NOTIMPLEMENTED(); 678} 679 680void NativeAppWindowCocoa::UpdateShape(scoped_ptr<SkRegion> region) { 681 NOTIMPLEMENTED(); 682} 683 684void NativeAppWindowCocoa::UpdateDraggableRegions( 685 const std::vector<extensions::DraggableRegion>& regions) { 686 // Draggable region is not supported for non-frameless window. 687 if (has_frame_) 688 return; 689 690 draggable_regions_ = regions; 691 UpdateDraggableRegionViews(); 692} 693 694SkRegion* NativeAppWindowCocoa::GetDraggableRegion() { 695 return NULL; 696} 697 698void NativeAppWindowCocoa::HandleKeyboardEvent( 699 const content::NativeWebKeyboardEvent& event) { 700 if (event.skip_in_browser || 701 event.type == content::NativeWebKeyboardEvent::Char) { 702 return; 703 } 704 [window() redispatchKeyEvent:event.os_event]; 705} 706 707void NativeAppWindowCocoa::UpdateDraggableRegionViews() { 708 if (has_frame_) 709 return; 710 711 // All ControlRegionViews should be added as children of the WebContentsView, 712 // because WebContentsView will be removed and re-added when entering and 713 // leaving fullscreen mode. 714 NSView* webView = WebContents()->GetNativeView(); 715 NSInteger webViewWidth = NSWidth([webView bounds]); 716 NSInteger webViewHeight = NSHeight([webView bounds]); 717 718 // Remove all ControlRegionViews that are added last time. 719 // Note that [webView subviews] returns the view's mutable internal array and 720 // it should be copied to avoid mutating the original array while enumerating 721 // it. 722 base::scoped_nsobject<NSArray> subviews([[webView subviews] copy]); 723 for (NSView* subview in subviews.get()) 724 if ([subview isKindOfClass:[ControlRegionView class]]) 725 [subview removeFromSuperview]; 726 727 // Draggable regions is implemented by having the whole web view draggable 728 // (mouseDownCanMoveWindow) and overlaying regions that are not draggable. 729 std::vector<gfx::Rect> system_drag_exclude_areas = 730 CalculateNonDraggableRegions( 731 draggable_regions_, webViewWidth, webViewHeight); 732 733 // Create and add a ControlRegionView for each region that needs to be 734 // excluded from the dragging. 735 for (std::vector<gfx::Rect>::const_iterator iter = 736 system_drag_exclude_areas.begin(); 737 iter != system_drag_exclude_areas.end(); 738 ++iter) { 739 base::scoped_nsobject<NSView> controlRegion( 740 [[ControlRegionView alloc] initWithFrame:NSZeroRect]); 741 [controlRegion setFrame:NSMakeRect(iter->x(), 742 webViewHeight - iter->bottom(), 743 iter->width(), 744 iter->height())]; 745 [webView addSubview:controlRegion]; 746 } 747} 748 749void NativeAppWindowCocoa::FlashFrame(bool flash) { 750 if (flash) { 751 attention_request_id_ = [NSApp requestUserAttention:NSInformationalRequest]; 752 } else { 753 [NSApp cancelUserAttentionRequest:attention_request_id_]; 754 attention_request_id_ = 0; 755 } 756} 757 758bool NativeAppWindowCocoa::IsAlwaysOnTop() const { 759 return [window() level] == AlwaysOnTopWindowLevel(); 760} 761 762void NativeAppWindowCocoa::RenderViewCreated(content::RenderViewHost* rvh) { 763 if (IsActive()) 764 WebContents()->RestoreFocus(); 765} 766 767bool NativeAppWindowCocoa::IsFrameless() const { 768 return !has_frame_; 769} 770 771bool NativeAppWindowCocoa::HasFrameColor() const { 772 return has_frame_color_; 773} 774 775SkColor NativeAppWindowCocoa::ActiveFrameColor() const { 776 return active_frame_color_; 777} 778 779SkColor NativeAppWindowCocoa::InactiveFrameColor() const { 780 return inactive_frame_color_; 781} 782 783gfx::Insets NativeAppWindowCocoa::GetFrameInsets() const { 784 if (!has_frame_) 785 return gfx::Insets(); 786 787 // Flip the coordinates based on the main screen. 788 NSInteger screen_height = 789 NSHeight([[[NSScreen screens] objectAtIndex:0] frame]); 790 791 NSRect frame_nsrect = [window() frame]; 792 gfx::Rect frame_rect(NSRectToCGRect(frame_nsrect)); 793 frame_rect.set_y(screen_height - NSMaxY(frame_nsrect)); 794 795 NSRect content_nsrect = [window() contentRectForFrameRect:frame_nsrect]; 796 gfx::Rect content_rect(NSRectToCGRect(content_nsrect)); 797 content_rect.set_y(screen_height - NSMaxY(content_nsrect)); 798 799 return frame_rect.InsetsFrom(content_rect); 800} 801 802bool NativeAppWindowCocoa::CanHaveAlphaEnabled() const { 803 return false; 804} 805 806gfx::NativeView NativeAppWindowCocoa::GetHostView() const { 807 NOTIMPLEMENTED(); 808 return NULL; 809} 810 811gfx::Point NativeAppWindowCocoa::GetDialogPosition(const gfx::Size& size) { 812 NOTIMPLEMENTED(); 813 return gfx::Point(); 814} 815 816gfx::Size NativeAppWindowCocoa::GetMaximumDialogSize() { 817 NOTIMPLEMENTED(); 818 return gfx::Size(); 819} 820 821void NativeAppWindowCocoa::AddObserver( 822 web_modal::ModalDialogHostObserver* observer) { 823 NOTIMPLEMENTED(); 824} 825 826void NativeAppWindowCocoa::RemoveObserver( 827 web_modal::ModalDialogHostObserver* observer) { 828 NOTIMPLEMENTED(); 829} 830 831void NativeAppWindowCocoa::WindowWillClose() { 832 [window_controller_ setAppWindow:NULL]; 833 app_window_->OnNativeWindowChanged(); 834 app_window_->OnNativeClose(); 835} 836 837void NativeAppWindowCocoa::WindowDidBecomeKey() { 838 content::RenderWidgetHostView* rwhv = 839 WebContents()->GetRenderWidgetHostView(); 840 if (rwhv) 841 rwhv->SetActive(true); 842 app_window_->OnNativeWindowActivated(); 843 844 WebContents()->RestoreFocus(); 845} 846 847void NativeAppWindowCocoa::WindowDidResignKey() { 848 // If our app is still active and we're still the key window, ignore this 849 // message, since it just means that a menu extra (on the "system status bar") 850 // was activated; we'll get another |-windowDidResignKey| if we ever really 851 // lose key window status. 852 if ([NSApp isActive] && ([NSApp keyWindow] == window())) 853 return; 854 855 WebContents()->StoreFocus(); 856 857 content::RenderWidgetHostView* rwhv = 858 WebContents()->GetRenderWidgetHostView(); 859 if (rwhv) 860 rwhv->SetActive(false); 861} 862 863void NativeAppWindowCocoa::WindowDidFinishResize() { 864 // Update |is_maximized_| if needed: 865 // - Exit maximized state if resized. 866 // - Consider us maximized if resize places us back to maximized location. 867 // This happens when returning from fullscreen. 868 NSRect frame = [window() frame]; 869 NSRect screen = [[window() screen] visibleFrame]; 870 if (!NSEqualSizes(frame.size, screen.size)) 871 is_maximized_ = false; 872 else if (NSEqualPoints(frame.origin, screen.origin)) 873 is_maximized_ = true; 874 875 UpdateRestoredBounds(); 876} 877 878void NativeAppWindowCocoa::WindowDidResize() { 879 app_window_->OnNativeWindowChanged(); 880 UpdateDraggableRegionViews(); 881} 882 883void NativeAppWindowCocoa::WindowDidMove() { 884 UpdateRestoredBounds(); 885 app_window_->OnNativeWindowChanged(); 886} 887 888void NativeAppWindowCocoa::WindowDidMiniaturize() { 889 app_window_->OnNativeWindowChanged(); 890} 891 892void NativeAppWindowCocoa::WindowDidDeminiaturize() { 893 app_window_->OnNativeWindowChanged(); 894} 895 896void NativeAppWindowCocoa::WindowDidEnterFullscreen() { 897 is_fullscreen_ = true; 898 app_window_->OSFullscreen(); 899 app_window_->OnNativeWindowChanged(); 900} 901 902void NativeAppWindowCocoa::WindowDidExitFullscreen() { 903 is_fullscreen_ = false; 904 if (!shows_fullscreen_controls_) 905 SetFullScreenCollectionBehavior(window(), false); 906 907 app_window_->Restore(); 908 app_window_->OnNativeWindowChanged(); 909} 910 911void NativeAppWindowCocoa::WindowWillZoom() { 912 // See top of file NOTE: Maximize and Zoom. 913 if (IsMaximized()) 914 Restore(); 915 else 916 Maximize(); 917} 918 919bool NativeAppWindowCocoa::HandledByExtensionCommand(NSEvent* event) { 920 return extension_keybinding_registry_->ProcessKeyEvent( 921 content::NativeWebKeyboardEvent(event)); 922} 923 924void NativeAppWindowCocoa::ShowWithApp() { 925 is_hidden_with_app_ = false; 926 if (!app_window_->is_hidden()) 927 ShowInactive(); 928} 929 930void NativeAppWindowCocoa::HideWithApp() { 931 is_hidden_with_app_ = true; 932 HideWithoutMarkingHidden(); 933} 934 935void NativeAppWindowCocoa::UpdateShelfMenu() { 936 // TODO(tmdiep): To be implemented for Mac. 937 NOTIMPLEMENTED(); 938} 939 940gfx::Size NativeAppWindowCocoa::GetContentMinimumSize() const { 941 return size_constraints_.GetMinimumSize(); 942} 943 944gfx::Size NativeAppWindowCocoa::GetContentMaximumSize() const { 945 return size_constraints_.GetMaximumSize(); 946} 947 948void NativeAppWindowCocoa::SetContentSizeConstraints( 949 const gfx::Size& min_size, const gfx::Size& max_size) { 950 // Update the size constraints. 951 size_constraints_.set_minimum_size(min_size); 952 size_constraints_.set_maximum_size(max_size); 953 954 gfx::Size minimum_size = size_constraints_.GetMinimumSize(); 955 [window() setContentMinSize:NSMakeSize(minimum_size.width(), 956 minimum_size.height())]; 957 958 gfx::Size maximum_size = size_constraints_.GetMaximumSize(); 959 const int kUnboundedSize = apps::SizeConstraints::kUnboundedSize; 960 CGFloat max_width = maximum_size.width() == kUnboundedSize ? 961 CGFLOAT_MAX : maximum_size.width(); 962 CGFloat max_height = maximum_size.height() == kUnboundedSize ? 963 CGFLOAT_MAX : maximum_size.height(); 964 [window() setContentMaxSize:NSMakeSize(max_width, max_height)]; 965 966 // Update the window controls. 967 shows_resize_controls_ = 968 is_resizable_ && !size_constraints_.HasFixedSize(); 969 shows_fullscreen_controls_ = 970 is_resizable_ && !size_constraints_.HasMaximumSize() && has_frame_; 971 972 if (!is_fullscreen_) { 973 [window() setStyleMask:GetWindowStyleMask()]; 974 975 // Set the window to participate in Lion Fullscreen mode. Setting this flag 976 // has no effect on Snow Leopard or earlier. UI controls for fullscreen are 977 // only shown for apps that have unbounded size. 978 if (base::mac::IsOSLionOrLater()) 979 SetFullScreenCollectionBehavior(window(), shows_fullscreen_controls_); 980 } 981 982 if (has_frame_) { 983 [window() setShowsResizeIndicator:shows_resize_controls_]; 984 [[window() standardWindowButton:NSWindowZoomButton] 985 setEnabled:shows_fullscreen_controls_]; 986 } 987} 988 989void NativeAppWindowCocoa::SetAlwaysOnTop(bool always_on_top) { 990 [window() setLevel:(always_on_top ? AlwaysOnTopWindowLevel() : 991 NSNormalWindowLevel)]; 992} 993 994NativeAppWindowCocoa::~NativeAppWindowCocoa() { 995} 996 997ShellNSWindow* NativeAppWindowCocoa::window() const { 998 NSWindow* window = [window_controller_ window]; 999 CHECK(!window || [window isKindOfClass:[ShellNSWindow class]]); 1000 return static_cast<ShellNSWindow*>(window); 1001} 1002 1003content::WebContents* NativeAppWindowCocoa::WebContents() const { 1004 return app_window_->web_contents(); 1005} 1006 1007void NativeAppWindowCocoa::UpdateRestoredBounds() { 1008 if (IsRestored(*this)) 1009 restored_bounds_ = [window() frame]; 1010} 1011 1012void NativeAppWindowCocoa::HideWithoutMarkingHidden() { 1013 [window() orderOut:window_controller_]; 1014} 1015