Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / content / browser / web_contents / web_contents_view_mac.mm
1 // Copyright (c) 2012 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 #import <Carbon/Carbon.h>
6
7 #import "content/browser/web_contents/web_contents_view_mac.h"
8
9 #include <string>
10
11 #import "base/mac/scoped_sending_event.h"
12 #include "base/message_loop/message_loop.h"
13 #import "base/message_loop/message_pump_mac.h"
14 #include "content/browser/renderer_host/popup_menu_helper_mac.h"
15 #include "content/browser/renderer_host/render_view_host_factory.h"
16 #include "content/browser/renderer_host/render_view_host_impl.h"
17 #include "content/browser/renderer_host/render_widget_host_view_mac.h"
18 #include "content/browser/web_contents/web_contents_impl.h"
19 #import "content/browser/web_contents/web_drag_dest_mac.h"
20 #import "content/browser/web_contents/web_drag_source_mac.h"
21 #include "content/common/view_messages.h"
22 #include "content/public/browser/web_contents_delegate.h"
23 #include "content/public/browser/web_contents_view_delegate.h"
24 #include "skia/ext/skia_utils_mac.h"
25 #import "third_party/mozilla/NSPasteboard+Utils.h"
26 #include "ui/base/clipboard/custom_data_helper.h"
27 #import "ui/base/cocoa/focus_tracker.h"
28 #include "ui/base/dragdrop/cocoa_dnd_util.h"
29 #include "ui/gfx/image/image_skia_util_mac.h"
30
31 using blink::WebDragOperation;
32 using blink::WebDragOperationsMask;
33 using content::DropData;
34 using content::PopupMenuHelper;
35 using content::RenderViewHostFactory;
36 using content::RenderWidgetHostView;
37 using content::RenderWidgetHostViewMac;
38 using content::WebContents;
39 using content::WebContentsImpl;
40 using content::WebContentsViewMac;
41
42 // Ensure that the blink::WebDragOperation enum values stay in sync with
43 // NSDragOperation constants, since the code below static_casts between 'em.
44 #define COMPILE_ASSERT_MATCHING_ENUM(name) \
45   COMPILE_ASSERT(int(NS##name) == int(blink::Web##name), enum_mismatch_##name)
46 COMPILE_ASSERT_MATCHING_ENUM(DragOperationNone);
47 COMPILE_ASSERT_MATCHING_ENUM(DragOperationCopy);
48 COMPILE_ASSERT_MATCHING_ENUM(DragOperationLink);
49 COMPILE_ASSERT_MATCHING_ENUM(DragOperationGeneric);
50 COMPILE_ASSERT_MATCHING_ENUM(DragOperationPrivate);
51 COMPILE_ASSERT_MATCHING_ENUM(DragOperationMove);
52 COMPILE_ASSERT_MATCHING_ENUM(DragOperationDelete);
53 COMPILE_ASSERT_MATCHING_ENUM(DragOperationEvery);
54
55 @interface WebContentsViewCocoa (Private)
56 - (id)initWithWebContentsViewMac:(WebContentsViewMac*)w;
57 - (void)registerDragTypes;
58 - (void)setCurrentDragOperation:(NSDragOperation)operation;
59 - (DropData*)dropData;
60 - (void)startDragWithDropData:(const DropData&)dropData
61             dragOperationMask:(NSDragOperation)operationMask
62                         image:(NSImage*)image
63                        offset:(NSPoint)offset;
64 - (void)cancelDeferredClose;
65 - (void)clearWebContentsView;
66 - (void)closeTabAfterEvent;
67 - (void)viewDidBecomeFirstResponder:(NSNotification*)notification;
68 @end
69
70 namespace content {
71 WebContentsView* CreateWebContentsView(
72     WebContentsImpl* web_contents,
73     WebContentsViewDelegate* delegate,
74     RenderViewHostDelegateView** render_view_host_delegate_view) {
75   WebContentsViewMac* rv = new WebContentsViewMac(web_contents, delegate);
76   *render_view_host_delegate_view = rv;
77   return rv;
78 }
79
80 WebContentsViewMac::WebContentsViewMac(WebContentsImpl* web_contents,
81                                        WebContentsViewDelegate* delegate)
82     : web_contents_(web_contents),
83       delegate_(delegate),
84       allow_overlapping_views_(false),
85       allow_other_views_(false) {
86 }
87
88 WebContentsViewMac::~WebContentsViewMac() {
89   // This handles the case where a renderer close call was deferred
90   // while the user was operating a UI control which resulted in a
91   // close.  In that case, the Cocoa view outlives the
92   // WebContentsViewMac instance due to Cocoa retain count.
93   [cocoa_view_ cancelDeferredClose];
94   [cocoa_view_ clearWebContentsView];
95 }
96
97 gfx::NativeView WebContentsViewMac::GetNativeView() const {
98   return cocoa_view_.get();
99 }
100
101 gfx::NativeView WebContentsViewMac::GetContentNativeView() const {
102   RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
103   if (!rwhv)
104     return NULL;
105   return rwhv->GetNativeView();
106 }
107
108 gfx::NativeWindow WebContentsViewMac::GetTopLevelNativeWindow() const {
109   return [cocoa_view_.get() window];
110 }
111
112 void WebContentsViewMac::GetContainerBounds(gfx::Rect* out) const {
113   // Convert bounds to window coordinate space.
114   NSRect bounds =
115       [cocoa_view_.get() convertRect:[cocoa_view_.get() bounds] toView:nil];
116
117   // Convert bounds to screen coordinate space.
118   NSWindow* window = [cocoa_view_.get() window];
119   bounds.origin = [window convertBaseToScreen:bounds.origin];
120
121   // Flip y to account for screen flip.
122   NSScreen* screen = [[NSScreen screens] objectAtIndex:0];
123   bounds.origin.y = [screen frame].size.height - bounds.origin.y
124       - bounds.size.height;
125   *out = gfx::Rect(NSRectToCGRect(bounds));
126 }
127
128 void WebContentsViewMac::StartDragging(
129     const DropData& drop_data,
130     WebDragOperationsMask allowed_operations,
131     const gfx::ImageSkia& image,
132     const gfx::Vector2d& image_offset,
133     const DragEventSourceInfo& event_info) {
134   // By allowing nested tasks, the code below also allows Close(),
135   // which would deallocate |this|.  The same problem can occur while
136   // processing -sendEvent:, so Close() is deferred in that case.
137   // Drags from web content do not come via -sendEvent:, this sets the
138   // same flag -sendEvent: would.
139   base::mac::ScopedSendingEvent sending_event_scoper;
140
141   // The drag invokes a nested event loop, arrange to continue
142   // processing events.
143   base::MessageLoop::ScopedNestableTaskAllower allow(
144       base::MessageLoop::current());
145   NSDragOperation mask = static_cast<NSDragOperation>(allowed_operations);
146   NSPoint offset = NSPointFromCGPoint(
147       gfx::PointAtOffsetFromOrigin(image_offset).ToCGPoint());
148   [cocoa_view_ startDragWithDropData:drop_data
149                    dragOperationMask:mask
150                                image:gfx::NSImageFromImageSkia(image)
151                               offset:offset];
152 }
153
154 void WebContentsViewMac::SizeContents(const gfx::Size& size) {
155   // TODO(brettw | japhet) This is a hack and should be removed.
156   // See web_contents_view.h.
157   // Note(erikchen): This method has /never/ worked correctly. I've removed the
158   // previous implementation.
159 }
160
161 void WebContentsViewMac::Focus() {
162   NSWindow* window = [cocoa_view_.get() window];
163   [window makeFirstResponder:GetContentNativeView()];
164   if (![window isVisible])
165     return;
166   [window makeKeyAndOrderFront:nil];
167 }
168
169 void WebContentsViewMac::SetInitialFocus() {
170   if (web_contents_->FocusLocationBarByDefault())
171     web_contents_->SetFocusToLocationBar(false);
172   else
173     [[cocoa_view_.get() window] makeFirstResponder:GetContentNativeView()];
174 }
175
176 void WebContentsViewMac::StoreFocus() {
177   // We're explicitly being asked to store focus, so don't worry if there's
178   // already a view saved.
179   focus_tracker_.reset(
180       [[FocusTracker alloc] initWithWindow:[cocoa_view_ window]]);
181 }
182
183 void WebContentsViewMac::RestoreFocus() {
184   // TODO(avi): Could we be restoring a view that's no longer in the key view
185   // chain?
186   if (!(focus_tracker_.get() &&
187         [focus_tracker_ restoreFocusInWindow:[cocoa_view_ window]])) {
188     // Fall back to the default focus behavior if we could not restore focus.
189     // TODO(shess): If location-bar gets focus by default, this will
190     // select-all in the field.  If there was a specific selection in
191     // the field when we navigated away from it, we should restore
192     // that selection.
193     SetInitialFocus();
194   }
195
196   focus_tracker_.reset(nil);
197 }
198
199 DropData* WebContentsViewMac::GetDropData() const {
200   return [cocoa_view_ dropData];
201 }
202
203 void WebContentsViewMac::UpdateDragCursor(WebDragOperation operation) {
204   [cocoa_view_ setCurrentDragOperation: operation];
205 }
206
207 void WebContentsViewMac::GotFocus() {
208   // This is only used in the views FocusManager stuff but it bleeds through
209   // all subclasses. http://crbug.com/21875
210 }
211
212 // This is called when the renderer asks us to take focus back (i.e., it has
213 // iterated past the last focusable element on the page).
214 void WebContentsViewMac::TakeFocus(bool reverse) {
215   if (reverse) {
216     [[cocoa_view_ window] selectPreviousKeyView:cocoa_view_.get()];
217   } else {
218     [[cocoa_view_ window] selectNextKeyView:cocoa_view_.get()];
219   }
220 }
221
222 void WebContentsViewMac::ShowContextMenu(
223     content::RenderFrameHost* render_frame_host,
224     const ContextMenuParams& params) {
225   // Allow delegates to handle the context menu operation first.
226   if (web_contents_->GetDelegate() &&
227       web_contents_->GetDelegate()->HandleContextMenu(params)) {
228     return;
229   }
230
231   if (delegate())
232     delegate()->ShowContextMenu(render_frame_host, params);
233   else
234     DLOG(ERROR) << "Cannot show context menus without a delegate.";
235 }
236
237 // Display a popup menu for WebKit using Cocoa widgets.
238 void WebContentsViewMac::ShowPopupMenu(
239     const gfx::Rect& bounds,
240     int item_height,
241     double item_font_size,
242     int selected_item,
243     const std::vector<MenuItem>& items,
244     bool right_aligned,
245     bool allow_multiple_selection) {
246   popup_menu_helper_.reset(
247       new PopupMenuHelper(web_contents_->GetRenderViewHost()));
248   popup_menu_helper_->ShowPopupMenu(bounds, item_height, item_font_size,
249                                     selected_item, items, right_aligned,
250                                     allow_multiple_selection);
251   popup_menu_helper_.reset();
252 }
253
254 void WebContentsViewMac::HidePopupMenu() {
255   if (popup_menu_helper_)
256     popup_menu_helper_->Hide();
257 }
258
259 gfx::Rect WebContentsViewMac::GetViewBounds() const {
260   // This method is not currently used on mac.
261   NOTIMPLEMENTED();
262   return gfx::Rect();
263 }
264
265 void WebContentsViewMac::SetAllowOverlappingViews(bool overlapping) {
266   if (allow_overlapping_views_ == overlapping)
267     return;
268
269   allow_overlapping_views_ = overlapping;
270   RenderWidgetHostViewMac* view = static_cast<RenderWidgetHostViewMac*>(
271       web_contents_->GetRenderWidgetHostView());
272   if (view)
273     view->SetAllowOverlappingViews(allow_overlapping_views_);
274 }
275
276 bool WebContentsViewMac::GetAllowOverlappingViews() const {
277   return allow_overlapping_views_;
278 }
279
280 void WebContentsViewMac::SetAllowOtherViews(bool allow) {
281   if (allow_other_views_ == allow)
282     return;
283
284   allow_other_views_ = allow;
285   RenderWidgetHostViewMac* view = static_cast<RenderWidgetHostViewMac*>(
286       web_contents_->GetRenderWidgetHostView());
287   if (view)
288     view->SetAllowPauseForResizeOrRepaint(!allow_other_views_);
289 }
290
291 bool WebContentsViewMac::GetAllowOtherViews() const {
292   return allow_other_views_;
293 }
294
295 void WebContentsViewMac::CreateView(
296     const gfx::Size& initial_size, gfx::NativeView context) {
297   WebContentsViewCocoa* view =
298       [[WebContentsViewCocoa alloc] initWithWebContentsViewMac:this];
299   cocoa_view_.reset(view);
300 }
301
302 RenderWidgetHostViewBase* WebContentsViewMac::CreateViewForWidget(
303     RenderWidgetHost* render_widget_host) {
304   if (render_widget_host->GetView()) {
305     // During testing, the view will already be set up in most cases to the
306     // test view, so we don't want to clobber it with a real one. To verify that
307     // this actually is happening (and somebody isn't accidentally creating the
308     // view twice), we check for the RVH Factory, which will be set when we're
309     // making special ones (which go along with the special views).
310     DCHECK(RenderViewHostFactory::has_factory());
311     return static_cast<RenderWidgetHostViewBase*>(
312         render_widget_host->GetView());
313   }
314
315   RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(
316       render_widget_host);
317   if (delegate()) {
318     base::scoped_nsobject<NSObject<RenderWidgetHostViewMacDelegate> >
319         rw_delegate(
320             delegate()->CreateRenderWidgetHostViewDelegate(render_widget_host));
321
322     view->SetDelegate(rw_delegate.get());
323   }
324   view->SetAllowOverlappingViews(allow_overlapping_views_);
325   view->SetAllowPauseForResizeOrRepaint(!allow_other_views_);
326
327   // Fancy layout comes later; for now just make it our size and resize it
328   // with us. In case there are other siblings of the content area, we want
329   // to make sure the content area is on the bottom so other things draw over
330   // it.
331   NSView* view_view = view->GetNativeView();
332   [view_view setFrame:[cocoa_view_.get() bounds]];
333   [view_view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
334   // Add the new view below all other views; this also keeps it below any
335   // overlay view installed.
336   [cocoa_view_.get() addSubview:view_view
337                      positioned:NSWindowBelow
338                      relativeTo:nil];
339   // For some reason known only to Cocoa, the autorecalculation of the key view
340   // loop set on the window doesn't set the next key view when the subview is
341   // added. On 10.6 things magically work fine; on 10.5 they fail
342   // <http://crbug.com/61493>. Digging into Cocoa key view loop code yielded
343   // madness; TODO(avi,rohit): look at this again and figure out what's really
344   // going on.
345   [cocoa_view_.get() setNextKeyView:view_view];
346   return view;
347 }
348
349 RenderWidgetHostViewBase* WebContentsViewMac::CreateViewForPopupWidget(
350     RenderWidgetHost* render_widget_host) {
351   return new RenderWidgetHostViewMac(render_widget_host);
352 }
353
354 void WebContentsViewMac::SetPageTitle(const base::string16& title) {
355   // Meaningless on the Mac; widgets don't have a "title" attribute
356 }
357
358
359 void WebContentsViewMac::RenderViewCreated(RenderViewHost* host) {
360   // We want updates whenever the intrinsic width of the webpage changes.
361   // Put the RenderView into that mode. The preferred width is used for example
362   // when the "zoom" button in the browser window is clicked.
363   host->EnablePreferredSizeMode();
364 }
365
366 void WebContentsViewMac::RenderViewSwappedIn(RenderViewHost* host) {
367 }
368
369 void WebContentsViewMac::SetOverscrollControllerEnabled(bool enabled) {
370 }
371
372 bool WebContentsViewMac::IsEventTracking() const {
373   return base::MessagePumpMac::IsHandlingSendEvent();
374 }
375
376 // Arrange to call CloseTab() after we're back to the main event loop.
377 // The obvious way to do this would be PostNonNestableTask(), but that
378 // will fire when the event-tracking loop polls for events.  So we
379 // need to bounce the message via Cocoa, instead.
380 void WebContentsViewMac::CloseTabAfterEventTracking() {
381   [cocoa_view_ cancelDeferredClose];
382   [cocoa_view_ performSelector:@selector(closeTabAfterEvent)
383                     withObject:nil
384                     afterDelay:0.0];
385 }
386
387 void WebContentsViewMac::CloseTab() {
388   web_contents_->Close(web_contents_->GetRenderViewHost());
389 }
390
391 }  // namespace content
392
393 @implementation WebContentsViewCocoa
394
395 - (id)initWithWebContentsViewMac:(WebContentsViewMac*)w {
396   self = [super initWithFrame:NSZeroRect];
397   if (self != nil) {
398     webContentsView_ = w;
399     dragDest_.reset(
400         [[WebDragDest alloc] initWithWebContentsImpl:[self webContents]]);
401     [self registerDragTypes];
402
403     [[NSNotificationCenter defaultCenter]
404          addObserver:self
405             selector:@selector(viewDidBecomeFirstResponder:)
406                 name:kViewDidBecomeFirstResponder
407               object:nil];
408
409     if (webContentsView_->delegate()) {
410       [dragDest_ setDragDelegate:webContentsView_->delegate()->
411           GetDragDestDelegate()];
412     }
413   }
414   return self;
415 }
416
417 - (void)dealloc {
418   // Cancel any deferred tab closes, just in case.
419   [self cancelDeferredClose];
420
421   // This probably isn't strictly necessary, but can't hurt.
422   [self unregisterDraggedTypes];
423
424   [[NSNotificationCenter defaultCenter] removeObserver:self];
425
426   [super dealloc];
427 }
428
429 // Registers for the view for the appropriate drag types.
430 - (void)registerDragTypes {
431   NSArray* types = [NSArray arrayWithObjects:
432       ui::kChromeDragDummyPboardType,
433       kWebURLsWithTitlesPboardType,
434       NSURLPboardType,
435       NSStringPboardType,
436       NSHTMLPboardType,
437       NSRTFPboardType,
438       NSFilenamesPboardType,
439       ui::kWebCustomDataPboardType,
440       nil];
441   [self registerForDraggedTypes:types];
442 }
443
444 - (void)setCurrentDragOperation:(NSDragOperation)operation {
445   [dragDest_ setCurrentOperation:operation];
446 }
447
448 - (DropData*)dropData {
449   return [dragDest_ currentDropData];
450 }
451
452 - (WebContentsImpl*)webContents {
453   if (webContentsView_ == NULL)
454     return NULL;
455   return webContentsView_->web_contents();
456 }
457
458 - (void)mouseEvent:(NSEvent*)theEvent {
459   WebContentsImpl* webContents = [self webContents];
460   if (webContents && webContents->GetDelegate()) {
461     NSPoint location = [NSEvent mouseLocation];
462     if ([theEvent type] == NSMouseMoved)
463       webContents->GetDelegate()->ContentsMouseEvent(
464           webContents, gfx::Point(location.x, location.y), true);
465     if ([theEvent type] == NSMouseExited)
466       webContents->GetDelegate()->ContentsMouseEvent(
467           webContents, gfx::Point(location.x, location.y), false);
468   }
469 }
470
471 - (void)setMouseDownCanMoveWindow:(BOOL)canMove {
472   mouseDownCanMoveWindow_ = canMove;
473 }
474
475 - (BOOL)mouseDownCanMoveWindow {
476   // This is needed to prevent mouseDowns from moving the window
477   // around.  The default implementation returns YES only for opaque
478   // views.  WebContentsViewCocoa does not draw itself in any way, but
479   // its subviews do paint their entire frames.  Returning NO here
480   // saves us the effort of overriding this method in every possible
481   // subview.
482   return mouseDownCanMoveWindow_;
483 }
484
485 - (void)pasteboard:(NSPasteboard*)sender provideDataForType:(NSString*)type {
486   [dragSource_ lazyWriteToPasteboard:sender
487                              forType:type];
488 }
489
490 - (void)startDragWithDropData:(const DropData&)dropData
491             dragOperationMask:(NSDragOperation)operationMask
492                         image:(NSImage*)image
493                        offset:(NSPoint)offset {
494   dragSource_.reset([[WebDragSource alloc]
495       initWithContents:[self webContents]
496                   view:self
497               dropData:&dropData
498                  image:image
499                 offset:offset
500             pasteboard:[NSPasteboard pasteboardWithName:NSDragPboard]
501      dragOperationMask:operationMask]);
502   [dragSource_ startDrag];
503 }
504
505 // NSDraggingSource methods
506
507 // Returns what kind of drag operations are available. This is a required
508 // method for NSDraggingSource.
509 - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal {
510   if (dragSource_)
511     return [dragSource_ draggingSourceOperationMaskForLocal:isLocal];
512   // No web drag source - this is the case for dragging a file from the
513   // downloads manager. Default to copy operation. Note: It is desirable to
514   // allow the user to either move or copy, but this requires additional
515   // plumbing to update the download item's path once its moved.
516   return NSDragOperationCopy;
517 }
518
519 // Called when a drag initiated in our view ends.
520 - (void)draggedImage:(NSImage*)anImage
521              endedAt:(NSPoint)screenPoint
522            operation:(NSDragOperation)operation {
523   [dragSource_ endDragAt:screenPoint operation:operation];
524
525   // Might as well throw out this object now.
526   dragSource_.reset();
527 }
528
529 // Called when a drag initiated in our view moves.
530 - (void)draggedImage:(NSImage*)draggedImage movedTo:(NSPoint)screenPoint {
531 }
532
533 // Called when a file drag is dropped and the promised files need to be written.
534 - (NSArray*)namesOfPromisedFilesDroppedAtDestination:(NSURL*)dropDest {
535   if (![dropDest isFileURL])
536     return nil;
537
538   NSString* fileName = [dragSource_ dragPromisedFileTo:[dropDest path]];
539   if (!fileName)
540     return nil;
541
542   return @[ fileName ];
543 }
544
545 // NSDraggingDestination methods
546
547 - (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
548   return [dragDest_ draggingEntered:sender view:self];
549 }
550
551 - (void)draggingExited:(id<NSDraggingInfo>)sender {
552   [dragDest_ draggingExited:sender];
553 }
554
555 - (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender {
556   return [dragDest_ draggingUpdated:sender view:self];
557 }
558
559 - (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
560   return [dragDest_ performDragOperation:sender view:self];
561 }
562
563 - (void)cancelDeferredClose {
564   SEL aSel = @selector(closeTabAfterEvent);
565   [NSObject cancelPreviousPerformRequestsWithTarget:self
566                                            selector:aSel
567                                              object:nil];
568 }
569
570 - (void)clearWebContentsView {
571   webContentsView_ = NULL;
572   [dragSource_ clearWebContentsView];
573 }
574
575 - (void)closeTabAfterEvent {
576   webContentsView_->CloseTab();
577 }
578
579 - (void)viewDidBecomeFirstResponder:(NSNotification*)notification {
580   NSView* view = [notification object];
581   if (![[self subviews] containsObject:view])
582     return;
583
584   NSSelectionDirection direction =
585       [[[notification userInfo] objectForKey:kSelectionDirection]
586         unsignedIntegerValue];
587   if (direction == NSDirectSelection)
588     return;
589
590   [self webContents]->
591       FocusThroughTabTraversal(direction == NSSelectingPrevious);
592 }
593
594 // When the subviews require a layout, their size should be reset to the size
595 // of this view. (It is possible for the size to get out of sync as an
596 // optimization in preparation for an upcoming WebContentsView resize.
597 // http://crbug.com/264207)
598 - (void)resizeSubviewsWithOldSize:(NSSize)oldBoundsSize {
599   for (NSView* subview in self.subviews)
600     [subview setFrame:self.bounds];
601 }
602
603 @end