WebKit Bugzilla
Attachment 369872 Details for
Bug 185626
: A service worker process should app nap when all its clients app nap
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
Patch for landing
bug-185626-20190514111544.patch (text/plain), 41.21 KB, created by
youenn fablet
on 2019-05-14 11:15:45 PDT
(
hide
)
Description:
Patch for landing
Filename:
MIME Type:
Creator:
youenn fablet
Created:
2019-05-14 11:15:45 PDT
Size:
41.21 KB
patch
obsolete
>Subversion Revision: 245233 >diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog >index 85a49c3bc05afb9972a8f31e1a4b3e95af2568b2..b6b9ae46b1a0d228a697058e395053263cbb5153 100644 >--- a/Source/WebCore/ChangeLog >+++ b/Source/WebCore/ChangeLog >@@ -1,3 +1,34 @@ >+2019-05-14 Youenn Fablet <youenn@apple.com> >+ >+ A service worker process should app nap when all its clients app nap >+ https://bugs.webkit.org/show_bug.cgi?id=185626 >+ <rdar://problem/46785908> >+ >+ Reviewed by Alex Christensen. >+ >+ Update RegistrableDomain to work with SecurityOriginData. >+ Add internal API to enable accessing to service worker process throttle state. >+ >+ Test: http/wpt/service-workers/mac/processSuppression.https.html >+ >+ * platform/RegistrableDomain.h: >+ (WebCore::RegistrableDomain::RegistrableDomain): >+ (WebCore::RegistrableDomain::matches const): >+ (WebCore::RegistrableDomain::registrableDomainFromHost): >+ * testing/ServiceWorkerInternals.cpp: >+ (WebCore::ServiceWorkerInternals::isThrottleable const): >+ * testing/ServiceWorkerInternals.h: >+ * testing/ServiceWorkerInternals.idl: >+ * workers/service/SWClientConnection.h: >+ * workers/service/context/SWContextManager.cpp: >+ * workers/service/context/SWContextManager.h: >+ * workers/service/server/SWServer.cpp: >+ (WebCore::SWServer::serverToContextConnectionCreated): >+ * workers/service/server/SWServer.h: >+ (WebCore::SWServer::Connection::server const): >+ (WebCore::SWServer::connections const): >+ * workers/service/server/SWServerToContextConnection.h: >+ > 2019-05-13 Youenn Fablet <youenn@apple.com> > > getUserMedia capture changes on iOS after homing out >diff --git a/Source/WebKit/ChangeLog b/Source/WebKit/ChangeLog >index 244ae3d3f5f8d3b0641a8a3e0286a7752305a161..6ff2f0ee2776e35b45fb586e1c2719c2c31c674e 100644 >--- a/Source/WebKit/ChangeLog >+++ b/Source/WebKit/ChangeLog >@@ -1,3 +1,50 @@ >+2019-05-14 Youenn Fablet <youenn@apple.com> >+ >+ A service worker process should app nap when all its clients app nap >+ https://bugs.webkit.org/show_bug.cgi?id=185626 >+ <rdar://problem/46785908> >+ >+ Reviewed by Alex Christensen. >+ >+ Compute whether a given web process can be throttled on every page throttling change. >+ Send that information to network process which stores that information in WebSWServerConnection. >+ Every WebSWServerToContextConnection throttle state is then computed based on all WebSWServerConnection >+ that have a client that matches the registrable domain of the context connection. >+ >+ * NetworkProcess/ServiceWorker/WebSWServerConnection.cpp: >+ (WebKit::WebSWServerConnection::registerServiceWorkerClient): >+ (WebKit::WebSWServerConnection::unregisterServiceWorkerClient): >+ (WebKit::WebSWServerConnection::hasMatchingClient const): >+ (WebKit::WebSWServerConnection::computeThrottleState const): >+ (WebKit::WebSWServerConnection::setThrottleState): >+ (WebKit::WebSWServerConnection::updateThrottleState): >+ (WebKit::WebSWServerConnection::serverToContextConnectionCreated): >+ * NetworkProcess/ServiceWorker/WebSWServerConnection.h: >+ (WebKit::WebSWServerConnection::isThrottleable const): >+ * NetworkProcess/ServiceWorker/WebSWServerConnection.messages.in: >+ * NetworkProcess/ServiceWorker/WebSWServerToContextConnection.cpp: >+ (WebKit::WebSWServerToContextConnection::setThrottleState): >+ * NetworkProcess/ServiceWorker/WebSWServerToContextConnection.h: >+ (WebKit::WebSWServerToContextConnection::isThrottleable const): >+ * UIProcess/ServiceWorkerProcessProxy.cpp: >+ * UIProcess/ServiceWorkerProcessProxy.h: >+ * WebProcess/Storage/WebSWClientConnection.cpp: >+ (WebKit::WebSWClientConnection::WebSWClientConnection): >+ (WebKit::WebSWClientConnection::updateThrottleState): >+ * WebProcess/Storage/WebSWClientConnection.h: >+ * WebProcess/Storage/WebSWContextManagerConnection.cpp: >+ (WebKit::WebSWContextManagerConnection::setThrottleState): >+ (WebKit::WebSWContextManagerConnection::isThrottleable const): >+ * WebProcess/Storage/WebSWContextManagerConnection.h: >+ * WebProcess/Storage/WebSWContextManagerConnection.messages.in: >+ * WebProcess/WebPage/WebPage.cpp: >+ (WebKit::WebPage::updateUserActivity): >+ (WebKit::WebPage::isThrottleable const): >+ * WebProcess/WebPage/WebPage.h: >+ * WebProcess/WebProcess.cpp: >+ (WebKit::WebProcess::arePagesThrottleable const): >+ * WebProcess/WebProcess.h: >+ > 2019-05-12 Takashi Komori <Takashi.Komori@sony.com> > > [Curl] Suppress extra didReceiveAuthenticationChallenge call when accessing a server which checks basic auth. >diff --git a/Source/WebCore/platform/RegistrableDomain.h b/Source/WebCore/platform/RegistrableDomain.h >index 448af3f0bb4879a7ac15f9a1c0b731a5d651323d..90aec2536491028cb3bc16f41d860965a97c4c01 100644 >--- a/Source/WebCore/platform/RegistrableDomain.h >+++ b/Source/WebCore/platform/RegistrableDomain.h >@@ -26,6 +26,7 @@ > #pragma once > > #include "PublicSuffix.h" >+#include "SecurityOriginData.h" > #include <wtf/HashTraits.h> > #include <wtf/URL.h> > #include <wtf/text/WTFString.h> >@@ -36,18 +37,15 @@ class RegistrableDomain { > WTF_MAKE_FAST_ALLOCATED; > public: > RegistrableDomain() = default; >+ > explicit RegistrableDomain(const URL& url) >-#if ENABLE(PUBLIC_SUFFIX_LIST) >- : m_registrableDomain { topPrivatelyControlledDomain(url.host().toString()) } >-#else >- : m_registrableDomain { url.host().toString() } >-#endif >+ : RegistrableDomain(registrableDomainFromHost(url.host().toString())) >+ { >+ } >+ >+ explicit RegistrableDomain(const SecurityOriginData& origin) >+ : RegistrableDomain(registrableDomainFromHost(origin.host)) > { >- auto hostString = url.host().toString(); >- if (hostString.isEmpty()) >- m_registrableDomain = "nullOrigin"_s; >- else if (m_registrableDomain.isEmpty()) >- m_registrableDomain = hostString; > } > > bool isEmpty() const { return m_registrableDomain.isEmpty() || m_registrableDomain == "nullOrigin"_s; } >@@ -58,14 +56,12 @@ public: > > bool matches(const URL& url) const > { >- auto host = url.host(); >- if (host.isEmpty() && m_registrableDomain == "nullOrigin"_s) >- return true; >- if (!host.endsWith(m_registrableDomain)) >- return false; >- if (host.length() == m_registrableDomain.length()) >- return true; >- return host[host.length() - m_registrableDomain.length() - 1] == '.'; >+ return matches(url.host()); >+ } >+ >+ bool matches(const SecurityOriginData& origin) const >+ { >+ return matches(origin.host); > } > > RegistrableDomain isolatedCopy() const { return RegistrableDomain { m_registrableDomain.isolatedCopy() }; } >@@ -109,6 +105,31 @@ private: > { > } > >+ bool matches(StringView host) const >+ { >+ if (host.isEmpty() && m_registrableDomain == "nullOrigin"_s) >+ return true; >+ if (!host.endsWith(m_registrableDomain)) >+ return false; >+ if (host.length() == m_registrableDomain.length()) >+ return true; >+ return host[host.length() - m_registrableDomain.length() - 1] == '.'; >+ } >+ >+ static inline String registrableDomainFromHost(const String& host) >+ { >+#if ENABLE(PUBLIC_SUFFIX_LIST) >+ auto domain = topPrivatelyControlledDomain(host); >+#else >+ auto domain = host; >+#endif >+ if (host.isEmpty()) >+ domain = "nullOrigin"_s; >+ else if (domain.isEmpty()) >+ domain = host; >+ return domain; >+ } >+ > String m_registrableDomain; > }; > >diff --git a/Source/WebCore/testing/ServiceWorkerInternals.cpp b/Source/WebCore/testing/ServiceWorkerInternals.cpp >index 27417da0606667f85c8183ada8bf654cee06f289..55445615e0e1e81eca14b816c3e8e491b737b3f4 100644 >--- a/Source/WebCore/testing/ServiceWorkerInternals.cpp >+++ b/Source/WebCore/testing/ServiceWorkerInternals.cpp >@@ -96,6 +96,12 @@ String ServiceWorkerInternals::processName() const > } > #endif > >+bool ServiceWorkerInternals::isThrottleable() const >+{ >+ auto* connection = SWContextManager::singleton().connection(); >+ return connection ? connection->isThrottleable() : true; >+} >+ > } // namespace WebCore > > #endif >diff --git a/Source/WebCore/testing/ServiceWorkerInternals.h b/Source/WebCore/testing/ServiceWorkerInternals.h >index 8171c865c2be9eea4946e2d27eaf8380b62c2c0f..89395744b987d236434bf512cc71338727bbcf61 100644 >--- a/Source/WebCore/testing/ServiceWorkerInternals.h >+++ b/Source/WebCore/testing/ServiceWorkerInternals.h >@@ -51,6 +51,8 @@ public: > > String processName() const; > >+ bool isThrottleable() const; >+ > private: > explicit ServiceWorkerInternals(ServiceWorkerIdentifier); > >diff --git a/Source/WebCore/testing/ServiceWorkerInternals.idl b/Source/WebCore/testing/ServiceWorkerInternals.idl >index 1d131b2ac7024368e89b9c47ee83374f7c183182..535dd805f88c361f2b0f4d66ec0c603e896bf3cf 100644 >--- a/Source/WebCore/testing/ServiceWorkerInternals.idl >+++ b/Source/WebCore/testing/ServiceWorkerInternals.idl >@@ -37,4 +37,5 @@ > sequence<ByteString> fetchResponseHeaderList(FetchResponse response); > > readonly attribute DOMString processName; >+ readonly attribute boolean isThrottleable; > }; >diff --git a/Source/WebCore/workers/service/SWClientConnection.h b/Source/WebCore/workers/service/SWClientConnection.h >index 1fc7162399ed41a8c8d20843627db5e1ffcf2cf3..64f9e2362f4d82f2070bdacd669362f12d1287cf 100644 >--- a/Source/WebCore/workers/service/SWClientConnection.h >+++ b/Source/WebCore/workers/service/SWClientConnection.h >@@ -85,6 +85,9 @@ public: > > virtual void finishFetchingScriptInServer(const ServiceWorkerFetchResult&) = 0; > >+ virtual bool isThrottleable() const = 0; >+ virtual void updateThrottleState() = 0; >+ > protected: > WEBCORE_EXPORT SWClientConnection(); > >diff --git a/Source/WebCore/workers/service/context/SWContextManager.h b/Source/WebCore/workers/service/context/SWContextManager.h >index 7607a8639b4c79b38661daadfdbe523800c2d294..787ad94d825405629f5168f99a15a0cc35e4e0fa 100644 >--- a/Source/WebCore/workers/service/context/SWContextManager.h >+++ b/Source/WebCore/workers/service/context/SWContextManager.h >@@ -62,6 +62,8 @@ public: > virtual void findClientByIdentifier(ServiceWorkerIdentifier, ServiceWorkerClientIdentifier, FindClientByIdentifierCallback&&) = 0; > virtual void matchAll(ServiceWorkerIdentifier, const ServiceWorkerClientQueryOptions&, ServiceWorkerClientsMatchAllCallback&&) = 0; > virtual void claim(ServiceWorkerIdentifier, CompletionHandler<void()>&&) = 0; >+ >+ virtual bool isThrottleable() const = 0; > }; > > WEBCORE_EXPORT void setConnection(std::unique_ptr<Connection>&&); >diff --git a/Source/WebCore/workers/service/server/SWServer.cpp b/Source/WebCore/workers/service/server/SWServer.cpp >index 0200ec608a5c359fb0154108f7c2e0cb3a779213..97b30c15bc90b5fb8b40cdd1cf23b7a0dfe8510f 100644 >--- a/Source/WebCore/workers/service/server/SWServer.cpp >+++ b/Source/WebCore/workers/service/server/SWServer.cpp >@@ -544,6 +544,9 @@ void SWServer::tryInstallContextData(ServiceWorkerContextData&& data) > > void SWServer::serverToContextConnectionCreated(SWServerToContextConnection& contextConnection) > { >+ for (auto& connection : m_connections.values()) >+ connection->serverToContextConnectionCreated(contextConnection); >+ > auto pendingContextDatas = m_pendingContextDatas.take(contextConnection.registrableDomain()); > for (auto& data : pendingContextDatas) > installContextData(data); >diff --git a/Source/WebCore/workers/service/server/SWServer.h b/Source/WebCore/workers/service/server/SWServer.h >index 3ed3da912df4ffd1d9f6e65738cee741874f5617..67753881e986feed8d167e19b51959b6c8c9e895 100644 >--- a/Source/WebCore/workers/service/server/SWServer.h >+++ b/Source/WebCore/workers/service/server/SWServer.h >@@ -87,7 +87,10 @@ public: > virtual void notifyClientsOfControllerChange(const HashSet<DocumentIdentifier>& contextIdentifiers, const ServiceWorkerData& newController) = 0; > virtual void registrationReady(uint64_t registrationReadyRequestIdentifier, ServiceWorkerRegistrationData&&) = 0; > >+ virtual void serverToContextConnectionCreated(SWServerToContextConnection&) = 0; >+ > SWServer& server() { return m_server; } >+ const SWServer& server() const { return m_server; } > > protected: > WEBCORE_EXPORT explicit Connection(SWServer&); >@@ -153,6 +156,8 @@ public: > WEBCORE_EXPORT void removeConnection(SWServerConnectionIdentifier); > Connection* connection(SWServerConnectionIdentifier identifier) const { return m_connections.get(identifier); } > >+ const HashMap<SWServerConnectionIdentifier, std::unique_ptr<Connection>>& connections() const { return m_connections; } >+ > SWOriginStore& originStore() { return m_originStore; } > > void scriptContextFailedToStart(const Optional<ServiceWorkerJobDataIdentifier>&, SWServerWorker&, const String& message); >diff --git a/Source/WebCore/workers/service/server/SWServerToContextConnection.h b/Source/WebCore/workers/service/server/SWServerToContextConnection.h >index 2b60f1e718ba63a60fd96289e8331a5d658bf73a..b59c44d6611918469dc05e7eebf5ca0d14365d32 100644 >--- a/Source/WebCore/workers/service/server/SWServerToContextConnection.h >+++ b/Source/WebCore/workers/service/server/SWServerToContextConnection.h >@@ -76,7 +76,7 @@ public: > WEBCORE_EXPORT void claim(uint64_t requestIdentifier, ServiceWorkerIdentifier); > WEBCORE_EXPORT void setScriptResource(ServiceWorkerIdentifier, URL&& scriptURL, String&& script, URL&& responseURL, String&& mimeType); > >- static SWServerToContextConnection* connectionForRegistrableDomain(const RegistrableDomain&); >+ WEBCORE_EXPORT static SWServerToContextConnection* connectionForRegistrableDomain(const RegistrableDomain&); > > const RegistrableDomain& registrableDomain() const { return m_registrableDomain; } > >diff --git a/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerConnection.cpp b/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerConnection.cpp >index 373aaea9200ba3ba67ca4c31b69588ba069c7a8b..d32cd46389ec022bfc50145b737ef4caaec93231 100644 >--- a/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerConnection.cpp >+++ b/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerConnection.cpp >@@ -51,6 +51,7 @@ > #include <WebCore/ServiceWorkerContextData.h> > #include <WebCore/ServiceWorkerJobData.h> > #include <WebCore/ServiceWorkerUpdateViaCache.h> >+#include <wtf/Algorithms.h> > #include <wtf/MainThread.h> > > namespace WebKit { >@@ -278,6 +279,9 @@ void WebSWServerConnection::registerServiceWorkerClient(SecurityOriginData&& top > auto clientOrigin = ClientOrigin { WTFMove(topOrigin), SecurityOriginData::fromURL(data.url) }; > m_clientOrigins.add(data.identifier, clientOrigin); > server().registerServiceWorkerClient(WTFMove(clientOrigin), WTFMove(data), controllingServiceWorkerRegistrationIdentifier, WTFMove(userAgent)); >+ >+ if (!m_isThrottleable) >+ updateThrottleState(); > } > > void WebSWServerConnection::unregisterServiceWorkerClient(const ServiceWorkerClientIdentifier& clientIdentifier) >@@ -288,6 +292,56 @@ void WebSWServerConnection::unregisterServiceWorkerClient(const ServiceWorkerCli > > server().unregisterServiceWorkerClient(iterator->value, clientIdentifier); > m_clientOrigins.remove(iterator); >+ >+ if (!m_isThrottleable) >+ updateThrottleState(); >+} >+ >+bool WebSWServerConnection::hasMatchingClient(const RegistrableDomain& domain) const >+{ >+ return WTF::anyOf(m_clientOrigins.values(), [&domain](auto& origin) { >+ return domain.matches(origin.clientOrigin); >+ }); >+} >+ >+bool WebSWServerConnection::computeThrottleState(const RegistrableDomain& domain) const >+{ >+ return WTF::allOf(server().connections().values(), [&domain](auto& serverConnection) { >+ auto& connection = static_cast<WebSWServerConnection&>(*serverConnection); >+ return connection.isThrottleable() || !connection.hasMatchingClient(domain); >+ }); >+} >+ >+void WebSWServerConnection::setThrottleState(bool isThrottleable) >+{ >+ m_isThrottleable = isThrottleable; >+ updateThrottleState(); >+} >+ >+void WebSWServerConnection::updateThrottleState() >+{ >+ HashSet<SecurityOriginData> origins; >+ for (auto& origin : m_clientOrigins.values()) >+ origins.add(origin.clientOrigin); >+ >+ for (auto& origin : origins) { >+ if (auto* contextConnection = SWServerToContextConnection::connectionForRegistrableDomain(RegistrableDomain { origin })) { >+ auto& connection = static_cast<WebSWServerToContextConnection&>(*contextConnection); >+ >+ if (connection.isThrottleable() == m_isThrottleable) >+ continue; >+ bool newThrottleState = computeThrottleState(connection.registrableDomain()); >+ if (connection.isThrottleable() == newThrottleState) >+ continue; >+ connection.setThrottleState(newThrottleState); >+ } >+ } >+} >+ >+void WebSWServerConnection::serverToContextConnectionCreated(WebCore::SWServerToContextConnection& contextConnection) >+{ >+ auto& connection = static_cast<WebSWServerToContextConnection&>(contextConnection); >+ connection.setThrottleState(computeThrottleState(connection.registrableDomain())); > } > > void WebSWServerConnection::syncTerminateWorkerFromClient(WebCore::ServiceWorkerIdentifier&& identifier, CompletionHandler<void()>&& completionHandler) >diff --git a/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerConnection.h b/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerConnection.h >index 1f59d11bf8414987919b43fb8e232e396056ae01..90023d66184fd2d1871d4489617a0f9f672a028b 100644 >--- a/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerConnection.h >+++ b/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerConnection.h >@@ -94,6 +94,14 @@ private: > void unregisterServiceWorkerClient(const WebCore::ServiceWorkerClientIdentifier&); > void syncTerminateWorkerFromClient(WebCore::ServiceWorkerIdentifier&&, CompletionHandler<void()>&&); > >+ void serverToContextConnectionCreated(WebCore::SWServerToContextConnection&) final; >+ >+ bool isThrottleable() const { return m_isThrottleable; } >+ bool hasMatchingClient(const WebCore::RegistrableDomain&) const; >+ bool computeThrottleState(const WebCore::RegistrableDomain&) const; >+ void setThrottleState(bool isThrottleable); >+ void updateThrottleState(); >+ > IPC::Connection* messageSenderConnection() const final { return m_contentConnection.ptr(); } > uint64_t messageSenderDestinationID() const final { return identifier().toUInt64(); } > >@@ -103,6 +111,7 @@ private: > Ref<IPC::Connection> m_contentConnection; > Ref<NetworkProcess> m_networkProcess; > HashMap<WebCore::ServiceWorkerClientIdentifier, WebCore::ClientOrigin> m_clientOrigins; >+ bool m_isThrottleable { true }; > }; > > } // namespace WebKit >diff --git a/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerConnection.messages.in b/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerConnection.messages.in >index e3bd740bb509d6b47202f96c8e4d3411c71f3d14..7c50e624a3181499e3a53f8b73fb18eca76b929f 100644 >--- a/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerConnection.messages.in >+++ b/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerConnection.messages.in >@@ -44,6 +44,8 @@ messages -> WebSWServerConnection { > UnregisterServiceWorkerClient(struct WebCore::ServiceWorkerClientIdentifier identifier) > > SyncTerminateWorkerFromClient(WebCore::ServiceWorkerIdentifier workerIdentifier) -> () Synchronous >+ >+ SetThrottleState(bool isThrottleable) > } > > #endif // ENABLE(SERVICE_WORKER) >diff --git a/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerToContextConnection.cpp b/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerToContextConnection.cpp >index 3752e5697210f71c51c573ede985290724f7e037..e782821321fd0d6b8f1464cc620a40fa920b7ec4 100644 >--- a/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerToContextConnection.cpp >+++ b/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerToContextConnection.cpp >@@ -115,6 +115,12 @@ void WebSWServerToContextConnection::connectionMayNoLongerBeNeeded() > m_networkProcess->swContextConnectionMayNoLongerBeNeeded(*this); > } > >+void WebSWServerToContextConnection::setThrottleState(bool isThrottleable) >+{ >+ m_isThrottleable = isThrottleable; >+ send(Messages::WebSWContextManagerConnection::SetThrottleState { isThrottleable }); >+} >+ > void WebSWServerToContextConnection::terminate() > { > send(Messages::WebSWContextManagerConnection::TerminateProcess()); >diff --git a/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerToContextConnection.h b/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerToContextConnection.h >index 82905eac64ac3ebefc51473eb4de421b9bb82ae2..dc95efa8c4efe3c7fc55be91541a216803ef2dc8 100644 >--- a/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerToContextConnection.h >+++ b/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerToContextConnection.h >@@ -71,6 +71,9 @@ public: > > void didReceiveFetchTaskMessage(IPC::Connection&, IPC::Decoder&); > >+ void setThrottleState(bool isThrottleable); >+ bool isThrottleable() const { return m_isThrottleable; } >+ > private: > WebSWServerToContextConnection(NetworkProcess&, const WebCore::RegistrableDomain&, Ref<IPC::Connection>&&); > ~WebSWServerToContextConnection(); >@@ -97,6 +100,7 @@ private: > > HashMap<ServiceWorkerFetchTask::Identifier, WebCore::FetchIdentifier> m_ongoingFetchIdentifiers; > HashMap<WebCore::FetchIdentifier, Ref<ServiceWorkerFetchTask>> m_ongoingFetches; >+ bool m_isThrottleable { true }; > }; // class WebSWServerToContextConnection > > } // namespace WebKit >diff --git a/Source/WebKit/UIProcess/ServiceWorkerProcessProxy.cpp b/Source/WebKit/UIProcess/ServiceWorkerProcessProxy.cpp >index f1ff2f1f256f08c432760d95693a8b6a4ee871d4..5a14fbe4dcd4b38c7a0bfd5c2f70a06c5c575f86 100644 >--- a/Source/WebKit/UIProcess/ServiceWorkerProcessProxy.cpp >+++ b/Source/WebKit/UIProcess/ServiceWorkerProcessProxy.cpp >@@ -106,15 +106,6 @@ void ServiceWorkerProcessProxy::didReceiveAuthenticationChallenge(uint64_t pageI > challenge->listener().completeChallenge(AuthenticationChallengeDisposition::PerformDefaultHandling); > } > >-void ServiceWorkerProcessProxy::didFinishLaunching(ProcessLauncher* launcher, IPC::Connection::Identifier connectionIdentifier) >-{ >- WebProcessProxy::didFinishLaunching(launcher, connectionIdentifier); >- >- // Prevent App Nap for Service Worker processes. >- // FIXME: Ideally, the Service Worker process would app nap when all its clients app nap (http://webkit.org/b/185626). >- setProcessSuppressionEnabled(false); >-} >- > } // namespace WebKit > > #endif // ENABLE(SERVICE_WORKER) >diff --git a/Source/WebKit/UIProcess/ServiceWorkerProcessProxy.h b/Source/WebKit/UIProcess/ServiceWorkerProcessProxy.h >index 6f759e9ff45fd5afce273baeb3f05dbd6a09f798..93245f0dc76f265757ec2bfe7163e9edaae9c0f0 100644 >--- a/Source/WebKit/UIProcess/ServiceWorkerProcessProxy.h >+++ b/Source/WebKit/UIProcess/ServiceWorkerProcessProxy.h >@@ -54,9 +54,6 @@ private: > // AuxiliaryProcessProxy > void getLaunchOptions(ProcessLauncher::LaunchOptions&) final; > >- // ProcessLauncher::Client >- void didFinishLaunching(ProcessLauncher*, IPC::Connection::Identifier) final; >- > bool isServiceWorkerProcess() const final { return true; } > > ServiceWorkerProcessProxy(WebProcessPool&, const WebCore::RegistrableDomain&, WebsiteDataStore&); >diff --git a/Source/WebKit/WebProcess/Storage/WebSWClientConnection.cpp b/Source/WebKit/WebProcess/Storage/WebSWClientConnection.cpp >index 7905b63af1aaff4b1dae8bafeb9c7f03f5d3b7c3..bef0422f93331538a9a25cbf01bc83bb68d33203 100644 >--- a/Source/WebKit/WebProcess/Storage/WebSWClientConnection.cpp >+++ b/Source/WebKit/WebProcess/Storage/WebSWClientConnection.cpp >@@ -60,6 +60,7 @@ WebSWClientConnection::WebSWClientConnection(IPC::Connection& connection, Sessio > bool result = sendSync(Messages::NetworkConnectionToWebProcess::EstablishSWServerConnection(sessionID), Messages::NetworkConnectionToWebProcess::EstablishSWServerConnection::Reply(m_identifier)); > > ASSERT_UNUSED(result, result); >+ updateThrottleState(); > } > > WebSWClientConnection::~WebSWClientConnection() >@@ -231,6 +232,12 @@ void WebSWClientConnection::syncTerminateWorker(ServiceWorkerIdentifier identifi > sendSync(Messages::WebSWServerConnection::SyncTerminateWorkerFromClient(identifier), Messages::WebSWServerConnection::SyncTerminateWorkerFromClient::Reply()); > } > >+void WebSWClientConnection::updateThrottleState() >+{ >+ m_isThrottleable = WebProcess::singleton().areAllPagesThrottleable(); >+ send(Messages::WebSWServerConnection::SetThrottleState { m_isThrottleable }); >+} >+ > } // namespace WebKit > > #endif // ENABLE(SERVICE_WORKER) >diff --git a/Source/WebKit/WebProcess/Storage/WebSWClientConnection.h b/Source/WebKit/WebProcess/Storage/WebSWClientConnection.h >index 63e18d558fffbf534ea1bb9fce33113875ad197b..5177a34913b0d3a2f78afeb1be9910cea889e321 100644 >--- a/Source/WebKit/WebProcess/Storage/WebSWClientConnection.h >+++ b/Source/WebKit/WebProcess/Storage/WebSWClientConnection.h >@@ -87,6 +87,8 @@ private: > void getRegistrations(WebCore::SecurityOriginData&& topOrigin, const URL& clientURL, GetRegistrationsCallback&&) final; > > void didResolveRegistrationPromise(const WebCore::ServiceWorkerRegistrationKey&) final; >+ void updateThrottleState() final; >+ bool isThrottleable() const final { return m_isThrottleable; } > > void scheduleStorageJob(const WebCore::ServiceWorkerJobData&); > >@@ -109,7 +111,7 @@ private: > HashMap<uint64_t, GetRegistrationsCallback> m_ongoingGetRegistrationsTasks; > HashMap<uint64_t, WhenRegistrationReadyCallback> m_ongoingRegistrationReadyTasks; > Deque<WTF::Function<void()>> m_tasksPendingOriginImport; >- >+ bool m_isThrottleable { true }; > }; // class WebSWServerConnection > > } // namespace WebKit >diff --git a/Source/WebKit/WebProcess/Storage/WebSWContextManagerConnection.cpp b/Source/WebKit/WebProcess/Storage/WebSWContextManagerConnection.cpp >index 4d9a654e193ad65f607ccc9d8891a5fe2ad0ee34..34b5a643f03bda61fda4b33d887b5d1db89dc607 100644 >--- a/Source/WebKit/WebProcess/Storage/WebSWContextManagerConnection.cpp >+++ b/Source/WebKit/WebProcess/Storage/WebSWContextManagerConnection.cpp >@@ -364,6 +364,18 @@ void WebSWContextManagerConnection::terminateProcess() > _exit(EXIT_SUCCESS); > } > >+void WebSWContextManagerConnection::setThrottleState(bool isThrottleable) >+{ >+ RELEASE_LOG(ServiceWorker, "Service worker throttleable state is set to %d", isThrottleable); >+ m_isThrottleable = isThrottleable; >+ WebProcess::singleton().setProcessSuppressionEnabled(isThrottleable); >+} >+ >+bool WebSWContextManagerConnection::isThrottleable() const >+{ >+ return m_isThrottleable; >+} >+ > } // namespace WebCore > > #endif // ENABLE(SERVICE_WORKER) >diff --git a/Source/WebKit/WebProcess/Storage/WebSWContextManagerConnection.h b/Source/WebKit/WebProcess/Storage/WebSWContextManagerConnection.h >index f71af45e68226c7f4bd1248af67a25cb6a2da4af..6ce83d1d09eaedcae4fe873f18ec52b59e4fa172 100644 >--- a/Source/WebKit/WebProcess/Storage/WebSWContextManagerConnection.h >+++ b/Source/WebKit/WebProcess/Storage/WebSWContextManagerConnection.h >@@ -73,6 +73,7 @@ private: > void claim(WebCore::ServiceWorkerIdentifier, CompletionHandler<void()>&&) final; > void skipWaiting(WebCore::ServiceWorkerIdentifier, Function<void()>&&) final; > void setScriptResource(WebCore::ServiceWorkerIdentifier, const URL&, const WebCore::ServiceWorkerContextData::ImportedScript&) final; >+ bool isThrottleable() const final; > > // IPC messages. > void serviceWorkerStartedWithMessage(Optional<WebCore::ServiceWorkerJobDataIdentifier>, WebCore::ServiceWorkerIdentifier, const String& exceptionMessage) final; >@@ -91,6 +92,7 @@ private: > void didFinishSkipWaiting(uint64_t callbackID); > void setUserAgent(String&& userAgent); > NO_RETURN void terminateProcess(); >+ void setThrottleState(bool isThrottleable); > > Ref<IPC::Connection> m_connectionToNetworkProcess; > uint64_t m_pageGroupID; >@@ -106,6 +108,7 @@ private: > HashMap<uint64_t, WTF::Function<void()>> m_skipWaitingRequests; > uint64_t m_previousRequestIdentifier { 0 }; > String m_userAgent; >+ bool m_isThrottleable { true }; > }; > > } // namespace WebKit >diff --git a/Source/WebKit/WebProcess/Storage/WebSWContextManagerConnection.messages.in b/Source/WebKit/WebProcess/Storage/WebSWContextManagerConnection.messages.in >index 96350c057ac521a60dbbc2eabdfe6a422a156407..e15b02f61d2646fc1cb1ec70f906fa3c67cdd8d2 100644 >--- a/Source/WebKit/WebProcess/Storage/WebSWContextManagerConnection.messages.in >+++ b/Source/WebKit/WebProcess/Storage/WebSWContextManagerConnection.messages.in >@@ -39,6 +39,7 @@ messages -> WebSWContextManagerConnection { > SetUserAgent(String userAgent) > UpdatePreferencesStore(struct WebKit::WebPreferencesStore store) > TerminateProcess() >+ SetThrottleState(bool isThrottleable) > } > > #endif >diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.cpp b/Source/WebKit/WebProcess/WebPage/WebPage.cpp >index 0104e0063f46cadc91071caffa38ee4e3fe35d6f..0ed89d0f305fb4fdf3a5dce8823f2547126e7004 100644 >--- a/Source/WebKit/WebProcess/WebPage/WebPage.cpp >+++ b/Source/WebKit/WebProcess/WebPage/WebPage.cpp >@@ -213,6 +213,7 @@ > #include <WebCore/ResourceRequest.h> > #include <WebCore/ResourceResponse.h> > #include <WebCore/RuntimeEnabledFeatures.h> >+#include <WebCore/SWClientConnection.h> > #include <WebCore/SchemeRegistry.h> > #include <WebCore/ScriptController.h> > #include <WebCore/SerializedScriptValue.h> >@@ -765,17 +766,29 @@ void WebPage::reinitializeWebPage(WebPageCreationParameters&& parameters) > > void WebPage::updateThrottleState() > { >- bool isActive = m_activityState.containsAny({ ActivityState::IsLoading, ActivityState::IsAudible, ActivityState::IsCapturingMedia, ActivityState::WindowIsActive }); >- bool isVisuallyIdle = m_activityState.contains(ActivityState::IsVisuallyIdle); >- >- bool shouldAllowAppNap = m_isAppNapEnabled && !isActive && isVisuallyIdle; >+ bool isThrottleable = this->isThrottleable(); > > // The UserActivity prevents App Nap. So if we want to allow App Nap of the page, stop the activity. > // If the page should not be app nap'd, start it. >- if (shouldAllowAppNap) >+ if (isThrottleable) > m_userActivity.stop(); > else > m_userActivity.start(); >+ >+#if ENABLE(SERVICE_WORKER) >+ if (auto* connection = ServiceWorkerProvider::singleton().existingServiceWorkerConnectionForSession(sessionID())) { >+ if (isThrottleable != connection->isThrottleable()) >+ connection->updateThrottleState(); >+ } >+#endif >+} >+ >+bool WebPage::isThrottleable() const >+{ >+ bool isActive = m_activityState.containsAny({ ActivityState::IsLoading, ActivityState::IsAudible, ActivityState::IsCapturingMedia, ActivityState::WindowIsActive }); >+ bool isVisuallyIdle = m_activityState.contains(ActivityState::IsVisuallyIdle); >+ >+ return m_isAppNapEnabled && !isActive && isVisuallyIdle; > } > > WebPage::~WebPage() >diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.h b/Source/WebKit/WebProcess/WebPage/WebPage.h >index a38eb8ba1de101f8fbf32ebb24e36bd72edb3c9d..ec15c439b91e32040f0ed82ae3eb63ee32e0fe73 100644 >--- a/Source/WebKit/WebProcess/WebPage/WebPage.h >+++ b/Source/WebKit/WebProcess/WebPage/WebPage.h >@@ -1191,6 +1191,7 @@ public: > > void updateIntrinsicContentSizeIfNeeded(const WebCore::IntSize&); > void scheduleFullEditorStateUpdate(); >+ bool isThrottleable() const; > > private: > WebPage(uint64_t pageID, WebPageCreationParameters&&); >diff --git a/Source/WebKit/WebProcess/WebProcess.cpp b/Source/WebKit/WebProcess/WebProcess.cpp >index 2528387ab3b7a226828030be75252d41ca838a0a..97b8305880eebf442ead494cb5c3e5017a2f0e04 100644 >--- a/Source/WebKit/WebProcess/WebProcess.cpp >+++ b/Source/WebKit/WebProcess/WebProcess.cpp >@@ -117,6 +117,7 @@ > #include <WebCore/ServiceWorkerContextData.h> > #include <WebCore/Settings.h> > #include <WebCore/UserGestureIndicator.h> >+#include <wtf/Algorithms.h> > #include <wtf/Language.h> > #include <wtf/ProcessPrivilege.h> > #include <wtf/RunLoop.h> >@@ -1907,4 +1908,11 @@ void WebProcess::unblockAccessibilityServer(const SandboxExtension::Handle& hand > } > #endif > >+bool WebProcess::areAllPagesThrottleable() const >+{ >+ return WTF::allOf(m_pageMap.values(), [](auto& page) { >+ return page->isThrottleable(); >+ }); >+} >+ > } // namespace WebKit >diff --git a/Source/WebKit/WebProcess/WebProcess.h b/Source/WebKit/WebProcess/WebProcess.h >index 30bd59a715297470d3e2932959957e6d4e2a60d4..4b6ddc693476833dfb3da1025b25afb0911d341f 100644 >--- a/Source/WebKit/WebProcess/WebProcess.h >+++ b/Source/WebKit/WebProcess/WebProcess.h >@@ -269,6 +269,8 @@ public: > void setMediaMIMETypes(const Vector<String>); > #endif > >+ bool areAllPagesThrottleable() const; >+ > private: > WebProcess(); > ~WebProcess(); >diff --git a/Tools/ChangeLog b/Tools/ChangeLog >index 17d6ca023e23ab60f85b6d0b54b10b8c21716311..410fb6518e81635d06fc9196e2d6fd9b9d740bd1 100644 >--- a/Tools/ChangeLog >+++ b/Tools/ChangeLog >@@ -1,3 +1,19 @@ >+2019-05-14 Youenn Fablet <youenn@apple.com> >+ >+ A service worker process should app nap when all its clients app nap >+ https://bugs.webkit.org/show_bug.cgi?id=185626 >+ <rdar://problem/46785908> >+ >+ Reviewed by Alex Christensen. >+ >+ Allow to enable app nap through test header. >+ >+ * WebKitTestRunner/TestController.cpp: >+ (WTR::TestController::resetPreferencesToConsistentValues): >+ (WTR::updateTestOptionsFromTestHeader): >+ * WebKitTestRunner/TestOptions.h: >+ (WTR::TestOptions::hasSameInitializationOptions const): >+ > 2019-05-12 Yusuke Suzuki <ysuzuki@apple.com> > > [JSC] Compress Watchpoint size by using enum type and Packed<> data structure >diff --git a/Tools/WebKitTestRunner/TestController.cpp b/Tools/WebKitTestRunner/TestController.cpp >index b2632c4e43b6812847f5e1268db92353daf77d7e..14d1b2b44e020599a789096df71b18ab059c0417 100644 >--- a/Tools/WebKitTestRunner/TestController.cpp >+++ b/Tools/WebKitTestRunner/TestController.cpp >@@ -784,7 +784,7 @@ void TestController::resetPreferencesToConsistentValues(const TestOptions& optio > WKPreferencesSetInternalDebugFeatureForKey(preferences, internalDebugFeature.value, toWK(internalDebugFeature.key).get()); > > WKPreferencesSetProcessSwapOnNavigationEnabled(preferences, options.contextOptions.shouldEnableProcessSwapOnNavigation()); >- WKPreferencesSetPageVisibilityBasedProcessSuppressionEnabled(preferences, false); >+ WKPreferencesSetPageVisibilityBasedProcessSuppressionEnabled(preferences, options.enableAppNap); > WKPreferencesSetOfflineWebApplicationCacheEnabled(preferences, true); > WKPreferencesSetSubpixelAntialiasedLayerTextEnabled(preferences, false); > WKPreferencesSetXSSAuditorEnabled(preferences, false); >@@ -1391,6 +1391,8 @@ static void updateTestOptionsFromTestHeader(TestOptions& testOptions, const std: > testOptions.contextOptions.ignoreSynchronousMessagingTimeouts = parseBooleanTestHeaderValue(value); > else if (key == "shouldUseModernCompatibilityMode") > testOptions.shouldUseModernCompatibilityMode = parseBooleanTestHeaderValue(value); >+ else if (key == "enableAppNap") >+ testOptions.enableAppNap = parseBooleanTestHeaderValue(value); > pairStart = pairEnd + 1; > } > } >diff --git a/Tools/WebKitTestRunner/TestOptions.h b/Tools/WebKitTestRunner/TestOptions.h >index 2b1dbf9ce5049ec1c9e4166bf257e1b78a4346dd..f65368833dfccddbf4234b87d223f2a118843743 100644 >--- a/Tools/WebKitTestRunner/TestOptions.h >+++ b/Tools/WebKitTestRunner/TestOptions.h >@@ -92,6 +92,7 @@ struct TestOptions { > bool shouldHandleRunOpenPanel { true }; > bool shouldPresentPopovers { true }; > bool shouldUseModernCompatibilityMode { false }; >+ bool enableAppNap { false }; > > double contentInsetTop { 0 }; > >@@ -142,7 +143,8 @@ struct TestOptions { > || shouldHandleRunOpenPanel != options.shouldHandleRunOpenPanel > || shouldPresentPopovers != options.shouldPresentPopovers > || contentInsetTop != options.contentInsetTop >- || shouldUseModernCompatibilityMode != options.shouldUseModernCompatibilityMode) >+ || shouldUseModernCompatibilityMode != options.shouldUseModernCompatibilityMode >+ || enableAppNap != options.enableAppNap) > return false; > > if (!contextOptions.hasSameInitializationOptions(options.contextOptions)) >diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog >index ea00b880adeed0b9faa473d5a57554f04d2790ce..a4111c07650b5e202b9af0c42620be1e39876678 100644 >--- a/LayoutTests/ChangeLog >+++ b/LayoutTests/ChangeLog >@@ -1,3 +1,16 @@ >+2019-05-14 Youenn Fablet <youenn@apple.com> >+ >+ A service worker process should app nap when all its clients app nap >+ https://bugs.webkit.org/show_bug.cgi?id=185626 >+ <rdar://problem/46785908> >+ >+ Reviewed by Alex Christensen. >+ >+ * http/wpt/service-workers/mac/throttleable-worker.js: Added. >+ * http/wpt/service-workers/mac/throttleable.https-expected.txt: Added. >+ * http/wpt/service-workers/mac/throttleable.https.html: Added. >+ * platform/ios-wk2/TestExpectations: >+ > 2019-05-12 Simon Fraser <simon.fraser@apple.com> > > REGRESSION (r245208): compositing/shared-backing/sharing-bounds-non-clipping-shared-layer.html asserts >diff --git a/LayoutTests/http/wpt/service-workers/mac/throttleable-worker.js b/LayoutTests/http/wpt/service-workers/mac/throttleable-worker.js >new file mode 100644 >index 0000000000000000000000000000000000000000..ce7855f7120ab5e11d2d8274663252eb54c656af >--- /dev/null >+++ b/LayoutTests/http/wpt/service-workers/mac/throttleable-worker.js >@@ -0,0 +1,8 @@ >+onactivate = (e) => e.waitUntil(clients.claim()); >+ >+function checkThrottleable(event) >+{ >+ event.source.postMessage(self.internals ? self.internals.isThrottleable : "needs internals"); >+} >+ >+self.addEventListener("message", checkThrottleable); >diff --git a/LayoutTests/http/wpt/service-workers/mac/throttleable.https-expected.txt b/LayoutTests/http/wpt/service-workers/mac/throttleable.https-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..154bfe1a0bf53f6bb4d1cccc5babcd9186fceea2 >--- /dev/null >+++ b/LayoutTests/http/wpt/service-workers/mac/throttleable.https-expected.txt >@@ -0,0 +1,5 @@ >+ >+PASS Setup worker >+PASS Service worker should not be throttleable >+PASS Service worker should be throttleable >+ >diff --git a/LayoutTests/http/wpt/service-workers/mac/throttleable.https.html b/LayoutTests/http/wpt/service-workers/mac/throttleable.https.html >new file mode 100644 >index 0000000000000000000000000000000000000000..c26838c8dc3e994be05d39a163357a947c08634e >--- /dev/null >+++ b/LayoutTests/http/wpt/service-workers/mac/throttleable.https.html >@@ -0,0 +1,63 @@ >+<!doctype html><!-- webkit-test-runner [ enableAppNap=true ] --> >+<html> >+<head> >+<script src="/resources/testharness.js"></script> >+<script src="/resources/testharnessreport.js"></script> >+</head> >+<body> >+<script> >+var scope = "/WebKit/service-workers/mac/throttleable.https.html"; >+var activeWorker; >+ >+promise_test(async (test) => { >+ var registration = await navigator.serviceWorker.getRegistration(scope); >+ if (registration && registration.scope === scope) >+ await registration.unregister(); >+ >+ var registration = await navigator.serviceWorker.register("throttleable-worker.js", { scope : scope }); >+ activeWorker = registration.active; >+ if (activeWorker) >+ return; >+ activeWorker = registration.installing; >+ return new Promise(resolve => { >+ activeWorker.addEventListener('statechange', () => { >+ if (activeWorker.state === "activated") >+ resolve(); >+ }); >+ }); >+}, "Setup worker"); >+ >+promise_test(async (test) => { >+ var promise = new Promise((resolve, reject) => { >+ navigator.serviceWorker.addEventListener("message", (event) => { >+ resolve(event.data); >+ }); >+ }); >+ >+ activeWorker.postMessage("checkThrottleable"); >+ >+ assert_false(await promise); >+}, "Service worker should not be throttleable"); >+ >+promise_test(async (test) => { >+ if (window.testRunner) { >+ testRunner.setWindowIsKey(false); >+ testRunner.setPageVisibility("hidden"); >+ } >+ >+ let throttleable = false; >+ for (let cptr = 0; cptr < 1000 && !throttleable; ++cptr) { >+ throttleable = await new Promise((resolve, reject) => { >+ navigator.serviceWorker.addEventListener("message", (event) => { >+ resolve(event.data); >+ }); >+ activeWorker.postMessage("checkThrottleable"); >+ }); >+ await new Promise(resolve => setTimeout(resolve, 50)); >+ } >+ assert_true(throttleable); >+}, "Service worker should be throttleable"); >+ >+</script> >+</body> >+</html> >diff --git a/LayoutTests/platform/ios-wk2/TestExpectations b/LayoutTests/platform/ios-wk2/TestExpectations >index a634ceb4ae03b70d2c18a042f7288d3599474579..0a0b9192651d11ee3042c2c329b6cd618a45f4fc 100644 >--- a/LayoutTests/platform/ios-wk2/TestExpectations >+++ b/LayoutTests/platform/ios-wk2/TestExpectations >@@ -367,6 +367,8 @@ http/tests/security/video-cross-origin-readback.html > http/tests/websocket/tests/hybi/invalid-subprotocol-characters.html > http/tests/xmlhttprequest/cross-origin-authorization-with-embedder.html > >+http/wpt/service-workers/mac/ [ Skip ] >+ > # HTTP tests that are flaky: > http/tests/navigation/forward-and-cancel.html [ Failure Pass ] > http/tests/security/xss-DENIED-xsl-external-entity-redirect.xml [ Failure Pass ]
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Formatted Diff
|
Diff
Attachments on
bug 185626
:
369227
|
369298
|
369306
|
369330
|
369578
|
369599
|
369613
| 369872