Title: [213857] trunk/Source
Revision
213857
Author
[email protected]
Date
2017-03-13 11:09:30 -0700 (Mon, 13 Mar 2017)

Log Message

Allow termination of background WebProcesses that go over a given CPU usage threshold
https://bugs.webkit.org/show_bug.cgi?id=169456
<rdar://problem/30960968>

Reviewed by Andreas Kling.

Source/WebCore:

Add CPUMonitor utility class to monitor CPU usage and call a provided lambda
whenever the process exceeds the provided CPU limit over a given period of
time.

* WebCore.xcodeproj/project.pbxproj:
* platform/CPUMonitor.cpp: Added.
(WebCore::CPUMonitor::CPUMonitor):
(WebCore::CPUMonitor::setCPULimit):
(WebCore::CPUMonitor::timerFired):
* platform/CPUMonitor.h: Added.

Source/WebKit2:

Allow termination of background WebProcesses that go over a given CPU usage threshold.
This can be enabled client side via the WKPageConfigurationSetBackgroundCPULimit()
SPI. The client provides the actual CPU threshold it wants to use at page level.

If such limit is set, whenever a WebContent process has no visible pages, we start
monitoring its CPU usage over 15 minutes periods. At the end of each period, we
check if the process' average CPU usage over this period was greater than the
background CPU limit. If it greater, the WebContent process send an IPC message to
the UIProcess letting it know that it exceeded the CPU limit. The UI process will
then log a message and terminate the process unless it has any audio playing.

Once a WebProcess has been terminated, we do not let the client know until one of its
pages becomes visible again. When this happens, we call the processDidCrash
delegate and Safari will take care of reloading the tab and showing the crash
banner then. This is done because we do not want to reload content that is
using a lot of CPU while in the background.

* Shared/WebPageCreationParameters.cpp:
(WebKit::WebPageCreationParameters::encode):
(WebKit::WebPageCreationParameters::decode):
* Shared/WebPageCreationParameters.h:
Add backgroundCPULimit to WebPageCreationParameters.

* UIProcess/API/APIPageConfiguration.cpp:
(API::PageConfiguration::copy):
* UIProcess/API/APIPageConfiguration.h:
(API::PageConfiguration::backgroundCPULimit):
(API::PageConfiguration::setBackgroundCPULimit):
Add backgroundCPULimit to APIPageConfiguration.

* UIProcess/API/C/WKPageConfigurationRef.cpp:
(WKPageConfigurationSetBackgroundCPULimit):
* UIProcess/API/C/WKPageConfigurationRef.h:
Add SPI to set background CPU limit.

* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::reattachToWebProcess):
Reset m_wasTerminatedDueToResourceExhaustionWhileInBackground flag
as the process was restarted.

(WebKit::WebPageProxy::dispatchActivityStateChange):
When the visibility state changes for a page that was terminated
while in the background due to exceeded CPU limit, notify the
client that the process crashed via the processDidCrash delegate.
Safari will use this delegate to reload the tab and show the crash
banner.

(WebKit::WebPageProxy::terminateProcess):
Add parameter to terminateProcess() to provide the reason. If the
page was terminated due to reaching CPU limit, set a flag so we
can delay calling processDidCrash until the page becomes visible
again.

(WebKit::WebPageProxy::creationParameters):
Set backgroundCPULimit on the WebPageCreationParameters.

* UIProcess/WebPageProxy.h:

* UIProcess/WebProcessProxy.cpp:
(WebKit::pagesCopy):
Add utility function to copy the list of pages to a Vector.

(WebKit::WebProcessProxy::didExceedBackgroundCPULimit):
When we get an IPC message from a WebContent process to let us
know that the process exceeded the background CPU limit, we log
a message and we terminate it if it has no audio playing.

* UIProcess/WebProcessProxy.h:

* UIProcess/WebProcessProxy.messages.in:
Add DidExceedBackgroundCPULimit IPC message so the WebContent process
can let us know when it goes over the background CPU limit.

* WebProcess/WebPage/WebPage.cpp:
(WebKit::m_backgroundCPULimit):
(WebKit::WebPage::setActivityState):
Notify the WebProcess whenever the activity state of a WebPage changes.
The WebProcess currently uses this information to determine when the
visibility of a page changes. This is needed as we only want to monitor
CPU usage of *background* WebContent processes (processes that have no
visible WebPage).

* WebProcess/WebPage/WebPage.h:
(WebKit::WebPage::backgroundCPULimit):

* WebProcess/WebProcess.cpp:
(WebKit::WebProcess::createWebPage):
(WebKit::WebProcess::removeWebPage):
Call updateBackgroundCPULimit() whenever a WebPage is added or removed
since the limit is per page.

(WebKit::WebProcess::updateBackgroundCPULimit):
(WebKit::WebProcess::updateBackgroundCPUMonitorState):
No-ops on other platforms than Mac at the moment.

(WebKit::WebProcess::pageActivityStateDidChange):
Call updateBackgroundCPUMonitorState() whenever the visibility of the
WebPage changes as we only monitor the CPU usage of *background* WebContent
processes.

(WebKit::WebProcess::hasVisibleWebPage):
Add utility function to determine if there is any visible WebPage in this
WebContent process. If the function returns false, then we consider the
WebContent process to be a *background* WebContent process.

* WebProcess/WebProcess.h:

* WebProcess/cocoa/WebProcessCocoa.mm:
(WebKit::WebProcess::updateBackgroundCPULimit):
Compute the WebProcess' background CPU limit given the limit set for each
of its WebPages. We use the largest (i.e. most permissive) background CPU
limit among all the pages.

(WebKit::WebProcess::updateBackgroundCPUMonitorState):
Update the state of the background CPU monitor. This is called whenever
the background CPU limit of the process changes or whenever the visibility
of a WebPage changes.

Modified Paths

Added Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (213856 => 213857)


--- trunk/Source/WebCore/ChangeLog	2017-03-13 18:00:25 UTC (rev 213856)
+++ trunk/Source/WebCore/ChangeLog	2017-03-13 18:09:30 UTC (rev 213857)
@@ -1,3 +1,22 @@
+2017-03-13  Chris Dumez  <[email protected]>
+
+        Allow termination of background WebProcesses that go over a given CPU usage threshold
+        https://bugs.webkit.org/show_bug.cgi?id=169456
+        <rdar://problem/30960968>
+
+        Reviewed by Andreas Kling.
+
+        Add CPUMonitor utility class to monitor CPU usage and call a provided lambda
+        whenever the process exceeds the provided CPU limit over a given period of
+        time.
+
+        * WebCore.xcodeproj/project.pbxproj:
+        * platform/CPUMonitor.cpp: Added.
+        (WebCore::CPUMonitor::CPUMonitor):
+        (WebCore::CPUMonitor::setCPULimit):
+        (WebCore::CPUMonitor::timerFired):
+        * platform/CPUMonitor.h: Added.
+
 2017-03-13  Antoine Quint  <[email protected]>
 
         [Modern Media Controls] Volume icon doesn't turn to mute when the knob is set to 0

Modified: trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj (213856 => 213857)


--- trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2017-03-13 18:00:25 UTC (rev 213856)
+++ trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2017-03-13 18:09:30 UTC (rev 213857)
@@ -1912,6 +1912,8 @@
 		467302021C4EFE7800BCB357 /* IgnoreOpensDuringUnloadCountIncrementer.h in Headers */ = {isa = PBXBuildFile; fileRef = 467302011C4EFE6600BCB357 /* IgnoreOpensDuringUnloadCountIncrementer.h */; };
 		4689F1AF1267BAE100E8D380 /* FileMetadata.h in Headers */ = {isa = PBXBuildFile; fileRef = 4689F1AE1267BAE100E8D380 /* FileMetadata.h */; };
 		46B63F6C1C6E8D19002E914B /* JSEventTargetCustom.h in Headers */ = {isa = PBXBuildFile; fileRef = 46B63F6B1C6E8CDF002E914B /* JSEventTargetCustom.h */; settings = {ATTRIBUTES = (Private, ); }; };
+		46C696CB1E7205F700597937 /* CPUMonitor.h in Headers */ = {isa = PBXBuildFile; fileRef = 46C696C91E7205E400597937 /* CPUMonitor.h */; settings = {ATTRIBUTES = (Private, ); }; };
+		46C696CC1E7205FC00597937 /* CPUMonitor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 46C696CA1E7205E400597937 /* CPUMonitor.cpp */; };
 		46C83EFD1A9BBE2900A79A41 /* GeoNotifier.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 46C83EFB1A9BBE2900A79A41 /* GeoNotifier.cpp */; };
 		46C83EFE1A9BBE2900A79A41 /* GeoNotifier.h in Headers */ = {isa = PBXBuildFile; fileRef = 46C83EFC1A9BBE2900A79A41 /* GeoNotifier.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		46DBB6501AB8C96F00D9A813 /* PowerObserverMac.h in Headers */ = {isa = PBXBuildFile; fileRef = 46DBB64E1AB8C96F00D9A813 /* PowerObserverMac.h */; };
@@ -9481,6 +9483,8 @@
 		467302011C4EFE6600BCB357 /* IgnoreOpensDuringUnloadCountIncrementer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IgnoreOpensDuringUnloadCountIncrementer.h; sourceTree = "<group>"; };
 		4689F1AE1267BAE100E8D380 /* FileMetadata.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FileMetadata.h; sourceTree = "<group>"; };
 		46B63F6B1C6E8CDF002E914B /* JSEventTargetCustom.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSEventTargetCustom.h; sourceTree = "<group>"; };
+		46C696C91E7205E400597937 /* CPUMonitor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CPUMonitor.h; sourceTree = "<group>"; };
+		46C696CA1E7205E400597937 /* CPUMonitor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CPUMonitor.cpp; sourceTree = "<group>"; };
 		46C83EFB1A9BBE2900A79A41 /* GeoNotifier.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GeoNotifier.cpp; sourceTree = "<group>"; };
 		46C83EFC1A9BBE2900A79A41 /* GeoNotifier.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GeoNotifier.h; sourceTree = "<group>"; };
 		46DBB64E1AB8C96F00D9A813 /* PowerObserverMac.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PowerObserverMac.h; sourceTree = "<group>"; };
@@ -23637,6 +23641,8 @@
 				D8B6152E1032495100C8554A /* Cookie.h */,
 				339B5B62131DAA3200F48D02 /* CookiesStrategy.h */,
 				862F129D18C1572C005C54AF /* CountedUserActivity.h */,
+				46C696CA1E7205E400597937 /* CPUMonitor.cpp */,
+				46C696C91E7205E400597937 /* CPUMonitor.h */,
 				463763061E26FDBA008CD46D /* CPUTime.cpp */,
 				463763071E26FDBA008CD46D /* CPUTime.h */,
 				E11AF15011B9A1A300805103 /* Cursor.cpp */,
@@ -29023,6 +29029,7 @@
 				E139866415478474001E3F65 /* StyleResolver.h in Headers */,
 				E4BBED4D14FCDBA1003F0B98 /* StyleRule.h in Headers */,
 				E4946EAF156E64DD00D3297F /* StyleRuleImport.h in Headers */,
+				46C696CB1E7205F700597937 /* CPUMonitor.h in Headers */,
 				E461D65F1BB0C80D00CB5645 /* StyleScope.h in Headers */,
 				F47A5E3E195B8C8A00483100 /* StyleScrollSnapPoints.h in Headers */,
 				9D6380101AF173220031A15C /* StyleSelfAlignmentData.h in Headers */,
@@ -30262,6 +30269,7 @@
 				59B597731108656B007159E8 /* BridgeJSC.cpp in Sources */,
 				7A45032F18DB717200377B34 /* BufferedLineReader.cpp in Sources */,
 				F55B3DAF1251F12D003EF269 /* ButtonInputType.cpp in Sources */,
+				46C696CC1E7205FC00597937 /* CPUMonitor.cpp in Sources */,
 				1A569CF70D7E2B82007C3983 /* c_class.cpp in Sources */,
 				ECA680C91E67730B00731D20 /* StringUtilities.mm in Sources */,
 				1A569CF90D7E2B82007C3983 /* c_instance.cpp in Sources */,

Added: trunk/Source/WebCore/platform/CPUMonitor.cpp (0 => 213857)


--- trunk/Source/WebCore/platform/CPUMonitor.cpp	                        (rev 0)
+++ trunk/Source/WebCore/platform/CPUMonitor.cpp	2017-03-13 18:09:30 UTC (rev 213857)
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "CPUMonitor.h"
+
+namespace WebCore {
+
+CPUMonitor::CPUMonitor(Seconds checkInterval, ExceededCPULimitHandler&& exceededCPULimitHandler)
+    : m_checkInterval(checkInterval)
+    , m_exceededCPULimitHandler(WTFMove(exceededCPULimitHandler))
+    , m_timer(*this, &CPUMonitor::timerFired)
+{
+}
+
+void CPUMonitor::setCPULimit(std::optional<double> cpuLimit)
+{
+    if (m_cpuLimit == cpuLimit)
+        return;
+
+    m_cpuLimit = cpuLimit;
+    if (m_cpuLimit) {
+        if (!m_timer.isActive()) {
+            m_lastCPUTime = getCPUTime();
+            m_timer.startRepeating(m_checkInterval);
+        }
+    } else
+        m_timer.stop();
+}
+
+void CPUMonitor::timerFired()
+{
+    ASSERT(m_cpuLimit);
+
+    if (!m_lastCPUTime) {
+        m_lastCPUTime = getCPUTime();
+        return;
+    }
+
+    auto cpuTime = getCPUTime();
+    if (!cpuTime)
+        return;
+
+    auto cpuUsagePercent = cpuTime.value().percentageCPUUsageSince(m_lastCPUTime.value());
+    if (cpuUsagePercent > m_cpuLimit.value() * 100)
+        m_exceededCPULimitHandler();
+
+    m_lastCPUTime = cpuTime;
+}
+
+} // namespace WebCore

Added: trunk/Source/WebCore/platform/CPUMonitor.h (0 => 213857)


--- trunk/Source/WebCore/platform/CPUMonitor.h	                        (rev 0)
+++ trunk/Source/WebCore/platform/CPUMonitor.h	2017-03-13 18:09:30 UTC (rev 213857)
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "CPUTime.h"
+#include "Timer.h"
+
+#include <wtf/Function.h>
+
+namespace WebCore {
+
+class CPUMonitor {
+public:
+    using ExceededCPULimitHandler = WTF::Function<void()>;
+    WEBCORE_EXPORT CPUMonitor(Seconds checkInterval, ExceededCPULimitHandler&&);
+
+    WEBCORE_EXPORT void setCPULimit(std::optional<double>);
+
+private:
+    void timerFired();
+
+    Seconds m_checkInterval;
+    ExceededCPULimitHandler m_exceededCPULimitHandler;
+    Timer m_timer;
+    std::optional<double> m_cpuLimit;
+    std::optional<CPUTime> m_lastCPUTime;
+};
+
+} // namespace WebCore

Modified: trunk/Source/WebKit2/ChangeLog (213856 => 213857)


--- trunk/Source/WebKit2/ChangeLog	2017-03-13 18:00:25 UTC (rev 213856)
+++ trunk/Source/WebKit2/ChangeLog	2017-03-13 18:09:30 UTC (rev 213857)
@@ -1,3 +1,129 @@
+2017-03-13  Chris Dumez  <[email protected]>
+
+        Allow termination of background WebProcesses that go over a given CPU usage threshold
+        https://bugs.webkit.org/show_bug.cgi?id=169456
+        <rdar://problem/30960968>
+
+        Reviewed by Andreas Kling.
+
+        Allow termination of background WebProcesses that go over a given CPU usage threshold.
+        This can be enabled client side via the WKPageConfigurationSetBackgroundCPULimit()
+        SPI. The client provides the actual CPU threshold it wants to use at page level.
+
+        If such limit is set, whenever a WebContent process has no visible pages, we start
+        monitoring its CPU usage over 15 minutes periods. At the end of each period, we
+        check if the process' average CPU usage over this period was greater than the
+        background CPU limit. If it greater, the WebContent process send an IPC message to
+        the UIProcess letting it know that it exceeded the CPU limit. The UI process will
+        then log a message and terminate the process unless it has any audio playing.
+
+        Once a WebProcess has been terminated, we do not let the client know until one of its
+        pages becomes visible again. When this happens, we call the processDidCrash
+        delegate and Safari will take care of reloading the tab and showing the crash
+        banner then. This is done because we do not want to reload content that is
+        using a lot of CPU while in the background.
+
+        * Shared/WebPageCreationParameters.cpp:
+        (WebKit::WebPageCreationParameters::encode):
+        (WebKit::WebPageCreationParameters::decode):
+        * Shared/WebPageCreationParameters.h:
+        Add backgroundCPULimit to WebPageCreationParameters.
+
+        * UIProcess/API/APIPageConfiguration.cpp:
+        (API::PageConfiguration::copy):
+        * UIProcess/API/APIPageConfiguration.h:
+        (API::PageConfiguration::backgroundCPULimit):
+        (API::PageConfiguration::setBackgroundCPULimit):
+        Add backgroundCPULimit to APIPageConfiguration.
+
+        * UIProcess/API/C/WKPageConfigurationRef.cpp:
+        (WKPageConfigurationSetBackgroundCPULimit):
+        * UIProcess/API/C/WKPageConfigurationRef.h:
+        Add SPI to set background CPU limit.
+
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::WebPageProxy::reattachToWebProcess):
+        Reset m_wasTerminatedDueToResourceExhaustionWhileInBackground flag
+        as the process was restarted.
+
+        (WebKit::WebPageProxy::dispatchActivityStateChange):
+        When the visibility state changes for a page that was terminated
+        while in the background due to exceeded CPU limit, notify the
+        client that the process crashed via the processDidCrash delegate.
+        Safari will use this delegate to reload the tab and show the crash
+        banner.
+
+        (WebKit::WebPageProxy::terminateProcess):
+        Add parameter to terminateProcess() to provide the reason. If the
+        page was terminated due to reaching CPU limit, set a flag so we
+        can delay calling processDidCrash until the page becomes visible
+        again.
+
+        (WebKit::WebPageProxy::creationParameters):
+        Set backgroundCPULimit on the WebPageCreationParameters.
+
+        * UIProcess/WebPageProxy.h:
+
+        * UIProcess/WebProcessProxy.cpp:
+        (WebKit::pagesCopy):
+        Add utility function to copy the list of pages to a Vector.
+
+        (WebKit::WebProcessProxy::didExceedBackgroundCPULimit):
+        When we get an IPC message from a WebContent process to let us
+        know that the process exceeded the background CPU limit, we log
+        a message and we terminate it if it has no audio playing.
+
+        * UIProcess/WebProcessProxy.h:
+
+        * UIProcess/WebProcessProxy.messages.in:
+        Add DidExceedBackgroundCPULimit IPC message so the WebContent process
+        can let us know when it goes over the background CPU limit.
+
+        * WebProcess/WebPage/WebPage.cpp:
+        (WebKit::m_backgroundCPULimit):
+        (WebKit::WebPage::setActivityState):
+        Notify the WebProcess whenever the activity state of a WebPage changes.
+        The WebProcess currently uses this information to determine when the
+        visibility of a page changes. This is needed as we only want to monitor
+        CPU usage of *background* WebContent processes (processes that have no
+        visible WebPage).
+
+        * WebProcess/WebPage/WebPage.h:
+        (WebKit::WebPage::backgroundCPULimit):
+
+        * WebProcess/WebProcess.cpp:
+        (WebKit::WebProcess::createWebPage):
+        (WebKit::WebProcess::removeWebPage):
+        Call updateBackgroundCPULimit() whenever a WebPage is added or removed
+        since the limit is per page.
+
+        (WebKit::WebProcess::updateBackgroundCPULimit):
+        (WebKit::WebProcess::updateBackgroundCPUMonitorState):
+        No-ops on other platforms than Mac at the moment.
+
+        (WebKit::WebProcess::pageActivityStateDidChange):
+        Call updateBackgroundCPUMonitorState() whenever the visibility of the
+        WebPage changes as we only monitor the CPU usage of *background* WebContent
+        processes.
+
+        (WebKit::WebProcess::hasVisibleWebPage):
+        Add utility function to determine if there is any visible WebPage in this
+        WebContent process. If the function returns false, then we consider the
+        WebContent process to be a *background* WebContent process.
+
+        * WebProcess/WebProcess.h:
+
+        * WebProcess/cocoa/WebProcessCocoa.mm:
+        (WebKit::WebProcess::updateBackgroundCPULimit):
+        Compute the WebProcess' background CPU limit given the limit set for each
+        of its WebPages. We use the largest (i.e. most permissive) background CPU
+        limit among all the pages.
+
+        (WebKit::WebProcess::updateBackgroundCPUMonitorState):
+        Update the state of the background CPU monitor. This is called whenever
+        the background CPU limit of the process changes or whenever the visibility
+        of a WebPage changes.
+
 2017-03-13  Michael Saboff  <[email protected]>
 
         Add iOS plumbing to WebProcess to enable _javascript_Core configuration and logging

Modified: trunk/Source/WebKit2/Shared/WebPageCreationParameters.cpp (213856 => 213857)


--- trunk/Source/WebKit2/Shared/WebPageCreationParameters.cpp	2017-03-13 18:00:25 UTC (rev 213856)
+++ trunk/Source/WebKit2/Shared/WebPageCreationParameters.cpp	2017-03-13 18:09:30 UTC (rev 213857)
@@ -93,6 +93,7 @@
     encoder.encodeEnum(userInterfaceLayoutDirection);
     encoder.encodeEnum(observedLayoutMilestones);
     encoder << overrideContentSecurityPolicy;
+    encoder << backgroundCPULimit;
     encoder << urlSchemeHandlers;
 #if ENABLE(WEB_RTC)
     encoder << iceCandidateFilteringEnabled;
@@ -222,6 +223,9 @@
     if (!decoder.decode(parameters.overrideContentSecurityPolicy))
         return false;
 
+    if (!decoder.decode(parameters.backgroundCPULimit))
+        return false;
+
     if (!decoder.decode(parameters.urlSchemeHandlers))
         return false;
 

Modified: trunk/Source/WebKit2/Shared/WebPageCreationParameters.h (213856 => 213857)


--- trunk/Source/WebKit2/Shared/WebPageCreationParameters.h	2017-03-13 18:00:25 UTC (rev 213856)
+++ trunk/Source/WebKit2/Shared/WebPageCreationParameters.h	2017-03-13 18:09:30 UTC (rev 213857)
@@ -146,6 +146,7 @@
     WebCore::LayoutMilestones observedLayoutMilestones;
 
     String overrideContentSecurityPolicy;
+    std::optional<double> backgroundCPULimit;
 
     HashMap<String, uint64_t> urlSchemeHandlers;
 

Modified: trunk/Source/WebKit2/UIProcess/API/APIPageConfiguration.cpp (213856 => 213857)


--- trunk/Source/WebKit2/UIProcess/API/APIPageConfiguration.cpp	2017-03-13 18:00:25 UTC (rev 213856)
+++ trunk/Source/WebKit2/UIProcess/API/APIPageConfiguration.cpp	2017-03-13 18:09:30 UTC (rev 213857)
@@ -68,6 +68,7 @@
     copy->m_alwaysRunsAtForegroundPriority = this->m_alwaysRunsAtForegroundPriority;
 #endif
     copy->m_initialCapitalizationEnabled = this->m_initialCapitalizationEnabled;
+    copy->m_backgroundCPULimit = this->m_backgroundCPULimit;
     copy->m_controlledByAutomation = this->m_controlledByAutomation;
     copy->m_overrideContentSecurityPolicy = this->m_overrideContentSecurityPolicy;
 

Modified: trunk/Source/WebKit2/UIProcess/API/APIPageConfiguration.h (213856 => 213857)


--- trunk/Source/WebKit2/UIProcess/API/APIPageConfiguration.h	2017-03-13 18:00:25 UTC (rev 213856)
+++ trunk/Source/WebKit2/UIProcess/API/APIPageConfiguration.h	2017-03-13 18:09:30 UTC (rev 213857)
@@ -93,6 +93,9 @@
     bool initialCapitalizationEnabled() { return m_initialCapitalizationEnabled; }
     void setInitialCapitalizationEnabled(bool initialCapitalizationEnabled) { m_initialCapitalizationEnabled = initialCapitalizationEnabled; }
 
+    std::optional<double> backgroundCPULimit() const { return m_backgroundCPULimit; }
+    void setBackgroundCPULimit(double cpuLimit) { m_backgroundCPULimit = cpuLimit; }
+
     bool waitsForPaintAfterViewDidMoveToWindow() const { return m_waitsForPaintAfterViewDidMoveToWindow; }
     void setWaitsForPaintAfterViewDidMoveToWindow(bool shouldSynchronize) { m_waitsForPaintAfterViewDidMoveToWindow = shouldSynchronize; }
 
@@ -124,6 +127,7 @@
     bool m_initialCapitalizationEnabled = true;
     bool m_waitsForPaintAfterViewDidMoveToWindow = true;
     bool m_controlledByAutomation = false;
+    std::optional<double> m_backgroundCPULimit;
 
     WTF::String m_overrideContentSecurityPolicy;
 };

Modified: trunk/Source/WebKit2/UIProcess/API/C/WKPageConfigurationRef.cpp (213856 => 213857)


--- trunk/Source/WebKit2/UIProcess/API/C/WKPageConfigurationRef.cpp	2017-03-13 18:00:25 UTC (rev 213856)
+++ trunk/Source/WebKit2/UIProcess/API/C/WKPageConfigurationRef.cpp	2017-03-13 18:09:30 UTC (rev 213857)
@@ -108,3 +108,8 @@
 {
     toImpl(configuration)->setInitialCapitalizationEnabled(enabled);
 }
+
+void WKPageConfigurationSetBackgroundCPULimit(WKPageConfigurationRef configuration, double cpuLimit)
+{
+    toImpl(configuration)->setBackgroundCPULimit(cpuLimit);
+}

Modified: trunk/Source/WebKit2/UIProcess/API/C/WKPageConfigurationRef.h (213856 => 213857)


--- trunk/Source/WebKit2/UIProcess/API/C/WKPageConfigurationRef.h	2017-03-13 18:00:25 UTC (rev 213856)
+++ trunk/Source/WebKit2/UIProcess/API/C/WKPageConfigurationRef.h	2017-03-13 18:09:30 UTC (rev 213857)
@@ -55,6 +55,7 @@
 WK_EXPORT void WKPageConfigurationSetWebsiteDataStore(WKPageConfigurationRef configuration, WKWebsiteDataStoreRef websiteDataStore);
 
 WK_EXPORT void WKPageConfigurationSetInitialCapitalizationEnabled(WKPageConfigurationRef configuration, bool enabled);
+WK_EXPORT void WKPageConfigurationSetBackgroundCPULimit(WKPageConfigurationRef configuration, double cpuLimit); // Terminates process if it uses more than CPU limit over an extended period of time while in the background.
 
 #ifdef __cplusplus
 }

Modified: trunk/Source/WebKit2/UIProcess/WebPageProxy.cpp (213856 => 213857)


--- trunk/Source/WebKit2/UIProcess/WebPageProxy.cpp	2017-03-13 18:00:25 UTC (rev 213856)
+++ trunk/Source/WebKit2/UIProcess/WebPageProxy.cpp	2017-03-13 18:09:30 UTC (rev 213857)
@@ -356,6 +356,7 @@
     , m_alwaysRunsAtForegroundPriority(m_configuration->alwaysRunsAtForegroundPriority())
 #endif
     , m_initialCapitalizationEnabled(m_configuration->initialCapitalizationEnabled())
+    , m_backgroundCPULimit(m_configuration->backgroundCPULimit())
     , m_backForwardList(WebBackForwardList::create(*this))
     , m_maintainsInactiveSelection(false)
     , m_waitsForPaintAfterViewDidMoveToWindow(m_configuration->waitsForPaintAfterViewDidMoveToWindow())
@@ -716,6 +717,7 @@
     ASSERT(m_process->state() == WebProcessProxy::State::Terminated);
 
     m_isValid = true;
+    m_wasTerminatedDueToResourceExhaustionWhileInBackground = false;
     m_process->removeWebPage(m_pageID);
     m_process->removeMessageReceiver(Messages::WebPageProxy::messageReceiverName(), m_pageID);
 
@@ -1528,8 +1530,13 @@
     m_activityStateChangeDispatcher->invalidate();
 #endif
 
-    if (!isValid())
+    if (!isValid()) {
+        if (m_potentiallyChangedActivityStateFlags & ActivityState::IsVisible && m_wasTerminatedDueToResourceExhaustionWhileInBackground) {
+            m_wasTerminatedDueToResourceExhaustionWhileInBackground = false;
+            processDidCrash();
+        }
         return;
+    }
 
     // If the visibility state may have changed, then so may the visually idle & occluded agnostic state.
     if (m_potentiallyChangedActivityStateFlags & ActivityState::IsVisible)
@@ -2383,7 +2390,7 @@
     m_process->send(Messages::WebPage::SetCustomTextEncodingName(encodingName), m_pageID);
 }
 
-void WebPageProxy::terminateProcess()
+void WebPageProxy::terminateProcess(TerminationReason terminationReason)
 {
     // NOTE: This uses a check of m_isValid rather than calling isValid() since
     // we want this to run even for pages being closed or that already closed.
@@ -2392,6 +2399,7 @@
 
     m_process->requestTermination();
     resetStateAfterProcessExited();
+    m_wasTerminatedDueToResourceExhaustionWhileInBackground = terminationReason == TerminationReason::ResourceExhaustionWhileInBackground;
 }
 
 SessionState WebPageProxy::sessionState(const std::function<bool (WebBackForwardListItem&)>& filter) const
@@ -5589,6 +5597,7 @@
     parameters.userInterfaceLayoutDirection = m_pageClient.userInterfaceLayoutDirection();
     parameters.observedLayoutMilestones = m_observedLayoutMilestones;
     parameters.overrideContentSecurityPolicy = m_overrideContentSecurityPolicy;
+    parameters.backgroundCPULimit = m_backgroundCPULimit;
 
     for (auto& iterator : m_urlSchemeHandlersByScheme)
         parameters.urlSchemeHandlers.set(iterator.key, iterator.value->identifier());

Modified: trunk/Source/WebKit2/UIProcess/WebPageProxy.h (213856 => 213857)


--- trunk/Source/WebKit2/UIProcess/WebPageProxy.h	2017-03-13 18:00:25 UTC (rev 213856)
+++ trunk/Source/WebKit2/UIProcess/WebPageProxy.h	2017-03-13 18:09:30 UTC (rev 213857)
@@ -680,7 +680,8 @@
 
     double estimatedProgress() const;
 
-    void terminateProcess();
+    enum class TerminationReason { ResourceExhaustionWhileInBackground, Other };
+    void terminateProcess(TerminationReason = TerminationReason::Other);
 
     SessionState sessionState(const std::function<bool (WebBackForwardListItem&)>& = nullptr) const;
     RefPtr<API::Navigation> restoreFromSessionState(SessionState, bool navigate);
@@ -1721,6 +1722,7 @@
     ProcessThrottler::ForegroundActivityToken m_activityToken;
 #endif
     bool m_initialCapitalizationEnabled;
+    std::optional<double> m_backgroundCPULimit;
     Ref<WebBackForwardList> m_backForwardList;
         
     bool m_maintainsInactiveSelection;
@@ -1984,6 +1986,7 @@
 #endif
 
     bool m_isUsingHighPerformanceWebGL { false };
+    bool m_wasTerminatedDueToResourceExhaustionWhileInBackground { false };
         
     WeakPtrFactory<WebPageProxy> m_weakPtrFactory;
 

Modified: trunk/Source/WebKit2/UIProcess/WebProcessProxy.cpp (213856 => 213857)


--- trunk/Source/WebKit2/UIProcess/WebProcessProxy.cpp	2017-03-13 18:00:25 UTC (rev 213856)
+++ trunk/Source/WebKit2/UIProcess/WebProcessProxy.cpp	2017-03-13 18:09:30 UTC (rev 213857)
@@ -1113,6 +1113,33 @@
     m_backgroundResponsivenessTimer.processTerminated();
 }
 
+static Vector<RefPtr<WebPageProxy>> pagesCopy(WTF::IteratorRange<WebProcessProxy::WebPageProxyMap::const_iterator::Values> pages)
+{
+    Vector<RefPtr<WebPageProxy>> vector;
+    for (auto& page : pages)
+        vector.append(page);
+    return vector;
+}
+
+void WebProcessProxy::didExceedBackgroundCPULimit()
+{
+    for (auto& page : pages()) {
+        if (page->isViewVisible())
+            return;
+
+        if (page->isPlayingAudio()) {
+            RELEASE_LOG(PerformanceLogging, "%p - WebProcessProxy::didExceedBackgroundCPULimit() WebProcess has exceeded the background CPU limit but we are not terminating it because there is audio playing", this);
+            return;
+        }
+    }
+
+    RELEASE_LOG(PerformanceLogging, "%p - WebProcessProxy::didExceedBackgroundCPULimit() Terminating background WebProcess that has exceeded the background CPU limit", this);
+
+    // We only terminate the process here. We will call processDidCrash() once one of the pages becomes visible again (see WebPageProxy::dispatchActivityStateChange()).
+    for (auto& page : pagesCopy(pages()))
+        page->terminateProcess(WebPageProxy::TerminationReason::ResourceExhaustionWhileInBackground);
+}
+
 void WebProcessProxy::updateBackgroundResponsivenessTimer()
 {
     m_backgroundResponsivenessTimer.updateState();

Modified: trunk/Source/WebKit2/UIProcess/WebProcessProxy.h (213856 => 213857)


--- trunk/Source/WebKit2/UIProcess/WebProcessProxy.h	2017-03-13 18:00:25 UTC (rev 213856)
+++ trunk/Source/WebKit2/UIProcess/WebProcessProxy.h	2017-03-13 18:09:30 UTC (rev 213857)
@@ -164,6 +164,7 @@
     bool isUnderMemoryPressure() const { return m_isUnderMemoryPressure; }
 
     void processTerminated();
+    void didExceedBackgroundCPULimit();
 
 private:
     explicit WebProcessProxy(WebProcessPool&, WebsiteDataStore*);

Modified: trunk/Source/WebKit2/UIProcess/WebProcessProxy.messages.in (213856 => 213857)


--- trunk/Source/WebKit2/UIProcess/WebProcessProxy.messages.in	2017-03-13 18:00:25 UTC (rev 213856)
+++ trunk/Source/WebKit2/UIProcess/WebProcessProxy.messages.in	2017-03-13 18:09:30 UTC (rev 213857)
@@ -45,6 +45,8 @@
 
     SetIsHoldingLockedFiles(bool isHoldingLockedFiles)
 
+    DidExceedBackgroundCPULimit()
+
     RetainIconForPageURL(String pageURL)
     ReleaseIconForPageURL(String pageURL)
 

Modified: trunk/Source/WebKit2/WebProcess/WebPage/WebPage.cpp (213856 => 213857)


--- trunk/Source/WebKit2/WebProcess/WebPage/WebPage.cpp	2017-03-13 18:00:25 UTC (rev 213856)
+++ trunk/Source/WebKit2/WebProcess/WebPage/WebPage.cpp	2017-03-13 18:09:30 UTC (rev 213857)
@@ -361,6 +361,7 @@
     , m_userActivityHysteresis([this](HysteresisState) { updateUserActivity(); })
     , m_userInterfaceLayoutDirection(parameters.userInterfaceLayoutDirection)
     , m_overrideContentSecurityPolicy { parameters.overrideContentSecurityPolicy }
+    , m_backgroundCPULimit(parameters.backgroundCPULimit)
 {
     ASSERT(m_pageID);
 
@@ -2624,6 +2625,7 @@
         pluginView->activityStateDidChange(changed);
 
     m_drawingArea->activityStateDidChange(changed, wantsDidUpdateActivityState, callbackIDs);
+    WebProcess::singleton().pageActivityStateDidChange(m_pageID, changed);
 
     if (changed & ActivityState::IsInWindow)
         updateIsInWindow();

Modified: trunk/Source/WebKit2/WebProcess/WebPage/WebPage.h (213856 => 213857)


--- trunk/Source/WebKit2/WebProcess/WebPage/WebPage.h	2017-03-13 18:00:25 UTC (rev 213856)
+++ trunk/Source/WebKit2/WebProcess/WebPage/WebPage.h	2017-03-13 18:09:30 UTC (rev 213857)
@@ -972,6 +972,7 @@
 #endif
 
     WebURLSchemeHandlerProxy* urlSchemeHandlerForScheme(const String&);
+    std::optional<double> backgroundCPULimit() const { return m_backgroundCPULimit; }
 
 private:
     WebPage(uint64_t pageID, WebPageCreationParameters&&);
@@ -1550,6 +1551,7 @@
     WebCore::UserInterfaceLayoutDirection m_userInterfaceLayoutDirection { WebCore::UserInterfaceLayoutDirection::LTR };
 
     const String m_overrideContentSecurityPolicy;
+    const std::optional<double> m_backgroundCPULimit;
 
     HashMap<String, std::unique_ptr<WebURLSchemeHandlerProxy>> m_schemeToURLSchemeHandlerProxyMap;
     HashMap<uint64_t, WebURLSchemeHandlerProxy*> m_identifierToURLSchemeHandlerProxyMap;

Modified: trunk/Source/WebKit2/WebProcess/WebProcess.cpp (213856 => 213857)


--- trunk/Source/WebKit2/WebProcess/WebProcess.cpp	2017-03-13 18:00:25 UTC (rev 213856)
+++ trunk/Source/WebKit2/WebProcess/WebProcess.cpp	2017-03-13 18:09:30 UTC (rev 213857)
@@ -71,6 +71,7 @@
 #include <WebCore/AXObjectCache.h>
 #include <WebCore/ApplicationCacheStorage.h>
 #include <WebCore/AuthenticationChallenge.h>
+#include <WebCore/CPUMonitor.h>
 #include <WebCore/CommonVM.h>
 #include <WebCore/CrossOriginPreflightResultCache.h>
 #include <WebCore/DNS.h>
@@ -580,6 +581,7 @@
 
         // Balanced by an enableTermination in removeWebPage.
         disableTermination();
+        updateBackgroundCPULimit();
     } else
         result.iterator->value->reinitializeWebPage(WTFMove(parameters));
 
@@ -594,6 +596,7 @@
     m_pageMap.remove(pageID);
 
     enableTermination();
+    updateBackgroundCPULimit();
 }
 
 bool WebProcess::shouldTerminate()
@@ -1268,8 +1271,22 @@
 {
 }
 
+void WebProcess::updateBackgroundCPULimit()
+{
+}
+
+void WebProcess::updateBackgroundCPUMonitorState()
+{
+}
+
 #endif
 
+void WebProcess::pageActivityStateDidChange(uint64_t, WebCore::ActivityState::Flags changed)
+{
+    if (changed & WebCore::ActivityState::IsVisible)
+        updateBackgroundCPUMonitorState();
+}
+
 #if PLATFORM(IOS)
 void WebProcess::resetAllGeolocationPermissions()
 {
@@ -1561,6 +1578,15 @@
     m_dnsPrefetchHystereris.impulse();
 }
 
+bool WebProcess::hasVisibleWebPage() const
+{
+    for (auto& page : m_pageMap.values()) {
+        if (page->isVisible())
+            return true;
+    }
+    return false;
+}
+
 #if USE(LIBWEBRTC)
 LibWebRTCNetwork& WebProcess::libWebRTCNetwork()
 {

Modified: trunk/Source/WebKit2/WebProcess/WebProcess.h (213856 => 213857)


--- trunk/Source/WebKit2/WebProcess/WebProcess.h	2017-03-13 18:00:25 UTC (rev 213856)
+++ trunk/Source/WebKit2/WebProcess/WebProcess.h	2017-03-13 18:09:30 UTC (rev 213857)
@@ -62,6 +62,7 @@
 
 namespace WebCore {
 class ApplicationCacheStorage;
+class CPUMonitor;
 class CertificateInfo;
 class PageGroup;
 class ResourceRequest;
@@ -194,6 +195,7 @@
 #endif
 
     void updateActivePages();
+    void pageActivityStateDidChange(uint64_t pageID, WebCore::ActivityState::Flags changed);
 
     void setHiddenPageDOMTimerThrottlingIncreaseLimit(int milliseconds);
 
@@ -318,6 +320,9 @@
     void destroyAutomationSessionProxy();
 
     void logDiagnosticMessageForNetworkProcessCrash();
+    bool hasVisibleWebPage() const;
+    void updateBackgroundCPULimit();
+    void updateBackgroundCPUMonitorState();
 
     // ChildProcess
     void initializeProcess(const ChildProcessInitializationParameters&) override;
@@ -419,6 +424,10 @@
 
     unsigned m_pagesMarkingLayersAsVolatile { 0 };
     bool m_suppressMemoryPressureHandler { false };
+#if PLATFORM(MAC)
+    std::unique_ptr<WebCore::CPUMonitor> m_backgroundCPUMonitor;
+    std::optional<double> m_backgroundCPULimit;
+#endif
 
     HashMap<WebCore::UserGestureToken *, uint64_t> m_userGestureTokens;
 

Modified: trunk/Source/WebKit2/WebProcess/cocoa/WebProcessCocoa.mm (213856 => 213857)


--- trunk/Source/WebKit2/WebProcess/cocoa/WebProcessCocoa.mm	2017-03-13 18:00:25 UTC (rev 213856)
+++ trunk/Source/WebKit2/WebProcess/cocoa/WebProcessCocoa.mm	2017-03-13 18:09:30 UTC (rev 213857)
@@ -48,6 +48,7 @@
 #import <_javascript_Core/Options.h>
 #import <WebCore/AXObjectCache.h>
 #import <WebCore/CFNetworkSPI.h>
+#import <WebCore/CPUMonitor.h>
 #import <WebCore/FileSystem.h>
 #import <WebCore/FontCache.h>
 #import <WebCore/FontCascade.h>
@@ -77,6 +78,10 @@
 
 namespace WebKit {
 
+#if PLATFORM(MAC)
+static const Seconds backgroundCPUMonitoringInterval { 15_min };
+#endif
+
 void WebProcess::platformSetCacheModel(CacheModel)
 {
 }
@@ -369,6 +374,49 @@
 #endif
 }
 
+void WebProcess::updateBackgroundCPULimit()
+{
+#if PLATFORM(MAC)
+    std::optional<double> backgroundCPULimit;
+
+    // Use the largest limit among all pages in this process.
+    for (auto& page : m_pageMap.values()) {
+        auto pageCPULimit = page->backgroundCPULimit();
+        if (!pageCPULimit) {
+            backgroundCPULimit = std::nullopt;
+            break;
+        }
+        if (!backgroundCPULimit || pageCPULimit > backgroundCPULimit.value())
+            backgroundCPULimit = pageCPULimit;
+    }
+
+    if (m_backgroundCPULimit == backgroundCPULimit)
+        return;
+
+    m_backgroundCPULimit = backgroundCPULimit;
+    updateBackgroundCPUMonitorState();
+#endif
+}
+
+void WebProcess::updateBackgroundCPUMonitorState()
+{
+#if PLATFORM(MAC)
+    if (!m_backgroundCPULimit || hasVisibleWebPage()) {
+        if (m_backgroundCPUMonitor)
+            m_backgroundCPUMonitor->setCPULimit(std::nullopt);
+        return;
+    }
+
+    if (!m_backgroundCPUMonitor) {
+        m_backgroundCPUMonitor = std::make_unique<CPUMonitor>(backgroundCPUMonitoringInterval, [this] {
+            RELEASE_LOG(PerformanceLogging, "%p - WebProcess exceeded background CPU limit of %.1f%%", this, m_backgroundCPULimit.value() * 100);
+            parentProcessConnection()->send(Messages::WebProcessProxy::DidExceedBackgroundCPULimit(), 0);
+        });
+    }
+    m_backgroundCPUMonitor->setCPULimit(m_backgroundCPULimit.value());
+#endif
+}
+
 RefPtr<ObjCObjectGraph> WebProcess::transformHandlesToObjects(ObjCObjectGraph& objectGraph)
 {
     struct Transformer final : ObjCObjectGraph::Transformer {
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to