| OLD | NEW | 
|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be | 
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. | 
| 4 | 4 | 
| 5 #include <stdint.h> | 5 #include <stdint.h> | 
| 6 | 6 | 
| 7 #include <set> | 7 #include <set> | 
|  | 8 #include <string> | 
|  | 9 #include <vector> | 
| 8 | 10 | 
| 9 #include "base/run_loop.h" | 11 #include "base/run_loop.h" | 
| 10 #include "base/strings/string_number_conversions.h" | 12 #include "base/strings/string_number_conversions.h" | 
| 11 #include "chrome/browser/extensions/extension_browsertest.h" | 13 #include "chrome/browser/extensions/extension_browsertest.h" | 
| 12 #include "chrome/browser/extensions/extension_service.h" | 14 #include "chrome/browser/extensions/extension_service.h" | 
| 13 #include "chrome/browser/extensions/extension_storage_monitor.h" | 15 #include "chrome/browser/extensions/extension_storage_monitor.h" | 
|  | 16 #include "chrome/browser/extensions/test_extension_dir.h" | 
| 14 #include "chrome/browser/ui/extensions/app_launch_params.h" | 17 #include "chrome/browser/ui/extensions/app_launch_params.h" | 
| 15 #include "chrome/browser/ui/extensions/application_launch.h" | 18 #include "chrome/browser/ui/extensions/application_launch.h" | 
|  | 19 #include "content/public/test/browser_test_utils.h" | 
| 16 #include "content/public/test/test_utils.h" | 20 #include "content/public/test/test_utils.h" | 
| 17 #include "extensions/browser/extension_dialog_auto_confirm.h" | 21 #include "extensions/browser/extension_dialog_auto_confirm.h" | 
| 18 #include "extensions/browser/extension_prefs.h" | 22 #include "extensions/browser/extension_prefs.h" | 
| 19 #include "extensions/browser/extension_registry.h" | 23 #include "extensions/browser/extension_registry.h" | 
| 20 #include "extensions/browser/extension_system.h" | 24 #include "extensions/browser/extension_system.h" | 
| 21 #include "extensions/browser/test_extension_registry_observer.h" | 25 #include "extensions/browser/test_extension_registry_observer.h" | 
| 22 #include "extensions/common/constants.h" | 26 #include "extensions/common/constants.h" | 
|  | 27 #include "extensions/common/value_builder.h" | 
| 23 #include "extensions/test/extension_test_message_listener.h" | 28 #include "extensions/test/extension_test_message_listener.h" | 
|  | 29 #include "net/dns/mock_host_resolver.h" | 
| 24 #include "ui/message_center/message_center.h" | 30 #include "ui/message_center/message_center.h" | 
| 25 #include "ui/message_center/message_center_observer.h" | 31 #include "ui/message_center/message_center_observer.h" | 
| 26 | 32 | 
| 27 namespace extensions { | 33 namespace extensions { | 
| 28 | 34 | 
| 29 namespace { | 35 namespace { | 
| 30 | 36 | 
| 31 const int kInitialUsageThreshold = 500; | 37 const int kInitialUsageThreshold = 500; | 
| 32 | 38 | 
| 33 const char kWriteDataApp[] = "storage_monitor/write_data"; | 39 const char kWriteDataApp[] = "storage_monitor/write_data"; | 
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 79 | 85 | 
| 80 class ExtensionStorageMonitorTest : public ExtensionBrowserTest { | 86 class ExtensionStorageMonitorTest : public ExtensionBrowserTest { | 
| 81  public: | 87  public: | 
| 82   ExtensionStorageMonitorTest() : storage_monitor_(NULL) {} | 88   ExtensionStorageMonitorTest() : storage_monitor_(NULL) {} | 
| 83 | 89 | 
| 84  protected: | 90  protected: | 
| 85   // ExtensionBrowserTest overrides: | 91   // ExtensionBrowserTest overrides: | 
| 86   void SetUpOnMainThread() override { | 92   void SetUpOnMainThread() override { | 
| 87     ExtensionBrowserTest::SetUpOnMainThread(); | 93     ExtensionBrowserTest::SetUpOnMainThread(); | 
| 88 | 94 | 
|  | 95     host_resolver()->AddRule("*", "127.0.0.1"); | 
|  | 96 | 
| 89     InitStorageMonitor(); | 97     InitStorageMonitor(); | 
| 90   } | 98   } | 
| 91 | 99 | 
| 92   ExtensionStorageMonitor* monitor() { | 100   ExtensionStorageMonitor* monitor() { | 
| 93     CHECK(storage_monitor_); | 101     CHECK(storage_monitor_); | 
| 94     return storage_monitor_; | 102     return storage_monitor_; | 
| 95   } | 103   } | 
| 96 | 104 | 
| 97   int64_t GetInitialExtensionThreshold() { | 105   int64_t GetInitialExtensionThreshold() { | 
| 98     CHECK(storage_monitor_); | 106     CHECK(storage_monitor_); | 
| 99     return storage_monitor_->initial_extension_threshold_; | 107     return storage_monitor_->initial_extension_threshold_; | 
| 100   } | 108   } | 
| 101 | 109 | 
| 102   void DisableForInstalledExtensions() { | 110   void DisableForInstalledExtensions() { | 
| 103     CHECK(storage_monitor_); | 111     CHECK(storage_monitor_); | 
| 104     storage_monitor_->enable_for_all_extensions_ = false; | 112     storage_monitor_->enable_for_all_extensions_ = false; | 
| 105   } | 113   } | 
| 106 | 114 | 
| 107   const Extension* InitWriteDataApp() { | 115   const Extension* InitWriteDataApp() { | 
| 108     base::FilePath path = test_data_dir_.AppendASCII(kWriteDataApp); | 116     base::FilePath path = test_data_dir_.AppendASCII(kWriteDataApp); | 
| 109     const Extension* extension = InstallExtension(path, 1); | 117     const Extension* extension = InstallExtension(path, 1); | 
| 110     EXPECT_TRUE(extension); | 118     EXPECT_TRUE(extension); | 
| 111     return extension; | 119     return extension; | 
| 112   } | 120   } | 
| 113 | 121 | 
|  | 122   const Extension* CreateHostedApp(const std::string& name, | 
|  | 123                                    GURL app_url, | 
|  | 124                                    std::vector<std::string> permissions) { | 
|  | 125     auto dir = base::MakeUnique<TestExtensionDir>(); | 
|  | 126 | 
|  | 127     url::Replacements<char> clear_port; | 
|  | 128     clear_port.ClearPort(); | 
|  | 129 | 
|  | 130     DictionaryBuilder manifest; | 
|  | 131     manifest.Set("name", name) | 
|  | 132         .Set("version", "1.0") | 
|  | 133         .Set("manifest_version", 2) | 
|  | 134         .Set( | 
|  | 135             "app", | 
|  | 136             DictionaryBuilder() | 
|  | 137                 .Set("urls", | 
|  | 138                      ListBuilder() | 
|  | 139                          .Append(app_url.ReplaceComponents(clear_port).spec()) | 
|  | 140                          .Build()) | 
|  | 141                 .Set("launch", | 
|  | 142                      DictionaryBuilder().Set("web_url", app_url.spec()).Build()) | 
|  | 143                 .Build()); | 
|  | 144     ListBuilder permissions_builder; | 
|  | 145     for (const std::string& permission : permissions) | 
|  | 146       permissions_builder.Append(permission); | 
|  | 147     manifest.Set("permissions", permissions_builder.Build()); | 
|  | 148     dir->WriteManifest(manifest.ToJSON()); | 
|  | 149 | 
|  | 150     const Extension* extension = LoadExtension(dir->UnpackedPath()); | 
|  | 151     EXPECT_TRUE(extension); | 
|  | 152     temp_dirs_.push_back(std::move(dir)); | 
|  | 153     return extension; | 
|  | 154   } | 
|  | 155 | 
| 114   std::string GetNotificationId(const std::string& extension_id) { | 156   std::string GetNotificationId(const std::string& extension_id) { | 
| 115     return monitor()->GetNotificationId(extension_id); | 157     return monitor()->GetNotificationId(extension_id); | 
| 116   } | 158   } | 
| 117 | 159 | 
| 118   bool IsStorageNotificationEnabled(const std::string& extension_id) { | 160   bool IsStorageNotificationEnabled(const std::string& extension_id) { | 
| 119     return monitor()->IsStorageNotificationEnabled(extension_id); | 161     return monitor()->IsStorageNotificationEnabled(extension_id); | 
| 120   } | 162   } | 
| 121 | 163 | 
| 122   int64_t GetNextStorageThreshold(const std::string& extension_id) { | 164   int64_t GetNextStorageThreshold(const std::string& extension_id) { | 
| 123     return monitor()->GetNextStorageThreshold(extension_id); | 165     return monitor()->GetNextStorageThreshold(extension_id); | 
| 124   } | 166   } | 
| 125 | 167 | 
| 126   void WriteBytesExpectingNotification(const Extension* extension, | 168   void WriteBytesExpectingNotification(const Extension* extension, | 
| 127                                        int num_bytes) { | 169                                        int num_bytes, | 
|  | 170                                        const char* filesystem = "PERSISTENT") { | 
| 128     int64_t previous_threshold = GetNextStorageThreshold(extension->id()); | 171     int64_t previous_threshold = GetNextStorageThreshold(extension->id()); | 
| 129     WriteBytes(extension, num_bytes, true); | 172     WriteBytes(extension, num_bytes, filesystem, true); | 
| 130     EXPECT_GT(GetNextStorageThreshold(extension->id()), previous_threshold); | 173     ASSERT_GT(GetNextStorageThreshold(extension->id()), previous_threshold); | 
| 131   } | 174   } | 
| 132 | 175 | 
| 133   void WriteBytesNotExpectingNotification(const Extension* extension, | 176   void WriteBytesNotExpectingNotification( | 
| 134                                          int num_bytes) { | 177       const Extension* extension, | 
| 135     WriteBytes(extension, num_bytes, false); | 178       int num_bytes, | 
|  | 179       const char* filesystem = "PERSISTENT") { | 
|  | 180     WriteBytes(extension, num_bytes, filesystem, false); | 
| 136   } | 181   } | 
| 137 | 182 | 
|  | 183   void SimulateProfileShutdown() { storage_monitor_->StopMonitoringAll(); } | 
|  | 184 | 
| 138  private: | 185  private: | 
| 139   void InitStorageMonitor() { | 186   void InitStorageMonitor() { | 
| 140     storage_monitor_ = ExtensionStorageMonitor::Get(profile()); | 187     storage_monitor_ = ExtensionStorageMonitor::Get(profile()); | 
| 141     ASSERT_TRUE(storage_monitor_); | 188     ASSERT_TRUE(storage_monitor_); | 
| 142 | 189 | 
| 143     // Override thresholds so that we don't have to write a huge amount of data | 190     // Override thresholds so that we don't have to write a huge amount of data | 
| 144     // to trigger notifications in these tests. | 191     // to trigger notifications in these tests. | 
| 145     storage_monitor_->enable_for_all_extensions_ = true; | 192     storage_monitor_->enable_for_all_extensions_ = true; | 
| 146     storage_monitor_->initial_extension_threshold_ = kInitialUsageThreshold; | 193     storage_monitor_->initial_extension_threshold_ = kInitialUsageThreshold; | 
| 147 | 194 | 
| 148     // To ensure storage events are dispatched from QuotaManager immediately. | 195     // To ensure storage events are dispatched from QuotaManager immediately. | 
| 149     storage_monitor_->observer_rate_ = base::TimeDelta(); | 196     storage_monitor_->observer_rate_ = base::TimeDelta(); | 
| 150   } | 197   } | 
| 151 | 198 | 
|  | 199   // Write bytes for a hosted app page that's loaded the script: | 
|  | 200   //     //chrome/test/data/extensions/storage_monitor/hosted_apps/common.js | 
|  | 201   void WriteBytesForHostedApp(const Extension* extension, | 
|  | 202                               int num_bytes, | 
|  | 203                               const std::string& filesystem) { | 
|  | 204     content::WebContents* web_contents = OpenApplication(AppLaunchParams( | 
|  | 205         profile(), extension, LAUNCH_CONTAINER_TAB, | 
|  | 206         WindowOpenDisposition::SINGLETON_TAB, extensions::SOURCE_TEST)); | 
|  | 207 | 
|  | 208     ASSERT_TRUE(WaitForLoadStop(web_contents)); | 
|  | 209     std::string result; | 
|  | 210     const char* script = R"( | 
|  | 211         HostedAppWriteData(%s, %d) | 
|  | 212            .then(() => domAutomationController.send('write_done')) | 
|  | 213            .catch(e => domAutomationController.send('write_error: ' + e)); | 
|  | 214     )"; | 
|  | 215     ASSERT_TRUE(ExecuteScriptAndExtractString( | 
|  | 216         web_contents, base::StringPrintf(script, filesystem.c_str(), num_bytes), | 
|  | 217         &result)); | 
|  | 218     ASSERT_EQ("write_done", result); | 
|  | 219   } | 
|  | 220 | 
|  | 221   // Write bytes for the extension loaded from: | 
|  | 222   //     //chrome/test/data/extensions/storage_monitor/write_data | 
|  | 223   void WriteBytesForExtension(const Extension* extension, int num_bytes) { | 
|  | 224     ExtensionTestMessageListener launched_listener("launched", true); | 
|  | 225     ExtensionTestMessageListener write_complete_listener("write_complete", | 
|  | 226                                                          false); | 
|  | 227 | 
|  | 228     OpenApplication(AppLaunchParams(profile(), extension, LAUNCH_CONTAINER_NONE, | 
|  | 229                                     WindowOpenDisposition::NEW_WINDOW, | 
|  | 230                                     extensions::SOURCE_TEST)); | 
|  | 231 | 
|  | 232     ASSERT_TRUE(launched_listener.WaitUntilSatisfied()); | 
|  | 233 | 
|  | 234     // Instruct the app to write |num_bytes| of data. | 
|  | 235     launched_listener.Reply(base::IntToString(num_bytes)); | 
|  | 236     ASSERT_TRUE(write_complete_listener.WaitUntilSatisfied()); | 
|  | 237   } | 
|  | 238 | 
| 152   // Write a number of bytes to persistent storage. | 239   // Write a number of bytes to persistent storage. | 
| 153   void WriteBytes(const Extension* extension, | 240   void WriteBytes(const Extension* extension, | 
| 154                   int num_bytes, | 241                   int num_bytes, | 
|  | 242                   const std::string& filesystem, | 
| 155                   bool expected_notification) { | 243                   bool expected_notification) { | 
| 156     ExtensionTestMessageListener launched_listener("launched", true); |  | 
| 157     ExtensionTestMessageListener write_complete_listener( |  | 
| 158         "write_complete", false); |  | 
| 159     NotificationObserver notification_observer( | 244     NotificationObserver notification_observer( | 
| 160         GetNotificationId(extension->id())); | 245         GetNotificationId(extension->id())); | 
| 161 | 246 | 
| 162     OpenApplication(AppLaunchParams(profile(), extension, LAUNCH_CONTAINER_NONE, | 247     if (extension->is_hosted_app()) { | 
| 163                                     WindowOpenDisposition::NEW_WINDOW, | 248       WriteBytesForHostedApp(extension, num_bytes, filesystem); | 
| 164                                     extensions::SOURCE_TEST)); | 249     } else { | 
| 165     ASSERT_TRUE(launched_listener.WaitUntilSatisfied()); | 250       ASSERT_EQ("PERSISTENT", filesystem) << "Not implemented in the js code."; | 
| 166 | 251       WriteBytesForExtension(extension, num_bytes); | 
| 167     // Instruct the app to write |num_bytes| of data. | 252     } | 
| 168     launched_listener.Reply(base::IntToString(num_bytes)); |  | 
| 169     ASSERT_TRUE(write_complete_listener.WaitUntilSatisfied()); |  | 
| 170 | 253 | 
| 171     if (expected_notification) { | 254     if (expected_notification) { | 
| 172       EXPECT_TRUE(notification_observer.WaitForNotification()); | 255       ASSERT_TRUE(notification_observer.WaitForNotification()); | 
| 173     } else { | 256     } else { | 
| 174       base::RunLoop().RunUntilIdle(); | 257       base::RunLoop().RunUntilIdle(); | 
| 175       EXPECT_FALSE(notification_observer.HasReceivedNotification()); | 258       ASSERT_FALSE(notification_observer.HasReceivedNotification()); | 
| 176     } | 259     } | 
| 177   } | 260   } | 
| 178 | 261 | 
| 179   ExtensionStorageMonitor* storage_monitor_; | 262   ExtensionStorageMonitor* storage_monitor_; | 
|  | 263   std::vector<std::unique_ptr<TestExtensionDir>> temp_dirs_; | 
| 180 }; | 264 }; | 
| 181 | 265 | 
| 182 // Control - No notifications should be shown if usage remains under the | 266 // Control - No notifications should be shown if usage remains under the | 
| 183 // threshold. | 267 // threshold. | 
| 184 IN_PROC_BROWSER_TEST_F(ExtensionStorageMonitorTest, UnderThreshold) { | 268 IN_PROC_BROWSER_TEST_F(ExtensionStorageMonitorTest, UnderThreshold) { | 
| 185   const Extension* extension = InitWriteDataApp(); | 269   const Extension* extension = InitWriteDataApp(); | 
| 186   ASSERT_TRUE(extension); | 270   ASSERT_TRUE(extension); | 
| 187   WriteBytesNotExpectingNotification(extension, 1); | 271   WriteBytesNotExpectingNotification(extension, 1); | 
| 188 } | 272 } | 
| 189 | 273 | 
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 246 // is eventually enabled for all extensions. | 330 // is eventually enabled for all extensions. | 
| 247 IN_PROC_BROWSER_TEST_F(ExtensionStorageMonitorTest, | 331 IN_PROC_BROWSER_TEST_F(ExtensionStorageMonitorTest, | 
| 248                        DisableForInstalledExtensions) { | 332                        DisableForInstalledExtensions) { | 
| 249   DisableForInstalledExtensions(); | 333   DisableForInstalledExtensions(); | 
| 250 | 334 | 
| 251   const Extension* extension = InitWriteDataApp(); | 335   const Extension* extension = InitWriteDataApp(); | 
| 252   ASSERT_TRUE(extension); | 336   ASSERT_TRUE(extension); | 
| 253   WriteBytesNotExpectingNotification(extension, GetInitialExtensionThreshold()); | 337   WriteBytesNotExpectingNotification(extension, GetInitialExtensionThreshold()); | 
| 254 } | 338 } | 
| 255 | 339 | 
|  | 340 // Regression test for https://crbug.com/716426 | 
|  | 341 IN_PROC_BROWSER_TEST_F(ExtensionStorageMonitorTest, | 
|  | 342                        HostedAppTemporaryFilesystem) { | 
|  | 343   ASSERT_TRUE(embedded_test_server()->Start()); | 
|  | 344 | 
|  | 345   GURL url = embedded_test_server()->GetURL( | 
|  | 346       "chromium.org", "/extensions/storage_monitor/hosted_apps/one/index.html"); | 
|  | 347   const Extension* app = | 
|  | 348       CreateHostedApp("Hosted App", url, {"unlimitedStorage"}); | 
|  | 349 | 
|  | 350   EXPECT_NO_FATAL_FAILURE(WriteBytesExpectingNotification( | 
|  | 351       app, GetInitialExtensionThreshold(), "TEMPORARY")); | 
|  | 352   EXPECT_NO_FATAL_FAILURE(WriteBytesNotExpectingNotification( | 
|  | 353       app, GetInitialExtensionThreshold(), "PERSISTENT")); | 
|  | 354   EXPECT_NO_FATAL_FAILURE(WriteBytesExpectingNotification( | 
|  | 355       app, GetInitialExtensionThreshold(), "TEMPORARY")); | 
|  | 356 | 
|  | 357   // Bug 716426 was a shutdown crash due to not removing a | 
|  | 358   // storage::StorageObserver registration before deleting the observer. To | 
|  | 359   // recreate that scenario, first disable the app (which leaks the observer | 
|  | 360   // registration), then simulate the step of profile exit where we delete the | 
|  | 361   // StorageObserver. | 
|  | 362   DisableExtension(app->id()); | 
|  | 363   SimulateProfileShutdown(); | 
|  | 364 | 
|  | 365   // Now generate more storage activity for the hosted app's temporary | 
|  | 366   // filesystem. Note that it's not a hosted app anymore -- it's just a webpage. | 
|  | 367   // Bug 716426 caused this to crash the browser. | 
|  | 368   EXPECT_NO_FATAL_FAILURE(WriteBytesNotExpectingNotification( | 
|  | 369       app, GetInitialExtensionThreshold(), "TEMPORARY")); | 
|  | 370 } | 
|  | 371 | 
|  | 372 // Exercises the case where two hosted apps are same-origin but have non- | 
|  | 373 // overlapping extents. Disabling one should not suppress storage monitoring for | 
|  | 374 // the other. | 
|  | 375 IN_PROC_BROWSER_TEST_F(ExtensionStorageMonitorTest, TwoHostedAppsInSameOrigin) { | 
|  | 376   ASSERT_TRUE(embedded_test_server()->Start()); | 
|  | 377 | 
|  | 378   GURL url1 = embedded_test_server()->GetURL( | 
|  | 379       "chromium.org", "/extensions/storage_monitor/hosted_apps/one/index.html"); | 
|  | 380   const Extension* app1 = CreateHostedApp("App 1", url1, {"unlimitedStorage"}); | 
|  | 381 | 
|  | 382   GURL url2 = embedded_test_server()->GetURL( | 
|  | 383       "chromium.org", "/extensions/storage_monitor/hosted_apps/two/index.html"); | 
|  | 384   const Extension* app2 = CreateHostedApp("App 2", url2, {"unlimitedStorage"}); | 
|  | 385 | 
|  | 386   EXPECT_EQ(url1.GetOrigin(), url2.GetOrigin()); | 
|  | 387 | 
|  | 388   EXPECT_NO_FATAL_FAILURE( | 
|  | 389       WriteBytesExpectingNotification(app1, GetInitialExtensionThreshold())); | 
|  | 390   EXPECT_NO_FATAL_FAILURE(WriteBytesExpectingNotification( | 
|  | 391       app2, GetInitialExtensionThreshold() * 2)); | 
|  | 392 | 
|  | 393   // Disable app2. We should still be monitoring the origin on behalf of app1. | 
|  | 394   DisableExtension(app2->id()); | 
|  | 395 | 
|  | 396   // Writing a bunch of data in app1 should trigger the warning. | 
|  | 397   EXPECT_NO_FATAL_FAILURE(WriteBytesExpectingNotification( | 
|  | 398       app1, GetInitialExtensionThreshold() * 4)); | 
|  | 399 } | 
|  | 400 | 
| 256 // Verify that notifications are disabled when the user clicks the action button | 401 // Verify that notifications are disabled when the user clicks the action button | 
| 257 // in the notification. | 402 // in the notification. | 
| 258 // Flaky: https://crbug.com/617801 | 403 // Flaky: https://crbug.com/617801 | 
| 259 IN_PROC_BROWSER_TEST_F(ExtensionStorageMonitorTest, | 404 IN_PROC_BROWSER_TEST_F(ExtensionStorageMonitorTest, | 
| 260                        DISABLED_UninstallExtension) { | 405                        DISABLED_UninstallExtension) { | 
| 261   const Extension* extension = InitWriteDataApp(); | 406   const Extension* extension = InitWriteDataApp(); | 
| 262   ASSERT_TRUE(extension); | 407   ASSERT_TRUE(extension); | 
| 263   WriteBytesExpectingNotification(extension, GetInitialExtensionThreshold()); | 408   WriteBytesExpectingNotification(extension, GetInitialExtensionThreshold()); | 
| 264 | 409 | 
| 265   // Fake clicking the notification button to uninstall and accepting the | 410   // Fake clicking the notification button to uninstall and accepting the | 
| 266   // uninstall. | 411   // uninstall. | 
| 267   ScopedTestDialogAutoConfirm scoped_autoconfirm( | 412   ScopedTestDialogAutoConfirm scoped_autoconfirm( | 
| 268       ScopedTestDialogAutoConfirm::ACCEPT); | 413       ScopedTestDialogAutoConfirm::ACCEPT); | 
| 269   TestExtensionRegistryObserver observer(ExtensionRegistry::Get(profile()), | 414   TestExtensionRegistryObserver observer(ExtensionRegistry::Get(profile()), | 
| 270                                          extension->id()); | 415                                          extension->id()); | 
| 271   message_center::MessageCenter::Get()->ClickOnNotificationButton( | 416   message_center::MessageCenter::Get()->ClickOnNotificationButton( | 
| 272       GetNotificationId(extension->id()), | 417       GetNotificationId(extension->id()), | 
| 273       ExtensionStorageMonitor::BUTTON_UNINSTALL); | 418       ExtensionStorageMonitor::BUTTON_UNINSTALL); | 
| 274   observer.WaitForExtensionUninstalled(); | 419   observer.WaitForExtensionUninstalled(); | 
| 275 } | 420 } | 
| 276 | 421 | 
| 277 }  // namespace extensions | 422 }  // namespace extensions | 
| OLD | NEW | 
|---|