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 "content/public/browser/native_web_keyboard_event.h"
18 #include "content/public/browser/render_widget_host_view.h"
19 #include "content/public/browser/web_contents.h"
20 #include "content/public/browser/web_contents_view.h"
21 #include "extensions/common/extension.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
41 using apps::AppWindow;
43 @interface NSWindow (NSPrivateApis)
44 - (void)setBottomCornerRounded:(BOOL)rounded;
47 // Replicate specific 10.7 SDK declarations for building with prior SDKs.
48 #if !defined(MAC_OS_X_VERSION_10_7) || \
49 MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
51 @interface NSWindow (LionSDKDeclarations)
52 - (void)toggleFullScreen:(id)sender;
56 NSWindowCollectionBehaviorFullScreenPrimary = 1 << 7,
57 NSFullScreenWindowMask = 1 << 14
60 #endif // MAC_OS_X_VERSION_10_7
64 void SetFullScreenCollectionBehavior(NSWindow* window, bool allow_fullscreen) {
65 NSWindowCollectionBehavior behavior = [window collectionBehavior];
67 behavior |= NSWindowCollectionBehaviorFullScreenPrimary;
69 behavior &= ~NSWindowCollectionBehaviorFullScreenPrimary;
70 [window setCollectionBehavior:behavior];
73 void InitCollectionBehavior(NSWindow* window) {
74 // Since always-on-top windows have a higher window level
75 // than NSNormalWindowLevel, they will default to
76 // NSWindowCollectionBehaviorTransient. Set the value
77 // explicitly here to match normal windows.
78 NSWindowCollectionBehavior behavior = [window collectionBehavior];
79 behavior |= NSWindowCollectionBehaviorManaged;
80 [window setCollectionBehavior:behavior];
83 // Returns the level for windows that are configured to be always on top.
84 // This is not a constant because NSFloatingWindowLevel is a macro defined
85 // as a function call.
86 NSInteger AlwaysOnTopWindowLevel() {
87 return NSFloatingWindowLevel;
92 @implementation NativeAppWindowController
94 @synthesize appWindow = appWindow_;
96 - (void)windowWillClose:(NSNotification*)notification {
98 appWindow_->WindowWillClose();
101 - (void)windowDidBecomeKey:(NSNotification*)notification {
103 appWindow_->WindowDidBecomeKey();
106 - (void)windowDidResignKey:(NSNotification*)notification {
108 appWindow_->WindowDidResignKey();
111 - (void)windowDidResize:(NSNotification*)notification {
113 appWindow_->WindowDidResize();
116 - (void)windowDidEndLiveResize:(NSNotification*)notification {
118 appWindow_->WindowDidFinishResize();
121 - (void)windowDidEnterFullScreen:(NSNotification*)notification {
123 appWindow_->WindowDidFinishResize();
126 - (void)windowDidExitFullScreen:(NSNotification*)notification {
128 appWindow_->WindowDidFinishResize();
131 - (void)windowDidMove:(NSNotification*)notification {
133 appWindow_->WindowDidMove();
136 - (void)windowDidMiniaturize:(NSNotification*)notification {
138 appWindow_->WindowDidMiniaturize();
141 - (void)windowDidDeminiaturize:(NSNotification*)notification {
143 appWindow_->WindowDidDeminiaturize();
146 - (BOOL)windowShouldZoom:(NSWindow*)window
147 toFrame:(NSRect)newFrame {
149 appWindow_->WindowWillZoom();
150 return NO; // See top of file NOTE: Maximize and Zoom.
153 // Allow non resizable windows (without NSResizableWindowMask) to enter
154 // fullscreen by passing through the full size in willUseFullScreenContentSize.
155 - (NSSize)window:(NSWindow *)window
156 willUseFullScreenContentSize:(NSSize)proposedSize {
160 - (void)executeCommand:(int)command {
161 // No-op, swallow the event.
164 - (BOOL)handledByExtensionCommand:(NSEvent*)event {
166 return appWindow_->HandledByExtensionCommand(event);
172 // This is really a method on NSGrayFrame, so it should only be called on the
173 // view passed into -[NSWindow drawCustomFrameRect:forView:].
174 @interface NSView (PrivateMethods)
175 - (CGFloat)roundedCornerRadius;
178 // TODO(jamescook): Should these be AppNSWindow to match apps::AppWindow?
179 // http://crbug.com/344082
180 @interface ShellNSWindow : ChromeEventProcessingWindow
182 @implementation ShellNSWindow
185 @interface ShellCustomFrameNSWindow : ShellNSWindow
187 - (void)drawCustomFrameRect:(NSRect)rect forView:(NSView*)view;
191 @implementation ShellCustomFrameNSWindow
193 - (void)drawCustomFrameRect:(NSRect)rect forView:(NSView*)view {
194 [[NSBezierPath bezierPathWithRect:rect] addClip];
195 [[NSColor clearColor] set];
199 CGFloat cornerRadius = 4.0;
200 if ([view respondsToSelector:@selector(roundedCornerRadius)])
201 cornerRadius = [view roundedCornerRadius];
202 [[NSBezierPath bezierPathWithRoundedRect:[view bounds]
204 yRadius:cornerRadius] addClip];
205 [[NSColor whiteColor] set];
211 @interface ShellFramelessNSWindow : ShellCustomFrameNSWindow
215 @implementation ShellFramelessNSWindow
217 + (NSRect)frameRectForContentRect:(NSRect)contentRect
218 styleMask:(NSUInteger)mask {
222 + (NSRect)contentRectForFrameRect:(NSRect)frameRect
223 styleMask:(NSUInteger)mask {
227 - (NSRect)frameRectForContentRect:(NSRect)contentRect {
231 - (NSRect)contentRectForFrameRect:(NSRect)frameRect {
237 @interface ControlRegionView : NSView {
239 NativeAppWindowCocoa* appWindow_; // Weak; owns self.
244 @implementation ControlRegionView
246 - (id)initWithAppWindow:(NativeAppWindowCocoa*)appWindow {
247 if ((self = [super init]))
248 appWindow_ = appWindow;
252 - (BOOL)mouseDownCanMoveWindow {
256 - (NSView*)hitTest:(NSPoint)aPoint {
257 if (appWindow_->use_system_drag() ||
258 !appWindow_->IsWithinDraggableRegion(aPoint)) {
264 - (void)mouseDown:(NSEvent*)event {
265 appWindow_->HandleMouseEvent(event);
268 - (void)mouseDragged:(NSEvent*)event {
269 appWindow_->HandleMouseEvent(event);
274 @interface NSView (WebContentsView)
275 - (void)setMouseDownCanMoveWindow:(BOOL)can_move;
278 NativeAppWindowCocoa::NativeAppWindowCocoa(
279 AppWindow* app_window,
280 const AppWindow::CreateParams& params)
281 : app_window_(app_window),
282 has_frame_(params.frame == AppWindow::FRAME_CHROME),
284 is_hidden_with_app_(false),
285 is_maximized_(false),
286 is_fullscreen_(false),
287 attention_request_id_(0),
288 use_system_drag_(true) {
289 Observe(web_contents());
291 // Flip coordinates based on the primary screen.
292 NSRect main_screen_rect = [[[NSScreen screens] objectAtIndex:0] frame];
293 NSRect cocoa_bounds = NSMakeRect(params.bounds.x(),
294 NSHeight(main_screen_rect) - params.bounds.y() - params.bounds.height(),
295 params.bounds.width(), params.bounds.height());
297 // If coordinates are < 0, center window on primary screen.
298 if (params.bounds.x() == INT_MIN) {
299 cocoa_bounds.origin.x =
300 floor((NSWidth(main_screen_rect) - NSWidth(cocoa_bounds)) / 2);
302 if (params.bounds.y() == INT_MIN) {
303 cocoa_bounds.origin.y =
304 floor((NSHeight(main_screen_rect) - NSHeight(cocoa_bounds)) / 2);
307 // Initialize |restored_bounds_| after |cocoa_bounds| have been sanitized.
308 restored_bounds_ = cocoa_bounds;
310 base::scoped_nsobject<NSWindow> window;
313 bool should_use_native_frame =
314 CommandLine::ForCurrentProcess()->HasSwitch(
315 switches::kAppsUseNativeFrame);
316 window_class = should_use_native_frame ?
317 [ShellNSWindow class] : [ShellCustomFrameNSWindow class];
319 window_class = [ShellFramelessNSWindow class];
322 AppWindow::SizeConstraints size_constraints = app_window_->size_constraints();
323 shows_resize_controls_ =
324 params.resizable && !size_constraints.HasFixedSize();
325 shows_fullscreen_controls_ =
326 params.resizable && !size_constraints.HasMaximumSize();
327 window.reset([[window_class alloc]
328 initWithContentRect:cocoa_bounds
329 styleMask:GetWindowStyleMask()
330 backing:NSBackingStoreBuffered
332 [window setTitle:base::SysUTF8ToNSString(extension()->name())];
334 if (base::mac::IsOSSnowLeopard() &&
335 [window respondsToSelector:@selector(setBottomCornerRounded:)])
336 [window setBottomCornerRounded:NO];
338 if (params.always_on_top)
339 [window setLevel:AlwaysOnTopWindowLevel()];
340 InitCollectionBehavior(window);
342 // Set the window to participate in Lion Fullscreen mode. Setting this flag
343 // has no effect on Snow Leopard or earlier. UI controls for fullscreen are
344 // only shown for apps that have unbounded size.
345 if (shows_fullscreen_controls_)
346 SetFullScreenCollectionBehavior(window, true);
348 window_controller_.reset(
349 [[NativeAppWindowController alloc] initWithWindow:window.release()]);
351 NSView* view = web_contents()->GetView()->GetNativeView();
352 [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
354 // By default, the whole frameless window is not draggable.
356 gfx::Rect window_bounds(
357 0, 0, NSWidth(cocoa_bounds), NSHeight(cocoa_bounds));
358 system_drag_exclude_areas_.push_back(window_bounds);
363 [[window_controller_ window] setDelegate:window_controller_];
364 [window_controller_ setAppWindow:this];
365 UpdateWindowMinMaxSize();
367 extension_keybinding_registry_.reset(new ExtensionKeybindingRegistryCocoa(
368 Profile::FromBrowserContext(app_window_->browser_context()),
370 extensions::ExtensionKeybindingRegistry::PLATFORM_APPS_ONLY,
374 NSUInteger NativeAppWindowCocoa::GetWindowStyleMask() const {
375 NSUInteger style_mask = NSTitledWindowMask | NSClosableWindowMask |
376 NSMiniaturizableWindowMask;
377 if (shows_resize_controls_)
378 style_mask |= NSResizableWindowMask;
380 !CommandLine::ForCurrentProcess()->HasSwitch(
381 switches::kAppsUseNativeFrame)) {
382 style_mask |= NSTexturedBackgroundWindowMask;
387 void NativeAppWindowCocoa::InstallView() {
388 NSView* view = web_contents()->GetView()->GetNativeView();
390 [view setFrame:[[window() contentView] bounds]];
391 [[window() contentView] addSubview:view];
392 if (!shows_fullscreen_controls_)
393 [[window() standardWindowButton:NSWindowZoomButton] setEnabled:NO];
394 if (!shows_resize_controls_)
395 [window() setShowsResizeIndicator:NO];
397 // TODO(jeremya): find a cleaner way to send this information to the
398 // WebContentsViewCocoa view.
400 respondsToSelector:@selector(setMouseDownCanMoveWindow:)]);
401 [view setMouseDownCanMoveWindow:YES];
403 NSView* frameView = [[window() contentView] superview];
404 [view setFrame:[frameView bounds]];
405 [frameView addSubview:view];
407 [[window() standardWindowButton:NSWindowZoomButton] setHidden:YES];
408 [[window() standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES];
409 [[window() standardWindowButton:NSWindowCloseButton] setHidden:YES];
411 // Some third-party OS X utilities check the zoom button's enabled state to
412 // determine whether to show custom UI on hover, so we disable it here to
413 // prevent them from doing so in a frameless app window.
414 [[window() standardWindowButton:NSWindowZoomButton] setEnabled:NO];
416 InstallDraggableRegionViews();
420 void NativeAppWindowCocoa::UninstallView() {
421 NSView* view = web_contents()->GetView()->GetNativeView();
422 [view removeFromSuperview];
425 bool NativeAppWindowCocoa::IsActive() const {
426 return [window() isKeyWindow];
429 bool NativeAppWindowCocoa::IsMaximized() const {
430 return is_maximized_;
433 bool NativeAppWindowCocoa::IsMinimized() const {
434 return [window() isMiniaturized];
437 bool NativeAppWindowCocoa::IsFullscreen() const {
438 return is_fullscreen_;
441 void NativeAppWindowCocoa::SetFullscreen(int fullscreen_types) {
442 bool fullscreen = (fullscreen_types != AppWindow::FULLSCREEN_TYPE_NONE);
443 if (fullscreen == is_fullscreen_)
445 is_fullscreen_ = fullscreen;
447 if (base::mac::IsOSLionOrLater()) {
448 // If going fullscreen, but the window is constrained (fullscreen UI control
449 // is disabled), temporarily enable it. It will be disabled again on leaving
451 if (fullscreen && !shows_fullscreen_controls_)
452 SetFullScreenCollectionBehavior(window(), true);
453 [window() toggleFullScreen:nil];
457 DCHECK(base::mac::IsOSSnowLeopard());
460 const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
461 bool did_fade_out = false;
462 CGDisplayFadeReservationToken token;
463 if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token) ==
466 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
467 kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);
470 // Since frameless windows insert the WebContentsView into the NSThemeFrame
471 // ([[window contentView] superview]), and since that NSThemeFrame is
472 // destroyed and recreated when we change the styleMask of the window, we
473 // need to remove the view from the window when we change the style, and
474 // add it back afterwards.
477 UpdateRestoredBounds();
478 [window() setStyleMask:NSBorderlessWindowMask];
479 [window() setFrame:[window()
480 frameRectForContentRect:[[window() screen] frame]]
482 base::mac::RequestFullScreen(base::mac::kFullScreenModeAutoHideAll);
484 base::mac::ReleaseFullScreen(base::mac::kFullScreenModeAutoHideAll);
485 [window() setStyleMask:GetWindowStyleMask()];
486 [window() setFrame:restored_bounds_ display:YES];
492 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor,
493 kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false);
494 CGReleaseDisplayFadeReservation(token);
498 bool NativeAppWindowCocoa::IsFullscreenOrPending() const {
499 return is_fullscreen_;
502 bool NativeAppWindowCocoa::IsDetached() const {
506 gfx::NativeWindow NativeAppWindowCocoa::GetNativeWindow() {
510 gfx::Rect NativeAppWindowCocoa::GetRestoredBounds() const {
511 // Flip coordinates based on the primary screen.
512 NSScreen* screen = [[NSScreen screens] objectAtIndex:0];
513 NSRect frame = restored_bounds_;
514 gfx::Rect bounds(frame.origin.x, 0, NSWidth(frame), NSHeight(frame));
515 bounds.set_y(NSHeight([screen frame]) - NSMaxY(frame));
519 ui::WindowShowState NativeAppWindowCocoa::GetRestoredState() const {
521 return ui::SHOW_STATE_MAXIMIZED;
523 return ui::SHOW_STATE_FULLSCREEN;
524 return ui::SHOW_STATE_NORMAL;
527 gfx::Rect NativeAppWindowCocoa::GetBounds() const {
528 // Flip coordinates based on the primary screen.
529 NSScreen* screen = [[NSScreen screens] objectAtIndex:0];
530 NSRect frame = [window() frame];
531 gfx::Rect bounds(frame.origin.x, 0, NSWidth(frame), NSHeight(frame));
532 bounds.set_y(NSHeight([screen frame]) - NSMaxY(frame));
536 void NativeAppWindowCocoa::Show() {
539 if (is_hidden_with_app_) {
540 // If there is a shim to gently request attention, return here. Otherwise
541 // show the window as usual.
542 if (apps::ExtensionAppShimHandler::RequestUserAttentionForWindow(
548 [window_controller_ showWindow:nil];
552 void NativeAppWindowCocoa::ShowInactive() {
554 [window() orderFront:window_controller_];
557 void NativeAppWindowCocoa::Hide() {
559 HideWithoutMarkingHidden();
562 void NativeAppWindowCocoa::Close() {
563 [window() performClose:nil];
566 void NativeAppWindowCocoa::Activate() {
567 [BrowserWindowUtils activateWindowForController:window_controller_];
570 void NativeAppWindowCocoa::Deactivate() {
571 // TODO(jcivelli): http://crbug.com/51364 Implement me.
575 void NativeAppWindowCocoa::Maximize() {
576 UpdateRestoredBounds();
577 is_maximized_ = true; // See top of file NOTE: State Before Update.
578 [window() setFrame:[[window() screen] visibleFrame] display:YES animate:YES];
581 void NativeAppWindowCocoa::Minimize() {
582 [window() miniaturize:window_controller_];
585 void NativeAppWindowCocoa::Restore() {
586 DCHECK(!IsFullscreenOrPending()); // SetFullscreen, not Restore, expected.
589 is_maximized_ = false; // See top of file NOTE: State Before Update.
590 [window() setFrame:restored_bounds() display:YES animate:YES];
591 } else if (IsMinimized()) {
592 is_maximized_ = false; // See top of file NOTE: State Before Update.
593 [window() deminiaturize:window_controller_];
597 void NativeAppWindowCocoa::SetBounds(const gfx::Rect& bounds) {
598 // Enforce minimum/maximum bounds.
599 gfx::Rect checked_bounds = bounds;
601 NSSize min_size = [window() minSize];
602 if (bounds.width() < min_size.width)
603 checked_bounds.set_width(min_size.width);
604 if (bounds.height() < min_size.height)
605 checked_bounds.set_height(min_size.height);
606 NSSize max_size = [window() maxSize];
607 if (checked_bounds.width() > max_size.width)
608 checked_bounds.set_width(max_size.width);
609 if (checked_bounds.height() > max_size.height)
610 checked_bounds.set_height(max_size.height);
612 NSRect cocoa_bounds = NSMakeRect(checked_bounds.x(), 0,
613 checked_bounds.width(),
614 checked_bounds.height());
615 // Flip coordinates based on the primary screen.
616 NSScreen* screen = [[NSScreen screens] objectAtIndex:0];
617 cocoa_bounds.origin.y = NSHeight([screen frame]) - checked_bounds.bottom();
619 [window() setFrame:cocoa_bounds display:YES];
620 // setFrame: without animate: does not trigger a windowDidEndLiveResize: so
622 WindowDidFinishResize();
625 void NativeAppWindowCocoa::UpdateWindowIcon() {
626 // TODO(junmin): implement.
629 void NativeAppWindowCocoa::UpdateWindowTitle() {
630 base::string16 title = app_window_->GetTitle();
631 [window() setTitle:base::SysUTF16ToNSString(title)];
634 void NativeAppWindowCocoa::UpdateBadgeIcon() {
635 // TODO(benwells): implement.
639 void NativeAppWindowCocoa::UpdateShape(scoped_ptr<SkRegion> region) {
643 void NativeAppWindowCocoa::UpdateDraggableRegions(
644 const std::vector<extensions::DraggableRegion>& regions) {
645 // Draggable region is not supported for non-frameless window.
649 // To use system drag, the window has to be marked as draggable with
650 // non-draggable areas being excluded via overlapping views.
651 // 1) If no draggable area is provided, the window is not draggable at all.
652 // 2) If only one draggable area is given, as this is the most common
653 // case, use the system drag. The non-draggable areas that are opposite of
654 // the draggable area are computed.
655 // 3) Otherwise, use the custom drag. As such, we lose the capability to
656 // support some features like snapping into other space.
658 // Determine how to perform the drag by counting the number of draggable
660 const extensions::DraggableRegion* draggable_area = NULL;
661 use_system_drag_ = true;
662 for (std::vector<extensions::DraggableRegion>::const_iterator iter =
664 iter != regions.end();
666 if (iter->draggable) {
667 // If more than one draggable area is found, use custom drag.
668 if (draggable_area) {
669 use_system_drag_ = false;
672 draggable_area = &(*iter);
676 if (use_system_drag_)
677 UpdateDraggableRegionsForSystemDrag(regions, draggable_area);
679 UpdateDraggableRegionsForCustomDrag(regions);
681 InstallDraggableRegionViews();
684 SkRegion* NativeAppWindowCocoa::GetDraggableRegion() {
685 return draggable_region_.get();
688 void NativeAppWindowCocoa::UpdateDraggableRegionsForSystemDrag(
689 const std::vector<extensions::DraggableRegion>& regions,
690 const extensions::DraggableRegion* draggable_area) {
691 NSView* web_view = web_contents()->GetView()->GetNativeView();
692 NSInteger web_view_width = NSWidth([web_view bounds]);
693 NSInteger web_view_height = NSHeight([web_view bounds]);
695 system_drag_exclude_areas_.clear();
697 // The whole window is not draggable if no draggable area is given.
698 if (!draggable_area) {
699 gfx::Rect window_bounds(0, 0, web_view_width, web_view_height);
700 system_drag_exclude_areas_.push_back(window_bounds);
704 // Otherwise, there is only one draggable area. Compute non-draggable areas
705 // that are the opposite of the given draggable area, combined with the
706 // remaining provided non-draggable areas.
708 // Copy all given non-draggable areas.
709 for (std::vector<extensions::DraggableRegion>::const_iterator iter =
711 iter != regions.end();
713 if (!iter->draggable)
714 system_drag_exclude_areas_.push_back(iter->bounds);
717 gfx::Rect draggable_bounds = draggable_area->bounds;
718 gfx::Rect non_draggable_bounds;
720 // Add the non-draggable area above the given draggable area.
721 if (draggable_bounds.y() > 0) {
722 non_draggable_bounds.SetRect(0,
725 draggable_bounds.y() - 1);
726 system_drag_exclude_areas_.push_back(non_draggable_bounds);
729 // Add the non-draggable area below the given draggable area.
730 if (draggable_bounds.bottom() < web_view_height) {
731 non_draggable_bounds.SetRect(0,
732 draggable_bounds.bottom() + 1,
734 web_view_height - draggable_bounds.bottom());
735 system_drag_exclude_areas_.push_back(non_draggable_bounds);
738 // Add the non-draggable area to the left of the given draggable area.
739 if (draggable_bounds.x() > 0) {
740 non_draggable_bounds.SetRect(0,
741 draggable_bounds.y(),
742 draggable_bounds.x() - 1,
743 draggable_bounds.height());
744 system_drag_exclude_areas_.push_back(non_draggable_bounds);
747 // Add the non-draggable area to the right of the given draggable area.
748 if (draggable_bounds.right() < web_view_width) {
749 non_draggable_bounds.SetRect(draggable_bounds.right() + 1,
750 draggable_bounds.y(),
751 web_view_width - draggable_bounds.right(),
752 draggable_bounds.height());
753 system_drag_exclude_areas_.push_back(non_draggable_bounds);
757 void NativeAppWindowCocoa::UpdateDraggableRegionsForCustomDrag(
758 const std::vector<extensions::DraggableRegion>& regions) {
759 // We still need one ControlRegionView to cover the whole window such that
760 // mouse events could be captured.
761 NSView* web_view = web_contents()->GetView()->GetNativeView();
762 gfx::Rect window_bounds(
763 0, 0, NSWidth([web_view bounds]), NSHeight([web_view bounds]));
764 system_drag_exclude_areas_.clear();
765 system_drag_exclude_areas_.push_back(window_bounds);
767 // Aggregate the draggable areas and non-draggable areas such that hit test
768 // could be performed easily.
769 draggable_region_.reset(AppWindow::RawDraggableRegionsToSkRegion(regions));
772 void NativeAppWindowCocoa::HandleKeyboardEvent(
773 const content::NativeWebKeyboardEvent& event) {
774 if (event.skip_in_browser ||
775 event.type == content::NativeWebKeyboardEvent::Char) {
778 [window() redispatchKeyEvent:event.os_event];
781 void NativeAppWindowCocoa::InstallDraggableRegionViews() {
784 // All ControlRegionViews should be added as children of the WebContentsView,
785 // because WebContentsView will be removed and re-added when entering and
786 // leaving fullscreen mode.
787 NSView* webView = web_contents()->GetView()->GetNativeView();
788 NSInteger webViewHeight = NSHeight([webView bounds]);
790 // Remove all ControlRegionViews that are added last time.
791 // Note that [webView subviews] returns the view's mutable internal array and
792 // it should be copied to avoid mutating the original array while enumerating
794 base::scoped_nsobject<NSArray> subviews([[webView subviews] copy]);
795 for (NSView* subview in subviews.get())
796 if ([subview isKindOfClass:[ControlRegionView class]])
797 [subview removeFromSuperview];
799 // Create and add ControlRegionView for each region that needs to be excluded
800 // from the dragging.
801 for (std::vector<gfx::Rect>::const_iterator iter =
802 system_drag_exclude_areas_.begin();
803 iter != system_drag_exclude_areas_.end();
805 base::scoped_nsobject<NSView> controlRegion(
806 [[ControlRegionView alloc] initWithAppWindow:this]);
807 [controlRegion setFrame:NSMakeRect(iter->x(),
808 webViewHeight - iter->bottom(),
811 [controlRegion setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
812 [webView addSubview:controlRegion];
816 void NativeAppWindowCocoa::FlashFrame(bool flash) {
818 attention_request_id_ = [NSApp requestUserAttention:NSInformationalRequest];
820 [NSApp cancelUserAttentionRequest:attention_request_id_];
821 attention_request_id_ = 0;
825 bool NativeAppWindowCocoa::IsAlwaysOnTop() const {
826 return [window() level] == AlwaysOnTopWindowLevel();
829 void NativeAppWindowCocoa::RenderViewCreated(content::RenderViewHost* rvh) {
831 web_contents()->GetView()->RestoreFocus();
834 bool NativeAppWindowCocoa::IsFrameless() const {
838 gfx::Insets NativeAppWindowCocoa::GetFrameInsets() const {
840 return gfx::Insets();
842 // Flip the coordinates based on the main screen.
843 NSInteger screen_height =
844 NSHeight([[[NSScreen screens] objectAtIndex:0] frame]);
846 NSRect frame_nsrect = [window() frame];
847 gfx::Rect frame_rect(NSRectToCGRect(frame_nsrect));
848 frame_rect.set_y(screen_height - NSMaxY(frame_nsrect));
850 NSRect content_nsrect = [window() contentRectForFrameRect:frame_nsrect];
851 gfx::Rect content_rect(NSRectToCGRect(content_nsrect));
852 content_rect.set_y(screen_height - NSMaxY(content_nsrect));
854 return frame_rect.InsetsFrom(content_rect);
857 gfx::NativeView NativeAppWindowCocoa::GetHostView() const {
862 gfx::Point NativeAppWindowCocoa::GetDialogPosition(const gfx::Size& size) {
867 gfx::Size NativeAppWindowCocoa::GetMaximumDialogSize() {
872 void NativeAppWindowCocoa::AddObserver(
873 web_modal::ModalDialogHostObserver* observer) {
877 void NativeAppWindowCocoa::RemoveObserver(
878 web_modal::ModalDialogHostObserver* observer) {
882 void NativeAppWindowCocoa::WindowWillClose() {
883 [window_controller_ setAppWindow:NULL];
884 app_window_->OnNativeWindowChanged();
885 app_window_->OnNativeClose();
888 void NativeAppWindowCocoa::WindowDidBecomeKey() {
889 content::RenderWidgetHostView* rwhv =
890 web_contents()->GetRenderWidgetHostView();
892 rwhv->SetActive(true);
893 app_window_->OnNativeWindowActivated();
895 web_contents()->GetView()->RestoreFocus();
898 void NativeAppWindowCocoa::WindowDidResignKey() {
899 // If our app is still active and we're still the key window, ignore this
900 // message, since it just means that a menu extra (on the "system status bar")
901 // was activated; we'll get another |-windowDidResignKey| if we ever really
902 // lose key window status.
903 if ([NSApp isActive] && ([NSApp keyWindow] == window()))
906 web_contents()->GetView()->StoreFocus();
908 content::RenderWidgetHostView* rwhv =
909 web_contents()->GetRenderWidgetHostView();
911 rwhv->SetActive(false);
914 void NativeAppWindowCocoa::WindowDidFinishResize() {
915 // Update |is_maximized_| if needed:
916 // - Exit maximized state if resized.
917 // - Consider us maximized if resize places us back to maximized location.
918 // This happens when returning from fullscreen.
919 NSRect frame = [window() frame];
920 NSRect screen = [[window() screen] visibleFrame];
921 if (!NSEqualSizes(frame.size, screen.size))
922 is_maximized_ = false;
923 else if (NSEqualPoints(frame.origin, screen.origin))
924 is_maximized_ = true;
926 // Update |is_fullscreen_| if needed.
927 is_fullscreen_ = ([window() styleMask] & NSFullScreenWindowMask) != 0;
928 // If not fullscreen but the window is constrained, disable the fullscreen UI
930 if (!is_fullscreen_ && !shows_fullscreen_controls_)
931 SetFullScreenCollectionBehavior(window(), false);
933 UpdateRestoredBounds();
936 void NativeAppWindowCocoa::WindowDidResize() {
937 app_window_->OnNativeWindowChanged();
940 void NativeAppWindowCocoa::WindowDidMove() {
941 UpdateRestoredBounds();
942 app_window_->OnNativeWindowChanged();
945 void NativeAppWindowCocoa::WindowDidMiniaturize() {
946 app_window_->OnNativeWindowChanged();
949 void NativeAppWindowCocoa::WindowDidDeminiaturize() {
950 app_window_->OnNativeWindowChanged();
953 void NativeAppWindowCocoa::WindowWillZoom() {
954 // See top of file NOTE: Maximize and Zoom.
961 bool NativeAppWindowCocoa::HandledByExtensionCommand(NSEvent* event) {
962 return extension_keybinding_registry_->ProcessKeyEvent(
963 content::NativeWebKeyboardEvent(event));
966 void NativeAppWindowCocoa::HandleMouseEvent(NSEvent* event) {
967 if ([event type] == NSLeftMouseDown) {
968 last_mouse_location_ =
969 [window() convertBaseToScreen:[event locationInWindow]];
970 } else if ([event type] == NSLeftMouseDragged) {
971 NSPoint current_mouse_location =
972 [window() convertBaseToScreen:[event locationInWindow]];
973 NSPoint frame_origin = [window() frame].origin;
974 frame_origin.x += current_mouse_location.x - last_mouse_location_.x;
975 frame_origin.y += current_mouse_location.y - last_mouse_location_.y;
976 [window() setFrameOrigin:frame_origin];
977 last_mouse_location_ = current_mouse_location;
981 bool NativeAppWindowCocoa::IsWithinDraggableRegion(NSPoint point) const {
982 if (!draggable_region_)
984 NSView* webView = web_contents()->GetView()->GetNativeView();
985 NSInteger webViewHeight = NSHeight([webView bounds]);
986 // |draggable_region_| is stored in local platform-indepdent coordiate system
987 // while |point| is in local Cocoa coordinate system. Do the conversion
988 // to match these two.
989 return draggable_region_->contains(point.x, webViewHeight - point.y);
992 void NativeAppWindowCocoa::HideWithApp() {
993 is_hidden_with_app_ = true;
994 HideWithoutMarkingHidden();
997 void NativeAppWindowCocoa::ShowWithApp() {
998 is_hidden_with_app_ = false;
1003 void NativeAppWindowCocoa::SetAlwaysOnTop(bool always_on_top) {
1004 [window() setLevel:(always_on_top ? AlwaysOnTopWindowLevel() :
1005 NSNormalWindowLevel)];
1008 void NativeAppWindowCocoa::HideWithoutMarkingHidden() {
1009 [window() orderOut:window_controller_];
1012 NativeAppWindowCocoa::~NativeAppWindowCocoa() {
1015 ShellNSWindow* NativeAppWindowCocoa::window() const {
1016 NSWindow* window = [window_controller_ window];
1017 CHECK(!window || [window isKindOfClass:[ShellNSWindow class]]);
1018 return static_cast<ShellNSWindow*>(window);
1021 void NativeAppWindowCocoa::UpdateRestoredBounds() {
1022 if (IsRestored(*this))
1023 restored_bounds_ = [window() frame];
1026 void NativeAppWindowCocoa::UpdateWindowMinMaxSize() {
1027 gfx::Size min_size = app_window_->size_constraints().GetMinimumSize();
1028 [window() setContentMinSize:NSMakeSize(min_size.width(), min_size.height())];
1030 gfx::Size max_size = app_window_->size_constraints().GetMaximumSize();
1031 const int kUnboundedSize = AppWindow::SizeConstraints::kUnboundedSize;
1032 CGFloat max_width = max_size.width() == kUnboundedSize ?
1033 CGFLOAT_MAX : max_size.width();
1034 CGFloat max_height = max_size.height() == kUnboundedSize ?
1035 CGFLOAT_MAX : max_size.height();
1036 [window() setContentMaxSize:NSMakeSize(max_width, max_height)];