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