1// Copyright 2016 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "components/remote_cocoa/app_shim/window_move_loop.h" 6 7#include "base/debug/stack_trace.h" 8#include "base/run_loop.h" 9#include "base/strings/stringprintf.h" 10#include "components/crash/core/common/crash_key.h" 11#import "components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h" 12#include "ui/display/screen.h" 13#import "ui/gfx/mac/coordinate_conversion.h" 14 15// When event monitors process the events the full list of monitors is cached, 16// and if we unregister the event monitor that's at the end of the list while 17// processing the first monitor's handler -- the callback for the unregistered 18// monitor will still be called even though it's unregistered. This will result 19// in dereferencing an invalid pointer. 20// 21// WeakCocoaWindowMoveLoop is retained by the event monitor and stores weak 22// pointer for the CocoaWindowMoveLoop, so there will be no invalid memory 23// access. 24@interface WeakCocoaWindowMoveLoop : NSObject { 25 @private 26 base::WeakPtr<remote_cocoa::CocoaWindowMoveLoop> _weak; 27} 28@end 29 30@implementation WeakCocoaWindowMoveLoop 31- (instancetype)initWithWeakPtr: 32 (const base::WeakPtr<remote_cocoa::CocoaWindowMoveLoop>&)weak { 33 if ((self = [super init])) { 34 _weak = weak; 35 } 36 return self; 37} 38 39- (base::WeakPtr<remote_cocoa::CocoaWindowMoveLoop>&)weak { 40 return _weak; 41} 42@end 43 44namespace remote_cocoa { 45 46CocoaWindowMoveLoop::CocoaWindowMoveLoop(NativeWidgetNSWindowBridge* owner, 47 const NSPoint& initial_mouse_in_screen) 48 : owner_(owner), 49 initial_mouse_in_screen_(initial_mouse_in_screen), 50 weak_factory_(this) {} 51 52CocoaWindowMoveLoop::~CocoaWindowMoveLoop() { 53 // Record the address and stack to help catch https://crbug.com/876493. 54 static crash_reporter::CrashKeyString<19> address_key("move_loop_address"); 55 address_key.Set(base::StringPrintf("%p", this)); 56 57 static crash_reporter::CrashKeyString<1024> stack_key("move_loop_stack"); 58 crash_reporter::SetCrashKeyStringToStackTrace(&stack_key, 59 base::debug::StackTrace()); 60 // Handle the pathological case, where |this| is destroyed while running. 61 if (exit_reason_ref_) { 62 *exit_reason_ref_ = WINDOW_DESTROYED; 63 std::move(quit_closure_).Run(); 64 } 65 66 owner_ = nullptr; 67} 68 69bool CocoaWindowMoveLoop::Run() { 70 LoopExitReason exit_reason = ENDED_EXTERNALLY; 71 exit_reason_ref_ = &exit_reason; 72 NSWindow* window = owner_->ns_window(); 73 const NSRect initial_frame = [window frame]; 74 75 base::RunLoop run_loop; 76 quit_closure_ = run_loop.QuitClosure(); 77 78 // Will be retained by the monitor handler block. 79 WeakCocoaWindowMoveLoop* weak_cocoa_window_move_loop = 80 [[[WeakCocoaWindowMoveLoop alloc] 81 initWithWeakPtr:weak_factory_.GetWeakPtr()] autorelease]; 82 83 // Esc keypress is handled by EscapeTracker, which is installed by 84 // TabDragController. 85 NSEventMask mask = NSLeftMouseUpMask | NSLeftMouseDraggedMask; 86 auto handler = ^NSEvent*(NSEvent* event) { 87 // The docs say this always runs on the main thread, but if it didn't, 88 // it would explain https://crbug.com/876493, so let's make sure. 89 CHECK_EQ(CFRunLoopGetMain(), CFRunLoopGetCurrent()); 90 91 CocoaWindowMoveLoop* strong = [weak_cocoa_window_move_loop weak].get(); 92 if (!strong || !strong->exit_reason_ref_) { 93 // By this point CocoaWindowMoveLoop was deleted while processing this 94 // same event, and this event monitor was not unregistered in time. See 95 // the WeakCocoaWindowMoveLoop comment above. 96 // Continue processing the event. 97 return event; 98 } 99 100 if ([event type] == NSLeftMouseDragged) { 101 const NSPoint mouse_in_screen = [NSEvent mouseLocation]; 102 103 const NSRect ns_frame = NSOffsetRect( 104 initial_frame, mouse_in_screen.x - initial_mouse_in_screen_.x, 105 mouse_in_screen.y - initial_mouse_in_screen_.y); 106 [window setFrame:ns_frame display:NO animate:NO]; 107 108 return event; 109 } 110 111 DCHECK_EQ([event type], NSLeftMouseUp); 112 *strong->exit_reason_ref_ = MOUSE_UP; 113 std::move(strong->quit_closure_).Run(); 114 return event; // Process the MouseUp. 115 }; 116 id monitor = [NSEvent addLocalMonitorForEventsMatchingMask:mask 117 handler:handler]; 118 119 run_loop.Run(); 120 [NSEvent removeMonitor:monitor]; 121 122 if (exit_reason != WINDOW_DESTROYED && exit_reason != ENDED_EXTERNALLY) { 123 exit_reason_ref_ = nullptr; // Ensure End() doesn't replace the reason. 124 owner_->EndMoveLoop(); // Deletes |this|. 125 } 126 127 return exit_reason == MOUSE_UP; 128} 129 130void CocoaWindowMoveLoop::End() { 131 if (exit_reason_ref_) { 132 DCHECK_EQ(*exit_reason_ref_, ENDED_EXTERNALLY); 133 // Ensure the destructor doesn't replace the reason. 134 exit_reason_ref_ = nullptr; 135 std::move(quit_closure_).Run(); 136 } 137} 138 139} // namespace remote_cocoa 140