From f52abbbacd4b9fd731e8a250e5e8c6379add50a5 Mon Sep 17 00:00:00 2001 From: Rupin Mittal Date: Wed, 11 Dec 2024 22:50:24 -0800 Subject: [PATCH] Add WKWebView SPI to fetch and restore Session Storage data https://bugs.webkit.org/show_bug.cgi?id=283503 rdar://140356755 Reviewed by Sihui Liu and Wenson Hsieh. Some users may want Session Storage to be restored after a software update. So we want to allow clients (Safari) to fetch this storage and then later restore it. The restoration process will have two parts: 1. Client fetches the session storage data from WebKit and holds onto it. 2. Client gives back the data to WebKit to restore it. This patch adds SPI for fetch and restore--and will eventually go through API review. The SPI works for both ephemeral and persistent data stores, so there is an API test for each. The SPI also works for both first party storage and for third party storage, so there are API tests for each. * Source/WebKit/NetworkProcess/NetworkProcess.cpp: (WebKit::NetworkProcess::fetchSessionStorage): (WebKit::NetworkProcess::restoreSessionStorage): * Source/WebKit/NetworkProcess/NetworkProcess.h: * Source/WebKit/NetworkProcess/NetworkProcess.messages.in: * Source/WebKit/NetworkProcess/storage/LocalStorageManager.cpp: (WebKit::LocalStorageManager::setStorageMap): (WebKit::LocalStorageManager::populateStorageArea): Deleted. * Source/WebKit/NetworkProcess/storage/LocalStorageManager.h: * Source/WebKit/NetworkProcess/storage/NetworkStorageManager.cpp: (WebKit::NetworkStorageManager::fetchSessionStorageForWebPage): (WebKit::NetworkStorageManager::restoreSessionStorageForWebPage): (WebKit::NetworkStorageManager::restoreLocalStorage): * Source/WebKit/NetworkProcess/storage/NetworkStorageManager.h: * Source/WebKit/NetworkProcess/storage/SessionStorageManager.cpp: (WebKit::SessionStorageManager::fetchStorageMap): (WebKit::SessionStorageManager::setStorageMap): * Source/WebKit/NetworkProcess/storage/SessionStorageManager.h: * Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm: (-[WKWebView _fetchDataOfTypes:completionHandler:]): (-[WKWebView _restoreData:completionHandler:]): * Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h: * Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.mm: (-[WKWebsiteDataStore _fetchDataOfTypes:completionHandler:]): (-[WKWebsiteDataStore _restoreData:completionHandler:]): * Source/WebKit/UIProcess/WebPageProxy.cpp: (WebKit::WebPageProxy::fetchSessionStorage): (WebKit::WebPageProxy::restoreSessionStorage): * Source/WebKit/UIProcess/WebPageProxy.h: * Source/WebKit/UIProcess/WebsiteData/WebsiteDataStore.cpp: (WebKit::WebsiteDataStore::fetchLocalStorage): * Tools/TestWebKitAPI/SourcesCocoa.txt: * Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj: * Tools/TestWebKitAPI/Tests/WebKitCocoa/RestoreLocalStorage.mm: (TEST(WebKit, RestoreLocalStorageFromPersistentDataStore)): (TEST(WebKit, RestoreLocalStorageFromEphemeralDataStore)): (RestoreLocalStorageFromPersistentDataStoreThirdPartyIFrame)): (RestoreLocalStorageFromEphemeralDataStoreThirdPartyIFrame)): * Tools/TestWebKitAPI/Tests/WebKitCocoa/RestoreSessionStorage.mm: Copied froTools/TestWebKitAPI/Tests/WebKitCocoa/RestoreLocalStorage.mm. (testRestoreSessionStorage): (TEST(WebKit, RestoreSessionStorageFromPersistentDataStore)): (TEST(WebKit, RestoreSessionStorageFromEphemeralDataStore)): (-[RestoreSessionStorageMessageHandler userContentController:didReceiveScriptMessage:]): (postMessage): (item): (RestoreSessionStorageFromPersistentDataStoreThirdPartyIFrame)): (RestoreSessionStorageFromEphemeralDataStoreThirdPartyIFrame)): * Tools/TestWebKitAPI/Tests/WebKitCocoa/SiteIsolation.mm: * Tools/TestWebKitAPI/Tests/WebKitCocoa/TextWidth.mm: Canonical link: https://commits.webkit.org/287729@main --- .../WebKit/NetworkProcess/NetworkProcess.cpp | 22 ++ Source/WebKit/NetworkProcess/NetworkProcess.h | 3 + .../NetworkProcess/NetworkProcess.messages.in | 3 + .../storage/LocalStorageManager.cpp | 2 +- .../storage/LocalStorageManager.h | 2 +- .../storage/NetworkStorageManager.cpp | 54 +++- .../storage/NetworkStorageManager.h | 2 + .../storage/SessionStorageManager.cpp | 32 +++ .../storage/SessionStorageManager.h | 3 + .../WebKit/UIProcess/API/Cocoa/WKWebView.mm | 107 ++++++++ .../UIProcess/API/Cocoa/WKWebViewPrivate.h | 7 + .../UIProcess/API/Cocoa/WKWebsiteDataStore.mm | 8 +- Source/WebKit/UIProcess/WebPageProxy.cpp | 13 + Source/WebKit/UIProcess/WebPageProxy.h | 3 + .../WebsiteData/WebsiteDataStore.cpp | 5 +- Tools/TestWebKitAPI/SourcesCocoa.txt | 1 + .../TestWebKitAPI.xcodeproj/project.pbxproj | 2 + .../Tests/WebKitCocoa/RestoreLocalStorage.mm | 8 +- .../WebKitCocoa/RestoreSessionStorage.mm | 252 ++++++++++++++++++ .../Tests/WebKitCocoa/SiteIsolation.mm | 1 + .../Tests/WebKitCocoa/TextWidth.mm | 1 + 21 files changed, 519 insertions(+), 12 deletions(-) create mode 100644 Tools/TestWebKitAPI/Tests/WebKitCocoa/RestoreSessionStorage.mm diff --git a/Source/WebKit/NetworkProcess/NetworkProcess.cpp b/Source/WebKit/NetworkProcess/NetworkProcess.cpp index c4b83f38525b7..f542d04d92205 100644 --- a/Source/WebKit/NetworkProcess/NetworkProcess.cpp +++ b/Source/WebKit/NetworkProcess/NetworkProcess.cpp @@ -3127,4 +3127,26 @@ void NetworkProcess::restoreLocalStorage(PAL::SessionID sessionID, HashMapprotectedStorageManager()->restoreLocalStorage(WTFMove(localStorageMap), WTFMove(completionHandler)); } +void NetworkProcess::fetchSessionStorage(PAL::SessionID sessionID, WebPageProxyIdentifier pageID, CompletionHandler>&&)>&& completionHandler) +{ + CheckedPtr session = networkSession(sessionID); + if (!session) { + completionHandler({ }); + return; + } + + session->protectedStorageManager()->fetchSessionStorageForWebPage(pageID, WTFMove(completionHandler)); +} + +void NetworkProcess::restoreSessionStorage(PAL::SessionID sessionID, WebPageProxyIdentifier pageID, HashMap>&& sessionStorageMap, CompletionHandler&& completionHandler) +{ + CheckedPtr session = networkSession(sessionID); + if (!session) { + completionHandler(false); + return; + } + + session->protectedStorageManager()->restoreSessionStorageForWebPage(pageID, WTFMove(sessionStorageMap), WTFMove(completionHandler)); +} + } // namespace WebKit diff --git a/Source/WebKit/NetworkProcess/NetworkProcess.h b/Source/WebKit/NetworkProcess/NetworkProcess.h index 2581220ee3c0a..a64bf407608e1 100644 --- a/Source/WebKit/NetworkProcess/NetworkProcess.h +++ b/Source/WebKit/NetworkProcess/NetworkProcess.h @@ -455,6 +455,9 @@ class NetworkProcess final : public AuxiliaryProcess, private DownloadManager::C void fetchLocalStorage(PAL::SessionID, CompletionHandler>&&)>&&); void restoreLocalStorage(PAL::SessionID, HashMap>&&, CompletionHandler&&); + void fetchSessionStorage(PAL::SessionID, WebPageProxyIdentifier, CompletionHandler>&&)>&&); + void restoreSessionStorage(PAL::SessionID, WebPageProxyIdentifier, HashMap>&&, CompletionHandler&&); + private: void platformInitializeNetworkProcess(const NetworkProcessCreationParameters&); diff --git a/Source/WebKit/NetworkProcess/NetworkProcess.messages.in b/Source/WebKit/NetworkProcess/NetworkProcess.messages.in index d937eedeb0ec2..12a535d3240f9 100644 --- a/Source/WebKit/NetworkProcess/NetworkProcess.messages.in +++ b/Source/WebKit/NetworkProcess/NetworkProcess.messages.in @@ -262,4 +262,7 @@ messages -> NetworkProcess : AuxiliaryProcess WantsAsyncDispatchMessage { FetchLocalStorage(PAL::SessionID sessionID) -> (HashMap> localStorageMap) RestoreLocalStorage(PAL::SessionID sessionID, HashMap> localStorageMap) -> (bool succeeded) + + FetchSessionStorage(PAL::SessionID sessionID, WebKit::WebPageProxyIdentifier pageID) -> (HashMap> sessionStorageMap) + RestoreSessionStorage(PAL::SessionID sessionID, WebKit::WebPageProxyIdentifier pageID, HashMap> localStorageMap) -> (bool succeeded) } diff --git a/Source/WebKit/NetworkProcess/storage/LocalStorageManager.cpp b/Source/WebKit/NetworkProcess/storage/LocalStorageManager.cpp index 84cb19f8f9ff0..81f62f84cbe6e 100644 --- a/Source/WebKit/NetworkProcess/storage/LocalStorageManager.cpp +++ b/Source/WebKit/NetworkProcess/storage/LocalStorageManager.cpp @@ -261,7 +261,7 @@ HashMap LocalStorageManager::fetchStorageMap() const return { }; } -bool LocalStorageManager::populateStorageArea(WebCore::ClientOrigin clientOrigin, HashMap&& storageMap, Ref&& workQueue) +bool LocalStorageManager::setStorageMap(WebCore::ClientOrigin clientOrigin, HashMap&& storageMap, Ref&& workQueue) { bool succeeded = true; diff --git a/Source/WebKit/NetworkProcess/storage/LocalStorageManager.h b/Source/WebKit/NetworkProcess/storage/LocalStorageManager.h index 39ea30401a6a6..84a2131dda70b 100644 --- a/Source/WebKit/NetworkProcess/storage/LocalStorageManager.h +++ b/Source/WebKit/NetworkProcess/storage/LocalStorageManager.h @@ -66,7 +66,7 @@ class LocalStorageManager { void disconnectFromStorageArea(IPC::Connection::UniqueID, StorageAreaIdentifier); HashMap fetchStorageMap() const; - bool populateStorageArea(WebCore::ClientOrigin, HashMap&&, Ref&&); + bool setStorageMap(WebCore::ClientOrigin, HashMap&&, Ref&&); private: void connectionClosedForLocalStorageArea(IPC::Connection::UniqueID); diff --git a/Source/WebKit/NetworkProcess/storage/NetworkStorageManager.cpp b/Source/WebKit/NetworkProcess/storage/NetworkStorageManager.cpp index e860e71785331..220fdc635cc8b 100644 --- a/Source/WebKit/NetworkProcess/storage/NetworkStorageManager.cpp +++ b/Source/WebKit/NetworkProcess/storage/NetworkStorageManager.cpp @@ -830,6 +830,58 @@ void NetworkStorageManager::cloneSessionStorageNamespace(StorageNamespaceIdentif } } +void NetworkStorageManager::fetchSessionStorageForWebPage(WebPageProxyIdentifier pageIdentifier, CompletionHandler>&&)>&& completionHandler) +{ + ASSERT(RunLoop::isMain()); + ASSERT(!m_closed); + + protectedWorkQueue()->dispatch([this, protectedThis = Ref { *this }, pageIdentifier, completionHandler = WTFMove(completionHandler)]() mutable { + assertIsCurrent(workQueue()); + + HashMap> sessionStorageMap; + StorageNamespaceIdentifier storageNameSpaceIdentifier { pageIdentifier.toUInt64() }; + + for (auto& [origin, originStorageManager] : m_originStorageManagers) { + auto* sessionStorageManager = originStorageManager->existingSessionStorageManager(); + if (!sessionStorageManager) + continue; + + auto storageMap = sessionStorageManager->fetchStorageMap(storageNameSpaceIdentifier); + if (!storageMap.isEmpty()) + sessionStorageMap.add(origin, WTFMove(storageMap)); + } + + RunLoop::protectedMain()->dispatch([completionHandler = WTFMove(completionHandler), sessionStorageMap = crossThreadCopy(WTFMove(sessionStorageMap))] mutable { + completionHandler(WTFMove(sessionStorageMap)); + }); + }); +} + +void NetworkStorageManager::restoreSessionStorageForWebPage(WebPageProxyIdentifier pageIdentifier, HashMap>&& sessionStorageMap, CompletionHandler&& completionHandler) +{ + ASSERT(RunLoop::isMain()); + ASSERT(!m_closed); + + protectedWorkQueue()->dispatch([this, protectedThis = Ref { *this }, pageIdentifier, sessionStorageMap = crossThreadCopy(WTFMove(sessionStorageMap)), completionHandler = WTFMove(completionHandler)]() mutable { + assertIsCurrent(workQueue()); + + bool succeeded = true; + StorageNamespaceIdentifier storageNameSpaceIdentifier { pageIdentifier.toUInt64() }; + + for (auto& [clientOrigin, storageMap] : sessionStorageMap) { + auto& sessionStorageManager = originStorageManager(clientOrigin, ShouldWriteOriginFile::Yes).sessionStorageManager(*m_storageAreaRegistry); + auto result = sessionStorageManager.setStorageMap(storageNameSpaceIdentifier, clientOrigin, WTFMove(storageMap)); + + if (!result) + succeeded = false; + } + + RunLoop::protectedMain()->dispatch([protectedThis = WTFMove(protectedThis), completionHandler = WTFMove(completionHandler), succeeded] mutable { + completionHandler(succeeded); + }); + }); +} + void NetworkStorageManager::didIncreaseQuota(WebCore::ClientOrigin&& origin, QuotaIncreaseRequestIdentifier identifier, std::optional newQuota) { ASSERT(RunLoop::isMain()); @@ -1353,7 +1405,7 @@ void NetworkStorageManager::restoreLocalStorage(HashMap&&); void clearStorageForWebPage(WebPageProxyIdentifier); void cloneSessionStorageForWebPage(WebPageProxyIdentifier, WebPageProxyIdentifier); + void fetchSessionStorageForWebPage(WebPageProxyIdentifier, CompletionHandler>&&)>&&); + void restoreSessionStorageForWebPage(WebPageProxyIdentifier, HashMap>&&, CompletionHandler&&); void didIncreaseQuota(WebCore::ClientOrigin&&, QuotaIncreaseRequestIdentifier, std::optional newQuota); enum class ShouldComputeSize : bool { No, Yes }; void fetchData(OptionSet, ShouldComputeSize, CompletionHandler&&)>&&); diff --git a/Source/WebKit/NetworkProcess/storage/SessionStorageManager.cpp b/Source/WebKit/NetworkProcess/storage/SessionStorageManager.cpp index 974a4adf1b52c..fe0e2acfca236 100644 --- a/Source/WebKit/NetworkProcess/storage/SessionStorageManager.cpp +++ b/Source/WebKit/NetworkProcess/storage/SessionStorageManager.cpp @@ -129,4 +129,36 @@ void SessionStorageManager::cloneStorageArea(StorageNamespaceIdentifier sourceNa addStorageArea(storageArea->clone(), targetNamespaceIdentifier); } +HashMap SessionStorageManager::fetchStorageMap(StorageNamespaceIdentifier namespaceIdentifier) +{ + auto identifier = m_storageAreasByNamespace.getOptional(namespaceIdentifier); + if (!identifier) + return { }; + + RefPtr storageArea = m_storageAreas.get(*identifier); + if (!storageArea) + return { }; + + return storageArea->allItems(); +} + +bool SessionStorageManager::setStorageMap(StorageNamespaceIdentifier storageNamespaceIdentifier, WebCore::ClientOrigin clientOrigin, HashMap&& storageMap) +{ + auto identifier = m_storageAreasByNamespace.getOptional(storageNamespaceIdentifier); + if (!identifier) + identifier = addStorageArea(MemoryStorageArea::create(clientOrigin), storageNamespaceIdentifier); + + RefPtr storageArea = m_storageAreas.get(*identifier); + if (!storageArea) + return false; + + bool succeeded = true; + for (auto& [key, value] : storageMap) { + if (!storageArea->setItem({ }, { }, WTFMove(key), WTFMove(value), { })) + succeeded = false; + } + + return succeeded; +} + } // namespace WebKit diff --git a/Source/WebKit/NetworkProcess/storage/SessionStorageManager.h b/Source/WebKit/NetworkProcess/storage/SessionStorageManager.h index 0cb7c61d4d1b3..ec2c5625eda99 100644 --- a/Source/WebKit/NetworkProcess/storage/SessionStorageManager.h +++ b/Source/WebKit/NetworkProcess/storage/SessionStorageManager.h @@ -55,6 +55,9 @@ class SessionStorageManager { void disconnectFromStorageArea(IPC::Connection::UniqueID, StorageAreaIdentifier); void cloneStorageArea(StorageNamespaceIdentifier, StorageNamespaceIdentifier); + HashMap fetchStorageMap(StorageNamespaceIdentifier); + bool setStorageMap(StorageNamespaceIdentifier, WebCore::ClientOrigin, HashMap&&); + private: StorageAreaIdentifier addStorageArea(Ref&&, StorageNamespaceIdentifier); diff --git a/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm b/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm index 286fe6ba5f459..4dec1c7a19c22 100644 --- a/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm +++ b/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm @@ -181,6 +181,8 @@ #import #import #import +#import +#import #import #import #import @@ -5245,6 +5247,111 @@ - (_WKWebProcessState)_webProcessState } } +namespace WebKit { +enum class WebViewDataType : uint32_t { + SessionStorage +}; +} + +namespace WTF { +template<> struct EnumTraitsForPersistence { + using values = EnumValues< + WebKit::WebViewDataType, + WebKit::WebViewDataType::SessionStorage + >; +}; +} + +struct WKWebViewData { + std::optional>> sessionStorage; +}; + +- (void)_fetchDataOfTypes:(_WKWebViewDataType)dataTypes completionHandler:(void (^)(NSData *))completionHandler +{ + Vector dataTypesToEncode; + if (dataTypes & _WKWebViewDataTypeSessionStorage) + dataTypesToEncode.append(WebKit::WebViewDataType::SessionStorage); + + auto data = Box::create(); + + auto callbackAggregator = CallbackAggregator::create([completionHandler = makeBlockPtr(completionHandler), dataTypesToEncode = WTFMove(dataTypesToEncode), data] { + WTF::Persistence::Encoder encoder; + constexpr unsigned currentWKWebViewDataSerializationVersion = 1; + encoder << currentWKWebViewDataSerializationVersion; + encoder << dataTypesToEncode; + + for (auto& dataTypeToEncode : dataTypesToEncode) { + switch (dataTypeToEncode) { + case WebKit::WebViewDataType::SessionStorage: + encoder << data->sessionStorage.value(); + break; + default: + ASSERT_NOT_REACHED(); + break; + } + } + + completionHandler(toNSData(encoder.span()).get()); + }); + + if (dataTypes & _WKWebViewDataTypeSessionStorage) { + RefPtr page = [self _protectedPage]; + page->fetchSessionStorage([callbackAggregator, protectedPage = page, data](auto&& sessionStorage) { + data->sessionStorage = WTFMove(sessionStorage); + }); + } +} + +- (void)_restoreData:(NSData *)data completionHandler:(void(^)(BOOL))completionHandler +{ + WTF::Persistence::Decoder decoder(span(data)); + + std::optional currentWKWebViewDataSerializationVersion; + decoder >> currentWKWebViewDataSerializationVersion; + if (!currentWKWebViewDataSerializationVersion) { + completionHandler(NO); + return; + } + + std::optional> encodedDataTypes; + decoder >> encodedDataTypes; + if (!encodedDataTypes) { + completionHandler(NO); + return; + } + + auto succeeded = Box::create(true); + auto callbackAggregator = CallbackAggregator::create([completionHandler = makeBlockPtr(completionHandler), succeeded] { + completionHandler(*succeeded); + }); + + for (auto& encodedDataType : *encodedDataTypes) { + switch (encodedDataType) { + case WebKit::WebViewDataType::SessionStorage: { + std::optional>> sessionStorage; + decoder >> sessionStorage; + + if (!sessionStorage) { + *succeeded = false; + return; + } + + if (!sessionStorage->isEmpty()) { + RefPtr page = [self _protectedPage]; + page->restoreSessionStorage(WTFMove(*sessionStorage), [callbackAggregator, succeeded](bool restoreSucceeded) { + if (!restoreSucceeded) + *succeeded = false; + }); + } + break; + } + default: + ASSERT_NOT_REACHED(); + break; + } + } +} + @end @implementation WKWebView (WKDeprecated) diff --git a/Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h b/Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h index ea4092fbe15f9..adb64d96373ef 100644 --- a/Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h +++ b/Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h @@ -601,6 +601,13 @@ typedef NS_OPTIONS(NSUInteger, WKDisplayCaptureSurfaces) { // This property is KVO compliant. @property (nonatomic, readonly) _WKWebProcessState _webProcessState WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA), visionos(WK_XROS_TBA)); +typedef NS_OPTIONS(NSUInteger, _WKWebViewDataType) { + _WKWebViewDataTypeSessionStorage = 1 << 0 +} WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA), visionos(WK_XROS_TBA)); + +- (void)_fetchDataOfTypes:(_WKWebViewDataType)dataTypes completionHandler:(WK_SWIFT_UI_ACTOR void (^)(NSData *))completionHandler WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA), visionos(WK_XROS_TBA)); +- (void)_restoreData:(NSData *)data completionHandler:(WK_SWIFT_UI_ACTOR void(^)(BOOL))completionHandler WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA), visionos(WK_XROS_TBA)); + @end #if TARGET_OS_IPHONE diff --git a/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.mm b/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.mm index 9b704db4cc6ce..56afa6a3814a1 100644 --- a/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.mm +++ b/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.mm @@ -1470,7 +1470,7 @@ - (void)_fetchDataOfTypes:(NSSet *)dataTypes completionHandler:(void }); if ([dataTypes containsObject:WKWebsiteDataTypeLocalStorage]) { - _websiteDataStore->fetchLocalStorage([callbackAggregator, data](HashMap>&& localStorage) { + _websiteDataStore->fetchLocalStorage([callbackAggregator, data](auto&& localStorage) { data->localStorage = WTFMove(localStorage); }); } @@ -1480,9 +1480,9 @@ - (void)_restoreData:(NSData *)data completionHandler:(void(^)(BOOL))completionH { WTF::Persistence::Decoder decoder(span(data)); - std::optional currentLocalStorageSerializationVersion; - decoder >> currentLocalStorageSerializationVersion; - if (!currentLocalStorageSerializationVersion) { + std::optional currentWKWebsiteDataSerializationVersion; + decoder >> currentWKWebsiteDataSerializationVersion; + if (!currentWKWebsiteDataSerializationVersion) { completionHandler(NO); return; } diff --git a/Source/WebKit/UIProcess/WebPageProxy.cpp b/Source/WebKit/UIProcess/WebPageProxy.cpp index a3bfbf88b4b2b..c7b5563c4100b 100644 --- a/Source/WebKit/UIProcess/WebPageProxy.cpp +++ b/Source/WebKit/UIProcess/WebPageProxy.cpp @@ -15651,6 +15651,19 @@ FloatPoint WebPageProxy::mainFrameScrollPosition() const #endif // PLATFORM(COCOA) && ENABLE(ASYNC_SCROLLING) +void WebPageProxy::fetchSessionStorage(CompletionHandler>&&)>&& completionHandler) +{ + if (RefPtr networkProcess = websiteDataStore().networkProcessIfExists()) + networkProcess->sendWithAsyncReply(Messages::NetworkProcess::FetchSessionStorage(sessionID(), identifier()), WTFMove(completionHandler)); + else + completionHandler({ }); +} + +void WebPageProxy::restoreSessionStorage(HashMap>&& sessionStorage, CompletionHandler&& completionHandler) +{ + protectedWebsiteDataStore()->protectedNetworkProcess()->sendWithAsyncReply(Messages::NetworkProcess::RestoreSessionStorage(sessionID(), identifier(), WTFMove(sessionStorage)), WTFMove(completionHandler)); +} + } // namespace WebKit #undef WEBPAGEPROXY_RELEASE_LOG diff --git a/Source/WebKit/UIProcess/WebPageProxy.h b/Source/WebKit/UIProcess/WebPageProxy.h index aaf5d75c4c91b..6870f64391718 100644 --- a/Source/WebKit/UIProcess/WebPageProxy.h +++ b/Source/WebKit/UIProcess/WebPageProxy.h @@ -2614,6 +2614,9 @@ class WebPageProxy final : public API::ObjectImpl, publ WebCore::FloatPoint mainFrameScrollPosition() const; #endif + void fetchSessionStorage(CompletionHandler>&&)>&&); + void restoreSessionStorage(HashMap>&&, CompletionHandler&&); + private: void getWebCryptoMasterKey(CompletionHandler>&&)>&&); WebPageProxy(PageClient&, WebProcessProxy&, Ref&&); diff --git a/Source/WebKit/UIProcess/WebsiteData/WebsiteDataStore.cpp b/Source/WebKit/UIProcess/WebsiteData/WebsiteDataStore.cpp index faa68024d2edf..ca63f2137b0bb 100644 --- a/Source/WebKit/UIProcess/WebsiteData/WebsiteDataStore.cpp +++ b/Source/WebKit/UIProcess/WebsiteData/WebsiteDataStore.cpp @@ -2718,7 +2718,10 @@ void WebsiteDataStore::setRestrictedOpenerTypeForDomainForTesting(const WebCore: void WebsiteDataStore::fetchLocalStorage(CompletionHandler>&&)>&& completionHandler) { - protectedNetworkProcess()->fetchLocalStorage(m_sessionID, WTFMove(completionHandler)); + if (RefPtr networkProcess = networkProcessIfExists()) + networkProcess->fetchLocalStorage(m_sessionID, WTFMove(completionHandler)); + else + completionHandler({ }); } void WebsiteDataStore::restoreLocalStorage(HashMap>&& localStorage, CompletionHandler&& completionHandler) diff --git a/Tools/TestWebKitAPI/SourcesCocoa.txt b/Tools/TestWebKitAPI/SourcesCocoa.txt index c733f3fef7469..3e350bb6c5425 100644 --- a/Tools/TestWebKitAPI/SourcesCocoa.txt +++ b/Tools/TestWebKitAPI/SourcesCocoa.txt @@ -236,6 +236,7 @@ Tests/WebKitCocoa/ResponsivenessTimerDoesntFireEarly.mm Tests/WebKitCocoa/RestoreLocalStorage.mm Tests/WebKitCocoa/RestoreScrollPosition.mm Tests/WebKitCocoa/RestoreSessionStateWithoutNavigation.mm +Tests/WebKitCocoa/RestoreSessionStorage.mm Tests/WebKitCocoa/RunOpenPanel.mm Tests/WebKitCocoa/RunScriptAfterDocumentLoad.mm Tests/WebKitCocoa/SafeBrowsing.mm diff --git a/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj b/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj index d246fdcc60d82..4aeda23360f09 100644 --- a/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj +++ b/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj @@ -3018,6 +3018,7 @@ 7B2739F22632AB640040F182 /* ThreadAssertionsTest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ThreadAssertionsTest.cpp; sourceTree = ""; }; 7B2950DA2972AFDD008CC225 /* StreamConnectionEncoderTests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StreamConnectionEncoderTests.cpp; sourceTree = ""; }; 7B397C0528BE0EAD00239202 /* ConnectionTests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ConnectionTests.cpp; sourceTree = ""; }; + 7B41A8302D08CD9400CE56F9 /* RestoreSessionStorage.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RestoreSessionStorage.mm; sourceTree = ""; }; 7B52E8642A2A1A2200A3251F /* ThreadSafeObjectHeapTests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ThreadSafeObjectHeapTests.cpp; sourceTree = ""; }; 7B636FC129700F0700F3670F /* StreamConnectionWorkQueueTests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StreamConnectionWorkQueueTests.cpp; sourceTree = ""; }; 7B7392E128F849EC007297FC /* MessageSenderTests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MessageSenderTests.cpp; sourceTree = ""; }; @@ -4458,6 +4459,7 @@ 7BE962542CF015FA00D7C11F /* RestoreLocalStorage.mm */, 46EBD846237231E600223A6E /* RestoreScrollPosition.mm */, 5CCB10DE2134579D00AC5AF0 /* RestoreSessionStateWithoutNavigation.mm */, + 7B41A8302D08CD9400CE56F9 /* RestoreSessionStorage.mm */, A180C0F91EE67DF000468F47 /* RunOpenPanel.mm */, F4D2986D20FEE7370092D636 /* RunScriptAfterDocumentLoad.mm */, 5CA985512113CB8C0057EB6B /* SafeBrowsing.mm */, diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/RestoreLocalStorage.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/RestoreLocalStorage.mm index 7585211af7e94..dedf6d40b5923 100644 --- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/RestoreLocalStorage.mm +++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/RestoreLocalStorage.mm @@ -117,12 +117,12 @@ static void testRestoreLocalStorage(RetainPtr websiteDataSto TEST(WebKit, RestoreLocalStorageFromPersistentDataStore) { - testRestoreLocalStorage(adoptNS([WKWebsiteDataStore defaultDataStore])); + testRestoreLocalStorage([WKWebsiteDataStore defaultDataStore]); } TEST(WebKit, RestoreLocalStorageFromEphemeralDataStore) { - testRestoreLocalStorage(adoptNS([WKWebsiteDataStore nonPersistentDataStore])); + testRestoreLocalStorage([WKWebsiteDataStore nonPersistentDataStore]); } @interface RestoreLocalStorageMessageHandler : NSObject @@ -246,10 +246,10 @@ static void testRestoreLocalStorageThirdPartyIFrame(RetainPtr +#import +#import +#import + +static void testRestoreSessionStorage(RetainPtr websiteDataStore) +{ + // Fetch Session Storage. + + // Clear the data store. + __block bool done = false; + [websiteDataStore removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^{ + done = true; + }]; + TestWebKitAPI::Util::run(&done); + done = false; + + // Create and load the WebView. + RetainPtr viewConfiguration = adoptNS([WKWebViewConfiguration new]); + [viewConfiguration setWebsiteDataStore:websiteDataStore.get()]; + RetainPtr webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectMake(0, 0, 100, 100) configuration:viewConfiguration.get()]); + RetainPtr navigationDelegate = adoptNS([[TestNavigationDelegate alloc] init]); + [webView setNavigationDelegate:navigationDelegate.get()]; + + [webView loadHTMLString:@"webkit.org" baseURL:[NSURL URLWithString:@"https://webkit.org"]]; + [navigationDelegate waitForDidFinishNavigation]; + + // Put data into session storage. + [webView evaluateJavaScript:@"sessionStorage.setItem('key', 'value');" completionHandler:^(id value, NSError *error) { + EXPECT_NULL(error); + done = true; + }]; + TestWebKitAPI::Util::run(&done); + done = false; + + // Fetch the session storage data. + __block RetainPtr sessionStorageData; + [webView _fetchDataOfTypes:_WKWebViewDataTypeSessionStorage completionHandler:^(NSData *data) { + EXPECT_NOT_NULL(data); + sessionStorageData = data; + done = true; + }]; + TestWebKitAPI::Util::run(&done); + done = false; + + // Restore Session Storage. + + // Clear the data store. + [websiteDataStore removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^{ + done = true; + }]; + TestWebKitAPI::Util::run(&done); + done = false; + + // Create a new WebView. + RetainPtr newWebView = adoptNS([[WKWebView alloc] initWithFrame:CGRectMake(0, 0, 100, 100) configuration:viewConfiguration.get()]); + [newWebView setNavigationDelegate:navigationDelegate.get()]; + + // Restore the session storage data. + [newWebView _restoreData:sessionStorageData.get() completionHandler:^(BOOL succeeded) { + EXPECT_TRUE(succeeded); + done = true; + }]; + TestWebKitAPI::Util::run(&done); + done = false; + + // Load the same page as before. + [newWebView loadHTMLString:@"webkit.org" baseURL:[NSURL URLWithString:@"https://webkit.org"]]; + [navigationDelegate waitForDidFinishNavigation]; + + // Check that the session storage data was correctly restored. + [newWebView evaluateJavaScript:@"sessionStorage.getItem('key');" completionHandler:^(id value, NSError *error) { + EXPECT_NULL(error); + EXPECT_TRUE([value isKindOfClass:[NSString class]]); + EXPECT_WK_STREQ(@"value", value); + done = true; + }]; + TestWebKitAPI::Util::run(&done); + done = false; +} + +TEST(WebKit, RestoreSessionStorageFromPersistentDataStore) +{ + testRestoreSessionStorage([WKWebsiteDataStore defaultDataStore]); +} + +TEST(WebKit, RestoreSessionStorageFromEphemeralDataStore) +{ + testRestoreSessionStorage([WKWebsiteDataStore nonPersistentDataStore]); +} + +@interface RestoreSessionStorageMessageHandler : NSObject +@end + +@implementation RestoreSessionStorageMessageHandler + +- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message +{ + receivedScriptMessage = true; + lastScriptMessage = message; +} + +@end + +static NSString *mainFrameString = @" \ +