| 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..c70d5253e128097d2e3016c9bb534ecee057f5c5 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) { | 
| +    auto dir = base::MakeUnique<TestExtensionDir>(); | 
| + | 
| +    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,71 @@ 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; | 
| +    const char* script = R"( | 
| +        HostedAppWriteData(%s, %d) | 
| +           .then(() => domAutomationController.send('write_done')) | 
| +           .catch(e => domAutomationController.send('write_error: ' + e)); | 
| +    )"; | 
| +    ASSERT_TRUE(ExecuteScriptAndExtractString( | 
| +        web_contents, base::StringPrintf(script, filesystem.c_str(), num_bytes), | 
| +        &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 +337,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 | 
|  |