Home | History | Annotate | Download | only in web_contents
      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 // NSDraggingDestination methods
    516 
    517 - (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
    518   return [dragDest_ draggingEntered:sender view:self];
    519 }
    520 
    521 - (void)draggingExited:(id<NSDraggingInfo>)sender {
    522   [dragDest_ draggingExited:sender];
    523 }
    524 
    525 - (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender {
    526   return [dragDest_ draggingUpdated:sender view:self];
    527 }
    528 
    529 - (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
    530   return [dragDest_ performDragOperation:sender view:self];
    531 }
    532 
    533 - (void)cancelDeferredClose {
    534   SEL aSel = @selector(closeTabAfterEvent);
    535   [NSObject cancelPreviousPerformRequestsWithTarget:self
    536                                            selector:aSel
    537                                              object:nil];
    538 }
    539 
    540 - (void)clearWebContentsView {
    541   webContentsView_ = NULL;
    542   [dragSource_ clearWebContentsView];
    543 }
    544 
    545 - (void)closeTabAfterEvent {
    546   webContentsView_->CloseTab();
    547 }
    548 
    549 - (void)viewDidBecomeFirstResponder:(NSNotification*)notification {
    550   NSView* view = [notification object];
    551   if (![[self subviews] containsObject:view])
    552     return;
    553 
    554   NSSelectionDirection direction =
    555       [[[notification userInfo] objectForKey:kSelectionDirection]
    556         unsignedIntegerValue];
    557   if (direction == NSDirectSelection)
    558     return;
    559 
    560   [self webContents]->
    561       FocusThroughTabTraversal(direction == NSSelectingPrevious);
    562 }
    563 
    564 @end
    565