blob: ccc8dc119a0f8efdc546cc73854be7b5897fb9b7 [file] [log] [blame]
// 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"
#include "ui/base/dragdrop/drag_drop_types.h"
using remote_cocoa::mojom::DraggingInfo;
using remote_cocoa::mojom::SelectionDirection;
using remote_cocoa::mojom::Visibility;
using content::DropData;
// Ensure that the ui::DragDropTypes::DragOperation enum values stay in sync
// with NSDragOperation constants, since the code below uses
// NSDragOperationToDragOperation to filter invalid values.
#define STATIC_ASSERT_ENUM(a, b) \
static_assert(static_cast<int>(a) == static_cast<int>(b), \
"enum mismatch: " #a)
STATIC_ASSERT_ENUM(NSDragOperationNone, ui::DragDropTypes::DRAG_NONE);
STATIC_ASSERT_ENUM(NSDragOperationCopy, ui::DragDropTypes::DRAG_COPY);
STATIC_ASSERT_ENUM(NSDragOperationLink, ui::DragDropTypes::DRAG_LINK);
STATIC_ASSERT_ENUM(NSDragOperationMove, ui::DragDropTypes::DRAG_MOVE);
////////////////////////////////////////////////////////////////////////////////
// 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<NSDraggingInfo>)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 = ui::DragDropTypes::NSDragOperationToDragOperation(
[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:ui::DragDropTypes::NSDragOperationToDragOperation(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<NSDraggingInfo>)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<NSDraggingInfo>)sender {
if (!_host)
return;
_host->DraggingExited();
}
- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)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<NSDraggingInfo>)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<NSSelectionDirection>([[[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