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