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
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 void InitCollectionBehavior(NSWindow* window) {
75 // Since always-on-top windows have a higher window level
76 // than NSNormalWindowLevel, they will default to
77 // NSWindowCollectionBehaviorTransient. Set the value
78 // explicitly here to match normal windows.
79 NSWindowCollectionBehavior behavior = [window collectionBehavior];
80 behavior |= NSWindowCollectionBehaviorManaged;
81 [window setCollectionBehavior:behavior];
84 // Returns the level for windows that are configured to be always on top.
85 // This is not a constant because NSFloatingWindowLevel is a macro defined
86 // as a function call.
87 NSInteger AlwaysOnTopWindowLevel() {
88 return NSFloatingWindowLevel;
93 @implementation NativeAppWindowController
95 @synthesize appWindow = appWindow_;
97 - (void)windowWillClose:(NSNotification*)notification {
99 appWindow_->WindowWillClose();
102 - (void)windowDidBecomeKey:(NSNotification*)notification {
104 appWindow_->WindowDidBecomeKey();
107 - (void)windowDidResignKey:(NSNotification*)notification {
109 appWindow_->WindowDidResignKey();
112 - (void)windowDidResize:(NSNotification*)notification {
114 appWindow_->WindowDidResize();
117 - (void)windowDidEndLiveResize:(NSNotification*)notification {
119 appWindow_->WindowDidFinishResize();
122 - (void)windowDidEnterFullScreen:(NSNotification*)notification {
124 appWindow_->WindowDidFinishResize();
127 - (void)windowDidExitFullScreen:(NSNotification*)notification {
129 appWindow_->WindowDidFinishResize();
132 - (void)windowDidMove:(NSNotification*)notification {
134 appWindow_->WindowDidMove();
137 - (void)windowDidMiniaturize:(NSNotification*)notification {
139 appWindow_->WindowDidMiniaturize();
142 - (void)windowDidDeminiaturize:(NSNotification*)notification {
144 appWindow_->WindowDidDeminiaturize();
147 - (BOOL)windowShouldZoom:(NSWindow*)window
148 toFrame:(NSRect)newFrame {
150 appWindow_->WindowWillZoom();
151 return NO; // See top of file NOTE: Maximize and Zoom.
154 // Allow non resizable windows (without NSResizableWindowMask) to enter
155 // fullscreen by passing through the full size in willUseFullScreenContentSize.
156 - (NSSize)window:(NSWindow *)window
157 willUseFullScreenContentSize:(NSSize)proposedSize {
161 - (void)executeCommand:(int)command {
162 // No-op, swallow the event.
165 - (BOOL)handledByExtensionCommand:(NSEvent*)event {
167 return appWindow_->HandledByExtensionCommand(event);
173 // This is really a method on NSGrayFrame, so it should only be called on the
174 // view passed into -[NSWindow drawCustomFrameRect:forView:].
175 @interface NSView (PrivateMethods)
176 - (CGFloat)roundedCornerRadius;
179 @interface ShellNSWindow : ChromeEventProcessingWindow
181 @implementation ShellNSWindow
184 @interface ShellCustomFrameNSWindow : ShellNSWindow
186 - (void)drawCustomFrameRect:(NSRect)rect forView:(NSView*)view;
190 @implementation ShellCustomFrameNSWindow
192 - (void)drawCustomFrameRect:(NSRect)rect forView:(NSView*)view {
193 [[NSBezierPath bezierPathWithRect:rect] addClip];
194 [[NSColor clearColor] set];
198 CGFloat cornerRadius = 4.0;
199 if ([view respondsToSelector:@selector(roundedCornerRadius)])
200 cornerRadius = [view roundedCornerRadius];
201 [[NSBezierPath bezierPathWithRoundedRect:[view bounds]
203 yRadius:cornerRadius] addClip];
204 [[NSColor whiteColor] set];
210 @interface ShellFramelessNSWindow : ShellCustomFrameNSWindow
214 @implementation ShellFramelessNSWindow
216 + (NSRect)frameRectForContentRect:(NSRect)contentRect
217 styleMask:(NSUInteger)mask {
221 + (NSRect)contentRectForFrameRect:(NSRect)frameRect
222 styleMask:(NSUInteger)mask {
226 - (NSRect)frameRectForContentRect:(NSRect)contentRect {
230 - (NSRect)contentRectForFrameRect:(NSRect)frameRect {
236 @interface ControlRegionView : NSView {
238 NativeAppWindowCocoa* appWindow_; // Weak; owns self.
243 @implementation ControlRegionView
245 - (id)initWithAppWindow:(NativeAppWindowCocoa*)appWindow {
246 if ((self = [super init]))
247 appWindow_ = appWindow;
251 - (BOOL)mouseDownCanMoveWindow {
255 - (NSView*)hitTest:(NSPoint)aPoint {
256 if (appWindow_->use_system_drag() ||
257 !appWindow_->IsWithinDraggableRegion(aPoint)) {
263 - (void)mouseDown:(NSEvent*)event {
264 appWindow_->HandleMouseEvent(event);
267 - (void)mouseDragged:(NSEvent*)event {
268 appWindow_->HandleMouseEvent(event);
273 @interface NSView (WebContentsView)
274 - (void)setMouseDownCanMoveWindow:(BOOL)can_move;
277 NativeAppWindowCocoa::NativeAppWindowCocoa(
278 ShellWindow* shell_window,
279 const ShellWindow::CreateParams& params)
280 : shell_window_(shell_window),
281 has_frame_(params.frame == ShellWindow::FRAME_CHROME),
283 is_hidden_with_app_(false),
284 is_maximized_(false),
285 is_fullscreen_(false),
286 attention_request_id_(0),
287 use_system_drag_(true) {
288 Observe(web_contents());
290 // Flip coordinates based on the primary screen.
291 NSRect main_screen_rect = [[[NSScreen screens] objectAtIndex:0] frame];
292 NSRect cocoa_bounds = NSMakeRect(params.bounds.x(),
293 NSHeight(main_screen_rect) - params.bounds.y() - params.bounds.height(),
294 params.bounds.width(), params.bounds.height());
296 // If coordinates are < 0, center window on primary screen.
297 if (params.bounds.x() == INT_MIN) {
298 cocoa_bounds.origin.x =
299 floor((NSWidth(main_screen_rect) - NSWidth(cocoa_bounds)) / 2);
301 if (params.bounds.y() == INT_MIN) {
302 cocoa_bounds.origin.y =
303 floor((NSHeight(main_screen_rect) - NSHeight(cocoa_bounds)) / 2);
306 // Initialize |restored_bounds_| after |cocoa_bounds| have been sanitized.
307 restored_bounds_ = cocoa_bounds;
309 base::scoped_nsobject<NSWindow> window;
312 bool should_use_native_frame =
313 CommandLine::ForCurrentProcess()->HasSwitch(
314 switches::kAppsUseNativeFrame);
315 window_class = should_use_native_frame ?
316 [ShellNSWindow class] : [ShellCustomFrameNSWindow class];
318 window_class = [ShellFramelessNSWindow class];
321 ShellWindow::SizeConstraints size_constraints =
322 shell_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 shell_window_->profile(),
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 != ShellWindow::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 = shell_window_->GetTitle();
631 [window() setTitle:base::SysUTF16ToNSString(title)];
634 void NativeAppWindowCocoa::UpdateShape(scoped_ptr<SkRegion> region) {
638 void NativeAppWindowCocoa::UpdateDraggableRegions(
639 const std::vector<extensions::DraggableRegion>& regions) {
640 // Draggable region is not supported for non-frameless window.
644 // To use system drag, the window has to be marked as draggable with
645 // non-draggable areas being excluded via overlapping views.
646 // 1) If no draggable area is provided, the window is not draggable at all.
647 // 2) If only one draggable area is given, as this is the most common
648 // case, use the system drag. The non-draggable areas that are opposite of
649 // the draggable area are computed.
650 // 3) Otherwise, use the custom drag. As such, we lose the capability to
651 // support some features like snapping into other space.
653 // Determine how to perform the drag by counting the number of draggable
655 const extensions::DraggableRegion* draggable_area = NULL;
656 use_system_drag_ = true;
657 for (std::vector<extensions::DraggableRegion>::const_iterator iter =
659 iter != regions.end();
661 if (iter->draggable) {
662 // If more than one draggable area is found, use custom drag.
663 if (draggable_area) {
664 use_system_drag_ = false;
667 draggable_area = &(*iter);
671 if (use_system_drag_)
672 UpdateDraggableRegionsForSystemDrag(regions, draggable_area);
674 UpdateDraggableRegionsForCustomDrag(regions);
676 InstallDraggableRegionViews();
679 SkRegion* NativeAppWindowCocoa::GetDraggableRegion() {
680 return draggable_region_.get();
683 void NativeAppWindowCocoa::UpdateDraggableRegionsForSystemDrag(
684 const std::vector<extensions::DraggableRegion>& regions,
685 const extensions::DraggableRegion* draggable_area) {
686 NSView* web_view = web_contents()->GetView()->GetNativeView();
687 NSInteger web_view_width = NSWidth([web_view bounds]);
688 NSInteger web_view_height = NSHeight([web_view bounds]);
690 system_drag_exclude_areas_.clear();
692 // The whole window is not draggable if no draggable area is given.
693 if (!draggable_area) {
694 gfx::Rect window_bounds(0, 0, web_view_width, web_view_height);
695 system_drag_exclude_areas_.push_back(window_bounds);
699 // Otherwise, there is only one draggable area. Compute non-draggable areas
700 // that are the opposite of the given draggable area, combined with the
701 // remaining provided non-draggable areas.
703 // Copy all given non-draggable areas.
704 for (std::vector<extensions::DraggableRegion>::const_iterator iter =
706 iter != regions.end();
708 if (!iter->draggable)
709 system_drag_exclude_areas_.push_back(iter->bounds);
712 gfx::Rect draggable_bounds = draggable_area->bounds;
713 gfx::Rect non_draggable_bounds;
715 // Add the non-draggable area above the given draggable area.
716 if (draggable_bounds.y() > 0) {
717 non_draggable_bounds.SetRect(0,
720 draggable_bounds.y() - 1);
721 system_drag_exclude_areas_.push_back(non_draggable_bounds);
724 // Add the non-draggable area below the given draggable area.
725 if (draggable_bounds.bottom() < web_view_height) {
726 non_draggable_bounds.SetRect(0,
727 draggable_bounds.bottom() + 1,
729 web_view_height - draggable_bounds.bottom());
730 system_drag_exclude_areas_.push_back(non_draggable_bounds);
733 // Add the non-draggable area to the left of the given draggable area.
734 if (draggable_bounds.x() > 0) {
735 non_draggable_bounds.SetRect(0,
736 draggable_bounds.y(),
737 draggable_bounds.x() - 1,
738 draggable_bounds.height());
739 system_drag_exclude_areas_.push_back(non_draggable_bounds);
742 // Add the non-draggable area to the right of the given draggable area.
743 if (draggable_bounds.right() < web_view_width) {
744 non_draggable_bounds.SetRect(draggable_bounds.right() + 1,
745 draggable_bounds.y(),
746 web_view_width - draggable_bounds.right(),
747 draggable_bounds.height());
748 system_drag_exclude_areas_.push_back(non_draggable_bounds);
752 void NativeAppWindowCocoa::UpdateDraggableRegionsForCustomDrag(
753 const std::vector<extensions::DraggableRegion>& regions) {
754 // We still need one ControlRegionView to cover the whole window such that
755 // mouse events could be captured.
756 NSView* web_view = web_contents()->GetView()->GetNativeView();
757 gfx::Rect window_bounds(
758 0, 0, NSWidth([web_view bounds]), NSHeight([web_view bounds]));
759 system_drag_exclude_areas_.clear();
760 system_drag_exclude_areas_.push_back(window_bounds);
762 // Aggregate the draggable areas and non-draggable areas such that hit test
763 // could be performed easily.
764 draggable_region_.reset(ShellWindow::RawDraggableRegionsToSkRegion(regions));
767 void NativeAppWindowCocoa::HandleKeyboardEvent(
768 const content::NativeWebKeyboardEvent& event) {
769 if (event.skip_in_browser ||
770 event.type == content::NativeWebKeyboardEvent::Char) {
773 [window() redispatchKeyEvent:event.os_event];
776 void NativeAppWindowCocoa::InstallDraggableRegionViews() {
779 // All ControlRegionViews should be added as children of the WebContentsView,
780 // because WebContentsView will be removed and re-added when entering and
781 // leaving fullscreen mode.
782 NSView* webView = web_contents()->GetView()->GetNativeView();
783 NSInteger webViewHeight = NSHeight([webView bounds]);
785 // Remove all ControlRegionViews that are added last time.
786 // Note that [webView subviews] returns the view's mutable internal array and
787 // it should be copied to avoid mutating the original array while enumerating
789 base::scoped_nsobject<NSArray> subviews([[webView subviews] copy]);
790 for (NSView* subview in subviews.get())
791 if ([subview isKindOfClass:[ControlRegionView class]])
792 [subview removeFromSuperview];
794 // Create and add ControlRegionView for each region that needs to be excluded
795 // from the dragging.
796 for (std::vector<gfx::Rect>::const_iterator iter =
797 system_drag_exclude_areas_.begin();
798 iter != system_drag_exclude_areas_.end();
800 base::scoped_nsobject<NSView> controlRegion(
801 [[ControlRegionView alloc] initWithAppWindow:this]);
802 [controlRegion setFrame:NSMakeRect(iter->x(),
803 webViewHeight - iter->bottom(),
806 [controlRegion setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
807 [webView addSubview:controlRegion];
811 void NativeAppWindowCocoa::FlashFrame(bool flash) {
813 attention_request_id_ = [NSApp requestUserAttention:NSInformationalRequest];
815 [NSApp cancelUserAttentionRequest:attention_request_id_];
816 attention_request_id_ = 0;
820 bool NativeAppWindowCocoa::IsAlwaysOnTop() const {
821 return [window() level] == AlwaysOnTopWindowLevel();
824 void NativeAppWindowCocoa::RenderViewCreated(content::RenderViewHost* rvh) {
826 web_contents()->GetView()->RestoreFocus();
829 bool NativeAppWindowCocoa::IsFrameless() const {
833 gfx::Insets NativeAppWindowCocoa::GetFrameInsets() const {
835 return gfx::Insets();
837 // Flip the coordinates based on the main screen.
838 NSInteger screen_height =
839 NSHeight([[[NSScreen screens] objectAtIndex:0] frame]);
841 NSRect frame_nsrect = [window() frame];
842 gfx::Rect frame_rect(NSRectToCGRect(frame_nsrect));
843 frame_rect.set_y(screen_height - NSMaxY(frame_nsrect));
845 NSRect content_nsrect = [window() contentRectForFrameRect:frame_nsrect];
846 gfx::Rect content_rect(NSRectToCGRect(content_nsrect));
847 content_rect.set_y(screen_height - NSMaxY(content_nsrect));
849 return frame_rect.InsetsFrom(content_rect);
852 gfx::NativeView NativeAppWindowCocoa::GetHostView() const {
857 gfx::Point NativeAppWindowCocoa::GetDialogPosition(const gfx::Size& size) {
862 gfx::Size NativeAppWindowCocoa::GetMaximumDialogSize() {
867 void NativeAppWindowCocoa::AddObserver(
868 web_modal::ModalDialogHostObserver* observer) {
872 void NativeAppWindowCocoa::RemoveObserver(
873 web_modal::ModalDialogHostObserver* observer) {
877 void NativeAppWindowCocoa::WindowWillClose() {
878 [window_controller_ setAppWindow:NULL];
879 shell_window_->OnNativeWindowChanged();
880 shell_window_->OnNativeClose();
883 void NativeAppWindowCocoa::WindowDidBecomeKey() {
884 content::RenderWidgetHostView* rwhv =
885 web_contents()->GetRenderWidgetHostView();
887 rwhv->SetActive(true);
888 shell_window_->OnNativeWindowActivated();
890 web_contents()->GetView()->RestoreFocus();
893 void NativeAppWindowCocoa::WindowDidResignKey() {
894 // If our app is still active and we're still the key window, ignore this
895 // message, since it just means that a menu extra (on the "system status bar")
896 // was activated; we'll get another |-windowDidResignKey| if we ever really
897 // lose key window status.
898 if ([NSApp isActive] && ([NSApp keyWindow] == window()))
901 web_contents()->GetView()->StoreFocus();
903 content::RenderWidgetHostView* rwhv =
904 web_contents()->GetRenderWidgetHostView();
906 rwhv->SetActive(false);
909 void NativeAppWindowCocoa::WindowDidFinishResize() {
910 // Update |is_maximized_| if needed:
911 // - Exit maximized state if resized.
912 // - Consider us maximized if resize places us back to maximized location.
913 // This happens when returning from fullscreen.
914 NSRect frame = [window() frame];
915 NSRect screen = [[window() screen] visibleFrame];
916 if (!NSEqualSizes(frame.size, screen.size))
917 is_maximized_ = false;
918 else if (NSEqualPoints(frame.origin, screen.origin))
919 is_maximized_ = true;
921 // Update |is_fullscreen_| if needed.
922 is_fullscreen_ = ([window() styleMask] & NSFullScreenWindowMask) != 0;
923 // If not fullscreen but the window is constrained, disable the fullscreen UI
925 if (!is_fullscreen_ && !shows_fullscreen_controls_)
926 SetFullScreenCollectionBehavior(window(), false);
928 UpdateRestoredBounds();
931 void NativeAppWindowCocoa::WindowDidResize() {
932 shell_window_->OnNativeWindowChanged();
935 void NativeAppWindowCocoa::WindowDidMove() {
936 UpdateRestoredBounds();
937 shell_window_->OnNativeWindowChanged();
940 void NativeAppWindowCocoa::WindowDidMiniaturize() {
941 shell_window_->OnNativeWindowChanged();
944 void NativeAppWindowCocoa::WindowDidDeminiaturize() {
945 shell_window_->OnNativeWindowChanged();
948 void NativeAppWindowCocoa::WindowWillZoom() {
949 // See top of file NOTE: Maximize and Zoom.
956 bool NativeAppWindowCocoa::HandledByExtensionCommand(NSEvent* event) {
957 return extension_keybinding_registry_->ProcessKeyEvent(
958 content::NativeWebKeyboardEvent(event));
961 void NativeAppWindowCocoa::HandleMouseEvent(NSEvent* event) {
962 if ([event type] == NSLeftMouseDown) {
963 last_mouse_location_ =
964 [window() convertBaseToScreen:[event locationInWindow]];
965 } else if ([event type] == NSLeftMouseDragged) {
966 NSPoint current_mouse_location =
967 [window() convertBaseToScreen:[event locationInWindow]];
968 NSPoint frame_origin = [window() frame].origin;
969 frame_origin.x += current_mouse_location.x - last_mouse_location_.x;
970 frame_origin.y += current_mouse_location.y - last_mouse_location_.y;
971 [window() setFrameOrigin:frame_origin];
972 last_mouse_location_ = current_mouse_location;
976 bool NativeAppWindowCocoa::IsWithinDraggableRegion(NSPoint point) const {
977 if (!draggable_region_)
979 NSView* webView = web_contents()->GetView()->GetNativeView();
980 NSInteger webViewHeight = NSHeight([webView bounds]);
981 // |draggable_region_| is stored in local platform-indepdent coordiate system
982 // while |point| is in local Cocoa coordinate system. Do the conversion
983 // to match these two.
984 return draggable_region_->contains(point.x, webViewHeight - point.y);
987 void NativeAppWindowCocoa::HideWithApp() {
988 is_hidden_with_app_ = true;
989 HideWithoutMarkingHidden();
992 void NativeAppWindowCocoa::ShowWithApp() {
993 is_hidden_with_app_ = false;
998 void NativeAppWindowCocoa::SetAlwaysOnTop(bool always_on_top) {
999 [window() setLevel:(always_on_top ? AlwaysOnTopWindowLevel() :
1000 NSNormalWindowLevel)];
1003 void NativeAppWindowCocoa::HideWithoutMarkingHidden() {
1004 [window() orderOut:window_controller_];
1007 NativeAppWindowCocoa::~NativeAppWindowCocoa() {
1010 ShellNSWindow* NativeAppWindowCocoa::window() const {
1011 NSWindow* window = [window_controller_ window];
1012 CHECK(!window || [window isKindOfClass:[ShellNSWindow class]]);
1013 return static_cast<ShellNSWindow*>(window);
1016 void NativeAppWindowCocoa::UpdateRestoredBounds() {
1017 if (IsRestored(*this))
1018 restored_bounds_ = [window() frame];
1021 void NativeAppWindowCocoa::UpdateWindowMinMaxSize() {
1022 gfx::Size min_size = shell_window_->size_constraints().GetMinimumSize();
1023 [window() setContentMinSize:NSMakeSize(min_size.width(), min_size.height())];
1025 gfx::Size max_size = shell_window_->size_constraints().GetMaximumSize();
1026 const int kUnboundedSize = ShellWindow::SizeConstraints::kUnboundedSize;
1027 CGFloat max_width = max_size.width() == kUnboundedSize ?
1028 CGFLOAT_MAX : max_size.width();
1029 CGFloat max_height = max_size.height() == kUnboundedSize ?
1030 CGFLOAT_MAX : max_size.height();
1031 [window() setContentMaxSize:NSMakeSize(max_width, max_height)];