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