Speed up StorageManager::getValues()
https://bugs.webkit.org/show_bug.cgi?id=199812

Reviewed by Alex Christensen.

Source/WebCore:

* storage/StorageMap.cpp:
(WebCore::StorageMap::importItems):
* storage/StorageMap.h:

Source/WebKit:

Made the following performance improvements:
- Made StorageManager a WorkQueueMessageReceiver again (like it was before it
  got moved from the UIProcess to the Network process). This avoids a lot of
  thread hopping (IPC thread -> Main thread -> StorageManagerThread -> Main Thread)
  and a lot of isolatedCopying of the strings.
- Move values around when possible to avoid copying.
- Add fast path to StorageMap::importItems() for when the StorageMap is
  empty when importing (15ms -> 2.5ms).

* NetworkProcess/NetworkConnectionToWebProcess.cpp:
(WebKit::NetworkConnectionToWebProcess::didReceiveMessage):
(WebKit::NetworkConnectionToWebProcess::didReceiveSyncMessage):
* NetworkProcess/WebStorage/LocalStorageDatabase.cpp:
(WebKit::LocalStorageDatabase::importItems):
* NetworkProcess/WebStorage/StorageManager.cpp:
(WebKit::StorageManager::addAllowedSessionStorageNamespaceConnection):
(WebKit::StorageManager::removeAllowedSessionStorageNamespaceConnection):
(WebKit::StorageManager::processDidCloseConnection):
(WebKit::StorageManager::createLocalStorageMap):
(WebKit::StorageManager::createTransientLocalStorageMap):
(WebKit::StorageManager::createSessionStorageMap):
(WebKit::StorageManager::destroyStorageMap):
(WebKit::StorageManager::getValues):
(WebKit::StorageManager::setItem):
(WebKit::StorageManager::setItems):
(WebKit::StorageManager::removeItem):
(WebKit::StorageManager::clear):
* NetworkProcess/WebStorage/StorageManager.h:

* Platform/IPC/Connection.cpp:
(IPC::Connection::addWorkQueueMessageReceiver):
(IPC::Connection::removeWorkQueueMessageReceiver):
(IPC::Connection::processIncomingMessage):
(IPC::Connection::dispatchMessage):
(IPC::Connection::dispatchMessageToWorkQueueReceiver):
* Platform/IPC/Connection.h:
* WebProcess/WebStorage/StorageAreaMap.cpp:
(WebKit::StorageAreaMap::loadValuesIfNeeded):
Messages to WorkQueueMessageReceivers are normally dispatched from the IPC WorkQueue. However, there is a race if
a client (here StorageManager) adds itself as a WorkQueueMessageReceiver as a result of receiving an IPC message
on the main thread (here NetworkConnectionToWebProcess::WebPageWasAdded).
The message might have already been dispatched from the IPC WorkQueue to the main thread by the time the
client registers itself as a WorkQueueMessageReceiver. To address this, we check again for messages receivers
once the message arrives on the main thread.

Source/WebKitLegacy:

* Storage/StorageAreaImpl.cpp:
(WebKit::StorageAreaImpl::importItems):
* Storage/StorageAreaImpl.h:
* Storage/StorageAreaSync.cpp:
(WebKit::StorageAreaSync::performImport):


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@247486 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index c0d7c77..5dabf2b 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,14 @@
+2019-07-16  Chris Dumez  <cdumez@apple.com>
+
+        Speed up StorageManager::getValues()
+        https://bugs.webkit.org/show_bug.cgi?id=199812
+
+        Reviewed by Alex Christensen.
+
+        * storage/StorageMap.cpp:
+        (WebCore::StorageMap::importItems):
+        * storage/StorageMap.h:
+
 2019-07-16  Wenson Hsieh  <wenson_hsieh@apple.com>
 
         [Text autosizing] [iPadOS] Paragraph text on the front page of LinkedIn.com is not boosted
diff --git a/Source/WebCore/storage/StorageMap.cpp b/Source/WebCore/storage/StorageMap.cpp
index 15598a8..ea5198d 100644
--- a/Source/WebCore/storage/StorageMap.cpp
+++ b/Source/WebCore/storage/StorageMap.cpp
@@ -182,19 +182,27 @@
     return m_map.contains(key);
 }
 
-void StorageMap::importItems(const HashMap<String, String>& items)
+void StorageMap::importItems(HashMap<String, String>&& items)
 {
+    if (m_map.isEmpty()) {
+        // Fast path.
+        m_map = WTFMove(items);
+        for (auto& pair : m_map) {
+            ASSERT(m_currentLength + pair.key.length() + pair.value.length() >= m_currentLength);
+            m_currentLength += (pair.key.length() + pair.value.length());
+        }
+        return;
+    }
+
     for (auto& item : items) {
-        const String& key = item.key;
-        const String& value = item.value;
+        auto& key = item.key;
+        auto& value = item.value;
 
-        HashMap<String, String>::AddResult result = m_map.add(key, value);
+        ASSERT(m_currentLength + key.length() + value.length() >= m_currentLength);
+        m_currentLength += (key.length() + value.length());
+        
+        auto result = m_map.add(WTFMove(key), WTFMove(value));
         ASSERT_UNUSED(result, result.isNewEntry); // True if the key didn't exist previously.
-
-        ASSERT(m_currentLength + key.length() >= m_currentLength);
-        m_currentLength += key.length();
-        ASSERT(m_currentLength + value.length() >= m_currentLength);
-        m_currentLength += value.length();
     }
 }
 
diff --git a/Source/WebCore/storage/StorageMap.h b/Source/WebCore/storage/StorageMap.h
index fcdcca4..3a295ee 100644
--- a/Source/WebCore/storage/StorageMap.h
+++ b/Source/WebCore/storage/StorageMap.h
@@ -46,7 +46,7 @@
 
     WEBCORE_EXPORT bool contains(const String& key) const;
 
-    WEBCORE_EXPORT void importItems(const HashMap<String, String>&);
+    WEBCORE_EXPORT void importItems(HashMap<String, String>&&);
     const HashMap<String, String>& items() const { return m_map; }
 
     unsigned quota() const { return m_quotaSize; }
diff --git a/Source/WebKit/ChangeLog b/Source/WebKit/ChangeLog
index 639ed9c..c36bc25 100644
--- a/Source/WebKit/ChangeLog
+++ b/Source/WebKit/ChangeLog
@@ -1,3 +1,55 @@
+2019-07-16  Chris Dumez  <cdumez@apple.com>
+
+        Speed up StorageManager::getValues()
+        https://bugs.webkit.org/show_bug.cgi?id=199812
+
+        Reviewed by Alex Christensen.
+
+        Made the following performance improvements:
+        - Made StorageManager a WorkQueueMessageReceiver again (like it was before it
+          got moved from the UIProcess to the Network process). This avoids a lot of
+          thread hopping (IPC thread -> Main thread -> StorageManagerThread -> Main Thread)
+          and a lot of isolatedCopying of the strings.
+        - Move values around when possible to avoid copying.
+        - Add fast path to StorageMap::importItems() for when the StorageMap is
+          empty when importing (15ms -> 2.5ms).
+
+        * NetworkProcess/NetworkConnectionToWebProcess.cpp:
+        (WebKit::NetworkConnectionToWebProcess::didReceiveMessage):
+        (WebKit::NetworkConnectionToWebProcess::didReceiveSyncMessage):
+        * NetworkProcess/WebStorage/LocalStorageDatabase.cpp:
+        (WebKit::LocalStorageDatabase::importItems):
+        * NetworkProcess/WebStorage/StorageManager.cpp:
+        (WebKit::StorageManager::addAllowedSessionStorageNamespaceConnection):
+        (WebKit::StorageManager::removeAllowedSessionStorageNamespaceConnection):
+        (WebKit::StorageManager::processDidCloseConnection):
+        (WebKit::StorageManager::createLocalStorageMap):
+        (WebKit::StorageManager::createTransientLocalStorageMap):
+        (WebKit::StorageManager::createSessionStorageMap):
+        (WebKit::StorageManager::destroyStorageMap):
+        (WebKit::StorageManager::getValues):
+        (WebKit::StorageManager::setItem):
+        (WebKit::StorageManager::setItems):
+        (WebKit::StorageManager::removeItem):
+        (WebKit::StorageManager::clear):
+        * NetworkProcess/WebStorage/StorageManager.h:
+
+        * Platform/IPC/Connection.cpp:
+        (IPC::Connection::addWorkQueueMessageReceiver):
+        (IPC::Connection::removeWorkQueueMessageReceiver):
+        (IPC::Connection::processIncomingMessage):
+        (IPC::Connection::dispatchMessage):
+        (IPC::Connection::dispatchMessageToWorkQueueReceiver):
+        * Platform/IPC/Connection.h:
+        * WebProcess/WebStorage/StorageAreaMap.cpp:
+        (WebKit::StorageAreaMap::loadValuesIfNeeded):
+        Messages to WorkQueueMessageReceivers are normally dispatched from the IPC WorkQueue. However, there is a race if
+        a client (here StorageManager) adds itself as a WorkQueueMessageReceiver as a result of receiving an IPC message
+        on the main thread (here NetworkConnectionToWebProcess::WebPageWasAdded).
+        The message might have already been dispatched from the IPC WorkQueue to the main thread by the time the
+        client registers itself as a WorkQueueMessageReceiver. To address this, we check again for messages receivers
+        once the message arrives on the main thread.
+
 2019-07-16  Zalan Bujtas  <zalan@apple.com>
 
         [ContentChangeObserver] Cancel ongoing content observation when tap is failed/cancelled
diff --git a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp
index b5bc8a3..87a6e5c 100644
--- a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp
+++ b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp
@@ -49,7 +49,6 @@
 #include "PreconnectTask.h"
 #include "ServiceWorkerFetchTaskMessages.h"
 #include "StorageManager.h"
-#include "StorageManagerMessages.h"
 #include "WebCoreArgumentCoders.h"
 #include "WebErrors.h"
 #include "WebIDBConnectionToClient.h"
@@ -232,13 +231,6 @@
         return paymentCoordinator().didReceiveMessage(connection, decoder);
 #endif
 
-    if (decoder.messageReceiverName() == Messages::StorageManager::messageReceiverName()) {
-        if (auto* session = m_networkProcess->networkSessionByConnection(connection)) {
-            session->storageManager().didReceiveMessage(connection, decoder);
-            return;
-        }
-    }
-
     ASSERT_NOT_REACHED();
 }
 
@@ -278,13 +270,6 @@
         return paymentCoordinator().didReceiveSyncMessage(connection, decoder, reply);
 #endif
 
-    if (decoder.messageReceiverName() == Messages::StorageManager::messageReceiverName()) {
-        if (auto* session = m_networkProcess->networkSessionByConnection(connection)) {
-            session->storageManager().didReceiveSyncMessage(connection, decoder, reply);
-            return;
-        }
-    }
-
     ASSERT_NOT_REACHED();
 }
 
diff --git a/Source/WebKit/NetworkProcess/WebStorage/LocalStorageDatabase.cpp b/Source/WebKit/NetworkProcess/WebStorage/LocalStorageDatabase.cpp
index 3f2f4f3..bf51578 100644
--- a/Source/WebKit/NetworkProcess/WebStorage/LocalStorageDatabase.cpp
+++ b/Source/WebKit/NetworkProcess/WebStorage/LocalStorageDatabase.cpp
@@ -171,7 +171,7 @@
     if (!m_database.isOpen())
         return;
 
-    SQLiteStatement query(m_database, "SELECT key, value FROM ItemTable");
+    SQLiteStatement query(m_database, "SELECT key, value FROM ItemTable"_str);
     if (query.prepare() != SQLITE_OK) {
         LOG_ERROR("Unable to select items from ItemTable for local storage");
         return;
@@ -184,7 +184,7 @@
         String key = query.getColumnText(0);
         String value = query.getColumnBlobAsString(1);
         if (!key.isNull() && !value.isNull())
-            items.set(key, value);
+            items.add(WTFMove(key), WTFMove(value));
         result = query.step();
     }
 
@@ -193,7 +193,7 @@
         return;
     }
 
-    storageMap.importItems(items);
+    storageMap.importItems(WTFMove(items));
 }
 
 void LocalStorageDatabase::setItem(const String& key, const String& value)
diff --git a/Source/WebKit/NetworkProcess/WebStorage/StorageManager.cpp b/Source/WebKit/NetworkProcess/WebStorage/StorageManager.cpp
index 5e8f929..7e0336f 100644
--- a/Source/WebKit/NetworkProcess/WebStorage/StorageManager.cpp
+++ b/Source/WebKit/NetworkProcess/WebStorage/StorageManager.cpp
@@ -523,6 +523,10 @@
 void StorageManager::addAllowedSessionStorageNamespaceConnection(uint64_t storageNamespaceID, IPC::Connection& allowedConnection)
 {
     auto allowedConnectionID = allowedConnection.uniqueID();
+    auto addResult = m_connections.add(allowedConnectionID);
+    if (addResult.isNewEntry)
+        allowedConnection.addWorkQueueMessageReceiver(Messages::StorageManager::messageReceiverName(), m_queue.get(), this);
+
     m_queue->dispatch([this, protectedThis = makeRef(*this), allowedConnectionID, storageNamespaceID]() mutable {
         ASSERT(m_sessionStorageNamespaces.contains(storageNamespaceID));
 
@@ -533,6 +537,9 @@
 void StorageManager::removeAllowedSessionStorageNamespaceConnection(uint64_t storageNamespaceID, IPC::Connection& allowedConnection)
 {
     auto allowedConnectionID = allowedConnection.uniqueID();
+    if (m_connections.remove(allowedConnectionID))
+        allowedConnection.removeWorkQueueMessageReceiver(Messages::StorageManager::messageReceiverName());
+
     m_queue->dispatch([this, protectedThis = makeRef(*this), allowedConnectionID, storageNamespaceID]() mutable {
         ASSERT(m_sessionStorageNamespaces.contains(storageNamespaceID));
         if (auto* sessionStorageNamespace = m_sessionStorageNamespaces.get(storageNamespaceID))
@@ -567,6 +574,9 @@
 
 void StorageManager::processDidCloseConnection(IPC::Connection& connection)
 {
+    if (m_connections.removeAll(connection.uniqueID()))
+        connection.removeWorkQueueMessageReceiver(Messages::StorageManager::messageReceiverName());
+
     m_queue->dispatch([this, protectedThis = makeRef(*this), connectionID = connection.uniqueID()]() mutable {
         Vector<std::pair<IPC::Connection::UniqueID, uint64_t>> connectionAndStorageMapIDPairsToRemove;
         for (auto& storageArea : m_storageAreasByConnection) {
@@ -729,180 +739,170 @@
 
 void StorageManager::createLocalStorageMap(IPC::Connection& connection, uint64_t storageMapID, uint64_t storageNamespaceID, SecurityOriginData&& securityOriginData)
 {
-    m_queue->dispatch([this, protectedThis = makeRef(*this), connectionID = connection.uniqueID(), storageMapID, storageNamespaceID, securityOriginData = securityOriginData.isolatedCopy()]() mutable {
-        std::pair<IPC::Connection::UniqueID, uint64_t> connectionAndStorageMapIDPair(connectionID, storageMapID);
+    ASSERT(!RunLoop::isMain());
+    auto connectionID = connection.uniqueID();
+    std::pair<IPC::Connection::UniqueID, uint64_t> connectionAndStorageMapIDPair(connectionID, storageMapID);
 
-        ASSERT((HashMap<std::pair<IPC::Connection::UniqueID, uint64_t>, RefPtr<StorageArea>>::isValidKey(connectionAndStorageMapIDPair)));
+    ASSERT((HashMap<std::pair<IPC::Connection::UniqueID, uint64_t>, RefPtr<StorageArea>>::isValidKey(connectionAndStorageMapIDPair)));
 
-        auto result = m_storageAreasByConnection.add(connectionAndStorageMapIDPair, nullptr);
-        ASSERT(result.isNewEntry);
-        ASSERT((HashMap<uint64_t, RefPtr<LocalStorageNamespace>>::isValidKey(storageNamespaceID)));
+    auto result = m_storageAreasByConnection.add(connectionAndStorageMapIDPair, nullptr);
+    ASSERT(result.isNewEntry);
+    ASSERT((HashMap<uint64_t, RefPtr<LocalStorageNamespace>>::isValidKey(storageNamespaceID)));
 
-        LocalStorageNamespace* localStorageNamespace = getOrCreateLocalStorageNamespace(storageNamespaceID);
-        ASSERT(localStorageNamespace);
+    LocalStorageNamespace* localStorageNamespace = getOrCreateLocalStorageNamespace(storageNamespaceID);
+    ASSERT(localStorageNamespace);
 
-        auto storageArea = localStorageNamespace->getOrCreateStorageArea(WTFMove(securityOriginData), m_localStorageDatabaseTracker ? StorageManager::LocalStorageNamespace::IsEphemeral::No : StorageManager::LocalStorageNamespace::IsEphemeral::Yes);
-        storageArea->addListener(connectionID, storageMapID);
+    auto storageArea = localStorageNamespace->getOrCreateStorageArea(WTFMove(securityOriginData), m_localStorageDatabaseTracker ? StorageManager::LocalStorageNamespace::IsEphemeral::No : StorageManager::LocalStorageNamespace::IsEphemeral::Yes);
+    storageArea->addListener(connectionID, storageMapID);
 
-        result.iterator->value = WTFMove(storageArea);
-    });
+    result.iterator->value = WTFMove(storageArea);
 }
 
 void StorageManager::createTransientLocalStorageMap(IPC::Connection& connection, uint64_t storageMapID, uint64_t storageNamespaceID, SecurityOriginData&& topLevelOriginData, SecurityOriginData&& origin)
 {
-    m_queue->dispatch([this, protectedThis = makeRef(*this), connectionID = connection.uniqueID(), storageMapID, storageNamespaceID, topLevelOriginData = topLevelOriginData.isolatedCopy(), origin = origin.isolatedCopy()]() mutable {
-        ASSERT(m_storageAreasByConnection.isValidKey({ connectionID, storageMapID }));
+    ASSERT(!RunLoop::isMain());
+    auto connectionID = connection.uniqueID();
 
-        // See if we already have session storage for this connection/origin combo.
-        // If so, update the map with the new ID, otherwise keep on trucking.
-        for (auto it = m_storageAreasByConnection.begin(), end = m_storageAreasByConnection.end(); it != end; ++it) {
-            if (it->key.first != connectionID)
-                continue;
-            Ref<StorageArea> area = *it->value;
-            if (!area->isEphemeral())
-                continue;
-            if (!origin.securityOrigin()->isSameSchemeHostPort(area->securityOrigin().securityOrigin().get()))
-                continue;
-            area->addListener(connectionID, storageMapID);
-            // If the storageMapID used as key in m_storageAreasByConnection is no longer one of the StorageArea's listeners, then this means
-            // that destroyStorageMap() was already called for that storageMapID but it decided not to remove it from m_storageAreasByConnection
-            // so that we could reuse it later on for the same connection/origin combo. In this case, it is safe to remove the previous
-            // storageMapID from m_storageAreasByConnection.
-            if (!area->hasListener(connectionID, it->key.second))
-                m_storageAreasByConnection.remove(it);
-            m_storageAreasByConnection.add({ connectionID, storageMapID }, WTFMove(area));
-            return;
-        }
+    ASSERT(m_storageAreasByConnection.isValidKey({ connectionID, storageMapID }));
 
-        auto& slot = m_storageAreasByConnection.add({ connectionID, storageMapID }, nullptr).iterator->value;
-        ASSERT(!slot);
+    // See if we already have session storage for this connection/origin combo.
+    // If so, update the map with the new ID, otherwise keep on trucking.
+    for (auto it = m_storageAreasByConnection.begin(), end = m_storageAreasByConnection.end(); it != end; ++it) {
+        if (it->key.first != connectionID)
+            continue;
+        Ref<StorageArea> area = *it->value;
+        if (!area->isEphemeral())
+            continue;
+        if (!origin.securityOrigin()->isSameSchemeHostPort(area->securityOrigin().securityOrigin().get()))
+            continue;
+        area->addListener(connectionID, storageMapID);
+        // If the storageMapID used as key in m_storageAreasByConnection is no longer one of the StorageArea's listeners, then this means
+        // that destroyStorageMap() was already called for that storageMapID but it decided not to remove it from m_storageAreasByConnection
+        // so that we could reuse it later on for the same connection/origin combo. In this case, it is safe to remove the previous
+        // storageMapID from m_storageAreasByConnection.
+        if (!area->hasListener(connectionID, it->key.second))
+            m_storageAreasByConnection.remove(it);
+        m_storageAreasByConnection.add({ connectionID, storageMapID }, WTFMove(area));
+        return;
+    }
 
-        auto* transientLocalStorageNamespace = getOrCreateTransientLocalStorageNamespace(storageNamespaceID, WTFMove(topLevelOriginData));
+    auto& slot = m_storageAreasByConnection.add({ connectionID, storageMapID }, nullptr).iterator->value;
+    ASSERT(!slot);
 
-        auto storageArea = transientLocalStorageNamespace->getOrCreateStorageArea(WTFMove(origin));
-        storageArea->addListener(connectionID, storageMapID);
+    auto* transientLocalStorageNamespace = getOrCreateTransientLocalStorageNamespace(storageNamespaceID, WTFMove(topLevelOriginData));
 
-        slot = WTFMove(storageArea);
-    });
+    auto storageArea = transientLocalStorageNamespace->getOrCreateStorageArea(WTFMove(origin));
+    storageArea->addListener(connectionID, storageMapID);
+
+    slot = WTFMove(storageArea);
 }
 
 void StorageManager::createSessionStorageMap(IPC::Connection& connection, uint64_t storageMapID, uint64_t storageNamespaceID, SecurityOriginData&& securityOriginData)
 {
-    m_queue->dispatch([this, protectedThis = makeRef(*this), connectionID = connection.uniqueID(), storageMapID, storageNamespaceID, securityOriginData = securityOriginData.isolatedCopy()]() mutable {
-        ASSERT(m_sessionStorageNamespaces.isValidKey(storageNamespaceID));
+    ASSERT(!RunLoop::isMain());
+    auto connectionID = connection.uniqueID();
+    ASSERT(m_sessionStorageNamespaces.isValidKey(storageNamespaceID));
 
-        SessionStorageNamespace* sessionStorageNamespace = m_sessionStorageNamespaces.get(storageNamespaceID);
-        if (!sessionStorageNamespace) {
-            // We're getting an incoming message from the web process that's for session storage for a web page
-            // that has already been closed, just ignore it.
-            return;
-        }
+    SessionStorageNamespace* sessionStorageNamespace = m_sessionStorageNamespaces.get(storageNamespaceID);
+    if (!sessionStorageNamespace) {
+        // We're getting an incoming message from the web process that's for session storage for a web page
+        // that has already been closed, just ignore it.
+        return;
+    }
 
-        ASSERT(m_storageAreasByConnection.isValidKey({ connectionID, storageMapID }));
+    ASSERT(m_storageAreasByConnection.isValidKey({ connectionID, storageMapID }));
 
-        auto& slot = m_storageAreasByConnection.add({ connectionID, storageMapID }, nullptr).iterator->value;
-        ASSERT(!slot);
-        ASSERT(sessionStorageNamespace->allowedConnections().contains(connectionID));
+    auto& slot = m_storageAreasByConnection.add({ connectionID, storageMapID }, nullptr).iterator->value;
+    ASSERT(!slot);
+    ASSERT(sessionStorageNamespace->allowedConnections().contains(connectionID));
 
-        auto storageArea = sessionStorageNamespace->getOrCreateStorageArea(WTFMove(securityOriginData));
-        storageArea->addListener(connectionID, storageMapID);
+    auto storageArea = sessionStorageNamespace->getOrCreateStorageArea(WTFMove(securityOriginData));
+    storageArea->addListener(connectionID, storageMapID);
 
-        slot = WTFMove(storageArea);
-    });
+    slot = WTFMove(storageArea);
 }
 
 void StorageManager::destroyStorageMap(IPC::Connection& connection, uint64_t storageMapID)
 {
-    m_queue->dispatch([this, protectedThis = makeRef(*this), connectionID = connection.uniqueID(), storageMapID]() mutable {
-        std::pair<IPC::Connection::UniqueID, uint64_t> connectionAndStorageMapIDPair(connectionID, storageMapID);
-        ASSERT(m_storageAreasByConnection.isValidKey(connectionAndStorageMapIDPair));
+    ASSERT(!RunLoop::isMain());
+    auto connectionID = connection.uniqueID();
 
-        auto it = m_storageAreasByConnection.find(connectionAndStorageMapIDPair);
-        if (it == m_storageAreasByConnection.end()) {
-            // The connection has been removed because the last page was closed.
-            return;
-        }
+    std::pair<IPC::Connection::UniqueID, uint64_t> connectionAndStorageMapIDPair(connectionID, storageMapID);
+    ASSERT(m_storageAreasByConnection.isValidKey(connectionAndStorageMapIDPair));
 
-        it->value->removeListener(connectionID, storageMapID);
+    auto it = m_storageAreasByConnection.find(connectionAndStorageMapIDPair);
+    if (it == m_storageAreasByConnection.end()) {
+        // The connection has been removed because the last page was closed.
+        return;
+    }
 
-        // Don't remove session storage maps. The web process may reconnect and expect the data to still be around.
-        if (it->value->isEphemeral())
-            return;
+    it->value->removeListener(connectionID, storageMapID);
 
-        m_storageAreasByConnection.remove(connectionAndStorageMapIDPair);
-    });
-}
+    // Don't remove session storage maps. The web process may reconnect and expect the data to still be around.
+    if (it->value->isEphemeral())
+        return;
 
-static void didGetValues(IPC::Connection& connection, uint64_t storageMapID, const HashMap<String, String>& items, GetValuesCallback&& completionHandler)
-{
-    RunLoop::main().dispatch([items = crossThreadCopy(items), completionHandler = WTFMove(completionHandler)]() mutable {
-        completionHandler(items);
-    });
+    m_storageAreasByConnection.remove(connectionAndStorageMapIDPair);
 }
 
 void StorageManager::getValues(IPC::Connection& connection, WebCore::SecurityOriginData&& securityOriginData, uint64_t storageMapID, uint64_t storageMapSeed, GetValuesCallback&& completionHandler)
 {
-    m_queue->dispatch([this, protectedThis = makeRef(*this), connection = makeRef(connection), securityOriginData = securityOriginData.isolatedCopy(), storageMapID, storageMapSeed, completionHandler = WTFMove(completionHandler)]() mutable {
-        auto* storageArea = findStorageArea(connection.get(), storageMapID);
+    ASSERT(!RunLoop::isMain());
+    auto* storageArea = findStorageArea(connection, storageMapID);
 
-        // This is a session storage area for a page that has already been closed. Ignore it.
-        if (!storageArea)
-            return didGetValues(connection.get(), storageMapID, { }, WTFMove(completionHandler));
+    // This is a session storage area for a page that has already been closed. Ignore it.
+    if (!storageArea)
+        return completionHandler({ });
 
-        didGetValues(connection.get(), storageMapID, storageArea->items(), WTFMove(completionHandler));
-        connection->send(Messages::StorageAreaMap::DidGetValues(storageMapSeed), storageMapID);
-    });
+    completionHandler(storageArea->items());
+    connection.send(Messages::StorageAreaMap::DidGetValues(storageMapSeed), storageMapID);
 }
 
 void StorageManager::setItem(IPC::Connection& connection, WebCore::SecurityOriginData&& securityOriginData, uint64_t storageMapID, uint64_t sourceStorageAreaID, uint64_t storageMapSeed, const String& key, const String& value, const String& urlString)
 {
-    m_queue->dispatch([this, protectedThis = makeRef(*this), connection = makeRef(connection), securityOriginData = securityOriginData.isolatedCopy(), storageMapID, sourceStorageAreaID, storageMapSeed, key = key.isolatedCopy(), value = value.isolatedCopy(), urlString = urlString.isolatedCopy()]() mutable {
-        auto* storageArea = findStorageArea(connection.get(), storageMapID);
+    ASSERT(!RunLoop::isMain());
+    auto* storageArea = findStorageArea(connection, storageMapID);
 
-        // This is a session storage area for a page that has already been closed. Ignore it.
-        if (!storageArea)
-            return;
+    // This is a session storage area for a page that has already been closed. Ignore it.
+    if (!storageArea)
+        return;
 
-        bool quotaError;
-        storageArea->setItem(connection->uniqueID(), sourceStorageAreaID, key, value, urlString, quotaError);
-        connection->send(Messages::StorageAreaMap::DidSetItem(storageMapSeed, key, quotaError), storageMapID);
-    });
+    bool quotaError;
+    storageArea->setItem(connection.uniqueID(), sourceStorageAreaID, key, value, urlString, quotaError);
+    connection.send(Messages::StorageAreaMap::DidSetItem(storageMapSeed, key, quotaError), storageMapID);
 }
 
 void StorageManager::setItems(IPC::Connection& connection, uint64_t storageMapID, const HashMap<String, String>& items)
 {
-    m_queue->dispatch([this, protectedThis = makeRef(*this), connection = makeRef(connection), storageMapID, items = crossThreadCopy(items)]() mutable {
-        if (auto* storageArea = findStorageArea(connection.get(), storageMapID))
-            storageArea->setItems(items);
-    });
+    ASSERT(!RunLoop::isMain());
+    if (auto* storageArea = findStorageArea(connection, storageMapID))
+        storageArea->setItems(items);
 }
 
 void StorageManager::removeItem(IPC::Connection& connection, WebCore::SecurityOriginData&& securityOriginData, uint64_t storageMapID, uint64_t sourceStorageAreaID, uint64_t storageMapSeed, const String& key, const String& urlString)
 {
-    m_queue->dispatch([this, protectedThis = makeRef(*this), connection = makeRef(connection), securityOriginData = securityOriginData.isolatedCopy(), storageMapID, sourceStorageAreaID, storageMapSeed, key = key.isolatedCopy(), urlString = urlString.isolatedCopy()]() mutable {
-        auto* storageArea = findStorageArea(connection.get(), storageMapID);
+    ASSERT(!RunLoop::isMain());
+    auto* storageArea = findStorageArea(connection, storageMapID);
 
-        // This is a session storage area for a page that has already been closed. Ignore it.
-        if (!storageArea)
-            return;
+    // This is a session storage area for a page that has already been closed. Ignore it.
+    if (!storageArea)
+        return;
 
-        storageArea->removeItem(connection->uniqueID(), sourceStorageAreaID, key, urlString);
-        connection->send(Messages::StorageAreaMap::DidRemoveItem(storageMapSeed, key), storageMapID);
-    });
+    storageArea->removeItem(connection.uniqueID(), sourceStorageAreaID, key, urlString);
+    connection.send(Messages::StorageAreaMap::DidRemoveItem(storageMapSeed, key), storageMapID);
 }
 
 void StorageManager::clear(IPC::Connection& connection, WebCore::SecurityOriginData&& securityOriginData, uint64_t storageMapID, uint64_t sourceStorageAreaID, uint64_t storageMapSeed, const String& urlString)
 {
-    m_queue->dispatch([this, protectedThis = makeRef(*this), connection = makeRef(connection), securityOriginData = securityOriginData.isolatedCopy(), storageMapID, sourceStorageAreaID, storageMapSeed, urlString = urlString.isolatedCopy()]() mutable {
-        auto* storageArea = findStorageArea(connection.get(), storageMapID);
+    ASSERT(!RunLoop::isMain());
+    auto* storageArea = findStorageArea(connection, storageMapID);
 
-        // This is a session storage area for a page that has already been closed. Ignore it.
-        if (!storageArea)
-            return;
+    // This is a session storage area for a page that has already been closed. Ignore it.
+    if (!storageArea)
+        return;
 
-        storageArea->clear(connection->uniqueID(), sourceStorageAreaID, urlString);
-        connection->send(Messages::StorageAreaMap::DidClear(storageMapSeed), storageMapID);
-    });
+    storageArea->clear(connection.uniqueID(), sourceStorageAreaID, urlString);
+    connection.send(Messages::StorageAreaMap::DidClear(storageMapSeed), storageMapID);
 }
 
 void StorageManager::waitUntilTasksFinished()
diff --git a/Source/WebKit/NetworkProcess/WebStorage/StorageManager.h b/Source/WebKit/NetworkProcess/WebStorage/StorageManager.h
index a5afada..4cdf501 100644
--- a/Source/WebKit/NetworkProcess/WebStorage/StorageManager.h
+++ b/Source/WebKit/NetworkProcess/WebStorage/StorageManager.h
@@ -31,6 +31,7 @@
 #include <WebCore/StorageMap.h>
 #include <wtf/Forward.h>
 #include <wtf/Function.h>
+#include <wtf/HashCountedSet.h>
 #include <wtf/HashSet.h>
 #include <wtf/text/StringHash.h>
 
@@ -45,7 +46,7 @@
 
 using GetValuesCallback = CompletionHandler<void(const HashMap<String, String>&)>;
 
-class StorageManager : public ThreadSafeRefCounted<StorageManager, WTF::DestructionThread::MainRunLoop> {
+class StorageManager : public IPC::Connection::WorkQueueMessageReceiver {
 public:
     static Ref<StorageManager> create(String&& localStorageDirectory);
     ~StorageManager();
@@ -111,6 +112,7 @@
     HashMap<uint64_t, RefPtr<SessionStorageNamespace>> m_sessionStorageNamespaces;
 
     HashMap<std::pair<IPC::Connection::UniqueID, uint64_t>, RefPtr<StorageArea>> m_storageAreasByConnection;
+    HashCountedSet<IPC::Connection::UniqueID> m_connections;
 
     enum class State {
         Running,
diff --git a/Source/WebKit/Platform/IPC/Connection.cpp b/Source/WebKit/Platform/IPC/Connection.cpp
index 8f69c87..019fbc8 100644
--- a/Source/WebKit/Platform/IPC/Connection.cpp
+++ b/Source/WebKit/Platform/IPC/Connection.cpp
@@ -307,21 +307,19 @@
 {
     ASSERT(RunLoop::isMain());
 
-    m_connectionQueue->dispatch([protectedThis = makeRef(*this), messageReceiverName = WTFMove(messageReceiverName), workQueue = &workQueue, workQueueMessageReceiver]() mutable {
-        ASSERT(!protectedThis->m_workQueueMessageReceivers.contains(messageReceiverName));
+    std::lock_guard<Lock> lock(m_workQueueMessageReceiversMutex);
+    ASSERT(!m_workQueueMessageReceivers.contains(messageReceiverName));
 
-        protectedThis->m_workQueueMessageReceivers.add(messageReceiverName, std::make_pair(workQueue, workQueueMessageReceiver));
-    });
+    m_workQueueMessageReceivers.add(messageReceiverName, std::make_pair(&workQueue, workQueueMessageReceiver));
 }
 
 void Connection::removeWorkQueueMessageReceiver(StringReference messageReceiverName)
 {
     ASSERT(RunLoop::isMain());
 
-    m_connectionQueue->dispatch([protectedThis = makeRef(*this), messageReceiverName = WTFMove(messageReceiverName)]() mutable {
-        ASSERT(protectedThis->m_workQueueMessageReceivers.contains(messageReceiverName));
-        protectedThis->m_workQueueMessageReceivers.remove(messageReceiverName);
-    });
+    std::lock_guard<Lock> lock(m_workQueueMessageReceiversMutex);
+    ASSERT(m_workQueueMessageReceivers.contains(messageReceiverName));
+    m_workQueueMessageReceivers.remove(messageReceiverName);
 }
 
 void Connection::dispatchWorkQueueMessageReceiverMessage(WorkQueueMessageReceiver& workQueueMessageReceiver, Decoder& decoder)
@@ -693,7 +691,7 @@
         return;
     }
 
-    if (!m_workQueueMessageReceivers.isValidKey(message->messageReceiverName())) {
+    if (!WorkQueueMessageReceiverMap::isValidKey(message->messageReceiverName())) {
         RefPtr<Connection> protectedThis(this);
         StringReference messageReceiverNameReference = message->messageReceiverName();
         String messageReceiverName(messageReceiverNameReference.isEmpty() ? "<unknown message receiver>" : String(messageReceiverNameReference.data(), messageReceiverNameReference.size()));
@@ -706,13 +704,8 @@
         return;
     }
 
-    auto it = m_workQueueMessageReceivers.find(message->messageReceiverName());
-    if (it != m_workQueueMessageReceivers.end()) {
-        it->value.first->dispatch([protectedThis = makeRef(*this), workQueueMessageReceiver = it->value.second, decoder = WTFMove(message)]() mutable {
-            protectedThis->dispatchWorkQueueMessageReceiverMessage(*workQueueMessageReceiver, *decoder);
-        });
+    if (dispatchMessageToWorkQueueReceiver(message))
         return;
-    }
 
 #if HAVE(QOS_CLASSES)
     if (message->isSyncMessage() && m_shouldBoostMainThreadOnSyncMessage) {
@@ -987,14 +980,37 @@
         handler(&decoder);
         return;
     }
+
     m_client.didReceiveMessage(*this, decoder);
 }
 
+bool Connection::dispatchMessageToWorkQueueReceiver(std::unique_ptr<Decoder>& message)
+{
+    std::lock_guard<Lock> lock(m_workQueueMessageReceiversMutex);
+    auto it = m_workQueueMessageReceivers.find(message->messageReceiverName());
+    if (it != m_workQueueMessageReceivers.end()) {
+        it->value.first->dispatch([protectedThis = makeRef(*this), workQueueMessageReceiver = it->value.second, decoder = WTFMove(message)]() mutable {
+            protectedThis->dispatchWorkQueueMessageReceiverMessage(*workQueueMessageReceiver, *decoder);
+        });
+        return true;
+    }
+    return false;
+}
+
 void Connection::dispatchMessage(std::unique_ptr<Decoder> message)
 {
+    ASSERT(RunLoop::isMain());
     if (!isValid())
         return;
 
+    // Messages to WorkQueueMessageReceivers are normally dispatched from the IPC WorkQueue. However, there is a race if
+    // a client adds itself as a WorkQueueMessageReceiver as a result of receiving an IPC message on the main thread.
+    // The message might have already been dispatched from the IPC WorkQueue to the main thread by the time the
+    // client registers itself as a WorkQueueMessageReceiver. To address this, we check again for messages receivers
+    // once the message arrives on the main thread.
+    if (dispatchMessageToWorkQueueReceiver(message))
+        return;
+
     if (message->shouldUseFullySynchronousModeForTesting()) {
         if (!m_fullySynchronousModeIsAllowedForTesting) {
             m_client.didReceiveInvalidMessage(*this, message->messageReceiverName(), message->messageName());
diff --git a/Source/WebKit/Platform/IPC/Connection.h b/Source/WebKit/Platform/IPC/Connection.h
index d7e1d28..42f836d 100644
--- a/Source/WebKit/Platform/IPC/Connection.h
+++ b/Source/WebKit/Platform/IPC/Connection.h
@@ -262,6 +262,8 @@
     
     std::unique_ptr<Decoder> waitForSyncReply(uint64_t syncRequestID, Seconds timeout, OptionSet<SendSyncOption>);
 
+    bool dispatchMessageToWorkQueueReceiver(std::unique_ptr<Decoder>&);
+
     // Called on the connection work queue.
     void processIncomingMessage(std::unique_ptr<Decoder>);
     void processIncomingSyncReply(std::unique_ptr<Decoder>);
@@ -325,7 +327,9 @@
     bool m_isConnected;
     Ref<WorkQueue> m_connectionQueue;
 
-    HashMap<StringReference, std::pair<RefPtr<WorkQueue>, RefPtr<WorkQueueMessageReceiver>>> m_workQueueMessageReceivers;
+    Lock m_workQueueMessageReceiversMutex;
+    using WorkQueueMessageReceiverMap = HashMap<StringReference, std::pair<RefPtr<WorkQueue>, RefPtr<WorkQueueMessageReceiver>>>;
+    WorkQueueMessageReceiverMap m_workQueueMessageReceivers;
 
     unsigned m_inSendSyncCount;
     unsigned m_inDispatchMessageCount;
diff --git a/Source/WebKit/WebProcess/WebStorage/StorageAreaMap.cpp b/Source/WebKit/WebProcess/WebStorage/StorageAreaMap.cpp
index dd743a9..bf0dc6c 100644
--- a/Source/WebKit/WebProcess/WebStorage/StorageAreaMap.cpp
+++ b/Source/WebKit/WebProcess/WebStorage/StorageAreaMap.cpp
@@ -181,7 +181,7 @@
     WebProcess::singleton().ensureNetworkProcessConnection().connection().sendSync(Messages::StorageManager::GetValues(m_securityOrigin->data(), m_storageMapID, m_currentSeed), Messages::StorageManager::GetValues::Reply(values), 0);
 
     m_storageMap = StorageMap::create(m_quotaInBytes);
-    m_storageMap->importItems(values);
+    m_storageMap->importItems(WTFMove(values));
 
     // We want to ignore all changes until we get the DidGetValues message.
     m_hasPendingGetValues = true;
diff --git a/Source/WebKitLegacy/ChangeLog b/Source/WebKitLegacy/ChangeLog
index 803836a..060d346 100644
--- a/Source/WebKitLegacy/ChangeLog
+++ b/Source/WebKitLegacy/ChangeLog
@@ -1,3 +1,16 @@
+2019-07-16  Chris Dumez  <cdumez@apple.com>
+
+        Speed up StorageManager::getValues()
+        https://bugs.webkit.org/show_bug.cgi?id=199812
+
+        Reviewed by Alex Christensen.
+
+        * Storage/StorageAreaImpl.cpp:
+        (WebKit::StorageAreaImpl::importItems):
+        * Storage/StorageAreaImpl.h:
+        * Storage/StorageAreaSync.cpp:
+        (WebKit::StorageAreaSync::performImport):
+
 2019-07-12  Alex Christensen  <achristensen@webkit.org>
 
         Begin unifying WebKitLegacy sources
diff --git a/Source/WebKitLegacy/Storage/StorageAreaImpl.cpp b/Source/WebKitLegacy/Storage/StorageAreaImpl.cpp
index fc9562f..7da5c72 100644
--- a/Source/WebKitLegacy/Storage/StorageAreaImpl.cpp
+++ b/Source/WebKitLegacy/Storage/StorageAreaImpl.cpp
@@ -194,11 +194,11 @@
     return m_storageMap->contains(key);
 }
 
-void StorageAreaImpl::importItems(const HashMap<String, String>& items)
+void StorageAreaImpl::importItems(HashMap<String, String>&& items)
 {
     ASSERT(!m_isShutdown);
 
-    m_storageMap->importItems(items);
+    m_storageMap->importItems(WTFMove(items));
 }
 
 void StorageAreaImpl::close()
diff --git a/Source/WebKitLegacy/Storage/StorageAreaImpl.h b/Source/WebKitLegacy/Storage/StorageAreaImpl.h
index 386d48d..58fcba0 100644
--- a/Source/WebKitLegacy/Storage/StorageAreaImpl.h
+++ b/Source/WebKitLegacy/Storage/StorageAreaImpl.h
@@ -67,7 +67,7 @@
     void close();
 
     // Only called from a background thread.
-    void importItems(const HashMap<String, String>& items);
+    void importItems(HashMap<String, String>&& items);
 
     // Used to clear a StorageArea and close db before backing db file is deleted.
     void clearForOriginDeletion();
diff --git a/Source/WebKitLegacy/Storage/StorageAreaSync.cpp b/Source/WebKitLegacy/Storage/StorageAreaSync.cpp
index 17d4894..ce5c29b 100644
--- a/Source/WebKitLegacy/Storage/StorageAreaSync.cpp
+++ b/Source/WebKitLegacy/Storage/StorageAreaSync.cpp
@@ -347,7 +347,7 @@
         return;
     }
 
-    m_storageArea->importItems(itemMap);
+    m_storageArea->importItems(WTFMove(itemMap));
 
     markImported();
 }