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