| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ash/crostini/crostini_force_close_watcher.h" |
| |
| #include <memory> |
| |
| #include "base/functional/bind.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/ui/views/crostini/crostini_force_close_view.h" |
| #include "components/exo/shell_surface_base.h" |
| #include "ui/views/widget/widget.h" |
| |
| namespace crostini { |
| |
| namespace { |
| |
| constexpr base::TimeDelta kDefaultForceCloseDelay = base::Seconds(5); |
| } |
| |
| ForceCloseWatcher::Delegate::~Delegate() = default; |
| |
| void ForceCloseWatcher::Watch(std::unique_ptr<Delegate> delegate) { |
| new ForceCloseWatcher(std::move(delegate)); |
| } |
| |
| void ForceCloseWatcher::OnWidgetDestroying(views::Widget* widget) { |
| delegate_->Hide(); |
| widget->RemoveObserver(this); |
| delete this; |
| } |
| |
| void ForceCloseWatcher::OnCloseRequested() { |
| if (!show_dialog_timer_.has_value()) { |
| show_dialog_timer_ = base::ElapsedTimer(); |
| return; |
| } |
| |
| if (show_dialog_timer_->Elapsed() < force_close_delay_) { |
| return; |
| } |
| |
| delegate_->Prompt(); |
| } |
| |
| void ForceCloseWatcher::OverrideDelayForTesting(base::TimeDelta delay) { |
| force_close_delay_ = delay; |
| } |
| |
| ForceCloseWatcher::ForceCloseWatcher(std::unique_ptr<Delegate> delegate) |
| : delegate_(std::move(delegate)), |
| force_close_delay_(kDefaultForceCloseDelay) { |
| delegate_->GetClosableWidget()->AddObserver(this); |
| delegate_->Watched(this); |
| } |
| |
| ForceCloseWatcher::~ForceCloseWatcher() { |
| CHECK(!IsInObserverList()); |
| } |
| |
| ShellSurfaceForceCloseDelegate::ShellSurfaceForceCloseDelegate( |
| exo::ShellSurfaceBase* shell_surface, |
| std::string app_name) |
| : shell_surface_(shell_surface), |
| app_name_(std::move(app_name)), |
| weak_ptr_factory_(this) {} |
| |
| void ShellSurfaceForceCloseDelegate::ForceClose() { |
| // Post a task because the dialog needs to finish running its accept button |
| // handling code. If we CloseNow() here it will destroy the dialog's parent |
| // widget, destroying the dialog and causing a use-after-free. |
| // https://crbug.com/1215247 |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&ShellSurfaceForceCloseDelegate::ForceCloseNow, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void ShellSurfaceForceCloseDelegate::ForceCloseNow() { |
| // This must use CloseNow() and not Close() because ShellSurfaceBase widgets |
| // respond to Close() by asking the client app to close. In this case |
| // the app is not responding, so it won't respond to the Wayland protocol |
| // zxdg_toplevel_v6_send_close() message. |
| GetClosableWidget()->CloseNow(); |
| } |
| |
| ShellSurfaceForceCloseDelegate::~ShellSurfaceForceCloseDelegate() { |
| CHECK(!IsInObserverList()); |
| } |
| |
| views::Widget* ShellSurfaceForceCloseDelegate::GetClosableWidget() { |
| DCHECK(shell_surface_->GetWidget()); |
| return shell_surface_->GetWidget(); |
| } |
| |
| void ShellSurfaceForceCloseDelegate::Watched(ForceCloseWatcher* watcher) { |
| // It is safe to use base::Unretained here. The watcher's liefetime is tied to |
| // the widget associated with this shell surface, and the widget's |
| // pre_close_callback_ can not be called on a deleted widget, so the watcher |
| // will also be alive. |
| shell_surface_->set_pre_close_callback(base::BindRepeating( |
| &ForceCloseWatcher::OnCloseRequested, base::Unretained(watcher))); |
| } |
| |
| void ShellSurfaceForceCloseDelegate::Prompt() { |
| if (current_dialog_) { |
| Hide(); |
| } |
| |
| DCHECK(!current_dialog_); |
| current_dialog_ = ShowCrostiniForceCloseDialog( |
| app_name_, GetClosableWidget(), |
| base::BindOnce(&ShellSurfaceForceCloseDelegate::ForceClose, |
| weak_ptr_factory_.GetWeakPtr())); |
| current_dialog_->AddObserver(this); |
| } |
| |
| void ShellSurfaceForceCloseDelegate::Hide() { |
| if (current_dialog_) { |
| current_dialog_->RemoveObserver(this); |
| current_dialog_->Close(); |
| current_dialog_ = nullptr; |
| } |
| } |
| |
| void ShellSurfaceForceCloseDelegate::OnWidgetDestroying(views::Widget* widget) { |
| if (current_dialog_) { |
| current_dialog_->RemoveObserver(this); |
| current_dialog_ = nullptr; |
| } |
| } |
| |
| } // namespace crostini |