web_contents_view_mac.mm revision 7dbb3d5cf0c15f500944d211057644d6a2f37371
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 31using WebKit::WebDragOperation; 32using WebKit::WebDragOperationsMask; 33using content::DropData; 34using content::PopupMenuHelper; 35using content::RenderViewHostFactory; 36using content::RenderWidgetHostView; 37using content::RenderWidgetHostViewMac; 38using content::WebContents; 39using content::WebContentsImpl; 40using 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) 46COMPILE_ASSERT_MATCHING_ENUM(DragOperationNone); 47COMPILE_ASSERT_MATCHING_ENUM(DragOperationCopy); 48COMPILE_ASSERT_MATCHING_ENUM(DragOperationLink); 49COMPILE_ASSERT_MATCHING_ENUM(DragOperationGeneric); 50COMPILE_ASSERT_MATCHING_ENUM(DragOperationPrivate); 51COMPILE_ASSERT_MATCHING_ENUM(DragOperationMove); 52COMPILE_ASSERT_MATCHING_ENUM(DragOperationDelete); 53COMPILE_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 70namespace content { 71WebContentsViewPort* 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 80WebContentsViewMac::WebContentsViewMac(WebContentsImpl* web_contents, 81 WebContentsViewDelegate* delegate) 82 : web_contents_(web_contents), 83 delegate_(delegate), 84 allow_overlapping_views_(false) { 85} 86 87WebContentsViewMac::~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 96gfx::NativeView WebContentsViewMac::GetNativeView() const { 97 return cocoa_view_.get(); 98} 99 100gfx::NativeView WebContentsViewMac::GetContentNativeView() const { 101 RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView(); 102 if (!rwhv) 103 return NULL; 104 return rwhv->GetNativeView(); 105} 106 107gfx::NativeWindow WebContentsViewMac::GetTopLevelNativeWindow() const { 108 return [cocoa_view_.get() window]; 109} 110 111void 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 127void 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 153void WebContentsViewMac::OnTabCrashed(base::TerminationStatus /* status */, 154 int /* error_code */) { 155} 156 157void 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 170void 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 178void WebContentsViewMac::SetInitialFocus() { 179 if (web_contents_->FocusLocationBarByDefault()) 180 web_contents_->SetFocusToLocationBar(false); 181 else 182 [[cocoa_view_.get() window] makeFirstResponder:GetContentNativeView()]; 183} 184 185void 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 192void 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 208DropData* WebContentsViewMac::GetDropData() const { 209 return [cocoa_view_ dropData]; 210} 211 212void WebContentsViewMac::UpdateDragCursor(WebDragOperation operation) { 213 [cocoa_view_ setCurrentDragOperation: operation]; 214} 215 216void 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). 223void 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 231void 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. 245void 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 259gfx::Rect WebContentsViewMac::GetViewBounds() const { 260 // This method is not currently used on mac. 261 NOTIMPLEMENTED(); 262 return gfx::Rect(); 263} 264 265void 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 276bool WebContentsViewMac::GetAllowOverlappingViews() const { 277 return allow_overlapping_views_; 278} 279 280void 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 287RenderWidgetHostView* 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 330RenderWidgetHostView* WebContentsViewMac::CreateViewForPopupWidget( 331 RenderWidgetHost* render_widget_host) { 332 return RenderWidgetHostViewPort::CreateViewForWidget(render_widget_host); 333} 334 335void WebContentsViewMac::SetPageTitle(const string16& title) { 336 // Meaningless on the Mac; widgets don't have a "title" attribute 337} 338 339 340void 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 347void WebContentsViewMac::RenderViewSwappedIn(RenderViewHost* host) { 348} 349 350void WebContentsViewMac::SetOverscrollControllerEnabled(bool enabled) { 351} 352 353bool 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. 361void WebContentsViewMac::CloseTabAfterEventTracking() { 362 [cocoa_view_ cancelDeferredClose]; 363 [cocoa_view_ performSelector:@selector(closeTabAfterEvent) 364 withObject:nil 365 afterDelay:0.0]; 366} 367 368void 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