[Extensions] Don't dispatch a chrome update event unconditionally

Currently, we always dispatch the onInstalled event, regardless of
whether an extension has registered a listener.  The reason for this is
because if an extension is just installed (either a first installation
or an update), then it won't have registered its listeners, and we won't
know if we need to dispatch the event or not.

However, we also dispatch the onInstalled event in the case of chrome
updates (see crbug.com/148898).  In this case, there's no reason we
wouldn't know whether an extension has registered a listener for the
onInstalled event, so we can choose to only dispatch the event if a
listener is registered.

Add a test ensuring extensions are only woken when a listener is
registered.

BUG=451268

Review-Url: https://codereview.chromium.org/2941153002
Cr-Commit-Position: refs/heads/master@{#481583}
diff --git a/chrome/browser/extensions/chrome_extensions_browser_client.cc b/chrome/browser/extensions/chrome_extensions_browser_client.cc
index 0e03d73..c7aee0a 100644
--- a/chrome/browser/extensions/chrome_extensions_browser_client.cc
+++ b/chrome/browser/extensions/chrome_extensions_browser_client.cc
@@ -72,6 +72,14 @@
 
 namespace extensions {
 
+namespace {
+
+// If true, the extensions client will behave as though there is always a
+// new chrome update.
+bool g_did_chrome_update_for_testing = false;
+
+}  // namespace
+
 ChromeExtensionsBrowserClient::ChromeExtensionsBrowserClient() {
   process_manager_delegate_.reset(new ChromeProcessManagerDelegate);
   api_client_.reset(new ChromeExtensionsAPIClient);
@@ -209,6 +217,9 @@
   if (!extension_prefs)
     return false;
 
+  if (g_did_chrome_update_for_testing)
+    return true;
+
   // If we're inside a browser test, then assume prefs are all up to date.
   if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType))
     return false;
@@ -450,4 +461,11 @@
   return false;
 #endif
 }
+
+// static
+void ChromeExtensionsBrowserClient::set_did_chrome_update_for_testing(
+    bool did_update) {
+  g_did_chrome_update_for_testing = did_update;
+}
+
 }  // namespace extensions
diff --git a/chrome/browser/extensions/chrome_extensions_browser_client.h b/chrome/browser/extensions/chrome_extensions_browser_client.h
index 8f8e6e0b..1905cd3 100644
--- a/chrome/browser/extensions/chrome_extensions_browser_client.h
+++ b/chrome/browser/extensions/chrome_extensions_browser_client.h
@@ -126,6 +126,8 @@
   KioskDelegate* GetKioskDelegate() override;
   bool IsLockScreenContext(content::BrowserContext* context) override;
 
+  static void set_did_chrome_update_for_testing(bool did_update);
+
  private:
   friend struct base::LazyInstanceTraitsBase<ChromeExtensionsBrowserClient>;
 
diff --git a/chrome/browser/extensions/chrome_test_extension_loader.cc b/chrome/browser/extensions/chrome_test_extension_loader.cc
index aef672f..ed1485a 100644
--- a/chrome/browser/extensions/chrome_test_extension_loader.cc
+++ b/chrome/browser/extensions/chrome_test_extension_loader.cc
@@ -8,6 +8,7 @@
 
 #include "base/files/file_util.h"
 #include "base/run_loop.h"
+#include "base/threading/thread_restrictions.h"
 #include "chrome/browser/extensions/chrome_extension_test_notification_observer.h"
 #include "chrome/browser/extensions/crx_installer.h"
 #include "chrome/browser/extensions/extension_creator.h"
@@ -33,7 +34,13 @@
       extension_service_(extension_system_->extension_service()),
       extension_registry_(ExtensionRegistry::Get(browser_context)) {}
 
-ChromeTestExtensionLoader::~ChromeTestExtensionLoader() {}
+ChromeTestExtensionLoader::~ChromeTestExtensionLoader() {
+  // If there was a temporary directory created for a CRX, we need to clean it
+  // up before the member is destroyed so we can explicitly allow IO.
+  base::ThreadRestrictions::ScopedAllowIO allow_io;
+  if (temp_dir_.IsValid())
+    EXPECT_TRUE(temp_dir_.Delete());
+}
 
 scoped_refptr<const Extension> ChromeTestExtensionLoader::LoadExtension(
     const base::FilePath& path) {
@@ -99,6 +106,7 @@
 
 base::FilePath ChromeTestExtensionLoader::PackExtension(
     const base::FilePath& unpacked_path) {
+  base::ThreadRestrictions::ScopedAllowIO allow_io;
   if (!base::PathExists(unpacked_path)) {
     ADD_FAILURE() << "Unpacked path does not exist: " << unpacked_path.value();
     return base::FilePath();
diff --git a/chrome/browser/extensions/events_apitest.cc b/chrome/browser/extensions/events_apitest.cc
index 143a82d..da6bf40 100644
--- a/chrome/browser/extensions/events_apitest.cc
+++ b/chrome/browser/extensions/events_apitest.cc
@@ -4,12 +4,18 @@
 
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
+#include "chrome/browser/extensions/chrome_extension_test_notification_observer.h"
+#include "chrome/browser/extensions/chrome_extensions_browser_client.h"
+#include "chrome/browser/extensions/chrome_test_extension_loader.h"
 #include "chrome/browser/extensions/extension_apitest.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "extensions/browser/event_router.h"
 #include "extensions/browser/extension_registry.h"
+#include "extensions/browser/process_manager.h"
+#include "extensions/browser/process_manager_observer.h"
 #include "extensions/browser/scoped_ignore_content_verifier_for_test.h"
+#include "extensions/test/extension_test_message_listener.h"
 #include "extensions/test/result_catcher.h"
 
 namespace extensions {
@@ -211,4 +217,99 @@
   }
 }
 
+class ChromeUpdatesEventsApiTest : public EventsApiTest,
+                                   public ProcessManagerObserver {
+ public:
+  ChromeUpdatesEventsApiTest() {
+    // We set this in the constructor (rather than in a SetUp() method) because
+    // it needs to be done before any of the extensions system is created.
+    ChromeExtensionsBrowserClient::set_did_chrome_update_for_testing(true);
+  }
+
+  void SetUpOnMainThread() override {
+    EventsApiTest::SetUpOnMainThread();
+    ProcessManager* process_manager = ProcessManager::Get(profile());
+    ProcessManager::Get(profile())->AddObserver(this);
+    const ProcessManager::FrameSet& frames = process_manager->GetAllFrames();
+    for (auto* frame : frames) {
+      const Extension* extension =
+          process_manager->GetExtensionForRenderFrameHost(frame);
+      if (extension)
+        observed_extension_names_.insert(extension->name());
+    }
+
+    updates_listener_ =
+        base::MakeUnique<ExtensionTestMessageListener>("update event", false);
+    failure_listener_ =
+        base::MakeUnique<ExtensionTestMessageListener>("not listening", false);
+  }
+
+  void TearDownOnMainThread() override {
+    ProcessManager::Get(profile())->RemoveObserver(this);
+    ChromeExtensionsBrowserClient::set_did_chrome_update_for_testing(false);
+    EventsApiTest::TearDownOnMainThread();
+  }
+
+  void OnBackgroundHostCreated(ExtensionHost* host) override {
+    // Use name since it's more deterministic than ID.
+    observed_extension_names_.insert(host->extension()->name());
+  }
+
+  ExtensionTestMessageListener* updates_listener() {
+    return updates_listener_.get();
+  }
+  ExtensionTestMessageListener* failure_listener() {
+    return failure_listener_.get();
+  }
+  const std::set<std::string> observed_extension_names() const {
+    return observed_extension_names_;
+  }
+
+ private:
+  std::unique_ptr<ExtensionTestMessageListener> updates_listener_;
+  std::unique_ptr<ExtensionTestMessageListener> failure_listener_;
+
+  std::set<std::string> observed_extension_names_;
+
+  DISALLOW_COPY_AND_ASSIGN(ChromeUpdatesEventsApiTest);
+};
+
+IN_PROC_BROWSER_TEST_F(ChromeUpdatesEventsApiTest, PRE_ChromeUpdates) {
+  {
+    ChromeTestExtensionLoader loader(profile());
+    loader.set_pack_extension(true);
+    ResultCatcher catcher;
+    ASSERT_TRUE(loader.LoadExtension(
+        test_data_dir_.AppendASCII("lazy_events/chrome_updates/listener")));
+    EXPECT_TRUE(catcher.GetNextResult());
+  }
+  {
+    ChromeTestExtensionLoader loader(profile());
+    loader.set_pack_extension(true);
+    ResultCatcher catcher;
+    ASSERT_TRUE(loader.LoadExtension(
+        test_data_dir_.AppendASCII("lazy_events/chrome_updates/non_listener")));
+    EXPECT_TRUE(catcher.GetNextResult());
+  }
+}
+
+// Test that we only dispatch the onInstalled event triggered by a chrome update
+// to extensions that have a registered onInstalled listener.
+IN_PROC_BROWSER_TEST_F(ChromeUpdatesEventsApiTest, ChromeUpdates) {
+  ChromeExtensionTestNotificationObserver(browser())
+      .WaitForExtensionViewsToLoad();
+
+  // "chrome updates listener" registerd a listener for the onInstalled event,
+  // whereas "chrome updates non listener" did not. Only the
+  // "chrome updates listener" extension should have been woken up for the
+  // chrome update event.
+  EXPECT_TRUE(observed_extension_names().count("chrome updates listener"));
+  EXPECT_FALSE(observed_extension_names().count("chrome updates non listener"));
+
+  EXPECT_TRUE(updates_listener()->WaitUntilSatisfied());
+  content::RunAllPendingInMessageLoop();
+  content::RunAllBlockingPoolTasksUntilIdle();
+  EXPECT_FALSE(failure_listener()->was_satisfied());
+}
+
 }  // namespace extensions