File: window_move_loop.mm

package info (click to toggle)
chromium 138.0.7204.183-1~deb12u1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm-proposed-updates
  • size: 6,080,960 kB
  • sloc: cpp: 34,937,079; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,954; asm: 946,768; xml: 739,971; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,811; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (203 lines) | stat: -rw-r--r-- 7,172 bytes parent folder | download | duplicates (7)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/remote_cocoa/app_shim/window_move_loop.h"

#include <map>
#include <memory>
#include <utility>
#include <vector>

#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#import "components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h"
#include "ui/display/screen.h"
#import "ui/gfx/mac/coordinate_conversion.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<remote_cocoa::CocoaWindowMoveLoop> _weak;
}
@end

@implementation WeakCocoaWindowMoveLoop
- (instancetype)initWithWeakPtr:
    (const base::WeakPtr<remote_cocoa::CocoaWindowMoveLoop>&)weak {
  if ((self = [super init])) {
    _weak = weak;
  }
  return self;
}

- (base::WeakPtr<remote_cocoa::CocoaWindowMoveLoop>&)weak {
  return _weak;
}
@end

namespace {

// This class addresses a macOS 14 issue where child windows don't follow
// the parent during tab dragging.
class ChildWindowMover {
 public:
  ChildWindowMover(NSWindow* window) : window_(window) {
    initial_parent_origin_ = gfx::Point(window.frame.origin);
    for (NSWindow* child in window.childWindows) {
      initial_origins_.emplace_back(child, child.frame.origin);
    }
  }

  // Moves child windows based on a parent origin offset relative to their
  // initial origins captured at the construction of this class.
  void MoveByOriginOffset() {
    if (!window_) {
      return;
    }

    gfx::Point parent_origin = gfx::Point(window_.frame.origin);
    gfx::Vector2d origin_offset(parent_origin.x() - initial_parent_origin_.x(),
                                parent_origin.y() - initial_parent_origin_.y());

    for (const auto& [child, initial_origin] : initial_origins_) {
      if (!child || child.parentWindow != window_) {
        continue;
      }

      gfx::Point expected_origin = initial_origin + origin_offset;
      // On macOS 14, child windows occasionally fail to follow their parent
      // during tab dragging. A workaround for this issue is to temporarily
      // remove the child window, set its frame origin, and then re-add it.
      [window_ removeChildWindow:child];
      [child
          setFrameOrigin:NSMakePoint(expected_origin.x(), expected_origin.y())];
      [window_ addChildWindow:child ordered:NSWindowAbove];
    }
  }

 private:
  NSWindow* __weak window_;
  std::vector<std::pair<NSWindow * __weak, gfx::Point>> initial_origins_;
  gfx::Point initial_parent_origin_;
};

}  // namespace

namespace remote_cocoa {

CocoaWindowMoveLoop::CocoaWindowMoveLoop(NativeWidgetNSWindowBridge* 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;
    std::move(quit_closure_).Run();
  }

  owner_ = nullptr;
}

bool CocoaWindowMoveLoop::Run() {
  LoopExitReason exit_reason = ENDED_EXTERNALLY;
  exit_reason_ref_ = &exit_reason;
  NSWindow* window = owner_->ns_window();
  const NSRect initial_frame = [window frame];
  __block ChildWindowMover child_window_mover(window);

  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()];

  __block BOOL has_moved = NO;
  screen_disabler_ = std::make_unique<gfx::ScopedCocoaDisableScreenUpdates>();

  // Esc keypress is handled by EscapeTracker, which is installed by
  // TabDragController.
  NSEventMask mask = NSEventMaskLeftMouseUp | NSEventMaskLeftMouseDragged |
                     NSEventMaskMouseMoved;
  auto handler = ^NSEvent*(NSEvent* event) {
    // The docs say this always runs on the main thread, but if it didn't,
    // it would explain https://crbug.com/876493, so let's make sure.
    CHECK(NSThread.isMainThread);

    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] == NSEventTypeLeftMouseDragged) {
      const NSPoint mouse_in_screen = [NSEvent mouseLocation];
      gfx::Vector2d mouse_offset(
          mouse_in_screen.x - initial_mouse_in_screen_.x,
          mouse_in_screen.y - initial_mouse_in_screen_.y);
      NSRect ns_frame =
          NSOffsetRect(initial_frame, mouse_offset.x(), mouse_offset.y());
      [window setFrame:ns_frame display:NO animate:NO];
      child_window_mover.MoveByOriginOffset();
      // `setFrame:...` may have destroyed `this`, so do the weak check again.
      bool is_valid = [weak_cocoa_window_move_loop weak].get() == strong;
      if (is_valid && !has_moved) {
        has_moved = YES;
        strong->screen_disabler_.reset();
      }

      return event;
    }

    // In theory, we shouldn't see any kind of NSEventTypeMouseMoved, but if we
    // see one and the left button isn't pressed, we know for a fact that we
    // missed a NSEventTypeLeftMouseUp.
    BOOL unexpectedMove = [event type] == NSEventTypeMouseMoved &&
                          ([NSEvent pressedMouseButtons] & 1) != 1;
    if (unexpectedMove || [event type] == NSEventTypeLeftMouseUp) {
      *strong->exit_reason_ref_ = MOUSE_UP;
      std::move(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;
}

void CocoaWindowMoveLoop::End() {
  screen_disabler_.reset();
  if (exit_reason_ref_) {
    DCHECK_EQ(*exit_reason_ref_, ENDED_EXTERNALLY);
    // Ensure the destructor doesn't replace the reason.
    exit_reason_ref_ = nullptr;
    std::move(quit_closure_).Run();
  }
}

}  // namespace remote_cocoa