[extension SW] Support lazy events from extension service workers.

This CL adds support to register and dispatch lazy events from/to
extension SW
(a) To register, we use SW's (unique) scope url to identify a SW
within an extension. Pass the information about scope url through
ServiceWorkerContextClient. Initially EventListener::worker_thread_id_
was used to identify whether an event is for SW context or not.
However, that is not enough for lazy service worker events, because
we need to persist lazy events in the browser process and
worker_thread_id_ is temporary for a running SW.
Change SW EventListener by adding EventListener::is_for_service_worker()
to support this.
(b) To dispatch, we need to start a worker before dispatching the
event. The content/ API:
ServiceWorkerContext::GetWorkerInfoAfterStartWorker()
was added to do that. Add ServiceWorkerTaskQueue (similar to
LazyBackgroundTaskQueue for lazy background pages) to dispatch
the event (a task) in extensions/ layer.

IPC changes:
In order to identify a (possibly stopped) SW, IPCs for lazy
service worker events were changed to accept service worker scope
urls. In order to not convolute existing lazy background page IPCs
(ExtensionHostMsg_Add/RemoveLazyListener), new ones have been
introduced: ExtensionHostMsg_Add/RemoveLazyServiceWorkerListener.

Other notable changes:
- LazyContextTaskQueue is introduced to express a lazy runnable
  context. Make the new ServiceWorkerTaskQueue and existing
  LazyBackgroundTaskQueue derived from that. One can post [1]
  PendingTasks to these contexts. Unfortunately becuase of many
  existing usages of LazyBackgroundTaskQueue::AddPendingTask,
  [1] was named AddPendingTaskToDispatchEvent to avoid collision.
- LazyContextTaskQueue provides a contexual information expressed
  as LazyContextTaskQueue::ContextInfo. This was introduced to
  avoid ExtensionHost from LazyContextTaskQueue (See
  LazyBackgroundTaskQueue::AddPendingTask)

BUG=721147
Test=After an extension service worker stops, it should still be
possible to dispatch extension events to it. That will wake up
the service worker.

Review-Url: https://codereview.chromium.org/2943583002
Cr-Commit-Position: refs/heads/master@{#483820}
diff --git a/chrome/browser/extensions/service_worker_apitest.cc b/chrome/browser/extensions/service_worker_apitest.cc
index 89ef3b65..5f005f8 100644
--- a/chrome/browser/extensions/service_worker_apitest.cc
+++ b/chrome/browser/extensions/service_worker_apitest.cc
@@ -36,6 +36,7 @@
 #include "content/public/common/page_type.h"
 #include "content/public/test/background_sync_test_util.h"
 #include "content/public/test/browser_test_utils.h"
+#include "content/public/test/service_worker_test_helpers.h"
 #include "extensions/browser/extension_host.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/process_manager.h"
@@ -43,6 +44,7 @@
 #include "extensions/test/extension_test_message_listener.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
+#include "url/url_constants.h"
 
 namespace extensions {
 
@@ -682,6 +684,40 @@
   ASSERT_EQ("chrome.tabs.onUpdated callback", result);
 }
 
+IN_PROC_BROWSER_TEST_F(ServiceWorkerTest, EventsToStoppedWorker) {
+  // 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/events_to_stopped_worker"),
+      kFlagNone);
+  ASSERT_TRUE(extension);
+  ui_test_utils::NavigateToURL(browser(),
+                               extension->GetResourceURL("page.html"));
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  {
+    std::string result;
+    ASSERT_TRUE(content::ExecuteScriptAndExtractString(
+        web_contents, "window.runServiceWorker()", &result));
+    ASSERT_EQ("ready", result);
+
+    base::RunLoop run_loop;
+    content::StoragePartition* storage_partition =
+        content::BrowserContext::GetDefaultStoragePartition(
+            browser()->profile());
+    content::StopServiceWorkerForPattern(
+        storage_partition->GetServiceWorkerContext(),
+        // The service worker is registered at the top level scope.
+        extension->url(), run_loop.QuitClosure());
+    run_loop.Run();
+  }
+
+  std::string result;
+  ASSERT_TRUE(content::ExecuteScriptAndExtractString(
+      web_contents, "window.createTabThenUpdate()", &result));
+  ASSERT_EQ("chrome.tabs.onUpdated callback", result);
+}
+
 // Tests that worker ref count increments while extension API function is
 // active.
 
diff --git a/chrome/renderer/chrome_content_renderer_client.cc b/chrome/renderer/chrome_content_renderer_client.cc
index fa44f43..3943d04 100644
--- a/chrome/renderer/chrome_content_renderer_client.cc
+++ b/chrome/renderer/chrome_content_renderer_client.cc
@@ -1504,22 +1504,24 @@
     DidInitializeServiceWorkerContextOnWorkerThread(
         v8::Local<v8::Context> context,
         int64_t service_worker_version_id,
-        const GURL& url) {
+        const GURL& service_worker_scope,
+        const GURL& script_url) {
 #if BUILDFLAG(ENABLE_EXTENSIONS)
   ChromeExtensionsRendererClient::GetInstance()
       ->extension_dispatcher()
       ->DidInitializeServiceWorkerContextOnWorkerThread(
-          context, service_worker_version_id, url);
+          context, service_worker_version_id, service_worker_scope, script_url);
 #endif
 }
 
 void ChromeContentRendererClient::WillDestroyServiceWorkerContextOnWorkerThread(
     v8::Local<v8::Context> context,
     int64_t service_worker_version_id,
-    const GURL& url) {
+    const GURL& service_worker_scope,
+    const GURL& script_url) {
 #if BUILDFLAG(ENABLE_EXTENSIONS)
   extensions::Dispatcher::WillDestroyServiceWorkerContextOnWorkerThread(
-      context, service_worker_version_id, url);
+      context, service_worker_version_id, service_worker_scope, script_url);
 #endif
 }
 
diff --git a/chrome/renderer/chrome_content_renderer_client.h b/chrome/renderer/chrome_content_renderer_client.h
index e9e6622..2e78bdd 100644
--- a/chrome/renderer/chrome_content_renderer_client.h
+++ b/chrome/renderer/chrome_content_renderer_client.h
@@ -187,11 +187,13 @@
   void DidInitializeServiceWorkerContextOnWorkerThread(
       v8::Local<v8::Context> context,
       int64_t service_worker_version_id,
-      const GURL& url) override;
+      const GURL& service_worker_scope,
+      const GURL& script_url) override;
   void WillDestroyServiceWorkerContextOnWorkerThread(
       v8::Local<v8::Context> context,
       int64_t service_worker_version_id,
-      const GURL& url) override;
+      const GURL& service_worker_scope,
+      const GURL& script_url) override;
   bool ShouldEnforceWebRTCRoutingPreferences() override;
   GURL OverrideFlashEmbedWithHTML(const GURL& url) override;
   std::unique_ptr<base::TaskScheduler::InitParams> GetTaskSchedulerInitParams()
diff --git a/chrome/test/data/extensions/api_test/service_worker/events_to_stopped_worker/manifest.json b/chrome/test/data/extensions/api_test/service_worker/events_to_stopped_worker/manifest.json
new file mode 100644
index 0000000..30bd2d73
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/service_worker/events_to_stopped_worker/manifest.json
@@ -0,0 +1,6 @@
+{
+  "name": "Service worker with chrome.tabs.onUpdated event",
+  "version": "1.0",
+  "manifest_version": 2,
+  "permissions": [ "tabs" ]
+}
diff --git a/chrome/test/data/extensions/api_test/service_worker/events_to_stopped_worker/on_updated.html b/chrome/test/data/extensions/api_test/service_worker/events_to_stopped_worker/on_updated.html
new file mode 100644
index 0000000..41cd8f6
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/service_worker/events_to_stopped_worker/on_updated.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <script src="on_updated.js"></script>
+</head>
+</html>
diff --git a/chrome/test/data/extensions/api_test/service_worker/events_to_stopped_worker/on_updated.js b/chrome/test/data/extensions/api_test/service_worker/events_to_stopped_worker/on_updated.js
new file mode 100644
index 0000000..5ce2a61
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/service_worker/events_to_stopped_worker/on_updated.js
@@ -0,0 +1,13 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+window.onload = function() {
+  setTimeout(function() {
+    document.title = 'foo';
+    setTimeout(function() {
+      document.title = 'bar';
+    }, 0);
+  }, 0);
+}
+
diff --git a/chrome/test/data/extensions/api_test/service_worker/events_to_stopped_worker/page.html b/chrome/test/data/extensions/api_test/service_worker/events_to_stopped_worker/page.html
new file mode 100644
index 0000000..f6e2de85
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/service_worker/events_to_stopped_worker/page.html
@@ -0,0 +1 @@
+<script src="page.js"></script>
diff --git a/chrome/test/data/extensions/api_test/service_worker/events_to_stopped_worker/page.js b/chrome/test/data/extensions/api_test/service_worker/events_to_stopped_worker/page.js
new file mode 100644
index 0000000..43c3454
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/service_worker/events_to_stopped_worker/page.js
@@ -0,0 +1,30 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var readyPromise = new Promise(function(resolve, reject) {
+  navigator.serviceWorker.register('sw.js').then(function() {
+    return navigator.serviceWorker.ready;
+  }).then(function(registration) {
+    resolve('ready');
+  }).catch(function(err) {
+    reject(err);
+  });
+});
+
+window.runServiceWorker = function() {
+  readyPromise.then(function(message) {
+    window.domAutomationController.send(message);
+  }).catch(function(err) {
+    window.domAutomationController.send('FAILURE');
+  });
+};
+
+window.createTabThenUpdate = function() {
+  navigator.serviceWorker.onmessage = function(e) {
+    // e.data -> 'chrome.tabs.onUpdated callback'.
+    window.domAutomationController.send(e.data);
+  };
+  var url = chrome.extension.getURL('on_updated.html');
+  chrome.tabs.create({url: url});
+};
diff --git a/chrome/test/data/extensions/api_test/service_worker/events_to_stopped_worker/sw.js b/chrome/test/data/extensions/api_test/service_worker/events_to_stopped_worker/sw.js
new file mode 100644
index 0000000..f6a7374c
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/service_worker/events_to_stopped_worker/sw.js
@@ -0,0 +1,55 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var expectedEventData;
+var capturedEventData;
+
+function expect(data) {
+  expectedEventData = data;
+  capturedEventData = [];
+}
+
+// Claim clients to send postMessage reply to them.
+self.addEventListener('activate', function(e) {
+  e.waitUntil(self.clients.claim());
+});
+
+function sendMessage(msg) {
+  clients.matchAll({}).then(function(clients) {
+    clients.forEach(function(client) {
+      client.postMessage(msg);
+    });
+  });
+}
+
+function checkExpectations() {
+  if (capturedEventData.length < expectedEventData.length) {
+    return;
+  }
+
+  var passed = JSON.stringify(expectedEventData) ==
+      JSON.stringify(capturedEventData);
+  if (passed) {
+    sendMessage('chrome.tabs.onUpdated callback');
+  } else {
+    sendMessage('FAILURE');
+  }
+}
+
+function addOnUpdatedListener() {
+  chrome.tabs.onUpdated.addListener(function(tabId, info, tab) {
+    capturedEventData.push(info);
+    checkExpectations();
+  });
+
+  var url = chrome.extension.getURL('on_updated.html');
+  expect([
+    {status: 'loading', 'url': url},
+    {status: 'complete'},
+    {title: 'foo'},
+    {title: 'bar'}
+  ]);
+};
+
+addOnUpdatedListener();
diff --git a/content/browser/service_worker/service_worker_context_wrapper.cc b/content/browser/service_worker/service_worker_context_wrapper.cc
index fbb00d4fc..eddab03 100644
--- a/content/browser/service_worker/service_worker_context_wrapper.cc
+++ b/content/browser/service_worker/service_worker_context_wrapper.cc
@@ -82,6 +82,41 @@
   registration->ActivateWaitingVersionWhenReady();
 }
 
+void DidStartWorker(
+    scoped_refptr<ServiceWorkerVersion> version,
+    ServiceWorkerContext::StartActiveWorkerCallback info_callback) {
+  EmbeddedWorkerInstance* instance = version->embedded_worker();
+  std::move(info_callback).Run(instance->process_id(), instance->thread_id());
+}
+
+void DidFailStartWorker(base::OnceClosure error_callback,
+                        ServiceWorkerStatusCode code) {
+  std::move(error_callback).Run();
+}
+
+void FoundReadyRegistrationForStartActiveWorker(
+    ServiceWorkerContext::StartActiveWorkerCallback info_callback,
+    base::OnceClosure failure_callback,
+    ServiceWorkerStatusCode service_worker_status,
+    scoped_refptr<ServiceWorkerRegistration> service_worker_registration) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  if (service_worker_status == SERVICE_WORKER_OK) {
+    // Note: There might be a remote possibility that
+    // |service_worker_registration|'s active version might change between here
+    // and DidStartWorker, so bind |active_version| to RunAfterStartWorker.
+    scoped_refptr<ServiceWorkerVersion> active_version =
+        service_worker_registration->active_version();
+    DCHECK(active_version.get());
+    active_version->RunAfterStartWorker(
+        ServiceWorkerMetrics::EventType::EXTERNAL_REQUEST,
+        base::Bind(&DidStartWorker, active_version,
+                   base::Passed(&info_callback)),
+        base::Bind(&DidFailStartWorker, base::Passed(&failure_callback)));
+  } else {
+    std::move(failure_callback).Run();
+  }
+}
+
 }  // namespace
 
 void ServiceWorkerContext::AddExcludedHeadersForFetchEvent(
@@ -841,6 +876,17 @@
   return version->FinishExternalRequest(request_uuid);
 }
 
+void ServiceWorkerContextWrapper::StartActiveWorkerForPattern(
+    const GURL& pattern,
+    StartActiveWorkerCallback info_callback,
+    base::OnceClosure failure_callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  FindReadyRegistrationForPattern(
+      pattern, base::Bind(&FoundReadyRegistrationForStartActiveWorker,
+                          base::Passed(&info_callback),
+                          base::Passed(&failure_callback)));
+}
+
 void ServiceWorkerContextWrapper::DidDeleteAndStartOver(
     ServiceWorkerStatusCode status) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
diff --git a/content/browser/service_worker/service_worker_context_wrapper.h b/content/browser/service_worker/service_worker_context_wrapper.h
index a41d245..aea3fae 100644
--- a/content/browser/service_worker/service_worker_context_wrapper.h
+++ b/content/browser/service_worker/service_worker_context_wrapper.h
@@ -121,6 +121,9 @@
   void StartServiceWorkerForNavigationHint(
       const GURL& document_url,
       const StartServiceWorkerForNavigationHintCallback& callback) override;
+  void StartActiveWorkerForPattern(const GURL& pattern,
+                                   StartActiveWorkerCallback info_callback,
+                                   base::OnceClosure failure_callback) override;
 
   // These methods must only be called from the IO thread.
   ServiceWorkerRegistration* GetLiveRegistration(int64_t registration_id);
diff --git a/content/public/browser/service_worker_context.h b/content/public/browser/service_worker_context.h
index 9a9aa0a..5a105ad 100644
--- a/content/public/browser/service_worker_context.h
+++ b/content/public/browser/service_worker_context.h
@@ -58,6 +58,9 @@
   using StartServiceWorkerForNavigationHintCallback =
       base::Callback<void(StartServiceWorkerForNavigationHintResult result)>;
 
+  using StartActiveWorkerCallback =
+      base::OnceCallback<void(int process_id, int thread_id)>;
+
   // Registers the header name which should not be passed to the ServiceWorker.
   // Must be called from the IO thread.
   CONTENT_EXPORT static void AddExcludedHeadersForFetchEvent(
@@ -107,6 +110,15 @@
   virtual bool FinishedExternalRequest(int64_t service_worker_version_id,
                                        const std::string& request_uuid) = 0;
 
+  // Starts the active worker of the registration whose scope is |pattern|.
+  // |info_callback| is passed the worker's render process id and thread id.
+  //
+  // Must be called on IO thread.
+  virtual void StartActiveWorkerForPattern(
+      const GURL& pattern,
+      StartActiveWorkerCallback info_callback,
+      base::OnceClosure failure_callback) = 0;
+
   // Equivalent to calling navigator.serviceWorker.unregister(pattern) from a
   // renderer, except that |pattern| is an absolute URL instead of relative to
   // some current origin.  |callback| is passed true when the JS promise is
diff --git a/content/public/renderer/content_renderer_client.h b/content/public/renderer/content_renderer_client.h
index 1b09c28..9246fd8 100644
--- a/content/public/renderer/content_renderer_client.h
+++ b/content/public/renderer/content_renderer_client.h
@@ -346,14 +346,16 @@
   virtual void DidInitializeServiceWorkerContextOnWorkerThread(
       v8::Local<v8::Context> context,
       int64_t service_worker_version_id,
-      const GURL& url) {}
+      const GURL& service_worker_scope,
+      const GURL& script_url) {}
 
   // Notifies that a service worker context will be destroyed. This function
   // is called from the worker thread.
   virtual void WillDestroyServiceWorkerContextOnWorkerThread(
       v8::Local<v8::Context> context,
       int64_t service_worker_version_id,
-      const GURL& url) {}
+      const GURL& service_worker_scope,
+      const GURL& script_url) {}
 
   // Whether this renderer should enforce preferences related to the WebRTC
   // routing logic, i.e. allowing multiple routes and non-proxied UDP.
diff --git a/content/public/test/service_worker_test_helpers.cc b/content/public/test/service_worker_test_helpers.cc
new file mode 100644
index 0000000..f8bf25a
--- /dev/null
+++ b/content/public/test/service_worker_test_helpers.cc
@@ -0,0 +1,106 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/public/test/service_worker_test_helpers.h"
+
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "content/browser/service_worker/service_worker_context_core_observer.h"
+#include "content/browser/service_worker/service_worker_context_wrapper.h"
+#include "content/public/browser/browser_thread.h"
+#include "url/gurl.h"
+
+namespace content {
+
+namespace {
+
+class StoppedObserver : public base::RefCountedThreadSafe<StoppedObserver> {
+ public:
+  StoppedObserver(ServiceWorkerContextWrapper* context,
+                  int64_t service_worker_version_id,
+                  base::OnceClosure completion_callback_ui)
+      : inner_observer_(context,
+                        service_worker_version_id,
+                        // Adds a ref to StoppedObserver to keep |this| around
+                        // until the worker is stopped.
+                        base::BindOnce(&StoppedObserver::OnStopped, this)),
+        completion_callback_ui_(std::move(completion_callback_ui)) {}
+
+ private:
+  friend class base::RefCountedThreadSafe<StoppedObserver>;
+  ~StoppedObserver() {}
+  class Observer : public ServiceWorkerContextCoreObserver {
+   public:
+    Observer(ServiceWorkerContextWrapper* context,
+             int64_t service_worker_version_id,
+             base::OnceClosure stopped_callback)
+        : context_(context),
+          version_id_(service_worker_version_id),
+          stopped_callback_(std::move(stopped_callback)) {
+      context_->AddObserver(this);
+    }
+
+    // ServiceWorkerContextCoreObserver:
+    void OnRunningStateChanged(int64_t version_id,
+                               EmbeddedWorkerStatus status) override {
+      DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+      if (version_id != version_id_ || status != EmbeddedWorkerStatus::STOPPED)
+        return;
+      std::move(stopped_callback_).Run();
+    }
+    ~Observer() override { context_->RemoveObserver(this); }
+
+   private:
+    ServiceWorkerContextWrapper* const context_;
+    int64_t version_id_;
+    base::OnceClosure stopped_callback_;
+  };
+
+  void OnStopped() {
+    if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+      BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+                              base::Bind(&StoppedObserver::OnStopped, this));
+      return;
+    }
+    std::move(completion_callback_ui_).Run();
+  }
+
+  Observer inner_observer_;
+  base::OnceClosure completion_callback_ui_;
+
+  DISALLOW_COPY_AND_ASSIGN(StoppedObserver);
+};
+
+void FoundReadyRegistration(
+    ServiceWorkerContextWrapper* context_wrapper,
+    base::OnceClosure completion_callback,
+    ServiceWorkerStatusCode service_worker_status,
+    scoped_refptr<ServiceWorkerRegistration> service_worker_registration) {
+  DCHECK_EQ(SERVICE_WORKER_OK, service_worker_status);
+  int64_t version_id =
+      service_worker_registration->active_version()->version_id();
+  scoped_refptr<StoppedObserver> observer(new StoppedObserver(
+      context_wrapper, version_id, std::move(completion_callback)));
+  service_worker_registration->active_version()->embedded_worker()->Stop();
+}
+
+}  // namespace
+
+void StopServiceWorkerForPattern(ServiceWorkerContext* context,
+                                 const GURL& pattern,
+                                 base::OnceClosure completion_callback_ui) {
+  if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
+    BrowserThread::PostTask(
+        BrowserThread::IO, FROM_HERE,
+        base::Bind(&StopServiceWorkerForPattern, context, pattern,
+                   base::Passed(&completion_callback_ui)));
+    return;
+  }
+  auto* context_wrapper = static_cast<ServiceWorkerContextWrapper*>(context);
+  context_wrapper->FindReadyRegistrationForPattern(
+      pattern, base::Bind(&FoundReadyRegistration, context_wrapper,
+                          base::Passed(&completion_callback_ui)));
+}
+
+}  // namespace content
diff --git a/content/public/test/service_worker_test_helpers.h b/content/public/test/service_worker_test_helpers.h
new file mode 100644
index 0000000..754d8b1
--- /dev/null
+++ b/content/public/test/service_worker_test_helpers.h
@@ -0,0 +1,25 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_PUBLIC_TEST_SERVICE_WORKER_TEST_OBSERVER_H_
+#define CONTENT_PUBLIC_TEST_SERVICE_WORKER_TEST_OBSERVER_H_
+
+#include "base/callback_forward.h"
+
+class GURL;
+namespace content {
+
+class ServiceWorkerContext;
+
+// Stops the active service worker of the registration whose scope is |pattern|,
+// and calls |complete_callback_ui| callback on UI thread when done.
+//
+// Can be called from UI/IO thread.
+void StopServiceWorkerForPattern(ServiceWorkerContext* context,
+                                 const GURL& pattern,
+                                 base::OnceClosure complete_callback_ui);
+
+}  // namespace content
+
+#endif  // CONTENT_PUBLIC_TEST_SERVICE_WORKER_TEST_OBSERVER_H_
diff --git a/content/renderer/service_worker/service_worker_context_client.cc b/content/renderer/service_worker/service_worker_context_client.cc
index f9ea547..c939a50 100644
--- a/content/renderer/service_worker/service_worker_context_client.cc
+++ b/content/renderer/service_worker/service_worker_context_client.cc
@@ -744,7 +744,8 @@
   GetContentClient()
       ->renderer()
       ->DidInitializeServiceWorkerContextOnWorkerThread(
-          context, service_worker_version_id_, script_url_);
+          context, service_worker_version_id_, service_worker_scope_,
+          script_url_);
 }
 
 void ServiceWorkerContextClient::WillDestroyWorkerContext(
@@ -778,7 +779,7 @@
   g_worker_client_tls.Pointer()->Set(NULL);
 
   GetContentClient()->renderer()->WillDestroyServiceWorkerContextOnWorkerThread(
-      context, service_worker_version_id_, script_url_);
+      context, service_worker_version_id_, service_worker_scope_, script_url_);
 }
 
 void ServiceWorkerContextClient::WorkerContextDestroyed() {
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 785d142..c44a09e 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -100,6 +100,8 @@
     "../public/test/render_view_test.h",
     "../public/test/repeated_notification_observer.cc",
     "../public/test/repeated_notification_observer.h",
+    "../public/test/service_worker_test_helpers.cc",
+    "../public/test/service_worker_test_helpers.h",
     "../public/test/test_browser_context.cc",
     "../public/test/test_browser_context.h",
     "../public/test/test_browser_thread.cc",
diff --git a/extensions/browser/BUILD.gn b/extensions/browser/BUILD.gn
index 61e91c65..1b45fa4 100644
--- a/extensions/browser/BUILD.gn
+++ b/extensions/browser/BUILD.gn
@@ -253,6 +253,7 @@
     "lazy_background_task_queue_factory.h",
     "lazy_context_id.cc",
     "lazy_context_id.h",
+    "lazy_context_task_queue.h",
     "load_monitoring_extension_host_queue.cc",
     "load_monitoring_extension_host_queue.h",
     "management_policy.cc",
@@ -302,6 +303,10 @@
     "serial_extension_host_queue.h",
     "service_worker_manager.cc",
     "service_worker_manager.h",
+    "service_worker_task_queue.cc",
+    "service_worker_task_queue.h",
+    "service_worker_task_queue_factory.cc",
+    "service_worker_task_queue_factory.h",
     "state_store.cc",
     "state_store.h",
     "suggest_permission_util.cc",
diff --git a/extensions/browser/event_listener_map.cc b/extensions/browser/event_listener_map.cc
index b60e5bf..f666659 100644
--- a/extensions/browser/event_listener_map.cc
+++ b/extensions/browser/event_listener_map.cc
@@ -30,7 +30,7 @@
     content::RenderProcessHost* process,
     std::unique_ptr<base::DictionaryValue> filter) {
   return base::WrapUnique(new EventListener(event_name, extension_id, GURL(),
-                                            process, kNonWorkerThreadId,
+                                            process, false, kNonWorkerThreadId,
                                             std::move(filter)));
 }
 
@@ -46,18 +46,19 @@
   // we dispatched events to processes more intelligently this could be avoided.
   return base::WrapUnique(new EventListener(
       event_name, ExtensionId(), url::Origin(listener_url).GetURL(), process,
-      kNonWorkerThreadId, std::move(filter)));
+      false, kNonWorkerThreadId, std::move(filter)));
 }
 
 std::unique_ptr<EventListener> EventListener::ForExtensionServiceWorker(
     const std::string& event_name,
     const std::string& extension_id,
     content::RenderProcessHost* process,
+    const GURL& service_worker_scope,
     int worker_thread_id,
     std::unique_ptr<base::DictionaryValue> filter) {
-  return base::WrapUnique(new EventListener(event_name, extension_id, GURL(),
-                                            process, worker_thread_id,
-                                            std::move(filter)));
+  return base::WrapUnique(
+      new EventListener(event_name, extension_id, service_worker_scope, process,
+                        true, worker_thread_id, std::move(filter)));
 }
 
 EventListener::~EventListener() {}
@@ -69,6 +70,7 @@
   return event_name_ == other->event_name_ &&
          extension_id_ == other->extension_id_ &&
          listener_url_ == other->listener_url_ && process_ == other->process_ &&
+         is_for_service_worker_ == other->is_for_service_worker_ &&
          worker_thread_id_ == other->worker_thread_id_ &&
          ((!!filter_.get()) == (!!other->filter_.get())) &&
          (!filter_.get() || filter_->Equals(other->filter_.get()));
@@ -78,19 +80,15 @@
   std::unique_ptr<DictionaryValue> filter_copy;
   if (filter_)
     filter_copy = filter_->CreateDeepCopy();
-  return base::WrapUnique(
-      new EventListener(event_name_, extension_id_, listener_url_, process_,
-                        worker_thread_id_, std::move(filter_copy)));
+  return base::WrapUnique(new EventListener(
+      event_name_, extension_id_, listener_url_, process_,
+      is_for_service_worker_, worker_thread_id_, std::move(filter_copy)));
 }
 
 bool EventListener::IsLazy() const {
   return !process_;
 }
 
-bool EventListener::IsForServiceWorker() const {
-  return worker_thread_id_ != kNonWorkerThreadId;
-}
-
 void EventListener::MakeLazy() {
   DCHECK_EQ(worker_thread_id_, kNonWorkerThreadId);
   process_ = nullptr;
@@ -104,12 +102,14 @@
                              const std::string& extension_id,
                              const GURL& listener_url,
                              content::RenderProcessHost* process,
+                             bool is_for_service_worker,
                              int worker_thread_id,
                              std::unique_ptr<DictionaryValue> filter)
     : event_name_(event_name),
       extension_id_(extension_id),
       listener_url_(listener_url),
       process_(process),
+      is_for_service_worker_(is_for_service_worker),
       worker_thread_id_(worker_thread_id),
       filter_(std::move(filter)),
       matcher_id_(-1) {}
diff --git a/extensions/browser/event_listener_map.h b/extensions/browser/event_listener_map.h
index ea25ea6..5bebe7c 100644
--- a/extensions/browser/event_listener_map.h
+++ b/extensions/browser/event_listener_map.h
@@ -67,6 +67,7 @@
       const std::string& event_name,
       const std::string& extension_id,
       content::RenderProcessHost* process,
+      const GURL& service_worker_scope,
       int worker_thread_id,
       std::unique_ptr<base::DictionaryValue> filter);
 
@@ -80,9 +81,9 @@
   // or an extension service worker. This listener does not have |process_|.
   bool IsLazy() const;
 
-  // Returns true if this listener was registered for an extension service
-  // worker.
-  bool IsForServiceWorker() const;
+  // Returns true if this listener (lazy or not) was registered for an extension
+  // service worker.
+  bool is_for_service_worker() const { return is_for_service_worker_; }
 
   // Modifies this listener to be a lazy listener, clearing process references.
   void MakeLazy();
@@ -105,6 +106,7 @@
                 const std::string& extension_id,
                 const GURL& listener_url,
                 content::RenderProcessHost* process,
+                bool is_for_service_worker,
                 int worker_thread_id,
                 std::unique_ptr<base::DictionaryValue> filter);
 
@@ -112,7 +114,15 @@
   const std::string extension_id_;
   const GURL listener_url_;
   content::RenderProcessHost* process_;
+
+  const bool is_for_service_worker_ = false;
+
+  // If this listener is for a service worker (i.e.
+  // is_for_service_worker_ = true) and the worker is in running state, then
+  // this is the worker's thread id in the worker |process_|. For lazy service
+  // worker events, this will be kNonWorkerThreadId.
   const int worker_thread_id_;
+
   std::unique_ptr<base::DictionaryValue> filter_;
   EventFilter::MatcherID matcher_id_;  // -1 if unset.
 
diff --git a/extensions/browser/event_router.cc b/extensions/browser/event_router.cc
index 04d38f0..210930d 100644
--- a/extensions/browser/event_router.cc
+++ b/extensions/browser/event_router.cc
@@ -181,9 +181,11 @@
     const std::string& event_name,
     content::RenderProcessHost* process,
     const ExtensionId& extension_id,
+    const GURL& service_worker_scope,
     int worker_thread_id) {
   listeners_.AddListener(EventListener::ForExtensionServiceWorker(
-      event_name, extension_id, process, worker_thread_id, nullptr));
+      event_name, extension_id, process, service_worker_scope, worker_thread_id,
+      nullptr));
 }
 
 void EventRouter::RemoveEventListener(const std::string& event_name,
@@ -198,10 +200,12 @@
     const std::string& event_name,
     content::RenderProcessHost* process,
     const ExtensionId& extension_id,
+    const GURL& service_worker_scope,
     int worker_thread_id) {
   std::unique_ptr<EventListener> listener =
-      EventListener::ForExtensionServiceWorker(
-          event_name, extension_id, process, worker_thread_id, nullptr);
+      EventListener::ForExtensionServiceWorker(event_name, extension_id,
+                                               process, service_worker_scope,
+                                               worker_thread_id, nullptr);
   listeners_.RemoveListener(listener.get());
 }
 
@@ -281,26 +285,42 @@
 
 void EventRouter::AddLazyEventListener(const std::string& event_name,
                                        const ExtensionId& extension_id) {
-  AddLazyEventListenerImpl(event_name, extension_id, kNonWorkerThreadId);
+  AddLazyEventListenerImpl(
+      EventListener::ForExtension(event_name, extension_id, nullptr, nullptr),
+      RegisteredEventType::kLazy);
 }
 
 void EventRouter::RemoveLazyEventListener(const std::string& event_name,
                                           const ExtensionId& extension_id) {
-  RemoveLazyEventListenerImpl(event_name, extension_id, kNonWorkerThreadId);
+  RemoveLazyEventListenerImpl(
+      EventListener::ForExtension(event_name, extension_id, nullptr, nullptr),
+      RegisteredEventType::kLazy);
 }
 
 void EventRouter::AddLazyServiceWorkerEventListener(
     const std::string& event_name,
     const ExtensionId& extension_id,
-    int worker_thread_id) {
-  AddLazyEventListenerImpl(event_name, extension_id, worker_thread_id);
+    const GURL& service_worker_scope) {
+  std::unique_ptr<EventListener> listener =
+      EventListener::ForExtensionServiceWorker(
+          event_name, extension_id, nullptr, service_worker_scope,
+          kNonWorkerThreadId,  // Lazy, without worker thread id.
+          nullptr);
+  AddLazyEventListenerImpl(std::move(listener),
+                           RegisteredEventType::kServiceWorker);
 }
 
 void EventRouter::RemoveLazyServiceWorkerEventListener(
     const std::string& event_name,
     const ExtensionId& extension_id,
-    int worker_thread_id) {
-  RemoveLazyEventListenerImpl(event_name, extension_id, worker_thread_id);
+    const GURL& service_worker_scope) {
+  std::unique_ptr<EventListener> listener =
+      EventListener::ForExtensionServiceWorker(
+          event_name, extension_id, nullptr, service_worker_scope,
+          kNonWorkerThreadId,  // Lazy, without worker thread id.
+          nullptr);
+  RemoveLazyEventListenerImpl(std::move(listener),
+                              RegisteredEventType::kServiceWorker);
 }
 
 // TODO(lazyboy): Support filters for extension SW events.
@@ -462,10 +482,14 @@
         restrict_to_extension_id != listener->extension_id()) {
       continue;
     }
-    // TODO(lazyboy): Support lazy listeners for extension SW events.
-    if (listener->IsLazy() && !listener->IsForServiceWorker()) {
-      lazy_event_dispatcher.DispatchToEventPage(listener->extension_id(),
-                                                listener->filter());
+    if (listener->IsLazy()) {
+      if (listener->is_for_service_worker()) {
+        lazy_event_dispatcher.DispatchToServiceWorker(
+            listener->extension_id(), listener->listener_url(), nullptr);
+      } else {
+        lazy_event_dispatcher.DispatchToEventPage(listener->extension_id(),
+                                                  listener->filter());
+      }
     }
   }
 
@@ -564,6 +588,9 @@
                            event->event_args.get(), event->user_gesture,
                            event->filter_info);
 
+  // TODO(lazyboy): This is wrong for extensions SW events. We need to:
+  // 1. Increment worker ref count
+  // 2. Add EventAck IPC to decrement that ref count.
   if (extension) {
     ReportEvent(event->histogram_value, extension, did_enqueue);
     IncrementInFlightEvents(listener_context, extension, event_id,
@@ -679,17 +706,18 @@
   }
 }
 
-void EventRouter::DispatchPendingEvent(const linked_ptr<Event>& event,
-                                       ExtensionHost* host) {
-  if (!host)
+void EventRouter::DispatchPendingEvent(
+    const linked_ptr<Event>& event,
+    std::unique_ptr<LazyContextTaskQueue::ContextInfo> params) {
+  if (!params)
     return;
 
-  if (listeners_.HasProcessListener(host->render_process_host(),
-                                    kNonWorkerThreadId,
-                                    host->extension()->id())) {
-    DispatchEventToProcess(host->extension()->id(), host->GetURL(),
-                           host->render_process_host(), kNonWorkerThreadId,
-                           event, nullptr, true /* did_enqueue */);
+  if (listeners_.HasProcessListener(params->render_process_host,
+                                    params->worker_thread_id,
+                                    params->extension_id)) {
+    DispatchEventToProcess(
+        params->extension_id, params->url, params->render_process_host,
+        params->worker_thread_id, event, nullptr, true /* did_enqueue */);
   }
 }
 
@@ -745,21 +773,13 @@
   listeners_.RemoveListenersForExtension(extension->id());
 }
 
-void EventRouter::AddLazyEventListenerImpl(const std::string& event_name,
-                                           const ExtensionId& extension_id,
-                                           int worker_thread_id) {
-  bool is_for_service_worker = worker_thread_id != kNonWorkerThreadId;
-  bool is_new = listeners_.AddListener(
-      is_for_service_worker
-          ? EventListener::ForExtensionServiceWorker(
-                event_name, extension_id, nullptr, worker_thread_id, nullptr)
-          : EventListener::ForExtension(event_name, extension_id, nullptr,
-                                        nullptr));
-
+void EventRouter::AddLazyEventListenerImpl(
+    std::unique_ptr<EventListener> listener,
+    RegisteredEventType type) {
+  const ExtensionId extension_id = listener->extension_id();
+  const std::string event_name = listener->event_name();
+  bool is_new = listeners_.AddListener(std::move(listener));
   if (is_new) {
-    RegisteredEventType type = is_for_service_worker
-                                   ? RegisteredEventType::kServiceWorker
-                                   : RegisteredEventType::kLazy;
     std::set<std::string> events = GetRegisteredEvents(extension_id, type);
     bool prefs_is_new = events.insert(event_name).second;
     if (prefs_is_new)
@@ -767,22 +787,13 @@
   }
 }
 
-void EventRouter::RemoveLazyEventListenerImpl(const std::string& event_name,
-                                              const ExtensionId& extension_id,
-                                              int worker_thread_id) {
-  bool is_for_service_worker = worker_thread_id != kNonWorkerThreadId;
-  std::unique_ptr<EventListener> listener =
-      is_for_service_worker
-          ? EventListener::ForExtensionServiceWorker(
-                event_name, extension_id, nullptr, worker_thread_id, nullptr)
-          : EventListener::ForExtension(event_name, extension_id, nullptr,
-                                        nullptr);
+void EventRouter::RemoveLazyEventListenerImpl(
+    std::unique_ptr<EventListener> listener,
+    RegisteredEventType type) {
+  const ExtensionId extension_id = listener->extension_id();
+  const std::string event_name = listener->event_name();
   bool did_exist = listeners_.RemoveListener(listener.get());
-
   if (did_exist) {
-    RegisteredEventType type = is_for_service_worker
-                                   ? RegisteredEventType::kServiceWorker
-                                   : RegisteredEventType::kLazy;
     std::set<std::string> events = GetRegisteredEvents(extension_id, type);
     bool prefs_did_exist = events.erase(event_name) > 0;
     DCHECK(prefs_did_exist);
diff --git a/extensions/browser/event_router.h b/extensions/browser/event_router.h
index f0c5d0fc..d63152d 100644
--- a/extensions/browser/event_router.h
+++ b/extensions/browser/event_router.h
@@ -23,6 +23,7 @@
 #include "extensions/browser/events/lazy_event_dispatch_util.h"
 #include "extensions/browser/extension_event_histogram_value.h"
 #include "extensions/browser/extension_registry_observer.h"
+#include "extensions/browser/lazy_context_task_queue.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/event_filtering_info.h"
 #include "ipc/ipc_sender.h"
@@ -37,7 +38,6 @@
 
 namespace extensions {
 class Extension;
-class ExtensionHost;
 class ExtensionPrefs;
 class ExtensionRegistry;
 
@@ -125,6 +125,7 @@
   void AddServiceWorkerEventListener(const std::string& event_name,
                                      content::RenderProcessHost* process,
                                      const ExtensionId& extension_id,
+                                     const GURL& service_worker_scope,
                                      int worker_thread_id);
   void RemoveEventListener(const std::string& event_name,
                            content::RenderProcessHost* process,
@@ -132,6 +133,7 @@
   void RemoveServiceWorkerEventListener(const std::string& event_name,
                                         content::RenderProcessHost* process,
                                         const ExtensionId& extension_id,
+                                        const GURL& service_worker_scope,
                                         int worker_thread_id);
 
   // Add or remove a URL as an event listener for |event_name|.
@@ -164,10 +166,10 @@
   // workers.
   void AddLazyServiceWorkerEventListener(const std::string& event_name,
                                          const ExtensionId& extension_id,
-                                         int worker_thread_id);
+                                         const GURL& service_worker_scope);
   void RemoveLazyServiceWorkerEventListener(const std::string& event_name,
                                             const ExtensionId& extension_id,
-                                            int worker_thread_id);
+                                            const GURL& service_worker_scope);
 
   // If |add_lazy_listener| is true also add the lazy version of this listener.
   void AddFilteredEventListener(const std::string& event_name,
@@ -268,12 +270,10 @@
                            const Extension* extension,
                            UnloadedExtensionReason reason) override;
 
-  void AddLazyEventListenerImpl(const std::string& event_name,
-                                const ExtensionId& extension_id,
-                                int worker_thread_id);
-  void RemoveLazyEventListenerImpl(const std::string& event_name,
-                                   const ExtensionId& extension_id,
-                                   int worker_thread_id);
+  void AddLazyEventListenerImpl(std::unique_ptr<EventListener> listener,
+                                RegisteredEventType type);
+  void RemoveLazyEventListenerImpl(std::unique_ptr<EventListener> listener,
+                                   RegisteredEventType type);
 
   // Shared by all event dispatch methods. If |restrict_to_extension_id| is
   // empty, the event is broadcast.  An event that just came off the pending
@@ -321,8 +321,9 @@
       events::HistogramValue histogram_value,
       const std::string& event_name);
 
-  void DispatchPendingEvent(const linked_ptr<Event>& event,
-                            ExtensionHost* host);
+  void DispatchPendingEvent(
+      const linked_ptr<Event>& event,
+      std::unique_ptr<LazyContextTaskQueue::ContextInfo> params);
 
   // Implementation of EventListenerMap::Delegate.
   void OnListenerAdded(const EventListener* listener) override;
diff --git a/extensions/browser/events/lazy_event_dispatcher.cc b/extensions/browser/events/lazy_event_dispatcher.cc
index 60dbbe4..7f4da36 100644
--- a/extensions/browser/events/lazy_event_dispatcher.cc
+++ b/extensions/browser/events/lazy_event_dispatcher.cc
@@ -11,6 +11,7 @@
 #include "extensions/browser/extensions_browser_client.h"
 #include "extensions/browser/lazy_background_task_queue.h"
 #include "extensions/browser/lazy_context_id.h"
+#include "extensions/browser/service_worker_task_queue.h"
 #include "extensions/common/manifest_handlers/incognito_info.h"
 
 using content::BrowserContext;
@@ -34,11 +35,27 @@
   DispatchToLazyContext(&dispatch_context, listener_filter);
 }
 
+void LazyEventDispatcher::DispatchToServiceWorker(
+    const ExtensionId& extension_id,
+    const GURL& service_worker_scope,
+    const base::DictionaryValue* listener_filter) {
+  LazyContextId dispatch_context(browser_context_, extension_id,
+                                 service_worker_scope);
+  DispatchToLazyContext(&dispatch_context, listener_filter);
+}
+
 bool LazyEventDispatcher::HasAlreadyDispatched(
     BrowserContext* context,
     const EventListener* listener) const {
-  auto dispatch_context =
-      base::MakeUnique<LazyContextId>(context, listener->extension_id());
+  std::unique_ptr<LazyContextId> dispatch_context;
+  if (listener->is_for_service_worker()) {
+    dispatch_context = base::MakeUnique<LazyContextId>(
+        context, listener->extension_id(), listener->listener_url());
+  } else {
+    dispatch_context =
+        base::MakeUnique<LazyContextId>(context, listener->extension_id());
+  }
+
   return HasAlreadyDispatchedImpl(dispatch_context.get());
 }
 
@@ -79,7 +96,7 @@
   if (HasAlreadyDispatchedImpl(dispatch_context))
     return false;
 
-  LazyBackgroundTaskQueue* queue = dispatch_context->GetTaskQueue();
+  LazyContextTaskQueue* queue = dispatch_context->GetTaskQueue();
   if (!queue->ShouldEnqueueTask(dispatch_context->browser_context(),
                                 extension)) {
     return false;
@@ -102,15 +119,20 @@
     dispatched_event->will_dispatch_callback.Reset();
   }
 
-  queue->AddPendingTask(dispatch_context->browser_context(),
-                        dispatch_context->extension_id(),
-                        base::Bind(dispatch_function_, dispatched_event));
+  queue->AddPendingTaskToDispatchEvent(
+      dispatch_context, base::Bind(dispatch_function_, dispatched_event));
 
   return true;
 }
 
 bool LazyEventDispatcher::HasAlreadyDispatchedImpl(
     const LazyContextId* dispatch_context) const {
+  if (dispatch_context->is_for_service_worker()) {
+    ServiceWorkerDispatchIdentifier dispatch_id(
+        dispatch_context->browser_context(),
+        dispatch_context->service_worker_scope());
+    return base::ContainsKey(dispatched_ids_for_service_worker_, dispatch_id);
+  }
   DCHECK(dispatch_context->is_for_event_page());
   EventPageDispatchIdentifier dispatch_id(dispatch_context->browser_context(),
                                           dispatch_context->extension_id());
@@ -119,6 +141,12 @@
 
 void LazyEventDispatcher::RecordAlreadyDispatched(
     LazyContextId* dispatch_context) {
+  if (dispatch_context->is_for_service_worker()) {
+    dispatched_ids_for_service_worker_.insert(
+        std::make_pair(dispatch_context->browser_context(),
+                       dispatch_context->service_worker_scope()));
+    return;
+  }
   DCHECK(dispatch_context->is_for_event_page());
   dispatched_ids_for_event_page_.insert(std::make_pair(
       dispatch_context->browser_context(), dispatch_context->extension_id()));
diff --git a/extensions/browser/events/lazy_event_dispatcher.h b/extensions/browser/events/lazy_event_dispatcher.h
index 0ad46d3..b844657 100644
--- a/extensions/browser/events/lazy_event_dispatcher.h
+++ b/extensions/browser/events/lazy_event_dispatcher.h
@@ -10,6 +10,7 @@
 
 #include "base/callback.h"
 #include "base/memory/linked_ptr.h"
+#include "extensions/browser/lazy_context_task_queue.h"
 #include "extensions/common/extension_id.h"
 
 namespace base {
@@ -23,7 +24,6 @@
 namespace extensions {
 class EventListener;
 class Extension;
-class ExtensionHost;
 class LazyContextId;
 struct Event;
 
@@ -32,22 +32,28 @@
 // Manages waking up lazy contexts if they are stopped.
 class LazyEventDispatcher {
  public:
-  // TODO(lazyboy): ExtensionHost is specific to events pages, provide a generic
-  // context info that works for both event pages and service workers.
   using DispatchFunction =
-      base::Callback<void(const linked_ptr<Event>&, ExtensionHost*)>;
+      base::Callback<void(const linked_ptr<Event>&,
+                          std::unique_ptr<LazyContextTaskQueue::ContextInfo>)>;
 
   LazyEventDispatcher(content::BrowserContext* browser_context,
                       const linked_ptr<Event>& event,
                       const DispatchFunction& dispatch_function);
   ~LazyEventDispatcher();
 
-  // Dispatches a lazy event to |extension_id|.
+  // Dispatches the lazy |event_| to |extension_id|.
   //
   // Ensures that all lazy background pages that are interested in the given
   // event are loaded, and queues the event if the page is not ready yet.
   void DispatchToEventPage(const ExtensionId& extension_id,
                            const base::DictionaryValue* listener_filter);
+  // Dispatches the lazy |event_| to |extension_id|'s service worker.
+  //
+  // Service workers are started if they were stopped, before dispatching the
+  // event.
+  void DispatchToServiceWorker(const ExtensionId& extension_id,
+                               const GURL& service_worker_scope,
+                               const base::DictionaryValue* listener_filter);
 
   // Returns whether or not an event listener identical to |listener| is queued
   // for dispatch already.
@@ -57,6 +63,8 @@
  private:
   using EventPageDispatchIdentifier =
       std::pair<const content::BrowserContext*, std::string>;
+  using ServiceWorkerDispatchIdentifier =
+      std::pair<const content::BrowserContext*, GURL>;
 
   void DispatchToLazyContext(LazyContextId* dispatch_context,
                              const base::DictionaryValue* listener_filter);
@@ -78,7 +86,10 @@
   linked_ptr<Event> event_;
   DispatchFunction dispatch_function_;
 
+  // TODO(lazyboy): Instead of keeping these two std::sets, compbine them using
+  // LazyContextId key when service worker event listeners are more common.
   std::set<EventPageDispatchIdentifier> dispatched_ids_for_event_page_;
+  std::set<ServiceWorkerDispatchIdentifier> dispatched_ids_for_service_worker_;
 
   DISALLOW_COPY_AND_ASSIGN(LazyEventDispatcher);
 };
diff --git a/extensions/browser/extension_message_filter.cc b/extensions/browser/extension_message_filter.cc
index 91c46ac..42ccc92 100644
--- a/extensions/browser/extension_message_filter.cc
+++ b/extensions/browser/extension_message_filter.cc
@@ -90,6 +90,8 @@
     case ExtensionHostMsg_RemoveListener::ID:
     case ExtensionHostMsg_AddLazyListener::ID:
     case ExtensionHostMsg_RemoveLazyListener::ID:
+    case ExtensionHostMsg_AddLazyServiceWorkerListener::ID:
+    case ExtensionHostMsg_RemoveLazyServiceWorkerListener::ID:
     case ExtensionHostMsg_AddFilteredListener::ID:
     case ExtensionHostMsg_RemoveFilteredListener::ID:
     case ExtensionHostMsg_ShouldSuspendAck::ID:
@@ -122,6 +124,10 @@
                         OnExtensionAddLazyListener)
     IPC_MESSAGE_HANDLER(ExtensionHostMsg_RemoveLazyListener,
                         OnExtensionRemoveLazyListener)
+    IPC_MESSAGE_HANDLER(ExtensionHostMsg_AddLazyServiceWorkerListener,
+                        OnExtensionAddLazyServiceWorkerListener);
+    IPC_MESSAGE_HANDLER(ExtensionHostMsg_RemoveLazyServiceWorkerListener,
+                        OnExtensionRemoveLazyServiceWorkerListener);
     IPC_MESSAGE_HANDLER(ExtensionHostMsg_AddFilteredListener,
                         OnExtensionAddFilteredListener)
     IPC_MESSAGE_HANDLER(ExtensionHostMsg_RemoveFilteredListener,
@@ -141,7 +147,7 @@
 
 void ExtensionMessageFilter::OnExtensionAddListener(
     const std::string& extension_id,
-    const GURL& listener_url,
+    const GURL& listener_or_worker_scope_url,
     const std::string& event_name,
     int worker_thread_id) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
@@ -154,14 +160,19 @@
 
   EventRouter* event_router = GetEventRouter();
   if (crx_file::id_util::IdIsValid(extension_id)) {
-    if (worker_thread_id != kNonWorkerThreadId) {
+    const bool is_service_worker_context =
+        worker_thread_id != kNonWorkerThreadId;
+    if (is_service_worker_context) {
+      DCHECK(listener_or_worker_scope_url.is_valid());
       event_router->AddServiceWorkerEventListener(
-          event_name, process, extension_id, worker_thread_id);
+          event_name, process, extension_id, listener_or_worker_scope_url,
+          worker_thread_id);
     } else {
       event_router->AddEventListener(event_name, process, extension_id);
     }
-  } else if (listener_url.is_valid()) {
-    event_router->AddEventListenerForURL(event_name, process, listener_url);
+  } else if (listener_or_worker_scope_url.is_valid()) {
+    event_router->AddEventListenerForURL(event_name, process,
+                                         listener_or_worker_scope_url);
   } else {
     NOTREACHED() << "Tried to add an event listener without a valid "
                  << "extension ID nor listener URL";
@@ -170,7 +181,7 @@
 
 void ExtensionMessageFilter::OnExtensionRemoveListener(
     const std::string& extension_id,
-    const GURL& listener_url,
+    const GURL& listener_or_worker_scope_url,
     const std::string& event_name,
     int worker_thread_id) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
@@ -182,15 +193,19 @@
     return;
 
   if (crx_file::id_util::IdIsValid(extension_id)) {
-    if (worker_thread_id != kNonWorkerThreadId) {
+    const bool is_service_worker_context =
+        worker_thread_id != kNonWorkerThreadId;
+    if (is_service_worker_context) {
+      DCHECK(listener_or_worker_scope_url.is_valid());
       GetEventRouter()->RemoveServiceWorkerEventListener(
-          event_name, process, extension_id, worker_thread_id);
+          event_name, process, extension_id, listener_or_worker_scope_url,
+          worker_thread_id);
     } else {
       GetEventRouter()->RemoveEventListener(event_name, process, extension_id);
     }
-  } else if (listener_url.is_valid()) {
+  } else if (listener_or_worker_scope_url.is_valid()) {
     GetEventRouter()->RemoveEventListenerForURL(event_name, process,
-                                                listener_url);
+                                                listener_or_worker_scope_url);
   } else {
     NOTREACHED() << "Tried to remove an event listener without a valid "
                  << "extension ID nor listener URL";
@@ -199,34 +214,45 @@
 
 void ExtensionMessageFilter::OnExtensionAddLazyListener(
     const std::string& extension_id,
+    const std::string& event_name) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  if (!browser_context_)
+    return;
+  GetEventRouter()->AddLazyEventListener(event_name, extension_id);
+}
+
+void ExtensionMessageFilter::OnExtensionAddLazyServiceWorkerListener(
+    const std::string& extension_id,
     const std::string& event_name,
-    int worker_thread_id) {
+    const GURL& service_worker_scope) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!browser_context_)
     return;
 
-  if (worker_thread_id == kNonWorkerThreadId) {
-    GetEventRouter()->AddLazyEventListener(event_name, extension_id);
-  } else {
-    GetEventRouter()->AddLazyServiceWorkerEventListener(
-        event_name, extension_id, worker_thread_id);
-  }
+  GetEventRouter()->AddLazyServiceWorkerEventListener(event_name, extension_id,
+                                                      service_worker_scope);
 }
 
 void ExtensionMessageFilter::OnExtensionRemoveLazyListener(
     const std::string& extension_id,
-    const std::string& event_name,
-    int worker_thread_id) {
+    const std::string& event_name) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!browser_context_)
     return;
 
-  if (worker_thread_id == kNonWorkerThreadId) {
-    GetEventRouter()->RemoveLazyEventListener(event_name, extension_id);
-  } else {
-    GetEventRouter()->RemoveLazyServiceWorkerEventListener(
-        event_name, extension_id, worker_thread_id);
-  }
+  GetEventRouter()->RemoveLazyEventListener(event_name, extension_id);
+}
+
+void ExtensionMessageFilter::OnExtensionRemoveLazyServiceWorkerListener(
+    const std::string& extension_id,
+    const std::string& event_name,
+    const GURL& worker_scope_url) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  if (!browser_context_)
+    return;
+
+  GetEventRouter()->RemoveLazyServiceWorkerEventListener(
+      event_name, extension_id, worker_scope_url);
 }
 
 void ExtensionMessageFilter::OnExtensionAddFilteredListener(
diff --git a/extensions/browser/extension_message_filter.h b/extensions/browser/extension_message_filter.h
index 5859a97..8e3b6f7 100644
--- a/extensions/browser/extension_message_filter.h
+++ b/extensions/browser/extension_message_filter.h
@@ -61,11 +61,17 @@
                                  const std::string& event_name,
                                  int worker_thread_id);
   void OnExtensionAddLazyListener(const std::string& extension_id,
-                                  const std::string& event_name,
-                                  int worker_thread_id);
+                                  const std::string& event_name);
+  void OnExtensionAddLazyServiceWorkerListener(
+      const std::string& extension_id,
+      const std::string& event_name,
+      const GURL& service_worker_scope);
   void OnExtensionRemoveLazyListener(const std::string& extension_id,
-                                     const std::string& event_name,
-                                     int worker_thread_id);
+                                     const std::string& event_name);
+  void OnExtensionRemoveLazyServiceWorkerListener(
+      const std::string& extension_id,
+      const std::string& event_name,
+      const GURL& worker_scope_url);
   void OnExtensionAddFilteredListener(const std::string& extension_id,
                                       const std::string& event_name,
                                       const base::DictionaryValue& filter,
diff --git a/extensions/browser/lazy_background_task_queue.cc b/extensions/browser/lazy_background_task_queue.cc
index 2403154..2e07737 100644
--- a/extensions/browser/lazy_background_task_queue.cc
+++ b/extensions/browser/lazy_background_task_queue.cc
@@ -16,6 +16,7 @@
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extensions_browser_client.h"
 #include "extensions/browser/lazy_background_task_queue_factory.h"
+#include "extensions/browser/lazy_context_id.h"
 #include "extensions/browser/notification_types.h"
 #include "extensions/browser/process_manager.h"
 #include "extensions/browser/process_map.h"
@@ -25,6 +26,23 @@
 
 namespace extensions {
 
+namespace {
+
+// Adapts a LazyBackgroundTaskQueue pending task callback to
+// LazyContextTaskQueue's callback.
+void PendingTaskAdapter(const LazyContextTaskQueue::PendingTask& original_task,
+                        ExtensionHost* host) {
+  if (!host) {
+    original_task.Run(nullptr);
+  } else {
+    original_task.Run(base::MakeUnique<LazyContextTaskQueue::ContextInfo>(
+        host->extension()->id(), host->render_process_host(),
+        kNonWorkerThreadId, host->GetURL()));
+  }
+}
+
+}  // namespace
+
 LazyBackgroundTaskQueue::LazyBackgroundTaskQueue(
     content::BrowserContext* browser_context)
     : browser_context_(browser_context), extension_registry_observer_(this) {
@@ -66,6 +84,13 @@
   return false;
 }
 
+void LazyBackgroundTaskQueue::AddPendingTaskToDispatchEvent(
+    LazyContextId* context_id,
+    const LazyContextTaskQueue::PendingTask& task) {
+  AddPendingTask(context_id->browser_context(), context_id->extension_id(),
+                 base::Bind(&PendingTaskAdapter, task));
+}
+
 void LazyBackgroundTaskQueue::AddPendingTask(
     content::BrowserContext* browser_context,
     const std::string& extension_id,
diff --git a/extensions/browser/lazy_background_task_queue.h b/extensions/browser/lazy_background_task_queue.h
index 2f24e27a..8d0bcd25 100644
--- a/extensions/browser/lazy_background_task_queue.h
+++ b/extensions/browser/lazy_background_task_queue.h
@@ -18,6 +18,7 @@
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
 #include "extensions/browser/extension_registry_observer.h"
+#include "extensions/browser/lazy_context_task_queue.h"
 #include "extensions/common/extension_id.h"
 
 namespace content {
@@ -28,6 +29,7 @@
 class Extension;
 class ExtensionHost;
 class ExtensionRegistry;
+class LazyContextId;
 
 // This class maintains a queue of tasks that should execute when an
 // extension's lazy background page is loaded. It is also in charge of loading
@@ -36,6 +38,7 @@
 // It is the consumer's responsibility to use this class when appropriate, i.e.
 // only with extensions that have not-yet-loaded lazy background pages.
 class LazyBackgroundTaskQueue : public KeyedService,
+                                public LazyContextTaskQueue,
                                 public content::NotificationObserver,
                                 public ExtensionRegistryObserver {
  public:
@@ -53,7 +56,13 @@
   // extension has a lazy background page that is being suspended this method
   // cancels that suspension.
   bool ShouldEnqueueTask(content::BrowserContext* context,
-                         const Extension* extension);
+                         const Extension* extension) override;
+  // TODO(lazyboy): Find a better way to use AddPendingTask instead of this.
+  // Currently AddPendingTask has lots of consumers that depend on
+  // ExtensionHost.
+  void AddPendingTaskToDispatchEvent(
+      LazyContextId* context_id,
+      const LazyContextTaskQueue::PendingTask& task) override;
 
   // Adds a task to the queue for a given extension. If this is the first
   // task added for the extension, its lazy background page will be loaded.
diff --git a/extensions/browser/lazy_context_id.cc b/extensions/browser/lazy_context_id.cc
index 80a4dcf..d8c9fe16 100644
--- a/extensions/browser/lazy_context_id.cc
+++ b/extensions/browser/lazy_context_id.cc
@@ -5,6 +5,7 @@
 #include "extensions/browser/lazy_context_id.h"
 
 #include "extensions/browser/lazy_background_task_queue.h"
+#include "extensions/browser/service_worker_task_queue.h"
 
 namespace extensions {
 
@@ -12,9 +13,19 @@
                              const ExtensionId& extension_id)
     : type_(Type::kEventPage), context_(context), extension_id_(extension_id) {}
 
-LazyBackgroundTaskQueue* LazyContextId::GetTaskQueue() {
-  DCHECK(is_for_event_page());
-  return LazyBackgroundTaskQueue::Get(context_);
+LazyContextId::LazyContextId(content::BrowserContext* context,
+                             const ExtensionId& extension_id,
+                             const GURL& service_worker_scope)
+    : type_(Type::kServiceWorker),
+      context_(context),
+      extension_id_(extension_id),
+      service_worker_scope_(service_worker_scope) {}
+
+LazyContextTaskQueue* LazyContextId::GetTaskQueue() {
+  if (is_for_event_page())
+    return LazyBackgroundTaskQueue::Get(context_);
+  DCHECK(is_for_service_worker());
+  return ServiceWorkerTaskQueue::Get(context_);
 }
 
 }  // namespace extensions
diff --git a/extensions/browser/lazy_context_id.h b/extensions/browser/lazy_context_id.h
index aef418d..13a90614 100644
--- a/extensions/browser/lazy_context_id.h
+++ b/extensions/browser/lazy_context_id.h
@@ -13,20 +13,26 @@
 }
 
 namespace extensions {
-class LazyBackgroundTaskQueue;
+class LazyContextTaskQueue;
 
 class LazyContextId {
  public:
   enum class Type {
     kEventPage,
+    kServiceWorker,
   };
 
   // An event page (lazy background) context.
   LazyContextId(content::BrowserContext* context,
                 const ExtensionId& extension_id);
-  // TODO(lazyboy): Service worker context.
+
+  // An extension service worker context.
+  LazyContextId(content::BrowserContext* context,
+                const ExtensionId& extension_id,
+                const GURL& service_worker_scope);
 
   bool is_for_event_page() const { return type_ == Type::kEventPage; }
+  bool is_for_service_worker() const { return type_ == Type::kServiceWorker; }
 
   content::BrowserContext* browser_context() const { return context_; }
   void set_browser_context(content::BrowserContext* context) {
@@ -35,13 +41,18 @@
 
   const ExtensionId& extension_id() const { return extension_id_; }
 
-  // TODO(lazyboy): Use a generic interface to support service workers.
-  LazyBackgroundTaskQueue* GetTaskQueue();
+  const GURL& service_worker_scope() const {
+    DCHECK(is_for_service_worker());
+    return service_worker_scope_;
+  }
+
+  LazyContextTaskQueue* GetTaskQueue();
 
  private:
   const Type type_;
   content::BrowserContext* context_;
   const ExtensionId extension_id_;
+  const GURL service_worker_scope_;
 
   DISALLOW_COPY_AND_ASSIGN(LazyContextId);
 };
diff --git a/extensions/browser/lazy_context_task_queue.h b/extensions/browser/lazy_context_task_queue.h
new file mode 100644
index 0000000..f27033d
--- /dev/null
+++ b/extensions/browser/lazy_context_task_queue.h
@@ -0,0 +1,71 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_LAZY_CONTEXT_TASK_QUEUE_H_
+#define EXTENSIONS_BROWSER_LAZY_CONTEXT_TASK_QUEUE_H_
+
+#include "base/callback.h"
+#include "extensions/common/extension_id.h"
+#include "url/gurl.h"
+
+namespace content {
+class BrowserContext;
+class RenderProcessHost;
+}  // namespace content
+
+namespace extensions {
+class Extension;
+class LazyContextId;
+
+// Interface for performing tasks after loading lazy contexts of an extension.
+//
+// Lazy contexts are non-persistent, so they can unload any time and this
+// interface exposes an async mechanism to perform tasks after loading the
+// context.
+class LazyContextTaskQueue {
+ public:
+  // Represents information about an extension lazy context, which is passed to
+  // consumers that add tasks to LazyContextTaskQueue.
+  struct ContextInfo {
+    const ExtensionId extension_id;
+    content::RenderProcessHost* const render_process_host;
+    const int worker_thread_id;
+    const GURL url;
+    ContextInfo(const ExtensionId& extension_id,
+                content::RenderProcessHost* render_process_host,
+                int worker_thread_id,
+                const GURL& url)
+        : extension_id(extension_id),
+          render_process_host(render_process_host),
+          worker_thread_id(worker_thread_id),
+          url(url) {}
+  };
+  using PendingTask = base::Callback<void(std::unique_ptr<ContextInfo> params)>;
+
+  // Returns true if the task should be added to the queue (that is, if the
+  // extension has a lazy background page or service worker that isn't ready
+  // yet).
+  virtual bool ShouldEnqueueTask(content::BrowserContext* context,
+                                 const Extension* extension) = 0;
+
+  // Adds a task to the queue for a given extension. If this is the first
+  // task added for the extension, its "lazy context" (i.e. lazy background
+  // page for event pages, service worker for extension service workers) will
+  // be loaded. The task will be called either when the page is loaded,
+  // or when the page fails to load for some reason (e.g. a crash or browser
+  // shutdown). In the latter case, the ContextInfo will be nullptr.
+  //
+  // TODO(lazyboy): Remove "ToDispatchEvent" suffix and simply call this
+  // AddPendingTask. Issues:
+  // 1. We already have LazyBackgroundTaskQueue::AddPendingTask. Moreover, that
+  //    is heavily used thoughout the codebase.
+  // 2. LazyBackgroundTaskQueue::AddPendingTask is tied to ExtensionHost. This
+  //    class should be ExtensionHost agnostic.
+  virtual void AddPendingTaskToDispatchEvent(LazyContextId* context_id,
+                                             const PendingTask& task) = 0;
+};
+
+}  // namespace extensions
+
+#endif  // EXTENSIONS_BROWSER_LAZY_CONTEXT_TASK_QUEUE_H_
diff --git a/extensions/browser/service_worker_task_queue.cc b/extensions/browser/service_worker_task_queue.cc
new file mode 100644
index 0000000..35eea0f
--- /dev/null
+++ b/extensions/browser/service_worker_task_queue.cc
@@ -0,0 +1,98 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/service_worker_task_queue.h"
+
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/service_worker_context.h"
+#include "content/public/browser/storage_partition.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/lazy_context_id.h"
+#include "extensions/browser/service_worker_task_queue_factory.h"
+#include "extensions/common/constants.h"
+
+using content::BrowserContext;
+using content::BrowserThread;
+
+namespace extensions {
+
+namespace {
+
+void FinishTask(const LazyContextTaskQueue::PendingTask& task,
+                const ExtensionId& extension_id,
+                int process_id,
+                int thread_id) {
+  auto params = base::MakeUnique<LazyContextTaskQueue::ContextInfo>(
+      extension_id, content::RenderProcessHost::FromID(process_id), thread_id,
+      GURL());
+  task.Run(std::move(params));
+}
+
+void DidStartActiveWorkerForPattern(
+    const LazyContextTaskQueue::PendingTask& task,
+    const ExtensionId& extension_id,
+    int process_id,
+    int thread_id) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+  content::BrowserThread::PostTask(
+      content::BrowserThread::UI, FROM_HERE,
+      base::Bind(FinishTask, task, extension_id, process_id, thread_id));
+}
+
+void DidStartActiveWorkerFail() {
+  DCHECK(false) << "DidStartActiveWorkerFail";
+  // TODO(lazyboy): Handle failure case.
+}
+
+void GetServiceWorkerInfoOnIO(
+    const GURL& pattern,
+    const ExtensionId& extension_id,
+    content::ServiceWorkerContext* service_worker_context,
+    const LazyContextTaskQueue::PendingTask& task) {
+  service_worker_context->StartActiveWorkerForPattern(
+      pattern,
+      base::BindOnce(&DidStartActiveWorkerForPattern, task, extension_id),
+      base::BindOnce(&DidStartActiveWorkerFail));
+}
+
+}  // namespace
+
+ServiceWorkerTaskQueue::ServiceWorkerTaskQueue(
+    content::BrowserContext* browser_context) {}
+
+ServiceWorkerTaskQueue::~ServiceWorkerTaskQueue() {}
+
+// static
+ServiceWorkerTaskQueue* ServiceWorkerTaskQueue::Get(
+    content::BrowserContext* context) {
+  return ServiceWorkerTaskQueueFactory::GetForBrowserContext(context);
+}
+
+bool ServiceWorkerTaskQueue::ShouldEnqueueTask(content::BrowserContext* context,
+                                               const Extension* extension) {
+  // We call StartWorker every time we want to dispatch an event to an extension
+  // Service worker.
+  // TODO(lazyboy): Is that a problem?
+  return true;
+}
+
+void ServiceWorkerTaskQueue::AddPendingTaskToDispatchEvent(
+    LazyContextId* context_id,
+    const LazyContextTaskQueue::PendingTask& task) {
+  DCHECK(context_id->is_for_service_worker());
+  content::StoragePartition* partition =
+      BrowserContext::GetStoragePartitionForSite(
+          context_id->browser_context(), context_id->service_worker_scope());
+  content::ServiceWorkerContext* service_worker_context =
+      partition->GetServiceWorkerContext();
+
+  BrowserThread::PostTask(
+      BrowserThread::IO, FROM_HERE,
+      base::Bind(&GetServiceWorkerInfoOnIO, context_id->service_worker_scope(),
+                 context_id->extension_id(), service_worker_context, task));
+}
+
+}  // namespace extensions
diff --git a/extensions/browser/service_worker_task_queue.h b/extensions/browser/service_worker_task_queue.h
new file mode 100644
index 0000000..07ffe181
--- /dev/null
+++ b/extensions/browser/service_worker_task_queue.h
@@ -0,0 +1,46 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_SERVICE_WORKER_TASK_QUEUE_H_
+#define EXTENSIONS_BROWSER_SERVICE_WORKER_TASK_QUEUE_H_
+
+#include "components/keyed_service/core/keyed_service.h"
+#include "extensions/browser/lazy_context_task_queue.h"
+#include "extensions/common/extension_id.h"
+#include "url/gurl.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+class Extension;
+class LazyContextId;
+
+// TODO(lazyboy): This class doesn't queue up any tasks, implement. See
+// LazyBackgroundTaskQueue.
+// TODO(lazyboy): Clean up queue when extension is unloaded/uninstalled.
+class ServiceWorkerTaskQueue : public KeyedService,
+                               public LazyContextTaskQueue {
+ public:
+  explicit ServiceWorkerTaskQueue(content::BrowserContext* browser_context);
+  ~ServiceWorkerTaskQueue() override;
+
+  // Convenience method to return the ServiceWorkerTaskQueue for a given
+  // |context|.
+  static ServiceWorkerTaskQueue* Get(content::BrowserContext* context);
+
+  bool ShouldEnqueueTask(content::BrowserContext* context,
+                         const Extension* extension) override;
+  void AddPendingTaskToDispatchEvent(
+      LazyContextId* context_id,
+      const LazyContextTaskQueue::PendingTask& task) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ServiceWorkerTaskQueue);
+};
+
+}  // namespace extensions
+
+#endif  // EXTENSIONS_BROWSER_SERVICE_WORKER_TASK_QUEUE_H_
diff --git a/extensions/browser/service_worker_task_queue_factory.cc b/extensions/browser/service_worker_task_queue_factory.cc
new file mode 100644
index 0000000..aa3755a3
--- /dev/null
+++ b/extensions/browser/service_worker_task_queue_factory.cc
@@ -0,0 +1,44 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/service_worker_task_queue_factory.h"
+
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/service_worker_task_queue.h"
+
+using content::BrowserContext;
+namespace extensions {
+
+// static
+ServiceWorkerTaskQueue* ServiceWorkerTaskQueueFactory::GetForBrowserContext(
+    BrowserContext* context) {
+  return static_cast<ServiceWorkerTaskQueue*>(
+      GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+// static
+ServiceWorkerTaskQueueFactory* ServiceWorkerTaskQueueFactory::GetInstance() {
+  return base::Singleton<ServiceWorkerTaskQueueFactory>::get();
+}
+
+ServiceWorkerTaskQueueFactory::ServiceWorkerTaskQueueFactory()
+    : BrowserContextKeyedServiceFactory(
+          "ServiceWorkerTaskQueue",
+          BrowserContextDependencyManager::GetInstance()) {}
+
+ServiceWorkerTaskQueueFactory::~ServiceWorkerTaskQueueFactory() {}
+
+KeyedService* ServiceWorkerTaskQueueFactory::BuildServiceInstanceFor(
+    BrowserContext* context) const {
+  return new ServiceWorkerTaskQueue(context);
+}
+
+BrowserContext* ServiceWorkerTaskQueueFactory::GetBrowserContextToUse(
+    BrowserContext* context) const {
+  // Redirected in incognito.
+  return ExtensionsBrowserClient::Get()->GetOriginalContext(context);
+}
+
+}  // namespace extensions
diff --git a/extensions/browser/service_worker_task_queue_factory.h b/extensions/browser/service_worker_task_queue_factory.h
new file mode 100644
index 0000000..0a78b5f
--- /dev/null
+++ b/extensions/browser/service_worker_task_queue_factory.h
@@ -0,0 +1,39 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_SERVICE_WORKER_TASK_QUEUE_FACTORY_H_
+#define EXTENSIONS_BROWSER_SERVICE_WORKER_TASK_QUEUE_FACTORY_H_
+
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+namespace extensions {
+
+class ServiceWorkerTaskQueue;
+
+class ServiceWorkerTaskQueueFactory : public BrowserContextKeyedServiceFactory {
+ public:
+  static ServiceWorkerTaskQueue* GetForBrowserContext(
+      content::BrowserContext* context);
+  static ServiceWorkerTaskQueueFactory* GetInstance();
+
+ private:
+  friend struct base::DefaultSingletonTraits<ServiceWorkerTaskQueueFactory>;
+
+  ServiceWorkerTaskQueueFactory();
+  ~ServiceWorkerTaskQueueFactory() override;
+
+  // BrowserContextKeyedServiceFactory:
+  KeyedService* BuildServiceInstanceFor(
+      content::BrowserContext* context) const override;
+  content::BrowserContext* GetBrowserContextToUse(
+      content::BrowserContext* context) const override;
+
+  DISALLOW_COPY_AND_ASSIGN(ServiceWorkerTaskQueueFactory);
+};
+
+}  // namespace extensions
+
+#endif  // EXTENSIONS_BROWSER_SERVICE_WORKER_TASK_QUEUE_FACTORY_H_
diff --git a/extensions/common/extension_messages.h b/extensions/common/extension_messages.h
index c95da70..bcbd18ac 100644
--- a/extensions/common/extension_messages.h
+++ b/extensions/common/extension_messages.h
@@ -691,7 +691,7 @@
 // Notify the browser that the given extension added a listener to an event.
 IPC_MESSAGE_CONTROL4(ExtensionHostMsg_AddListener,
                      std::string /* extension_id */,
-                     GURL /* listener_url */,
+                     GURL /* listener_or_worker_scope_url */,
                      std::string /* name */,
                      int /* worker_thread_id */)
 
@@ -699,23 +699,35 @@
 // event.
 IPC_MESSAGE_CONTROL4(ExtensionHostMsg_RemoveListener,
                      std::string /* extension_id */,
-                     GURL /* listener_url */,
+                     GURL /* listener_or_worker_scope_url */,
                      std::string /* name */,
                      int /* worker_thread_id */)
 
 // Notify the browser that the given extension added a listener to an event from
 // a lazy background page.
-IPC_MESSAGE_CONTROL3(ExtensionHostMsg_AddLazyListener,
+IPC_MESSAGE_CONTROL2(ExtensionHostMsg_AddLazyListener,
                      std::string /* extension_id */,
-                     std::string /* name */,
-                     int /* worker_thread_id */)
+                     std::string /* name */)
 
 // Notify the browser that the given extension is no longer interested in
 // receiving the given event from a lazy background page.
-IPC_MESSAGE_CONTROL3(ExtensionHostMsg_RemoveLazyListener,
+IPC_MESSAGE_CONTROL2(ExtensionHostMsg_RemoveLazyListener,
+                     std::string /* extension_id */,
+                     std::string /* event_name */)
+
+// Notify the browser that the given extension added a listener to an event from
+// an extension service worker.
+IPC_MESSAGE_CONTROL3(ExtensionHostMsg_AddLazyServiceWorkerListener,
                      std::string /* extension_id */,
                      std::string /* name */,
-                     int /* worker_thread_id */)
+                     GURL /* service_worker_scope */)
+
+// Notify the browser that the given extension is no longer interested in
+// receiving the given event from an extension service worker.
+IPC_MESSAGE_CONTROL3(ExtensionHostMsg_RemoveLazyServiceWorkerListener,
+                     std::string /* extension_id */,
+                     std::string /* name */,
+                     GURL /* service_worker_scope */)
 
 // Notify the browser that the given extension added a listener to instances of
 // the named event that satisfy the filter.
diff --git a/extensions/renderer/dispatcher.cc b/extensions/renderer/dispatcher.cc
index 0659214..7dfb51c 100644
--- a/extensions/renderer/dispatcher.cc
+++ b/extensions/renderer/dispatcher.cc
@@ -237,16 +237,16 @@
       render_thread->Send(new ExtensionHostMsg_AddListener(
           extension_id, context->url(), event_name, worker_thread_id));
       if (lazy) {
-        render_thread->Send(new ExtensionHostMsg_AddLazyListener(
-            extension_id, event_name, worker_thread_id));
+        render_thread->Send(
+            new ExtensionHostMsg_AddLazyListener(extension_id, event_name));
       }
     } else {
       DCHECK_EQ(binding::EventListenersChanged::NO_LISTENERS, changed);
       render_thread->Send(new ExtensionHostMsg_RemoveListener(
           extension_id, context->url(), event_name, worker_thread_id));
       if (lazy && was_manual) {
-        render_thread->Send(new ExtensionHostMsg_RemoveLazyListener(
-            extension_id, event_name, worker_thread_id));
+        render_thread->Send(
+            new ExtensionHostMsg_RemoveLazyListener(extension_id, event_name));
       }
     }
   }
@@ -434,10 +434,11 @@
 void Dispatcher::DidInitializeServiceWorkerContextOnWorkerThread(
     v8::Local<v8::Context> v8_context,
     int64_t service_worker_version_id,
-    const GURL& url) {
+    const GURL& service_worker_scope,
+    const GURL& script_url) {
   const base::TimeTicks start_time = base::TimeTicks::Now();
 
-  if (!url.SchemeIs(kExtensionScheme)) {
+  if (!script_url.SchemeIs(kExtensionScheme)) {
     // Early-out if this isn't a chrome-extension:// scheme, because looking up
     // the extension registry is unnecessary if it's not. Checking this will
     // also skip over hosted apps, which is the desired behavior - hosted app
@@ -446,14 +447,14 @@
   }
 
   const Extension* extension =
-      RendererExtensionRegistry::Get()->GetExtensionOrAppByURL(url);
+      RendererExtensionRegistry::Get()->GetExtensionOrAppByURL(script_url);
 
   if (!extension) {
     // TODO(kalman): This is no good. Instead we need to either:
     //
     // - Hold onto the v8::Context and create the ScriptContext and install
     //   our bindings when this extension is loaded.
-    // - Deal with there being an extension ID (url.host()) but no
+    // - Deal with there being an extension ID (script_url.host()) but no
     //   extension associated with it, then document that getBackgroundClient
     //   may fail if the extension hasn't loaded yet.
     //
@@ -475,7 +476,8 @@
   ScriptContext* context = new ScriptContext(
       v8_context, nullptr, extension, Feature::SERVICE_WORKER_CONTEXT,
       extension, Feature::SERVICE_WORKER_CONTEXT);
-  context->set_url(url);
+  context->set_url(script_url);
+  context->set_service_worker_scope(service_worker_scope);
 
   if (ExtensionsClient::Get()->ExtensionAPIEnabledInExtensionServiceWorkers()) {
     WorkerThreadDispatcher::Get()->AddWorkerData(service_worker_version_id,
@@ -565,10 +567,11 @@
 void Dispatcher::WillDestroyServiceWorkerContextOnWorkerThread(
     v8::Local<v8::Context> v8_context,
     int64_t service_worker_version_id,
-    const GURL& url) {
-  if (url.SchemeIs(kExtensionScheme)) {
+    const GURL& service_worker_scope,
+    const GURL& script_url) {
+  if (script_url.SchemeIs(kExtensionScheme)) {
     // See comment in DidInitializeServiceWorkerContextOnWorkerThread.
-    g_worker_script_context_set.Get().Remove(v8_context, url);
+    g_worker_script_context_set.Get().Remove(v8_context, script_url);
     // TODO(devlin): We're not calling
     // ExtensionBindingsSystem::WillReleaseScriptContext() here. This should be
     // fine, since the entire bindings system is being destroyed when we
diff --git a/extensions/renderer/dispatcher.h b/extensions/renderer/dispatcher.h
index e69de3c1..25800fb 100644
--- a/extensions/renderer/dispatcher.h
+++ b/extensions/renderer/dispatcher.h
@@ -95,7 +95,8 @@
   void DidInitializeServiceWorkerContextOnWorkerThread(
       v8::Local<v8::Context> v8_context,
       int64_t service_worker_version_id,
-      const GURL& url);
+      const GURL& service_worker_scope,
+      const GURL& script_url);
 
   void WillReleaseScriptContext(blink::WebLocalFrame* frame,
                                 const v8::Local<v8::Context>& context,
@@ -105,7 +106,8 @@
   static void WillDestroyServiceWorkerContextOnWorkerThread(
       v8::Local<v8::Context> v8_context,
       int64_t service_worker_version_id,
-      const GURL& url);
+      const GURL& service_worker_scope,
+      const GURL& script_url);
 
   // This method is not allowed to run JavaScript code in the frame.
   void DidCreateDocumentElement(blink::WebLocalFrame* frame);
diff --git a/extensions/renderer/event_bindings.cc b/extensions/renderer/event_bindings.cc
index 59cc404..7a5dee4d 100644
--- a/extensions/renderer/event_bindings.cc
+++ b/extensions/renderer/event_bindings.cc
@@ -249,10 +249,15 @@
 
   const int worker_thread_id = content::WorkerThread::GetCurrentId();
   const std::string& extension_id = context()->GetExtensionID();
+  const bool is_service_worker_context =
+      context()->context_type() == Feature::SERVICE_WORKER_CONTEXT;
   IPC::Sender* sender = GetIPCSender();
   if (IncrementEventListenerCount(context(), event_name) == 1) {
     sender->Send(new ExtensionHostMsg_AddListener(
-        extension_id, context()->url(), event_name, worker_thread_id));
+        extension_id,
+        is_service_worker_context ? context()->service_worker_scope()
+                                  : context()->url(),
+        event_name, worker_thread_id));
   }
 
   // This is called the first time the page has added a listener. Since
@@ -262,8 +267,13 @@
       ExtensionFrameHelper::IsContextForEventPage(context()) ||
       context()->context_type() == Feature::SERVICE_WORKER_CONTEXT;
   if (is_lazy_context) {
-    sender->Send(new ExtensionHostMsg_AddLazyListener(extension_id, event_name,
-                                                      worker_thread_id));
+    if (is_service_worker_context) {
+      sender->Send(new ExtensionHostMsg_AddLazyServiceWorkerListener(
+          extension_id, event_name, context()->service_worker_scope()));
+    } else {
+      sender->Send(
+          new ExtensionHostMsg_AddLazyListener(extension_id, event_name));
+    }
   }
 }
 
@@ -280,12 +290,16 @@
   attached_event_names_.erase(event_name);
 
   int worker_thread_id = content::WorkerThread::GetCurrentId();
+  const bool is_service_worker_context = worker_thread_id != kNonWorkerThreadId;
   IPC::Sender* sender = GetIPCSender();
   const std::string& extension_id = context()->GetExtensionID();
 
   if (DecrementEventListenerCount(context(), event_name) == 0) {
     sender->Send(new ExtensionHostMsg_RemoveListener(
-        extension_id, context()->url(), event_name, worker_thread_id));
+        extension_id,
+        is_service_worker_context ? context()->service_worker_scope()
+                                  : context()->url(),
+        event_name, worker_thread_id));
   }
 
   // DetachEvent is called when the last listener for the context is
@@ -297,8 +311,13 @@
         ExtensionFrameHelper::IsContextForEventPage(context()) ||
         context()->context_type() == Feature::SERVICE_WORKER_CONTEXT;
     if (is_lazy_context) {
-      sender->Send(new ExtensionHostMsg_RemoveLazyListener(
-          extension_id, event_name, worker_thread_id));
+      if (is_service_worker_context) {
+        sender->Send(new ExtensionHostMsg_RemoveLazyServiceWorkerListener(
+            extension_id, event_name, context()->service_worker_scope()));
+      } else {
+        sender->Send(
+            new ExtensionHostMsg_RemoveLazyListener(extension_id, event_name));
+      }
     }
   }
 }
diff --git a/extensions/renderer/script_context.cc b/extensions/renderer/script_context.cc
index 9a60177a..531e416 100644
--- a/extensions/renderer/script_context.cc
+++ b/extensions/renderer/script_context.cc
@@ -254,6 +254,11 @@
   return GetContextTypeDescriptionString(effective_context_type_);
 }
 
+const GURL& ScriptContext::service_worker_scope() const {
+  DCHECK_EQ(Feature::SERVICE_WORKER_CONTEXT, context_type());
+  return service_worker_scope_;
+}
+
 bool ScriptContext::IsAnyFeatureAvailableToContext(
     const Feature& api,
     CheckAliasStatus check_alias) {
diff --git a/extensions/renderer/script_context.h b/extensions/renderer/script_context.h
index 63a17d0..17b82e50 100644
--- a/extensions/renderer/script_context.h
+++ b/extensions/renderer/script_context.h
@@ -149,11 +149,16 @@
   //  - It might let us remove the about:blank resolving?
   const GURL& url() const { return url_; }
 
+  const GURL& service_worker_scope() const;
+
   // Sets the URL of this ScriptContext. Usually this will automatically be set
   // on construction, unless this isn't constructed with enough information to
   // determine the URL (e.g. frame was null).
   // TODO(kalman): Make this a constructor parameter (as an origin).
   void set_url(const GURL& url) { url_ = url; }
+  void set_service_worker_scope(const GURL& scope) {
+    service_worker_scope_ = scope;
+  }
 
   // Returns whether the API |api| or any part of the API could be available in
   // this context without taking into account the context's extension.
@@ -269,6 +274,8 @@
 
   GURL url_;
 
+  GURL service_worker_scope_;
+
   std::unique_ptr<Runner> runner_;
 
   base::ThreadChecker thread_checker_;