1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <set>
6
7#include "base/run_loop.h"
8#include "base/strings/string_number_conversions.h"
9#include "chrome/browser/extensions/extension_browsertest.h"
10#include "chrome/browser/extensions/extension_service.h"
11#include "chrome/browser/extensions/extension_storage_monitor.h"
12#include "chrome/browser/ui/extensions/application_launch.h"
13#include "content/public/test/test_utils.h"
14#include "extensions/browser/extension_prefs.h"
15#include "extensions/browser/extension_registry.h"
16#include "extensions/browser/extension_system.h"
17#include "extensions/browser/test_extension_registry_observer.h"
18#include "extensions/test/extension_test_message_listener.h"
19#include "ui/message_center/message_center.h"
20#include "ui/message_center/message_center_observer.h"
21
22namespace extensions {
23
24namespace {
25
26const int kInitialUsageThreshold = 500;
27
28const char kWriteDataApp[] = "storage_monitor/write_data";
29
30class NotificationObserver : public message_center::MessageCenterObserver {
31 public:
32  explicit NotificationObserver(const std::string& target_notification)
33      : message_center_(message_center::MessageCenter::Get()),
34        target_notification_id_(target_notification),
35        waiting_(false) {
36    message_center_->AddObserver(this);
37  }
38
39  virtual ~NotificationObserver() {
40    message_center_->RemoveObserver(this);
41  }
42
43  bool HasReceivedNotification() const {
44    return received_notifications_.find(target_notification_id_) !=
45      received_notifications_.end();
46  }
47
48  // Runs the message loop and returns true if a notification is received.
49  // Immediately returns true if a notification has already been received.
50  bool WaitForNotification() {
51    if (HasReceivedNotification())
52      return true;
53
54    waiting_ = true;
55    content::RunMessageLoop();
56    waiting_ = false;
57    return HasReceivedNotification();
58  }
59
60 private:
61  // MessageCenterObserver implementation:
62  virtual void OnNotificationAdded(
63      const std::string& notification_id) OVERRIDE {
64    received_notifications_.insert(notification_id);
65
66    if (waiting_ && HasReceivedNotification())
67      base::MessageLoopForUI::current()->Quit();
68  }
69
70  message_center::MessageCenter* message_center_;
71  std::set<std::string> received_notifications_;
72  std::string target_notification_id_;
73  bool waiting_;
74};
75
76}  // namespace
77
78class ExtensionStorageMonitorTest : public ExtensionBrowserTest {
79 public:
80  ExtensionStorageMonitorTest() : storage_monitor_(NULL) {}
81
82 protected:
83  // ExtensionBrowserTest overrides:
84  virtual void SetUpOnMainThread() OVERRIDE {
85    ExtensionBrowserTest::SetUpOnMainThread();
86
87    InitStorageMonitor();
88  }
89
90  ExtensionStorageMonitor* monitor() {
91    CHECK(storage_monitor_);
92    return storage_monitor_;
93  }
94
95  int64 GetInitialExtensionThreshold() {
96    CHECK(storage_monitor_);
97    return storage_monitor_->initial_extension_threshold_;
98  }
99
100  int64 GetInitialEphemeralThreshold() {
101    CHECK(storage_monitor_);
102    return storage_monitor_->initial_ephemeral_threshold_;
103  }
104
105  void DisableForInstalledExtensions() {
106    CHECK(storage_monitor_);
107    storage_monitor_->enable_for_all_extensions_ = false;
108  }
109
110  const Extension* InitWriteDataApp() {
111    base::FilePath path = test_data_dir_.AppendASCII(kWriteDataApp);
112    const Extension* extension = InstallExtension(path, 1);
113    EXPECT_TRUE(extension);
114    return extension;
115  }
116
117  const Extension* InitWriteDataEphemeralApp() {
118    // The threshold for installed extensions should be higher than ephemeral
119    // apps.
120    storage_monitor_->initial_extension_threshold_ =
121        storage_monitor_->initial_ephemeral_threshold_ * 4;
122
123    base::FilePath path = test_data_dir_.AppendASCII(kWriteDataApp);
124    const Extension* extension = InstallEphemeralAppWithSourceAndFlags(
125        path, 1, Manifest::INTERNAL, Extension::NO_FLAGS);
126    EXPECT_TRUE(extension);
127    return extension;
128  }
129
130  std::string GetNotificationId(const std::string& extension_id) {
131    return monitor()->GetNotificationId(extension_id);
132  }
133
134  bool IsStorageNotificationEnabled(const std::string& extension_id) {
135    return monitor()->IsStorageNotificationEnabled(extension_id);
136  }
137
138  int64 GetNextStorageThreshold(const std::string& extension_id) {
139    return monitor()->GetNextStorageThreshold(extension_id);
140  }
141
142  void WriteBytesExpectingNotification(const Extension* extension,
143                                       int num_bytes) {
144    int64 previous_threshold = GetNextStorageThreshold(extension->id());
145    WriteBytes(extension, num_bytes, true);
146    EXPECT_GT(GetNextStorageThreshold(extension->id()), previous_threshold);
147  }
148
149  void WriteBytesNotExpectingNotification(const Extension* extension,
150                                         int num_bytes) {
151    WriteBytes(extension, num_bytes, false);
152  }
153
154  void SimulateUninstallDialogAccept() {
155    // Ensure the uninstall dialog was shown and fake an accept.
156    ASSERT_TRUE(monitor()->uninstall_dialog_.get());
157    monitor()->ExtensionUninstallAccepted();
158  }
159
160 private:
161  void InitStorageMonitor() {
162    storage_monitor_ = ExtensionStorageMonitor::Get(profile());
163    ASSERT_TRUE(storage_monitor_);
164
165    // Override thresholds so that we don't have to write a huge amount of data
166    // to trigger notifications in these tests.
167    storage_monitor_->enable_for_all_extensions_ = true;
168    storage_monitor_->initial_extension_threshold_ = kInitialUsageThreshold;
169    storage_monitor_->initial_ephemeral_threshold_ = kInitialUsageThreshold;
170
171    // To ensure storage events are dispatched from QuotaManager immediately.
172    storage_monitor_->observer_rate_ = 0;
173  }
174
175  // Write a number of bytes to persistent storage.
176  void WriteBytes(const Extension* extension,
177                  int num_bytes,
178                  bool expected_notification) {
179    ExtensionTestMessageListener launched_listener("launched", true);
180    ExtensionTestMessageListener write_complete_listener(
181        "write_complete", false);
182    NotificationObserver notification_observer(
183        GetNotificationId(extension->id()));
184
185    OpenApplication(AppLaunchParams(
186        profile(), extension, LAUNCH_CONTAINER_NONE, NEW_WINDOW));
187    ASSERT_TRUE(launched_listener.WaitUntilSatisfied());
188
189    // Instruct the app to write |num_bytes| of data.
190    launched_listener.Reply(base::IntToString(num_bytes));
191    ASSERT_TRUE(write_complete_listener.WaitUntilSatisfied());
192
193    if (expected_notification) {
194      EXPECT_TRUE(notification_observer.WaitForNotification());
195    } else {
196      base::RunLoop().RunUntilIdle();
197      EXPECT_FALSE(notification_observer.HasReceivedNotification());
198    }
199  }
200
201  ExtensionStorageMonitor* storage_monitor_;
202};
203
204// Control - No notifications should be shown if usage remains under the
205// threshold.
206IN_PROC_BROWSER_TEST_F(ExtensionStorageMonitorTest, UnderThreshold) {
207  const Extension* extension = InitWriteDataApp();
208  ASSERT_TRUE(extension);
209  WriteBytesNotExpectingNotification(extension, 1);
210}
211
212// Ensure a notification is shown when usage reaches the first threshold.
213IN_PROC_BROWSER_TEST_F(ExtensionStorageMonitorTest, ExceedInitialThreshold) {
214  const Extension* extension = InitWriteDataApp();
215  ASSERT_TRUE(extension);
216  WriteBytesExpectingNotification(extension, GetInitialExtensionThreshold());
217}
218
219// Ensure a notification is shown when usage immediately exceeds double the
220// first threshold.
221IN_PROC_BROWSER_TEST_F(ExtensionStorageMonitorTest, DoubleInitialThreshold) {
222  const Extension* extension = InitWriteDataApp();
223  ASSERT_TRUE(extension);
224  WriteBytesExpectingNotification(extension,
225                                  GetInitialExtensionThreshold() * 2);
226}
227
228// Ensure that notifications are not fired if the next threshold has not been
229// reached.
230IN_PROC_BROWSER_TEST_F(ExtensionStorageMonitorTest, ThrottleNotifications) {
231  const Extension* extension = InitWriteDataApp();
232  ASSERT_TRUE(extension);
233
234  // Exceed the first threshold.
235  WriteBytesExpectingNotification(extension, GetInitialExtensionThreshold());
236
237  // Stay within the next threshold.
238  WriteBytesNotExpectingNotification(extension, 1);
239}
240
241// Verify that notifications are disabled when the user clicks the action button
242// in the notification.
243IN_PROC_BROWSER_TEST_F(ExtensionStorageMonitorTest, UserDisabledNotifications) {
244  const Extension* extension = InitWriteDataApp();
245  ASSERT_TRUE(extension);
246  WriteBytesExpectingNotification(extension, GetInitialExtensionThreshold());
247
248  EXPECT_TRUE(IsStorageNotificationEnabled(extension->id()));
249
250  // Fake clicking the notification button to disable notifications.
251  message_center::MessageCenter::Get()->ClickOnNotificationButton(
252      GetNotificationId(extension->id()),
253      ExtensionStorageMonitor::BUTTON_DISABLE_NOTIFICATION);
254
255  EXPECT_FALSE(IsStorageNotificationEnabled(extension->id()));
256
257  // Expect to receive no further notifications when usage continues to
258  // increase.
259  int64 next_threshold = GetNextStorageThreshold(extension->id());
260  int64 next_data_size = next_threshold - GetInitialExtensionThreshold();
261  ASSERT_GT(next_data_size, 0);
262
263  WriteBytesNotExpectingNotification(extension, next_data_size);
264}
265
266// Verify that thresholds for ephemeral apps are reset when they are
267// promoted to regular installed apps.
268IN_PROC_BROWSER_TEST_F(ExtensionStorageMonitorTest, EphemeralAppLowUsage) {
269  const Extension* extension = InitWriteDataEphemeralApp();
270  ASSERT_TRUE(extension);
271  WriteBytesExpectingNotification(extension, GetInitialEphemeralThreshold());
272
273  // Store the number of bytes until the next threshold is reached.
274  int64 next_threshold = GetNextStorageThreshold(extension->id());
275  int64 next_data_size = next_threshold - GetInitialEphemeralThreshold();
276  ASSERT_GT(next_data_size, 0);
277  EXPECT_GE(GetInitialExtensionThreshold(), next_threshold);
278
279  // Promote the ephemeral app.
280  ExtensionService* service =
281      ExtensionSystem::Get(profile())->extension_service();
282  service->PromoteEphemeralApp(extension, false);
283
284  // The next threshold should now be equal to the initial threshold for
285  // extensions (which is higher than the initial threshold for ephemeral apps).
286  EXPECT_EQ(GetInitialExtensionThreshold(),
287            GetNextStorageThreshold(extension->id()));
288
289  // Since the threshold was increased, a notification should not be
290  // triggered.
291  WriteBytesNotExpectingNotification(extension, next_data_size);
292}
293
294// Verify that thresholds for ephemeral apps are not reset when they are
295// promoted to regular installed apps if their usage is higher than the initial
296// threshold for installed extensions.
297IN_PROC_BROWSER_TEST_F(ExtensionStorageMonitorTest, EphemeralAppWithHighUsage) {
298  const Extension* extension = InitWriteDataEphemeralApp();
299  ASSERT_TRUE(extension);
300  WriteBytesExpectingNotification(extension, GetInitialExtensionThreshold());
301  int64 saved_next_threshold = GetNextStorageThreshold(extension->id());
302
303  // Promote the ephemeral app.
304  ExtensionService* service =
305      ExtensionSystem::Get(profile())->extension_service();
306  service->PromoteEphemeralApp(extension, false);
307
308  // The next threshold should not have changed.
309  EXPECT_EQ(saved_next_threshold, GetNextStorageThreshold(extension->id()));
310}
311
312// Ensure that monitoring is disabled for installed extensions if
313// |enable_for_all_extensions_| is false. This test can be removed if monitoring
314// is eventually enabled for all extensions.
315IN_PROC_BROWSER_TEST_F(ExtensionStorageMonitorTest,
316                       DisableForInstalledExtensions) {
317  DisableForInstalledExtensions();
318
319  const Extension* extension = InitWriteDataApp();
320  ASSERT_TRUE(extension);
321  WriteBytesNotExpectingNotification(extension, GetInitialExtensionThreshold());
322}
323
324// Verify that notifications are disabled when the user clicks the action button
325// in the notification.
326IN_PROC_BROWSER_TEST_F(ExtensionStorageMonitorTest, UninstallExtension) {
327  const Extension* extension = InitWriteDataApp();
328  ASSERT_TRUE(extension);
329  WriteBytesExpectingNotification(extension, GetInitialExtensionThreshold());
330
331  // Fake clicking the notification button to uninstall.
332  message_center::MessageCenter::Get()->ClickOnNotificationButton(
333      GetNotificationId(extension->id()),
334      ExtensionStorageMonitor::BUTTON_UNINSTALL);
335
336  // Also fake accepting the uninstall.
337  TestExtensionRegistryObserver observer(ExtensionRegistry::Get(profile()),
338                                         extension->id());
339  SimulateUninstallDialogAccept();
340  observer.WaitForExtensionUninstalled();
341}
342
343}  // namespace extensions
344