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
|
// 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
|