Add ref count to service workers for extension API.

We need a way to keep a service worker alive
1) during extension function's request->response roundtrip completes.
2) when an event is about to be dispatched to a (stopped) service worker.

This CL shows a way to do #1. #2 can follow later.

The CL adds plumbing to expose functions to increment/decrement ref
counting to an ServiceWorkerVersion. This is done by adding a way to
add "external requests" to a ServiceWorkerVersion: when a worker has
external requests pending, it will be considered to be in working state,
i.e ServiceWorkerVersion::HasWork() will return true.
The public interface is exposed through ServiceWorkerContext. And the
interface methods expect a GUID/nonce for each such requests from service
worker renderer:
ServiceWorkerContext::StartingExternalRequest() and
ServiceWorkerContext::FinishedExternalRequest()

Extension APIs that are expected to be long running aren't handled in
this CL. For example: an extension API showing a dialog to user that
waits for user action.

BUG=602442
Test=There's no easy way to test it without tweaking the code, I've
used the following steps to make sure that we keep SW alive when an extension
API is in-flight:
Change the stop worker idle timeout and worker timeout to sth small, e.g. 3s.
Call an extension function that runs for 7s (> 3s + 3s). Without the CL, the
extension function's callback won't be called because the worker would shut
down after 6s.
The added test ServiceWorkerTest.WorkerRefCount tests this at a bit lower level:
by checking ref count (= count of external requests for a ServiceWorkerVersion).

Review-Url: https://codereview.chromium.org/2166523003
Cr-Commit-Position: refs/heads/master@{#425824}
diff --git a/chrome/browser/extensions/service_worker_apitest.cc b/chrome/browser/extensions/service_worker_apitest.cc
index 227584f..9db618f 100644
--- a/chrome/browser/extensions/service_worker_apitest.cc
+++ b/chrome/browser/extensions/service_worker_apitest.cc
@@ -24,6 +24,8 @@
 #include "content/public/browser/navigation_controller.h"
 #include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/permission_type.h"
+#include "content/public/browser/service_worker_context.h"
+#include "content/public/browser/storage_partition.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/common/origin_util.h"
@@ -162,6 +164,24 @@
     return ExtractInnerText(Navigate(url));
   }
 
+  size_t GetWorkerRefCount(const GURL& origin) {
+    content::ServiceWorkerContext* sw_context =
+        content::BrowserContext::GetDefaultStoragePartition(
+            browser()->profile())
+            ->GetServiceWorkerContext();
+    base::RunLoop run_loop;
+    size_t ref_count = 0;
+    auto set_ref_count = [](size_t* ref_count, base::RunLoop* run_loop,
+                            size_t external_request_count) {
+      *ref_count = external_request_count;
+      run_loop->Quit();
+    };
+    sw_context->CountExternalRequestsForTest(
+        origin, base::Bind(set_ref_count, &ref_count, &run_loop));
+    run_loop.Run();
+    return ref_count;
+  }
+
  private:
   // Sets the channel to "stable".
   // Not useful after we've opened extension Service Workers to stable
@@ -630,6 +650,63 @@
   EXPECT_EQ(starting_tab_count, browser()->tab_strip_model()->count());
 }
 
+// Tests that worker ref count increments while extension API function is
+// active.
+IN_PROC_BROWSER_TEST_F(ServiceWorkerTest, WorkerRefCount) {
+  // Extensions APIs from SW are only enabled on trunk.
+  ScopedCurrentChannel current_channel_override(version_info::Channel::UNKNOWN);
+  const Extension* extension = LoadExtensionWithFlags(
+      test_data_dir_.AppendASCII("service_worker/api_worker_ref_count"),
+      kFlagNone);
+  ASSERT_TRUE(extension);
+  ui_test_utils::NavigateToURL(browser(),
+                               extension->GetResourceURL("page.html"));
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+
+  ExtensionTestMessageListener worker_start_listener("WORKER STARTED", false);
+  worker_start_listener.set_failure_message("FAILURE");
+  ASSERT_TRUE(
+      content::ExecuteScript(web_contents, "window.runServiceWorker()"));
+  ASSERT_TRUE(worker_start_listener.WaitUntilSatisfied());
+
+  // Service worker should have no pending requests because it hasn't peformed
+  // any extension API request yet.
+  EXPECT_EQ(0u, GetWorkerRefCount(extension->url()));
+
+  ExtensionTestMessageListener worker_listener("CHECK_REF_COUNT", true);
+  worker_listener.set_failure_message("FAILURE");
+  ASSERT_TRUE(content::ExecuteScript(web_contents, "window.testSendMessage()"));
+  ASSERT_TRUE(worker_listener.WaitUntilSatisfied());
+
+  // Service worker should have exactly one pending request because
+  // chrome.test.sendMessage() API call is in-flight.
+  EXPECT_EQ(1u, GetWorkerRefCount(extension->url()));
+
+  // Peform another extension API request while one is ongoing.
+  {
+    ExtensionTestMessageListener listener("CHECK_REF_COUNT", true);
+    listener.set_failure_message("FAILURE");
+    ASSERT_TRUE(
+        content::ExecuteScript(web_contents, "window.testSendMessage()"));
+    ASSERT_TRUE(listener.WaitUntilSatisfied());
+
+    // Service worker currently has two extension API requests in-flight.
+    EXPECT_EQ(2u, GetWorkerRefCount(extension->url()));
+    // Finish executing the nested chrome.test.sendMessage() first.
+    listener.Reply("Hello world");
+  }
+
+  ExtensionTestMessageListener extension_listener("SUCCESS", false);
+  extension_listener.set_failure_message("FAILURE");
+  // Finish executing chrome.test.sendMessage().
+  worker_listener.Reply("Hello world");
+  ASSERT_TRUE(extension_listener.WaitUntilSatisfied());
+
+  // The ref count should drop to 0.
+  EXPECT_EQ(0u, GetWorkerRefCount(extension->url()));
+}
+
 // This test loads a web page that has an iframe pointing to a
 // chrome-extension:// URL. The URL is listed in the extension's
 // web_accessible_resources. Initially the iframe is served from the extension's