Home | History | Annotate | Download | only in tab_contents
      1 // Copyright (c) 2011 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 #include "chrome/browser/tab_contents/tab_contents_view_mac.h"
      8 
      9 #include <string>
     10 
     11 #include "chrome/browser/global_keyboard_shortcuts_mac.h"
     12 #include "chrome/browser/renderer_host/render_widget_host_view_mac.h"
     13 #include "chrome/browser/tab_contents/popup_menu_helper_mac.h"
     14 #include "chrome/browser/tab_contents/render_view_context_menu_mac.h"
     15 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
     16 #import "chrome/browser/ui/cocoa/focus_tracker.h"
     17 #import "chrome/browser/ui/cocoa/tab_contents/sad_tab_controller.h"
     18 #import "chrome/browser/ui/cocoa/tab_contents/web_drag_source.h"
     19 #import "chrome/browser/ui/cocoa/tab_contents/web_drop_target.h"
     20 #import "chrome/browser/ui/cocoa/view_id_util.h"
     21 #include "chrome/common/render_messages.h"
     22 #include "content/browser/renderer_host/render_view_host.h"
     23 #include "content/browser/renderer_host/render_view_host_factory.h"
     24 #include "content/browser/renderer_host/render_widget_host.h"
     25 #include "content/browser/tab_contents/tab_contents.h"
     26 #include "content/browser/tab_contents/tab_contents_delegate.h"
     27 #import "content/common/chrome_application_mac.h"
     28 #include "content/common/notification_details.h"
     29 #include "content/common/notification_source.h"
     30 #include "content/common/notification_type.h"
     31 #include "content/common/view_messages.h"
     32 #include "skia/ext/skia_utils_mac.h"
     33 #import "third_party/mozilla/NSPasteboard+Utils.h"
     34 
     35 using WebKit::WebDragOperation;
     36 using WebKit::WebDragOperationsMask;
     37 
     38 // Ensure that the WebKit::WebDragOperation enum values stay in sync with
     39 // NSDragOperation constants, since the code below static_casts between 'em.
     40 #define COMPILE_ASSERT_MATCHING_ENUM(name) \
     41   COMPILE_ASSERT(int(NS##name) == int(WebKit::Web##name), enum_mismatch_##name)
     42 COMPILE_ASSERT_MATCHING_ENUM(DragOperationNone);
     43 COMPILE_ASSERT_MATCHING_ENUM(DragOperationCopy);
     44 COMPILE_ASSERT_MATCHING_ENUM(DragOperationLink);
     45 COMPILE_ASSERT_MATCHING_ENUM(DragOperationGeneric);
     46 COMPILE_ASSERT_MATCHING_ENUM(DragOperationPrivate);
     47 COMPILE_ASSERT_MATCHING_ENUM(DragOperationMove);
     48 COMPILE_ASSERT_MATCHING_ENUM(DragOperationDelete);
     49 COMPILE_ASSERT_MATCHING_ENUM(DragOperationEvery);
     50 
     51 @interface TabContentsViewCocoa (Private)
     52 - (id)initWithTabContentsViewMac:(TabContentsViewMac*)w;
     53 - (void)registerDragTypes;
     54 - (void)setCurrentDragOperation:(NSDragOperation)operation;
     55 - (void)startDragWithDropData:(const WebDropData&)dropData
     56             dragOperationMask:(NSDragOperation)operationMask
     57                         image:(NSImage*)image
     58                        offset:(NSPoint)offset;
     59 - (void)cancelDeferredClose;
     60 - (void)closeTabAfterEvent;
     61 - (void)viewDidBecomeFirstResponder:(NSNotification*)notification;
     62 @end
     63 
     64 // static
     65 TabContentsView* TabContentsView::Create(TabContents* tab_contents) {
     66   return new TabContentsViewMac(tab_contents);
     67 }
     68 
     69 TabContentsViewMac::TabContentsViewMac(TabContents* tab_contents)
     70     : TabContentsView(tab_contents),
     71       preferred_width_(0) {
     72   registrar_.Add(this, NotificationType::TAB_CONTENTS_CONNECTED,
     73                  Source<TabContents>(tab_contents));
     74 }
     75 
     76 TabContentsViewMac::~TabContentsViewMac() {
     77   // This handles the case where a renderer close call was deferred
     78   // while the user was operating a UI control which resulted in a
     79   // close.  In that case, the Cocoa view outlives the
     80   // TabContentsViewMac instance due to Cocoa retain count.
     81   [cocoa_view_ cancelDeferredClose];
     82 }
     83 
     84 void TabContentsViewMac::CreateView(const gfx::Size& initial_size) {
     85   TabContentsViewCocoa* view =
     86       [[TabContentsViewCocoa alloc] initWithTabContentsViewMac:this];
     87   [view setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
     88   cocoa_view_.reset(view);
     89 }
     90 
     91 RenderWidgetHostView* TabContentsViewMac::CreateViewForWidget(
     92     RenderWidgetHost* render_widget_host) {
     93   if (render_widget_host->view()) {
     94     // During testing, the view will already be set up in most cases to the
     95     // test view, so we don't want to clobber it with a real one. To verify that
     96     // this actually is happening (and somebody isn't accidentally creating the
     97     // view twice), we check for the RVH Factory, which will be set when we're
     98     // making special ones (which go along with the special views).
     99     DCHECK(RenderViewHostFactory::has_factory());
    100     return render_widget_host->view();
    101   }
    102 
    103   RenderWidgetHostViewMac* view =
    104       new RenderWidgetHostViewMac(render_widget_host);
    105 
    106   // Fancy layout comes later; for now just make it our size and resize it
    107   // with us. In case there are other siblings of the content area, we want
    108   // to make sure the content area is on the bottom so other things draw over
    109   // it.
    110   NSView* view_view = view->native_view();
    111   [view_view setFrame:[cocoa_view_.get() bounds]];
    112   [view_view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
    113   [cocoa_view_.get() addSubview:view_view
    114                      positioned:NSWindowBelow
    115                      relativeTo:nil];
    116   // For some reason known only to Cocoa, the autorecalculation of the key view
    117   // loop set on the window doesn't set the next key view when the subview is
    118   // added. On 10.6 things magically work fine; on 10.5 they fail
    119   // <http://crbug.com/61493>. Digging into Cocoa key view loop code yielded
    120   // madness; TODO(avi,rohit): look at this again and figure out what's really
    121   // going on.
    122   [cocoa_view_.get() setNextKeyView:view_view];
    123   return view;
    124 }
    125 
    126 gfx::NativeView TabContentsViewMac::GetNativeView() const {
    127   return cocoa_view_.get();
    128 }
    129 
    130 gfx::NativeView TabContentsViewMac::GetContentNativeView() const {
    131   RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView();
    132   if (!rwhv)
    133     return NULL;
    134   return rwhv->GetNativeView();
    135 }
    136 
    137 gfx::NativeWindow TabContentsViewMac::GetTopLevelNativeWindow() const {
    138   return [cocoa_view_.get() window];
    139 }
    140 
    141 void TabContentsViewMac::GetContainerBounds(gfx::Rect* out) const {
    142   *out = [cocoa_view_.get() flipNSRectToRect:[cocoa_view_.get() bounds]];
    143 }
    144 
    145 void TabContentsViewMac::StartDragging(
    146     const WebDropData& drop_data,
    147     WebDragOperationsMask allowed_operations,
    148     const SkBitmap& image,
    149     const gfx::Point& image_offset) {
    150   // By allowing nested tasks, the code below also allows Close(),
    151   // which would deallocate |this|.  The same problem can occur while
    152   // processing -sendEvent:, so Close() is deferred in that case.
    153   // Drags from web content do not come via -sendEvent:, this sets the
    154   // same flag -sendEvent: would.
    155   chrome_application_mac::ScopedSendingEvent sendingEventScoper;
    156 
    157   // The drag invokes a nested event loop, arrange to continue
    158   // processing events.
    159   MessageLoop::ScopedNestableTaskAllower allow(MessageLoop::current());
    160   NSDragOperation mask = static_cast<NSDragOperation>(allowed_operations);
    161   NSPoint offset = NSPointFromCGPoint(image_offset.ToCGPoint());
    162   [cocoa_view_ startDragWithDropData:drop_data
    163                    dragOperationMask:mask
    164                                image:gfx::SkBitmapToNSImage(image)
    165                               offset:offset];
    166 }
    167 
    168 void TabContentsViewMac::RenderViewCreated(RenderViewHost* host) {
    169   // We want updates whenever the intrinsic width of the webpage changes.
    170   // Put the RenderView into that mode. The preferred width is used for example
    171   // when the "zoom" button in the browser window is clicked.
    172   host->EnablePreferredSizeChangedMode(kPreferredSizeWidth);
    173 }
    174 
    175 void TabContentsViewMac::SetPageTitle(const std::wstring& title) {
    176   // Meaningless on the Mac; widgets don't have a "title" attribute
    177 }
    178 
    179 void TabContentsViewMac::OnTabCrashed(base::TerminationStatus /* status */,
    180                                       int /* error_code */) {
    181   if (!sad_tab_.get()) {
    182     TabContents* contents = tab_contents();
    183     DCHECK(contents);
    184     if (contents) {
    185       SadTabController* sad_tab =
    186           [[SadTabController alloc] initWithTabContents:contents
    187                                               superview:cocoa_view_];
    188       sad_tab_.reset(sad_tab);
    189     }
    190   }
    191 }
    192 
    193 void TabContentsViewMac::SizeContents(const gfx::Size& size) {
    194   // TODO(brettw | japhet) This is a hack and should be removed.
    195   // See tab_contents_view.h.
    196   gfx::Rect rect(gfx::Point(), size);
    197   TabContentsViewCocoa* view = cocoa_view_.get();
    198   [view setFrame:[view flipRectToNSRect:rect]];
    199 }
    200 
    201 void TabContentsViewMac::Focus() {
    202   [[cocoa_view_.get() window] makeFirstResponder:GetContentNativeView()];
    203   [[cocoa_view_.get() window] makeKeyAndOrderFront:GetContentNativeView()];
    204 }
    205 
    206 void TabContentsViewMac::SetInitialFocus() {
    207   if (tab_contents()->FocusLocationBarByDefault())
    208     tab_contents()->SetFocusToLocationBar(false);
    209   else
    210     [[cocoa_view_.get() window] makeFirstResponder:GetContentNativeView()];
    211 }
    212 
    213 void TabContentsViewMac::StoreFocus() {
    214   // We're explicitly being asked to store focus, so don't worry if there's
    215   // already a view saved.
    216   focus_tracker_.reset(
    217       [[FocusTracker alloc] initWithWindow:[cocoa_view_ window]]);
    218 }
    219 
    220 void TabContentsViewMac::RestoreFocus() {
    221   // TODO(avi): Could we be restoring a view that's no longer in the key view
    222   // chain?
    223   if (!(focus_tracker_.get() &&
    224         [focus_tracker_ restoreFocusInWindow:[cocoa_view_ window]])) {
    225     // Fall back to the default focus behavior if we could not restore focus.
    226     // TODO(shess): If location-bar gets focus by default, this will
    227     // select-all in the field.  If there was a specific selection in
    228     // the field when we navigated away from it, we should restore
    229     // that selection.
    230     SetInitialFocus();
    231   }
    232 
    233   focus_tracker_.reset(nil);
    234 }
    235 
    236 void TabContentsViewMac::UpdatePreferredSize(const gfx::Size& pref_size) {
    237   preferred_width_ = pref_size.width();
    238   TabContentsView::UpdatePreferredSize(pref_size);
    239 }
    240 
    241 void TabContentsViewMac::UpdateDragCursor(WebDragOperation operation) {
    242   [cocoa_view_ setCurrentDragOperation: operation];
    243 }
    244 
    245 void TabContentsViewMac::GotFocus() {
    246   // This is only used in the views FocusManager stuff but it bleeds through
    247   // all subclasses. http://crbug.com/21875
    248 }
    249 
    250 // This is called when the renderer asks us to take focus back (i.e., it has
    251 // iterated past the last focusable element on the page).
    252 void TabContentsViewMac::TakeFocus(bool reverse) {
    253   if (reverse) {
    254     [[cocoa_view_ window] selectPreviousKeyView:cocoa_view_.get()];
    255   } else {
    256     [[cocoa_view_ window] selectNextKeyView:cocoa_view_.get()];
    257   }
    258 }
    259 
    260 void TabContentsViewMac::ShowContextMenu(const ContextMenuParams& params) {
    261   RenderViewContextMenuMac menu(tab_contents(),
    262                                 params,
    263                                 GetContentNativeView());
    264   menu.Init();
    265 }
    266 
    267 // Display a popup menu for WebKit using Cocoa widgets.
    268 void TabContentsViewMac::ShowPopupMenu(
    269     const gfx::Rect& bounds,
    270     int item_height,
    271     double item_font_size,
    272     int selected_item,
    273     const std::vector<WebMenuItem>& items,
    274     bool right_aligned) {
    275   PopupMenuHelper popup_menu_helper(tab_contents()->render_view_host());
    276   popup_menu_helper.ShowPopupMenu(bounds, item_height, item_font_size,
    277                                   selected_item, items, right_aligned);
    278 }
    279 
    280 RenderWidgetHostView* TabContentsViewMac::CreateNewWidgetInternal(
    281     int route_id,
    282     WebKit::WebPopupType popup_type) {
    283   // A RenderWidgetHostViewMac has lifetime scoped to the view. We'll retain it
    284   // to allow it to survive the trip without being hosted.
    285   RenderWidgetHostView* widget_view =
    286       TabContentsView::CreateNewWidgetInternal(route_id, popup_type);
    287   RenderWidgetHostViewMac* widget_view_mac =
    288       static_cast<RenderWidgetHostViewMac*>(widget_view);
    289   [widget_view_mac->native_view() retain];
    290 
    291   return widget_view;
    292 }
    293 
    294 void TabContentsViewMac::ShowCreatedWidgetInternal(
    295     RenderWidgetHostView* widget_host_view,
    296     const gfx::Rect& initial_pos) {
    297   TabContentsView::ShowCreatedWidgetInternal(widget_host_view, initial_pos);
    298 
    299   // A RenderWidgetHostViewMac has lifetime scoped to the view. Now that it's
    300   // properly embedded (or purposefully ignored) we can release the retain we
    301   // took in CreateNewWidgetInternal().
    302   RenderWidgetHostViewMac* widget_view_mac =
    303       static_cast<RenderWidgetHostViewMac*>(widget_host_view);
    304   [widget_view_mac->native_view() release];
    305 }
    306 
    307 bool TabContentsViewMac::IsEventTracking() const {
    308   if ([NSApp isKindOfClass:[CrApplication class]] &&
    309       [static_cast<CrApplication*>(NSApp) isHandlingSendEvent]) {
    310     return true;
    311   }
    312   return false;
    313 }
    314 
    315 // Arrange to call CloseTab() after we're back to the main event loop.
    316 // The obvious way to do this would be PostNonNestableTask(), but that
    317 // will fire when the event-tracking loop polls for events.  So we
    318 // need to bounce the message via Cocoa, instead.
    319 void TabContentsViewMac::CloseTabAfterEventTracking() {
    320   [cocoa_view_ cancelDeferredClose];
    321   [cocoa_view_ performSelector:@selector(closeTabAfterEvent)
    322                     withObject:nil
    323                     afterDelay:0.0];
    324 }
    325 
    326 void TabContentsViewMac::GetViewBounds(gfx::Rect* out) const {
    327   // This method is noth currently used on mac.
    328   NOTIMPLEMENTED();
    329 }
    330 
    331 void TabContentsViewMac::CloseTab() {
    332   tab_contents()->Close(tab_contents()->render_view_host());
    333 }
    334 
    335 void TabContentsViewMac::Observe(NotificationType type,
    336                                  const NotificationSource& source,
    337                                  const NotificationDetails& details) {
    338   switch (type.value) {
    339     case NotificationType::TAB_CONTENTS_CONNECTED: {
    340       sad_tab_.reset();
    341       break;
    342     }
    343     default:
    344       NOTREACHED() << "Got a notification we didn't register for.";
    345   }
    346 }
    347 
    348 @implementation TabContentsViewCocoa
    349 
    350 - (id)initWithTabContentsViewMac:(TabContentsViewMac*)w {
    351   self = [super initWithFrame:NSZeroRect];
    352   if (self != nil) {
    353     tabContentsView_ = w;
    354     dropTarget_.reset(
    355         [[WebDropTarget alloc] initWithTabContents:[self tabContents]]);
    356     [self registerDragTypes];
    357     // TabContentsViewCocoa's ViewID may be changed to VIEW_ID_DEV_TOOLS_DOCKED
    358     // by TabContentsController, so we can't just override -viewID method to
    359     // return it.
    360     view_id_util::SetID(self, VIEW_ID_TAB_CONTAINER);
    361 
    362     [[NSNotificationCenter defaultCenter]
    363          addObserver:self
    364             selector:@selector(viewDidBecomeFirstResponder:)
    365                 name:kViewDidBecomeFirstResponder
    366               object:nil];
    367   }
    368   return self;
    369 }
    370 
    371 - (void)dealloc {
    372   view_id_util::UnsetID(self);
    373 
    374   // Cancel any deferred tab closes, just in case.
    375   [self cancelDeferredClose];
    376 
    377   // This probably isn't strictly necessary, but can't hurt.
    378   [self unregisterDraggedTypes];
    379 
    380   [[NSNotificationCenter defaultCenter] removeObserver:self];
    381 
    382   [super dealloc];
    383 }
    384 
    385 // Registers for the view for the appropriate drag types.
    386 - (void)registerDragTypes {
    387   NSArray* types = [NSArray arrayWithObjects:NSStringPboardType,
    388       NSHTMLPboardType, NSURLPboardType, nil];
    389   [self registerForDraggedTypes:types];
    390 }
    391 
    392 - (void)setCurrentDragOperation:(NSDragOperation)operation {
    393   [dropTarget_ setCurrentOperation:operation];
    394 }
    395 
    396 - (TabContents*)tabContents {
    397   return tabContentsView_->tab_contents();
    398 }
    399 
    400 - (void)mouseEvent:(NSEvent *)theEvent {
    401   TabContents* tabContents = [self tabContents];
    402   if (tabContents->delegate()) {
    403     NSPoint location = [NSEvent mouseLocation];
    404     if ([theEvent type] == NSMouseMoved)
    405       tabContents->delegate()->ContentsMouseEvent(
    406           tabContents, gfx::Point(location.x, location.y), true);
    407     if ([theEvent type] == NSMouseExited)
    408       tabContents->delegate()->ContentsMouseEvent(
    409           tabContents, gfx::Point(location.x, location.y), false);
    410   }
    411 }
    412 
    413 - (BOOL)mouseDownCanMoveWindow {
    414   // This is needed to prevent mouseDowns from moving the window
    415   // around.  The default implementation returns YES only for opaque
    416   // views.  TabContentsViewCocoa does not draw itself in any way, but
    417   // its subviews do paint their entire frames.  Returning NO here
    418   // saves us the effort of overriding this method in every possible
    419   // subview.
    420   return NO;
    421 }
    422 
    423 - (void)pasteboard:(NSPasteboard*)sender provideDataForType:(NSString*)type {
    424   [dragSource_ lazyWriteToPasteboard:sender
    425                              forType:type];
    426 }
    427 
    428 - (void)startDragWithDropData:(const WebDropData&)dropData
    429             dragOperationMask:(NSDragOperation)operationMask
    430                         image:(NSImage*)image
    431                        offset:(NSPoint)offset {
    432   dragSource_.reset([[WebDragSource alloc]
    433           initWithContentsView:self
    434                       dropData:&dropData
    435                          image:image
    436                         offset:offset
    437                     pasteboard:[NSPasteboard pasteboardWithName:NSDragPboard]
    438              dragOperationMask:operationMask]);
    439   [dragSource_ startDrag];
    440 }
    441 
    442 // NSDraggingSource methods
    443 
    444 // Returns what kind of drag operations are available. This is a required
    445 // method for NSDraggingSource.
    446 - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal {
    447   return [dragSource_ draggingSourceOperationMaskForLocal:isLocal];
    448 }
    449 
    450 // Called when a drag initiated in our view ends.
    451 - (void)draggedImage:(NSImage*)anImage
    452              endedAt:(NSPoint)screenPoint
    453            operation:(NSDragOperation)operation {
    454   [dragSource_ endDragAt:screenPoint operation:operation];
    455 
    456   // Might as well throw out this object now.
    457   dragSource_.reset();
    458 }
    459 
    460 // Called when a drag initiated in our view moves.
    461 - (void)draggedImage:(NSImage*)draggedImage movedTo:(NSPoint)screenPoint {
    462   [dragSource_ moveDragTo:screenPoint];
    463 }
    464 
    465 // Called when we're informed where a file should be dropped.
    466 - (NSArray*)namesOfPromisedFilesDroppedAtDestination:(NSURL*)dropDest {
    467   if (![dropDest isFileURL])
    468     return nil;
    469 
    470   NSString* file_name = [dragSource_ dragPromisedFileTo:[dropDest path]];
    471   if (!file_name)
    472     return nil;
    473 
    474   return [NSArray arrayWithObject:file_name];
    475 }
    476 
    477 // NSDraggingDestination methods
    478 
    479 - (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
    480   return [dropTarget_ draggingEntered:sender view:self];
    481 }
    482 
    483 - (void)draggingExited:(id<NSDraggingInfo>)sender {
    484   [dropTarget_ draggingExited:sender];
    485 }
    486 
    487 - (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender {
    488   return [dropTarget_ draggingUpdated:sender view:self];
    489 }
    490 
    491 - (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
    492   return [dropTarget_ performDragOperation:sender view:self];
    493 }
    494 
    495 - (void)cancelDeferredClose {
    496   SEL aSel = @selector(closeTabAfterEvent);
    497   [NSObject cancelPreviousPerformRequestsWithTarget:self
    498                                            selector:aSel
    499                                              object:nil];
    500 }
    501 
    502 - (void)closeTabAfterEvent {
    503   tabContentsView_->CloseTab();
    504 }
    505 
    506 - (void)viewDidBecomeFirstResponder:(NSNotification*)notification {
    507   NSView* view = [notification object];
    508   if (![[self subviews] containsObject:view])
    509     return;
    510 
    511   NSSelectionDirection direction =
    512       [[[notification userInfo] objectForKey:kSelectionDirection]
    513         unsignedIntegerValue];
    514   if (direction == NSDirectSelection)
    515     return;
    516 
    517   [self tabContents]->
    518       FocusThroughTabTraversal(direction == NSSelectingPrevious);
    519 }
    520 
    521 @end
    522