Index: ui/views/cocoa/cocoa_window_move_loop.mm |
diff --git a/ui/views/cocoa/cocoa_window_move_loop.mm b/ui/views/cocoa/cocoa_window_move_loop.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..5a69157b5d7d851dea17bdd8f18ca5bd97db0f58 |
--- /dev/null |
+++ b/ui/views/cocoa/cocoa_window_move_loop.mm |
@@ -0,0 +1,127 @@ |
+// Copyright 2016 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. |
+ |
+#include "ui/views/cocoa/cocoa_window_move_loop.h" |
+ |
+#include "base/run_loop.h" |
+#include "ui/display/screen.h" |
+#import "ui/gfx/mac/coordinate_conversion.mm" |
+#import "ui/views/cocoa/bridged_native_widget.h" |
+ |
+// When event monitors process the events the full list of monitors is cached, |
+// and if we unregister the event monitor that's at the end of the list while |
+// processing the first monitor's handler -- the callback for the unregistered |
+// monitor will still be called even though it's unregistered. This will result |
+// in dereferencing an invalid pointer. |
+// |
+// WeakCocoaWindowMoveLoop is retained by the event monitor and stores weak |
+// pointer for the CocoaWindowMoveLoop, so there will be no invalid memory |
+// access. |
+@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 { |
+ |
+CocoaWindowMoveLoop::CocoaWindowMoveLoop( |
+ BridgedNativeWidget* owner, |
+ const NSPoint& initial_mouse_in_screen) |
+ : owner_(owner), |
+ initial_mouse_in_screen_(initial_mouse_in_screen), |
+ weak_factory_(this) { |
+} |
+ |
+CocoaWindowMoveLoop::~CocoaWindowMoveLoop() { |
+ // Handle the pathological case, where |this| is destroyed while running. |
+ if (exit_reason_ref_) { |
+ *exit_reason_ref_ = WINDOW_DESTROYED; |
+ quit_closure_.Run(); |
+ } |
+ |
+ owner_ = nullptr; |
+} |
+ |
+Widget::MoveLoopResult CocoaWindowMoveLoop::Run() { |
+ LoopExitReason exit_reason = ENDED_EXTERNALLY; |
+ exit_reason_ref_ = &exit_reason; |
+ NSWindow* window = owner_->ns_window(); |
+ const NSRect initial_frame = [window frame]; |
+ |
+ base::RunLoop run_loop; |
+ quit_closure_ = run_loop.QuitClosure(); |
+ |
+ // Will be retained by the monitor handler block. |
+ WeakCocoaWindowMoveLoop* weak_cocoa_window_move_loop = |
+ [[[WeakCocoaWindowMoveLoop alloc] |
+ initWithWeakPtr:weak_factory_.GetWeakPtr()] autorelease]; |
+ |
+ // Esc keypress is handled by EscapeTracker, which is installed by |
+ // TabDragController. |
+ NSEventMask mask = NSLeftMouseUpMask | NSLeftMouseDraggedMask; |
+ auto handler = ^NSEvent*(NSEvent* event) { |
+ CocoaWindowMoveLoop* strong = [weak_cocoa_window_move_loop weak].get(); |
+ if (!strong || !strong->exit_reason_ref_) { |
+ // By this point CocoaWindowMoveLoop was deleted while processing this |
+ // same event, and this event monitor was not unregistered in time. See |
+ // the WeakCocoaWindowMoveLoop comment above. |
+ // Continue processing the event. |
+ return event; |
+ } |
+ |
+ if ([event type] == NSLeftMouseDragged) { |
+ const NSPoint mouse_in_screen = [NSEvent mouseLocation]; |
+ |
+ const NSRect ns_frame = NSOffsetRect( |
+ initial_frame, mouse_in_screen.x - initial_mouse_in_screen_.x, |
+ mouse_in_screen.y - initial_mouse_in_screen_.y); |
+ [window setFrame:ns_frame display:NO animate:NO]; |
+ |
+ return event; |
+ } |
+ |
+ DCHECK_EQ([event type], NSLeftMouseUp); |
+ *strong->exit_reason_ref_ = MOUSE_UP; |
+ strong->quit_closure_.Run(); |
+ return event; // Process the MouseUp. |
+ }; |
+ id monitor = |
+ [NSEvent addLocalMonitorForEventsMatchingMask:mask handler:handler]; |
+ |
+ run_loop.Run(); |
+ [NSEvent removeMonitor:monitor]; |
+ |
+ if (exit_reason != WINDOW_DESTROYED && exit_reason != ENDED_EXTERNALLY) { |
+ exit_reason_ref_ = nullptr; // Ensure End() doesn't replace the reason. |
+ owner_->EndMoveLoop(); // Deletes |this|. |
+ } |
+ |
+ return exit_reason != MOUSE_UP ? Widget::MOVE_LOOP_CANCELED |
+ : Widget::MOVE_LOOP_SUCCESSFUL; |
+} |
+ |
+void CocoaWindowMoveLoop::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(); |
+ } |
+} |
+ |
+} // namespace views |