 Chromium Code Reviews
 Chromium Code Reviews Issue 2923663002:
  ExtensionStorageMonitor: use smaller, self-registering StorageObservers  (Closed)
    
  
    Issue 2923663002:
  ExtensionStorageMonitor: use smaller, self-registering StorageObservers  (Closed) 
  | Index: chrome/browser/extensions/extension_storage_monitor_browsertest.cc | 
| diff --git a/chrome/browser/extensions/extension_storage_monitor_browsertest.cc b/chrome/browser/extensions/extension_storage_monitor_browsertest.cc | 
| index b9671a8945d6d1e405271ba4c42003ae989915d1..0dd170ddff23062b3488ff3c38f12b1026397583 100644 | 
| --- a/chrome/browser/extensions/extension_storage_monitor_browsertest.cc | 
| +++ b/chrome/browser/extensions/extension_storage_monitor_browsertest.cc | 
| @@ -5,14 +5,18 @@ | 
| #include <stdint.h> | 
| #include <set> | 
| +#include <string> | 
| +#include <vector> | 
| #include "base/run_loop.h" | 
| #include "base/strings/string_number_conversions.h" | 
| #include "chrome/browser/extensions/extension_browsertest.h" | 
| #include "chrome/browser/extensions/extension_service.h" | 
| #include "chrome/browser/extensions/extension_storage_monitor.h" | 
| +#include "chrome/browser/extensions/test_extension_dir.h" | 
| #include "chrome/browser/ui/extensions/app_launch_params.h" | 
| #include "chrome/browser/ui/extensions/application_launch.h" | 
| +#include "content/public/test/browser_test_utils.h" | 
| #include "content/public/test/test_utils.h" | 
| #include "extensions/browser/extension_dialog_auto_confirm.h" | 
| #include "extensions/browser/extension_prefs.h" | 
| @@ -20,7 +24,9 @@ | 
| #include "extensions/browser/extension_system.h" | 
| #include "extensions/browser/test_extension_registry_observer.h" | 
| #include "extensions/common/constants.h" | 
| +#include "extensions/common/value_builder.h" | 
| #include "extensions/test/extension_test_message_listener.h" | 
| +#include "net/dns/mock_host_resolver.h" | 
| #include "ui/message_center/message_center.h" | 
| #include "ui/message_center/message_center_observer.h" | 
| @@ -86,6 +92,8 @@ class ExtensionStorageMonitorTest : public ExtensionBrowserTest { | 
| void SetUpOnMainThread() override { | 
| ExtensionBrowserTest::SetUpOnMainThread(); | 
| + host_resolver()->AddRule("*", "127.0.0.1"); | 
| + | 
| InitStorageMonitor(); | 
| } | 
| @@ -111,6 +119,40 @@ class ExtensionStorageMonitorTest : public ExtensionBrowserTest { | 
| return extension; | 
| } | 
| + const Extension* CreateHostedApp(const std::string& name, | 
| + GURL app_url, | 
| + std::vector<std::string> permissions) { | 
| + std::unique_ptr<TestExtensionDir> dir(new TestExtensionDir); | 
| 
Devlin
2017/06/12 20:15:05
prefer auto dir = base::MakeUnique<TestExtensionDi
 
ncarter (slow)
2017/06/12 22:52:07
Done.
 | 
| + | 
| + url::Replacements<char> clear_port; | 
| + clear_port.ClearPort(); | 
| + | 
| + DictionaryBuilder manifest; | 
| + manifest.Set("name", name) | 
| + .Set("version", "1.0") | 
| + .Set("manifest_version", 2) | 
| + .Set( | 
| + "app", | 
| + DictionaryBuilder() | 
| + .Set("urls", | 
| + ListBuilder() | 
| + .Append(app_url.ReplaceComponents(clear_port).spec()) | 
| + .Build()) | 
| + .Set("launch", | 
| + DictionaryBuilder().Set("web_url", app_url.spec()).Build()) | 
| + .Build()); | 
| + ListBuilder permissions_builder; | 
| + for (const std::string& permission : permissions) | 
| + permissions_builder.Append(permission); | 
| + manifest.Set("permissions", permissions_builder.Build()); | 
| + dir->WriteManifest(manifest.ToJSON()); | 
| + | 
| + const Extension* extension = LoadExtension(dir->UnpackedPath()); | 
| + EXPECT_TRUE(extension); | 
| + temp_dirs_.push_back(std::move(dir)); | 
| + return extension; | 
| + } | 
| + | 
| std::string GetNotificationId(const std::string& extension_id) { | 
| return monitor()->GetNotificationId(extension_id); | 
| } | 
| @@ -124,17 +166,22 @@ class ExtensionStorageMonitorTest : public ExtensionBrowserTest { | 
| } | 
| void WriteBytesExpectingNotification(const Extension* extension, | 
| - int num_bytes) { | 
| + int num_bytes, | 
| + const char* filesystem = "PERSISTENT") { | 
| int64_t previous_threshold = GetNextStorageThreshold(extension->id()); | 
| - WriteBytes(extension, num_bytes, true); | 
| - EXPECT_GT(GetNextStorageThreshold(extension->id()), previous_threshold); | 
| + WriteBytes(extension, num_bytes, filesystem, true); | 
| + ASSERT_GT(GetNextStorageThreshold(extension->id()), previous_threshold); | 
| } | 
| - void WriteBytesNotExpectingNotification(const Extension* extension, | 
| - int num_bytes) { | 
| - WriteBytes(extension, num_bytes, false); | 
| + void WriteBytesNotExpectingNotification( | 
| + const Extension* extension, | 
| + int num_bytes, | 
| + const char* filesystem = "PERSISTENT") { | 
| + WriteBytes(extension, num_bytes, filesystem, false); | 
| } | 
| + void SimulateProfileShutdown() { storage_monitor_->StopMonitoringAll(); } | 
| + | 
| private: | 
| void InitStorageMonitor() { | 
| storage_monitor_ = ExtensionStorageMonitor::Get(profile()); | 
| @@ -149,34 +196,73 @@ class ExtensionStorageMonitorTest : public ExtensionBrowserTest { | 
| storage_monitor_->observer_rate_ = base::TimeDelta(); | 
| } | 
| - // Write a number of bytes to persistent storage. | 
| - void WriteBytes(const Extension* extension, | 
| - int num_bytes, | 
| - bool expected_notification) { | 
| + // Write bytes for a hosted app page that's loaded the script: | 
| + // //chrome/test/data/extensions/storage_monitor/hosted_apps/common.js | 
| + void WriteBytesForHostedApp(const Extension* extension, | 
| + int num_bytes, | 
| + const std::string& filesystem) { | 
| + content::WebContents* web_contents = OpenApplication(AppLaunchParams( | 
| + profile(), extension, LAUNCH_CONTAINER_TAB, | 
| + WindowOpenDisposition::SINGLETON_TAB, extensions::SOURCE_TEST)); | 
| + | 
| + ASSERT_TRUE(WaitForLoadStop(web_contents)); | 
| + std::string result; | 
| + ASSERT_TRUE(ExecuteScriptAndExtractString( | 
| + web_contents, | 
| + base::StringPrintf(R"( | 
| + HostedAppWriteData(%s, %d) | 
| + .then(() => domAutomationController.send('write_done')) | 
| + .catch(e => domAutomationController.send('write_error: ' + e)); | 
| + )", | 
| + filesystem.c_str(), num_bytes), | 
| 
Devlin
2017/06/12 20:15:05
... this formatting's a bit odd.  Was this git cl
 
ncarter (slow)
2017/06/12 22:52:07
Fixed by extracting the literal to a local variabl
 | 
| + &result)); | 
| + | 
| + ASSERT_EQ("write_done", result); | 
| + } | 
| + | 
| + // Write bytes for the extension loaded from: | 
| + // //chrome/test/data/extensions/storage_monitor/write_data | 
| + void WriteBytesForExtension(const Extension* extension, int num_bytes) { | 
| ExtensionTestMessageListener launched_listener("launched", true); | 
| - ExtensionTestMessageListener write_complete_listener( | 
| - "write_complete", false); | 
| - NotificationObserver notification_observer( | 
| - GetNotificationId(extension->id())); | 
| + ExtensionTestMessageListener write_complete_listener("write_complete", | 
| + false); | 
| OpenApplication(AppLaunchParams(profile(), extension, LAUNCH_CONTAINER_NONE, | 
| WindowOpenDisposition::NEW_WINDOW, | 
| extensions::SOURCE_TEST)); | 
| + | 
| ASSERT_TRUE(launched_listener.WaitUntilSatisfied()); | 
| // Instruct the app to write |num_bytes| of data. | 
| launched_listener.Reply(base::IntToString(num_bytes)); | 
| ASSERT_TRUE(write_complete_listener.WaitUntilSatisfied()); | 
| + } | 
| + | 
| + // Write a number of bytes to persistent storage. | 
| + void WriteBytes(const Extension* extension, | 
| + int num_bytes, | 
| + const std::string& filesystem, | 
| + bool expected_notification) { | 
| + NotificationObserver notification_observer( | 
| + GetNotificationId(extension->id())); | 
| + | 
| + if (extension->is_hosted_app()) { | 
| + WriteBytesForHostedApp(extension, num_bytes, filesystem); | 
| + } else { | 
| + ASSERT_EQ("PERSISTENT", filesystem) << "Not implemented in the js code."; | 
| + WriteBytesForExtension(extension, num_bytes); | 
| + } | 
| if (expected_notification) { | 
| - EXPECT_TRUE(notification_observer.WaitForNotification()); | 
| + ASSERT_TRUE(notification_observer.WaitForNotification()); | 
| } else { | 
| base::RunLoop().RunUntilIdle(); | 
| - EXPECT_FALSE(notification_observer.HasReceivedNotification()); | 
| + ASSERT_FALSE(notification_observer.HasReceivedNotification()); | 
| } | 
| } | 
| ExtensionStorageMonitor* storage_monitor_; | 
| + std::vector<std::unique_ptr<TestExtensionDir>> temp_dirs_; | 
| }; | 
| // Control - No notifications should be shown if usage remains under the | 
| @@ -253,6 +339,67 @@ IN_PROC_BROWSER_TEST_F(ExtensionStorageMonitorTest, | 
| WriteBytesNotExpectingNotification(extension, GetInitialExtensionThreshold()); | 
| } | 
| +// Regression test for https://crbug.com/716426 | 
| +IN_PROC_BROWSER_TEST_F(ExtensionStorageMonitorTest, | 
| + HostedAppTemporaryFilesystem) { | 
| + ASSERT_TRUE(embedded_test_server()->Start()); | 
| + | 
| + GURL url = embedded_test_server()->GetURL( | 
| + "chromium.org", "/extensions/storage_monitor/hosted_apps/one/index.html"); | 
| + const Extension* app = | 
| + CreateHostedApp("Hosted App", url, {"unlimitedStorage"}); | 
| + | 
| + EXPECT_NO_FATAL_FAILURE(WriteBytesExpectingNotification( | 
| + app, GetInitialExtensionThreshold(), "TEMPORARY")); | 
| + EXPECT_NO_FATAL_FAILURE(WriteBytesNotExpectingNotification( | 
| + app, GetInitialExtensionThreshold(), "PERSISTENT")); | 
| + EXPECT_NO_FATAL_FAILURE(WriteBytesExpectingNotification( | 
| + app, GetInitialExtensionThreshold(), "TEMPORARY")); | 
| + | 
| + // Bug 716426 was a shutdown crash due to not removing a | 
| + // storage::StorageObserver registration before deleting the observer. To | 
| + // recreate that scenario, first disable the app (which leaks the observer | 
| + // registration), then simulate the step of profile exit where we delete the | 
| + // StorageObserver. | 
| + DisableExtension(app->id()); | 
| + SimulateProfileShutdown(); | 
| + | 
| + // Now generate more storage activity for the hosted app's temporary | 
| + // filesystem. Note that it's not a hosted app anymore -- it's just a webpage. | 
| + // Bug 716426 caused this to crash the browser. | 
| + EXPECT_NO_FATAL_FAILURE(WriteBytesNotExpectingNotification( | 
| + app, GetInitialExtensionThreshold(), "TEMPORARY")); | 
| +} | 
| + | 
| +// Exercises the case where two hosted apps are same-origin but have non- | 
| +// overlapping extents. Disabling one should not suppress storage monitoring for | 
| +// the other. | 
| +IN_PROC_BROWSER_TEST_F(ExtensionStorageMonitorTest, TwoHostedAppsInSameOrigin) { | 
| + ASSERT_TRUE(embedded_test_server()->Start()); | 
| + | 
| + GURL url1 = embedded_test_server()->GetURL( | 
| + "chromium.org", "/extensions/storage_monitor/hosted_apps/one/index.html"); | 
| + const Extension* app1 = CreateHostedApp("App 1", url1, {"unlimitedStorage"}); | 
| + | 
| + GURL url2 = embedded_test_server()->GetURL( | 
| + "chromium.org", "/extensions/storage_monitor/hosted_apps/two/index.html"); | 
| + const Extension* app2 = CreateHostedApp("App 2", url2, {"unlimitedStorage"}); | 
| + | 
| + EXPECT_EQ(url1.GetOrigin(), url2.GetOrigin()); | 
| + | 
| + EXPECT_NO_FATAL_FAILURE( | 
| + WriteBytesExpectingNotification(app1, GetInitialExtensionThreshold())); | 
| + EXPECT_NO_FATAL_FAILURE(WriteBytesExpectingNotification( | 
| + app2, GetInitialExtensionThreshold() * 2)); | 
| + | 
| + // Disable app2. We should still be monitoring the origin on behalf of app1. | 
| + DisableExtension(app2->id()); | 
| + | 
| + // Writing a bunch of data in app1 should trigger the warning. | 
| + EXPECT_NO_FATAL_FAILURE(WriteBytesExpectingNotification( | 
| + app1, GetInitialExtensionThreshold() * 4)); | 
| +} | 
| + | 
| // Verify that notifications are disabled when the user clicks the action button | 
| // in the notification. | 
| // Flaky: https://crbug.com/617801 |