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