native_widget_ns_window_fullscreen_controller.mm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. // Copyright 2022 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. #include "components/remote_cocoa/app_shim/native_widget_ns_window_fullscreen_controller.h"
  5. #include "base/callback_helpers.h"
  6. #include "base/logging.h"
  7. #include "base/threading/thread_task_runner_handle.h"
  8. #include "ui/base/cocoa/nswindow_test_util.h"
  9. namespace remote_cocoa {
  10. namespace {
  11. bool IsFakeForTesting() {
  12. return ui::NSWindowFakedForTesting::IsEnabled();
  13. }
  14. } // namespace
  15. NativeWidgetNSWindowFullscreenController::
  16. NativeWidgetNSWindowFullscreenController(Client* client)
  17. : client_(client) {}
  18. NativeWidgetNSWindowFullscreenController::
  19. ~NativeWidgetNSWindowFullscreenController() {}
  20. void NativeWidgetNSWindowFullscreenController::EnterFullscreen(
  21. int64_t target_display_id) {
  22. if (IsFakeForTesting()) {
  23. if (state_ == State::kWindowed) {
  24. state_ = State::kEnterFullscreenTransition;
  25. client_->FullscreenControllerTransitionStart(true);
  26. windowed_frame_ = client_->FullscreenControllerGetFrame();
  27. const gfx::Rect kFakeFullscreenRect(0, 0, 1024, 768);
  28. client_->FullscreenControllerSetFrame(kFakeFullscreenRect,
  29. /*animate=*/false,
  30. base::DoNothing());
  31. state_ = State::kFullscreen;
  32. client_->FullscreenControllerTransitionComplete(true);
  33. }
  34. return;
  35. }
  36. // Early-out for no-ops.
  37. if (state_ == State::kFullscreen) {
  38. if (target_display_id == display::kInvalidDisplayId ||
  39. target_display_id == client_->FullscreenControllerGetDisplayId()) {
  40. return;
  41. }
  42. }
  43. // If we are starting a new transition, then notify `client_`.
  44. if (!IsInFullscreenTransition()) {
  45. if (!windowed_frame_)
  46. windowed_frame_ = client_->FullscreenControllerGetFrame();
  47. client_->FullscreenControllerTransitionStart(true);
  48. }
  49. pending_state_ = PendingState();
  50. pending_state_->is_fullscreen = true;
  51. pending_state_->display_id = target_display_id;
  52. HandlePendingState();
  53. DCHECK(IsInFullscreenTransition());
  54. }
  55. void NativeWidgetNSWindowFullscreenController::ExitFullscreen() {
  56. if (IsFakeForTesting()) {
  57. if (state_ == State::kFullscreen) {
  58. state_ = State::kExitFullscreenTransition;
  59. client_->FullscreenControllerTransitionStart(false);
  60. client_->FullscreenControllerSetFrame(windowed_frame_.value(),
  61. /*animate=*/false,
  62. base::DoNothing());
  63. state_ = State::kWindowed;
  64. client_->FullscreenControllerTransitionComplete(false);
  65. }
  66. return;
  67. }
  68. // Early-out for no-ops.
  69. if (state_ == State::kWindowed)
  70. return;
  71. // If we are starting a new transition, then notify `client_`.
  72. if (!IsInFullscreenTransition())
  73. client_->FullscreenControllerTransitionStart(false);
  74. pending_state_ = PendingState();
  75. pending_state_->is_fullscreen = false;
  76. HandlePendingState();
  77. DCHECK(IsInFullscreenTransition());
  78. }
  79. void NativeWidgetNSWindowFullscreenController::
  80. MoveToTargetDisplayThenToggleFullscreen(int64_t target_display_id) {
  81. DCHECK_EQ(state_, State::kWindowedMovingToFullscreenTarget);
  82. gfx::Rect display_frame =
  83. client_->FullscreenControllerGetFrameForDisplay(target_display_id);
  84. if (!display_frame.IsEmpty()) {
  85. restore_windowed_frame_ = true;
  86. SetStateAndCancelPostedTasks(State::kEnterFullscreenTransition);
  87. client_->FullscreenControllerSetFrame(
  88. display_frame, /*animate=*/true,
  89. base::BindOnce(
  90. &NativeWidgetNSWindowFullscreenController::ToggleFullscreen,
  91. weak_factory_.GetWeakPtr()));
  92. }
  93. }
  94. void NativeWidgetNSWindowFullscreenController::RestoreWindowedFrame() {
  95. DCHECK_EQ(state_, State::kWindowedRestoringOriginalFrame);
  96. DCHECK(restore_windowed_frame_);
  97. DCHECK(windowed_frame_);
  98. client_->FullscreenControllerSetFrame(windowed_frame_.value(),
  99. /*animate=*/true, base::DoNothing());
  100. restore_windowed_frame_ = false;
  101. windowed_frame_.reset();
  102. // TODO(https://crbug.com/1302857): Consider not transitioning the state
  103. // until `animation_time` has elapsed, and the window has been restored
  104. // to its original position.
  105. SetStateAndCancelPostedTasks(State::kWindowed);
  106. HandlePendingState();
  107. if (!IsInFullscreenTransition()) {
  108. client_->FullscreenControllerTransitionComplete(
  109. /*target_fullscreen_state=*/false);
  110. }
  111. }
  112. void NativeWidgetNSWindowFullscreenController::ToggleFullscreen() {
  113. // Note that OnWindowWillEnterFullscreen or OnWindowWillExitFullscreen will
  114. // be called within the below call.
  115. client_->FullscreenControllerToggleFullscreen();
  116. }
  117. bool NativeWidgetNSWindowFullscreenController::CanResize() const {
  118. // Don't modify the size constraints or fullscreen collection behavior while
  119. // in fullscreen or during a transition. OnFullscreenTransitionComplete will
  120. // reset these after leaving fullscreen.
  121. return state_ == State::kWindowed;
  122. }
  123. void NativeWidgetNSWindowFullscreenController::SetStateAndCancelPostedTasks(
  124. State new_state) {
  125. weak_factory_.InvalidateWeakPtrs();
  126. state_ = new_state;
  127. }
  128. void NativeWidgetNSWindowFullscreenController::OnWindowWantsToClose() {
  129. if (state_ == State::kEnterFullscreenTransition ||
  130. state_ == State::kExitFullscreenTransition) {
  131. has_deferred_window_close_ = true;
  132. }
  133. }
  134. void NativeWidgetNSWindowFullscreenController::OnWindowWillClose() {
  135. // If a window closes while in a fullscreen transition, then the window will
  136. // hang in a zombie-like state.
  137. // https://crbug.com/945237
  138. if (state_ != State::kWindowed && state_ != State::kFullscreen) {
  139. DLOG(ERROR) << "-[NSWindow close] while in fullscreen transition will "
  140. "trigger zombie windows.";
  141. }
  142. }
  143. void NativeWidgetNSWindowFullscreenController::OnWindowWillEnterFullscreen() {
  144. // If we are starting a new transition, then notify `client_`.
  145. if (!IsInFullscreenTransition()) {
  146. if (!windowed_frame_)
  147. windowed_frame_ = client_->FullscreenControllerGetFrame();
  148. client_->FullscreenControllerTransitionStart(true);
  149. }
  150. SetStateAndCancelPostedTasks(State::kEnterFullscreenTransition);
  151. DCHECK(IsInFullscreenTransition());
  152. }
  153. void NativeWidgetNSWindowFullscreenController::OnWindowDidEnterFullscreen() {
  154. if (HandleDeferredClose())
  155. return;
  156. if (state_ == State::kExitFullscreenTransition) {
  157. // If transitioning out of fullscreen failed, then just remain in
  158. // fullscreen. Note that `pending_state_` could have been left present for a
  159. // fullscreen-to-fullscreen-on-another-display transition. If it looks like
  160. // we are in that situation, reset `pending_state_`.
  161. if (pending_state_ && pending_state_->is_fullscreen)
  162. pending_state_.reset();
  163. }
  164. SetStateAndCancelPostedTasks(State::kFullscreen);
  165. HandlePendingState();
  166. if (!IsInFullscreenTransition()) {
  167. client_->FullscreenControllerTransitionComplete(
  168. /*target_fullscreen_state=*/true);
  169. }
  170. }
  171. void NativeWidgetNSWindowFullscreenController::OnWindowWillExitFullscreen() {
  172. // If we are starting a new transition, then notify `client_`.
  173. if (!IsInFullscreenTransition())
  174. client_->FullscreenControllerTransitionStart(false);
  175. SetStateAndCancelPostedTasks(State::kExitFullscreenTransition);
  176. DCHECK(IsInFullscreenTransition());
  177. }
  178. void NativeWidgetNSWindowFullscreenController::OnWindowDidExitFullscreen() {
  179. if (HandleDeferredClose())
  180. return;
  181. SetStateAndCancelPostedTasks(State::kWindowed);
  182. HandlePendingState();
  183. if (!IsInFullscreenTransition()) {
  184. client_->FullscreenControllerTransitionComplete(
  185. /*actual_fullscreen_state=*/false);
  186. }
  187. }
  188. void NativeWidgetNSWindowFullscreenController::HandlePendingState() {
  189. // If in kWindowed or kFullscreen, then consume `pending_state_`.
  190. switch (state_) {
  191. case State::kClosed:
  192. pending_state_.reset();
  193. return;
  194. case State::kWindowed:
  195. if (pending_state_ && pending_state_->is_fullscreen) {
  196. if (pending_state_->display_id != display::kInvalidDisplayId) {
  197. // Handle entering fullscreen on a specified display (note that this
  198. // applies to entering fullscreen on the default display, if specified
  199. // explicitly).
  200. SetStateAndCancelPostedTasks(
  201. State::kWindowedMovingToFullscreenTarget);
  202. base::ThreadTaskRunnerHandle::Get()->PostTask(
  203. FROM_HERE,
  204. base::BindOnce(&NativeWidgetNSWindowFullscreenController::
  205. MoveToTargetDisplayThenToggleFullscreen,
  206. weak_factory_.GetWeakPtr(),
  207. pending_state_->display_id));
  208. } else {
  209. // Handle entering fullscreen on the default display.
  210. SetStateAndCancelPostedTasks(State::kEnterFullscreenTransition);
  211. base::ThreadTaskRunnerHandle::Get()->PostTask(
  212. FROM_HERE,
  213. base::BindOnce(
  214. &NativeWidgetNSWindowFullscreenController::ToggleFullscreen,
  215. weak_factory_.GetWeakPtr()));
  216. }
  217. } else if (restore_windowed_frame_) {
  218. // Handle returning to the kWindowed state after having been fullscreen
  219. // and having called setFrame during some transition. It is necessary
  220. // to restore the original frame prior to having entered fullscreen.
  221. SetStateAndCancelPostedTasks(State::kWindowedRestoringOriginalFrame);
  222. base::ThreadTaskRunnerHandle::Get()->PostTask(
  223. FROM_HERE,
  224. base::BindOnce(
  225. &NativeWidgetNSWindowFullscreenController::RestoreWindowedFrame,
  226. weak_factory_.GetWeakPtr()));
  227. } else {
  228. // Handle returning to the kWindowed state without having called
  229. // setFrame.
  230. windowed_frame_.reset();
  231. }
  232. // Always reset `pending_state_` when handling kWindowed state.
  233. pending_state_.reset();
  234. return;
  235. case State::kFullscreen:
  236. if (pending_state_) {
  237. if (pending_state_->is_fullscreen) {
  238. // If `pending_state_` is a no-op, then reset it.
  239. if (pending_state_->display_id == display::kInvalidDisplayId ||
  240. pending_state_->display_id ==
  241. client_->FullscreenControllerGetDisplayId()) {
  242. pending_state_.reset();
  243. return;
  244. }
  245. // Leave `pending_state_` in place. It will be consumed when we
  246. // come through here again via OnWindowDidExitFullscreen (or
  247. // via OnWindowDidEnterFullscreen, if we fail to exit fullscreen).
  248. } else {
  249. pending_state_.reset();
  250. }
  251. SetStateAndCancelPostedTasks(State::kExitFullscreenTransition);
  252. base::ThreadTaskRunnerHandle::Get()->PostTask(
  253. FROM_HERE,
  254. base::BindOnce(
  255. &NativeWidgetNSWindowFullscreenController::ToggleFullscreen,
  256. weak_factory_.GetWeakPtr()));
  257. }
  258. return;
  259. default:
  260. // Leave `pending_state_` unchanged. It will be re-examined when our
  261. // transition completes.
  262. break;
  263. }
  264. }
  265. bool NativeWidgetNSWindowFullscreenController::HandleDeferredClose() {
  266. CHECK_NE(state_, State::kClosed);
  267. if (has_deferred_window_close_) {
  268. SetStateAndCancelPostedTasks(State::kClosed);
  269. // Note that `this` may be deleted by the below call.
  270. client_->FullscreenControllerCloseWindow();
  271. return true;
  272. }
  273. return false;
  274. }
  275. bool NativeWidgetNSWindowFullscreenController::GetTargetFullscreenState()
  276. const {
  277. if (pending_state_)
  278. return pending_state_->is_fullscreen;
  279. switch (state_) {
  280. case State::kWindowed:
  281. case State::kWindowedRestoringOriginalFrame:
  282. case State::kExitFullscreenTransition:
  283. case State::kClosed:
  284. return false;
  285. case State::kWindowedMovingToFullscreenTarget:
  286. case State::kEnterFullscreenTransition:
  287. case State::kFullscreen:
  288. return true;
  289. }
  290. }
  291. bool NativeWidgetNSWindowFullscreenController::IsInFullscreenTransition()
  292. const {
  293. switch (state_) {
  294. case State::kWindowed:
  295. case State::kFullscreen:
  296. case State::kClosed:
  297. return false;
  298. default:
  299. return true;
  300. }
  301. }
  302. } // namespace remote_cocoa