Title: [291467] trunk
Revision
291467
Author
[email protected]
Date
2022-03-18 00:33:15 -0700 (Fri, 18 Mar 2022)

Log Message

Keep service workers alive when they are inspected even though they should be terminated
https://bugs.webkit.org/show_bug.cgi?id=237827
<rdar://88313935>

Reviewed by Alex Christensen.

Source/WebCore:

Store in SWServerWorker whether a worker is inspected or is processing push events.
In that case, we delay termination of workers until it is no longer inspected or no longer processing push events.
Two code paths are happening:
1. A service worker was triggered with service worker clients, and all service worker clients are removed.
   At that point, we were previously terminating service workers after a delay.
   Instead, we now only terminate workers that are no longer inspected or no longer processing push events.
   We reschedule the timer to continue trying removing the context connection.
2. A service worker is not stopped by removal of service worker clients. In that case, we need to terminate
   the service workers when inspected and/or push counter gets back to regular (not inspected, no push counter).
   When terminating such a service worker, we try removing the context connection as well.

To make sure SWServerWorker knows whether inspectable or not, we add connection support to transmit whether inspected from WebProcess.
ServiceWorkerInspectorProxy is responsible to update the inspectable value.
Introduce internals API to set inspected state of a service worker.

Covered by new API tests.

* testing/ServiceWorkerInternals.cpp:
* testing/ServiceWorkerInternals.h:
* testing/ServiceWorkerInternals.idl:
* workers/service/context/SWContextManager.cpp:
* workers/service/context/SWContextManager.h:
* workers/service/context/ServiceWorkerInspectorProxy.cpp:
* workers/service/server/SWServer.cpp:
* workers/service/server/SWServer.h:
* workers/service/server/SWServerToContextConnection.cpp:
* workers/service/server/SWServerToContextConnection.h:
* workers/service/server/SWServerWorker.cpp:
* workers/service/server/SWServerWorker.h:

Source/WebKit:

* NetworkProcess/ServiceWorker/WebSWServerToContextConnection.messages.in:
* WebProcess/Storage/WebSWContextManagerConnection.cpp:
* WebProcess/Storage/WebSWContextManagerConnection.h:

Tools:

* TestWebKitAPI/Tests/WebKitCocoa/PushAPI.mm:

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (291466 => 291467)


--- trunk/Source/WebCore/ChangeLog	2022-03-18 04:23:25 UTC (rev 291466)
+++ trunk/Source/WebCore/ChangeLog	2022-03-18 07:33:15 UTC (rev 291467)
@@ -1,3 +1,41 @@
+2022-03-18  Youenn Fablet  <[email protected]>
+
+        Keep service workers alive when they are inspected even though they should be terminated
+        https://bugs.webkit.org/show_bug.cgi?id=237827
+        <rdar://88313935>
+
+        Reviewed by Alex Christensen.
+
+        Store in SWServerWorker whether a worker is inspected or is processing push events.
+        In that case, we delay termination of workers until it is no longer inspected or no longer processing push events.
+        Two code paths are happening:
+        1. A service worker was triggered with service worker clients, and all service worker clients are removed.
+           At that point, we were previously terminating service workers after a delay.
+           Instead, we now only terminate workers that are no longer inspected or no longer processing push events.
+           We reschedule the timer to continue trying removing the context connection.
+        2. A service worker is not stopped by removal of service worker clients. In that case, we need to terminate
+           the service workers when inspected and/or push counter gets back to regular (not inspected, no push counter).
+           When terminating such a service worker, we try removing the context connection as well.
+
+        To make sure SWServerWorker knows whether inspectable or not, we add connection support to transmit whether inspected from WebProcess.
+        ServiceWorkerInspectorProxy is responsible to update the inspectable value.
+        Introduce internals API to set inspected state of a service worker.
+
+        Covered by new API tests.
+
+        * testing/ServiceWorkerInternals.cpp:
+        * testing/ServiceWorkerInternals.h:
+        * testing/ServiceWorkerInternals.idl:
+        * workers/service/context/SWContextManager.cpp:
+        * workers/service/context/SWContextManager.h:
+        * workers/service/context/ServiceWorkerInspectorProxy.cpp:
+        * workers/service/server/SWServer.cpp:
+        * workers/service/server/SWServer.h:
+        * workers/service/server/SWServerToContextConnection.cpp:
+        * workers/service/server/SWServerToContextConnection.h:
+        * workers/service/server/SWServerWorker.cpp:
+        * workers/service/server/SWServerWorker.h:
+
 2022-03-17  Matt Woodrow  <[email protected]>
 
         Subgrid items should always be stretched.

Modified: trunk/Source/WebCore/testing/ServiceWorkerInternals.cpp (291466 => 291467)


--- trunk/Source/WebCore/testing/ServiceWorkerInternals.cpp	2022-03-18 04:23:25 UTC (rev 291466)
+++ trunk/Source/WebCore/testing/ServiceWorkerInternals.cpp	2022-03-18 07:33:15 UTC (rev 291467)
@@ -199,6 +199,11 @@
     return client.identifier().toString();
 }
 
+void ServiceWorkerInternals::setAsInspected(bool isInspected)
+{
+    SWContextManager::singleton().setAsInspected(m_identifier, isInspected);
+}
+
 } // namespace WebCore
 
 #endif

Modified: trunk/Source/WebCore/testing/ServiceWorkerInternals.h (291466 => 291467)


--- trunk/Source/WebCore/testing/ServiceWorkerInternals.h	2022-03-18 04:23:25 UTC (rev 291466)
+++ trunk/Source/WebCore/testing/ServiceWorkerInternals.h	2022-03-18 07:33:15 UTC (rev 291467)
@@ -73,6 +73,7 @@
     bool fetchEventIsSameSite(FetchEvent&);
 
     String serviceWorkerClientInternalIdentifier(const ServiceWorkerClient&);
+    void setAsInspected(bool);
 
 private:
     explicit ServiceWorkerInternals(ServiceWorkerIdentifier);

Modified: trunk/Source/WebCore/testing/ServiceWorkerInternals.idl (291466 => 291467)


--- trunk/Source/WebCore/testing/ServiceWorkerInternals.idl	2022-03-18 04:23:25 UTC (rev 291466)
+++ trunk/Source/WebCore/testing/ServiceWorkerInternals.idl	2022-03-18 07:33:15 UTC (rev 291467)
@@ -52,4 +52,6 @@
     boolean fetchEventIsSameSite(FetchEvent event);
 
     DOMString serviceWorkerClientInternalIdentifier(ServiceWorkerClient client);
+
+    undefined setAsInspected(boolean isInspected);
 };

Modified: trunk/Source/WebCore/workers/service/context/SWContextManager.cpp (291466 => 291467)


--- trunk/Source/WebCore/workers/service/context/SWContextManager.cpp	2022-03-18 04:23:25 UTC (rev 291466)
+++ trunk/Source/WebCore/workers/service/context/SWContextManager.cpp	2022-03-18 07:33:15 UTC (rev 291467)
@@ -208,6 +208,12 @@
         stopWorker(serviceWorker, workerTerminationTimeout, [] { });
 }
 
+void SWContextManager::setAsInspected(ServiceWorkerIdentifier identifier, bool isInspected)
+{
+    if (m_connection)
+        m_connection->setAsInspected(identifier, isInspected);
+}
+
 } // namespace WebCore
 
 #endif

Modified: trunk/Source/WebCore/workers/service/context/SWContextManager.h (291466 => 291467)


--- trunk/Source/WebCore/workers/service/context/SWContextManager.h	2022-03-18 04:23:25 UTC (rev 291466)
+++ trunk/Source/WebCore/workers/service/context/SWContextManager.h	2022-03-18 07:33:15 UTC (rev 291467)
@@ -69,6 +69,7 @@
         virtual void claim(ServiceWorkerIdentifier, CompletionHandler<void(ExceptionOr<void>&&)>&&) = 0;
 
         virtual void didFailHeartBeatCheck(ServiceWorkerIdentifier) = 0;
+        virtual void setAsInspected(ServiceWorkerIdentifier, bool) = 0;
 
         virtual bool isThrottleable() const = 0;
         virtual PageIdentifier pageIdentifier() const = 0;
@@ -110,6 +111,8 @@
     static constexpr Seconds workerTerminationTimeout { 10_s };
     static constexpr Seconds syncWorkerTerminationTimeout { 100_ms }; // Only used by layout tests.
 
+    WEBCORE_EXPORT void setAsInspected(ServiceWorkerIdentifier, bool);
+
 private:
     SWContextManager() = default;
 

Modified: trunk/Source/WebCore/workers/service/context/ServiceWorkerInspectorProxy.cpp (291466 => 291467)


--- trunk/Source/WebCore/workers/service/context/ServiceWorkerInspectorProxy.cpp	2022-03-18 04:23:25 UTC (rev 291466)
+++ trunk/Source/WebCore/workers/service/context/ServiceWorkerInspectorProxy.cpp	2022-03-18 07:33:15 UTC (rev 291467)
@@ -61,6 +61,7 @@
 {
     m_channel = &channel;
 
+    SWContextManager::singleton().setAsInspected(m_serviceWorkerThreadProxy.identifier(), true);
     m_serviceWorkerThreadProxy.thread().runLoop().postDebuggerTask([] (ScriptExecutionContext& context) {
         downcast<WorkerGlobalScope>(context).inspectorController().connectFrontend();
     });
@@ -71,6 +72,7 @@
     ASSERT_UNUSED(channel, &channel == m_channel);
     m_channel = nullptr;
 
+    SWContextManager::singleton().setAsInspected(m_serviceWorkerThreadProxy.identifier(), false);
     m_serviceWorkerThreadProxy.thread().runLoop().postDebuggerTask([] (ScriptExecutionContext& context) {
         downcast<WorkerGlobalScope>(context).inspectorController().disconnectFrontend(DisconnectReason::InspectorDestroyed);
 

Modified: trunk/Source/WebCore/workers/service/server/SWServer.cpp (291466 => 291467)


--- trunk/Source/WebCore/workers/service/server/SWServer.cpp	2022-03-18 04:23:25 UTC (rev 291466)
+++ trunk/Source/WebCore/workers/service/server/SWServer.cpp	2022-03-18 07:33:15 UTC (rev 291467)
@@ -1038,17 +1038,17 @@
         iterator->value.terminateServiceWorkersTimer = makeUnique<Timer>([clientOrigin, clientRegistrableDomain, this] {
             Vector<SWServerWorker*> workersToTerminate;
             for (auto& worker : m_runningOrTerminatingWorkers.values()) {
-                if (worker->isRunning() && worker->origin() == clientOrigin)
+                if (worker->isRunning() && worker->origin() == clientOrigin && !worker->shouldContinue())
                     workersToTerminate.append(worker.ptr());
             }
             for (auto* worker : workersToTerminate)
                 worker->terminate();
 
-            if (!m_clientsByRegistrableDomain.contains(clientRegistrableDomain)) {
-                if (auto* connection = contextConnectionForRegistrableDomain(clientRegistrableDomain)) {
-                    removeContextConnection(*connection);
-                    connection->connectionIsNoLongerNeeded();
-                }
+            if (removeContextConnectionIfPossible(clientRegistrableDomain) == ShouldDelayRemoval::Yes) {
+                auto iterator = m_clientIdentifiersPerOrigin.find(clientOrigin);
+                ASSERT(iterator != m_clientIdentifiersPerOrigin.end());
+                iterator->value.terminateServiceWorkersTimer->startOneShot(m_isProcessTerminationDelayEnabled ? defaultTerminationDelay : defaultPushMessageDuration);
+                return;
             }
 
             m_clientIdentifiersPerOrigin.remove(clientOrigin);
@@ -1074,6 +1074,25 @@
     m_clientToControllingRegistration.remove(registrationIterator);
 }
 
+SWServer::ShouldDelayRemoval SWServer::removeContextConnectionIfPossible(const RegistrableDomain& domain)
+{
+    if (m_clientsByRegistrableDomain.contains(domain))
+        return ShouldDelayRemoval::No;
+
+    auto* connection = contextConnectionForRegistrableDomain(domain);
+    if (!connection)
+        return ShouldDelayRemoval::No;
+
+    for (auto& worker : m_runningOrTerminatingWorkers.values()) {
+        if (worker->isRunning() && worker->registrableDomain() == domain && worker->shouldContinue())
+            return ShouldDelayRemoval::Yes;
+    }
+
+    removeContextConnection(*connection);
+    connection->connectionIsNoLongerNeeded();
+    return ShouldDelayRemoval::No;
+}
+
 void SWServer::handleLowMemoryWarning()
 {
     // Accelerating the delayed termination of unused service workers due to memory pressure.
@@ -1281,14 +1300,18 @@
             }
 
             auto serviceWorkerIdentifier = worker->identifier();
-            auto terminateWorkerTimer = makeUnique<Timer>([worker = WTFMove(worker)] {
-                RELEASE_LOG_ERROR(ServiceWorker, "Terminating service worker as processing push event took too much time");
-                worker->terminate();
+
+            worker->incrementPushEventCounter();
+            auto terminateWorkerTimer = makeUnique<Timer>([worker] {
+                RELEASE_LOG_ERROR(ServiceWorker, "Service worker is taking too much time to process a push event");
+                worker->decrementPushEventCounter();
             });
-            terminateWorkerTimer->startOneShot(weakThis && weakThis->m_isProcessTerminationDelayEnabled ? defaultTerminationDelay : 2_s);
-            connectionOrStatus.value()->firePushEvent(serviceWorkerIdentifier, data, [callback = WTFMove(callback), terminateWorkerTimer = WTFMove(terminateWorkerTimer)](bool succeeded) mutable {
-                if (terminateWorkerTimer->isActive())
+            terminateWorkerTimer->startOneShot(weakThis && weakThis->m_isProcessTerminationDelayEnabled ? defaultTerminationDelay : defaultPushMessageDuration);
+            connectionOrStatus.value()->firePushEvent(serviceWorkerIdentifier, data, [callback = WTFMove(callback), terminateWorkerTimer = WTFMove(terminateWorkerTimer), worker = WTFMove(worker)](bool succeeded) mutable {
+                if (terminateWorkerTimer->isActive()) {
+                    worker->decrementPushEventCounter();
                     terminateWorkerTimer->stop();
+                }
 
                 callback(succeeded);
             });

Modified: trunk/Source/WebCore/workers/service/server/SWServer.h (291466 => 291467)


--- trunk/Source/WebCore/workers/service/server/SWServer.h	2022-03-18 04:23:25 UTC (rev 291466)
+++ trunk/Source/WebCore/workers/service/server/SWServer.h	2022-03-18 07:33:15 UTC (rev 291467)
@@ -218,6 +218,7 @@
     WEBCORE_EXPORT void handleLowMemoryWarning();
 
     static constexpr Seconds defaultTerminationDelay = 10_s;
+    static constexpr Seconds defaultPushMessageDuration = 2_s;
 
     LastNavigationWasAppInitiated clientIsAppInitiatedForRegistrableDomain(const RegistrableDomain&);
     bool shouldRunServiceWorkersOnMainThreadForTesting() const { return m_shouldRunServiceWorkersOnMainThreadForTesting; }
@@ -230,7 +231,11 @@
     ScriptExecutionContextIdentifier clientIdFromVisibleClientId(const String& visibleIdentifier) const { return m_visibleClientIdToInternalClientIdMap.get(visibleIdentifier); }
 
     void forEachServiceWorker(const Function<bool(const SWServerWorker&)>&) const;
+    bool hasClientsWithOrigin(const ClientOrigin& origin) { return m_clientIdentifiersPerOrigin.contains(origin); }
 
+    enum class ShouldDelayRemoval : bool { No, Yes };
+    ShouldDelayRemoval removeContextConnectionIfPossible(const RegistrableDomain&);
+
 private:
     void validateRegistrationDomain(WebCore::RegistrableDomain, ServiceWorkerJobType, CompletionHandler<void(bool)>&&);
 

Modified: trunk/Source/WebCore/workers/service/server/SWServerToContextConnection.cpp (291466 => 291467)


--- trunk/Source/WebCore/workers/service/server/SWServerToContextConnection.cpp	2022-03-18 04:23:25 UTC (rev 291466)
+++ trunk/Source/WebCore/workers/service/server/SWServerToContextConnection.cpp	2022-03-18 07:33:15 UTC (rev 291467)
@@ -137,6 +137,12 @@
         worker->didFailHeartBeatCheck();
 }
 
+void SWServerToContextConnection::setAsInspected(ServiceWorkerIdentifier identifier, bool isInspected)
+{
+    if (auto* worker = SWServerWorker::existingWorkerForIdentifier(identifier))
+        worker->setAsInspected(isInspected);
+}
+
 void SWServerToContextConnection::terminateWhenPossible()
 {
     m_shouldTerminateWhenPossible = true;

Modified: trunk/Source/WebCore/workers/service/server/SWServerToContextConnection.h (291466 => 291467)


--- trunk/Source/WebCore/workers/service/server/SWServerToContextConnection.h	2022-03-18 04:23:25 UTC (rev 291466)
+++ trunk/Source/WebCore/workers/service/server/SWServerToContextConnection.h	2022-03-18 07:33:15 UTC (rev 291467)
@@ -79,6 +79,7 @@
     WEBCORE_EXPORT void claim(ServiceWorkerIdentifier, CompletionHandler<void(std::optional<ExceptionData>&&)>&&);
     WEBCORE_EXPORT void setScriptResource(ServiceWorkerIdentifier, URL&& scriptURL, ServiceWorkerContextData::ImportedScript&&);
     WEBCORE_EXPORT void didFailHeartBeatCheck(ServiceWorkerIdentifier);
+    WEBCORE_EXPORT void setAsInspected(ServiceWorkerIdentifier, bool);
     WEBCORE_EXPORT void findClientByVisibleIdentifier(ServiceWorkerIdentifier, const String& clientIdentifier, CompletionHandler<void(std::optional<WebCore::ServiceWorkerClientData>&&)>&&);
 
     const RegistrableDomain& registrableDomain() const { return m_registrableDomain; }

Modified: trunk/Source/WebCore/workers/service/server/SWServerWorker.cpp (291466 => 291467)


--- trunk/Source/WebCore/workers/service/server/SWServerWorker.cpp	2022-03-18 04:23:25 UTC (rev 291466)
+++ trunk/Source/WebCore/workers/service/server/SWServerWorker.cpp	2022-03-18 07:33:15 UTC (rev 291467)
@@ -397,6 +397,28 @@
     return m_registration->serviceWorkerPageIdentifier();
 }
 
+void SWServerWorker::decrementPushEventCounter()
+{
+    ASSERT(m_pushEventCounter);
+    --m_pushEventCounter;
+    terminateIfPossible();
+}
+
+void SWServerWorker::setAsInspected(bool isInspected)
+{
+    m_isInspected = isInspected;
+    terminateIfPossible();
+}
+
+void SWServerWorker::terminateIfPossible()
+{
+    if (m_pushEventCounter || m_isInspected || !m_server || m_server->hasClientsWithOrigin(origin()))
+        return;
+
+    terminate();
+    m_server->removeContextConnectionIfPossible(registrableDomain());
+}
+
 } // namespace WebCore
 
 #endif // ENABLE(SERVICE_WORKER)

Modified: trunk/Source/WebCore/workers/service/server/SWServerWorker.h (291466 => 291467)


--- trunk/Source/WebCore/workers/service/server/SWServerWorker.h	2022-03-18 04:23:25 UTC (rev 291466)
+++ trunk/Source/WebCore/workers/service/server/SWServerWorker.h	2022-03-18 07:33:15 UTC (rev 291467)
@@ -135,6 +135,12 @@
 
     WorkerThreadMode workerThreadMode() const;
 
+    void incrementPushEventCounter() { ++m_pushEventCounter; }
+    void decrementPushEventCounter();
+    void setAsInspected(bool);
+
+    bool shouldContinue() const { return !!m_pushEventCounter || m_isInspected; }
+
 private:
     SWServerWorker(SWServer&, SWServerRegistration&, const URL&, const ScriptBuffer&, const CertificateInfo&, const ContentSecurityPolicyResponseHeaders&, const CrossOriginEmbedderPolicy&, String&& referrerPolicy, WorkerType, ServiceWorkerIdentifier, HashMap<URL, ServiceWorkerContextData::ImportedScript>&&);
 
@@ -144,6 +150,7 @@
     void terminationCompleted();
     void terminationTimerFired();
     void callTerminationCallbacks();
+    void terminateIfPossible();
 
     WeakPtr<SWServer> m_server;
     ServiceWorkerRegistrationKey m_registrationKey;
@@ -166,6 +173,8 @@
     Vector<CompletionHandler<void()>> m_terminationCallbacks;
     Timer m_terminationTimer;
     LastNavigationWasAppInitiated m_lastNavigationWasAppInitiated;
+    int m_pushEventCounter { 0 };
+    bool m_isInspected { false };
 };
 
 } // namespace WebCore

Modified: trunk/Source/WebKit/ChangeLog (291466 => 291467)


--- trunk/Source/WebKit/ChangeLog	2022-03-18 04:23:25 UTC (rev 291466)
+++ trunk/Source/WebKit/ChangeLog	2022-03-18 07:33:15 UTC (rev 291467)
@@ -1,3 +1,15 @@
+2022-03-18  Youenn Fablet  <[email protected]>
+
+        Keep service workers alive when they are inspected even though they should be terminated
+        https://bugs.webkit.org/show_bug.cgi?id=237827
+        <rdar://88313935>
+
+        Reviewed by Alex Christensen.
+
+        * NetworkProcess/ServiceWorker/WebSWServerToContextConnection.messages.in:
+        * WebProcess/Storage/WebSWContextManagerConnection.cpp:
+        * WebProcess/Storage/WebSWContextManagerConnection.h:
+
 2022-03-17  Alex Christensen  <[email protected]>
 
         Call doDailyActivityInManager on main thread in adattributiond

Modified: trunk/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerToContextConnection.messages.in (291466 => 291467)


--- trunk/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerToContextConnection.messages.in	2022-03-18 04:23:25 UTC (rev 291466)
+++ trunk/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerToContextConnection.messages.in	2022-03-18 07:33:15 UTC (rev 291467)
@@ -38,6 +38,7 @@
     SetScriptResource(WebCore::ServiceWorkerIdentifier identifier, URL scriptURL, WebCore::ServiceWorkerContextData::ImportedScript script)
     PostMessageToServiceWorkerClient(WebCore::ScriptExecutionContextIdentifier destination, struct WebCore::MessageWithMessagePorts message, WebCore::ServiceWorkerIdentifier source, String sourceOrigin)
     DidFailHeartBeatCheck(WebCore::ServiceWorkerIdentifier identifier)
+    SetAsInspected(WebCore::ServiceWorkerIdentifier identifier, bool isInspected)
 }
 
 #endif // ENABLE(SERVICE_WORKER)

Modified: trunk/Source/WebKit/WebProcess/Storage/WebSWContextManagerConnection.cpp (291466 => 291467)


--- trunk/Source/WebKit/WebProcess/Storage/WebSWContextManagerConnection.cpp	2022-03-18 04:23:25 UTC (rev 291466)
+++ trunk/Source/WebKit/WebProcess/Storage/WebSWContextManagerConnection.cpp	2022-03-18 07:33:15 UTC (rev 291467)
@@ -372,6 +372,11 @@
     m_connectionToNetworkProcess->send(Messages::WebSWServerToContextConnection::DidFailHeartBeatCheck { serviceWorkerIdentifier }, 0);
 }
 
+void WebSWContextManagerConnection::setAsInspected(WebCore::ServiceWorkerIdentifier identifier, bool isInspected)
+{
+    m_connectionToNetworkProcess->send(Messages::WebSWServerToContextConnection::SetAsInspected { identifier, isInspected }, 0);
+}
+
 } // namespace WebCore
 
 #endif // ENABLE(SERVICE_WORKER)

Modified: trunk/Source/WebKit/WebProcess/Storage/WebSWContextManagerConnection.h (291466 => 291467)


--- trunk/Source/WebKit/WebProcess/Storage/WebSWContextManagerConnection.h	2022-03-18 04:23:25 UTC (rev 291466)
+++ trunk/Source/WebKit/WebProcess/Storage/WebSWContextManagerConnection.h	2022-03-18 07:33:15 UTC (rev 291467)
@@ -85,6 +85,7 @@
     void setScriptResource(WebCore::ServiceWorkerIdentifier, const URL&, const WebCore::ServiceWorkerContextData::ImportedScript&) final;
     bool isThrottleable() const final;
     void didFailHeartBeatCheck(WebCore::ServiceWorkerIdentifier) final;
+    void setAsInspected(WebCore::ServiceWorkerIdentifier, bool) final;
 
     // IPC messages.
     void serviceWorkerStarted(std::optional<WebCore::ServiceWorkerJobDataIdentifier>, WebCore::ServiceWorkerIdentifier, bool doesHandleFetch) final;

Modified: trunk/Tools/ChangeLog (291466 => 291467)


--- trunk/Tools/ChangeLog	2022-03-18 04:23:25 UTC (rev 291466)
+++ trunk/Tools/ChangeLog	2022-03-18 07:33:15 UTC (rev 291467)
@@ -1,3 +1,13 @@
+2022-03-18  Youenn Fablet  <[email protected]>
+
+        Keep service workers alive when they are inspected even though they should be terminated
+        https://bugs.webkit.org/show_bug.cgi?id=237827
+        <rdar://88313935>
+
+        Reviewed by Alex Christensen.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/PushAPI.mm:
+
 2022-03-17  Jonathan Bedard  <[email protected]>
 
         configure-xcode-for-embedded-development fails with Xcode 13.3

Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/PushAPI.mm (291466 => 291467)


--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/PushAPI.mm	2022-03-18 04:23:25 UTC (rev 291466)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/PushAPI.mm	2022-03-18 07:33:15 UTC (rev 291467)
@@ -367,3 +367,232 @@
 
     clearWebsiteDataStore([configuration websiteDataStore]);
 }
+
+#if WK_HAVE_C_SPI
+
+static const char* pushEventsAndInspectedServiceWorkerScriptBytes = R"SWRESOURCE(
+let port;
+self.addEventListener("message", (event) => {
+    port = event.data.port;
+    port.postMessage(self.internals ? "Ready" : "No internals");
+});
+self.addEventListener("push", (event) => {
+    self.registration.showNotification("notification");
+    if (event.data.text() === 'first') {
+        self.internals.setAsInspected(true);
+        self.previousMessageData = 'first';
+        return;
+    }
+    if (event.data.text() === 'second') {
+        self.internals.setAsInspected(false);
+        if (self.previousMessageData !== 'first')
+            event.waitUntil(Promise.reject('expected first'));
+        return;
+    }
+    if (event.data.text() === 'third') {
+        if (self.previousMessageData !== undefined)
+            event.waitUntil(Promise.reject('expected undefined'));
+        return;
+    }
+    self.previousMessageData = event.data.text();
+    event.waitUntil(Promise.reject('expected a known message'));
+});
+)SWRESOURCE";
+
+TEST(PushAPI, pushEventsAndInspectedServiceWorker)
+{
+    TestWebKitAPI::HTTPServer server({
+        { "/", { mainBytes } },
+        { "/sw.js", { {{ "Content-Type", "application/_javascript_" }}, pushEventsAndInspectedServiceWorkerScriptBytes } }
+    }, TestWebKitAPI::HTTPServer::Protocol::Http);
+
+    [WKWebsiteDataStore _allowWebsiteDataRecordsForAllOrigins];
+
+    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    clearWebsiteDataStore([configuration websiteDataStore]);
+
+    auto context = adoptWK(TestWebKitAPI::Util::createContextForInjectedBundleTest("InternalsInjectedBundleTest"));
+    [configuration setProcessPool:(WKProcessPool *)context.get()];
+
+    auto provider = TestWebKitAPI::TestNotificationProvider({ [[configuration processPool] _notificationManagerForTesting], WKNotificationManagerGetSharedServiceWorkerNotificationManager() });
+    provider.setPermission(server.origin(), true);
+
+    auto messageHandler = adoptNS([[PushAPIMessageHandlerWithExpectedMessage alloc] init]);
+    [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"sw"];
+
+    expectedMessage = "Ready";
+    auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
+    [webView loadRequest:server.request()];
+
+    TestWebKitAPI::Util::run(&done);
+
+    [webView _close];
+    webView = nullptr;
+
+    terminateNetworkProcessWhileRegistrationIsStored(configuration.get());
+
+    // Push event for service worker without any related page.
+    pushMessageProcessed = false;
+    pushMessageSuccessful = false;
+    NSString *message = @"first";
+    [[configuration websiteDataStore] _processPushMessage:messageDictionary([message dataUsingEncoding:NSUTF8StringEncoding], [server.request() URL]) completionHandler:^(bool result) {
+        pushMessageSuccessful = result;
+        pushMessageProcessed = true;
+    }];
+    TestWebKitAPI::Util::run(&pushMessageProcessed);
+    EXPECT_TRUE(pushMessageSuccessful);
+
+    pushMessageProcessed = false;
+    pushMessageSuccessful = false;
+    message = @"second";
+    [[configuration websiteDataStore] _processPushMessage:messageDictionary([message dataUsingEncoding:NSUTF8StringEncoding], [server.request() URL]) completionHandler:^(bool result) {
+        pushMessageSuccessful = result;
+        pushMessageProcessed = true;
+    }];
+    TestWebKitAPI::Util::run(&pushMessageProcessed);
+    EXPECT_TRUE(pushMessageSuccessful);
+
+    pushMessageProcessed = false;
+    pushMessageSuccessful = false;
+    message = @"third";
+    [[configuration websiteDataStore] _processPushMessage:messageDictionary([message dataUsingEncoding:NSUTF8StringEncoding], [server.request() URL]) completionHandler:^(bool result) {
+        pushMessageSuccessful = result;
+        pushMessageProcessed = true;
+    }];
+    TestWebKitAPI::Util::run(&pushMessageProcessed);
+    EXPECT_TRUE(pushMessageSuccessful);
+}
+
+static const char* inspectedServiceWorkerWithoutPageScriptBytes = R"SWRESOURCE(
+let port;
+self.addEventListener("message", (event) => {
+    port = event.data.port;
+    port.postMessage(self.internals ? "Ready" : "No internals");
+});
+self.addEventListener("push", async (event) => {
+    self.registration.showNotification("notification");
+    if (event.data.text() === 'firstmessage-inspected' || event.data.text() === 'firstmessage-not-inspected') {
+        if (event.data.text() === 'firstmessage-inspected')
+            self.internals.setAsInspected(true);
+        if (self.previousMessageData !== undefined)
+            event.waitUntil(Promise.reject('unexpected state with inspected message'));
+        self.previousMessageData = 'inspected';
+        // Wait for client to go away before resolving the event promise;
+        let resolve;
+        event.waitUntil(new Promise(r => resolve = r));
+        while (true) {
+            const clients = await self.clients.matchAll({ includeUncontrolled : true });
+            if (!clients.length)
+                resolve();
+        }
+        return;
+    }
+    if (event.data.text() === 'not inspected') {
+        setTimeout(() => self.internals.setAsInspected(false), 10);
+        if (self.previousMessageData !== 'inspected')
+            event.waitUntil(Promise.reject('unexpected state with not inspected message'));
+        self.previousMessageData = 'not inspected';
+        return;
+    }
+    if (event.data.text() === 'last') {
+        if (self.previousMessageData !== undefined)
+            event.waitUntil(Promise.reject('unexpected state with last message'));
+    }
+});
+)SWRESOURCE";
+
+static void testInspectedServiceWorkerWithoutPage(bool enableServiceWorkerInspection)
+{
+    TestWebKitAPI::HTTPServer server({
+        { "/", { mainBytes } },
+        { "/sw.js", { {{ "Content-Type", "application/_javascript_" }}, inspectedServiceWorkerWithoutPageScriptBytes } }
+    }, TestWebKitAPI::HTTPServer::Protocol::Http);
+
+    [WKWebsiteDataStore _allowWebsiteDataRecordsForAllOrigins];
+
+    auto dataStoreConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] init]);
+    [dataStoreConfiguration setServiceWorkerProcessTerminationDelayEnabled:NO];
+    auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:dataStoreConfiguration.get()]);
+
+    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    configuration.get().websiteDataStore = dataStore.get();
+    clearWebsiteDataStore([configuration websiteDataStore]);
+
+    auto context = adoptWK(TestWebKitAPI::Util::createContextForInjectedBundleTest("InternalsInjectedBundleTest"));
+    [configuration setProcessPool:(WKProcessPool *)context.get()];
+
+    auto provider = TestWebKitAPI::TestNotificationProvider({ [[configuration processPool] _notificationManagerForTesting], WKNotificationManagerGetSharedServiceWorkerNotificationManager() });
+    provider.setPermission(server.origin(), true);
+
+    auto messageHandler = adoptNS([[PushAPIMessageHandlerWithExpectedMessage alloc] init]);
+    [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"sw"];
+
+    expectedMessage = "Ready";
+    auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
+    [webView loadRequest:server.request()];
+
+    TestWebKitAPI::Util::run(&done);
+
+    // Push event for service worker without any related page.
+    pushMessageProcessed = false;
+    pushMessageSuccessful = false;
+    NSString *message = @"firstmessage-inspected";
+    if (!enableServiceWorkerInspection)
+        message = @"firstmessage-not-inspected";
+    [[configuration websiteDataStore] _processPushMessage:messageDictionary([message dataUsingEncoding:NSUTF8StringEncoding], [server.request() URL]) completionHandler:^(bool result) {
+        pushMessageSuccessful = result;
+        pushMessageProcessed = true;
+    }];
+
+    // We delay so that the push message will happen before the unregistration of the service worker client.
+    sleep(0.5);
+
+    // Closing the web view should not terminate the service worker as service worker is inspected.
+    [webView _close];
+    webView = nullptr;
+
+    TestWebKitAPI::Util::run(&pushMessageProcessed);
+    EXPECT_TRUE(pushMessageSuccessful);
+
+    // We delay so that the timer to terminate service worker kicks in, at most up to the max push message allowed time, aka 2 seconds.
+    sleep(3);
+
+    // Send message at which point the service worker will not be inspected anymore and will be closed.
+    pushMessageProcessed = false;
+    pushMessageSuccessful = false;
+    message = @"not inspected";
+    [[configuration websiteDataStore] _processPushMessage:messageDictionary([message dataUsingEncoding:NSUTF8StringEncoding], [server.request() URL]) completionHandler:^(bool result) {
+        pushMessageSuccessful = result;
+        pushMessageProcessed = true;
+    }];
+    TestWebKitAPI::Util::run(&pushMessageProcessed);
+    EXPECT_EQ(pushMessageSuccessful, enableServiceWorkerInspection);
+
+    // We delay so that the timer to terminate service worker kicks in, at most up to the max push message allowed time, aka 2 seconds.
+    sleep(3);
+
+    pushMessageProcessed = false;
+    pushMessageSuccessful = false;
+    message = @"last";
+    [[configuration websiteDataStore] _processPushMessage:messageDictionary([message dataUsingEncoding:NSUTF8StringEncoding], [server.request() URL]) completionHandler:^(bool result) {
+        pushMessageSuccessful = result;
+        pushMessageProcessed = true;
+    }];
+
+    TestWebKitAPI::Util::run(&pushMessageProcessed);
+    EXPECT_TRUE(pushMessageSuccessful);
+}
+
+TEST(PushAPI, inspectedServiceWorkerWithoutPage)
+{
+    bool enableServiceWorkerInspection = true;
+    testInspectedServiceWorkerWithoutPage(enableServiceWorkerInspection);
+}
+
+TEST(PushAPI, uninspectedServiceWorkerWithoutPage)
+{
+    bool enableServiceWorkerInspection = false;
+    testInspectedServiceWorkerWithoutPage(enableServiceWorkerInspection);
+}
+
+#endif // WK_HAVE_C_SPI
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to