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