| 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..747c3b94e5d64f58d93411a96c1a5355d7ba9b83 100644
|
| --- a/ui/views/cocoa/bridged_native_widget.mm
|
| +++ b/ui/views/cocoa/bridged_native_widget.mm
|
| @@ -8,10 +8,12 @@
|
| #include <stddef.h>
|
| #include <stdint.h>
|
|
|
| +#include "base/debug/stack_trace.h"
|
| #include "base/logging.h"
|
| #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 +37,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 +86,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,9 +185,15 @@ 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;
|
| + DLOG(INFO) << "Reposting event for window drag";
|
| CGEventPost(kCGSessionEventTap, [ns_event CGEvent]);
|
| return nil;
|
| }
|
| @@ -263,10 +263,188 @@ 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);
|
| +
|
| +}
|
| +
|
| namespace views {
|
|
|
| +class CocoaWindowMoveLoop {
|
| + public:
|
| + explicit CocoaWindowMoveLoop(BridgedNativeWidget* owner)
|
| + : owner_(owner),
|
| + run_loop_(new base::RunLoop),
|
| + quit_closure_(run_loop_->QuitClosure()) {
|
| + // AppKit continues to send mouse events to the window, but toolkit-views
|
| + // goes berserk if it gets them during RunMoveLoop().
|
| + [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);
|
| +
|
| + NSEventMask mask =
|
| + NSLeftMouseUpMask | NSKeyDownMask | NSLeftMouseDraggedMask;
|
| + monitor_ = [NSEvent addLocalMonitorForEventsMatchingMask:mask
|
| + handler:^NSEvent*(NSEvent* event) {
|
| + DLOG(INFO) << "Saw a thing: " << this;
|
| + if ([event type] == NSLeftMouseDragged) {
|
| + DLOG(INFO) << "drag";
|
| + // 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;
|
| + }
|
| + DLOG(INFO) << "Quitting due to event in " << this;
|
| + quit_closure_.Run();
|
| + if ([event type] == NSLeftMouseUp) {
|
| + DLOG(INFO) << "mouseup";
|
| + *exit_reason_ref_ = MOUSE_UP;
|
| + return event; // Process the MouseUp.
|
| + }
|
| + *exit_reason_ref_ = ESCAPE_PRESSED;
|
| + DLOG(INFO) << "key";
|
| + 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.
|
| + DLOG(INFO) << "Calling own EndMoveLoop in " << this;
|
| + 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() {
|
| + DLOG(INFO) << "In End() for " << this
|
| + << " reason_ref = " << exit_reason_ref_;
|
| + 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_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(CocoaWindowMoveLoop);
|
| +};
|
| +
|
| // static
|
| gfx::Size BridgedNativeWidget::GetWindowSizeForClientSize(
|
| NSWindow* window,
|
| @@ -471,6 +649,10 @@ void BridgedNativeWidget::SetRootView(views::View* view) {
|
|
|
| // If this is ever false, the compositor will need to be properly torn down
|
| // and replaced, pointing at the new view.
|
| + if (view && compositor_widget_) {
|
| + DLOG(INFO) << "Gonna die: view=" << view
|
| + << " compositor_widget=" << compositor_widget_;
|
| + }
|
| DCHECK(!view || !compositor_widget_);
|
|
|
| [bridged_view_ clearView];
|
| @@ -546,6 +728,8 @@ void BridgedNativeWidget::SetVisibilityState(WindowVisibilityState new_state) {
|
| }
|
|
|
| void BridgedNativeWidget::AcquireCapture() {
|
| + base::debug::StackTrace().Print();
|
| + DLOG(INFO) << "AcquireCapture: " << this;
|
| DCHECK(!HasCapture());
|
| if (!window_visible_)
|
| return; // Capture on hidden windows is disallowed.
|
| @@ -562,13 +746,72 @@ void BridgedNativeWidget::AcquireCapture() {
|
| }
|
|
|
| void BridgedNativeWidget::ReleaseCapture() {
|
| + base::debug::StackTrace().Print();
|
| + DLOG(INFO) << "ReleaseCapture: " << this << " drag_loop=" << drag_run_loop_;
|
| mouse_capture_.reset();
|
| + //if (drag_run_loop_) {
|
| + // drag_run_loop_->Quit();
|
| + // drag_run_loop_ = nullptr;
|
| + //}
|
| }
|
|
|
| bool BridgedNativeWidget::HasCapture() {
|
| return mouse_capture_ && mouse_capture_->IsActive();
|
| }
|
|
|
| +Widget::MoveLoopResult BridgedNativeWidget::RunMoveLoop(
|
| + const gfx::Vector2d& drag_offset) {
|
| + base::debug::StackTrace().Print();
|
| + DLOG(INFO) << "RunMoveLoop(): " << this << " drag_loop=" << drag_run_loop_;
|
| +
|
| + 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();
|
| + NSLog(@"mouse, frame, offset -> frame: %@, %@, %s, %@",
|
| + NSStringFromRect([window_ frame]),
|
| + NSStringFromPoint(mouse_in_screen),
|
| + drag_offset.ToString().c_str(),
|
| + NSStringFromRect(frame));
|
| +
|
| + // 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_);
|
| + DLOG(INFO) << "Quitting due to EndMoveLoop for " << window_move_loop_.get();
|
| + window_move_loop_->End();
|
| + DLOG(INFO) << "Resetting: " << window_move_loop_.get();
|
| + window_move_loop_.reset();
|
| +}
|
| +
|
| void BridgedNativeWidget::SetNativeWindowProperty(const char* name,
|
| void* value) {
|
| NSString* key = [NSString stringWithUTF8String:name];
|
| @@ -590,6 +833,7 @@ void BridgedNativeWidget::SetCursor(NSCursor* cursor) {
|
| }
|
|
|
| void BridgedNativeWidget::OnWindowWillClose() {
|
| + DCHECK(!drag_run_loop_);
|
| if (parent_) {
|
| parent_->RemoveChildWindow(this);
|
| parent_ = nullptr;
|
| @@ -692,6 +936,10 @@ void BridgedNativeWidget::OnSizeChanged() {
|
| [bridged_view_ updateWindowMask];
|
| }
|
|
|
| +void BridgedNativeWidget::OnPositionChanged() {
|
| + native_widget_mac_->GetWidget()->OnNativeWidgetMove();
|
| +}
|
| +
|
| void BridgedNativeWidget::OnVisibilityChanged() {
|
| OnVisibilityChangedTo([window_ isVisible]);
|
| }
|
|
|