// Copyright 2018 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import "content/app_shim_remote_cocoa/web_contents_view_cocoa.h" #import "base/mac/mac_util.h" #import "content/app_shim_remote_cocoa/web_drag_source_mac.h" #import "content/browser/web_contents/web_drag_dest_mac.h" #include "content/common/web_contents_ns_view_bridge.mojom.h" #import "third_party/mozilla/NSPasteboard+Utils.h" #include "ui/base/clipboard/clipboard_constants.h" #include "ui/base/clipboard/custom_data_helper.h" #include "ui/base/cocoa/cocoa_base_utils.h" #include "ui/base/dragdrop/cocoa_dnd_util.h" using remote_cocoa::mojom::DraggingInfo; using remote_cocoa::mojom::SelectionDirection; using remote_cocoa::mojom::Visibility; using content::DropData; //////////////////////////////////////////////////////////////////////////////// // WebContentsViewCocoa @implementation WebContentsViewCocoa { BOOL _inFullScreenTransition; } - (id)initWithViewsHostableView:(ui::ViewsHostableView*)v { self = [super initWithFrame:NSZeroRect]; if (self != nil) { _viewsHostableView = v; [self registerDragTypes]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewDidBecomeFirstResponder:) name:kViewDidBecomeFirstResponder object:nil]; } return self; } - (void)dealloc { // This probably isn't strictly necessary, but can't hurt. [self unregisterDraggedTypes]; [[NSNotificationCenter defaultCenter] removeObserver:self]; [super dealloc]; } - (void)populateDraggingInfo:(DraggingInfo*)info fromNSDraggingInfo:(id)nsInfo { NSPoint windowPoint = [nsInfo draggingLocation]; NSPoint viewPoint = [self convertPoint:windowPoint fromView:nil]; NSRect viewFrame = [self frame]; info->location_in_view = gfx::PointF(viewPoint.x, viewFrame.size.height - viewPoint.y); NSPoint screenPoint = ui::ConvertPointFromWindowToScreen([self window], windowPoint); NSScreen* screen = [[self window] screen]; NSRect screenFrame = [screen frame]; info->location_in_screen = gfx::PointF(screenPoint.x, screenFrame.size.height - screenPoint.y); NSPasteboard* pboard = [nsInfo draggingPasteboard]; if ([pboard containsURLDataConvertingTextToURL:YES]) { GURL url; ui::PopulateURLAndTitleFromPasteboard(&url, NULL, pboard, YES); info->url.emplace(url); } info->operation_mask = [nsInfo draggingSourceOperationMask]; } - (BOOL)allowsVibrancy { // Returning YES will allow rendering this view with vibrancy effect if it is // incorporated into a view hierarchy that uses vibrancy, it will have no // effect otherwise. // For details see Apple documentation on NSView and NSVisualEffectView. return YES; } // Registers for the view for the appropriate drag types. - (void)registerDragTypes { NSArray* types = [NSArray arrayWithObjects:ui::kChromeDragDummyPboardType, kWebURLsWithTitlesPboardType, NSURLPboardType, NSStringPboardType, NSHTMLPboardType, NSRTFPboardType, NSFilenamesPboardType, ui::kWebCustomDataPboardType, nil]; [self registerForDraggedTypes:types]; } - (void)mouseEvent:(NSEvent*)theEvent { if (!_host) return; _host->OnMouseEvent([theEvent type] == NSMouseMoved, [theEvent type] == NSMouseExited); } - (void)setMouseDownCanMoveWindow:(BOOL)canMove { _mouseDownCanMoveWindow = canMove; } - (BOOL)mouseDownCanMoveWindow { // This is needed to prevent mouseDowns from moving the window // around. The default implementation returns YES only for opaque // views. WebContentsViewCocoa does not draw itself in any way, but // its subviews do paint their entire frames. Returning NO here // saves us the effort of overriding this method in every possible // subview. return _mouseDownCanMoveWindow; } - (void)pasteboard:(NSPasteboard*)sender provideDataForType:(NSString*)type { [_dragSource lazyWriteToPasteboard:sender forType:type]; } - (void)startDragWithDropData:(const DropData&)dropData dragOperationMask:(NSDragOperation)operationMask image:(NSImage*)image offset:(NSPoint)offset { if (!_host) return; _dragSource.reset([[WebDragSource alloc] initWithHost:_host view:self dropData:&dropData image:image offset:offset pasteboard:[NSPasteboard pasteboardWithName:NSDragPboard] dragOperationMask:operationMask]); [_dragSource startDrag]; } // NSDraggingSource methods - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal { if (_dragSource) return [_dragSource draggingSourceOperationMaskForLocal:isLocal]; // No web drag source - this is the case for dragging a file from the // downloads manager. Default to copy operation. Note: It is desirable to // allow the user to either move or copy, but this requires additional // plumbing to update the download item's path once its moved. return NSDragOperationCopy; } // Called when a drag initiated in our view ends. - (void)draggedImage:(NSImage*)anImage endedAt:(NSPoint)screenPoint operation:(NSDragOperation)operation { [_dragSource endDragAt:screenPoint operation:operation]; // Might as well throw out this object now. _dragSource.reset(); } // Called when a drag initiated in our view moves. - (void)draggedImage:(NSImage*)draggedImage movedTo:(NSPoint)screenPoint { } // Called when a file drag is dropped and the promised files need to be written. - (NSArray*)namesOfPromisedFilesDroppedAtDestination:(NSURL*)dropDest { if (![dropDest isFileURL]) return nil; NSString* fileName = [_dragSource dragPromisedFileTo:[dropDest path]]; if (!fileName) return nil; return @[ fileName ]; } // NSDraggingDestination methods - (NSDragOperation)draggingEntered:(id)sender { if (!_host) return NSDragOperationNone; // Fill out a DropData from pasteboard. DropData dropData; content::PopulateDropDataFromPasteboard(&dropData, [sender draggingPasteboard]); _host->SetDropData(dropData); auto draggingInfo = DraggingInfo::New(); [self populateDraggingInfo:draggingInfo.get() fromNSDraggingInfo:sender]; uint32_t result = 0; _host->DraggingEntered(std::move(draggingInfo), &result); return result; } - (void)draggingExited:(id)sender { if (!_host) return; _host->DraggingExited(); } - (NSDragOperation)draggingUpdated:(id)sender { if (!_host) return NSDragOperationNone; auto draggingInfo = DraggingInfo::New(); [self populateDraggingInfo:draggingInfo.get() fromNSDraggingInfo:sender]; uint32_t result = 0; _host->DraggingUpdated(std::move(draggingInfo), &result); return result; } - (BOOL)performDragOperation:(id)sender { if (!_host) return NO; auto draggingInfo = DraggingInfo::New(); [self populateDraggingInfo:draggingInfo.get() fromNSDraggingInfo:sender]; bool result = false; _host->PerformDragOperation(std::move(draggingInfo), &result); return result; } - (void)clearViewsHostableView { _viewsHostableView = nullptr; } - (void)setHost:(remote_cocoa::mojom::WebContentsNSViewHost*)host { if (!host) [_dragSource clearHostAndWebContentsView]; _host = host; } - (void)viewDidBecomeFirstResponder:(NSNotification*)notification { if (!_host) return; NSView* view = [notification object]; if (![[self subviews] containsObject:view]) return; NSSelectionDirection ns_direction = static_cast([[[notification userInfo] objectForKey:kSelectionDirection] unsignedIntegerValue]); SelectionDirection direction; switch (ns_direction) { case NSDirectSelection: direction = SelectionDirection::kDirect; break; case NSSelectingNext: direction = SelectionDirection::kForward; break; case NSSelectingPrevious: direction = SelectionDirection::kReverse; break; default: return; } _host->OnBecameFirstResponder(direction); } - (void)updateWebContentsVisibility { if (!_host || _inFullScreenTransition) return; Visibility visibility = Visibility::kVisible; if ([self isHiddenOrHasHiddenAncestor] || ![self window]) visibility = Visibility::kHidden; else if ([[self window] occlusionState] & NSWindowOcclusionStateVisible) visibility = Visibility::kVisible; else visibility = Visibility::kOccluded; _host->OnWindowVisibilityChanged(visibility); } - (void)resizeSubviewsWithOldSize:(NSSize)oldBoundsSize { // Subviews do not participate in auto layout unless the the size this view // changes. This allows RenderWidgetHostViewMac::SetBounds(..) to select a // size of the subview that differs from its superview in preparation for an // upcoming WebContentsView resize. // See http://crbug.com/264207 and http://crbug.com/655112. } - (void)setFrameSize:(NSSize)newSize { [super setFrameSize:newSize]; // Perform manual layout of subviews, e.g., when the window size changes. for (NSView* subview in [self subviews]) [subview setFrame:[self bounds]]; } - (void)viewWillMoveToWindow:(NSWindow*)newWindow { NSWindow* oldWindow = [self window]; NSNotificationCenter* notificationCenter = [NSNotificationCenter defaultCenter]; if (oldWindow) { NSArray* notificationsToRemove = @[ NSWindowDidChangeOcclusionStateNotification, NSWindowWillEnterFullScreenNotification, NSWindowDidEnterFullScreenNotification, NSWindowWillExitFullScreenNotification, NSWindowDidExitFullScreenNotification ]; for (NSString* notificationName in notificationsToRemove) { [notificationCenter removeObserver:self name:notificationName object:oldWindow]; } } if (newWindow) { [notificationCenter addObserver:self selector:@selector(windowChangedOcclusionState:) name:NSWindowDidChangeOcclusionStateNotification object:newWindow]; // The fullscreen transition causes spurious occlusion notifications. // See https://crbug.com/1081229 [notificationCenter addObserver:self selector:@selector(fullscreenTransitionStarted:) name:NSWindowWillEnterFullScreenNotification object:newWindow]; [notificationCenter addObserver:self selector:@selector(fullscreenTransitionComplete:) name:NSWindowDidEnterFullScreenNotification object:newWindow]; [notificationCenter addObserver:self selector:@selector(fullscreenTransitionStarted:) name:NSWindowWillExitFullScreenNotification object:newWindow]; [notificationCenter addObserver:self selector:@selector(fullscreenTransitionComplete:) name:NSWindowDidExitFullScreenNotification object:newWindow]; } } - (void)windowChangedOcclusionState:(NSNotification*)notification { [self updateWebContentsVisibility]; } - (void)fullscreenTransitionStarted:(NSNotification*)notification { _inFullScreenTransition = YES; } - (void)fullscreenTransitionComplete:(NSNotification*)notification { _inFullScreenTransition = NO; } - (void)viewDidMoveToWindow { [self updateWebContentsVisibility]; } - (void)viewDidHide { [self updateWebContentsVisibility]; } - (void)viewDidUnhide { [self updateWebContentsVisibility]; } // ViewsHostable protocol implementation. - (ui::ViewsHostableView*)viewsHostableView { return _viewsHostableView; } @end