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