Add warn before quitting experiment on Windows and Linux
This CL adds a warn-before-quitting flag. When enabled, pressing Ctrl+Shift+Q
will show a bubble asking to continue holding the shortcut to quit. Pressing
the shortcut a second time while the bubble is showing will also confirm the
quit.
R=sky
BUG=243164
Change-Id: I97c4dc37cb5107fde975a4162f349e1ea5337b5d
Reviewed-on: https://chromium-review.googlesource.com/1031097
Reviewed-by: Scott Violet <sky@chromium.org>
Commit-Queue: Thomas Anderson <thomasanderson@chromium.org>
Cr-Commit-Position: refs/heads/master@{#555909}
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index f583e30..537eff0d 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -2447,6 +2447,11 @@
flag_descriptions::kEnableInputImeApiDescription, kOsWin | kOsLinux,
ENABLE_DISABLE_VALUE_TYPE(switches::kEnableInputImeAPI,
switches::kDisableInputImeAPI)},
+#if !defined(OS_CHROMEOS)
+ {"warn-before-quitting", flag_descriptions::kWarnBeforeQuittingFlagName,
+ flag_descriptions::kWarnBeforeQuittingFlagDescription, kOsWin | kOsLinux,
+ FEATURE_VALUE_TYPE(features::kWarnBeforeQuitting)},
+#endif // OS_CHROMEOS
#endif // OS_WIN || OS_LINUX
{"enable-origin-trials", flag_descriptions::kOriginTrialsName,
flag_descriptions::kOriginTrialsDescription, kOsAll,
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 11b40d03..4922451 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -3026,6 +3026,14 @@
const char kEnableInputImeApiDescription[] =
"Enable the use of chrome.input.ime API.";
+#if !defined(OS_CHROMEOS)
+
+const char kWarnBeforeQuittingFlagName[] = "Warn Before Quitting";
+const char kWarnBeforeQuittingFlagDescription[] =
+ "Confirm to quit by either holding the quit shortcut or pressing it twice.";
+
+#endif // !defined(OS_CHROMEOS)
+
#endif // defined(OS_WIN) || defined(OS_LINUX)
#if defined(OS_WIN) || defined(OS_MACOSX)
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 9949be4..d69510e 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1851,6 +1851,13 @@
extern const char kEnableInputImeApiName[];
extern const char kEnableInputImeApiDescription[];
+#if !defined(OS_CHROMEOS)
+
+extern const char kWarnBeforeQuittingFlagName[];
+extern const char kWarnBeforeQuittingFlagDescription[];
+
+#endif // !defined(OS_CHROMEOS)
+
#endif // defined(OS_WIN) || defined(OS_LINUX)
#if defined(OS_WIN) || defined(OS_MACOSX)
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 391a9a1..e0ec670 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -2281,6 +2281,11 @@
sources += [
"input_method/input_method_engine.cc",
"input_method/input_method_engine.h",
+ "views/confirm_quit_bubble.cc",
+ "views/confirm_quit_bubble.h",
+ "views/confirm_quit_bubble.h",
+ "views/confirm_quit_bubble_controller.cc",
+ "views/confirm_quit_bubble_controller.h",
]
}
}
diff --git a/chrome/browser/ui/views/confirm_quit_bubble.cc b/chrome/browser/ui/views/confirm_quit_bubble.cc
new file mode 100644
index 0000000..610a92a72
--- /dev/null
+++ b/chrome/browser/ui/views/confirm_quit_bubble.cc
@@ -0,0 +1,76 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/confirm_quit_bubble.h"
+
+#include <utility>
+
+#include "base/message_loop/message_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/ui/views/subtle_notification_view.h"
+#include "ui/gfx/animation/animation.h"
+#include "ui/gfx/animation/slide_animation.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/strings/grit/ui_strings.h"
+#include "ui/views/border.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+namespace {
+
+constexpr base::TimeDelta kSlideDuration =
+ base::TimeDelta::FromMilliseconds(200);
+
+} // namespace
+
+ConfirmQuitBubble::ConfirmQuitBubble()
+ : animation_(std::make_unique<gfx::SlideAnimation>(this)) {
+ animation_->SetSlideDuration(kSlideDuration.InMilliseconds());
+}
+
+ConfirmQuitBubble::~ConfirmQuitBubble() {}
+
+void ConfirmQuitBubble::Show() {
+ animation_->Show();
+}
+
+void ConfirmQuitBubble::Hide() {
+ animation_->Hide();
+}
+
+void ConfirmQuitBubble::AnimationProgressed(const gfx::Animation* animation) {
+ float opacity = static_cast<float>(animation->CurrentValueBetween(0.0, 1.0));
+ if (opacity == 0) {
+ popup_.reset();
+ } else {
+ if (!popup_) {
+ SubtleNotificationView* view = new SubtleNotificationView();
+
+ popup_ = std::make_unique<views::Widget>();
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
+ params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
+ params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.accept_events = false;
+ params.keep_on_top = true;
+ popup_->Init(params);
+ popup_->SetContentsView(view);
+
+ // TODO(thomasanderson): Localize this string.
+ view->UpdateContent(
+ base::WideToUTF16(L"Hold |Ctrl|+|Shift|+|Q| to quit"));
+
+ gfx::Size size = view->GetPreferredSize();
+ view->SetSize(size);
+ popup_->CenterWindow(size);
+
+ popup_->ShowInactive();
+ }
+ popup_->SetOpacity(opacity);
+ }
+}
+
+void ConfirmQuitBubble::AnimationEnded(const gfx::Animation* animation) {
+ AnimationProgressed(animation);
+}
diff --git a/chrome/browser/ui/views/confirm_quit_bubble.h b/chrome/browser/ui/views/confirm_quit_bubble.h
new file mode 100644
index 0000000..b4de3d0
--- /dev/null
+++ b/chrome/browser/ui/views/confirm_quit_bubble.h
@@ -0,0 +1,47 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_CONFIRM_QUIT_BUBBLE_H_
+#define CHROME_BROWSER_UI_VIEWS_CONFIRM_QUIT_BUBBLE_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "chrome/browser/ui/views/confirm_quit_bubble_base.h"
+#include "ui/gfx/animation/animation_delegate.h"
+
+namespace gfx {
+class Animation;
+class SlideAnimation;
+} // namespace gfx
+
+namespace views {
+class Widget;
+} // namespace views
+
+// Manages showing and hiding a notification bubble that gives instructions to
+// continue holding the quit accelerator to quit.
+class ConfirmQuitBubble : public ConfirmQuitBubbleBase,
+ public gfx::AnimationDelegate {
+ public:
+ ConfirmQuitBubble();
+ ~ConfirmQuitBubble() override;
+
+ void Show() override;
+ void Hide() override;
+
+ private:
+ // gfx::AnimationDelegate:
+ void AnimationProgressed(const gfx::Animation* animation) override;
+ void AnimationEnded(const gfx::Animation* animation) override;
+
+ // Animation controlling showing/hiding of the bubble.
+ std::unique_ptr<gfx::SlideAnimation> const animation_;
+
+ std::unique_ptr<views::Widget> popup_;
+
+ DISALLOW_COPY_AND_ASSIGN(ConfirmQuitBubble);
+};
+
+#endif // CHROME_BROWSER_UI_VIEWS_CONFIRM_QUIT_BUBBLE_H_
diff --git a/chrome/browser/ui/views/confirm_quit_bubble_base.h b/chrome/browser/ui/views/confirm_quit_bubble_base.h
new file mode 100644
index 0000000..8bd73c2
--- /dev/null
+++ b/chrome/browser/ui/views/confirm_quit_bubble_base.h
@@ -0,0 +1,19 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_CONFIRM_QUIT_BUBBLE_BASE_H_
+#define CHROME_BROWSER_UI_VIEWS_CONFIRM_QUIT_BUBBLE_BASE_H_
+
+// Base class of ConfirmQuitBubble necessary for unit testing
+// ConfirmQuitBubbleController.
+class ConfirmQuitBubbleBase {
+ public:
+ ConfirmQuitBubbleBase() {}
+ virtual ~ConfirmQuitBubbleBase() {}
+
+ virtual void Show() = 0;
+ virtual void Hide() = 0;
+};
+
+#endif // CHROME_BROWSER_UI_VIEWS_CONFIRM_QUIT_BUBBLE_BASE_H_
diff --git a/chrome/browser/ui/views/confirm_quit_bubble_controller.cc b/chrome/browser/ui/views/confirm_quit_bubble_controller.cc
new file mode 100644
index 0000000..ab391c1
--- /dev/null
+++ b/chrome/browser/ui/views/confirm_quit_bubble_controller.cc
@@ -0,0 +1,94 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/confirm_quit_bubble_controller.h"
+
+#include <utility>
+
+#include "base/feature_list.h"
+#include "base/memory/singleton.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chrome/browser/ui/browser_commands.h"
+#include "chrome/browser/ui/views/confirm_quit_bubble.h"
+#include "ui/base/accelerators/accelerator.h"
+#include "ui/events/keycodes/keyboard_codes.h"
+
+namespace {
+
+constexpr ui::KeyboardCode kAcceleratorKeyCode = ui::VKEY_Q;
+constexpr int kAcceleratorModifiers = ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN;
+
+constexpr base::TimeDelta kShowDuration =
+ base::TimeDelta::FromMilliseconds(1500);
+
+} // namespace
+
+// static
+ConfirmQuitBubbleController* ConfirmQuitBubbleController::GetInstance() {
+ return base::Singleton<ConfirmQuitBubbleController>::get();
+}
+
+ConfirmQuitBubbleController::ConfirmQuitBubbleController()
+ : view_(std::make_unique<ConfirmQuitBubble>()),
+ hide_timer_(std::make_unique<base::OneShotTimer>()) {}
+
+ConfirmQuitBubbleController::ConfirmQuitBubbleController(
+ std::unique_ptr<ConfirmQuitBubbleBase> bubble,
+ std::unique_ptr<base::Timer> hide_timer)
+ : view_(std::move(bubble)), hide_timer_(std::move(hide_timer)) {}
+
+ConfirmQuitBubbleController::~ConfirmQuitBubbleController() {}
+
+bool ConfirmQuitBubbleController::HandleKeyboardEvent(
+ const ui::Accelerator& accelerator) {
+ if (accelerator.key_code() == kAcceleratorKeyCode &&
+ accelerator.modifiers() == kAcceleratorModifiers &&
+ accelerator.key_state() == ui::Accelerator::KeyState::PRESSED &&
+ !accelerator.IsRepeat()) {
+ if (!hide_timer_->IsRunning()) {
+ view_->Show();
+ released_key_ = false;
+ hide_timer_->Start(FROM_HERE, kShowDuration, this,
+ &ConfirmQuitBubbleController::OnTimerElapsed);
+ } else {
+ // The accelerator was pressed while the bubble was showing. Consider
+ // this a confirmation to quit.
+ view_->Hide();
+ hide_timer_->Stop();
+ Quit();
+ }
+ return true;
+ }
+ if (accelerator.key_code() == kAcceleratorKeyCode &&
+ accelerator.key_state() == ui::Accelerator::KeyState::RELEASED) {
+ released_key_ = true;
+ return true;
+ }
+ return false;
+}
+
+void ConfirmQuitBubbleController::OnTimerElapsed() {
+ view_->Hide();
+
+ if (!released_key_) {
+ // The accelerator was held down the entire time the bubble was showing.
+ Quit();
+ }
+}
+
+void ConfirmQuitBubbleController::Quit() {
+ if (quit_action_) {
+ std::move(quit_action_).Run();
+ } else {
+ // Delay quitting because doing so destroys objects that may be used when
+ // unwinding the stack.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ base::BindOnce(chrome::Exit));
+ }
+}
+
+void ConfirmQuitBubbleController::SetQuitActionForTest(
+ base::OnceClosure quit_action) {
+ quit_action_ = std::move(quit_action);
+}
diff --git a/chrome/browser/ui/views/confirm_quit_bubble_controller.h b/chrome/browser/ui/views/confirm_quit_bubble_controller.h
new file mode 100644
index 0000000..4357d38
--- /dev/null
+++ b/chrome/browser/ui/views/confirm_quit_bubble_controller.h
@@ -0,0 +1,62 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_CONFIRM_QUIT_BUBBLE_CONTROLLER_H_
+#define CHROME_BROWSER_UI_VIEWS_CONFIRM_QUIT_BUBBLE_CONTROLLER_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/timer/timer.h"
+
+class ConfirmQuitBubbleBase;
+
+namespace base {
+template <typename T>
+struct DefaultSingletonTraits;
+}
+
+namespace ui {
+class Accelerator;
+}
+
+// Manages showing and hiding the confirm-to-quit bubble. Requests Chrome to be
+// closed if the quit accelerator is held down or pressed twice in succession.
+class ConfirmQuitBubbleController {
+ public:
+ static ConfirmQuitBubbleController* GetInstance();
+
+ ~ConfirmQuitBubbleController();
+
+ // Returns true if the event was handled.
+ bool HandleKeyboardEvent(const ui::Accelerator& accelerator);
+
+ private:
+ friend struct base::DefaultSingletonTraits<ConfirmQuitBubbleController>;
+ friend class ConfirmQuitBubbleControllerTest;
+
+ ConfirmQuitBubbleController(std::unique_ptr<ConfirmQuitBubbleBase> bubble,
+ std::unique_ptr<base::Timer> hide_timer);
+
+ ConfirmQuitBubbleController();
+
+ void OnTimerElapsed();
+
+ void Quit();
+
+ void SetQuitActionForTest(base::OnceClosure quit_action);
+
+ std::unique_ptr<ConfirmQuitBubbleBase> const view_;
+
+ // Indicates if the accelerator was released while the timer was active.
+ bool released_key_ = false;
+
+ std::unique_ptr<base::Timer> hide_timer_;
+
+ base::OnceClosure quit_action_;
+
+ DISALLOW_COPY_AND_ASSIGN(ConfirmQuitBubbleController);
+};
+
+#endif // CHROME_BROWSER_UI_VIEWS_CONFIRM_QUIT_BUBBLE_CONTROLLER_H_
diff --git a/chrome/browser/ui/views/confirm_quit_bubble_controller_unittest.cc b/chrome/browser/ui/views/confirm_quit_bubble_controller_unittest.cc
new file mode 100644
index 0000000..e88cc753
--- /dev/null
+++ b/chrome/browser/ui/views/confirm_quit_bubble_controller_unittest.cc
@@ -0,0 +1,130 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/confirm_quit_bubble_controller.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/timer/mock_timer.h"
+#include "chrome/browser/ui/views/confirm_quit_bubble.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/accelerators/accelerator.h"
+#include "ui/events/keycodes/keyboard_codes.h"
+
+class TestConfirmQuitBubble : public ConfirmQuitBubbleBase {
+ public:
+ TestConfirmQuitBubble() {}
+ ~TestConfirmQuitBubble() override {}
+
+ void Show() override {}
+ void Hide() override {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestConfirmQuitBubble);
+};
+
+class ConfirmQuitBubbleControllerTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ std::unique_ptr<TestConfirmQuitBubble> bubble =
+ std::make_unique<TestConfirmQuitBubble>();
+ std::unique_ptr<base::MockTimer> timer =
+ std::make_unique<base::MockTimer>(false, false);
+ bubble_ = bubble.get();
+ timer_ = timer.get();
+ controller_.reset(
+ new ConfirmQuitBubbleController(std::move(bubble), std::move(timer)));
+
+ quit_called_ = false;
+ controller_->SetQuitActionForTest(base::BindOnce(
+ &ConfirmQuitBubbleControllerTest::OnQuit, base::Unretained(this)));
+ }
+
+ void TearDown() override { controller_.reset(); }
+
+ void OnQuit() { quit_called_ = true; }
+
+ void SendAccelerator(bool quit, bool press, bool repeat) {
+ ui::KeyboardCode key = quit ? ui::VKEY_Q : ui::VKEY_P;
+ int modifiers = ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN;
+ if (repeat)
+ modifiers |= ui::EF_IS_REPEAT;
+ ui::Accelerator::KeyState state = press
+ ? ui::Accelerator::KeyState::PRESSED
+ : ui::Accelerator::KeyState::RELEASED;
+ controller_->HandleKeyboardEvent(ui::Accelerator(key, modifiers, state));
+ }
+
+ void PressQuitAccelerator() { SendAccelerator(true, true, false); }
+
+ void ReleaseQuitAccelerator() { SendAccelerator(true, false, false); }
+
+ void RepeatQuitAccelerator() { SendAccelerator(true, true, true); }
+
+ void PressOtherAccelerator() { SendAccelerator(false, true, false); }
+
+ void ReleaseOtherAccelerator() { SendAccelerator(false, false, false); }
+
+ std::unique_ptr<ConfirmQuitBubbleController> controller_;
+
+ // Owned by |controller_|.
+ TestConfirmQuitBubble* bubble_;
+
+ // Owned by |controller_|.
+ base::MockTimer* timer_;
+
+ bool quit_called_ = false;
+};
+
+// Pressing and holding the shortcut should quit.
+TEST_F(ConfirmQuitBubbleControllerTest, PressAndHold) {
+ PressQuitAccelerator();
+ EXPECT_TRUE(timer_->IsRunning());
+ timer_->Fire();
+ EXPECT_TRUE(quit_called_);
+}
+
+// Pressing the shortcut twice should quit.
+TEST_F(ConfirmQuitBubbleControllerTest, DoublePress) {
+ PressQuitAccelerator();
+ ReleaseQuitAccelerator();
+ EXPECT_TRUE(timer_->IsRunning());
+ PressQuitAccelerator();
+ EXPECT_FALSE(timer_->IsRunning());
+ EXPECT_TRUE(quit_called_);
+}
+
+// Pressing the shortcut once should not quit.
+TEST_F(ConfirmQuitBubbleControllerTest, SinglePress) {
+ PressQuitAccelerator();
+ ReleaseQuitAccelerator();
+ EXPECT_TRUE(timer_->IsRunning());
+ timer_->Fire();
+ EXPECT_FALSE(quit_called_);
+}
+
+// Repeated presses should not be counted.
+TEST_F(ConfirmQuitBubbleControllerTest, RepeatedPresses) {
+ PressQuitAccelerator();
+ RepeatQuitAccelerator();
+ ReleaseQuitAccelerator();
+ EXPECT_TRUE(timer_->IsRunning());
+ timer_->Fire();
+ EXPECT_FALSE(quit_called_);
+}
+
+// Other keys shouldn't matter.
+TEST_F(ConfirmQuitBubbleControllerTest, OtherKeyPress) {
+ PressQuitAccelerator();
+ ReleaseQuitAccelerator();
+ PressOtherAccelerator();
+ ReleaseOtherAccelerator();
+ EXPECT_TRUE(timer_->IsRunning());
+ PressQuitAccelerator();
+ EXPECT_FALSE(timer_->IsRunning());
+ EXPECT_TRUE(quit_called_);
+}
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index d2394950..d5563e1 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -64,6 +64,7 @@
#include "chrome/browser/ui/views/autofill/save_card_icon_view.h"
#include "chrome/browser/ui/views/bookmarks/bookmark_bar_view.h"
#include "chrome/browser/ui/views/bookmarks/bookmark_bubble_view.h"
+#include "chrome/browser/ui/views/confirm_quit_bubble_controller.h"
#include "chrome/browser/ui/views/download/download_in_progress_dialog_view.h"
#include "chrome/browser/ui/views/download/download_shelf_view.h"
#include "chrome/browser/ui/views/exclusive_access_bubble_views.h"
@@ -95,6 +96,7 @@
#include "chrome/browser/ui/views/update_recommended_message_box.h"
#include "chrome/browser/ui/window_sizer/window_sizer.h"
#include "chrome/common/channel_info.h"
+#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/command.h"
#include "chrome/common/pref_names.h"
@@ -1323,6 +1325,14 @@
return content::KeyboardEventProcessingResult::NOT_HANDLED;
}
+#if defined(OS_WIN) || (defined(OS_LINUX) && !defined(OS_CHROMEOS))
+ if (base::FeatureList::IsEnabled(features::kWarnBeforeQuitting) &&
+ ConfirmQuitBubbleController::GetInstance()->HandleKeyboardEvent(
+ accelerator)) {
+ return content::KeyboardEventProcessingResult::HANDLED_WANTS_KEY_UP;
+ }
+#endif // defined(OS_WIN) || (defined(OS_LINUX) && !defined(OS_CHROMEOS))
+
#if defined(OS_CHROMEOS)
if (ash_util::IsAcceleratorDeprecated(accelerator)) {
return (event.GetType() == blink::WebInputEvent::kRawKeyDown)
@@ -2089,6 +2099,14 @@
// BrowserView, ui::AcceleratorTarget overrides:
bool BrowserView::AcceleratorPressed(const ui::Accelerator& accelerator) {
+#if defined(OS_WIN) || (defined(OS_LINUX) && !defined(OS_CHROMEOS))
+ if (base::FeatureList::IsEnabled(features::kWarnBeforeQuitting) &&
+ ConfirmQuitBubbleController::GetInstance()->HandleKeyboardEvent(
+ accelerator)) {
+ return true;
+ }
+#endif // defined(OS_WIN) || (defined(OS_LINUX) && !defined(OS_CHROMEOS))
+
int command_id;
// Though AcceleratorManager should not send unknown |accelerator| to us, it's
// still possible the command cannot be executed now.
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 8eb2170..9569d92 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -363,6 +363,11 @@
"AcknowledgeNtpOverrideOnDeactivate", base::FEATURE_DISABLED_BY_DEFAULT};
#endif
+#if defined(OS_WIN) || (defined(OS_LINUX) && !defined(OS_CHROMEOS))
+const base::Feature kWarnBeforeQuitting{"WarnBeforeQuitting",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+#endif
+
// The material redesign of the Incognito NTP.
const base::Feature kMaterialDesignIncognitoNTP{
"MaterialDesignIncognitoNTP",
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index 20b7000..42958c3 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -202,6 +202,10 @@
extern const base::Feature kAcknowledgeNtpOverrideOnDeactivate;
#endif
+#if defined(OS_WIN) || (defined(OS_LINUX) && !defined(OS_CHROMEOS))
+extern const base::Feature kWarnBeforeQuitting;
+#endif
+
extern const base::Feature kMaterialDesignIncognitoNTP;
#if !defined(OS_ANDROID)
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 4545de1..bbe48c9 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3189,6 +3189,11 @@
}
}
+ if (is_win || (is_linux && !is_chromeos)) {
+ sources +=
+ [ "../browser/ui/views/confirm_quit_bubble_controller_unittest.cc" ]
+ }
+
if (enable_native_notifications) {
if (is_desktop_linux) {
sources += [ "../browser/notifications/notification_platform_bridge_linux_unittest.cc" ]
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index 190a4c0b..d08b0982 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -1435,6 +1435,9 @@
switch (delegate_->PreHandleKeyboardEvent(key_event)) {
case KeyboardEventProcessingResult::HANDLED:
return;
+ case KeyboardEventProcessingResult::HANDLED_WANTS_KEY_UP:
+ suppress_events_until_keydown_ = false;
+ return;
#if defined(USE_AURA)
case KeyboardEventProcessingResult::HANDLED_DONT_UPDATE_EVENT:
if (update_event)
diff --git a/content/public/browser/keyboard_event_processing_result.h b/content/public/browser/keyboard_event_processing_result.h
index 4030a52..ba43542c 100644
--- a/content/public/browser/keyboard_event_processing_result.h
+++ b/content/public/browser/keyboard_event_processing_result.h
@@ -11,6 +11,9 @@
// The event was handled.
HANDLED,
+ // The event was handled, and we want to be notified of the keyup event too.
+ HANDLED_WANTS_KEY_UP,
+
#if defined(USE_AURA)
// The event was handled, but don't update the underlying event. A value
// HANDLED results in calling ui::Event::SetHandled(), where as this does not.
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 4127b44..d7a3bdc 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -27421,6 +27421,7 @@
<int value="423855924" label="enable-tab-switcher-theme-colors"/>
<int value="431691805" label="MediaDocumentDownloadButton:enabled"/>
<int value="434033638" label="PwaPersistentNotification:disabled"/>
+ <int value="438048339" label="WarnBeforeQuitting:disabled"/>
<int value="446316019" label="enable-threaded-compositing"/>
<int value="451196246" label="disable-impl-side-painting"/>
<int value="452139294" label="VrShellExperimentalRendering:enabled"/>
@@ -27460,6 +27461,7 @@
<int value="513356954" label="InstantTethering:disabled"/>
<int value="513372959" label="ViewsProfileChooser:enabled"/>
<int value="517568645" label="AnimatedAppMenuIcon:disabled"/>
+ <int value="530158943" label="WarnBeforeQuitting:enabled"/>
<int value="535131384" label="OmniboxTailSuggestions:enabled"/>
<int value="535976218" label="enable-plugin-power-saver"/>
<int value="538468149" label="OfflinePagesCT:enabled"/>