Diff
Modified: trunk/Source/WebKit/ChangeLog (239079 => 239080)
--- trunk/Source/WebKit/ChangeLog 2018-12-11 19:27:26 UTC (rev 239079)
+++ trunk/Source/WebKit/ChangeLog 2018-12-11 20:24:06 UTC (rev 239080)
@@ -1,3 +1,106 @@
+2018-12-11 Chris Dumez <cdu...@apple.com>
+
+ PSON logic gets confused by concurrent decidePolicyForNavigationAction requests
+ https://bugs.webkit.org/show_bug.cgi?id=192482
+ <rdar://problem/46470145>
+
+ Reviewed by Antti Koivisto.
+
+ It is possible to get 2 parallel decidePolicyForNavigationAction() requests from the
+ WebProcess when a new load is started before responding to the existing policy
+ decision. This would lead to several issues with regards to PSON:
+ - We would decide to swap for the first policy decision and tell the WebProcess to
+ suspend. However, because the WebProcess issued a new decidePolicyForNavigationAction
+ since for the same frame, the previous one is invalidated and the WebProcess would
+ ignore our request to suspend.
+ - We would hit assertions later on because the navigation has been destroyed and yet
+ we're getting a didStartProvisionalLoad for it.
+ - swapToWebProcess() was asynchronous so that it would wait for the destination
+ SuspendedPage to finish suspending before asking it to unsuspend. This led to various
+ problems because anything can happen in the UIProcess while we're waiting for the
+ suspension (e.g. another load). Also, we may create the SuspendedPageProxy for
+ the current page too late and start getting IPC from the previous process about
+ the suspension load to about:blank.
+
+ To address these issues, the following design is now implemented:
+ - swapToWebProcess() is no longer asynchronous
+ - instead, WebProcessPool::processForNavigation() is now asynchronous. This is better
+ because at this point we have not yet told the WebProcess about the policy decision.
+ We already properly deal with the policy decision being made asynchronously. This
+ also allows us to choose another process if the SuspendedPage we'd like to use
+ failed to suspend.
+ - If the WebProcess receives a PolicyAction::Suspend but ignores it, have it send an
+ IPC to the UIProcess so that the SuspendedPageProxy knows about it. We then destroy
+ the SuspendedPageProxy and make sure it is not used.
+ - After the asynchronous process selection, if the navigation has been destroy, abort
+ the process-swap to avoid hitting assertions later on due to doing a load for a
+ destroyed navigation.
+
+ * UIProcess/SuspendedPageProxy.cpp:
+ (WebKit::SuspendedPageProxy::~SuspendedPageProxy):
+ Make sure m_whenReadyToConsume completion handler gets called if necessary if the
+ SuspendedPageProxy gets destroyed. We pass null so that the SuspendedPage is not
+ used.
+
+ (WebKit::SuspendedPageProxy::whenReadyToConsume):
+ Add whenReadyToConsume() utility method to get a completion handler called when
+ the SuspendedPageProxy is ready to be used. This basically means we have to wait
+ for the page to finish its about:blank suspension load. If the suspension fails
+ then we call the completion handler with null to indicate that the suspended
+ page is not usable.
+
+ (WebKit::SuspendedPageProxy::unsuspendAndConsume):
+ Rename unsuspend() to unsuspendAndConsume() and make it synchronous. This only gets
+ called after whenReadyToConsume()'s completion handler has been called and if we
+ do decide to use the SuspendedPageProxy. It tells the WebProcess to unsuspend and
+ removes the SuspendedPageProxy from the WebProcessPool.
+
+ (WebKit::SuspendedPageProxy::didFinishLoad):
+ rename m_finishedSuspendingHandler to m_whenReadyToConsume.
+
+ (WebKit::SuspendedPageProxy::didFailToSuspend):
+ Add new didFailToSuspend() that gets called when the WebProcess sends us an IPC telling
+ us it ignored our request to suspend. We then call m_whenReadyToConsume completion
+ handler with null and destroy the SuspendedPageProxy.
+
+ (WebKit::SuspendedPageProxy::didReceiveMessage):
+ * UIProcess/SuspendedPageProxy.h:
+
+ * UIProcess/WebNavigationState.h:
+ (WebKit::WebNavigationState::hasNavigation const):
+ Add utility function to query if a navigation is still valid.
+
+ * UIProcess/WebPageProxy.cpp:
+ (WebKit::WebPageProxy::swapToWebProcess):
+ Update method so that it is no longer asynchronous. Some of its code was also moved to
+ continueNavigationInNewProcess() for clarity.
+
+ (WebKit::WebPageProxy::receivedNavigationPolicyDecision):
+ Deal with WebProcessPool::processForNavigation() now being asynchronous.
+
+ (WebKit::WebPageProxy::continueNavigationInNewProcess):
+ Update code now that swapToWebProcess() is no longer asynchronous. Some of the swapToWebProcess()
+ code was also moved here for clarity.
+
+ (WebKit::WebPageProxy::didFailToSuspendAfterProcessSwap):
+
+ * UIProcess/WebPageProxy.h:
+ * UIProcess/WebPageProxy.messages.in:
+ Add new DidFailToSuspendAfterProcessSwap IPC message.
+
+ * UIProcess/WebProcessPool.cpp:
+ (WebKit::WebProcessPool::processForNavigation):
+ (WebKit::WebProcessPool::processForNavigationInternal):
+ * UIProcess/WebProcessPool.h:
+ Make asynchronous. If we decide to use a SuspendedPageProxy's process, then call
+ whenReadyToConsume() on it to wait for it to suspend. If the SuspendedPageProxy
+ fails to suspend use a new process instead.
+
+ * WebProcess/WebPage/WebFrame.cpp:
+ (WebKit::WebFrame::didReceivePolicyDecision):
+ If we ignore the policy decision and the decision was "Suspend", send the DidFailToSuspendAfterProcessSwap
+ IPC to the UIProcess so that the SuspendedPageProxy knows about it.
+
2018-12-11 Andy Estes <aes...@apple.com>
Introduce makeBlockPtr for lambdas
Modified: trunk/Source/WebKit/UIProcess/SuspendedPageProxy.cpp (239079 => 239080)
--- trunk/Source/WebKit/UIProcess/SuspendedPageProxy.cpp 2018-12-11 19:27:26 UTC (rev 239079)
+++ trunk/Source/WebKit/UIProcess/SuspendedPageProxy.cpp 2018-12-11 20:24:06 UTC (rev 239080)
@@ -91,6 +91,9 @@
SuspendedPageProxy::~SuspendedPageProxy()
{
+ if (m_readyToUnsuspendHandler)
+ m_readyToUnsuspendHandler(nullptr);
+
if (!m_isSuspended)
return;
@@ -106,22 +109,25 @@
});
}
-void SuspendedPageProxy::unsuspend(CompletionHandler<void()>&& completionHandler)
+void SuspendedPageProxy::waitUntilReadyToUnsuspend(CompletionHandler<void(SuspendedPageProxy*)>&& completionHandler)
{
+ if (m_readyToUnsuspendHandler)
+ m_readyToUnsuspendHandler(nullptr);
+
+ if (!m_finishedSuspending)
+ m_readyToUnsuspendHandler = WTFMove(completionHandler);
+ else
+ completionHandler(this);
+}
+
+void SuspendedPageProxy::unsuspend()
+{
ASSERT(m_isSuspended);
+ ASSERT(m_finishedSuspending);
- auto doUnsuspend = [this, completionHandler = WTFMove(completionHandler)]() mutable {
- m_isSuspended = false;
- m_process->removeMessageReceiver(Messages::WebPageProxy::messageReceiverName(), m_page.pageID());
- m_process->send(Messages::WebPage::SetIsSuspended(false), m_page.pageID());
- completionHandler();
- };
-
- if (!m_finishedSuspending) {
- ASSERT(!m_finishedSuspendingHandler);
- m_finishedSuspendingHandler = WTFMove(doUnsuspend);
- } else
- doUnsuspend();
+ m_isSuspended = false;
+ m_process->removeMessageReceiver(Messages::WebPageProxy::messageReceiverName(), m_page.pageID());
+ m_process->send(Messages::WebPage::SetIsSuspended(false), m_page.pageID());
}
void SuspendedPageProxy::didFinishLoad()
@@ -136,10 +142,19 @@
m_suspensionToken = nullptr;
#endif
- if (auto finishedSuspendingHandler = WTFMove(m_finishedSuspendingHandler))
- finishedSuspendingHandler();
+ if (m_readyToUnsuspendHandler)
+ m_readyToUnsuspendHandler(this);
}
+void SuspendedPageProxy::didFailToSuspend()
+{
+ // We are unusable due to failure to suspend so remove ourselves from the WebProcessPool.
+ auto protectedThis = m_process->processPool().takeSuspendedPage(*this);
+
+ if (m_readyToUnsuspendHandler)
+ m_readyToUnsuspendHandler(nullptr);
+}
+
void SuspendedPageProxy::didReceiveMessage(IPC::Connection&, IPC::Decoder& decoder)
{
ASSERT(decoder.messageReceiverName() == Messages::WebPageProxy::messageReceiverName());
@@ -148,6 +163,12 @@
didFinishLoad();
return;
}
+
+ if (decoder.messageName() == Messages::WebPageProxy::DidFailToSuspendAfterProcessSwap::name()) {
+ didFailToSuspend();
+ return;
+ }
+
#if !LOG_DISABLED
if (!messageNamesToIgnoreWhileSuspended().contains(decoder.messageName()))
LOG(ProcessSwapping, "SuspendedPageProxy received unexpected WebPageProxy message '%s'", decoder.messageName().toString().data());
Modified: trunk/Source/WebKit/UIProcess/SuspendedPageProxy.h (239079 => 239080)
--- trunk/Source/WebKit/UIProcess/SuspendedPageProxy.h 2018-12-11 19:27:26 UTC (rev 239079)
+++ trunk/Source/WebKit/UIProcess/SuspendedPageProxy.h 2018-12-11 20:24:06 UTC (rev 239080)
@@ -47,7 +47,8 @@
uint64_t mainFrameID() const { return m_mainFrameID; }
const String& registrableDomain() const { return m_registrableDomain; }
- void unsuspend(CompletionHandler<void()>&&);
+ void waitUntilReadyToUnsuspend(CompletionHandler<void(SuspendedPageProxy*)>&&);
+ void unsuspend();
#if !LOG_DISABLED
const char* loggingString() const;
@@ -55,6 +56,7 @@
private:
void didFinishLoad();
+ void didFailToSuspend();
// IPC::MessageReceiver
void didReceiveMessage(IPC::Connection&, IPC::Decoder&) final;
@@ -68,7 +70,7 @@
bool m_isSuspended { true };
bool m_finishedSuspending { false };
- CompletionHandler<void()> m_finishedSuspendingHandler;
+ CompletionHandler<void(SuspendedPageProxy*)> m_readyToUnsuspendHandler;
#if PLATFORM(IOS_FAMILY)
ProcessThrottler::BackgroundActivityToken m_suspensionToken;
#endif
Modified: trunk/Source/WebKit/UIProcess/WebNavigationState.h (239079 => 239080)
--- trunk/Source/WebKit/UIProcess/WebNavigationState.h 2018-12-11 19:27:26 UTC (rev 239079)
+++ trunk/Source/WebKit/UIProcess/WebNavigationState.h 2018-12-11 20:24:06 UTC (rev 239080)
@@ -54,6 +54,7 @@
Ref<API::Navigation> createReloadNavigation();
Ref<API::Navigation> createLoadDataNavigation(std::unique_ptr<API::SubstituteData>&&);
+ bool hasNavigation(uint64_t navigationID) const { return m_navigations.contains(navigationID); }
API::Navigation* navigation(uint64_t navigationID);
RefPtr<API::Navigation> takeNavigation(uint64_t navigationID);
void didDestroyNavigation(uint64_t navigationID);
Modified: trunk/Source/WebKit/UIProcess/WebPageProxy.cpp (239079 => 239080)
--- trunk/Source/WebKit/UIProcess/WebPageProxy.cpp 2018-12-11 19:27:26 UTC (rev 239079)
+++ trunk/Source/WebKit/UIProcess/WebPageProxy.cpp 2018-12-11 20:24:06 UTC (rev 239080)
@@ -764,52 +764,30 @@
return true;
}
-void WebPageProxy::swapToWebProcess(Ref<WebProcessProxy>&& process, API::Navigation& navigation, std::optional<uint64_t> mainFrameIDInPreviousProcess, ProcessSwapRequestedByClient processSwapRequestedByClient, CompletionHandler<void(bool)>&& completionHandler)
+void WebPageProxy::swapToWebProcess(Ref<WebProcessProxy>&& process, std::unique_ptr<SuspendedPageProxy>&& destinationSuspendedPage, ShouldDelayAttachingDrawingArea shouldDelayAttachingDrawingArea)
{
ASSERT(!m_isClosed);
RELEASE_LOG_IF_ALLOWED(Process, "%p WebPageProxy::swapToWebProcess\n", this);
- std::unique_ptr<SuspendedPageProxy> destinationSuspendedPage;
- if (auto* backForwardListItem = navigation.targetItem()) {
- if (backForwardListItem->suspendedPage() && &backForwardListItem->suspendedPage()->process() == process.ptr()) {
- destinationSuspendedPage = this->process().processPool().takeSuspendedPage(*backForwardListItem->suspendedPage());
- ASSERT(destinationSuspendedPage);
- }
+ m_process = WTFMove(process);
+ m_isValid = true;
+
+ // If we are reattaching to a SuspendedPage, then the WebProcess' WebPage already exists and
+ // WebPageProxy::didCreateMainFrame() will not be called to initialize m_mainFrame. In such
+ // case, we need to initialize m_mainFrame to reflect the fact the the WebProcess' WebPage
+ // already exists and already has a main frame.
+ if (destinationSuspendedPage) {
+ ASSERT(!m_mainFrame);
+ ASSERT(&destinationSuspendedPage->process() == m_process.ptr());
+ destinationSuspendedPage->unsuspend();
+ m_mainFrame = WebFrameProxy::create(*this, destinationSuspendedPage->mainFrameID());
+ m_process->frameCreated(destinationSuspendedPage->mainFrameID(), *m_mainFrame);
}
- auto* destinationSuspendedPagePtr = destinationSuspendedPage.get();
- auto doSwap = [this, protectedThis = makeRef(*this), navigation = makeRef(navigation), process = WTFMove(process), mainFrameIDInPreviousProcess, processSwapRequestedByClient, destinationSuspendedPage = WTFMove(destinationSuspendedPage), completionHandler = WTFMove(completionHandler)]() mutable {
- processDidTerminate(ProcessTerminationReason::NavigationSwap);
+ m_process->addExistingWebPage(*this, m_pageID, WebProcessProxy::BeginsUsingDataStore::No);
+ m_process->addMessageReceiver(Messages::WebPageProxy::messageReceiverName(), m_pageID, *this);
- m_process->removeMessageReceiver(Messages::WebPageProxy::messageReceiverName(), m_pageID);
- bool didSuspendPreviousPage = suspendCurrentPageIfPossible(navigation, mainFrameIDInPreviousProcess, processSwapRequestedByClient);
- m_process->removeWebPage(*this, m_pageID, WebProcessProxy::EndsUsingDataStore::No);
-
- m_process = WTFMove(process);
- m_isValid = true;
-
- // If we are reattaching to a SuspendedPage, then the WebProcess' WebPage already exists and
- // WebPageProxy::didCreateMainFrame() will not be called to initialize m_mainFrame. In such
- // case, we need to initialize m_mainFrame to reflect the fact the the WebProcess' WebPage
- // already exists and already has a main frame.
- if (destinationSuspendedPage) {
- ASSERT(!m_mainFrame);
- ASSERT(&destinationSuspendedPage->process() == m_process.ptr());
- m_mainFrame = WebFrameProxy::create(*this, destinationSuspendedPage->mainFrameID());
- m_process->frameCreated(destinationSuspendedPage->mainFrameID(), *m_mainFrame);
- }
-
- m_process->addExistingWebPage(*this, m_pageID, WebProcessProxy::BeginsUsingDataStore::No);
- m_process->addMessageReceiver(Messages::WebPageProxy::messageReceiverName(), m_pageID, *this);
-
- finishAttachingToWebProcess(didSuspendPreviousPage ? ShouldDelayAttachingDrawingArea::Yes : ShouldDelayAttachingDrawingArea::No);
- completionHandler(didSuspendPreviousPage);
- };
-
- if (destinationSuspendedPagePtr)
- destinationSuspendedPagePtr->unsuspend(WTFMove(doSwap));
- else
- doSwap();
+ finishAttachingToWebProcess(shouldDelayAttachingDrawingArea);
}
void WebPageProxy::finishAttachingToWebProcess(ShouldDelayAttachingDrawingArea shouldDelayAttachingDrawingArea)
@@ -2635,11 +2613,18 @@
changeWebsiteDataStore(policies->websiteDataStore()->websiteDataStore());
}
- Ref<WebProcessProxy> processForNavigation = process();
- if (policyAction == PolicyAction::Use && frame.isMainFrame()) {
- String reason;
- processForNavigation = process().processPool().processForNavigation(*this, *navigation, processSwapRequestedByClient, reason);
- ASSERT(!reason.isNull());
+ if (policyAction != PolicyAction::Use || !frame.isMainFrame() || !navigation) {
+ receivedPolicyDecision(policyAction, navigation, WTFMove(data), WTFMove(sender));
+ return;
+ }
+
+ process().processPool().processForNavigation(*this, *navigation, processSwapRequestedByClient, [this, protectedThis = makeRef(*this), policyAction, navigation = makeRef(*navigation), data = "" sender = WTFMove(sender), processSwapRequestedByClient](Ref<WebProcessProxy>&& processForNavigation, SuspendedPageProxy* destinationSuspendedPage, const String& reason) mutable {
+ // If the navigation has been destroyed, then no need to proceed.
+ if (isClosed() || !navigationState().hasNavigation(navigation->navigationID())) {
+ receivedPolicyDecision(policyAction, navigation.ptr(), WTFMove(data), WTFMove(sender));
+ return;
+ }
+
if (processForNavigation.ptr() != &process()) {
policyAction = PolicyAction::Suspend;
RELEASE_LOG_IF_ALLOWED(ProcessSwapping, "%p - WebPageProxy::decidePolicyForNavigationAction, swapping process %i with process %i for navigation, reason: %{public}s", this, processIdentifier(), processForNavigation->processIdentifier(), reason.utf8().data());
@@ -2646,12 +2631,12 @@
LOG(ProcessSwapping, "(ProcessSwapping) Switching from process %i to new process (%i) for navigation %" PRIu64 " '%s'", processIdentifier(), processForNavigation->processIdentifier(), navigation->navigationID(), navigation->loggingString());
} else
RELEASE_LOG_IF_ALLOWED(ProcessSwapping, "%p - WebPageProxy::decidePolicyForNavigationAction, keep using process %i for navigation, reason: %{public}s", this, processIdentifier(), reason.utf8().data());
- }
- receivedPolicyDecision(policyAction, navigation, WTFMove(data), WTFMove(sender));
+ receivedPolicyDecision(policyAction, navigation.ptr(), WTFMove(data), WTFMove(sender));
- if (processForNavigation.ptr() != &process())
- continueNavigationInNewProcess(*navigation, WTFMove(processForNavigation), processSwapRequestedByClient);
+ if (processForNavigation.ptr() != &process())
+ continueNavigationInNewProcess(navigation, destinationSuspendedPage ? process().processPool().takeSuspendedPage(*destinationSuspendedPage) : nullptr, WTFMove(processForNavigation), processSwapRequestedByClient);
+ });
}
void WebPageProxy::receivedPolicyDecision(PolicyAction action, API::Navigation* navigation, std::optional<WebsitePoliciesData>&& websitePolicies, Ref<PolicyDecisionSender>&& sender)
@@ -2686,7 +2671,7 @@
sender->send(action, navigation ? navigation->navigationID() : 0, downloadID, WTFMove(websitePolicies));
}
-void WebPageProxy::continueNavigationInNewProcess(API::Navigation& navigation, Ref<WebProcessProxy>&& process, ProcessSwapRequestedByClient processSwapRequestedByClient)
+void WebPageProxy::continueNavigationInNewProcess(API::Navigation& navigation, std::unique_ptr<SuspendedPageProxy>&& suspendedPageProxy, Ref<WebProcessProxy>&& process, ProcessSwapRequestedByClient processSwapRequestedByClient)
{
LOG(Loading, "Continuing navigation %" PRIu64 " '%s' in a new web process", navigation.navigationID(), navigation.loggingString());
@@ -2696,75 +2681,90 @@
ASSERT(m_process.ptr() != process.ptr());
- swapToWebProcess(WTFMove(process), navigation, mainFrameIDInPreviousProcess, processSwapRequestedByClient, [this, protectedThis = makeRef(*this), navigation = makeRef(navigation), previousProcess = WTFMove(previousProcess), mainFrameIDInPreviousProcess, mainFrameURL = WTFMove(mainFrameURL)](bool didSuspendPreviousPage) mutable {
- if (auto* item = navigation->targetItem()) {
- LOG(Loading, "WebPageProxy %p continueNavigationInNewProcess to back item URL %s", this, item->url().utf8().data());
+ processDidTerminate(ProcessTerminationReason::NavigationSwap);
- auto transaction = m_pageLoadState.transaction();
- m_pageLoadState.setPendingAPIRequestURL(transaction, item->url());
+ m_process->removeMessageReceiver(Messages::WebPageProxy::messageReceiverName(), m_pageID);
+ bool didSuspendPreviousPage = suspendCurrentPageIfPossible(navigation, mainFrameIDInPreviousProcess, processSwapRequestedByClient);
+ m_process->removeWebPage(*this, m_pageID, WebProcessProxy::EndsUsingDataStore::No);
- auto itemStates = m_backForwardList->filteredItemStates([this, targetItem = item](WebBackForwardListItem& item) {
- if (auto* page = item.suspendedPage()) {
- if (&page->process() == m_process.ptr())
- return false;
- }
- return &item != targetItem;
- });
- m_process->send(Messages::WebPage::UpdateBackForwardListForReattach(WTFMove(itemStates)), m_pageID);
- m_process->send(Messages::WebPage::GoToBackForwardItem(navigation->navigationID(), item->itemID(), *navigation->backForwardFrameLoadType(), ShouldTreatAsContinuingLoad::Yes), m_pageID);
- m_process->responsivenessTimer().start();
+ swapToWebProcess(WTFMove(process), WTFMove(suspendedPageProxy), didSuspendPreviousPage ? ShouldDelayAttachingDrawingArea::Yes : ShouldDelayAttachingDrawingArea::No);
- return;
- }
+ if (auto* item = navigation.targetItem()) {
+ LOG(Loading, "WebPageProxy %p continueNavigationInNewProcess to back item URL %s", this, item->url().utf8().data());
- if (m_backForwardList->currentItem() && (navigation->lockBackForwardList() == LockBackForwardList::Yes || navigation->lockHistory() == LockHistory::Yes)) {
- // If WebCore is supposed to lock the history for this load, then the new process needs to know about the current history item so it can update
- // it instead of creating a new one.
- m_process->send(Messages::WebPage::SetCurrentHistoryItemForReattach(m_backForwardList->currentItem()->itemState()), m_pageID);
- }
+ auto transaction = m_pageLoadState.transaction();
+ m_pageLoadState.setPendingAPIRequestURL(transaction, item->url());
- // FIXME: Work out timing of responding with the last policy delegate, etc
- ASSERT(!navigation->currentRequest().isEmpty());
- if (auto& substituteData = navigation->substituteData())
- loadDataWithNavigation(navigation, { substituteData->content.data(), substituteData->content.size() }, substituteData->MIMEType, substituteData->encoding, substituteData->baseURL, substituteData->userData.get());
- else
- loadRequestWithNavigation(navigation, ResourceRequest { navigation->currentRequest() }, WebCore::ShouldOpenExternalURLsPolicy::ShouldAllowExternalSchemes, nullptr, ShouldTreatAsContinuingLoad::Yes);
+ auto itemStates = m_backForwardList->filteredItemStates([this, targetItem = item](WebBackForwardListItem& item) {
+ if (auto* page = item.suspendedPage()) {
+ if (&page->process() == m_process.ptr())
+ return false;
+ }
+ return &item != targetItem;
+ });
+ m_process->send(Messages::WebPage::UpdateBackForwardListForReattach(WTFMove(itemStates)), m_pageID);
+ m_process->send(Messages::WebPage::GoToBackForwardItem(navigation.navigationID(), item->itemID(), *navigation.backForwardFrameLoadType(), ShouldTreatAsContinuingLoad::Yes), m_pageID);
+ m_process->responsivenessTimer().start();
- ASSERT(!m_mainFrame);
- m_mainFrameCreationHandler = [this, protectedThis = WTFMove(protectedThis), navigationID = navigation->navigationID(), request = navigation->currentRequest(), mainFrameURL, isServerRedirect = navigation->currentRequestIsRedirect()]() mutable {
- ASSERT(m_mainFrame);
- // This navigation was destroyed so no need to notify of redirect.
- if (!navigationState().navigation(navigationID))
- return;
+ return;
+ }
- // Restore the main frame's committed URL as some clients may rely on it until the next load is committed.
- m_mainFrame->frameLoadState().setURL(mainFrameURL);
+ if (m_backForwardList->currentItem() && (navigation.lockBackForwardList() == LockBackForwardList::Yes || navigation.lockHistory() == LockHistory::Yes)) {
+ // If WebCore is supposed to lock the history for this load, then the new process needs to know about the current history item so it can update
+ // it instead of creating a new one.
+ m_process->send(Messages::WebPage::SetCurrentHistoryItemForReattach(m_backForwardList->currentItem()->itemState()), m_pageID);
+ }
- // Normally, notification of a server redirect comes from the WebContent process.
- // If we are process swapping in response to a server redirect then that notification will not come from the new WebContent process.
- // In this case we have the UIProcess synthesize the redirect notification at the appropriate time.
- if (isServerRedirect) {
- m_mainFrame->frameLoadState().didStartProvisionalLoad(request.url());
- didReceiveServerRedirectForProvisionalLoadForFrame(m_mainFrame->frameID(), navigationID, WTFMove(request), { });
- }
- };
+ // FIXME: Work out timing of responding with the last policy delegate, etc
+ ASSERT(!navigation.currentRequest().isEmpty());
+ if (auto& substituteData = navigation.substituteData())
+ loadDataWithNavigation(navigation, { substituteData->content.data(), substituteData->content.size() }, substituteData->MIMEType, substituteData->encoding, substituteData->baseURL, substituteData->userData.get());
+ else
+ loadRequestWithNavigation(navigation, ResourceRequest { navigation.currentRequest() }, WebCore::ShouldOpenExternalURLsPolicy::ShouldAllowExternalSchemes, nullptr, ShouldTreatAsContinuingLoad::Yes);
- bool isInitialNavigationInNewWindow = openedByDOM() && !hasCommittedAnyProvisionalLoads();
- if (!m_process->processPool().configuration().processSwapsOnWindowOpenWithOpener() || !isInitialNavigationInNewWindow || !mainFrameIDInPreviousProcess) {
- // There is no way we'll be able to return to the page in the previous page so close it.
- if (!didSuspendPreviousPage)
- previousProcess->send(Messages::WebPage::Close(), m_pageID);
+ ASSERT(!m_mainFrame);
+ m_mainFrameCreationHandler = [this, weakThis = makeWeakPtr(*this), navigationID = navigation.navigationID(), request = navigation.currentRequest(), mainFrameURL, isServerRedirect = navigation.currentRequestIsRedirect()]() mutable {
+ if (!weakThis)
return;
+
+ ASSERT(m_mainFrame);
+ // This navigation was destroyed so no need to notify of redirect.
+ if (!navigationState().navigation(navigationID))
+ return;
+
+ // Restore the main frame's committed URL as some clients may rely on it until the next load is committed.
+ m_mainFrame->frameLoadState().setURL(mainFrameURL);
+
+ // Normally, notification of a server redirect comes from the WebContent process.
+ // If we are process swapping in response to a server redirect then that notification will not come from the new WebContent process.
+ // In this case we have the UIProcess synthesize the redirect notification at the appropriate time.
+ if (isServerRedirect) {
+ m_mainFrame->frameLoadState().didStartProvisionalLoad(request.url());
+ didReceiveServerRedirectForProvisionalLoadForFrame(m_mainFrame->frameID(), navigationID, WTFMove(request), { });
}
+ };
- m_mainFrameWindowCreationHandler = [this, previousProcess = WTFMove(previousProcess), mainFrameIDInPreviousProcess = *mainFrameIDInPreviousProcess](const GlobalWindowIdentifier& windowIdentifier) {
- ASSERT(m_mainFrame);
- GlobalFrameIdentifier navigatedFrameIdentifierInNewProcess { pageID(), m_mainFrame->frameID() };
- previousProcess->send(Messages::WebPage::FrameBecameRemote(mainFrameIDInPreviousProcess, navigatedFrameIdentifierInNewProcess, windowIdentifier), pageID());
- };
- });
+ bool isInitialNavigationInNewWindow = openedByDOM() && !hasCommittedAnyProvisionalLoads();
+ if (!m_process->processPool().configuration().processSwapsOnWindowOpenWithOpener() || !isInitialNavigationInNewWindow || !mainFrameIDInPreviousProcess) {
+ // There is no way we'll be able to return to the page in the previous page so close it.
+ if (!didSuspendPreviousPage)
+ previousProcess->send(Messages::WebPage::Close(), m_pageID);
+ return;
+ }
+
+ m_mainFrameWindowCreationHandler = [this, previousProcess = WTFMove(previousProcess), mainFrameIDInPreviousProcess = *mainFrameIDInPreviousProcess](const GlobalWindowIdentifier& windowIdentifier) {
+ ASSERT(m_mainFrame);
+ GlobalFrameIdentifier navigatedFrameIdentifierInNewProcess { pageID(), m_mainFrame->frameID() };
+ previousProcess->send(Messages::WebPage::FrameBecameRemote(mainFrameIDInPreviousProcess, navigatedFrameIdentifierInNewProcess, windowIdentifier), pageID());
+ };
}
+NO_RETURN_DUE_TO_ASSERT void WebPageProxy::didFailToSuspendAfterProcessSwap()
+{
+ // Only the SuspendedPageProxy should be getting this call.
+ ASSERT_NOT_REACHED();
+}
+
void WebPageProxy::setUserAgent(String&& userAgent)
{
if (m_userAgent == userAgent)
Modified: trunk/Source/WebKit/UIProcess/WebPageProxy.h (239079 => 239080)
--- trunk/Source/WebKit/UIProcess/WebPageProxy.h 2018-12-11 19:27:26 UTC (rev 239079)
+++ trunk/Source/WebKit/UIProcess/WebPageProxy.h 2018-12-11 20:24:06 UTC (rev 239080)
@@ -1563,7 +1563,8 @@
void setCanShortCircuitHorizontalWheelEvents(bool canShortCircuitHorizontalWheelEvents) { m_canShortCircuitHorizontalWheelEvents = canShortCircuitHorizontalWheelEvents; }
void reattachToWebProcess();
- void swapToWebProcess(Ref<WebProcessProxy>&&, API::Navigation&, std::optional<uint64_t> mainFrameIDInPreviousProcess, ProcessSwapRequestedByClient, CompletionHandler<void(bool)>&&);
+ void swapToWebProcess(Ref<WebProcessProxy>&&, std::unique_ptr<SuspendedPageProxy>&&, ShouldDelayAttachingDrawingArea);
+ void didFailToSuspendAfterProcessSwap();
void finishAttachingToWebProcess(ShouldDelayAttachingDrawingArea = ShouldDelayAttachingDrawingArea::No);
@@ -1883,7 +1884,7 @@
void reportPageLoadResult(const WebCore::ResourceError& = { });
- void continueNavigationInNewProcess(API::Navigation&, Ref<WebProcessProxy>&&, ProcessSwapRequestedByClient);
+ void continueNavigationInNewProcess(API::Navigation&, std::unique_ptr<SuspendedPageProxy>&&, Ref<WebProcessProxy>&&, ProcessSwapRequestedByClient);
void setNeedsFontAttributes(bool);
void updateFontAttributesAfterEditorStateChange();
Modified: trunk/Source/WebKit/UIProcess/WebPageProxy.messages.in (239079 => 239080)
--- trunk/Source/WebKit/UIProcess/WebPageProxy.messages.in 2018-12-11 19:27:26 UTC (rev 239079)
+++ trunk/Source/WebKit/UIProcess/WebPageProxy.messages.in 2018-12-11 20:24:06 UTC (rev 239080)
@@ -504,6 +504,8 @@
RequestPointerUnlock()
#endif
+ DidFailToSuspendAfterProcessSwap()
+
ImageOrMediaDocumentSizeChanged(WebCore::IntSize newSize)
UseFixedLayoutDidChange(bool useFixedLayout)
Modified: trunk/Source/WebKit/UIProcess/WebProcessPool.cpp (239079 => 239080)
--- trunk/Source/WebKit/UIProcess/WebProcessPool.cpp 2018-12-11 19:27:26 UTC (rev 239079)
+++ trunk/Source/WebKit/UIProcess/WebProcessPool.cpp 2018-12-11 20:24:06 UTC (rev 239080)
@@ -2112,32 +2112,32 @@
m_swappedProcessesPerRegistrableDomain.remove(registrableDomain);
}
-Ref<WebProcessProxy> WebProcessPool::processForNavigation(WebPageProxy& page, const API::Navigation& navigation, ProcessSwapRequestedByClient processSwapRequestedByClient, String& reason)
+void WebProcessPool::processForNavigation(WebPageProxy& page, const API::Navigation& navigation, ProcessSwapRequestedByClient processSwapRequestedByClient, CompletionHandler<void(Ref<WebProcessProxy>&&, SuspendedPageProxy*, const String&)>&& completionHandler)
{
- auto process = processForNavigationInternal(page, navigation, processSwapRequestedByClient, reason);
+ processForNavigationInternal(page, navigation, processSwapRequestedByClient, [this, page = makeRefPtr(page), navigation = makeRef(navigation), processSwapRequestedByClient, completionHandler = WTFMove(completionHandler)](Ref<WebProcessProxy>&& process, SuspendedPageProxy* suspendedPage, const String& reason) mutable {
+ // We are process-swapping so automatic process prewarming would be beneficial if the client has not explicitly enabled / disabled it.
+ bool doingAnAutomaticProcessSwap = processSwapRequestedByClient == ProcessSwapRequestedByClient::No && process.ptr() != &page->process();
+ if (doingAnAutomaticProcessSwap && !configuration().wasAutomaticProcessWarmingSetByClient() && !configuration().clientWouldBenefitFromAutomaticProcessPrewarming()) {
+ RELEASE_LOG(PerformanceLogging, "Automatically turning on process prewarming because the client would benefit from it");
+ configuration().setClientWouldBenefitFromAutomaticProcessPrewarming(true);
+ }
- // We are process-swapping so automatic process prewarming would be beneficial if the client has not explicitly enabled / disabled it.
- bool doingAnAutomaticProcessSwap = processSwapRequestedByClient == ProcessSwapRequestedByClient::No && process.ptr() != &page.process();
- if (doingAnAutomaticProcessSwap && !configuration().wasAutomaticProcessWarmingSetByClient() && !configuration().clientWouldBenefitFromAutomaticProcessPrewarming()) {
- RELEASE_LOG(PerformanceLogging, "Automatically turning on process prewarming because the client would benefit from it");
- configuration().setClientWouldBenefitFromAutomaticProcessPrewarming(true);
- }
+ if (m_configuration->alwaysKeepAndReuseSwappedProcesses() && process.ptr() != &page->process()) {
+ static std::once_flag onceFlag;
+ std::call_once(onceFlag, [] {
+ WTFLogAlways("WARNING: The option to always keep swapped web processes alive is active. This is meant for debugging and testing only.");
+ });
- if (m_configuration->alwaysKeepAndReuseSwappedProcesses() && process.ptr() != &page.process()) {
- static std::once_flag onceFlag;
- std::call_once(onceFlag, [] {
- WTFLogAlways("WARNING: The option to always keep swapped web processes alive is active. This is meant for debugging and testing only.");
- });
+ addProcessToOriginCacheSet(*page);
- addProcessToOriginCacheSet(page);
+ LOG(ProcessSwapping, "(ProcessSwapping) Navigating from %s to %s, keeping around old process. Now holding on to old processes for %u origins.", page->currentURL().utf8().data(), navigation->currentRequest().url().string().utf8().data(), m_swappedProcessesPerRegistrableDomain.size());
+ }
- LOG(ProcessSwapping, "(ProcessSwapping) Navigating from %s to %s, keeping around old process. Now holding on to old processes for %u origins.", page.currentURL().utf8().data(), navigation.currentRequest().url().string().utf8().data(), m_swappedProcessesPerRegistrableDomain.size());
- }
-
- return process;
+ completionHandler(WTFMove(process), suspendedPage, reason);
+ });
}
-Ref<WebProcessProxy> WebProcessPool::processForNavigationInternal(WebPageProxy& page, const API::Navigation& navigation, ProcessSwapRequestedByClient processSwapRequestedByClient, String& reason)
+void WebProcessPool::processForNavigationInternal(WebPageProxy& page, const API::Navigation& navigation, ProcessSwapRequestedByClient processSwapRequestedByClient, CompletionHandler<void(Ref<WebProcessProxy>&&, SuspendedPageProxy*, const String&)>&& completionHandler)
{
auto& targetURL = navigation.currentRequest().url();
@@ -2150,44 +2150,37 @@
return createNewWebProcess(page.websiteDataStore());
};
- if (processSwapRequestedByClient == ProcessSwapRequestedByClient::Yes) {
- reason = "Process swap was requested by the client"_s;
- return createNewProcess();
- }
+ if (processSwapRequestedByClient == ProcessSwapRequestedByClient::Yes)
+ return completionHandler(createNewProcess(), nullptr, "Process swap was requested by the client"_s);
- if (!m_configuration->processSwapsOnNavigation()) {
- reason = "Feature is disabled"_s;
- return page.process();
- }
+ if (!m_configuration->processSwapsOnNavigation())
+ return completionHandler(page.process(), nullptr, "Feature is disabled"_s);
- if (m_automationSession) {
- reason = "An automation session is active"_s;
- return page.process();
- }
+ if (m_automationSession)
+ return completionHandler(page.process(), nullptr, "An automation session is active"_s);
if (!page.process().hasCommittedAnyProvisionalLoads()) {
- reason = "Process has not yet committed any provisional loads"_s;
tryPrewarmWithDomainInformation(page.process(), targetURL);
- return page.process();
+ return completionHandler(page.process(), nullptr, "Process has not yet committed any provisional loads"_s);
}
// FIXME: We should support process swap when a window has been opened via window.open() without 'noopener'.
// The issue is that the opener has a handle to the WindowProxy.
- if (navigation.openedByDOMWithOpener() && !m_configuration->processSwapsOnWindowOpenWithOpener()) {
- reason = "Browsing context been opened by DOM without 'noopener'"_s;
- return page.process();
- }
+ if (navigation.openedByDOMWithOpener() && !m_configuration->processSwapsOnWindowOpenWithOpener())
+ return completionHandler(page.process(), nullptr, "Browsing context been opened by DOM without 'noopener'"_s);
// FIXME: We should support process swap when a window has opened other windows via window.open.
- if (navigation.hasOpenedFrames()) {
- reason = "Browsing context has opened other windows"_s;
- return page.process();
- }
+ if (navigation.hasOpenedFrames())
+ return completionHandler(page.process(), nullptr, "Browsing context has opened other windows"_s);
if (auto* backForwardListItem = navigation.targetItem()) {
if (auto* suspendedPage = backForwardListItem->suspendedPage()) {
- reason = "Using target back/forward item's process"_s;
- return suspendedPage->process();
+ return suspendedPage->waitUntilReadyToUnsuspend([createNewProcess = WTFMove(createNewProcess), completionHandler = WTFMove(completionHandler)](SuspendedPageProxy* suspendedPage) mutable {
+ if (!suspendedPage)
+ return completionHandler(createNewProcess(), nullptr, "Using new process because target back/forward item's process failed to suspend"_s);
+ Ref<WebProcessProxy> process = suspendedPage->process();
+ completionHandler(WTFMove(process), suspendedPage, "Using target back/forward item's process"_s);
+ });
}
// If the target back/forward item and the current back/forward item originated
@@ -2196,17 +2189,13 @@
auto uiProcessIdentifier = Process::identifier();
// In case of session restore, the item's process identifier is the UIProcess' identifier, in which case we do not want to do this check
// or we'd never swap after a session restore.
- if (fromItem->itemID().processIdentifier == backForwardListItem->itemID().processIdentifier && backForwardListItem->itemID().processIdentifier != uiProcessIdentifier) {
- reason = "Source and target back/forward item originated in the same process"_s;
- return page.process();
- }
+ if (fromItem->itemID().processIdentifier == backForwardListItem->itemID().processIdentifier && backForwardListItem->itemID().processIdentifier != uiProcessIdentifier)
+ return completionHandler(page.process(), nullptr, "Source and target back/forward item originated in the same process"_s);
}
}
- if (navigation.treatAsSameOriginNavigation()) {
- reason = "The treatAsSameOriginNavigation flag is set"_s;
- return page.process();
- }
+ if (navigation.treatAsSameOriginNavigation())
+ return completionHandler(page.process(), nullptr, "The treatAsSameOriginNavigation flag is set"_s);
bool isInitialLoadInNewWindowOpenedByDOM = page.openedByDOM() && !page.hasCommittedAnyProvisionalLoads();
URL sourceURL;
@@ -2220,11 +2209,10 @@
RELEASE_LOG(ProcessSwapping, "Using related page %p's URL as source URL for process swap decision", page.configuration().relatedPage());
}
- if (!sourceURL.isValid() || !targetURL.isValid() || sourceURL.isEmpty() || sourceURL.protocolIsAbout() || registrableDomainsAreEqual(sourceURL, targetURL)) {
- reason = "Navigation is same-site"_s;
- return page.process();
- }
- reason = "Navigation is cross-site"_s;
+ if (!sourceURL.isValid() || !targetURL.isValid() || sourceURL.isEmpty() || sourceURL.protocolIsAbout() || registrableDomainsAreEqual(sourceURL, targetURL))
+ return completionHandler(page.process(), nullptr, "Navigation is same-site"_s);
+
+ String reason = "Navigation is cross-site"_s;
auto registrableDomain = toRegistrableDomain(targetURL);
if (m_configuration->alwaysKeepAndReuseSwappedProcesses()) {
@@ -2242,7 +2230,7 @@
return &suspendedPage->page() == &page && &suspendedPage->process() == process;
});
- return makeRef(*process);
+ return completionHandler(makeRef(*process), nullptr, reason);
}
}
}
@@ -2262,10 +2250,10 @@
if (&process->websiteDataStore() != &page.websiteDataStore())
process->send(Messages::WebProcess::AddWebsiteDataStore(page.websiteDataStore().parameters()), 0);
- return process;
+ return completionHandler(WTFMove(process), nullptr, reason);
}
- return createNewProcess();
+ return completionHandler(createNewProcess(), nullptr, reason);
}
void WebProcessPool::addSuspendedPage(std::unique_ptr<SuspendedPageProxy>&& suspendedPage)
Modified: trunk/Source/WebKit/UIProcess/WebProcessPool.h (239079 => 239080)
--- trunk/Source/WebKit/UIProcess/WebProcessPool.h 2018-12-11 19:27:26 UTC (rev 239079)
+++ trunk/Source/WebKit/UIProcess/WebProcessPool.h 2018-12-11 20:24:06 UTC (rev 239080)
@@ -445,7 +445,7 @@
BackgroundWebProcessToken backgroundWebProcessToken() const { return BackgroundWebProcessToken(m_backgroundWebProcessCounter.count()); }
#endif
- Ref<WebProcessProxy> processForNavigation(WebPageProxy&, const API::Navigation&, ProcessSwapRequestedByClient, String& reason);
+ void processForNavigation(WebPageProxy&, const API::Navigation&, ProcessSwapRequestedByClient, CompletionHandler<void(Ref<WebProcessProxy>&&, SuspendedPageProxy*, const String&)>&&);
// SuspendedPageProxy management.
void addSuspendedPage(std::unique_ptr<SuspendedPageProxy>&&);
@@ -479,7 +479,7 @@
void platformInitializeWebProcess(WebProcessCreationParameters&);
void platformInvalidateContext();
- Ref<WebProcessProxy> processForNavigationInternal(WebPageProxy&, const API::Navigation&, ProcessSwapRequestedByClient, String& reason);
+ void processForNavigationInternal(WebPageProxy&, const API::Navigation&, ProcessSwapRequestedByClient, CompletionHandler<void(Ref<WebProcessProxy>&&, SuspendedPageProxy*, const String&)>&&);
RefPtr<WebProcessProxy> tryTakePrewarmedProcess(WebsiteDataStore&);
Modified: trunk/Source/WebKit/WebProcess/WebPage/WebFrame.cpp (239079 => 239080)
--- trunk/Source/WebKit/WebProcess/WebPage/WebFrame.cpp 2018-12-11 19:27:26 UTC (rev 239079)
+++ trunk/Source/WebKit/WebProcess/WebPage/WebFrame.cpp 2018-12-11 20:24:06 UTC (rev 239080)
@@ -255,18 +255,12 @@
void WebFrame::didReceivePolicyDecision(uint64_t listenerID, PolicyAction action, uint64_t navigationID, DownloadID downloadID, std::optional<WebsitePoliciesData>&& websitePolicies)
{
- if (!m_coreFrame)
+ if (!m_coreFrame || !m_policyListenerID || listenerID != m_policyListenerID || !m_policyFunction) {
+ if (action == PolicyAction::Suspend)
+ page()->send(Messages::WebPageProxy::DidFailToSuspendAfterProcessSwap());
return;
+ }
- if (!m_policyListenerID)
- return;
-
- if (listenerID != m_policyListenerID)
- return;
-
- if (!m_policyFunction)
- return;
-
FramePolicyFunction function = WTFMove(m_policyFunction);
bool forNavigationAction = m_policyFunctionForNavigationAction == ForNavigationAction::Yes;
Modified: trunk/Tools/ChangeLog (239079 => 239080)
--- trunk/Tools/ChangeLog 2018-12-11 19:27:26 UTC (rev 239079)
+++ trunk/Tools/ChangeLog 2018-12-11 20:24:06 UTC (rev 239080)
@@ -1,3 +1,15 @@
+2018-12-11 Chris Dumez <cdu...@apple.com>
+
+ PSON logic gets confused by concurrent decidePolicyForNavigationAction requests
+ https://bugs.webkit.org/show_bug.cgi?id=192482
+ <rdar://problem/46470145>
+
+ Reviewed by Antti Koivisto.
+
+ Add API test coverage.
+
+ * TestWebKitAPI/Tests/WebKitCocoa/ProcessSwapOnNavigation.mm:
+
2018-12-11 Andy Estes <aes...@apple.com>
Introduce makeBlockPtr for lambdas
Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/ProcessSwapOnNavigation.mm (239079 => 239080)
--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/ProcessSwapOnNavigation.mm 2018-12-11 19:27:26 UTC (rev 239079)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/ProcessSwapOnNavigation.mm 2018-12-11 20:24:06 UTC (rev 239080)
@@ -2473,6 +2473,76 @@
EXPECT_EQ(pid1, pid3);
}
+TEST(ProcessSwap, ConcurrentHistoryNavigations)
+{
+ auto processPoolConfiguration = adoptNS([[_WKProcessPoolConfiguration alloc] init]);
+ processPoolConfiguration.get().processSwapsOnNavigation = YES;
+ auto processPool = adoptNS([[WKProcessPool alloc] _initWithConfiguration:processPoolConfiguration.get()]);
+
+ auto webViewConfiguration = adoptNS([[WKWebViewConfiguration alloc] init]);
+ [webViewConfiguration setProcessPool:processPool.get()];
+ auto handler = adoptNS([[PSONScheme alloc] init]);
+ [webViewConfiguration setURLSchemeHandler:handler.get() forURLScheme:@"PSON"];
+
+ auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:webViewConfiguration.get()]);
+ auto navigationDelegate = adoptNS([[PSONNavigationDelegate alloc] init]);
+ [webView setNavigationDelegate:navigationDelegate.get()];
+
+ [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"pson://www.webkit.org/main.html"]]];
+ TestWebKitAPI::Util::run(&done);
+ done = false;
+
+ auto webkitPID = [webView _webProcessIdentifier];
+
+ [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"pson://www.apple.com/main.html"]]];
+ TestWebKitAPI::Util::run(&done);
+ done = false;
+
+ auto applePID = [webView _webProcessIdentifier];
+
+ EXPECT_NE(webkitPID, applePID);
+
+ [webView goBack];
+ TestWebKitAPI::Util::run(&done);
+ done = false;
+
+ EXPECT_EQ(webkitPID, [webView _webProcessIdentifier]);
+ EXPECT_WK_STREQ(@"pson://www.webkit.org/main.html", [[webView URL] absoluteString]);
+
+ auto* backForwardList = [webView backForwardList];
+ EXPECT_WK_STREQ(@"pson://www.webkit.org/main.html", [backForwardList.currentItem.URL absoluteString]);
+ EXPECT_TRUE(!backForwardList.backItem);
+ EXPECT_EQ(1U, backForwardList.forwardList.count);
+ EXPECT_WK_STREQ(@"pson://www.apple.com/main.html", [backForwardList.forwardItem.URL absoluteString]);
+
+ // Concurrent requests to go forward, which process swaps.
+ [webView goForward];
+ [webView goForward];
+
+ TestWebKitAPI::Util::run(&done);
+ done = false;
+
+ EXPECT_EQ(applePID, [webView _webProcessIdentifier]);
+ EXPECT_WK_STREQ(@"pson://www.apple.com/main.html", [[webView URL] absoluteString]);
+
+ EXPECT_WK_STREQ(@"pson://www.apple.com/main.html", [backForwardList.currentItem.URL absoluteString]);
+ EXPECT_TRUE(!backForwardList.forwardItem);
+ EXPECT_EQ(1U, backForwardList.backList.count);
+ EXPECT_WK_STREQ(@"pson://www.webkit.org/main.html", [backForwardList.backItem.URL absoluteString]);
+
+ [webView goBack];
+ TestWebKitAPI::Util::run(&done);
+ done = false;
+
+ EXPECT_EQ(webkitPID, [webView _webProcessIdentifier]);
+ EXPECT_WK_STREQ(@"pson://www.webkit.org/main.html", [[webView URL] absoluteString]);
+
+ EXPECT_WK_STREQ(@"pson://www.webkit.org/main.html", [backForwardList.currentItem.URL absoluteString]);
+ EXPECT_TRUE(!backForwardList.backItem);
+ EXPECT_EQ(1U, backForwardList.forwardList.count);
+ EXPECT_WK_STREQ(@"pson://www.apple.com/main.html", [backForwardList.forwardItem.URL absoluteString]);
+}
+
TEST(ProcessSwap, NavigateToInvalidURL)
{
auto processPoolConfiguration = adoptNS([[_WKProcessPoolConfiguration alloc] init]);