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