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 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
|
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <utility>
#include "base/location.h"
#include "base/numerics/safe_conversions.h"
#include "base/run_loop.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/test_timeouts.h"
#include "base/version.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/policy/policy_test_utils.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/relaunch_notification/relaunch_required_dialog_view.h"
#include "chrome/browser/upgrade_detector/build_state.h"
#include "chrome/browser/upgrade_detector/upgrade_detector.h"
#include "chrome/browser/upgrade_detector/upgrade_observer.h"
#include "chrome/common/chrome_version.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/policy_constants.h"
#include "content/public/test/browser_test.h"
#include "ui/views/test/widget_activation_waiter.h"
#include "ui/views/test/widget_test.h"
#include "ui/views/widget/any_widget_observer.h"
namespace {
// Waits for the UpgradeDetector to broadcast to its observers that an upgrade
// has been detected.
class UpgradeRecommendedWaiter : public UpgradeObserver {
public:
UpgradeRecommendedWaiter() {
UpgradeDetector::GetInstance()->AddObserver(this);
}
~UpgradeRecommendedWaiter() override {
UpgradeDetector::GetInstance()->RemoveObserver(this);
}
void Wait() { run_loop_.Run(); }
// UpgradeObserver:
void OnUpgradeRecommended() override { run_loop_.QuitWhenIdle(); }
private:
base::RunLoop run_loop_;
};
} // namespace
class RelaunchNotificationControllerUiTest : public policy::PolicyTest {
protected:
// policy::PolicyTest:
void SetUpOnMainThread() override {
policy::PolicyTest::SetUpOnMainThread();
// Configure required relaunch notifications.
SetRelaunchNotificationPolicies();
}
void TearDownOnMainThread() override {
// Disable notifications so that the timer doesn't fire during teardown.
DisableRelaunchNotifications();
policy::PolicyTest::TearDownOnMainThread();
}
// Sets the RelaunchNotification policies to show the required notification
// at the default deadline.
void SetRelaunchNotificationPolicies() {
policies_.Set(policy::key::kRelaunchNotification,
policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_MACHINE,
policy::POLICY_SOURCE_PLATFORM,
base::Value(2), // Required
nullptr);
policies_.Set(policy::key::kRelaunchNotificationPeriod,
policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_MACHINE,
policy::POLICY_SOURCE_PLATFORM,
base::Value(base::saturated_cast<int>(
UpgradeDetector::GetDefaultHighAnnoyanceThreshold()
.InMilliseconds())),
nullptr);
UpdateProviderPolicy(policies_);
}
// Disables relaunch notifications.
void DisableRelaunchNotifications() {
policies_.Set(policy::key::kRelaunchNotification,
policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_MACHINE,
policy::POLICY_SOURCE_PLATFORM,
base::Value(0), // Disabled
nullptr);
UpdateProviderPolicy(policies_);
}
// Simulates that an update is available.
static void SimulateUpdate() {
g_browser_process->GetBuildState()->SetUpdate(
BuildState::UpdateType::kNormalUpdate,
base::Version({CHROME_VERSION_MAJOR, CHROME_VERSION_MINOR,
CHROME_VERSION_BUILD, CHROME_VERSION_PATCH + 1}),
std::nullopt);
}
// Triggers the annoyance level `level` to be announced to observers of
// UpgradeDetector.
void TriggerAnnoyanceLevel(
UpgradeDetector::UpgradeNotificationAnnoyanceLevel level) {
// Move the time at which the update was detected so that the original
// detection time is, relative to the new time, at the moment that `level`
// takes effect.
auto* const upgrade_detector = UpgradeDetector::GetInstance();
upgrade_detector->set_upgrade_detected_time(
upgrade_detector->upgrade_detected_time() -
(upgrade_detector->GetAnnoyanceLevelDeadline(level) -
upgrade_detector->upgrade_detected_time()));
// Force the UpgradeDetector to recompute the deadlines by reducing the
// period by 1ms. This will also be broadcast to observers.
policies_.Set(policy::key::kRelaunchNotificationPeriod,
policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_MACHINE,
policy::POLICY_SOURCE_PLATFORM,
base::Value(base::saturated_cast<int>(
UpgradeDetector::GetDefaultHighAnnoyanceThreshold()
.InMilliseconds() -
1)),
nullptr);
UpdateProviderPolicy(policies_);
}
// Closes `dialog` and waits for it to be destroyed.
static void CloseDialog(RelaunchRequiredDialogView* dialog) {
views::test::WidgetDestroyedWaiter waiter(dialog->GetWidget());
std::exchange(dialog, nullptr)->CancelDialog();
waiter.Wait();
}
// Minimizes `browser_view` and waits for it to be deactivated.
static void MinimizeBrowser(BrowserView* browser_view) {
browser_view->Minimize();
views::test::WaitForWidgetActive(browser_view->GetWidget(),
/*active=*/false);
// Pump all pending UI events so that the window manager isn't racing with
// the test.
base::RunLoop().RunUntilIdle();
}
// Sets the RelaunchNotificationPeriod so that the deadline is `delta` in
// the future. Returns the new deadline.
base::Time AdjustPeriodToDeadlineIn(base::TimeDelta delta) {
UpgradeRecommendedWaiter waiter;
const base::Time deadline = base::Time::Now() + delta;
const base::TimeDelta period =
deadline - UpgradeDetector::GetInstance()->upgrade_detected_time();
policies_.Set(
policy::key::kRelaunchNotificationPeriod,
policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_MACHINE,
policy::POLICY_SOURCE_PLATFORM,
base::Value(base::saturated_cast<int>(period.InMilliseconds())),
nullptr);
UpdateProviderPolicy(policies_);
waiter.Wait();
return deadline;
}
private:
policy::PolicyMap policies_;
};
// Tests that reactivating a browser window after the deadline has passed does
// not show a negative delta.
// Fails on mac64; see https://crbug.com/1462892.
IN_PROC_BROWSER_TEST_F(RelaunchNotificationControllerUiTest,
ReactivateAfterDeadline) {
// Make sure a browser window is active.
auto* const browser_view = BrowserView::GetBrowserViewForBrowser(browser());
ASSERT_TRUE(browser_view->IsActive());
ASSERT_EQ(chrome::FindBrowserWithActiveWindow(), browser());
// Simulate an update and wait for the notification to show.
RelaunchRequiredDialogView* dialog = nullptr;
{
views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{},
"RelaunchRequiredDialog");
SimulateUpdate();
TriggerAnnoyanceLevel(UpgradeDetector::UPGRADE_ANNOYANCE_ELEVATED);
auto* widget = waiter.WaitIfNeededAndGet();
ASSERT_TRUE(widget);
dialog = RelaunchRequiredDialogView::FromWidget(widget);
}
// Dismiss the notification and wait for its destruction.
CloseDialog(std::exchange(dialog, nullptr));
// Deactivate the browser window.
MinimizeBrowser(browser_view);
ASSERT_FALSE(chrome::FindBrowserWithActiveWindow());
// The code below assumes that `action_timeout` is greater than 2.5 seconds.
ASSERT_GT(TestTimeouts::action_timeout(), base::Milliseconds(2500));
// Reduce the relaunch notification period to a deadline that is just in the
// future. The browser is not active, so the controller will install an
// observer and wait for it to become active. The controller caches this
// deadline.
auto deadline = AdjustPeriodToDeadlineIn(TestTimeouts::action_timeout() / 5);
// Increase the relaunch notification period a bit. In the buggy case, the
// controller does not update the cached value of the deadline.
AdjustPeriodToDeadlineIn(TestTimeouts::action_timeout() / 2.5);
// Advance 1/2 second past the first revised deadline. This lets the clock
// move past the bad deadline cached by the controller for the dialog, but is
// still before the true relaunch deadline. It is significant that the clock
// is at least 1/2 second ahead so that the rounded delta to be shown in the
// UX is less than zero in the buggy case.
{
base::RunLoop run_loop;
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(),
(deadline + base::Milliseconds(500)) - base::Time::Now());
run_loop.Run();
}
// Activate the browser window and wait for the notification to show.
{
views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{},
"RelaunchRequiredDialog");
browser_view->Restore();
auto* widget = waiter.WaitIfNeededAndGet();
ASSERT_TRUE(widget);
dialog = RelaunchRequiredDialogView::FromWidget(widget);
}
ASSERT_TRUE(dialog);
ASSERT_GE(dialog->deadline(), base::Time::Now());
}
|