Use lossless compression for CRD icon.
[chromium-blink-merge.git] / content / browser / web_contents / web_contents_view_mac.mm
blob91a4f5d2667450a7b4d3f10e12196999f0a69316
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"
9 #include <string>
11 #import "base/mac/mac_util.h"
12 #import "base/mac/scoped_sending_event.h"
13 #include "base/mac/sdk_forward_declarations.h"
14 #include "base/message_loop/message_loop.h"
15 #import "base/message_loop/message_pump_mac.h"
16 #include "content/browser/frame_host/popup_menu_helper_mac.h"
17 #include "content/browser/renderer_host/render_view_host_factory.h"
18 #include "content/browser/renderer_host/render_view_host_impl.h"
19 #include "content/browser/renderer_host/render_widget_host_view_mac.h"
20 #include "content/browser/web_contents/web_contents_impl.h"
21 #import "content/browser/web_contents/web_drag_dest_mac.h"
22 #import "content/browser/web_contents/web_drag_source_mac.h"
23 #include "content/common/view_messages.h"
24 #include "content/public/browser/web_contents_delegate.h"
25 #include "content/public/browser/web_contents_view_delegate.h"
26 #include "skia/ext/skia_utils_mac.h"
27 #import "third_party/mozilla/NSPasteboard+Utils.h"
28 #include "ui/base/clipboard/custom_data_helper.h"
29 #import "ui/base/cocoa/focus_tracker.h"
30 #include "ui/base/dragdrop/cocoa_dnd_util.h"
31 #include "ui/gfx/image/image_skia_util_mac.h"
33 using blink::WebDragOperation;
34 using blink::WebDragOperationsMask;
35 using content::DropData;
36 using content::PopupMenuHelper;
37 using content::RenderViewHostFactory;
38 using content::RenderWidgetHostView;
39 using content::RenderWidgetHostViewMac;
40 using content::WebContents;
41 using content::WebContentsImpl;
42 using content::WebContentsViewMac;
44 // Ensure that the blink::WebDragOperation enum values stay in sync with
45 // NSDragOperation constants, since the code below static_casts between 'em.
46 #define STATIC_ASSERT_MATCHING_ENUM(name) \
47   static_assert(int(NS##name) == int(blink::Web##name), "enum mismatch: " #name)
48 STATIC_ASSERT_MATCHING_ENUM(DragOperationNone);
49 STATIC_ASSERT_MATCHING_ENUM(DragOperationCopy);
50 STATIC_ASSERT_MATCHING_ENUM(DragOperationLink);
51 STATIC_ASSERT_MATCHING_ENUM(DragOperationGeneric);
52 STATIC_ASSERT_MATCHING_ENUM(DragOperationPrivate);
53 STATIC_ASSERT_MATCHING_ENUM(DragOperationMove);
54 STATIC_ASSERT_MATCHING_ENUM(DragOperationDelete);
55 STATIC_ASSERT_MATCHING_ENUM(DragOperationEvery);
57 @interface WebContentsViewCocoa (Private)
58 - (id)initWithWebContentsViewMac:(WebContentsViewMac*)w;
59 - (void)registerDragTypes;
60 - (void)setCurrentDragOperation:(NSDragOperation)operation;
61 - (DropData*)dropData;
62 - (void)startDragWithDropData:(const DropData&)dropData
63             dragOperationMask:(NSDragOperation)operationMask
64                         image:(NSImage*)image
65                        offset:(NSPoint)offset;
66 - (void)cancelDeferredClose;
67 - (void)clearWebContentsView;
68 - (void)closeTabAfterEvent;
69 - (void)viewDidBecomeFirstResponder:(NSNotification*)notification;
70 @end
72 namespace content {
74 WebContentsView* CreateWebContentsView(
75     WebContentsImpl* web_contents,
76     WebContentsViewDelegate* delegate,
77     RenderViewHostDelegateView** render_view_host_delegate_view) {
78   WebContentsViewMac* rv = new WebContentsViewMac(web_contents, delegate);
79   *render_view_host_delegate_view = rv;
80   return rv;
83 WebContentsViewMac::WebContentsViewMac(WebContentsImpl* web_contents,
84                                        WebContentsViewDelegate* delegate)
85     : web_contents_(web_contents),
86       delegate_(delegate),
87       allow_other_views_(false) {
90 WebContentsViewMac::~WebContentsViewMac() {
91   // This handles the case where a renderer close call was deferred
92   // while the user was operating a UI control which resulted in a
93   // close.  In that case, the Cocoa view outlives the
94   // WebContentsViewMac instance due to Cocoa retain count.
95   [cocoa_view_ cancelDeferredClose];
96   [cocoa_view_ clearWebContentsView];
99 gfx::NativeView WebContentsViewMac::GetNativeView() const {
100   return cocoa_view_.get();
103 gfx::NativeView WebContentsViewMac::GetContentNativeView() const {
104   RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
105   if (!rwhv)
106     return NULL;
107   return rwhv->GetNativeView();
110 gfx::NativeWindow WebContentsViewMac::GetTopLevelNativeWindow() const {
111   NSWindow* window = [cocoa_view_.get() window];
112   return window ? window : delegate_->GetNativeWindow();
115 void WebContentsViewMac::GetContainerBounds(gfx::Rect* out) const {
116   // Convert bounds to window coordinate space.
117   NSRect bounds =
118       [cocoa_view_.get() convertRect:[cocoa_view_.get() bounds] toView:nil];
120   // Convert bounds to screen coordinate space.
121   NSWindow* window = [cocoa_view_.get() window];
122   bounds.origin = [window convertBaseToScreen:bounds.origin];
124   // Flip y to account for screen flip.
125   NSScreen* screen = [[NSScreen screens] objectAtIndex:0];
126   bounds.origin.y = [screen frame].size.height - bounds.origin.y
127       - bounds.size.height;
128   *out = gfx::Rect(NSRectToCGRect(bounds));
131 void WebContentsViewMac::StartDragging(
132     const DropData& drop_data,
133     WebDragOperationsMask allowed_operations,
134     const gfx::ImageSkia& image,
135     const gfx::Vector2d& image_offset,
136     const DragEventSourceInfo& event_info) {
137   // By allowing nested tasks, the code below also allows Close(),
138   // which would deallocate |this|.  The same problem can occur while
139   // processing -sendEvent:, so Close() is deferred in that case.
140   // Drags from web content do not come via -sendEvent:, this sets the
141   // same flag -sendEvent: would.
142   base::mac::ScopedSendingEvent sending_event_scoper;
144   // The drag invokes a nested event loop, arrange to continue
145   // processing events.
146   base::MessageLoop::ScopedNestableTaskAllower allow(
147       base::MessageLoop::current());
148   NSDragOperation mask = static_cast<NSDragOperation>(allowed_operations);
149   NSPoint offset = NSPointFromCGPoint(
150       gfx::PointAtOffsetFromOrigin(image_offset).ToCGPoint());
151   [cocoa_view_ startDragWithDropData:drop_data
152                    dragOperationMask:mask
153                                image:gfx::NSImageFromImageSkia(image)
154                               offset:offset];
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   // Note(erikchen): This method has /never/ worked correctly. I've removed the
161   // previous implementation.
164 gfx::NativeView WebContentsViewMac::GetNativeViewForFocus() const {
165   RenderWidgetHostView* rwhv =
166       web_contents_->GetFullscreenRenderWidgetHostView();
167   if (!rwhv)
168     rwhv = web_contents_->GetRenderWidgetHostView();
169   return rwhv ? rwhv->GetNativeView() : nil;
172 void WebContentsViewMac::Focus() {
173   gfx::NativeView native_view = GetNativeViewForFocus();
174   NSWindow* window = [native_view window];
175   [window makeFirstResponder:native_view];
176   if (![window isVisible])
177     return;
178   [window makeKeyAndOrderFront:nil];
181 void WebContentsViewMac::SetInitialFocus() {
182   if (web_contents_->FocusLocationBarByDefault())
183     web_contents_->SetFocusToLocationBar(false);
184   else
185     Focus();
188 void WebContentsViewMac::StoreFocus() {
189   gfx::NativeView native_view = GetNativeViewForFocus();
190   // We're explicitly being asked to store focus, so don't worry if there's
191   // already a view saved.
192   focus_tracker_.reset(
193       [[FocusTracker alloc] initWithWindow:[native_view window]]);
196 void WebContentsViewMac::RestoreFocus() {
197   gfx::NativeView native_view = GetNativeViewForFocus();
198   // TODO(avi): Could we be restoring a view that's no longer in the key view
199   // chain?
200   if (!(focus_tracker_.get() &&
201         [focus_tracker_ restoreFocusInWindow:[native_view window]])) {
202     // Fall back to the default focus behavior if we could not restore focus.
203     // TODO(shess): If location-bar gets focus by default, this will
204     // select-all in the field.  If there was a specific selection in
205     // the field when we navigated away from it, we should restore
206     // that selection.
207     SetInitialFocus();
208   }
210   focus_tracker_.reset(nil);
213 DropData* WebContentsViewMac::GetDropData() const {
214   return [cocoa_view_ dropData];
217 void WebContentsViewMac::UpdateDragCursor(WebDragOperation operation) {
218   [cocoa_view_ setCurrentDragOperation: operation];
221 void WebContentsViewMac::GotFocus() {
222   // This is only used in the views FocusManager stuff but it bleeds through
223   // all subclasses. http://crbug.com/21875
226 // This is called when the renderer asks us to take focus back (i.e., it has
227 // iterated past the last focusable element on the page).
228 void WebContentsViewMac::TakeFocus(bool reverse) {
229   if (reverse) {
230     [[cocoa_view_ window] selectPreviousKeyView:cocoa_view_.get()];
231   } else {
232     [[cocoa_view_ window] selectNextKeyView:cocoa_view_.get()];
233   }
236 void WebContentsViewMac::ShowContextMenu(
237     RenderFrameHost* render_frame_host,
238     const ContextMenuParams& params) {
239   // Allow delegates to handle the context menu operation first.
240   if (web_contents_->GetDelegate() &&
241       web_contents_->GetDelegate()->HandleContextMenu(params)) {
242     return;
243   }
245   if (delegate())
246     delegate()->ShowContextMenu(render_frame_host, params);
247   else
248     DLOG(ERROR) << "Cannot show context menus without a delegate.";
251 void WebContentsViewMac::ShowPopupMenu(
252     RenderFrameHost* render_frame_host,
253     const gfx::Rect& bounds,
254     int item_height,
255     double item_font_size,
256     int selected_item,
257     const std::vector<MenuItem>& items,
258     bool right_aligned,
259     bool allow_multiple_selection) {
260   popup_menu_helper_.reset(new PopupMenuHelper(render_frame_host));
261   popup_menu_helper_->ShowPopupMenu(bounds, item_height, item_font_size,
262                                     selected_item, items, right_aligned,
263                                     allow_multiple_selection);
264   popup_menu_helper_.reset();
267 void WebContentsViewMac::HidePopupMenu() {
268   if (popup_menu_helper_)
269     popup_menu_helper_->Hide();
272 gfx::Rect WebContentsViewMac::GetViewBounds() const {
273   // This method is not currently used on mac.
274   NOTIMPLEMENTED();
275   return gfx::Rect();
278 void WebContentsViewMac::SetAllowOtherViews(bool allow) {
279   if (allow_other_views_ == allow)
280     return;
282   allow_other_views_ = allow;
283   RenderWidgetHostViewMac* view = static_cast<RenderWidgetHostViewMac*>(
284       web_contents_->GetRenderWidgetHostView());
285   if (view)
286     view->SetAllowPauseForResizeOrRepaint(!allow_other_views_);
289 bool WebContentsViewMac::GetAllowOtherViews() const {
290   return allow_other_views_;
293 void WebContentsViewMac::CreateView(
294     const gfx::Size& initial_size, gfx::NativeView context) {
295   WebContentsViewCocoa* view =
296       [[WebContentsViewCocoa alloc] initWithWebContentsViewMac:this];
297   cocoa_view_.reset(view);
300 RenderWidgetHostViewBase* WebContentsViewMac::CreateViewForWidget(
301     RenderWidgetHost* render_widget_host, bool is_guest_view_hack) {
302   if (render_widget_host->GetView()) {
303     // During testing, the view will already be set up in most cases to the
304     // test view, so we don't want to clobber it with a real one. To verify that
305     // this actually is happening (and somebody isn't accidentally creating the
306     // view twice), we check for the RVH Factory, which will be set when we're
307     // making special ones (which go along with the special views).
308     DCHECK(RenderViewHostFactory::has_factory());
309     return static_cast<RenderWidgetHostViewBase*>(
310         render_widget_host->GetView());
311   }
313   RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(
314       render_widget_host, is_guest_view_hack);
315   if (delegate()) {
316     base::scoped_nsobject<NSObject<RenderWidgetHostViewMacDelegate> >
317         rw_delegate(
318             delegate()->CreateRenderWidgetHostViewDelegate(render_widget_host));
320     view->SetDelegate(rw_delegate.get());
321   }
322   view->SetAllowPauseForResizeOrRepaint(!allow_other_views_);
324   // Fancy layout comes later; for now just make it our size and resize it
325   // with us. In case there are other siblings of the content area, we want
326   // to make sure the content area is on the bottom so other things draw over
327   // it.
328   NSView* view_view = view->GetNativeView();
329   [view_view setFrame:[cocoa_view_.get() bounds]];
330   [view_view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
331   // Add the new view below all other views; this also keeps it below any
332   // overlay view installed.
333   [cocoa_view_.get() addSubview:view_view
334                      positioned:NSWindowBelow
335                      relativeTo:nil];
336   // For some reason known only to Cocoa, the autorecalculation of the key view
337   // loop set on the window doesn't set the next key view when the subview is
338   // added. On 10.6 things magically work fine; on 10.5 they fail
339   // <http://crbug.com/61493>. Digging into Cocoa key view loop code yielded
340   // madness; TODO(avi,rohit): look at this again and figure out what's really
341   // going on.
342   [cocoa_view_.get() setNextKeyView:view_view];
343   return view;
346 RenderWidgetHostViewBase* WebContentsViewMac::CreateViewForPopupWidget(
347     RenderWidgetHost* render_widget_host) {
348   return new RenderWidgetHostViewMac(render_widget_host, false);
351 void WebContentsViewMac::SetPageTitle(const base::string16& title) {
352   // Meaningless on the Mac; widgets don't have a "title" attribute
356 void WebContentsViewMac::RenderViewCreated(RenderViewHost* host) {
357   // We want updates whenever the intrinsic width of the webpage changes.
358   // Put the RenderView into that mode. The preferred width is used for example
359   // when the "zoom" button in the browser window is clicked.
360   host->EnablePreferredSizeMode();
363 void WebContentsViewMac::RenderViewSwappedIn(RenderViewHost* host) {
366 void WebContentsViewMac::SetOverscrollControllerEnabled(bool enabled) {
369 bool WebContentsViewMac::IsEventTracking() const {
370   return base::MessagePumpMac::IsHandlingSendEvent();
373 // Arrange to call CloseTab() after we're back to the main event loop.
374 // The obvious way to do this would be PostNonNestableTask(), but that
375 // will fire when the event-tracking loop polls for events.  So we
376 // need to bounce the message via Cocoa, instead.
377 void WebContentsViewMac::CloseTabAfterEventTracking() {
378   [cocoa_view_ cancelDeferredClose];
379   [cocoa_view_ performSelector:@selector(closeTabAfterEvent)
380                     withObject:nil
381                     afterDelay:0.0];
384 void WebContentsViewMac::CloseTab() {
385   web_contents_->Close(web_contents_->GetRenderViewHost());
388 }  // namespace content
390 @implementation WebContentsViewCocoa
392 - (id)initWithWebContentsViewMac:(WebContentsViewMac*)w {
393   self = [super initWithFrame:NSZeroRect];
394   if (self != nil) {
395     webContentsView_ = w;
396     dragDest_.reset(
397         [[WebDragDest alloc] initWithWebContentsImpl:[self webContents]]);
398     [self registerDragTypes];
400     [[NSNotificationCenter defaultCenter]
401          addObserver:self
402             selector:@selector(viewDidBecomeFirstResponder:)
403                 name:kViewDidBecomeFirstResponder
404               object:nil];
406     if (webContentsView_->delegate()) {
407       [dragDest_ setDragDelegate:webContentsView_->delegate()->
408           GetDragDestDelegate()];
409     }
410   }
411   return self;
414 - (void)dealloc {
415   // Cancel any deferred tab closes, just in case.
416   [self cancelDeferredClose];
418   // This probably isn't strictly necessary, but can't hurt.
419   [self unregisterDraggedTypes];
421   [[NSNotificationCenter defaultCenter] removeObserver:self];
423   [super dealloc];
426 // Registers for the view for the appropriate drag types.
427 - (void)registerDragTypes {
428   NSArray* types = [NSArray arrayWithObjects:
429       ui::kChromeDragDummyPboardType,
430       kWebURLsWithTitlesPboardType,
431       NSURLPboardType,
432       NSStringPboardType,
433       NSHTMLPboardType,
434       NSRTFPboardType,
435       NSFilenamesPboardType,
436       ui::kWebCustomDataPboardType,
437       nil];
438   [self registerForDraggedTypes:types];
441 - (void)setCurrentDragOperation:(NSDragOperation)operation {
442   [dragDest_ setCurrentOperation:operation];
445 - (DropData*)dropData {
446   return [dragDest_ currentDropData];
449 - (WebContentsImpl*)webContents {
450   if (webContentsView_ == NULL)
451     return NULL;
452   return webContentsView_->web_contents();
455 - (void)mouseEvent:(NSEvent*)theEvent {
456   WebContentsImpl* webContents = [self webContents];
457   if (webContents && webContents->GetDelegate()) {
458     NSPoint location = [NSEvent mouseLocation];
459     if ([theEvent type] == NSMouseMoved)
460       webContents->GetDelegate()->ContentsMouseEvent(
461           webContents, gfx::Point(location.x, location.y), true);
462     if ([theEvent type] == NSMouseExited)
463       webContents->GetDelegate()->ContentsMouseEvent(
464           webContents, gfx::Point(location.x, location.y), false);
465   }
468 - (void)setMouseDownCanMoveWindow:(BOOL)canMove {
469   mouseDownCanMoveWindow_ = canMove;
472 - (BOOL)mouseDownCanMoveWindow {
473   // This is needed to prevent mouseDowns from moving the window
474   // around.  The default implementation returns YES only for opaque
475   // views.  WebContentsViewCocoa does not draw itself in any way, but
476   // its subviews do paint their entire frames.  Returning NO here
477   // saves us the effort of overriding this method in every possible
478   // subview.
479   return mouseDownCanMoveWindow_;
482 - (void)pasteboard:(NSPasteboard*)sender provideDataForType:(NSString*)type {
483   [dragSource_ lazyWriteToPasteboard:sender
484                              forType:type];
487 - (void)startDragWithDropData:(const DropData&)dropData
488             dragOperationMask:(NSDragOperation)operationMask
489                         image:(NSImage*)image
490                        offset:(NSPoint)offset {
491   dragSource_.reset([[WebDragSource alloc]
492       initWithContents:[self webContents]
493                   view:self
494               dropData:&dropData
495                  image:image
496                 offset:offset
497             pasteboard:[NSPasteboard pasteboardWithName:NSDragPboard]
498      dragOperationMask:operationMask]);
499   [dragSource_ startDrag];
502 // NSDraggingSource methods
504 // Returns what kind of drag operations are available. This is a required
505 // method for NSDraggingSource.
506 - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal {
507   if (dragSource_)
508     return [dragSource_ draggingSourceOperationMaskForLocal:isLocal];
509   // No web drag source - this is the case for dragging a file from the
510   // downloads manager. Default to copy operation. Note: It is desirable to
511   // allow the user to either move or copy, but this requires additional
512   // plumbing to update the download item's path once its moved.
513   return NSDragOperationCopy;
516 // Called when a drag initiated in our view ends.
517 - (void)draggedImage:(NSImage*)anImage
518              endedAt:(NSPoint)screenPoint
519            operation:(NSDragOperation)operation {
520   [dragSource_ endDragAt:screenPoint operation:operation];
522   // Might as well throw out this object now.
523   dragSource_.reset();
526 // Called when a drag initiated in our view moves.
527 - (void)draggedImage:(NSImage*)draggedImage movedTo:(NSPoint)screenPoint {
530 // Called when a file drag is dropped and the promised files need to be written.
531 - (NSArray*)namesOfPromisedFilesDroppedAtDestination:(NSURL*)dropDest {
532   if (![dropDest isFileURL])
533     return nil;
535   NSString* fileName = [dragSource_ dragPromisedFileTo:[dropDest path]];
536   if (!fileName)
537     return nil;
539   return @[ fileName ];
542 // NSDraggingDestination methods
544 - (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
545   return [dragDest_ draggingEntered:sender view:self];
548 - (void)draggingExited:(id<NSDraggingInfo>)sender {
549   [dragDest_ draggingExited:sender];
552 - (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender {
553   return [dragDest_ draggingUpdated:sender view:self];
556 - (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
557   return [dragDest_ performDragOperation:sender view:self];
560 - (void)cancelDeferredClose {
561   SEL aSel = @selector(closeTabAfterEvent);
562   [NSObject cancelPreviousPerformRequestsWithTarget:self
563                                            selector:aSel
564                                              object:nil];
567 - (void)clearWebContentsView {
568   webContentsView_ = NULL;
569   [dragSource_ clearWebContentsView];
572 - (void)closeTabAfterEvent {
573   webContentsView_->CloseTab();
576 - (void)viewDidBecomeFirstResponder:(NSNotification*)notification {
577   NSView* view = [notification object];
578   if (![[self subviews] containsObject:view])
579     return;
581   NSSelectionDirection direction =
582       [[[notification userInfo] objectForKey:kSelectionDirection]
583         unsignedIntegerValue];
584   if (direction == NSDirectSelection)
585     return;
587   [self webContents]->
588       FocusThroughTabTraversal(direction == NSSelectingPrevious);
591 // When the subviews require a layout, their size should be reset to the size
592 // of this view. (It is possible for the size to get out of sync as an
593 // optimization in preparation for an upcoming WebContentsView resize.
594 // http://crbug.com/264207)
595 - (void)resizeSubviewsWithOldSize:(NSSize)oldBoundsSize {
596   for (NSView* subview in self.subviews)
597     [subview setFrame:self.bounds];
600 - (void)viewWillMoveToWindow:(NSWindow*)newWindow {
601   NSWindow* oldWindow = [self window];
603   NSNotificationCenter* notificationCenter =
604       [NSNotificationCenter defaultCenter];
606   // Occlusion notification APIs are new in Mavericks.
607   bool supportsOcclusionAPIs = base::mac::IsOSMavericksOrLater();
609   if (supportsOcclusionAPIs) {
610     if (oldWindow) {
611       [notificationCenter
612           removeObserver:self
613                     name:NSWindowDidChangeOcclusionStateNotification
614                   object:oldWindow];
615     }
616     if (newWindow) {
617       [notificationCenter
618           addObserver:self
619              selector:@selector(windowChangedOcclusionState:)
620                  name:NSWindowDidChangeOcclusionStateNotification
621                object:newWindow];
622     }
623   }
626 - (void)windowChangedOcclusionState:(NSNotification*)notification {
627   DCHECK(base::mac::IsOSMavericksOrLater());
628   NSWindow* window = [notification object];
629   WebContentsImpl* webContents = [self webContents];
630   if (window && webContents && !webContents->IsBeingDestroyed()) {
631     if ([window occlusionState] & NSWindowOcclusionStateVisible) {
632       webContents->WasUnOccluded();
633     } else {
634       webContents->WasOccluded();
635     }
636   }
639 @end