WebProcessProxy.cpp [plain text]
#include "config.h"
#include "WebProcessProxy.h"
#include "APIFrameHandle.h"
#include "APIPageGroupHandle.h"
#include "APIPageHandle.h"
#include "DataReference.h"
#include "DownloadProxyMap.h"
#include "Logging.h"
#include "PluginInfoStore.h"
#include "PluginProcessManager.h"
#include "TextChecker.h"
#include "TextCheckerState.h"
#include "UserData.h"
#include "WebBackForwardListItem.h"
#include "WebIconDatabase.h"
#include "WebInspectorUtilities.h"
#include "WebNavigationDataStore.h"
#include "WebNotificationManagerProxy.h"
#include "WebPageGroup.h"
#include "WebPageProxy.h"
#include "WebPasteboardProxy.h"
#include "WebProcessMessages.h"
#include "WebProcessPool.h"
#include "WebProcessProxyMessages.h"
#include "WebUserContentControllerProxy.h"
#include "WebsiteData.h"
#include <WebCore/DiagnosticLoggingKeys.h>
#include <WebCore/PublicSuffix.h>
#include <WebCore/SuddenTermination.h>
#include <WebCore/URL.h>
#include <stdio.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/RunLoop.h>
#include <wtf/text/CString.h>
#include <wtf/text/StringBuilder.h>
#include <wtf/text/WTFString.h>
#if PLATFORM(COCOA)
#include "ObjCObjectGraph.h"
#include "PDFPlugin.h"
#include "UserMediaCaptureManagerProxy.h"
#endif
#if ENABLE(SEC_ITEM_SHIM)
#include "SecItemShimProxy.h"
#endif
using namespace WebCore;
#define MESSAGE_CHECK(assertion) MESSAGE_CHECK_BASE(assertion, connection())
#define MESSAGE_CHECK_URL(url) MESSAGE_CHECK_BASE(checkURLReceivedFromWebProcess(url), connection())
namespace WebKit {
static uint64_t generatePageID()
{
static uint64_t uniquePageID;
return ++uniquePageID;
}
static WebProcessProxy::WebPageProxyMap& globalPageMap()
{
ASSERT(RunLoop::isMain());
static NeverDestroyed<WebProcessProxy::WebPageProxyMap> pageMap;
return pageMap;
}
Ref<WebProcessProxy> WebProcessProxy::create(WebProcessPool& processPool, WebsiteDataStore& websiteDataStore)
{
return adoptRef(*new WebProcessProxy(processPool, websiteDataStore));
}
WebProcessProxy::WebProcessProxy(WebProcessPool& processPool, WebsiteDataStore& websiteDataStore)
: ChildProcessProxy(processPool.alwaysRunsAtBackgroundPriority())
, m_responsivenessTimer(*this)
, m_backgroundResponsivenessTimer(*this)
, m_processPool(processPool)
, m_mayHaveUniversalFileReadSandboxExtension(false)
, m_numberOfTimesSuddenTerminationWasDisabled(0)
, m_throttler(*this, processPool.shouldTakeUIBackgroundAssertion())
, m_isResponsive(NoOrMaybe::Maybe)
, m_visiblePageCounter([this](RefCounterEvent) { updateBackgroundResponsivenessTimer(); })
, m_websiteDataStore(websiteDataStore)
#if PLATFORM(COCOA) && ENABLE(MEDIA_STREAM)
, m_userMediaCaptureManagerProxy(std::make_unique<UserMediaCaptureManagerProxy>(*this))
#endif
{
WebPasteboardProxy::singleton().addWebProcessProxy(*this);
connect();
}
WebProcessProxy::~WebProcessProxy()
{
ASSERT(m_pageURLRetainCountMap.isEmpty());
WebPasteboardProxy::singleton().removeWebProcessProxy(*this);
if (m_webConnection)
m_webConnection->invalidate();
while (m_numberOfTimesSuddenTerminationWasDisabled-- > 0)
WebCore::enableSuddenTermination();
}
void WebProcessProxy::getLaunchOptions(ProcessLauncher::LaunchOptions& launchOptions)
{
launchOptions.processType = ProcessLauncher::ProcessType::Web;
ChildProcessProxy::getLaunchOptions(launchOptions);
if (WebKit::isInspectorProcessPool(m_processPool))
launchOptions.extraInitializationData.add(ASCIILiteral("inspector-process"), ASCIILiteral("1"));
auto overrideLanguages = m_processPool->configuration().overrideLanguages();
if (overrideLanguages.size()) {
StringBuilder languageString;
for (size_t i = 0; i < overrideLanguages.size(); ++i) {
if (i)
languageString.append(',');
languageString.append(overrideLanguages[i]);
}
launchOptions.extraInitializationData.add(ASCIILiteral("OverrideLanguages"), languageString.toString());
}
}
void WebProcessProxy::connectionWillOpen(IPC::Connection& connection)
{
ASSERT(this->connection() == &connection);
#if ENABLE(SEC_ITEM_SHIM)
SecItemShimProxy::singleton().initializeConnection(connection);
#endif
for (auto& page : m_pageMap.values())
page->connectionWillOpen(connection);
}
void WebProcessProxy::processWillShutDown(IPC::Connection& connection)
{
ASSERT_UNUSED(connection, this->connection() == &connection);
for (auto& page : m_pageMap.values())
page->webProcessWillShutDown();
releaseRemainingIconsForPageURLs();
}
void WebProcessProxy::shutDown()
{
shutDownProcess();
if (m_webConnection) {
m_webConnection->invalidate();
m_webConnection = nullptr;
}
m_responsivenessTimer.invalidate();
m_backgroundResponsivenessTimer.invalidate();
m_tokenForHoldingLockedFiles = nullptr;
Vector<RefPtr<WebFrameProxy>> frames;
copyValuesToVector(m_frameMap, frames);
for (size_t i = 0, size = frames.size(); i < size; ++i)
frames[i]->webProcessWillShutDown();
m_frameMap.clear();
for (VisitedLinkStore* visitedLinkStore : m_visitedLinkStores)
visitedLinkStore->removeProcess(*this);
m_visitedLinkStores.clear();
for (WebUserContentControllerProxy* webUserContentControllerProxy : m_webUserContentControllerProxies)
webUserContentControllerProxy->removeProcess(*this);
m_webUserContentControllerProxies.clear();
m_userInitiatedActionMap.clear();
m_processPool->disconnectProcess(this);
}
WebPageProxy* WebProcessProxy::webPage(uint64_t pageID)
{
return globalPageMap().get(pageID);
}
void WebProcessProxy::deleteWebsiteDataForTopPrivatelyControlledDomainsInAllPersistentDataStores(OptionSet<WebsiteDataType> dataTypes, Vector<String>&& topPrivatelyControlledDomains, bool shouldNotifyPage, Function<void (const HashSet<String>&)>&& completionHandler)
{
ASSERT(RunLoop::isMain());
struct CallbackAggregator : ThreadSafeRefCounted<CallbackAggregator> {
explicit CallbackAggregator(Function<void(HashSet<String>)>&& completionHandler)
: completionHandler(WTFMove(completionHandler))
{
}
void addDomainsWithDeletedWebsiteData(const HashSet<String>& domains)
{
domainsWithDeletedWebsiteData.add(domains.begin(), domains.end());
}
void addPendingCallback()
{
++pendingCallbacks;
}
void removePendingCallback()
{
ASSERT(pendingCallbacks);
--pendingCallbacks;
callIfNeeded();
}
void callIfNeeded()
{
if (!pendingCallbacks)
completionHandler(domainsWithDeletedWebsiteData);
}
unsigned pendingCallbacks = 0;
Function<void(HashSet<String>)> completionHandler;
HashSet<String> domainsWithDeletedWebsiteData;
};
RefPtr<CallbackAggregator> callbackAggregator = adoptRef(new CallbackAggregator(WTFMove(completionHandler)));
HashSet<WebCore::SessionID> visitedSessionIDs;
for (auto& page : globalPageMap()) {
auto& dataStore = page.value->websiteDataStore();
if (!dataStore.isPersistent() || visitedSessionIDs.contains(dataStore.sessionID()))
continue;
visitedSessionIDs.add(dataStore.sessionID());
callbackAggregator->addPendingCallback();
dataStore.removeDataForTopPrivatelyControlledDomains(dataTypes, { }, topPrivatelyControlledDomains, [callbackAggregator, shouldNotifyPage, page](HashSet<String>&& domainsWithDeletedWebsiteData) {
ASSERT(RunLoop::isMain());
if (shouldNotifyPage)
page.value->postMessageToInjectedBundle("WebsiteDataDeletionForTopPrivatelyOwnedDomainsFinished", nullptr);
callbackAggregator->addDomainsWithDeletedWebsiteData(WTFMove(domainsWithDeletedWebsiteData));
callbackAggregator->removePendingCallback();
});
}
}
void WebProcessProxy::topPrivatelyControlledDomainsWithWebsiteData(OptionSet<WebsiteDataType> dataTypes, bool shouldNotifyPage, Function<void(HashSet<String>&&)>&& completionHandler)
{
ASSERT(RunLoop::isMain());
struct CallbackAggregator : ThreadSafeRefCounted<CallbackAggregator> {
explicit CallbackAggregator(Function<void(HashSet<String>&&)>&& completionHandler)
: completionHandler(WTFMove(completionHandler))
{
}
void addDomainsWithDeletedWebsiteData(HashSet<String>&& domains)
{
domainsWithDeletedWebsiteData.add(domains.begin(), domains.end());
}
void addPendingCallback()
{
++pendingCallbacks;
}
void removePendingCallback()
{
ASSERT(pendingCallbacks);
--pendingCallbacks;
callIfNeeded();
}
void callIfNeeded()
{
if (!pendingCallbacks)
completionHandler(WTFMove(domainsWithDeletedWebsiteData));
}
unsigned pendingCallbacks = 0;
Function<void(HashSet<String>&&)> completionHandler;
HashSet<String> domainsWithDeletedWebsiteData;
};
RefPtr<CallbackAggregator> callbackAggregator = adoptRef(new CallbackAggregator(WTFMove(completionHandler)));
HashSet<WebCore::SessionID> visitedSessionIDs;
for (auto& page : globalPageMap()) {
auto& dataStore = page.value->websiteDataStore();
if (!dataStore.isPersistent() || visitedSessionIDs.contains(dataStore.sessionID()))
continue;
visitedSessionIDs.add(dataStore.sessionID());
callbackAggregator->addPendingCallback();
dataStore.topPrivatelyControlledDomainsWithWebsiteData(dataTypes, { }, [callbackAggregator, shouldNotifyPage, page](HashSet<String>&& domainsWithDataRecords) {
ASSERT(RunLoop::isMain());
if (shouldNotifyPage)
page.value->postMessageToInjectedBundle("WebsiteDataScanForTopPrivatelyControlledDomainsFinished", nullptr);
callbackAggregator->addDomainsWithDeletedWebsiteData(WTFMove(domainsWithDataRecords));
callbackAggregator->removePendingCallback();
});
}
callbackAggregator->callIfNeeded();
}
void WebProcessProxy::notifyPageStatisticsAndDataRecordsProcessed()
{
for (auto& page : globalPageMap())
page.value->postMessageToInjectedBundle("WebsiteDataScanForTopPrivatelyControlledDomainsFinished", nullptr);
}
void WebProcessProxy::notifyPageStatisticsTelemetryFinished(API::Object* messageBody)
{
for (auto& page : globalPageMap())
page.value->postMessageToInjectedBundle("ResourceLoadStatisticsTelemetryFinished", messageBody);
}
Ref<WebPageProxy> WebProcessProxy::createWebPage(PageClient& pageClient, Ref<API::PageConfiguration>&& pageConfiguration)
{
uint64_t pageID = generatePageID();
Ref<WebPageProxy> webPage = WebPageProxy::create(pageClient, *this, pageID, WTFMove(pageConfiguration));
addExistingWebPage(webPage.get(), pageID);
return webPage;
}
void WebProcessProxy::addExistingWebPage(WebPageProxy& webPage, uint64_t pageID)
{
ASSERT(!m_pageMap.contains(pageID));
ASSERT(!globalPageMap().contains(pageID));
m_processPool->pageAddedToProcess(webPage);
m_pageMap.set(pageID, &webPage);
globalPageMap().set(pageID, &webPage);
updateBackgroundResponsivenessTimer();
}
void WebProcessProxy::removeWebPage(WebPageProxy& webPage, uint64_t pageID)
{
auto* removedPage = m_pageMap.take(pageID);
ASSERT_UNUSED(removedPage, removedPage == &webPage);
removedPage = globalPageMap().take(pageID);
ASSERT_UNUSED(removedPage, removedPage == &webPage);
m_processPool->pageRemovedFromProcess(webPage);
updateBackgroundResponsivenessTimer();
Vector<uint64_t> itemIDsToRemove;
for (auto& idAndItem : m_backForwardListItemMap) {
if (idAndItem.value->pageID() == pageID)
itemIDsToRemove.append(idAndItem.key);
}
for (auto itemID : itemIDsToRemove)
m_backForwardListItemMap.remove(itemID);
if (state() == State::Terminated || !canTerminateChildProcess())
return;
shutDown();
}
void WebProcessProxy::addVisitedLinkStore(VisitedLinkStore& store)
{
m_visitedLinkStores.add(&store);
store.addProcess(*this);
}
void WebProcessProxy::addWebUserContentControllerProxy(WebUserContentControllerProxy& proxy, WebPageCreationParameters& parameters)
{
m_webUserContentControllerProxies.add(&proxy);
proxy.addProcess(*this, parameters);
}
void WebProcessProxy::didDestroyVisitedLinkStore(VisitedLinkStore& store)
{
ASSERT(m_visitedLinkStores.contains(&store));
m_visitedLinkStores.remove(&store);
}
void WebProcessProxy::didDestroyWebUserContentControllerProxy(WebUserContentControllerProxy& proxy)
{
ASSERT(m_webUserContentControllerProxies.contains(&proxy));
m_webUserContentControllerProxies.remove(&proxy);
}
WebBackForwardListItem* WebProcessProxy::webBackForwardItem(uint64_t itemID) const
{
return m_backForwardListItemMap.get(itemID);
}
void WebProcessProxy::registerNewWebBackForwardListItem(WebBackForwardListItem* item)
{
ASSERT(!m_backForwardListItemMap.contains(item->itemID()));
m_backForwardListItemMap.set(item->itemID(), item);
}
void WebProcessProxy::removeBackForwardItem(uint64_t itemID)
{
m_backForwardListItemMap.remove(itemID);
}
void WebProcessProxy::assumeReadAccessToBaseURL(const String& urlString)
{
URL url(URL(), urlString);
if (!url.isLocalFile())
return;
URL baseURL(URL(), url.baseAsString());
m_localPathsWithAssumedReadAccess.add(baseURL.fileSystemPath());
}
bool WebProcessProxy::hasAssumedReadAccessToURL(const URL& url) const
{
if (!url.isLocalFile())
return false;
String path = url.fileSystemPath();
auto startsWithURLPath = [&path](const String& assumedAccessPath) {
return path.startsWith(assumedAccessPath);
};
auto& platformPaths = platformPathsWithAssumedReadAccess();
auto platformPathsEnd = platformPaths.end();
if (std::find_if(platformPaths.begin(), platformPathsEnd, startsWithURLPath) != platformPathsEnd)
return true;
auto localPathsEnd = m_localPathsWithAssumedReadAccess.end();
if (std::find_if(m_localPathsWithAssumedReadAccess.begin(), localPathsEnd, startsWithURLPath) != localPathsEnd)
return true;
return false;
}
bool WebProcessProxy::checkURLReceivedFromWebProcess(const String& urlString)
{
return checkURLReceivedFromWebProcess(URL(URL(), urlString));
}
bool WebProcessProxy::checkURLReceivedFromWebProcess(const URL& url)
{
if (!url.isLocalFile())
return true;
if (m_mayHaveUniversalFileReadSandboxExtension)
return true;
if (hasAssumedReadAccessToURL(url))
return true;
String path = url.fileSystemPath();
for (WebBackForwardListItemMap::iterator iter = m_backForwardListItemMap.begin(), end = m_backForwardListItemMap.end(); iter != end; ++iter) {
URL itemURL(URL(), iter->value->url());
if (itemURL.isLocalFile() && itemURL.fileSystemPath() == path)
return true;
URL itemOriginalURL(URL(), iter->value->originalURL());
if (itemOriginalURL.isLocalFile() && itemOriginalURL.fileSystemPath() == path)
return true;
}
WTFLogAlways("Received an unexpected URL from the web process: '%s'\n", url.string().utf8().data());
return false;
}
#if !PLATFORM(COCOA)
bool WebProcessProxy::fullKeyboardAccessEnabled()
{
return false;
}
#endif
void WebProcessProxy::addBackForwardItem(uint64_t itemID, uint64_t pageID, const PageState& pageState)
{
MESSAGE_CHECK_URL(pageState.mainFrameState.originalURLString);
MESSAGE_CHECK_URL(pageState.mainFrameState.urlString);
auto& backForwardListItem = m_backForwardListItemMap.add(itemID, nullptr).iterator->value;
if (!backForwardListItem) {
BackForwardListItemState backForwardListItemState;
backForwardListItemState.identifier = itemID;
backForwardListItemState.pageState = pageState;
backForwardListItem = WebBackForwardListItem::create(WTFMove(backForwardListItemState), pageID);
return;
}
backForwardListItem->setPageState(pageState);
}
#if ENABLE(NETSCAPE_PLUGIN_API)
void WebProcessProxy::getPlugins(bool refresh, Vector<PluginInfo>& plugins, Vector<PluginInfo>& applicationPlugins)
{
if (refresh)
m_processPool->pluginInfoStore().refresh();
Vector<PluginModuleInfo> pluginModules = m_processPool->pluginInfoStore().plugins();
for (size_t i = 0; i < pluginModules.size(); ++i)
plugins.append(pluginModules[i].info);
#if ENABLE(PDFKIT_PLUGIN)
if (!m_processPool->omitPDFSupport()) {
plugins.append(PDFPlugin::pluginInfo());
applicationPlugins.append(PDFPlugin::pluginInfo());
}
#else
UNUSED_PARAM(applicationPlugins);
#endif
}
#endif // ENABLE(NETSCAPE_PLUGIN_API)
#if ENABLE(NETSCAPE_PLUGIN_API)
void WebProcessProxy::getPluginProcessConnection(uint64_t pluginProcessToken, Ref<Messages::WebProcessProxy::GetPluginProcessConnection::DelayedReply>&& reply)
{
PluginProcessManager::singleton().getPluginProcessConnection(pluginProcessToken, WTFMove(reply));
}
#endif
void WebProcessProxy::getNetworkProcessConnection(Ref<Messages::WebProcessProxy::GetNetworkProcessConnection::DelayedReply>&& reply)
{
m_processPool->getNetworkProcessConnection(WTFMove(reply));
}
#if ENABLE(DATABASE_PROCESS)
void WebProcessProxy::getDatabaseProcessConnection(Ref<Messages::WebProcessProxy::GetDatabaseProcessConnection::DelayedReply>&& reply)
{
m_processPool->getDatabaseProcessConnection(WTFMove(reply));
}
#endif // ENABLE(DATABASE_PROCESS)
void WebProcessProxy::retainIconForPageURL(const String& pageURL)
{
WebIconDatabase* iconDatabase = processPool().iconDatabase();
if (!iconDatabase || pageURL.isEmpty())
return;
auto result = m_pageURLRetainCountMap.add(pageURL, 1);
if (!result.isNewEntry)
++result.iterator->value;
iconDatabase->retainIconForPageURL(pageURL);
}
void WebProcessProxy::releaseIconForPageURL(const String& pageURL)
{
WebIconDatabase* iconDatabase = processPool().iconDatabase();
if (!iconDatabase || pageURL.isEmpty())
return;
auto result = m_pageURLRetainCountMap.find(pageURL);
if (result == m_pageURLRetainCountMap.end())
return;
--result->value;
if (!result->value)
m_pageURLRetainCountMap.remove(result);
iconDatabase->releaseIconForPageURL(pageURL);
}
void WebProcessProxy::releaseRemainingIconsForPageURLs()
{
WebIconDatabase* iconDatabase = processPool().iconDatabase();
if (!iconDatabase)
return;
for (auto& entry : m_pageURLRetainCountMap) {
uint64_t count = entry.value;
for (uint64_t i = 0; i < count; ++i)
iconDatabase->releaseIconForPageURL(entry.key);
}
m_pageURLRetainCountMap.clear();
}
#if !PLATFORM(COCOA)
bool WebProcessProxy::platformIsBeingDebugged() const
{
return false;
}
#endif
void WebProcessProxy::didReceiveMessage(IPC::Connection& connection, IPC::Decoder& decoder)
{
if (dispatchMessage(connection, decoder))
return;
if (m_processPool->dispatchMessage(connection, decoder))
return;
if (decoder.messageReceiverName() == Messages::WebProcessProxy::messageReceiverName()) {
didReceiveWebProcessProxyMessage(connection, decoder);
return;
}
}
void WebProcessProxy::didReceiveSyncMessage(IPC::Connection& connection, IPC::Decoder& decoder, std::unique_ptr<IPC::Encoder>& replyEncoder)
{
if (dispatchSyncMessage(connection, decoder, replyEncoder))
return;
if (m_processPool->dispatchSyncMessage(connection, decoder, replyEncoder))
return;
if (decoder.messageReceiverName() == Messages::WebProcessProxy::messageReceiverName()) {
didReceiveSyncWebProcessProxyMessage(connection, decoder, replyEncoder);
return;
}
}
void WebProcessProxy::didClose(IPC::Connection&)
{
Ref<WebProcessProxy> protect(*this);
webConnection()->didClose();
Vector<RefPtr<WebPageProxy>> pages;
copyValuesToVector(m_pageMap, pages);
shutDown();
#if ENABLE(PUBLIC_SUFFIX_LIST)
if (pages.size() == 1) {
auto& page = *pages[0];
String domain = topPrivatelyControlledDomain(WebCore::URL(WebCore::ParsedURLString, page.currentURL()).host());
if (!domain.isEmpty())
page.logDiagnosticMessageWithEnhancedPrivacy(WebCore::DiagnosticLoggingKeys::domainCausingCrashKey(), domain, WebCore::ShouldSample::No);
}
#endif
for (size_t i = 0, size = pages.size(); i < size; ++i)
pages[i]->processDidTerminate(ProcessTerminationReason::Crash);
}
void WebProcessProxy::didReceiveInvalidMessage(IPC::Connection& connection, IPC::StringReference messageReceiverName, IPC::StringReference messageName)
{
WTFLogAlways("Received an invalid message \"%s.%s\" from the web process.\n", messageReceiverName.toString().data(), messageName.toString().data());
WebProcessPool::didReceiveInvalidMessage(messageReceiverName, messageName);
terminate();
didClose(connection);
}
void WebProcessProxy::didBecomeUnresponsive()
{
m_isResponsive = NoOrMaybe::No;
Vector<RefPtr<WebPageProxy>> pages;
copyValuesToVector(m_pageMap, pages);
auto isResponsiveCallbacks = WTFMove(m_isResponsiveCallbacks);
for (auto& page : pages)
page->processDidBecomeUnresponsive();
bool isWebProcessResponsive = false;
for (auto& callback : isResponsiveCallbacks)
callback(isWebProcessResponsive);
}
void WebProcessProxy::didBecomeResponsive()
{
m_isResponsive = NoOrMaybe::Maybe;
Vector<RefPtr<WebPageProxy>> pages;
copyValuesToVector(m_pageMap, pages);
for (auto& page : pages)
page->processDidBecomeResponsive();
}
void WebProcessProxy::willChangeIsResponsive()
{
Vector<RefPtr<WebPageProxy>> pages;
copyValuesToVector(m_pageMap, pages);
for (auto& page : pages)
page->willChangeProcessIsResponsive();
}
void WebProcessProxy::didChangeIsResponsive()
{
Vector<RefPtr<WebPageProxy>> pages;
copyValuesToVector(m_pageMap, pages);
for (auto& page : pages)
page->didChangeProcessIsResponsive();
}
bool WebProcessProxy::mayBecomeUnresponsive()
{
return !platformIsBeingDebugged();
}
void WebProcessProxy::didFinishLaunching(ProcessLauncher* launcher, IPC::Connection::Identifier connectionIdentifier)
{
ChildProcessProxy::didFinishLaunching(launcher, connectionIdentifier);
for (WebPageProxy* page : m_pageMap.values()) {
ASSERT(this == &page->process());
page->processDidFinishLaunching();
}
m_webConnection = WebConnectionToWebProcess::create(this);
m_processPool->processDidFinishLaunching(this);
#if PLATFORM(IOS)
if (connection()) {
if (xpc_connection_t xpcConnection = connection()->xpcConnection())
m_throttler.didConnectToProcess(xpc_connection_get_pid(xpcConnection));
}
#endif
}
WebFrameProxy* WebProcessProxy::webFrame(uint64_t frameID) const
{
if (!WebFrameProxyMap::isValidKey(frameID))
return 0;
return m_frameMap.get(frameID);
}
bool WebProcessProxy::canCreateFrame(uint64_t frameID) const
{
return WebFrameProxyMap::isValidKey(frameID) && !m_frameMap.contains(frameID);
}
void WebProcessProxy::frameCreated(uint64_t frameID, WebFrameProxy* frameProxy)
{
ASSERT(canCreateFrame(frameID));
m_frameMap.set(frameID, frameProxy);
}
void WebProcessProxy::didDestroyFrame(uint64_t frameID)
{
ASSERT(WebFrameProxyMap::isValidKey(frameID));
m_frameMap.remove(frameID);
}
void WebProcessProxy::disconnectFramesFromPage(WebPageProxy* page)
{
Vector<RefPtr<WebFrameProxy>> frames;
copyValuesToVector(m_frameMap, frames);
for (size_t i = 0, size = frames.size(); i < size; ++i) {
if (frames[i]->page() == page)
frames[i]->webProcessWillShutDown();
}
}
size_t WebProcessProxy::frameCountInPage(WebPageProxy* page) const
{
size_t result = 0;
for (HashMap<uint64_t, RefPtr<WebFrameProxy>>::const_iterator iter = m_frameMap.begin(); iter != m_frameMap.end(); ++iter) {
if (iter->value->page() == page)
++result;
}
return result;
}
auto WebProcessProxy::visiblePageToken() const -> VisibleWebPageToken
{
return m_visiblePageCounter.count();
}
RefPtr<API::UserInitiatedAction> WebProcessProxy::userInitiatedActivity(uint64_t identifier)
{
if (!UserInitiatedActionMap::isValidKey(identifier) || !identifier)
return nullptr;
auto result = m_userInitiatedActionMap.ensure(identifier, [] { return API::UserInitiatedAction::create(); });
return result.iterator->value;
}
bool WebProcessProxy::isResponsive() const
{
return m_responsivenessTimer.isResponsive() && m_backgroundResponsivenessTimer.isResponsive();
}
void WebProcessProxy::didDestroyUserGestureToken(uint64_t identifier)
{
ASSERT(UserInitiatedActionMap::isValidKey(identifier));
m_userInitiatedActionMap.remove(identifier);
}
bool WebProcessProxy::canTerminateChildProcess()
{
if (!m_pageMap.isEmpty())
return false;
if (!m_processPool->shouldTerminate(this))
return false;
return true;
}
void WebProcessProxy::shouldTerminate(bool& shouldTerminate)
{
shouldTerminate = canTerminateChildProcess();
if (shouldTerminate) {
shutDown();
}
}
void WebProcessProxy::updateTextCheckerState()
{
if (canSendMessage())
send(Messages::WebProcess::SetTextCheckerState(TextChecker::state()), 0);
}
void WebProcessProxy::didSaveToPageCache()
{
m_processPool->processDidCachePage(this);
}
void WebProcessProxy::releasePageCache()
{
if (canSendMessage())
send(Messages::WebProcess::ReleasePageCache(), 0);
}
void WebProcessProxy::windowServerConnectionStateChanged()
{
for (const auto& page : m_pageMap.values())
page->activityStateDidChange(ActivityState::IsVisuallyIdle);
}
void WebProcessProxy::fetchWebsiteData(SessionID sessionID, OptionSet<WebsiteDataType> dataTypes, Function<void(WebsiteData)>&& completionHandler)
{
ASSERT(canSendMessage());
auto token = throttler().backgroundActivityToken();
RELEASE_LOG_IF(sessionID.isAlwaysOnLoggingAllowed(), ProcessSuspension, "%p - WebProcessProxy is taking a background assertion because the Web process is fetching Website data", this);
connection()->sendWithReply(Messages::WebProcess::FetchWebsiteData(sessionID, dataTypes), 0, RunLoop::main(), [this, token, completionHandler = WTFMove(completionHandler), sessionID](auto reply) {
if (!reply) {
completionHandler(WebsiteData { });
return;
}
completionHandler(WTFMove(std::get<0>(*reply)));
RELEASE_LOG_IF(sessionID.isAlwaysOnLoggingAllowed(), ProcessSuspension, "%p - WebProcessProxy is releasing a background assertion because the Web process is done fetching Website data", this);
});
}
void WebProcessProxy::deleteWebsiteData(SessionID sessionID, OptionSet<WebsiteDataType> dataTypes, std::chrono::system_clock::time_point modifiedSince, Function<void()>&& completionHandler)
{
ASSERT(canSendMessage());
auto token = throttler().backgroundActivityToken();
RELEASE_LOG_IF(sessionID.isAlwaysOnLoggingAllowed(), ProcessSuspension, "%p - WebProcessProxy is taking a background assertion because the Web process is deleting Website data", this);
connection()->sendWithReply(Messages::WebProcess::DeleteWebsiteData(sessionID, dataTypes, modifiedSince), 0, RunLoop::main(), [this, token, completionHandler = WTFMove(completionHandler), sessionID](auto reply) {
completionHandler();
RELEASE_LOG_IF(sessionID.isAlwaysOnLoggingAllowed(), ProcessSuspension, "%p - WebProcessProxy is releasing a background assertion because the Web process is done deleting Website data", this);
});
}
void WebProcessProxy::deleteWebsiteDataForOrigins(SessionID sessionID, OptionSet<WebsiteDataType> dataTypes, const Vector<WebCore::SecurityOriginData>& origins, Function<void()>&& completionHandler)
{
ASSERT(canSendMessage());
auto token = throttler().backgroundActivityToken();
RELEASE_LOG_IF(sessionID.isAlwaysOnLoggingAllowed(), ProcessSuspension, "%p - WebProcessProxy is taking a background assertion because the Web process is deleting Website data for several origins", this);
connection()->sendWithReply(Messages::WebProcess::DeleteWebsiteDataForOrigins(sessionID, dataTypes, origins), 0, RunLoop::main(), [this, token, completionHandler = WTFMove(completionHandler), sessionID](auto reply) {
completionHandler();
RELEASE_LOG_IF(sessionID.isAlwaysOnLoggingAllowed(), ProcessSuspension, "%p - WebProcessProxy is releasing a background assertion because the Web process is done deleting Website data for several origins", this);
});
}
void WebProcessProxy::requestTermination(ProcessTerminationReason reason)
{
if (state() == State::Terminated)
return;
ChildProcessProxy::terminate();
if (webConnection())
webConnection()->didClose();
Vector<RefPtr<WebPageProxy>> pages;
copyValuesToVector(m_pageMap, pages);
shutDown();
for (size_t i = 0, size = pages.size(); i < size; ++i)
pages[i]->processDidTerminate(reason);
}
void WebProcessProxy::stopResponsivenessTimer()
{
responsivenessTimer().stop();
}
void WebProcessProxy::enableSuddenTermination()
{
if (state() != State::Running)
return;
ASSERT(m_numberOfTimesSuddenTerminationWasDisabled);
WebCore::enableSuddenTermination();
--m_numberOfTimesSuddenTerminationWasDisabled;
}
void WebProcessProxy::disableSuddenTermination()
{
if (state() != State::Running)
return;
WebCore::disableSuddenTermination();
++m_numberOfTimesSuddenTerminationWasDisabled;
}
RefPtr<API::Object> WebProcessProxy::transformHandlesToObjects(API::Object* object)
{
struct Transformer final : UserData::Transformer {
Transformer(WebProcessProxy& webProcessProxy)
: m_webProcessProxy(webProcessProxy)
{
}
bool shouldTransformObject(const API::Object& object) const override
{
switch (object.type()) {
case API::Object::Type::FrameHandle:
return static_cast<const API::FrameHandle&>(object).isAutoconverting();
case API::Object::Type::PageHandle:
return static_cast<const API::PageHandle&>(object).isAutoconverting();
case API::Object::Type::PageGroupHandle:
#if PLATFORM(COCOA)
case API::Object::Type::ObjCObjectGraph:
#endif
return true;
default:
return false;
}
}
RefPtr<API::Object> transformObject(API::Object& object) const override
{
switch (object.type()) {
case API::Object::Type::FrameHandle:
ASSERT(static_cast<API::FrameHandle&>(object).isAutoconverting());
return m_webProcessProxy.webFrame(static_cast<API::FrameHandle&>(object).frameID());
case API::Object::Type::PageGroupHandle:
return WebPageGroup::get(static_cast<API::PageGroupHandle&>(object).webPageGroupData().pageGroupID);
case API::Object::Type::PageHandle:
ASSERT(static_cast<API::PageHandle&>(object).isAutoconverting());
return m_webProcessProxy.webPage(static_cast<API::PageHandle&>(object).pageID());
#if PLATFORM(COCOA)
case API::Object::Type::ObjCObjectGraph:
return m_webProcessProxy.transformHandlesToObjects(static_cast<ObjCObjectGraph&>(object));
#endif
default:
return &object;
}
}
WebProcessProxy& m_webProcessProxy;
};
return UserData::transform(object, Transformer(*this));
}
RefPtr<API::Object> WebProcessProxy::transformObjectsToHandles(API::Object* object)
{
struct Transformer final : UserData::Transformer {
bool shouldTransformObject(const API::Object& object) const override
{
switch (object.type()) {
case API::Object::Type::Frame:
case API::Object::Type::Page:
case API::Object::Type::PageGroup:
#if PLATFORM(COCOA)
case API::Object::Type::ObjCObjectGraph:
#endif
return true;
default:
return false;
}
}
RefPtr<API::Object> transformObject(API::Object& object) const override
{
switch (object.type()) {
case API::Object::Type::Frame:
return API::FrameHandle::createAutoconverting(static_cast<const WebFrameProxy&>(object).frameID());
case API::Object::Type::Page:
return API::PageHandle::createAutoconverting(static_cast<const WebPageProxy&>(object).pageID());
case API::Object::Type::PageGroup:
return API::PageGroupHandle::create(WebPageGroupData(static_cast<const WebPageGroup&>(object).data()));
#if PLATFORM(COCOA)
case API::Object::Type::ObjCObjectGraph:
return transformObjectsToHandles(static_cast<ObjCObjectGraph&>(object));
#endif
default:
return &object;
}
}
};
return UserData::transform(object, Transformer());
}
void WebProcessProxy::sendProcessWillSuspendImminently()
{
if (!canSendMessage())
return;
bool handled = false;
sendSync(Messages::WebProcess::ProcessWillSuspendImminently(), Messages::WebProcess::ProcessWillSuspendImminently::Reply(handled), 0, 1_s);
}
void WebProcessProxy::sendPrepareToSuspend()
{
if (canSendMessage())
send(Messages::WebProcess::PrepareToSuspend(), 0);
}
void WebProcessProxy::sendCancelPrepareToSuspend()
{
if (canSendMessage())
send(Messages::WebProcess::CancelPrepareToSuspend(), 0);
}
void WebProcessProxy::sendProcessDidResume()
{
if (canSendMessage())
send(Messages::WebProcess::ProcessDidResume(), 0);
}
void WebProcessProxy::processReadyToSuspend()
{
m_throttler.processReadyToSuspend();
}
void WebProcessProxy::didCancelProcessSuspension()
{
m_throttler.didCancelProcessSuspension();
}
void WebProcessProxy::reinstateNetworkProcessAssertionState(NetworkProcessProxy& newNetworkProcessProxy)
{
#if PLATFORM(IOS)
ASSERT(!m_backgroundTokenForNetworkProcess || !m_foregroundTokenForNetworkProcess);
if (m_backgroundTokenForNetworkProcess)
m_backgroundTokenForNetworkProcess = newNetworkProcessProxy.throttler().backgroundActivityToken();
else if (m_foregroundTokenForNetworkProcess)
m_foregroundTokenForNetworkProcess = newNetworkProcessProxy.throttler().foregroundActivityToken();
#else
UNUSED_PARAM(newNetworkProcessProxy);
#endif
}
void WebProcessProxy::didSetAssertionState(AssertionState state)
{
#if PLATFORM(IOS)
ASSERT(!m_backgroundTokenForNetworkProcess || !m_foregroundTokenForNetworkProcess);
switch (state) {
case AssertionState::Suspended:
RELEASE_LOG(ProcessSuspension, "%p - WebProcessProxy::didSetAssertionState(Suspended) release all assertions for network process", this);
m_foregroundTokenForNetworkProcess = nullptr;
m_backgroundTokenForNetworkProcess = nullptr;
for (auto& page : m_pageMap.values())
page->processWillBecomeSuspended();
break;
case AssertionState::Background:
RELEASE_LOG(ProcessSuspension, "%p - WebProcessProxy::didSetAssertionState(Background) taking background assertion for network process", this);
m_backgroundTokenForNetworkProcess = processPool().ensureNetworkProcess().throttler().backgroundActivityToken();
m_foregroundTokenForNetworkProcess = nullptr;
break;
case AssertionState::Foreground:
RELEASE_LOG(ProcessSuspension, "%p - WebProcessProxy::didSetAssertionState(Foreground) taking foreground assertion for network process", this);
m_foregroundTokenForNetworkProcess = processPool().ensureNetworkProcess().throttler().foregroundActivityToken();
m_backgroundTokenForNetworkProcess = nullptr;
for (auto& page : m_pageMap.values())
page->processWillBecomeForeground();
break;
}
ASSERT(!m_backgroundTokenForNetworkProcess || !m_foregroundTokenForNetworkProcess);
#else
UNUSED_PARAM(state);
#endif
}
void WebProcessProxy::setIsHoldingLockedFiles(bool isHoldingLockedFiles)
{
if (!isHoldingLockedFiles) {
RELEASE_LOG(ProcessSuspension, "UIProcess is releasing a background assertion because the WebContent process is no longer holding locked files");
m_tokenForHoldingLockedFiles = nullptr;
return;
}
if (!m_tokenForHoldingLockedFiles) {
RELEASE_LOG(ProcessSuspension, "UIProcess is taking a background assertion because the WebContent process is holding locked files");
m_tokenForHoldingLockedFiles = m_throttler.backgroundActivityToken();
}
}
void WebProcessProxy::isResponsive(WTF::Function<void(bool isWebProcessResponsive)>&& callback)
{
if (m_isResponsive == NoOrMaybe::No) {
if (callback) {
RunLoop::main().dispatch([callback = WTFMove(callback)] {
bool isWebProcessResponsive = false;
callback(isWebProcessResponsive);
});
}
return;
}
if (callback)
m_isResponsiveCallbacks.append(WTFMove(callback));
responsivenessTimer().start();
send(Messages::WebProcess::MainThreadPing(), 0);
}
void WebProcessProxy::didReceiveMainThreadPing()
{
responsivenessTimer().stop();
auto isResponsiveCallbacks = WTFMove(m_isResponsiveCallbacks);
bool isWebProcessResponsive = true;
for (auto& callback : isResponsiveCallbacks)
callback(isWebProcessResponsive);
}
void WebProcessProxy::didReceiveBackgroundResponsivenessPing()
{
m_backgroundResponsivenessTimer.didReceiveBackgroundResponsivenessPong();
}
void WebProcessProxy::processTerminated()
{
m_responsivenessTimer.processTerminated();
m_backgroundResponsivenessTimer.processTerminated();
}
void WebProcessProxy::logDiagnosticMessageForResourceLimitTermination(const String& limitKey)
{
if (pageCount())
(*pages().begin())->logDiagnosticMessage(DiagnosticLoggingKeys::simulatedPageCrashKey(), limitKey, ShouldSample::No);
}
void WebProcessProxy::didExceedInactiveMemoryLimitWhileActive()
{
for (auto& page : pages())
page->didExceedInactiveMemoryLimitWhileActive();
}
void WebProcessProxy::didExceedActiveMemoryLimit()
{
RELEASE_LOG_ERROR(PerformanceLogging, "%p - WebProcessProxy::didExceedActiveMemoryLimit() Terminating WebProcess with pid %d that has exceeded the active memory limit", this, processIdentifier());
logDiagnosticMessageForResourceLimitTermination(DiagnosticLoggingKeys::exceededActiveMemoryLimitKey());
requestTermination(ProcessTerminationReason::ExceededMemoryLimit);
}
void WebProcessProxy::didExceedInactiveMemoryLimit()
{
RELEASE_LOG_ERROR(PerformanceLogging, "%p - WebProcessProxy::didExceedInactiveMemoryLimit() Terminating WebProcess with pid %d that has exceeded the inactive memory limit", this, processIdentifier());
logDiagnosticMessageForResourceLimitTermination(DiagnosticLoggingKeys::exceededInactiveMemoryLimitKey());
requestTermination(ProcessTerminationReason::ExceededMemoryLimit);
}
void WebProcessProxy::didExceedCPULimit()
{
for (auto& page : pages()) {
if (page->isPlayingAudio()) {
RELEASE_LOG(PerformanceLogging, "%p - WebProcessProxy::didExceedCPULimit() WebProcess with pid %d has exceeded the background CPU limit but we are not terminating it because there is audio playing", this, processIdentifier());
return;
}
if (page->hasActiveAudioStream() || page->hasActiveVideoStream()) {
RELEASE_LOG(PerformanceLogging, "%p - WebProcessProxy::didExceedCPULimit() WebProcess with pid %d has exceeded the background CPU limit but we are not terminating it because it is capturing audio / video", this, processIdentifier());
return;
}
}
bool hasVisiblePage = false;
for (auto& page : pages()) {
if (page->isViewVisible()) {
page->didExceedBackgroundCPULimitWhileInForeground();
hasVisiblePage = true;
}
}
if (hasVisiblePage)
return;
RELEASE_LOG_ERROR(PerformanceLogging, "%p - WebProcessProxy::didExceedCPULimit() Terminating background WebProcess with pid %d that has exceeded the background CPU limit", this, processIdentifier());
logDiagnosticMessageForResourceLimitTermination(DiagnosticLoggingKeys::exceededBackgroundCPULimitKey());
requestTermination(ProcessTerminationReason::ExceededCPULimit);
}
void WebProcessProxy::updateBackgroundResponsivenessTimer()
{
m_backgroundResponsivenessTimer.updateState();
}
#if !PLATFORM(COCOA)
const HashSet<String>& WebProcessProxy::platformPathsWithAssumedReadAccess()
{
static NeverDestroyed<HashSet<String>> platformPathsWithAssumedReadAccess;
return platformPathsWithAssumedReadAccess;
}
#endif
}