Worker: Fix broken GC logic on Dedicated Worker while DOMTimer is set

V8GC detemines whether a Dedicated Worker can be collectable by checking
pending activities on WorkerGlobalScope. This activity state is managed by
InProcessWorkerMessagingProxy on the main thread. In some situation, this
logic is broken and this CL fixes it.

<Before this CL>

InProcessWorkerObjectProxy on the worker thread reports pending activities
to the main thread in following cases:

(1) After DedicatedWorkerThread is initialized, reports that there are no
    pending activities regardless of whether they exist.
(2) After MessageEvent is dispatched, checks a pending activity using
    V8GCController and reports a result.

This logic does not work when a DOMTimer is set on initial script evaluation or
on MessageEvent. DOMTimer must be treated as a pending activity[*], but both
(1) and (2) do not take care of it (regarding 2, DOMTimer is not a
ScriptWrappable and V8GCController does not count such an object). As a result,
a worker can be GC'ed even if there is still an active timer.

<After this CL>

After initial script evaluation or MessageEvent, the object proxy starts an
exponential backoff timer to periodically check an activity state and reports
to the messaging proxy when all activities are done.

The messaging proxy reports to the GC that the worker object is collectable
after all posted messages and pending activities are completed.

[*] https://html.spec.whatwg.org/multipage/workers.html#the-worker's-lifetime

BUG=572226, 584851

Review-Url: https://codereview.chromium.org/2124693002
Cr-Commit-Position: refs/heads/master@{#413052}
diff --git a/third_party/WebKit/Source/core/core.gypi b/third_party/WebKit/Source/core/core.gypi
index 57a44e8f..f20f292 100644
--- a/third_party/WebKit/Source/core/core.gypi
+++ b/third_party/WebKit/Source/core/core.gypi
@@ -4280,6 +4280,7 @@
             'timing/MemoryInfoTest.cpp',
             'timing/PerformanceBaseTest.cpp',
             'timing/PerformanceObserverTest.cpp',
+            'workers/DedicatedWorkerTest.cpp',
             'workers/WorkerThreadTest.cpp',
             'workers/WorkerThreadTestHelper.h',
             'xml/parser/SharedBufferReaderTest.cpp',
diff --git a/third_party/WebKit/Source/core/frame/DOMTimerCoordinator.cpp b/third_party/WebKit/Source/core/frame/DOMTimerCoordinator.cpp
index e9311351..01e40073 100644
--- a/third_party/WebKit/Source/core/frame/DOMTimerCoordinator.cpp
+++ b/third_party/WebKit/Source/core/frame/DOMTimerCoordinator.cpp
@@ -44,6 +44,11 @@
     return removedTimer;
 }
 
+bool DOMTimerCoordinator::hasInstalledTimeout() const
+{
+    return !m_timers.isEmpty();
+}
+
 DEFINE_TRACE(DOMTimerCoordinator)
 {
     visitor->trace(m_timers);
diff --git a/third_party/WebKit/Source/core/frame/DOMTimerCoordinator.h b/third_party/WebKit/Source/core/frame/DOMTimerCoordinator.h
index a431574..6d80a6d 100644
--- a/third_party/WebKit/Source/core/frame/DOMTimerCoordinator.h
+++ b/third_party/WebKit/Source/core/frame/DOMTimerCoordinator.h
@@ -34,6 +34,8 @@
     // destroy the timer.
     DOMTimer* removeTimeoutByID(int id);
 
+    bool hasInstalledTimeout() const;
+
     // Timers created during the execution of other timers, and
     // repeating timers, are throttled. Timer nesting level tracks the
     // number of linked timers or repetitions of a timer. See
diff --git a/third_party/WebKit/Source/core/workers/DedicatedWorkerGlobalScope.h b/third_party/WebKit/Source/core/workers/DedicatedWorkerGlobalScope.h
index 5628e4f..ca22cca 100644
--- a/third_party/WebKit/Source/core/workers/DedicatedWorkerGlobalScope.h
+++ b/third_party/WebKit/Source/core/workers/DedicatedWorkerGlobalScope.h
@@ -64,6 +64,8 @@
     DECLARE_VIRTUAL_TRACE();
 
 private:
+    friend class DedicatedWorkerThreadForTest;
+
     DedicatedWorkerGlobalScope(const KURL&, const String& userAgent, DedicatedWorkerThread*, double timeOrigin, std::unique_ptr<SecurityOrigin::PrivilegeData>, WorkerClients*);
 };
 
diff --git a/third_party/WebKit/Source/core/workers/DedicatedWorkerTest.cpp b/third_party/WebKit/Source/core/workers/DedicatedWorkerTest.cpp
new file mode 100644
index 0000000..5aa4ac88
--- /dev/null
+++ b/third_party/WebKit/Source/core/workers/DedicatedWorkerTest.cpp
@@ -0,0 +1,314 @@
+// Copyright 2016 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 "core/dom/CrossThreadTask.h"
+#include "core/events/MessageEvent.h"
+#include "core/testing/DummyPageHolder.h"
+#include "core/workers/DedicatedWorkerGlobalScope.h"
+#include "core/workers/DedicatedWorkerThread.h"
+#include "core/workers/InProcessWorkerMessagingProxy.h"
+#include "core/workers/InProcessWorkerObjectProxy.h"
+#include "core/workers/WorkerThread.h"
+#include "core/workers/WorkerThreadStartupData.h"
+#include "core/workers/WorkerThreadTestHelper.h"
+#include "platform/testing/UnitTestHelpers.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include <memory>
+
+namespace blink {
+
+class DedicatedWorkerThreadForTest final : public DedicatedWorkerThread {
+public:
+    DedicatedWorkerThreadForTest(
+        WorkerLoaderProxyProvider* workerLoaderProxyProvider,
+        InProcessWorkerObjectProxy& workerObjectProxy)
+        : DedicatedWorkerThread(WorkerLoaderProxy::create(workerLoaderProxyProvider), workerObjectProxy, monotonicallyIncreasingTime())
+    {
+        m_workerBackingThread = WorkerBackingThread::createForTest("Test thread");
+    }
+
+    WorkerOrWorkletGlobalScope* createWorkerGlobalScope(std::unique_ptr<WorkerThreadStartupData> startupData) override
+    {
+        return new DedicatedWorkerGlobalScope(startupData->m_scriptURL, startupData->m_userAgent, this, m_timeOrigin, std::move(startupData->m_starterOriginPrivilegeData), std::move(startupData->m_workerClients));
+    }
+};
+
+class InProcessWorkerMessagingProxyForTest : public InProcessWorkerMessagingProxy {
+public:
+    InProcessWorkerMessagingProxyForTest(ExecutionContext* executionContext)
+        : InProcessWorkerMessagingProxy(executionContext, nullptr /* workerObject */ , nullptr /* workerClients */)
+    {
+        workerObjectProxy().m_nextIntervalInSec = 0.1;
+        workerObjectProxy().m_maxIntervalInSec = 0.2;
+
+        m_mockWorkerLoaderProxyProvider = wrapUnique(new MockWorkerLoaderProxyProvider());
+        m_workerThread = wrapUnique(new DedicatedWorkerThreadForTest(m_mockWorkerLoaderProxyProvider.get(), workerObjectProxy()));
+        workerThreadCreated();
+
+        m_mockWorkerThreadLifecycleObserver = new MockWorkerThreadLifecycleObserver(m_workerThread->getWorkerThreadLifecycleContext());
+        EXPECT_CALL(*m_mockWorkerThreadLifecycleObserver, contextDestroyed()).Times(1);
+    }
+
+    ~InProcessWorkerMessagingProxyForTest() override
+    {
+        EXPECT_EQ(WaitUntilMode::DontWait, m_waitUntilMode);
+        m_workerThread->workerLoaderProxy()->detachProvider(m_mockWorkerLoaderProxyProvider.get());
+    }
+
+    enum class WaitUntilMode {
+        DontWait,
+        MessageConfirmed,
+        PendingActivityReported,
+        ThreadTerminated,
+    };
+
+    // Blocks the main thread until a specified event happens.
+    void waitUntil(WaitUntilMode mode)
+    {
+        EXPECT_TRUE(isMainThread());
+        EXPECT_EQ(WaitUntilMode::DontWait, m_waitUntilMode);
+        m_waitUntilMode = mode;
+        testing::enterRunLoop();
+    }
+
+    void confirmMessageFromWorkerObject() override
+    {
+        EXPECT_TRUE(isMainThread());
+        InProcessWorkerMessagingProxy::confirmMessageFromWorkerObject();
+        if (m_waitUntilMode != WaitUntilMode::MessageConfirmed)
+            return;
+        m_waitUntilMode = WaitUntilMode::DontWait;
+        testing::exitRunLoop();
+    }
+
+    void pendingActivityFinished() override
+    {
+        EXPECT_TRUE(isMainThread());
+        InProcessWorkerMessagingProxy::pendingActivityFinished();
+        if (m_waitUntilMode != WaitUntilMode::PendingActivityReported)
+            return;
+        m_waitUntilMode = WaitUntilMode::DontWait;
+        testing::exitRunLoop();
+    }
+
+    void workerThreadTerminated() override
+    {
+        EXPECT_TRUE(isMainThread());
+        if (m_waitUntilMode != WaitUntilMode::ThreadTerminated)
+            return;
+        m_waitUntilMode = WaitUntilMode::DontWait;
+        testing::exitRunLoop();
+    }
+
+    std::unique_ptr<WorkerThread> createWorkerThread(double originTime) override
+    {
+        NOTREACHED();
+        return nullptr;
+    }
+
+    DedicatedWorkerThreadForTest* workerThread()
+    {
+        return static_cast<DedicatedWorkerThreadForTest*>(m_workerThread.get());
+    }
+
+    bool workerGlobalScopeMayHavePendingActivity() const { return m_workerGlobalScopeMayHavePendingActivity; }
+    unsigned unconfirmedMessageCount() const { return m_unconfirmedMessageCount; }
+
+private:
+    std::unique_ptr<MockWorkerLoaderProxyProvider> m_mockWorkerLoaderProxyProvider;
+    Persistent<MockWorkerThreadLifecycleObserver> m_mockWorkerThreadLifecycleObserver;
+
+    WaitUntilMode m_waitUntilMode = WaitUntilMode::DontWait;
+};
+
+using WaitUntilMode = InProcessWorkerMessagingProxyForTest::WaitUntilMode;
+
+class DedicatedWorkerTest : public ::testing::Test {
+public:
+    void SetUp() override
+    {
+        m_page = DummyPageHolder::create();
+        m_workerMessagingProxy = wrapUnique(new InProcessWorkerMessagingProxyForTest(&m_page->document()));
+        m_securityOrigin = SecurityOrigin::create(KURL(ParsedURLString, "http://fake.url/"));
+    }
+
+    void TearDown() override
+    {
+        workerThread()->terminate();
+        workerMessagingProxy()->waitUntil(WaitUntilMode::ThreadTerminated);
+    }
+
+    void startWithSourceCode(const String& source)
+    {
+        std::unique_ptr<Vector<CSPHeaderAndType>> headers = wrapUnique(new Vector<CSPHeaderAndType>());
+        CSPHeaderAndType headerAndType("contentSecurityPolicy", ContentSecurityPolicyHeaderTypeReport);
+        headers->append(headerAndType);
+        workerThread()->start(WorkerThreadStartupData::create(
+            KURL(ParsedURLString, "http://fake.url/"),
+            "fake user agent",
+            source,
+            nullptr /* cachedMetaData */,
+            DontPauseWorkerGlobalScopeOnStart,
+            headers.get(),
+            "" /* referrerPolicy */,
+            m_securityOrigin.get(),
+            nullptr /* workerClients */,
+            WebAddressSpaceLocal,
+            nullptr /* originTrialTokens */,
+            nullptr /* workerSettings */,
+            V8CacheOptionsDefault));
+    }
+
+    void dispatchMessageEvent()
+    {
+        workerMessagingProxy()->postMessageToWorkerGlobalScope(nullptr /* message */, nullptr /* channels */);
+    }
+
+    InProcessWorkerMessagingProxyForTest* workerMessagingProxy()
+    {
+        return m_workerMessagingProxy.get();
+    }
+
+    DedicatedWorkerThreadForTest* workerThread()
+    {
+        return m_workerMessagingProxy->workerThread();
+    }
+
+private:
+    RefPtr<SecurityOrigin> m_securityOrigin;
+    std::unique_ptr<DummyPageHolder> m_page;
+    std::unique_ptr<InProcessWorkerMessagingProxyForTest> m_workerMessagingProxy;
+};
+
+TEST_F(DedicatedWorkerTest, PendingActivity_NoActivity)
+{
+    const String sourceCode = "// Do nothing";
+    startWithSourceCode(sourceCode);
+
+    // Worker initialization should be counted as a pending activity.
+    EXPECT_TRUE(workerMessagingProxy()->workerGlobalScopeMayHavePendingActivity());
+
+    // There should be no pending activities after the initialization.
+    workerMessagingProxy()->waitUntil(WaitUntilMode::PendingActivityReported);
+    EXPECT_FALSE(workerMessagingProxy()->hasPendingActivity());
+}
+
+TEST_F(DedicatedWorkerTest, PendingActivity_SetTimeout)
+{
+    // Start an oneshot timer on initial script evaluation.
+    const String sourceCode = "setTimeout(function() {}, 50);";
+    startWithSourceCode(sourceCode);
+
+    // Worker initialization should be counted as a pending activity.
+    EXPECT_TRUE(workerMessagingProxy()->workerGlobalScopeMayHavePendingActivity());
+
+    // The timer is fired soon and there should be no pending activities after
+    // that.
+    workerMessagingProxy()->waitUntil(WaitUntilMode::PendingActivityReported);
+    EXPECT_FALSE(workerMessagingProxy()->hasPendingActivity());
+}
+
+TEST_F(DedicatedWorkerTest, PendingActivity_SetInterval)
+{
+    // Start a repeated timer on initial script evaluation, and stop it when a
+    // message is received.
+    const String sourceCode =
+        "var id = setInterval(function() {}, 50);"
+        "addEventListener('message', function(event) { clearInterval(id); });";
+    startWithSourceCode(sourceCode);
+
+    // Worker initialization should be counted as a pending activity.
+    EXPECT_TRUE(workerMessagingProxy()->workerGlobalScopeMayHavePendingActivity());
+
+    // Stop the timer.
+    dispatchMessageEvent();
+    EXPECT_EQ(1u, workerMessagingProxy()->unconfirmedMessageCount());
+    EXPECT_TRUE(workerMessagingProxy()->workerGlobalScopeMayHavePendingActivity());
+    workerMessagingProxy()->waitUntil(WaitUntilMode::MessageConfirmed);
+    EXPECT_EQ(0u, workerMessagingProxy()->unconfirmedMessageCount());
+    EXPECT_TRUE(workerMessagingProxy()->workerGlobalScopeMayHavePendingActivity());
+
+    // There should be no pending activities after the timer is stopped.
+    workerMessagingProxy()->waitUntil(WaitUntilMode::PendingActivityReported);
+    EXPECT_FALSE(workerMessagingProxy()->workerGlobalScopeMayHavePendingActivity());
+}
+
+TEST_F(DedicatedWorkerTest, PendingActivity_SetTimeoutOnMessageEvent)
+{
+    // Start an oneshot timer on a message event.
+    const String sourceCode =
+        "addEventListener('message', function(event) {"
+        "  setTimeout(function() {}, 50);"
+        "});";
+    startWithSourceCode(sourceCode);
+
+    // Worker initialization should be counted as a pending activity.
+    EXPECT_TRUE(workerMessagingProxy()->workerGlobalScopeMayHavePendingActivity());
+    workerMessagingProxy()->waitUntil(WaitUntilMode::PendingActivityReported);
+    EXPECT_FALSE(workerMessagingProxy()->workerGlobalScopeMayHavePendingActivity());
+
+    // A message starts the oneshot timer that is counted as a pending activity.
+    dispatchMessageEvent();
+    EXPECT_EQ(1u, workerMessagingProxy()->unconfirmedMessageCount());
+    EXPECT_TRUE(workerMessagingProxy()->workerGlobalScopeMayHavePendingActivity());
+    workerMessagingProxy()->waitUntil(WaitUntilMode::MessageConfirmed);
+    EXPECT_EQ(0u, workerMessagingProxy()->unconfirmedMessageCount());
+    EXPECT_TRUE(workerMessagingProxy()->workerGlobalScopeMayHavePendingActivity());
+
+    // The timer is fired soon and there should be no pending activities after
+    // that.
+    workerMessagingProxy()->waitUntil(WaitUntilMode::PendingActivityReported);
+    EXPECT_FALSE(workerMessagingProxy()->workerGlobalScopeMayHavePendingActivity());
+}
+
+TEST_F(DedicatedWorkerTest, PendingActivity_SetIntervalOnMessageEvent)
+{
+    // Start a repeated timer on a message event, and stop it when another
+    // message is received.
+    const String sourceCode =
+        "var count = 0;"
+        "var id;"
+        "addEventListener('message', function(event) {"
+        "  if (count++ == 0) {"
+        "    id = setInterval(function() {}, 50);"
+        "  } else {"
+        "    clearInterval(id);"
+        "  }"
+        "});";
+    startWithSourceCode(sourceCode);
+
+    // Worker initialization should be counted as a pending activity.
+    EXPECT_TRUE(workerMessagingProxy()->workerGlobalScopeMayHavePendingActivity());
+    workerMessagingProxy()->waitUntil(WaitUntilMode::PendingActivityReported);
+    EXPECT_FALSE(workerMessagingProxy()->workerGlobalScopeMayHavePendingActivity());
+
+    // The first message event sets the active timer that is counted as a
+    // pending activity.
+    dispatchMessageEvent();
+    EXPECT_EQ(1u, workerMessagingProxy()->unconfirmedMessageCount());
+    EXPECT_TRUE(workerMessagingProxy()->workerGlobalScopeMayHavePendingActivity());
+    workerMessagingProxy()->waitUntil(WaitUntilMode::MessageConfirmed);
+    EXPECT_EQ(0u, workerMessagingProxy()->unconfirmedMessageCount());
+    EXPECT_TRUE(workerMessagingProxy()->workerGlobalScopeMayHavePendingActivity());
+
+    // Run the message loop for a while to make sure the timer is counted as a
+    // pending activity until it's stopped.
+    testing::runDelayedTasks(1000);
+    EXPECT_TRUE(workerMessagingProxy()->workerGlobalScopeMayHavePendingActivity());
+
+    // Stop the timer.
+    dispatchMessageEvent();
+    EXPECT_EQ(1u, workerMessagingProxy()->unconfirmedMessageCount());
+    EXPECT_TRUE(workerMessagingProxy()->workerGlobalScopeMayHavePendingActivity());
+    workerMessagingProxy()->waitUntil(WaitUntilMode::MessageConfirmed);
+    EXPECT_EQ(0u, workerMessagingProxy()->unconfirmedMessageCount());
+    EXPECT_TRUE(workerMessagingProxy()->workerGlobalScopeMayHavePendingActivity());
+
+    // There should be no pending activities after the timer is stopped.
+    workerMessagingProxy()->waitUntil(WaitUntilMode::PendingActivityReported);
+    EXPECT_FALSE(workerMessagingProxy()->workerGlobalScopeMayHavePendingActivity());
+}
+
+} // namespace blink
diff --git a/third_party/WebKit/Source/core/workers/DedicatedWorkerThread.cpp b/third_party/WebKit/Source/core/workers/DedicatedWorkerThread.cpp
index 4d5edffb..82f6a763 100644
--- a/third_party/WebKit/Source/core/workers/DedicatedWorkerThread.cpp
+++ b/third_party/WebKit/Source/core/workers/DedicatedWorkerThread.cpp
@@ -66,11 +66,4 @@
     m_workerBackingThread = nullptr;
 }
 
-void DedicatedWorkerThread::postInitialize()
-{
-    // Notify the parent object of our current active state before the event
-    // loop starts processing tasks.
-    m_workerObjectProxy.reportPendingActivity(false);
-}
-
 } // namespace blink
diff --git a/third_party/WebKit/Source/core/workers/DedicatedWorkerThread.h b/third_party/WebKit/Source/core/workers/DedicatedWorkerThread.h
index c023d7a..6e60e5e6 100644
--- a/third_party/WebKit/Source/core/workers/DedicatedWorkerThread.h
+++ b/third_party/WebKit/Source/core/workers/DedicatedWorkerThread.h
@@ -38,7 +38,7 @@
 class InProcessWorkerObjectProxy;
 class WorkerThreadStartupData;
 
-class DedicatedWorkerThread final : public WorkerThread {
+class CORE_EXPORT DedicatedWorkerThread : public WorkerThread {
 public:
     static std::unique_ptr<DedicatedWorkerThread> create(PassRefPtr<WorkerLoaderProxy>, InProcessWorkerObjectProxy&, double timeOrigin);
     ~DedicatedWorkerThread() override;
@@ -48,11 +48,11 @@
     InProcessWorkerObjectProxy& workerObjectProxy() const { return m_workerObjectProxy; }
 
 protected:
+    DedicatedWorkerThread(PassRefPtr<WorkerLoaderProxy>, InProcessWorkerObjectProxy&, double timeOrigin);
     WorkerOrWorkletGlobalScope* createWorkerGlobalScope(std::unique_ptr<WorkerThreadStartupData>) override;
-    void postInitialize() override;
 
 private:
-    DedicatedWorkerThread(PassRefPtr<WorkerLoaderProxy>, InProcessWorkerObjectProxy&, double timeOrigin);
+    friend class DedicatedWorkerThreadForTest;
 
     std::unique_ptr<WorkerBackingThread> m_workerBackingThread;
     InProcessWorkerObjectProxy& m_workerObjectProxy;
diff --git a/third_party/WebKit/Source/core/workers/InProcessWorkerMessagingProxy.cpp b/third_party/WebKit/Source/core/workers/InProcessWorkerMessagingProxy.cpp
index 55832c57..d52aed13 100644
--- a/third_party/WebKit/Source/core/workers/InProcessWorkerMessagingProxy.cpp
+++ b/third_party/WebKit/Source/core/workers/InProcessWorkerMessagingProxy.cpp
@@ -28,7 +28,6 @@
 
 #include "core/workers/InProcessWorkerMessagingProxy.h"
 
-#include "bindings/core/v8/V8GCController.h"
 #include "core/dom/CrossThreadTask.h"
 #include "core/dom/Document.h"
 #include "core/dom/SecurityContext.h"
@@ -67,7 +66,8 @@
     WorkerGlobalScope* globalScope = toWorkerGlobalScope(scriptContext);
     MessagePortArray* ports = MessagePort::entanglePorts(*scriptContext, std::move(channels));
     globalScope->dispatchEvent(MessageEvent::create(ports, message));
-    workerObjectProxy->confirmMessageFromWorkerObject(V8GCController::hasPendingActivity(globalScope->thread()->isolate(), scriptContext));
+    workerObjectProxy->confirmMessageFromWorkerObject();
+    workerObjectProxy->startPendingActivityTimer();
 }
 
 static int s_liveMessagingProxyCount = 0;
@@ -75,20 +75,9 @@
 } // namespace
 
 InProcessWorkerMessagingProxy::InProcessWorkerMessagingProxy(InProcessWorkerBase* workerObject, WorkerClients* workerClients)
-    : m_executionContext(workerObject->getExecutionContext())
-    , m_workerObjectProxy(InProcessWorkerObjectProxy::create(this))
-    , m_workerObject(workerObject)
-    , m_mayBeDestroyed(false)
-    , m_unconfirmedMessageCount(0)
-    , m_workerThreadHadPendingActivity(false)
-    , m_askedToTerminate(false)
-    , m_workerInspectorProxy(WorkerInspectorProxy::create())
-    , m_workerClients(workerClients)
-    , m_parentFrameTaskRunners(ParentFrameTaskRunners::create(toDocument(m_executionContext.get())->frame()))
+    : InProcessWorkerMessagingProxy(workerObject->getExecutionContext(), workerObject, workerClients)
 {
-    DCHECK(isParentContextThread());
     DCHECK(m_workerObject);
-    s_liveMessagingProxyCount++;
 }
 
 InProcessWorkerMessagingProxy::~InProcessWorkerMessagingProxy()
@@ -151,6 +140,8 @@
 
     std::unique_ptr<ExecutionContextTask> task = createCrossThreadTask(&processMessageOnWorkerGlobalScope, message, passed(std::move(channels)), crossThreadUnretained(&workerObjectProxy()));
     if (m_workerThread) {
+        // A message event is an activity and may initiate another activity.
+        m_workerGlobalScopeMayHavePendingActivity = true;
         ++m_unconfirmedMessageCount;
         m_workerThread->postTask(BLINK_FROM_HERE, std::move(task));
     } else {
@@ -212,13 +203,29 @@
     m_unconfirmedMessageCount = m_queuedEarlyTasks.size();
 
     // Worker initialization means a pending activity.
-    m_workerThreadHadPendingActivity = true;
+    m_workerGlobalScopeMayHavePendingActivity = true;
 
     for (auto& earlyTasks : m_queuedEarlyTasks)
         m_workerThread->postTask(BLINK_FROM_HERE, std::move(earlyTasks));
     m_queuedEarlyTasks.clear();
 }
 
+InProcessWorkerMessagingProxy::InProcessWorkerMessagingProxy(ExecutionContext* executionContext, InProcessWorkerBase* workerObject, WorkerClients* workerClients)
+    : m_executionContext(executionContext)
+    , m_workerObjectProxy(InProcessWorkerObjectProxy::create(this))
+    , m_workerObject(workerObject)
+    , m_mayBeDestroyed(false)
+    , m_unconfirmedMessageCount(0)
+    , m_workerGlobalScopeMayHavePendingActivity(false)
+    , m_askedToTerminate(false)
+    , m_workerInspectorProxy(WorkerInspectorProxy::create())
+    , m_workerClients(workerClients)
+    , m_parentFrameTaskRunners(ParentFrameTaskRunners::create(toDocument(m_executionContext.get())->frame()))
+{
+    DCHECK(isParentContextThread());
+    s_liveMessagingProxyCount++;
+}
+
 void InProcessWorkerMessagingProxy::workerObjectDestroyed()
 {
     DCHECK(isParentContextThread());
@@ -276,26 +283,33 @@
         m_workerInspectorProxy->dispatchMessageFromWorker(message);
 }
 
-void InProcessWorkerMessagingProxy::confirmMessageFromWorkerObject(bool hasPendingActivity)
+void InProcessWorkerMessagingProxy::confirmMessageFromWorkerObject()
 {
     DCHECK(isParentContextThread());
-    if (!m_askedToTerminate) {
-        DCHECK(m_unconfirmedMessageCount);
-        --m_unconfirmedMessageCount;
-    }
-    reportPendingActivity(hasPendingActivity);
+    if (m_askedToTerminate)
+        return;
+    DCHECK(m_unconfirmedMessageCount);
+    --m_unconfirmedMessageCount;
 }
 
-void InProcessWorkerMessagingProxy::reportPendingActivity(bool hasPendingActivity)
+void InProcessWorkerMessagingProxy::pendingActivityFinished()
 {
     DCHECK(isParentContextThread());
-    m_workerThreadHadPendingActivity = hasPendingActivity;
+    DCHECK(m_workerGlobalScopeMayHavePendingActivity);
+    if (m_unconfirmedMessageCount > 0) {
+        // Ignore the report because an inflight message event may initiate a
+        // new activity.
+        return;
+    }
+    m_workerGlobalScopeMayHavePendingActivity = false;
 }
 
 bool InProcessWorkerMessagingProxy::hasPendingActivity() const
 {
     DCHECK(isParentContextThread());
-    return (m_unconfirmedMessageCount || m_workerThreadHadPendingActivity) && !m_askedToTerminate;
+    if (m_askedToTerminate)
+        return false;
+    return m_unconfirmedMessageCount || m_workerGlobalScopeMayHavePendingActivity;
 }
 
 bool InProcessWorkerMessagingProxy::isParentContextThread() const
diff --git a/third_party/WebKit/Source/core/workers/InProcessWorkerMessagingProxy.h b/third_party/WebKit/Source/core/workers/InProcessWorkerMessagingProxy.h
index 87064e25..05583b0b 100644
--- a/third_party/WebKit/Source/core/workers/InProcessWorkerMessagingProxy.h
+++ b/third_party/WebKit/Source/core/workers/InProcessWorkerMessagingProxy.h
@@ -71,9 +71,12 @@
     void dispatchErrorEvent(const String& errorMessage, std::unique_ptr<SourceLocation>, int exceptionId);
     void reportConsoleMessage(MessageSource, MessageLevel, const String& message, std::unique_ptr<SourceLocation>);
     void postMessageToPageInspector(const String&);
-    void confirmMessageFromWorkerObject(bool hasPendingActivity);
-    void reportPendingActivity(bool hasPendingActivity);
-    void workerThreadTerminated();
+
+    // 'virtual' for testing.
+    virtual void confirmMessageFromWorkerObject();
+    virtual void pendingActivityFinished();
+    virtual void workerThreadTerminated();
+
     void workerThreadCreated();
 
     ExecutionContext* getExecutionContext() const { return m_executionContext.get(); }
@@ -93,6 +96,9 @@
     InProcessWorkerObjectProxy& workerObjectProxy() { return *m_workerObjectProxy.get(); }
 
 private:
+    friend class InProcessWorkerMessagingProxyForTest;
+    InProcessWorkerMessagingProxy(ExecutionContext*, InProcessWorkerBase*, WorkerClients*);
+
     void workerObjectDestroyedInternal();
 
     // WorkerLoaderProxyProvider
@@ -113,10 +119,7 @@
     // Unconfirmed messages from the parent context thread to the worker thread.
     unsigned m_unconfirmedMessageCount;
 
-    // The latest confirmation from worker thread reported that it was still
-    // active.
-    bool m_workerThreadHadPendingActivity;
-
+    bool m_workerGlobalScopeMayHavePendingActivity;
     bool m_askedToTerminate;
 
     // Tasks are queued here until there's a thread object created.
diff --git a/third_party/WebKit/Source/core/workers/InProcessWorkerObjectProxy.cpp b/third_party/WebKit/Source/core/workers/InProcessWorkerObjectProxy.cpp
index a47dda4..fea2bb6 100644
--- a/third_party/WebKit/Source/core/workers/InProcessWorkerObjectProxy.cpp
+++ b/third_party/WebKit/Source/core/workers/InProcessWorkerObjectProxy.cpp
@@ -32,12 +32,15 @@
 
 #include "bindings/core/v8/SerializedScriptValue.h"
 #include "bindings/core/v8/SourceLocation.h"
+#include "bindings/core/v8/V8GCController.h"
 #include "core/dom/CrossThreadTask.h"
 #include "core/dom/Document.h"
 #include "core/dom/ExecutionContext.h"
 #include "core/inspector/ConsoleMessage.h"
 #include "core/workers/InProcessWorkerMessagingProxy.h"
 #include "core/workers/ParentFrameTaskRunners.h"
+#include "core/workers/WorkerGlobalScope.h"
+#include "core/workers/WorkerThread.h"
 #include "platform/CrossThreadFunctional.h"
 #include "public/platform/WebTaskRunner.h"
 #include "wtf/Functional.h"
@@ -46,12 +49,17 @@
 
 namespace blink {
 
+const double kDefaultIntervalInSec = 1;
+const double kMaxIntervalInSec = 30;
+
 std::unique_ptr<InProcessWorkerObjectProxy> InProcessWorkerObjectProxy::create(InProcessWorkerMessagingProxy* messagingProxy)
 {
     DCHECK(messagingProxy);
     return wrapUnique(new InProcessWorkerObjectProxy(messagingProxy));
 }
 
+InProcessWorkerObjectProxy::~InProcessWorkerObjectProxy() {}
+
 void InProcessWorkerObjectProxy::postMessageToWorkerObject(PassRefPtr<SerializedScriptValue> message, std::unique_ptr<MessagePortChannelArray> channels)
 {
     getParentFrameTaskRunners()->get(TaskType::PostedMessage)->postTask(BLINK_FROM_HERE, crossThreadBind(&InProcessWorkerMessagingProxy::postMessageToWorkerObject, crossThreadUnretained(m_messagingProxy), message, passed(std::move(channels))));
@@ -64,14 +72,22 @@
     getExecutionContext()->postTask(BLINK_FROM_HERE, std::move(task));
 }
 
-void InProcessWorkerObjectProxy::confirmMessageFromWorkerObject(bool hasPendingActivity)
+void InProcessWorkerObjectProxy::confirmMessageFromWorkerObject()
 {
-    getParentFrameTaskRunners()->get(TaskType::Internal)->postTask(BLINK_FROM_HERE, crossThreadBind(&InProcessWorkerMessagingProxy::confirmMessageFromWorkerObject, crossThreadUnretained(m_messagingProxy), hasPendingActivity));
+    getParentFrameTaskRunners()->get(TaskType::Internal)->postTask(BLINK_FROM_HERE, crossThreadBind(&InProcessWorkerMessagingProxy::confirmMessageFromWorkerObject, crossThreadUnretained(m_messagingProxy)));
 }
 
-void InProcessWorkerObjectProxy::reportPendingActivity(bool hasPendingActivity)
+void InProcessWorkerObjectProxy::startPendingActivityTimer()
 {
-    getParentFrameTaskRunners()->get(TaskType::Internal)->postTask(BLINK_FROM_HERE, crossThreadBind(&InProcessWorkerMessagingProxy::reportPendingActivity, crossThreadUnretained(m_messagingProxy), hasPendingActivity));
+    if (m_timer->isActive()) {
+        // Reset the next interval duration to check new activity state timely.
+        // For example, a long-running activity can be cancelled by a message
+        // event.
+        m_nextIntervalInSec = kDefaultIntervalInSec;
+        return;
+    }
+    m_timer->startOneShot(m_nextIntervalInSec, BLINK_FROM_HERE);
+    m_nextIntervalInSec = std::min(m_nextIntervalInSec * 1.5, m_maxIntervalInSec);
 }
 
 void InProcessWorkerObjectProxy::reportException(const String& errorMessage, std::unique_ptr<SourceLocation> location, int exceptionId)
@@ -94,6 +110,18 @@
     }
 }
 
+void InProcessWorkerObjectProxy::didEvaluateWorkerScript(bool)
+{
+    startPendingActivityTimer();
+}
+
+void InProcessWorkerObjectProxy::workerGlobalScopeStarted(WorkerOrWorkletGlobalScope* globalScope)
+{
+    DCHECK(!m_workerGlobalScope);
+    m_workerGlobalScope = toWorkerGlobalScope(globalScope);
+    m_timer = wrapUnique(new Timer<InProcessWorkerObjectProxy>(this, &InProcessWorkerObjectProxy::checkPendingActivity));
+}
+
 void InProcessWorkerObjectProxy::workerGlobalScopeClosed()
 {
     getParentFrameTaskRunners()->get(TaskType::Internal)->postTask(BLINK_FROM_HERE, crossThreadBind(&InProcessWorkerMessagingProxy::terminateWorkerGlobalScope, crossThreadUnretained(m_messagingProxy)));
@@ -105,8 +133,16 @@
     getParentFrameTaskRunners()->get(TaskType::Internal)->postTask(BLINK_FROM_HERE, crossThreadBind(&InProcessWorkerMessagingProxy::workerThreadTerminated, crossThreadUnretained(m_messagingProxy)));
 }
 
+void InProcessWorkerObjectProxy::willDestroyWorkerGlobalScope()
+{
+    m_timer.reset();
+    m_workerGlobalScope = nullptr;
+}
+
 InProcessWorkerObjectProxy::InProcessWorkerObjectProxy(InProcessWorkerMessagingProxy* messagingProxy)
     : m_messagingProxy(messagingProxy)
+    , m_nextIntervalInSec(kDefaultIntervalInSec)
+    , m_maxIntervalInSec(kMaxIntervalInSec)
 {
 }
 
@@ -122,4 +158,21 @@
     return m_messagingProxy->getExecutionContext();
 }
 
+void InProcessWorkerObjectProxy::checkPendingActivity(TimerBase*)
+{
+    bool hasPendingActivity = V8GCController::hasPendingActivity(m_workerGlobalScope->thread()->isolate(), m_workerGlobalScope);
+    if (!hasPendingActivity) {
+        // Report all activities are done.
+        getParentFrameTaskRunners()->get(TaskType::Internal)->postTask(BLINK_FROM_HERE, crossThreadBind(&InProcessWorkerMessagingProxy::pendingActivityFinished, crossThreadUnretained(m_messagingProxy)));
+
+        // Don't schedule a timer. It will be started again when a message event
+        // is dispatched.
+        m_nextIntervalInSec = kDefaultIntervalInSec;
+        return;
+    }
+
+    // There is still a pending activity. Check it later.
+    startPendingActivityTimer();
+}
+
 } // namespace blink
diff --git a/third_party/WebKit/Source/core/workers/InProcessWorkerObjectProxy.h b/third_party/WebKit/Source/core/workers/InProcessWorkerObjectProxy.h
index ddc9cde..d721344 100644
--- a/third_party/WebKit/Source/core/workers/InProcessWorkerObjectProxy.h
+++ b/third_party/WebKit/Source/core/workers/InProcessWorkerObjectProxy.h
@@ -34,6 +34,7 @@
 #include "core/CoreExport.h"
 #include "core/dom/MessagePort.h"
 #include "core/workers/WorkerReportingProxy.h"
+#include "platform/Timer.h"
 #include "platform/heap/Handle.h"
 #include "wtf/PassRefPtr.h"
 #include <memory>
@@ -45,46 +46,69 @@
 class ExecutionContextTask;
 class InProcessWorkerMessagingProxy;
 class ParentFrameTaskRunners;
+class WorkerGlobalScope;
 class WorkerOrWorkletGlobalScope;
 
 // A proxy to talk to the worker object. This object is created on the
 // parent context thread (i.e. usually the main thread), passed on to
-// the worker thread, and used just to proxy messages to the
+// the worker thread, and used to proxy messages to the
 // InProcessWorkerMessagingProxy on the parent context thread.
 //
+// This also checks pending activities on WorkerGlobalScope and reports a result
+// to the message proxy when an exponential backoff timer is fired.
+//
 // Used only by in-process workers (DedicatedWorker and CompositorWorker.)
 class CORE_EXPORT InProcessWorkerObjectProxy : public WorkerReportingProxy {
     USING_FAST_MALLOC(InProcessWorkerObjectProxy);
     WTF_MAKE_NONCOPYABLE(InProcessWorkerObjectProxy);
 public:
     static std::unique_ptr<InProcessWorkerObjectProxy> create(InProcessWorkerMessagingProxy*);
-    ~InProcessWorkerObjectProxy() override { }
+    ~InProcessWorkerObjectProxy() override;
 
     void postMessageToWorkerObject(PassRefPtr<SerializedScriptValue>, std::unique_ptr<MessagePortChannelArray>);
     void postTaskToMainExecutionContext(std::unique_ptr<ExecutionContextTask>);
-    void confirmMessageFromWorkerObject(bool hasPendingActivity);
-    void reportPendingActivity(bool hasPendingActivity);
+    void confirmMessageFromWorkerObject();
+    void startPendingActivityTimer();
 
     // WorkerReportingProxy overrides.
     void reportException(const String& errorMessage, std::unique_ptr<SourceLocation>, int exceptionId) override;
     void reportConsoleMessage(MessageSource, MessageLevel, const String& message, SourceLocation*) override;
     void postMessageToPageInspector(const String&) override;
-    void didEvaluateWorkerScript(bool success) override { }
-    void workerGlobalScopeStarted(WorkerOrWorkletGlobalScope*) override { }
+    void didEvaluateWorkerScript(bool success) override;
+    void workerGlobalScopeStarted(WorkerOrWorkletGlobalScope*) override;
     void workerGlobalScopeClosed() override;
     void workerThreadTerminated() override;
-    void willDestroyWorkerGlobalScope() override { }
+    void willDestroyWorkerGlobalScope() override;
 
 protected:
     InProcessWorkerObjectProxy(InProcessWorkerMessagingProxy*);
     virtual ExecutionContext* getExecutionContext();
 
 private:
+    friend class InProcessWorkerMessagingProxyForTest;
+
+    void checkPendingActivity(TimerBase*);
+
     // Returns the parent frame's task runners.
     ParentFrameTaskRunners* getParentFrameTaskRunners();
 
     // This object always outlives this proxy.
     InProcessWorkerMessagingProxy* m_messagingProxy;
+
+    // Used for checking pending activities on the worker global scope. This is
+    // cancelled when the worker global scope is destroyed.
+    std::unique_ptr<Timer<InProcessWorkerObjectProxy>> m_timer;
+
+    // The next interval duration of the timer. This is initially set to
+    // kDefaultIntervalInSec and exponentially increased up to
+    // |m_maxIntervalInSec|.
+    double m_nextIntervalInSec;
+
+    // The max interval duration of the timer. This is usually kMaxIntervalInSec
+    // but made as a member variable for testing.
+    double m_maxIntervalInSec;
+
+    Persistent<WorkerGlobalScope> m_workerGlobalScope;
 };
 
 } // namespace blink
diff --git a/third_party/WebKit/Source/core/workers/WorkerGlobalScope.cpp b/third_party/WebKit/Source/core/workers/WorkerGlobalScope.cpp
index 4d5192d..ece0725 100644
--- a/third_party/WebKit/Source/core/workers/WorkerGlobalScope.cpp
+++ b/third_party/WebKit/Source/core/workers/WorkerGlobalScope.cpp
@@ -235,6 +235,11 @@
     return v8::Local<v8::Object>();
 }
 
+bool WorkerGlobalScope::hasPendingActivity() const
+{
+    return m_timers.hasInstalledTimeout();
+}
+
 bool WorkerGlobalScope::isJSExecutionForbidden() const
 {
     return m_scriptController->isExecutionForbidden();
@@ -287,7 +292,8 @@
 }
 
 WorkerGlobalScope::WorkerGlobalScope(const KURL& url, const String& userAgent, WorkerThread* thread, double timeOrigin, std::unique_ptr<SecurityOrigin::PrivilegeData> starterOriginPrivilageData, WorkerClients* workerClients)
-    : m_url(url)
+    : ActiveScriptWrappable(this)
+    , m_url(url)
     , m_userAgent(userAgent)
     , m_v8CacheOptions(V8CacheOptionsDefault)
     , m_scriptController(WorkerOrWorkletScriptController::create(this, thread->isolate()))
diff --git a/third_party/WebKit/Source/core/workers/WorkerGlobalScope.h b/third_party/WebKit/Source/core/workers/WorkerGlobalScope.h
index 1c7cdd4..f3e44dd 100644
--- a/third_party/WebKit/Source/core/workers/WorkerGlobalScope.h
+++ b/third_party/WebKit/Source/core/workers/WorkerGlobalScope.h
@@ -27,6 +27,7 @@
 #ifndef WorkerGlobalScope_h
 #define WorkerGlobalScope_h
 
+#include "bindings/core/v8/ActiveScriptWrappable.h"
 #include "bindings/core/v8/V8CacheOptions.h"
 #include "bindings/core/v8/WorkerOrWorkletScriptController.h"
 #include "core/CoreExport.h"
@@ -56,7 +57,7 @@
 class WorkerNavigator;
 class WorkerThread;
 
-class CORE_EXPORT WorkerGlobalScope : public EventTargetWithInlineData, public SecurityContext, public WorkerOrWorkletGlobalScope, public Supplementable<WorkerGlobalScope>, public DOMWindowBase64 {
+class CORE_EXPORT WorkerGlobalScope : public EventTargetWithInlineData, public ActiveScriptWrappable, public SecurityContext, public WorkerOrWorkletGlobalScope, public Supplementable<WorkerGlobalScope>, public DOMWindowBase64 {
     DEFINE_WRAPPERTYPEINFO();
     USING_GARBAGE_COLLECTED_MIXIN(WorkerGlobalScope);
 public:
@@ -97,6 +98,9 @@
     v8::Local<v8::Object> wrap(v8::Isolate*, v8::Local<v8::Object> creationContext) final;
     v8::Local<v8::Object> associateWithWrapper(v8::Isolate*, const WrapperTypeInfo*, v8::Local<v8::Object> wrapper) final;
 
+    // ActiveScriptWrappable
+    bool hasPendingActivity() const override;
+
     // ExecutionContext
     bool isWorkerGlobalScope() const final { return true; }
     bool isJSExecutionForbidden() const final;
diff --git a/third_party/WebKit/Source/core/workers/WorkerGlobalScope.idl b/third_party/WebKit/Source/core/workers/WorkerGlobalScope.idl
index 1632dc1..811d8ba 100644
--- a/third_party/WebKit/Source/core/workers/WorkerGlobalScope.idl
+++ b/third_party/WebKit/Source/core/workers/WorkerGlobalScope.idl
@@ -27,6 +27,8 @@
 // https://html.spec.whatwg.org/#the-workerglobalscope-common-interface
 
 [
+    ActiveScriptWrappable,
+    DependentLifetime,
     Exposed=Worker,
 ] interface WorkerGlobalScope : EventTarget {
     readonly attribute WorkerGlobalScope self;
diff --git a/third_party/WebKit/Source/core/workers/WorkerThread.cpp b/third_party/WebKit/Source/core/workers/WorkerThread.cpp
index a9489c9..f9897d6 100644
--- a/third_party/WebKit/Source/core/workers/WorkerThread.cpp
+++ b/third_party/WebKit/Source/core/workers/WorkerThread.cpp
@@ -28,8 +28,6 @@
 
 #include "bindings/core/v8/Microtask.h"
 #include "bindings/core/v8/ScriptSourceCode.h"
-#include "bindings/core/v8/V8GCController.h"
-#include "bindings/core/v8/V8IdleTaskRunner.h"
 #include "bindings/core/v8/WorkerOrWorkletScriptController.h"
 #include "core/inspector/ConsoleMessageStorage.h"
 #include "core/inspector/InspectorInstrumentation.h"
@@ -548,8 +546,6 @@
         workerGlobalScope->didEvaluateWorkerScript();
         m_workerReportingProxy.didEvaluateWorkerScript(success);
     }
-
-    postInitialize();
 }
 
 void WorkerThread::prepareForShutdownOnWorkerThread()
diff --git a/third_party/WebKit/Source/core/workers/WorkerThread.h b/third_party/WebKit/Source/core/workers/WorkerThread.h
index 6a79d933..ef70bad 100644
--- a/third_party/WebKit/Source/core/workers/WorkerThread.h
+++ b/third_party/WebKit/Source/core/workers/WorkerThread.h
@@ -171,9 +171,6 @@
     // out of this class.
     virtual bool isOwningBackingThread() const { return true; }
 
-    // Called on the worker thread.
-    virtual void postInitialize() { }
-
 private:
     friend class WorkerThreadTest;
     FRIEND_TEST_ALL_PREFIXES(WorkerThreadTest, StartAndTerminateOnInitialization_TerminateWhileDebuggerTaskIsRunning);
diff --git a/third_party/WebKit/Source/platform/scheduler/child/compositor_worker_scheduler.cc b/third_party/WebKit/Source/platform/scheduler/child/compositor_worker_scheduler.cc
index 251d3a9..3e40551 100644
--- a/third_party/WebKit/Source/platform/scheduler/child/compositor_worker_scheduler.cc
+++ b/third_party/WebKit/Source/platform/scheduler/child/compositor_worker_scheduler.cc
@@ -93,7 +93,6 @@
   void SetTimeDomain(TimeDomain* domain) override { NOTREACHED(); }
 
   TimeDomain* GetTimeDomain() const override {
-    NOTREACHED();
     return nullptr;
   }