Add a minor text member to ui::MenuModel.
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / apps / native_app_window_cocoa.mm
bloba3281d8d947aa609a88ca05c4b4d0e6c057b725c
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/notification_source.h"
20 #include "content/public/browser/notification_types.h"
21 #include "content/public/browser/render_widget_host_view.h"
22 #include "content/public/browser/web_contents.h"
23 #include "content/public/browser/web_contents_view.h"
24 #include "third_party/skia/include/core/SkRegion.h"
26 // NOTE: State Before Update.
28 // Internal state, such as |is_maximized_|, must be set before the window
29 // state is changed so that it is accurate when e.g. a resize results in a call
30 // to |OnNativeWindowChanged|.
32 // NOTE: Maximize and Zoom.
34 // Zooming is implemented manually in order to implement maximize functionality
35 // and to support non resizable windows. The window will be resized explicitly
36 // in the |WindowWillZoom| call.
38 // Attempting maximize and restore functionality with non resizable windows
39 // using the native zoom method did not work, even with
40 // windowWillUseStandardFrame, as the window would not restore back to the
41 // desired size.
44 using apps::ShellWindow;
46 @interface NSWindow (NSPrivateApis)
47 - (void)setBottomCornerRounded:(BOOL)rounded;
48 @end
50 // Replicate specific 10.7 SDK declarations for building with prior SDKs.
51 #if !defined(MAC_OS_X_VERSION_10_7) || \
52     MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
54 @interface NSWindow (LionSDKDeclarations)
55 - (void)toggleFullScreen:(id)sender;
56 @end
58 enum {
59   NSWindowCollectionBehaviorFullScreenPrimary = 1 << 7
62 #endif  // MAC_OS_X_VERSION_10_7
64 @implementation NativeAppWindowController
66 @synthesize appWindow = appWindow_;
68 - (void)windowWillClose:(NSNotification*)notification {
69   if (appWindow_)
70     appWindow_->WindowWillClose();
73 - (void)windowDidBecomeKey:(NSNotification*)notification {
74   if (appWindow_)
75     appWindow_->WindowDidBecomeKey();
78 - (void)windowDidResignKey:(NSNotification*)notification {
79   if (appWindow_)
80     appWindow_->WindowDidResignKey();
83 - (void)windowDidResize:(NSNotification*)notification {
84   if (appWindow_)
85     appWindow_->WindowDidResize();
88 - (void)windowDidMove:(NSNotification*)notification {
89   if (appWindow_)
90     appWindow_->WindowDidMove();
93 - (void)windowDidMiniaturize:(NSNotification*)notification {
94   if (appWindow_)
95     appWindow_->WindowDidMiniaturize();
98 - (void)windowDidDeminiaturize:(NSNotification*)notification {
99   if (appWindow_)
100     appWindow_->WindowDidDeminiaturize();
103 - (BOOL)windowShouldZoom:(NSWindow*)window
104                  toFrame:(NSRect)newFrame {
105   if (appWindow_)
106     appWindow_->WindowWillZoom();
107   return NO;  // See top of file NOTE: Maximize and Zoom.
110 // Allow non resizable windows (without NSResizableWindowMask) to enter
111 // fullscreen by passing through the full size in willUseFullScreenContentSize.
112 - (NSSize)window:(NSWindow *)window
113     willUseFullScreenContentSize:(NSSize)proposedSize {
114   return proposedSize;
117 - (void)executeCommand:(int)command {
118   // No-op, swallow the event.
121 - (BOOL)handledByExtensionCommand:(NSEvent*)event {
122   if (appWindow_)
123     return appWindow_->HandledByExtensionCommand(event);
124   return NO;
127 @end
129 // This is really a method on NSGrayFrame, so it should only be called on the
130 // view passed into -[NSWindow drawCustomFrameRect:forView:].
131 @interface NSView (PrivateMethods)
132 - (CGFloat)roundedCornerRadius;
133 @end
135 @interface ShellNSWindow : ChromeEventProcessingWindow
136 @end
137 @implementation ShellNSWindow
138 @end
140 @interface ShellCustomFrameNSWindow : ShellNSWindow
142 - (void)drawCustomFrameRect:(NSRect)rect forView:(NSView*)view;
144 @end
146 @implementation ShellCustomFrameNSWindow
148 - (void)drawCustomFrameRect:(NSRect)rect forView:(NSView*)view {
149   [[NSBezierPath bezierPathWithRect:rect] addClip];
150   [[NSColor clearColor] set];
151   NSRectFill(rect);
153   // Set up our clip.
154   CGFloat cornerRadius = 4.0;
155   if ([view respondsToSelector:@selector(roundedCornerRadius)])
156     cornerRadius = [view roundedCornerRadius];
157   [[NSBezierPath bezierPathWithRoundedRect:[view bounds]
158                                    xRadius:cornerRadius
159                                    yRadius:cornerRadius] addClip];
160   [[NSColor whiteColor] set];
161   NSRectFill(rect);
164 @end
166 @interface ShellFramelessNSWindow : ShellCustomFrameNSWindow
168 @end
170 @implementation ShellFramelessNSWindow
172 + (NSRect)frameRectForContentRect:(NSRect)contentRect
173                         styleMask:(NSUInteger)mask {
174   return contentRect;
177 + (NSRect)contentRectForFrameRect:(NSRect)frameRect
178                         styleMask:(NSUInteger)mask {
179   return frameRect;
182 - (NSRect)frameRectForContentRect:(NSRect)contentRect {
183   return contentRect;
186 - (NSRect)contentRectForFrameRect:(NSRect)frameRect {
187   return frameRect;
190 @end
192 @interface ControlRegionView : NSView {
193  @private
194   NativeAppWindowCocoa* appWindow_;  // Weak; owns self.
197 @end
199 @implementation ControlRegionView
201 - (id)initWithAppWindow:(NativeAppWindowCocoa*)appWindow {
202   if ((self = [super init]))
203     appWindow_ = appWindow;
204   return self;
207 - (BOOL)mouseDownCanMoveWindow {
208   return NO;
211 - (NSView*)hitTest:(NSPoint)aPoint {
212   if (appWindow_->use_system_drag() ||
213       !appWindow_->IsWithinDraggableRegion(aPoint)) {
214     return nil;
215   }
216   return self;
219 - (void)mouseDown:(NSEvent*)event {
220   appWindow_->HandleMouseEvent(event);
223 - (void)mouseDragged:(NSEvent*)event {
224   appWindow_->HandleMouseEvent(event);
227 @end
229 @interface NSView (WebContentsView)
230 - (void)setMouseDownCanMoveWindow:(BOOL)can_move;
231 @end
233 NativeAppWindowCocoa::NativeAppWindowCocoa(
234     ShellWindow* shell_window,
235     const ShellWindow::CreateParams& params)
236     : shell_window_(shell_window),
237       has_frame_(params.frame == ShellWindow::FRAME_CHROME),
238       is_hidden_(false),
239       is_hidden_with_app_(false),
240       is_maximized_(false),
241       is_fullscreen_(false),
242       attention_request_id_(0),
243       use_system_drag_(true) {
244   // Flip coordinates based on the primary screen.
245   NSRect main_screen_rect = [[[NSScreen screens] objectAtIndex:0] frame];
246   NSRect cocoa_bounds = NSMakeRect(params.bounds.x(),
247       NSHeight(main_screen_rect) - params.bounds.y() - params.bounds.height(),
248       params.bounds.width(), params.bounds.height());
250   // If coordinates are < 0, center window on primary screen
251   if (params.bounds.x() == INT_MIN) {
252     cocoa_bounds.origin.x =
253         floor((NSWidth(main_screen_rect) - NSWidth(cocoa_bounds)) / 2);
254   }
255   if (params.bounds.y() == INT_MIN) {
256     cocoa_bounds.origin.y =
257         floor((NSHeight(main_screen_rect) - NSHeight(cocoa_bounds)) / 2);
258   }
260   // Initialize |restored_bounds_| after |cocoa_bounds| have been sanitized.
261   restored_bounds_ = cocoa_bounds;
263   resizable_ = params.resizable;
264   base::scoped_nsobject<NSWindow> window;
265   Class window_class;
266   if (has_frame_) {
267     bool should_use_native_frame =
268         CommandLine::ForCurrentProcess()->HasSwitch(
269             switches::kAppsUseNativeFrame);
270     window_class = should_use_native_frame ?
271         [ShellNSWindow class] : [ShellCustomFrameNSWindow class];
272   } else {
273     window_class = [ShellFramelessNSWindow class];
274   }
275   window.reset([[window_class alloc]
276       initWithContentRect:cocoa_bounds
277                 styleMask:GetWindowStyleMask()
278                   backing:NSBackingStoreBuffered
279                     defer:NO]);
280   [window setTitle:base::SysUTF8ToNSString(extension()->name())];
281   min_size_ = params.minimum_size;
282   if (min_size_.width() || min_size_.height()) {
283     [window setContentMinSize:
284         NSMakeSize(min_size_.width(), min_size_.height())];
285   }
286   max_size_ = params.maximum_size;
287   if (max_size_.width() || max_size_.height()) {
288     CGFloat max_width = max_size_.width() ? max_size_.width() : CGFLOAT_MAX;
289     CGFloat max_height = max_size_.height() ? max_size_.height() : CGFLOAT_MAX;
290     [window setContentMaxSize:NSMakeSize(max_width, max_height)];
291   }
293   if (base::mac::IsOSSnowLeopard() &&
294       [window respondsToSelector:@selector(setBottomCornerRounded:)])
295     [window setBottomCornerRounded:NO];
297   // Set the window to participate in Lion Fullscreen mode. Setting this flag
298   // has no effect on Snow Leopard or earlier. Packaged apps don't show the
299   // fullscreen button on their window decorations.
300   NSWindowCollectionBehavior behavior = [window collectionBehavior];
301   behavior |= NSWindowCollectionBehaviorFullScreenPrimary;
302   [window setCollectionBehavior:behavior];
304   window_controller_.reset(
305       [[NativeAppWindowController alloc] initWithWindow:window.release()]);
307   NSView* view = web_contents()->GetView()->GetNativeView();
308   [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
310   // By default, the whole frameless window is not draggable.
311   if (!has_frame_) {
312     gfx::Rect window_bounds(
313         0, 0, NSWidth(cocoa_bounds), NSHeight(cocoa_bounds));
314     system_drag_exclude_areas_.push_back(window_bounds);
315   }
317   InstallView();
319   [[window_controller_ window] setDelegate:window_controller_];
320   [window_controller_ setAppWindow:this];
322   extension_keybinding_registry_.reset(new ExtensionKeybindingRegistryCocoa(
323       shell_window_->profile(),
324       window,
325       extensions::ExtensionKeybindingRegistry::PLATFORM_APPS_ONLY,
326       shell_window));
329 NSUInteger NativeAppWindowCocoa::GetWindowStyleMask() const {
330   NSUInteger style_mask = NSTitledWindowMask | NSClosableWindowMask |
331                           NSMiniaturizableWindowMask;
332   if (resizable_)
333     style_mask |= NSResizableWindowMask;
334   if (!has_frame_ ||
335       !CommandLine::ForCurrentProcess()->HasSwitch(
336           switches::kAppsUseNativeFrame)) {
337     style_mask |= NSTexturedBackgroundWindowMask;
338   }
339   return style_mask;
342 void NativeAppWindowCocoa::InstallView() {
343   NSView* view = web_contents()->GetView()->GetNativeView();
344   if (has_frame_) {
345     [view setFrame:[[window() contentView] bounds]];
346     [[window() contentView] addSubview:view];
347     if (!max_size_.IsEmpty() && min_size_ == max_size_) {
348       [[window() standardWindowButton:NSWindowZoomButton] setEnabled:NO];
349       [window() setShowsResizeIndicator:NO];
350     }
351   } else {
352     // TODO(jeremya): find a cleaner way to send this information to the
353     // WebContentsViewCocoa view.
354     DCHECK([view
355         respondsToSelector:@selector(setMouseDownCanMoveWindow:)]);
356     [view setMouseDownCanMoveWindow:YES];
358     NSView* frameView = [[window() contentView] superview];
359     [view setFrame:[frameView bounds]];
360     [frameView addSubview:view];
362     [[window() standardWindowButton:NSWindowZoomButton] setHidden:YES];
363     [[window() standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES];
364     [[window() standardWindowButton:NSWindowCloseButton] setHidden:YES];
366     // Some third-party OS X utilities check the zoom button's enabled state to
367     // determine whether to show custom UI on hover, so we disable it here to
368     // prevent them from doing so in a frameless app window.
369     [[window() standardWindowButton:NSWindowZoomButton] setEnabled:NO];
371     InstallDraggableRegionViews();
372   }
375 void NativeAppWindowCocoa::UninstallView() {
376   NSView* view = web_contents()->GetView()->GetNativeView();
377   [view removeFromSuperview];
380 bool NativeAppWindowCocoa::IsActive() const {
381   return [window() isKeyWindow];
384 bool NativeAppWindowCocoa::IsMaximized() const {
385   return is_maximized_;
388 bool NativeAppWindowCocoa::IsMinimized() const {
389   return [window() isMiniaturized];
392 bool NativeAppWindowCocoa::IsFullscreen() const {
393   return is_fullscreen_;
396 void NativeAppWindowCocoa::SetFullscreen(bool fullscreen) {
397   if (fullscreen == is_fullscreen_)
398     return;
399   is_fullscreen_ = fullscreen;
401   if (base::mac::IsOSLionOrLater()) {
402     [window() toggleFullScreen:nil];
403     return;
404   }
406   DCHECK(base::mac::IsOSSnowLeopard());
408   // Fade to black.
409   const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
410   bool did_fade_out = false;
411   CGDisplayFadeReservationToken token;
412   if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token) ==
413       kCGErrorSuccess) {
414     did_fade_out = true;
415     CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
416         kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);
417   }
419   // Since frameless windows insert the WebContentsView into the NSThemeFrame
420   // ([[window contentView] superview]), and since that NSThemeFrame is
421   // destroyed and recreated when we change the styleMask of the window, we
422   // need to remove the view from the window when we change the style, and
423   // add it back afterwards.
424   UninstallView();
425   if (fullscreen) {
426     UpdateRestoredBounds();
427     [window() setStyleMask:NSBorderlessWindowMask];
428     [window() setFrame:[window()
429         frameRectForContentRect:[[window() screen] frame]]
430                display:YES];
431     base::mac::RequestFullScreen(base::mac::kFullScreenModeAutoHideAll);
432   } else {
433     base::mac::ReleaseFullScreen(base::mac::kFullScreenModeAutoHideAll);
434     [window() setStyleMask:GetWindowStyleMask()];
435     [window() setFrame:restored_bounds_ display:YES];
436   }
437   InstallView();
439   // Fade back in.
440   if (did_fade_out) {
441     CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor,
442         kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false);
443     CGReleaseDisplayFadeReservation(token);
444   }
447 bool NativeAppWindowCocoa::IsFullscreenOrPending() const {
448   return is_fullscreen_;
451 bool NativeAppWindowCocoa::IsDetached() const {
452   return false;
455 gfx::NativeWindow NativeAppWindowCocoa::GetNativeWindow() {
456   return window();
459 gfx::Rect NativeAppWindowCocoa::GetRestoredBounds() const {
460   // Flip coordinates based on the primary screen.
461   NSScreen* screen = [[NSScreen screens] objectAtIndex:0];
462   NSRect frame = restored_bounds_;
463   gfx::Rect bounds(frame.origin.x, 0, NSWidth(frame), NSHeight(frame));
464   bounds.set_y(NSHeight([screen frame]) - NSMaxY(frame));
465   return bounds;
468 ui::WindowShowState NativeAppWindowCocoa::GetRestoredState() const {
469   if (IsMaximized())
470     return ui::SHOW_STATE_MAXIMIZED;
471   return ui::SHOW_STATE_NORMAL;
474 gfx::Rect NativeAppWindowCocoa::GetBounds() const {
475   // Flip coordinates based on the primary screen.
476   NSScreen* screen = [[NSScreen screens] objectAtIndex:0];
477   NSRect frame = [window() frame];
478   gfx::Rect bounds(frame.origin.x, 0, NSWidth(frame), NSHeight(frame));
479   bounds.set_y(NSHeight([screen frame]) - NSMaxY(frame));
480   return bounds;
483 void NativeAppWindowCocoa::Show() {
484   is_hidden_ = false;
486   if (is_hidden_with_app_) {
487     // If there is a shim to gently request attention, return here. Otherwise
488     // show the window as usual.
489     if (apps::ExtensionAppShimHandler::RequestUserAttentionForWindow(
490             shell_window_)) {
491       return;
492     }
493   }
495   [window_controller_ showWindow:nil];
496   [window() makeKeyAndOrderFront:window_controller_];
499 void NativeAppWindowCocoa::ShowInactive() {
500   is_hidden_ = false;
501   [window() orderFront:window_controller_];
504 void NativeAppWindowCocoa::Hide() {
505   is_hidden_ = true;
506   HideWithoutMarkingHidden();
509 void NativeAppWindowCocoa::Close() {
510   [window() performClose:nil];
513 void NativeAppWindowCocoa::Activate() {
514   [BrowserWindowUtils activateWindowForController:window_controller_];
517 void NativeAppWindowCocoa::Deactivate() {
518   // TODO(jcivelli): http://crbug.com/51364 Implement me.
519   NOTIMPLEMENTED();
522 void NativeAppWindowCocoa::Maximize() {
523   UpdateRestoredBounds();
524   is_maximized_ = true;  // See top of file NOTE: State Before Update.
525   [window() setFrame:[[window() screen] visibleFrame] display:YES animate:YES];
528 void NativeAppWindowCocoa::Minimize() {
529   [window() miniaturize:window_controller_];
532 void NativeAppWindowCocoa::Restore() {
533   DCHECK(!IsFullscreenOrPending());   // SetFullscreen, not Restore, expected.
535   if (IsMaximized()) {
536     is_maximized_ = false;  // See top of file NOTE: State Before Update.
537     [window() setFrame:restored_bounds() display:YES animate:YES];
538   } else if (IsMinimized()) {
539     is_maximized_ = false;  // See top of file NOTE: State Before Update.
540     [window() deminiaturize:window_controller_];
541   }
544 void NativeAppWindowCocoa::SetBounds(const gfx::Rect& bounds) {
545   // Enforce minimum/maximum bounds.
546   gfx::Rect checked_bounds = bounds;
548   NSSize min_size = [window() minSize];
549   if (bounds.width() < min_size.width)
550     checked_bounds.set_width(min_size.width);
551   if (bounds.height() < min_size.height)
552     checked_bounds.set_height(min_size.height);
553   NSSize max_size = [window() maxSize];
554   if (checked_bounds.width() > max_size.width)
555     checked_bounds.set_width(max_size.width);
556   if (checked_bounds.height() > max_size.height)
557     checked_bounds.set_height(max_size.height);
559   NSRect cocoa_bounds = NSMakeRect(checked_bounds.x(), 0,
560                                    checked_bounds.width(),
561                                    checked_bounds.height());
562   // Flip coordinates based on the primary screen.
563   NSScreen* screen = [[NSScreen screens] objectAtIndex:0];
564   cocoa_bounds.origin.y = NSHeight([screen frame]) - checked_bounds.bottom();
566   [window() setFrame:cocoa_bounds display:YES];
569 void NativeAppWindowCocoa::UpdateWindowIcon() {
570   // TODO(junmin): implement.
573 void NativeAppWindowCocoa::UpdateWindowTitle() {
574   string16 title = shell_window_->GetTitle();
575   [window() setTitle:base::SysUTF16ToNSString(title)];
578 void NativeAppWindowCocoa::UpdateInputRegion(scoped_ptr<SkRegion> region) {
579   NOTIMPLEMENTED();
582 void NativeAppWindowCocoa::UpdateDraggableRegions(
583     const std::vector<extensions::DraggableRegion>& regions) {
584   // Draggable region is not supported for non-frameless window.
585   if (has_frame_)
586     return;
588   // To use system drag, the window has to be marked as draggable with
589   // non-draggable areas being excluded via overlapping views.
590   // 1) If no draggable area is provided, the window is not draggable at all.
591   // 2) If only one draggable area is given, as this is the most common
592   //    case, use the system drag. The non-draggable areas that are opposite of
593   //    the draggable area are computed.
594   // 3) Otherwise, use the custom drag. As such, we lose the capability to
595   //    support some features like snapping into other space.
597   // Determine how to perform the drag by counting the number of draggable
598   // areas.
599   const extensions::DraggableRegion* draggable_area = NULL;
600   use_system_drag_ = true;
601   for (std::vector<extensions::DraggableRegion>::const_iterator iter =
602            regions.begin();
603        iter != regions.end();
604        ++iter) {
605     if (iter->draggable) {
606       // If more than one draggable area is found, use custom drag.
607       if (draggable_area) {
608         use_system_drag_ = false;
609         break;
610       }
611       draggable_area = &(*iter);
612     }
613   }
615   if (use_system_drag_)
616     UpdateDraggableRegionsForSystemDrag(regions, draggable_area);
617   else
618     UpdateDraggableRegionsForCustomDrag(regions);
620   InstallDraggableRegionViews();
623 void NativeAppWindowCocoa::UpdateDraggableRegionsForSystemDrag(
624     const std::vector<extensions::DraggableRegion>& regions,
625     const extensions::DraggableRegion* draggable_area) {
626   NSView* web_view = web_contents()->GetView()->GetNativeView();
627   NSInteger web_view_width = NSWidth([web_view bounds]);
628   NSInteger web_view_height = NSHeight([web_view bounds]);
630   system_drag_exclude_areas_.clear();
632   // The whole window is not draggable if no draggable area is given.
633   if (!draggable_area) {
634     gfx::Rect window_bounds(0, 0, web_view_width, web_view_height);
635     system_drag_exclude_areas_.push_back(window_bounds);
636     return;
637   }
639   // Otherwise, there is only one draggable area. Compute non-draggable areas
640   // that are the opposite of the given draggable area, combined with the
641   // remaining provided non-draggable areas.
643   // Copy all given non-draggable areas.
644   for (std::vector<extensions::DraggableRegion>::const_iterator iter =
645            regions.begin();
646        iter != regions.end();
647        ++iter) {
648     if (!iter->draggable)
649       system_drag_exclude_areas_.push_back(iter->bounds);
650   }
652   gfx::Rect draggable_bounds = draggable_area->bounds;
653   gfx::Rect non_draggable_bounds;
655   // Add the non-draggable area above the given draggable area.
656   if (draggable_bounds.y() > 0) {
657     non_draggable_bounds.SetRect(0,
658                                  0,
659                                  web_view_width,
660                                  draggable_bounds.y() - 1);
661     system_drag_exclude_areas_.push_back(non_draggable_bounds);
662   }
664   // Add the non-draggable area below the given draggable area.
665   if (draggable_bounds.bottom() < web_view_height) {
666     non_draggable_bounds.SetRect(0,
667                                  draggable_bounds.bottom() + 1,
668                                  web_view_width,
669                                  web_view_height - draggable_bounds.bottom());
670     system_drag_exclude_areas_.push_back(non_draggable_bounds);
671   }
673   // Add the non-draggable area to the left of the given draggable area.
674   if (draggable_bounds.x() > 0) {
675     non_draggable_bounds.SetRect(0,
676                                  draggable_bounds.y(),
677                                  draggable_bounds.x() - 1,
678                                  draggable_bounds.height());
679     system_drag_exclude_areas_.push_back(non_draggable_bounds);
680   }
682   // Add the non-draggable area to the right of the given draggable area.
683   if (draggable_bounds.right() < web_view_width) {
684     non_draggable_bounds.SetRect(draggable_bounds.right() + 1,
685                                  draggable_bounds.y(),
686                                  web_view_width - draggable_bounds.right(),
687                                  draggable_bounds.height());
688     system_drag_exclude_areas_.push_back(non_draggable_bounds);
689   }
692 void NativeAppWindowCocoa::UpdateDraggableRegionsForCustomDrag(
693     const std::vector<extensions::DraggableRegion>& regions) {
694   // We still need one ControlRegionView to cover the whole window such that
695   // mouse events could be captured.
696   NSView* web_view = web_contents()->GetView()->GetNativeView();
697   gfx::Rect window_bounds(
698       0, 0, NSWidth([web_view bounds]), NSHeight([web_view bounds]));
699   system_drag_exclude_areas_.clear();
700   system_drag_exclude_areas_.push_back(window_bounds);
702   // Aggregate the draggable areas and non-draggable areas such that hit test
703   // could be performed easily.
704   draggable_region_.reset(ShellWindow::RawDraggableRegionsToSkRegion(regions));
707 void NativeAppWindowCocoa::HandleKeyboardEvent(
708     const content::NativeWebKeyboardEvent& event) {
709   if (event.skip_in_browser ||
710       event.type == content::NativeWebKeyboardEvent::Char) {
711     return;
712   }
713   [window() redispatchKeyEvent:event.os_event];
716 void NativeAppWindowCocoa::InstallDraggableRegionViews() {
717   DCHECK(!has_frame_);
719   // All ControlRegionViews should be added as children of the WebContentsView,
720   // because WebContentsView will be removed and re-added when entering and
721   // leaving fullscreen mode.
722   NSView* webView = web_contents()->GetView()->GetNativeView();
723   NSInteger webViewHeight = NSHeight([webView bounds]);
725   // Remove all ControlRegionViews that are added last time.
726   // Note that [webView subviews] returns the view's mutable internal array and
727   // it should be copied to avoid mutating the original array while enumerating
728   // it.
729   base::scoped_nsobject<NSArray> subviews([[webView subviews] copy]);
730   for (NSView* subview in subviews.get())
731     if ([subview isKindOfClass:[ControlRegionView class]])
732       [subview removeFromSuperview];
734   // Create and add ControlRegionView for each region that needs to be excluded
735   // from the dragging.
736   for (std::vector<gfx::Rect>::const_iterator iter =
737            system_drag_exclude_areas_.begin();
738        iter != system_drag_exclude_areas_.end();
739        ++iter) {
740     base::scoped_nsobject<NSView> controlRegion(
741         [[ControlRegionView alloc] initWithAppWindow:this]);
742     [controlRegion setFrame:NSMakeRect(iter->x(),
743                                        webViewHeight - iter->bottom(),
744                                        iter->width(),
745                                        iter->height())];
746     [controlRegion setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
747     [webView addSubview:controlRegion];
748   }
751 void NativeAppWindowCocoa::FlashFrame(bool flash) {
752   if (flash) {
753     attention_request_id_ = [NSApp requestUserAttention:NSInformationalRequest];
754   } else {
755     [NSApp cancelUserAttentionRequest:attention_request_id_];
756     attention_request_id_ = 0;
757   }
760 bool NativeAppWindowCocoa::IsAlwaysOnTop() const {
761   return false;
764 void NativeAppWindowCocoa::RenderViewHostChanged() {
765   web_contents()->GetView()->Focus();
768 gfx::Insets NativeAppWindowCocoa::GetFrameInsets() const {
769   if (!has_frame_)
770     return gfx::Insets();
772   // Flip the coordinates based on the main screen.
773   NSInteger screen_height =
774       NSHeight([[[NSScreen screens] objectAtIndex:0] frame]);
776   NSRect frame_nsrect = [window() frame];
777   gfx::Rect frame_rect(NSRectToCGRect(frame_nsrect));
778   frame_rect.set_y(screen_height - NSMaxY(frame_nsrect));
780   NSRect content_nsrect = [window() contentRectForFrameRect:frame_nsrect];
781   gfx::Rect content_rect(NSRectToCGRect(content_nsrect));
782   content_rect.set_y(screen_height - NSMaxY(content_nsrect));
784   return frame_rect.InsetsFrom(content_rect);
787 gfx::NativeView NativeAppWindowCocoa::GetHostView() const {
788   NOTIMPLEMENTED();
789   return NULL;
792 gfx::Point NativeAppWindowCocoa::GetDialogPosition(const gfx::Size& size) {
793   NOTIMPLEMENTED();
794   return gfx::Point();
797 void NativeAppWindowCocoa::AddObserver(
798     web_modal::WebContentsModalDialogHostObserver* observer) {
799   NOTIMPLEMENTED();
802 void NativeAppWindowCocoa::RemoveObserver(
803     web_modal::WebContentsModalDialogHostObserver* observer) {
804   NOTIMPLEMENTED();
807 void NativeAppWindowCocoa::WindowWillClose() {
808   [window_controller_ setAppWindow:NULL];
809   shell_window_->OnNativeWindowChanged();
810   shell_window_->OnNativeClose();
813 void NativeAppWindowCocoa::WindowDidBecomeKey() {
814   content::RenderWidgetHostView* rwhv =
815       web_contents()->GetRenderWidgetHostView();
816   if (rwhv)
817     rwhv->SetActive(true);
818   shell_window_->OnNativeWindowActivated();
821 void NativeAppWindowCocoa::WindowDidResignKey() {
822   // If our app is still active and we're still the key window, ignore this
823   // message, since it just means that a menu extra (on the "system status bar")
824   // was activated; we'll get another |-windowDidResignKey| if we ever really
825   // lose key window status.
826   if ([NSApp isActive] && ([NSApp keyWindow] == window()))
827     return;
829   content::RenderWidgetHostView* rwhv =
830       web_contents()->GetRenderWidgetHostView();
831   if (rwhv)
832     rwhv->SetActive(false);
835 void NativeAppWindowCocoa::WindowDidResize() {
836   // Update |is_maximized_| if needed:
837   // - Exit maximized state if resized.
838   // - Consider us maximized if resize places us back to maximized location.
839   //   This happens when returning from fullscreen.
840   NSRect frame = [window() frame];
841   NSRect screen = [[window() screen] visibleFrame];
842   if (!NSEqualSizes(frame.size, screen.size))
843     is_maximized_ = false;
844   else if (NSEqualPoints(frame.origin, screen.origin))
845     is_maximized_ = true;
847   shell_window_->OnNativeWindowChanged();
850 void NativeAppWindowCocoa::WindowDidMove() {
851   shell_window_->OnNativeWindowChanged();
854 void NativeAppWindowCocoa::WindowDidMiniaturize() {
855   shell_window_->OnNativeWindowChanged();
858 void NativeAppWindowCocoa::WindowDidDeminiaturize() {
859   shell_window_->OnNativeWindowChanged();
862 void NativeAppWindowCocoa::WindowWillZoom() {
863   // See top of file NOTE: Maximize and Zoom.
864   if (IsMaximized())
865     Restore();
866   else
867     Maximize();
870 bool NativeAppWindowCocoa::HandledByExtensionCommand(NSEvent* event) {
871   return extension_keybinding_registry_->ProcessKeyEvent(
872       content::NativeWebKeyboardEvent(event));
875 void NativeAppWindowCocoa::HandleMouseEvent(NSEvent* event) {
876   if ([event type] == NSLeftMouseDown) {
877     last_mouse_location_ =
878         [window() convertBaseToScreen:[event locationInWindow]];
879   } else if ([event type] == NSLeftMouseDragged) {
880     NSPoint current_mouse_location =
881         [window() convertBaseToScreen:[event locationInWindow]];
882     NSPoint frame_origin = [window() frame].origin;
883     frame_origin.x += current_mouse_location.x - last_mouse_location_.x;
884     frame_origin.y += current_mouse_location.y - last_mouse_location_.y;
885     [window() setFrameOrigin:frame_origin];
886     last_mouse_location_ = current_mouse_location;
887   }
890 bool NativeAppWindowCocoa::IsWithinDraggableRegion(NSPoint point) const {
891   if (!draggable_region_)
892     return false;
893   NSView* webView = web_contents()->GetView()->GetNativeView();
894   NSInteger webViewHeight = NSHeight([webView bounds]);
895   // |draggable_region_| is stored in local platform-indepdent coordiate system
896   // while |point| is in local Cocoa coordinate system. Do the conversion
897   // to match these two.
898   return draggable_region_->contains(point.x, webViewHeight - point.y);
901 void NativeAppWindowCocoa::HideWithApp() {
902   is_hidden_with_app_ = true;
903   HideWithoutMarkingHidden();
906 void NativeAppWindowCocoa::ShowWithApp() {
907   is_hidden_with_app_ = false;
908   if (!is_hidden_)
909     ShowInactive();
912 void NativeAppWindowCocoa::HideWithoutMarkingHidden() {
913   [window() orderOut:window_controller_];
916 NativeAppWindowCocoa::~NativeAppWindowCocoa() {
919 ShellNSWindow* NativeAppWindowCocoa::window() const {
920   NSWindow* window = [window_controller_ window];
921   CHECK(!window || [window isKindOfClass:[ShellNSWindow class]]);
922   return static_cast<ShellNSWindow*>(window);
925 void NativeAppWindowCocoa::UpdateRestoredBounds() {
926   if (IsRestored(*this))
927     restored_bounds_ = [window() frame];