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