| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/accelerated_widget_mac/ca_transaction_observer.h" |
| |
| #include "base/no_destructor.h" |
| #include "base/ranges/algorithm.h" |
| #include "base/time/default_tick_clock.h" |
| #include "base/trace_event/trace_event.h" |
| #include "ui/accelerated_widget_mac/window_resize_helper_mac.h" |
| |
| #import <AppKit/AppKit.h> |
| #import <CoreFoundation/CoreFoundation.h> |
| #import <QuartzCore/QuartzCore.h> |
| |
| typedef NS_ENUM(unsigned int, CATransactionPhase) { |
| kCATransactionPhasePreLayout, |
| kCATransactionPhasePreCommit, |
| kCATransactionPhasePostCommit, |
| }; |
| |
| @interface CATransaction () |
| + (void)addCommitHandler:(void (^)(void))block |
| forPhase:(CATransactionPhase)phase; |
| @end |
| |
| namespace ui { |
| |
| namespace { |
| NSString* kRunLoopMode = @"Chrome CATransactionCoordinator commit handler"; |
| constexpr auto kPostCommitTimeout = base::Milliseconds(50); |
| } // namespace |
| |
| CATransactionCoordinator& CATransactionCoordinator::Get() { |
| static base::NoDestructor<CATransactionCoordinator> instance; |
| return *instance; |
| } |
| |
| void CATransactionCoordinator::SynchronizeImpl() { |
| static bool registeredRunLoopMode = false; |
| if (!registeredRunLoopMode) { |
| CFRunLoopAddCommonMode(CFRunLoopGetCurrent(), |
| static_cast<CFStringRef>(kRunLoopMode)); |
| registeredRunLoopMode = true; |
| } |
| if (active_) |
| return; |
| active_ = true; |
| |
| for (auto& observer : post_commit_observers_) |
| observer->OnActivateForTransaction(); |
| |
| [CATransaction addCommitHandler:^{ |
| PreCommitHandler(); |
| } |
| forPhase:kCATransactionPhasePreCommit]; |
| |
| [CATransaction addCommitHandler:^{ |
| PostCommitHandler(); |
| } |
| forPhase:kCATransactionPhasePostCommit]; |
| } |
| |
| void CATransactionCoordinator::PreCommitHandler() { |
| TRACE_EVENT0("ui", "CATransactionCoordinator: pre-commit handler"); |
| auto* clock = base::DefaultTickClock::GetInstance(); |
| const base::TimeTicks start_time = clock->NowTicks(); |
| while (true) { |
| bool continue_waiting = false; |
| base::TimeTicks deadline = start_time; |
| for (auto& observer : pre_commit_observers_) { |
| if (observer.ShouldWaitInPreCommit()) { |
| continue_waiting = true; |
| deadline = std::max(deadline, start_time + observer.PreCommitTimeout()); |
| } |
| } |
| if (!continue_waiting) |
| break; // success |
| |
| base::TimeDelta time_left = deadline - clock->NowTicks(); |
| if (time_left <= base::Seconds(0)) |
| break; // timeout |
| |
| ui::WindowResizeHelperMac::Get()->WaitForSingleTaskToRun(time_left); |
| } |
| } |
| |
| void CATransactionCoordinator::PostCommitHandler() { |
| TRACE_EVENT0("ui", "CATransactionCoordinator: post-commit handler"); |
| |
| for (auto& observer : post_commit_observers_) |
| observer->OnEnterPostCommit(); |
| |
| auto* clock = base::DefaultTickClock::GetInstance(); |
| const base::TimeTicks deadline = clock->NowTicks() + kPostCommitTimeout; |
| while (true) { |
| bool continue_waiting = base::ranges::any_of( |
| post_commit_observers_, &PostCommitObserver::ShouldWaitInPostCommit); |
| if (!continue_waiting) |
| break; // success |
| |
| base::TimeDelta time_left = deadline - clock->NowTicks(); |
| if (time_left <= base::Seconds(0)) |
| break; // timeout |
| |
| ui::WindowResizeHelperMac::Get()->WaitForSingleTaskToRun(time_left); |
| } |
| active_ = false; |
| } |
| |
| CATransactionCoordinator::CATransactionCoordinator() = default; |
| CATransactionCoordinator::~CATransactionCoordinator() = default; |
| |
| void CATransactionCoordinator::Synchronize() { |
| if (disabled_for_testing_) |
| return; |
| SynchronizeImpl(); |
| } |
| |
| void CATransactionCoordinator::AddPreCommitObserver( |
| PreCommitObserver* observer) { |
| pre_commit_observers_.AddObserver(observer); |
| } |
| |
| void CATransactionCoordinator::RemovePreCommitObserver( |
| PreCommitObserver* observer) { |
| pre_commit_observers_.RemoveObserver(observer); |
| } |
| |
| void CATransactionCoordinator::AddPostCommitObserver( |
| scoped_refptr<PostCommitObserver> observer) { |
| DCHECK(!post_commit_observers_.count(observer)); |
| post_commit_observers_.insert(std::move(observer)); |
| } |
| |
| void CATransactionCoordinator::RemovePostCommitObserver( |
| scoped_refptr<PostCommitObserver> observer) { |
| DCHECK(post_commit_observers_.count(observer)); |
| post_commit_observers_.erase(std::move(observer)); |
| } |
| |
| } // namespace ui |