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