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.
5 #include "chrome/browser/ui/cocoa/apps/native_app_window_cocoa.h"
7 #include "apps/app_shim/extension_app_shim_handler_mac.h"
8 #include "base/command_line.h"
9 #include "base/mac/mac_util.h"
10 #include "base/strings/sys_string_conversions.h"
11 #include "chrome/browser/profiles/profile.h"
12 #include "chrome/browser/ui/cocoa/browser_window_utils.h"
13 #import "chrome/browser/ui/cocoa/chrome_event_processing_window.h"
14 #include "chrome/browser/ui/cocoa/extensions/extension_keybinding_registry_cocoa.h"
15 #include "chrome/browser/ui/cocoa/extensions/extension_view_mac.h"
16 #include "chrome/common/chrome_switches.h"
17 #include "chrome/common/extensions/extension.h"
18 #include "content/public/browser/native_web_keyboard_event.h"
19 #include "content/public/browser/render_widget_host_view.h"
20 #include "content/public/browser/web_contents.h"
21 #include "content/public/browser/web_contents_view.h"
22 #include "third_party/skia/include/core/SkRegion.h"
24 // NOTE: State Before Update.
26 // Internal state, such as |is_maximized_|, must be set before the window
27 // state is changed so that it is accurate when e.g. a resize results in a call
28 // to |OnNativeWindowChanged|.
30 // NOTE: Maximize and Zoom.
32 // Zooming is implemented manually in order to implement maximize functionality
33 // and to support non resizable windows. The window will be resized explicitly
34 // in the |WindowWillZoom| call.
36 // Attempting maximize and restore functionality with non resizable windows
37 // using the native zoom method did not work, even with
38 // windowWillUseStandardFrame, as the window would not restore back to the
42 using apps::ShellWindow;
44 @interface NSWindow (NSPrivateApis)
45 - (void)setBottomCornerRounded:(BOOL)rounded;
48 // Replicate specific 10.7 SDK declarations for building with prior SDKs.
49 #if !defined(MAC_OS_X_VERSION_10_7) || \
50 MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
52 @interface NSWindow (LionSDKDeclarations)
53 - (void)toggleFullScreen:(id)sender;
57 NSWindowCollectionBehaviorFullScreenPrimary = 1 << 7,
58 NSFullScreenWindowMask = 1 << 14
61 #endif // MAC_OS_X_VERSION_10_7
65 void SetFullScreenCollectionBehavior(NSWindow* window, bool allow_fullscreen) {
66 NSWindowCollectionBehavior behavior = [window collectionBehavior];
68 behavior |= NSWindowCollectionBehaviorFullScreenPrimary;
70 behavior &= ~NSWindowCollectionBehaviorFullScreenPrimary;
71 [window setCollectionBehavior:behavior];
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.
77 NSInteger AlwaysOnTopWindowLevel() {
78 return NSFloatingWindowLevel;
83 @implementation NativeAppWindowController
85 @synthesize appWindow = appWindow_;
87 - (void)windowWillClose:(NSNotification*)notification {
89 appWindow_->WindowWillClose();
92 - (void)windowDidBecomeKey:(NSNotification*)notification {
94 appWindow_->WindowDidBecomeKey();
97 - (void)windowDidResignKey:(NSNotification*)notification {
99 appWindow_->WindowDidResignKey();
102 - (void)windowDidResize:(NSNotification*)notification {
104 appWindow_->WindowDidResize();
107 - (void)windowDidEndLiveResize:(NSNotification*)notification {
109 appWindow_->WindowDidFinishResize();
112 - (void)windowDidEnterFullScreen:(NSNotification*)notification {
114 appWindow_->WindowDidFinishResize();
117 - (void)windowDidExitFullScreen:(NSNotification*)notification {
119 appWindow_->WindowDidFinishResize();
122 - (void)windowDidMove:(NSNotification*)notification {
124 appWindow_->WindowDidMove();
127 - (void)windowDidMiniaturize:(NSNotification*)notification {
129 appWindow_->WindowDidMiniaturize();
132 - (void)windowDidDeminiaturize:(NSNotification*)notification {
134 appWindow_->WindowDidDeminiaturize();
137 - (BOOL)windowShouldZoom:(NSWindow*)window
138 toFrame:(NSRect)newFrame {
140 appWindow_->WindowWillZoom();
141 return NO; // See top of file NOTE: Maximize and Zoom.
144 // Allow non resizable windows (without NSResizableWindowMask) to enter
145 // fullscreen by passing through the full size in willUseFullScreenContentSize.
146 - (NSSize)window:(NSWindow *)window
147 willUseFullScreenContentSize:(NSSize)proposedSize {
151 - (void)executeCommand:(int)command {
152 // No-op, swallow the event.
155 - (BOOL)handledByExtensionCommand:(NSEvent*)event {
157 return appWindow_->HandledByExtensionCommand(event);
163 // This is really a method on NSGrayFrame, so it should only be called on the
164 // view passed into -[NSWindow drawCustomFrameRect:forView:].
165 @interface NSView (PrivateMethods)
166 - (CGFloat)roundedCornerRadius;
169 @interface ShellNSWindow : ChromeEventProcessingWindow
171 @implementation ShellNSWindow
174 @interface ShellCustomFrameNSWindow : ShellNSWindow
176 - (void)drawCustomFrameRect:(NSRect)rect forView:(NSView*)view;
180 @implementation ShellCustomFrameNSWindow
182 - (void)drawCustomFrameRect:(NSRect)rect forView:(NSView*)view {
183 [[NSBezierPath bezierPathWithRect:rect] addClip];
184 [[NSColor clearColor] set];
188 CGFloat cornerRadius = 4.0;
189 if ([view respondsToSelector:@selector(roundedCornerRadius)])
190 cornerRadius = [view roundedCornerRadius];
191 [[NSBezierPath bezierPathWithRoundedRect:[view bounds]
193 yRadius:cornerRadius] addClip];
194 [[NSColor whiteColor] set];
200 @interface ShellFramelessNSWindow : ShellCustomFrameNSWindow
204 @implementation ShellFramelessNSWindow
206 + (NSRect)frameRectForContentRect:(NSRect)contentRect
207 styleMask:(NSUInteger)mask {
211 + (NSRect)contentRectForFrameRect:(NSRect)frameRect
212 styleMask:(NSUInteger)mask {
216 - (NSRect)frameRectForContentRect:(NSRect)contentRect {
220 - (NSRect)contentRectForFrameRect:(NSRect)frameRect {
226 @interface ControlRegionView : NSView {
228 NativeAppWindowCocoa* appWindow_; // Weak; owns self.
233 @implementation ControlRegionView
235 - (id)initWithAppWindow:(NativeAppWindowCocoa*)appWindow {
236 if ((self = [super init]))
237 appWindow_ = appWindow;
241 - (BOOL)mouseDownCanMoveWindow {
245 - (NSView*)hitTest:(NSPoint)aPoint {
246 if (appWindow_->use_system_drag() ||
247 !appWindow_->IsWithinDraggableRegion(aPoint)) {
253 - (void)mouseDown:(NSEvent*)event {
254 appWindow_->HandleMouseEvent(event);
257 - (void)mouseDragged:(NSEvent*)event {
258 appWindow_->HandleMouseEvent(event);
263 @interface NSView (WebContentsView)
264 - (void)setMouseDownCanMoveWindow:(BOOL)can_move;
267 NativeAppWindowCocoa::NativeAppWindowCocoa(
268 ShellWindow* shell_window,
269 const ShellWindow::CreateParams& params)
270 : shell_window_(shell_window),
271 has_frame_(params.frame == ShellWindow::FRAME_CHROME),
273 is_hidden_with_app_(false),
274 is_maximized_(false),
275 is_fullscreen_(false),
276 attention_request_id_(0),
277 use_system_drag_(true) {
278 Observe(web_contents());
280 // Flip coordinates based on the primary screen.
281 NSRect main_screen_rect = [[[NSScreen screens] objectAtIndex:0] frame];
282 NSRect cocoa_bounds = NSMakeRect(params.bounds.x(),
283 NSHeight(main_screen_rect) - params.bounds.y() - params.bounds.height(),
284 params.bounds.width(), params.bounds.height());
286 // If coordinates are < 0, center window on primary screen.
287 if (params.bounds.x() == INT_MIN) {
288 cocoa_bounds.origin.x =
289 floor((NSWidth(main_screen_rect) - NSWidth(cocoa_bounds)) / 2);
291 if (params.bounds.y() == INT_MIN) {
292 cocoa_bounds.origin.y =
293 floor((NSHeight(main_screen_rect) - NSHeight(cocoa_bounds)) / 2);
296 // Initialize |restored_bounds_| after |cocoa_bounds| have been sanitized.
297 restored_bounds_ = cocoa_bounds;
299 base::scoped_nsobject<NSWindow> window;
302 bool should_use_native_frame =
303 CommandLine::ForCurrentProcess()->HasSwitch(
304 switches::kAppsUseNativeFrame);
305 window_class = should_use_native_frame ?
306 [ShellNSWindow class] : [ShellCustomFrameNSWindow class];
308 window_class = [ShellFramelessNSWindow class];
311 ShellWindow::SizeConstraints size_constraints =
312 shell_window_->size_constraints();
313 shows_resize_controls_ =
314 params.resizable && !size_constraints.HasFixedSize();
315 shows_fullscreen_controls_ =
316 params.resizable && !size_constraints.HasMaximumSize();
317 window.reset([[window_class alloc]
318 initWithContentRect:cocoa_bounds
319 styleMask:GetWindowStyleMask()
320 backing:NSBackingStoreBuffered
322 [window setTitle:base::SysUTF8ToNSString(extension()->name())];
324 if (base::mac::IsOSSnowLeopard() &&
325 [window respondsToSelector:@selector(setBottomCornerRounded:)])
326 [window setBottomCornerRounded:NO];
328 if (params.always_on_top)
329 [window setLevel:AlwaysOnTopWindowLevel()];
331 // Set the window to participate in Lion Fullscreen mode. Setting this flag
332 // has no effect on Snow Leopard or earlier. UI controls for fullscreen are
333 // only shown for apps that have unbounded size.
334 if (shows_fullscreen_controls_)
335 SetFullScreenCollectionBehavior(window, true);
337 window_controller_.reset(
338 [[NativeAppWindowController alloc] initWithWindow:window.release()]);
340 NSView* view = web_contents()->GetView()->GetNativeView();
341 [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
343 // By default, the whole frameless window is not draggable.
345 gfx::Rect window_bounds(
346 0, 0, NSWidth(cocoa_bounds), NSHeight(cocoa_bounds));
347 system_drag_exclude_areas_.push_back(window_bounds);
352 [[window_controller_ window] setDelegate:window_controller_];
353 [window_controller_ setAppWindow:this];
354 UpdateWindowMinMaxSize();
356 extension_keybinding_registry_.reset(new ExtensionKeybindingRegistryCocoa(
357 shell_window_->profile(),
359 extensions::ExtensionKeybindingRegistry::PLATFORM_APPS_ONLY,
363 NSUInteger NativeAppWindowCocoa::GetWindowStyleMask() const {
364 NSUInteger style_mask = NSTitledWindowMask | NSClosableWindowMask |
365 NSMiniaturizableWindowMask;
366 if (shows_resize_controls_)
367 style_mask |= NSResizableWindowMask;
369 !CommandLine::ForCurrentProcess()->HasSwitch(
370 switches::kAppsUseNativeFrame)) {
371 style_mask |= NSTexturedBackgroundWindowMask;
376 void NativeAppWindowCocoa::InstallView() {
377 NSView* view = web_contents()->GetView()->GetNativeView();
379 [view setFrame:[[window() contentView] bounds]];
380 [[window() contentView] addSubview:view];
381 if (!shows_fullscreen_controls_)
382 [[window() standardWindowButton:NSWindowZoomButton] setEnabled:NO];
383 if (!shows_resize_controls_)
384 [window() setShowsResizeIndicator:NO];
386 // TODO(jeremya): find a cleaner way to send this information to the
387 // WebContentsViewCocoa view.
389 respondsToSelector:@selector(setMouseDownCanMoveWindow:)]);
390 [view setMouseDownCanMoveWindow:YES];
392 NSView* frameView = [[window() contentView] superview];
393 [view setFrame:[frameView bounds]];
394 [frameView addSubview:view];
396 [[window() standardWindowButton:NSWindowZoomButton] setHidden:YES];
397 [[window() standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES];
398 [[window() standardWindowButton:NSWindowCloseButton] setHidden:YES];
400 // Some third-party OS X utilities check the zoom button's enabled state to
401 // determine whether to show custom UI on hover, so we disable it here to
402 // prevent them from doing so in a frameless app window.
403 [[window() standardWindowButton:NSWindowZoomButton] setEnabled:NO];
405 InstallDraggableRegionViews();
409 void NativeAppWindowCocoa::UninstallView() {
410 NSView* view = web_contents()->GetView()->GetNativeView();
411 [view removeFromSuperview];
414 bool NativeAppWindowCocoa::IsActive() const {
415 return [window() isKeyWindow];
418 bool NativeAppWindowCocoa::IsMaximized() const {
419 return is_maximized_;
422 bool NativeAppWindowCocoa::IsMinimized() const {
423 return [window() isMiniaturized];
426 bool NativeAppWindowCocoa::IsFullscreen() const {
427 return is_fullscreen_;
430 void NativeAppWindowCocoa::SetFullscreen(bool fullscreen) {
431 if (fullscreen == is_fullscreen_)
433 is_fullscreen_ = fullscreen;
435 if (base::mac::IsOSLionOrLater()) {
436 // If going fullscreen, but the window is constrained (fullscreen UI control
437 // is disabled), temporarily enable it. It will be disabled again on leaving
439 if (fullscreen && !shows_fullscreen_controls_)
440 SetFullScreenCollectionBehavior(window(), true);
441 [window() toggleFullScreen:nil];
445 DCHECK(base::mac::IsOSSnowLeopard());
448 const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
449 bool did_fade_out = false;
450 CGDisplayFadeReservationToken token;
451 if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token) ==
454 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
455 kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);
458 // Since frameless windows insert the WebContentsView into the NSThemeFrame
459 // ([[window contentView] superview]), and since that NSThemeFrame is
460 // destroyed and recreated when we change the styleMask of the window, we
461 // need to remove the view from the window when we change the style, and
462 // add it back afterwards.
465 UpdateRestoredBounds();
466 [window() setStyleMask:NSBorderlessWindowMask];
467 [window() setFrame:[window()
468 frameRectForContentRect:[[window() screen] frame]]
470 base::mac::RequestFullScreen(base::mac::kFullScreenModeAutoHideAll);
472 base::mac::ReleaseFullScreen(base::mac::kFullScreenModeAutoHideAll);
473 [window() setStyleMask:GetWindowStyleMask()];
474 [window() setFrame:restored_bounds_ display:YES];
480 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor,
481 kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false);
482 CGReleaseDisplayFadeReservation(token);
486 bool NativeAppWindowCocoa::IsFullscreenOrPending() const {
487 return is_fullscreen_;
490 bool NativeAppWindowCocoa::IsDetached() const {
494 gfx::NativeWindow NativeAppWindowCocoa::GetNativeWindow() {
498 gfx::Rect NativeAppWindowCocoa::GetRestoredBounds() const {
499 // Flip coordinates based on the primary screen.
500 NSScreen* screen = [[NSScreen screens] objectAtIndex:0];
501 NSRect frame = restored_bounds_;
502 gfx::Rect bounds(frame.origin.x, 0, NSWidth(frame), NSHeight(frame));
503 bounds.set_y(NSHeight([screen frame]) - NSMaxY(frame));
507 ui::WindowShowState NativeAppWindowCocoa::GetRestoredState() const {
509 return ui::SHOW_STATE_MAXIMIZED;
511 return ui::SHOW_STATE_FULLSCREEN;
512 return ui::SHOW_STATE_NORMAL;
515 gfx::Rect NativeAppWindowCocoa::GetBounds() const {
516 // Flip coordinates based on the primary screen.
517 NSScreen* screen = [[NSScreen screens] objectAtIndex:0];
518 NSRect frame = [window() frame];
519 gfx::Rect bounds(frame.origin.x, 0, NSWidth(frame), NSHeight(frame));
520 bounds.set_y(NSHeight([screen frame]) - NSMaxY(frame));
524 void NativeAppWindowCocoa::Show() {
527 if (is_hidden_with_app_) {
528 // If there is a shim to gently request attention, return here. Otherwise
529 // show the window as usual.
530 if (apps::ExtensionAppShimHandler::RequestUserAttentionForWindow(
536 [window_controller_ showWindow:nil];
537 [window() makeKeyAndOrderFront:window_controller_];
540 void NativeAppWindowCocoa::ShowInactive() {
542 [window() orderFront:window_controller_];
545 void NativeAppWindowCocoa::Hide() {
547 HideWithoutMarkingHidden();
550 void NativeAppWindowCocoa::Close() {
551 [window() performClose:nil];
554 void NativeAppWindowCocoa::Activate() {
555 [BrowserWindowUtils activateWindowForController:window_controller_];
558 void NativeAppWindowCocoa::Deactivate() {
559 // TODO(jcivelli): http://crbug.com/51364 Implement me.
563 void NativeAppWindowCocoa::Maximize() {
564 UpdateRestoredBounds();
565 is_maximized_ = true; // See top of file NOTE: State Before Update.
566 [window() setFrame:[[window() screen] visibleFrame] display:YES animate:YES];
569 void NativeAppWindowCocoa::Minimize() {
570 [window() miniaturize:window_controller_];
573 void NativeAppWindowCocoa::Restore() {
574 DCHECK(!IsFullscreenOrPending()); // SetFullscreen, not Restore, expected.
577 is_maximized_ = false; // See top of file NOTE: State Before Update.
578 [window() setFrame:restored_bounds() display:YES animate:YES];
579 } else if (IsMinimized()) {
580 is_maximized_ = false; // See top of file NOTE: State Before Update.
581 [window() deminiaturize:window_controller_];
585 void NativeAppWindowCocoa::SetBounds(const gfx::Rect& bounds) {
586 // Enforce minimum/maximum bounds.
587 gfx::Rect checked_bounds = bounds;
589 NSSize min_size = [window() minSize];
590 if (bounds.width() < min_size.width)
591 checked_bounds.set_width(min_size.width);
592 if (bounds.height() < min_size.height)
593 checked_bounds.set_height(min_size.height);
594 NSSize max_size = [window() maxSize];
595 if (checked_bounds.width() > max_size.width)
596 checked_bounds.set_width(max_size.width);
597 if (checked_bounds.height() > max_size.height)
598 checked_bounds.set_height(max_size.height);
600 NSRect cocoa_bounds = NSMakeRect(checked_bounds.x(), 0,
601 checked_bounds.width(),
602 checked_bounds.height());
603 // Flip coordinates based on the primary screen.
604 NSScreen* screen = [[NSScreen screens] objectAtIndex:0];
605 cocoa_bounds.origin.y = NSHeight([screen frame]) - checked_bounds.bottom();
607 [window() setFrame:cocoa_bounds display:YES];
608 // setFrame: without animate: does not trigger a windowDidEndLiveResize: so
610 WindowDidFinishResize();
613 void NativeAppWindowCocoa::UpdateWindowIcon() {
614 // TODO(junmin): implement.
617 void NativeAppWindowCocoa::UpdateWindowTitle() {
618 string16 title = shell_window_->GetTitle();
619 [window() setTitle:base::SysUTF16ToNSString(title)];
622 void NativeAppWindowCocoa::UpdateInputRegion(scoped_ptr<SkRegion> region) {
626 void NativeAppWindowCocoa::UpdateDraggableRegions(
627 const std::vector<extensions::DraggableRegion>& regions) {
628 // Draggable region is not supported for non-frameless window.
632 // To use system drag, the window has to be marked as draggable with
633 // non-draggable areas being excluded via overlapping views.
634 // 1) If no draggable area is provided, the window is not draggable at all.
635 // 2) If only one draggable area is given, as this is the most common
636 // case, use the system drag. The non-draggable areas that are opposite of
637 // the draggable area are computed.
638 // 3) Otherwise, use the custom drag. As such, we lose the capability to
639 // support some features like snapping into other space.
641 // Determine how to perform the drag by counting the number of draggable
643 const extensions::DraggableRegion* draggable_area = NULL;
644 use_system_drag_ = true;
645 for (std::vector<extensions::DraggableRegion>::const_iterator iter =
647 iter != regions.end();
649 if (iter->draggable) {
650 // If more than one draggable area is found, use custom drag.
651 if (draggable_area) {
652 use_system_drag_ = false;
655 draggable_area = &(*iter);
659 if (use_system_drag_)
660 UpdateDraggableRegionsForSystemDrag(regions, draggable_area);
662 UpdateDraggableRegionsForCustomDrag(regions);
664 InstallDraggableRegionViews();
667 SkRegion* NativeAppWindowCocoa::GetDraggableRegion() {
668 return draggable_region_.get();
671 void NativeAppWindowCocoa::UpdateDraggableRegionsForSystemDrag(
672 const std::vector<extensions::DraggableRegion>& regions,
673 const extensions::DraggableRegion* draggable_area) {
674 NSView* web_view = web_contents()->GetView()->GetNativeView();
675 NSInteger web_view_width = NSWidth([web_view bounds]);
676 NSInteger web_view_height = NSHeight([web_view bounds]);
678 system_drag_exclude_areas_.clear();
680 // The whole window is not draggable if no draggable area is given.
681 if (!draggable_area) {
682 gfx::Rect window_bounds(0, 0, web_view_width, web_view_height);
683 system_drag_exclude_areas_.push_back(window_bounds);
687 // Otherwise, there is only one draggable area. Compute non-draggable areas
688 // that are the opposite of the given draggable area, combined with the
689 // remaining provided non-draggable areas.
691 // Copy all given non-draggable areas.
692 for (std::vector<extensions::DraggableRegion>::const_iterator iter =
694 iter != regions.end();
696 if (!iter->draggable)
697 system_drag_exclude_areas_.push_back(iter->bounds);
700 gfx::Rect draggable_bounds = draggable_area->bounds;
701 gfx::Rect non_draggable_bounds;
703 // Add the non-draggable area above the given draggable area.
704 if (draggable_bounds.y() > 0) {
705 non_draggable_bounds.SetRect(0,
708 draggable_bounds.y() - 1);
709 system_drag_exclude_areas_.push_back(non_draggable_bounds);
712 // Add the non-draggable area below the given draggable area.
713 if (draggable_bounds.bottom() < web_view_height) {
714 non_draggable_bounds.SetRect(0,
715 draggable_bounds.bottom() + 1,
717 web_view_height - draggable_bounds.bottom());
718 system_drag_exclude_areas_.push_back(non_draggable_bounds);
721 // Add the non-draggable area to the left of the given draggable area.
722 if (draggable_bounds.x() > 0) {
723 non_draggable_bounds.SetRect(0,
724 draggable_bounds.y(),
725 draggable_bounds.x() - 1,
726 draggable_bounds.height());
727 system_drag_exclude_areas_.push_back(non_draggable_bounds);
730 // Add the non-draggable area to the right of the given draggable area.
731 if (draggable_bounds.right() < web_view_width) {
732 non_draggable_bounds.SetRect(draggable_bounds.right() + 1,
733 draggable_bounds.y(),
734 web_view_width - draggable_bounds.right(),
735 draggable_bounds.height());
736 system_drag_exclude_areas_.push_back(non_draggable_bounds);
740 void NativeAppWindowCocoa::UpdateDraggableRegionsForCustomDrag(
741 const std::vector<extensions::DraggableRegion>& regions) {
742 // We still need one ControlRegionView to cover the whole window such that
743 // mouse events could be captured.
744 NSView* web_view = web_contents()->GetView()->GetNativeView();
745 gfx::Rect window_bounds(
746 0, 0, NSWidth([web_view bounds]), NSHeight([web_view bounds]));
747 system_drag_exclude_areas_.clear();
748 system_drag_exclude_areas_.push_back(window_bounds);
750 // Aggregate the draggable areas and non-draggable areas such that hit test
751 // could be performed easily.
752 draggable_region_.reset(ShellWindow::RawDraggableRegionsToSkRegion(regions));
755 void NativeAppWindowCocoa::HandleKeyboardEvent(
756 const content::NativeWebKeyboardEvent& event) {
757 if (event.skip_in_browser ||
758 event.type == content::NativeWebKeyboardEvent::Char) {
761 [window() redispatchKeyEvent:event.os_event];
764 void NativeAppWindowCocoa::InstallDraggableRegionViews() {
767 // All ControlRegionViews should be added as children of the WebContentsView,
768 // because WebContentsView will be removed and re-added when entering and
769 // leaving fullscreen mode.
770 NSView* webView = web_contents()->GetView()->GetNativeView();
771 NSInteger webViewHeight = NSHeight([webView bounds]);
773 // Remove all ControlRegionViews that are added last time.
774 // Note that [webView subviews] returns the view's mutable internal array and
775 // it should be copied to avoid mutating the original array while enumerating
777 base::scoped_nsobject<NSArray> subviews([[webView subviews] copy]);
778 for (NSView* subview in subviews.get())
779 if ([subview isKindOfClass:[ControlRegionView class]])
780 [subview removeFromSuperview];
782 // Create and add ControlRegionView for each region that needs to be excluded
783 // from the dragging.
784 for (std::vector<gfx::Rect>::const_iterator iter =
785 system_drag_exclude_areas_.begin();
786 iter != system_drag_exclude_areas_.end();
788 base::scoped_nsobject<NSView> controlRegion(
789 [[ControlRegionView alloc] initWithAppWindow:this]);
790 [controlRegion setFrame:NSMakeRect(iter->x(),
791 webViewHeight - iter->bottom(),
794 [controlRegion setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
795 [webView addSubview:controlRegion];
799 void NativeAppWindowCocoa::FlashFrame(bool flash) {
801 attention_request_id_ = [NSApp requestUserAttention:NSInformationalRequest];
803 [NSApp cancelUserAttentionRequest:attention_request_id_];
804 attention_request_id_ = 0;
808 bool NativeAppWindowCocoa::IsAlwaysOnTop() const {
809 return [window() level] == AlwaysOnTopWindowLevel();
812 void NativeAppWindowCocoa::RenderViewHostChanged(
813 content::RenderViewHost* old_host,
814 content::RenderViewHost* new_host) {
815 web_contents()->GetView()->Focus();
818 bool NativeAppWindowCocoa::IsFrameless() const {
822 gfx::Insets NativeAppWindowCocoa::GetFrameInsets() const {
824 return gfx::Insets();
826 // Flip the coordinates based on the main screen.
827 NSInteger screen_height =
828 NSHeight([[[NSScreen screens] objectAtIndex:0] frame]);
830 NSRect frame_nsrect = [window() frame];
831 gfx::Rect frame_rect(NSRectToCGRect(frame_nsrect));
832 frame_rect.set_y(screen_height - NSMaxY(frame_nsrect));
834 NSRect content_nsrect = [window() contentRectForFrameRect:frame_nsrect];
835 gfx::Rect content_rect(NSRectToCGRect(content_nsrect));
836 content_rect.set_y(screen_height - NSMaxY(content_nsrect));
838 return frame_rect.InsetsFrom(content_rect);
841 gfx::NativeView NativeAppWindowCocoa::GetHostView() const {
846 gfx::Point NativeAppWindowCocoa::GetDialogPosition(const gfx::Size& size) {
851 gfx::Size NativeAppWindowCocoa::GetMaximumDialogSize() {
856 void NativeAppWindowCocoa::AddObserver(
857 web_modal::ModalDialogHostObserver* observer) {
861 void NativeAppWindowCocoa::RemoveObserver(
862 web_modal::ModalDialogHostObserver* observer) {
866 void NativeAppWindowCocoa::WindowWillClose() {
867 [window_controller_ setAppWindow:NULL];
868 shell_window_->OnNativeWindowChanged();
869 shell_window_->OnNativeClose();
872 void NativeAppWindowCocoa::WindowDidBecomeKey() {
873 content::RenderWidgetHostView* rwhv =
874 web_contents()->GetRenderWidgetHostView();
876 rwhv->SetActive(true);
877 shell_window_->OnNativeWindowActivated();
880 void NativeAppWindowCocoa::WindowDidResignKey() {
881 // If our app is still active and we're still the key window, ignore this
882 // message, since it just means that a menu extra (on the "system status bar")
883 // was activated; we'll get another |-windowDidResignKey| if we ever really
884 // lose key window status.
885 if ([NSApp isActive] && ([NSApp keyWindow] == window()))
888 content::RenderWidgetHostView* rwhv =
889 web_contents()->GetRenderWidgetHostView();
891 rwhv->SetActive(false);
894 void NativeAppWindowCocoa::WindowDidFinishResize() {
895 // Update |is_maximized_| if needed:
896 // - Exit maximized state if resized.
897 // - Consider us maximized if resize places us back to maximized location.
898 // This happens when returning from fullscreen.
899 NSRect frame = [window() frame];
900 NSRect screen = [[window() screen] visibleFrame];
901 if (!NSEqualSizes(frame.size, screen.size))
902 is_maximized_ = false;
903 else if (NSEqualPoints(frame.origin, screen.origin))
904 is_maximized_ = true;
906 // Update |is_fullscreen_| if needed.
907 is_fullscreen_ = ([window() styleMask] & NSFullScreenWindowMask) != 0;
908 // If not fullscreen but the window is constrained, disable the fullscreen UI
910 if (!is_fullscreen_ && !shows_fullscreen_controls_)
911 SetFullScreenCollectionBehavior(window(), false);
913 UpdateRestoredBounds();
916 void NativeAppWindowCocoa::WindowDidResize() {
917 shell_window_->OnNativeWindowChanged();
920 void NativeAppWindowCocoa::WindowDidMove() {
921 UpdateRestoredBounds();
922 shell_window_->OnNativeWindowChanged();
925 void NativeAppWindowCocoa::WindowDidMiniaturize() {
926 shell_window_->OnNativeWindowChanged();
929 void NativeAppWindowCocoa::WindowDidDeminiaturize() {
930 shell_window_->OnNativeWindowChanged();
933 void NativeAppWindowCocoa::WindowWillZoom() {
934 // See top of file NOTE: Maximize and Zoom.
941 bool NativeAppWindowCocoa::HandledByExtensionCommand(NSEvent* event) {
942 return extension_keybinding_registry_->ProcessKeyEvent(
943 content::NativeWebKeyboardEvent(event));
946 void NativeAppWindowCocoa::HandleMouseEvent(NSEvent* event) {
947 if ([event type] == NSLeftMouseDown) {
948 last_mouse_location_ =
949 [window() convertBaseToScreen:[event locationInWindow]];
950 } else if ([event type] == NSLeftMouseDragged) {
951 NSPoint current_mouse_location =
952 [window() convertBaseToScreen:[event locationInWindow]];
953 NSPoint frame_origin = [window() frame].origin;
954 frame_origin.x += current_mouse_location.x - last_mouse_location_.x;
955 frame_origin.y += current_mouse_location.y - last_mouse_location_.y;
956 [window() setFrameOrigin:frame_origin];
957 last_mouse_location_ = current_mouse_location;
961 bool NativeAppWindowCocoa::IsWithinDraggableRegion(NSPoint point) const {
962 if (!draggable_region_)
964 NSView* webView = web_contents()->GetView()->GetNativeView();
965 NSInteger webViewHeight = NSHeight([webView bounds]);
966 // |draggable_region_| is stored in local platform-indepdent coordiate system
967 // while |point| is in local Cocoa coordinate system. Do the conversion
968 // to match these two.
969 return draggable_region_->contains(point.x, webViewHeight - point.y);
972 void NativeAppWindowCocoa::HideWithApp() {
973 is_hidden_with_app_ = true;
974 HideWithoutMarkingHidden();
977 void NativeAppWindowCocoa::ShowWithApp() {
978 is_hidden_with_app_ = false;
983 void NativeAppWindowCocoa::SetAlwaysOnTop(bool always_on_top) {
984 [window() setLevel:(always_on_top ? AlwaysOnTopWindowLevel() :
985 NSNormalWindowLevel)];
986 shell_window_->OnNativeWindowChanged();
989 void NativeAppWindowCocoa::HideWithoutMarkingHidden() {
990 [window() orderOut:window_controller_];
993 NativeAppWindowCocoa::~NativeAppWindowCocoa() {
996 ShellNSWindow* NativeAppWindowCocoa::window() const {
997 NSWindow* window = [window_controller_ window];
998 CHECK(!window || [window isKindOfClass:[ShellNSWindow class]]);
999 return static_cast<ShellNSWindow*>(window);
1002 void NativeAppWindowCocoa::UpdateRestoredBounds() {
1003 if (IsRestored(*this))
1004 restored_bounds_ = [window() frame];
1007 void NativeAppWindowCocoa::UpdateWindowMinMaxSize() {
1008 gfx::Size min_size = shell_window_->size_constraints().GetMinimumSize();
1009 [window() setContentMinSize:NSMakeSize(min_size.width(), min_size.height())];
1011 gfx::Size max_size = shell_window_->size_constraints().GetMaximumSize();
1012 const int kUnboundedSize = ShellWindow::SizeConstraints::kUnboundedSize;
1013 CGFloat max_width = max_size.width() == kUnboundedSize ?
1014 CGFLOAT_MAX : max_size.width();
1015 CGFloat max_height = max_size.height() == kUnboundedSize ?
1016 CGFLOAT_MAX : max_size.height();
1017 [window() setContentMaxSize:NSMakeSize(max_width, max_height)];