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.
5 #import <Carbon/Carbon.h>
7 #import "content/browser/web_contents/web_contents_view_mac.h"
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"
31 using WebKit::WebDragOperation;
32 using WebKit::WebDragOperationsMask;
33 using content::DropData;
34 using content::PopupMenuHelper;
35 using content::RenderViewHostFactory;
36 using content::RenderWidgetHostView;
37 using content::RenderWidgetHostViewMac;
38 using content::WebContents;
39 using content::WebContentsImpl;
40 using content::WebContentsViewMac;
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)
46 COMPILE_ASSERT_MATCHING_ENUM(DragOperationNone);
47 COMPILE_ASSERT_MATCHING_ENUM(DragOperationCopy);
48 COMPILE_ASSERT_MATCHING_ENUM(DragOperationLink);
49 COMPILE_ASSERT_MATCHING_ENUM(DragOperationGeneric);
50 COMPILE_ASSERT_MATCHING_ENUM(DragOperationPrivate);
51 COMPILE_ASSERT_MATCHING_ENUM(DragOperationMove);
52 COMPILE_ASSERT_MATCHING_ENUM(DragOperationDelete);
53 COMPILE_ASSERT_MATCHING_ENUM(DragOperationEvery);
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
63 offset:(NSPoint)offset;
64 - (void)cancelDeferredClose;
65 - (void)clearWebContentsView;
66 - (void)closeTabAfterEvent;
67 - (void)viewDidBecomeFirstResponder:(NSNotification*)notification;
71 WebContentsViewPort* 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;
80 WebContentsViewMac::WebContentsViewMac(WebContentsImpl* web_contents,
81 WebContentsViewDelegate* delegate)
82 : web_contents_(web_contents),
84 allow_overlapping_views_(false) {
87 WebContentsViewMac::~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];
96 gfx::NativeView WebContentsViewMac::GetNativeView() const {
97 return cocoa_view_.get();
100 gfx::NativeView WebContentsViewMac::GetContentNativeView() const {
101 RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
104 return rwhv->GetNativeView();
107 gfx::NativeWindow WebContentsViewMac::GetTopLevelNativeWindow() const {
108 return [cocoa_view_.get() window];
111 void WebContentsViewMac::GetContainerBounds(gfx::Rect* out) const {
112 // Convert bounds to window coordinate space.
114 [cocoa_view_.get() convertRect:[cocoa_view_.get() bounds] toView:nil];
116 // Convert bounds to screen coordinate space.
117 NSWindow* window = [cocoa_view_.get() window];
118 bounds.origin = [window convertBaseToScreen:bounds.origin];
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));
127 void 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;
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)
153 void WebContentsViewMac::OnTabCrashed(base::TerminationStatus /* status */,
154 int /* error_code */) {
157 void 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();
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];
170 void WebContentsViewMac::Focus() {
171 NSWindow* window = [cocoa_view_.get() window];
172 [window makeFirstResponder:GetContentNativeView()];
173 if (![window isVisible])
175 [window makeKeyAndOrderFront:nil];
178 void WebContentsViewMac::SetInitialFocus() {
179 if (web_contents_->FocusLocationBarByDefault())
180 web_contents_->SetFocusToLocationBar(false);
182 [[cocoa_view_.get() window] makeFirstResponder:GetContentNativeView()];
185 void 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]]);
192 void WebContentsViewMac::RestoreFocus() {
193 // TODO(avi): Could we be restoring a view that's no longer in the key view
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
205 focus_tracker_.reset(nil);
208 DropData* WebContentsViewMac::GetDropData() const {
209 return [cocoa_view_ dropData];
212 void WebContentsViewMac::UpdateDragCursor(WebDragOperation operation) {
213 [cocoa_view_ setCurrentDragOperation: operation];
216 void WebContentsViewMac::GotFocus() {
217 // This is only used in the views FocusManager stuff but it bleeds through
218 // all subclasses. http://crbug.com/21875
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).
223 void WebContentsViewMac::TakeFocus(bool reverse) {
225 [[cocoa_view_ window] selectPreviousKeyView:cocoa_view_.get()];
227 [[cocoa_view_ window] selectNextKeyView:cocoa_view_.get()];
231 void 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)) {
239 delegate()->ShowContextMenu(params);
241 DLOG(ERROR) << "Cannot show context menus without a delegate.";
244 // Display a popup menu for WebKit using Cocoa widgets.
245 void WebContentsViewMac::ShowPopupMenu(
246 const gfx::Rect& bounds,
248 double item_font_size,
250 const std::vector<MenuItem>& items,
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);
259 gfx::Rect WebContentsViewMac::GetViewBounds() const {
260 // This method is not currently used on mac.
265 void WebContentsViewMac::SetAllowOverlappingViews(bool overlapping) {
266 if (allow_overlapping_views_ == overlapping)
269 allow_overlapping_views_ = overlapping;
270 RenderWidgetHostViewMac* view = static_cast<RenderWidgetHostViewMac*>(
271 web_contents_->GetRenderWidgetHostView());
273 view->SetAllowOverlappingViews(allow_overlapping_views_);
276 bool WebContentsViewMac::GetAllowOverlappingViews() const {
277 return allow_overlapping_views_;
280 void WebContentsViewMac::CreateView(
281 const gfx::Size& initial_size, gfx::NativeView context) {
282 WebContentsViewCocoa* view =
283 [[WebContentsViewCocoa alloc] initWithWebContentsViewMac:this];
284 cocoa_view_.reset(view);
287 RenderWidgetHostView* 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();
299 RenderWidgetHostViewMac* view = static_cast<RenderWidgetHostViewMac*>(
300 RenderWidgetHostView::CreateViewForWidget(render_widget_host));
302 NSObject<RenderWidgetHostViewMacDelegate>* rw_delegate =
303 delegate()->CreateRenderWidgetHostViewDelegate(render_widget_host);
304 view->SetDelegate(rw_delegate);
306 view->SetAllowOverlappingViews(allow_overlapping_views_);
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
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
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
326 [cocoa_view_.get() setNextKeyView:view_view];
330 RenderWidgetHostView* WebContentsViewMac::CreateViewForPopupWidget(
331 RenderWidgetHost* render_widget_host) {
332 return RenderWidgetHostViewPort::CreateViewForWidget(render_widget_host);
335 void WebContentsViewMac::SetPageTitle(const string16& title) {
336 // Meaningless on the Mac; widgets don't have a "title" attribute
340 void 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();
347 void WebContentsViewMac::RenderViewSwappedIn(RenderViewHost* host) {
350 void WebContentsViewMac::SetOverscrollControllerEnabled(bool enabled) {
353 bool WebContentsViewMac::IsEventTracking() const {
354 return base::MessagePumpMac::IsHandlingSendEvent();
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.
361 void WebContentsViewMac::CloseTabAfterEventTracking() {
362 [cocoa_view_ cancelDeferredClose];
363 [cocoa_view_ performSelector:@selector(closeTabAfterEvent)
368 void WebContentsViewMac::CloseTab() {
369 web_contents_->Close(web_contents_->GetRenderViewHost());
372 } // namespace content
374 @implementation WebContentsViewCocoa
376 - (id)initWithWebContentsViewMac:(WebContentsViewMac*)w {
377 self = [super initWithFrame:NSZeroRect];
379 webContentsView_ = w;
381 [[WebDragDest alloc] initWithWebContentsImpl:[self webContents]]);
382 [self registerDragTypes];
384 [[NSNotificationCenter defaultCenter]
386 selector:@selector(viewDidBecomeFirstResponder:)
387 name:kViewDidBecomeFirstResponder
390 if (webContentsView_->delegate()) {
391 [dragDest_ setDragDelegate:webContentsView_->delegate()->
392 GetDragDestDelegate()];
399 // Cancel any deferred tab closes, just in case.
400 [self cancelDeferredClose];
402 // This probably isn't strictly necessary, but can't hurt.
403 [self unregisterDraggedTypes];
405 [[NSNotificationCenter defaultCenter] removeObserver:self];
410 // Registers for the view for the appropriate drag types.
411 - (void)registerDragTypes {
412 NSArray* types = [NSArray arrayWithObjects:
413 ui::kChromeDragDummyPboardType,
414 kWebURLsWithTitlesPboardType,
419 NSFilenamesPboardType,
420 ui::kWebCustomDataPboardType,
422 [self registerForDraggedTypes:types];
425 - (void)setCurrentDragOperation:(NSDragOperation)operation {
426 [dragDest_ setCurrentOperation:operation];
429 - (DropData*)dropData {
430 return [dragDest_ currentDropData];
433 - (WebContentsImpl*)webContents {
434 if (webContentsView_ == NULL)
436 return webContentsView_->web_contents();
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);
452 - (void)setMouseDownCanMoveWindow:(BOOL)canMove {
453 mouseDownCanMoveWindow_ = canMove;
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
463 return mouseDownCanMoveWindow_;
466 - (void)pasteboard:(NSPasteboard*)sender provideDataForType:(NSString*)type {
467 [dragSource_ lazyWriteToPasteboard:sender
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]
481 pasteboard:[NSPasteboard pasteboardWithName:NSDragPboard]
482 dragOperationMask:operationMask]);
483 [dragSource_ startDrag];
486 // NSDraggingSource methods
488 // Returns what kind of drag operations are available. This is a required
489 // method for NSDraggingSource.
490 - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal {
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;
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];
506 // Might as well throw out this object now.
510 // Called when a drag initiated in our view moves.
511 - (void)draggedImage:(NSImage*)draggedImage movedTo:(NSPoint)screenPoint {
512 [dragSource_ moveDragTo:screenPoint];
515 // Called when a file drag is dropped and the promised files need to be written.
516 - (NSArray*)namesOfPromisedFilesDroppedAtDestination:(NSURL*)dropDest {
517 if (![dropDest isFileURL])
520 NSString* fileName = [dragSource_ dragPromisedFileTo:[dropDest path]];
524 return @[ fileName ];
527 // NSDraggingDestination methods
529 - (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
530 return [dragDest_ draggingEntered:sender view:self];
533 - (void)draggingExited:(id<NSDraggingInfo>)sender {
534 [dragDest_ draggingExited:sender];
537 - (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender {
538 return [dragDest_ draggingUpdated:sender view:self];
541 - (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
542 return [dragDest_ performDragOperation:sender view:self];
545 - (void)cancelDeferredClose {
546 SEL aSel = @selector(closeTabAfterEvent);
547 [NSObject cancelPreviousPerformRequestsWithTarget:self
552 - (void)clearWebContentsView {
553 webContentsView_ = NULL;
554 [dragSource_ clearWebContentsView];
557 - (void)closeTabAfterEvent {
558 webContentsView_->CloseTab();
561 - (void)viewDidBecomeFirstResponder:(NSNotification*)notification {
562 NSView* view = [notification object];
563 if (![[self subviews] containsObject:view])
566 NSSelectionDirection direction =
567 [[[notification userInfo] objectForKey:kSelectionDirection]
568 unsignedIntegerValue];
569 if (direction == NSDirectSelection)
573 FocusThroughTabTraversal(direction == NSSelectingPrevious);