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