Chromium Code Reviews| Index: ui/views/cocoa/bridged_native_widget.mm |
| diff --git a/ui/views/cocoa/bridged_native_widget.mm b/ui/views/cocoa/bridged_native_widget.mm |
| index 6f5dec0f96864ede56c8a7cf940de5ebf964aa92..3671b1eb63511e47b7aeda036ad7a56dcf13cc5a 100644 |
| --- a/ui/views/cocoa/bridged_native_widget.mm |
| +++ b/ui/views/cocoa/bridged_native_widget.mm |
| @@ -12,6 +12,7 @@ |
| #import "base/mac/foundation_util.h" |
| #include "base/mac/mac_util.h" |
| #import "base/mac/sdk_forward_declarations.h" |
| +#include "base/run_loop.h" |
| #include "base/thread_task_runner_handle.h" |
| #include "ui/accelerated_widget_mac/window_resize_helper_mac.h" |
| #import "ui/base/cocoa/constrained_window/constrained_window_animation.h" |
| @@ -35,16 +36,6 @@ |
| #include "ui/views/widget/widget_aura_utils.h" |
| #include "ui/views/widget/widget_delegate.h" |
| -extern "C" { |
| - |
| -typedef int32_t CGSConnection; |
| -CGSConnection _CGSDefaultConnection(); |
| -CGError CGSSetWindowBackgroundBlurRadius(CGSConnection connection, |
| - NSInteger windowNumber, |
| - int radius); |
| - |
| -} |
| - |
| // The NSView that hosts the composited CALayer drawing the UI. It fills the |
| // window but is not hittable so that accessibility hit tests always go to the |
| // BridgedContentView. |
| @@ -94,6 +85,8 @@ const int kResizeAreaCornerSize = 12; |
| int kWindowPropertiesKey; |
| +bool g_ignore_next_mouse_down_for_draggable_regions = false; |
| + |
| float GetDeviceScaleFactorFromView(NSView* view) { |
| gfx::Display display = |
| gfx::Screen::GetScreen()->GetDisplayNearestWindow(view); |
| @@ -191,6 +184,11 @@ NSEvent* RepostEventIfHandledByWindow(NSEvent* ns_event) { |
| // - any repost loop. |
| if (repost_state == NONE) { |
| + if (g_ignore_next_mouse_down_for_draggable_regions) { |
| + g_ignore_next_mouse_down_for_draggable_regions = false; |
| + return ns_event; |
| + } |
| + |
| if (WindowWantsMouseDownReposted(ns_event)) { |
| repost_state = EXPECTING_REPOST; |
| reposted_event_number = event_number; |
| @@ -263,10 +261,215 @@ scoped_refptr<base::SingleThreadTaskRunner> GetCompositorTaskRunner() { |
| return task_runner ? task_runner : base::ThreadTaskRunnerHandle::Get(); |
| } |
| +CGPoint GetCGMousePosition() { |
| + return CGEventGetLocation( |
| + base::ScopedCFTypeRef<CGEventRef>(CGEventCreate(nullptr))); |
| +} |
| + |
| +void SendCustomLeftMouseEvent(CGEventType type) { |
| + base::ScopedCFTypeRef<CGEventRef> event( |
| + CGEventCreateMouseEvent( |
| + nullptr, type, GetCGMousePosition(), |
| + kCGMouseButtonLeft)); |
| + CGEventSetIntegerValueField(event, kCGEventSourceUserData, 1); |
| + CGEventPost(kCGSessionEventTap, event); |
| +} |
| + |
| } // namespace |
| +extern "C" { |
| + |
| +typedef int32_t CGSConnection; |
| +CGSConnection _CGSDefaultConnection(); |
| +CGError CGSSetWindowBackgroundBlurRadius(CGSConnection connection, |
| + NSInteger windowNumber, |
| + int radius); |
| + |
| +} |
| + |
| +// CocoaWindowMoveLoop can be deleted just before its local event monitor is |
| +// processed and in that case it's too late to call removeMonitor: and we have a |
| +// dangling this pointer. Use a proxy Obj-C class to store the weakptr. |
| +@interface WeakCocoaWindowMoveLoop : NSObject { |
| + @private |
| + base::WeakPtr<views::CocoaWindowMoveLoop> weak_; |
| +} |
| +@end |
| + |
| +@implementation WeakCocoaWindowMoveLoop |
| +- (id)initWithWeakPtr:(const base::WeakPtr<views::CocoaWindowMoveLoop>&)weak { |
| + if (self = [super init]) { |
| + weak_ = weak; |
| + } |
| + return self; |
| +} |
| + |
| +- (base::WeakPtr<views::CocoaWindowMoveLoop>&)weak { |
| + return weak_; |
| +} |
| +@end |
| + |
| namespace views { |
| +class CocoaWindowMoveLoop { |
|
tapted
2016/03/10 11:51:19
Move this to its own file (with WeakCocoaMoveLoop?
themblsha
2016/03/10 17:18:58
Oh, right, you told me about that and it seems tha
|
| + public: |
| + explicit CocoaWindowMoveLoop(BridgedNativeWidget* owner) |
| + : owner_(owner), |
| + run_loop_(new base::RunLoop), |
| + quit_closure_(run_loop_->QuitClosure()), |
| + weak_factory_(this) { |
| + // AppKit continues to send mouse events to the window, but toolkit-views |
| + // goes berserk if it gets them during RunMoveLoop(). |
|
tapted
2016/03/10 11:51:19
hehe - I remember writing `berserk`... Perhaps "bu
themblsha
2016/03/10 17:18:58
Done.
|
| + [owner_->ns_view() setIgnoreMouseEvents:YES]; |
| + owner_->SetDraggable(true); |
| + } |
| + |
| + ~CocoaWindowMoveLoop() { |
| + owner_->SetDraggable(false); |
| + // Note the crafted events in Run() should not make their way through to |
| + // toolkit-views, but regular events after that should be. So stop ignoring. |
| + [owner_->ns_view() setIgnoreMouseEvents:NO]; |
| + |
| + // Handle the pathological case, where |this| is destroyed while running. |
| + if (exit_reason_ref_) { |
| + *exit_reason_ref_ = WINDOW_DESTROYED; |
| + quit_closure_.Run(); |
| + } |
| + |
| + // Handle Run() never being called. |
| + if (monitor_) { |
| + [NSEvent removeMonitor:monitor_]; |
| + monitor_ = nil; |
| + } |
| + owner_ = nullptr; |
| + } |
| + |
| + Widget::MoveLoopResult Run() { |
| + LoopExitReason exit_reason = ENDED_EXTERNALLY; |
| + exit_reason_ref_ = &exit_reason; |
| + |
| + // Move the RunLoop to the stack so our destructor can be called while it is |
| + // running. |
| + scoped_ptr<base::RunLoop> run_loop(std::move(run_loop_)); |
| + |
| + // A new window may have just been created, so post an event at the session |
| + // tap level to initiate a window drag. |
| + // TODO(tapted): Move this "inside" the window or stuff breaks when dragging |
| + // the last tab/s off a browser. |
| + SendCustomLeftMouseEvent(kCGEventLeftMouseDown); |
| + |
| + WeakCocoaWindowMoveLoop* proxy_quit_closure = |
| + [[[WeakCocoaWindowMoveLoop alloc] |
| + initWithWeakPtr:weak_factory_.GetWeakPtr()] autorelease]; |
| + |
| + NSEventMask mask = |
| + NSLeftMouseUpMask | NSKeyDownMask | NSLeftMouseDraggedMask; |
| + monitor_ = [NSEvent addLocalMonitorForEventsMatchingMask:mask |
| + handler:^NSEvent*(NSEvent* event) { |
| + if ([event type] == NSLeftMouseDragged) { |
| + // AppKit doesn't supply position updates during a drag, so post a |
| + // task to notify observers once AppKit has moved the window. |
| + base::MessageLoop::current()->PostTask( |
| + FROM_HERE, base::Bind(&BridgedNativeWidget::OnPositionChanged, |
| + base::Unretained(owner_))); |
| + return event; |
| + } |
| + |
| + CocoaWindowMoveLoop* thiss = [proxy_quit_closure weak].get(); |
| + if (!thiss) |
| + return nil; |
| + |
| + thiss->quit_closure_.Run(); |
| + if ([event type] == NSLeftMouseUp) { |
| + *thiss->exit_reason_ref_ = MOUSE_UP; |
| + return event; // Process the MouseUp. |
| + } |
| + *thiss->exit_reason_ref_ = ESCAPE_PRESSED; |
| + return nil; // Swallow the keypress. |
| + }]; |
| + |
| + // NSKeyDownMask doesn't work inside addLocalMonitorForEventsMatchingMask: |
| + // the event is swallowed by the window move loop before it gets to -[NSApp |
| + // sendEvent:]. To see an escape keypress, hook in an event tap lower. |
| + |
| + run_loop->Run(); |
| + |
| + if (exit_reason != WINDOW_DESTROYED && exit_reason != ENDED_EXTERNALLY) { |
| + exit_reason_ref_ = nullptr; // Ensure End() doesn't replace the reason. |
| + owner_->EndMoveLoop(); // Deletes |this|. |
| + } |
| + |
| + if (exit_reason != MOUSE_UP) { |
| + // Tell AppKit to stop moving the window. Otherwise, AppKit will refuse to |
| + // start a new window drag. Note the window being dragged is going away in |
| + // this case, so it doesn't really matter where the event is located. |
| + SendCustomLeftMouseEvent(kCGEventLeftMouseUp); |
| + |
| + if (exit_reason == ENDED_EXTERNALLY) { |
| + // When not canceled, the non-moving drag in the original window must |
| + // resume. To do this, AppKit needs to see a mouseDown so that it sends |
| + // the correct events. Ideally, it also needs to be at the original |
| + // offset, so that it hits a non-draggable region of the original |
| + // window: The tab being dragged may move some distance from the cursor |
| + // when it "snaps in", so the cursor may not be over a tab. Sadly, this |
| + // method doesn't know which window that is. But all that really needs |
| + // to be done is to prevent a custom-dragging area from starting a |
| + // window-drag. So hook into the logic in RepostEventIfHandledByWindow() |
| + // by setting a flag here. Note this assumes the custom mouseDown event |
| + // is guaranteed to hit another BridgedNativeWidget when it gets to the |
| + // front of the event queue. |
| + // TODO(tapted): A better fix would be to keep the temporary window |
| + // around and never call EndMoveLoop() on Mac, making this block of code |
| + // obsolete. |
| + g_ignore_next_mouse_down_for_draggable_regions = true; |
| + SendCustomLeftMouseEvent(kCGEventLeftMouseDown); |
| + } |
| + } |
| + |
| + return exit_reason == ESCAPE_PRESSED |
| + ? Widget::MOVE_LOOP_CANCELED : Widget::MOVE_LOOP_SUCCESSFUL; |
| + } |
| + |
| + void End() { |
| + if (exit_reason_ref_) { |
| + DCHECK_EQ(*exit_reason_ref_, ENDED_EXTERNALLY); |
| + // Ensure the destructor doesn't replace the reason. |
| + exit_reason_ref_ = nullptr; |
| + quit_closure_.Run(); |
| + } |
| + } |
| + |
| + static CGEventRef EscapeKeypressMonitor(CGEventTapProxy proxy, |
| + CGEventType type, |
| + CGEventRef event, |
| + void* refcon) { |
| + CocoaWindowMoveLoop* self = static_cast<CocoaWindowMoveLoop*>(refcon); |
| + (void)self; |
| + return event; |
| + } |
| + |
| + private: |
| + enum LoopExitReason { |
| + ENDED_EXTERNALLY, |
| + ESCAPE_PRESSED, |
| + MOUSE_UP, |
| + WINDOW_DESTROYED, |
| + }; |
| + |
| + BridgedNativeWidget* owner_; // Weak. Owns this. |
| + scoped_ptr<base::RunLoop> run_loop_; |
| + id monitor_ = nil; |
| + |
| + // Pointer to a stack variable holding the exit reason. |
| + LoopExitReason* exit_reason_ref_ = nullptr; |
| + base::Closure quit_closure_; |
| + |
| + // WeakPtrFactory for event monitor safety. |
| + base::WeakPtrFactory<CocoaWindowMoveLoop> weak_factory_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(CocoaWindowMoveLoop); |
| +}; |
| + |
| // static |
| gfx::Size BridgedNativeWidget::GetWindowSizeForClientSize( |
| NSWindow* window, |
| @@ -569,6 +772,49 @@ bool BridgedNativeWidget::HasCapture() { |
| return mouse_capture_ && mouse_capture_->IsActive(); |
| } |
| +Widget::MoveLoopResult BridgedNativeWidget::RunMoveLoop( |
| + const gfx::Vector2d& drag_offset) { |
| + DCHECK(!HasCapture()); |
| + DCHECK(!window_move_loop_); |
| + |
| + // First, position the window in the right place. The point |drag_offset| |
| + // away from the top-left corner needs to be positioned under the mouse. |
| + // TODO(tapted): Figure out why the toolkit-views drag controller doesn't get |
| + // this right when it first initializes the Widget. |
| + NSPoint mouse_in_screen = gfx::ScreenPointToNSPoint( |
| + gfx::Screen::GetScreen()->GetCursorScreenPoint()); |
| + NSRect frame = [window_ frame]; |
| + frame.origin.x = mouse_in_screen.x - drag_offset.x(); |
| + frame.origin.y = mouse_in_screen.y - NSHeight(frame) + drag_offset.y(); |
| + |
| + // After setting the frame to correct the initial offset, the drag controller |
| + // may immediately want to quit when it's notified of the new bounds. So the |
| + // MoveLoop must be set up before the call to setFrame. |
| + window_move_loop_.reset(new CocoaWindowMoveLoop(this)); |
| + |
| + // Animating may provide a less janky UX, but something custom would be |
| + // required so that it follows updates to the mouse position. |
| + [window_ setFrame:frame display:YES animate:NO]; |
| + |
| + // Setting the frame will call OnWidgetBoundsChanged(), which could result in |
| + // a call to EndMoveLoop(). |
| + if (!window_move_loop_) |
| + return Widget::MOVE_LOOP_SUCCESSFUL; |
| + |
| + return window_move_loop_->Run(); |
| + |
| + // |this| may be destroyed during the RunLoop, causing it to exit early. |
| + // Even if that doesn't happen, CocoaWindowMoveLoop will clean itself up by |
| + // calling EndMoveLoop(). So window_move_loop_ will always be null before the |
| + // function returns. But don't DCHECK since |this| might not be valid. |
| +} |
| + |
| +void BridgedNativeWidget::EndMoveLoop() { |
| + DCHECK(window_move_loop_); |
| + window_move_loop_->End(); |
| + window_move_loop_.reset(); |
| +} |
| + |
| void BridgedNativeWidget::SetNativeWindowProperty(const char* name, |
| void* value) { |
| NSString* key = [NSString stringWithUTF8String:name]; |
| @@ -590,6 +836,7 @@ void BridgedNativeWidget::SetCursor(NSCursor* cursor) { |
| } |
| void BridgedNativeWidget::OnWindowWillClose() { |
| + DCHECK(!drag_run_loop_); |
| if (parent_) { |
| parent_->RemoveChildWindow(this); |
| parent_ = nullptr; |
| @@ -692,6 +939,10 @@ void BridgedNativeWidget::OnSizeChanged() { |
| [bridged_view_ updateWindowMask]; |
| } |
| +void BridgedNativeWidget::OnPositionChanged() { |
| + native_widget_mac_->GetWidget()->OnNativeWidgetMove(); |
| +} |
| + |
| void BridgedNativeWidget::OnVisibilityChanged() { |
| OnVisibilityChangedTo([window_ isVisible]); |
| } |