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