https://bugs.webkit.org/show_bug.cgi?id=134004
<rdar://problem/
17186342>
Reviewed by Simon Fraser.
WebKit tries to make layers volatile when unparented, but sometimes isn't given
a chance to do so before the process gets suspended, so we end up with lots of
non-volatile surfaces that should really be volatile.
* Shared/mac/RemoteLayerBackingStoreCollection.h:
* Shared/mac/RemoteLayerBackingStoreCollection.mm:
(WebKit::RemoteLayerBackingStoreCollection::markBackingStoreVolatileImmediately):
(WebKit::RemoteLayerBackingStoreCollection::markAllBackingStoreVolatileImmediatelyIfPossible):
Add markAllBackingStoreVolatileImmediatelyIfPossible, which tries to mark *all*
buffers of *all* backing store, (live and unreachable), (front, back, and secondary),
volatile right away. It returns false if any buffer isn't marked volatile (because it was in-use).
* UIProcess/ios/ProcessThrottler.h:
* UIProcess/ios/ProcessThrottler.mm:
(WebKit::ProcessThrottler::updateAssertion):
(WebKit::ProcessThrottler::processReadyToSuspend):
(WebKit::ProcessThrottler::didCancelProcessSuspension):
* UIProcess/ios/WebProcessProxyIOS.mm:
(WebKit::WebProcessProxy::sendCancelProcessWillSuspend):
(WebKit::WebProcessProxy::didCancelProcessSuspension):
* UIProcess/WebProcessProxy.h:
* UIProcess/WebProcessProxy.messages.in:
* WebProcess/WebProcess.h:
* WebProcess/WebProcess.messages.in:
If the UI process is waiting for the Web process to confirm that it can suspend
and something happens (the view is reparented) that cancels the suspension, inform
the Web process that this happened, so that it can cancel any cleanup that might still be taking place.
* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::viewStateDidChange):
If a view goes in-window, dispatch the view state change immediately without delay,
to minimize the latency between coming in-window and being ready to go.
* WebProcess/WebPage/DrawingArea.h:
(WebKit::DrawingArea::markLayersVolatileImmediatelyIfPossible):
* WebProcess/WebPage/mac/RemoteLayerTreeDrawingArea.h:
* WebProcess/WebPage/mac/RemoteLayerTreeDrawingArea.mm:
(WebKit::RemoteLayerTreeDrawingArea::setRootCompositingLayer):
Schedule a flush when we change the root layer; otherwise, we can end up
detaching the root layer but changing nothing else, and never committing that change.
(WebKit::RemoteLayerTreeDrawingArea::markLayersVolatileImmediatelyIfPossible):
* WebProcess/WebProcess.cpp:
(WebKit::WebProcess::WebProcess):
(WebKit::WebProcess::processWillSuspend):
(WebKit::WebProcess::cancelProcessWillSuspend):
(WebKit::WebProcess::markAllLayersVolatileIfPossible):
(WebKit::WebProcess::processSuspensionCleanupTimerFired):
When the UI process is going to suspend the process, it sends us ProcessWillSuspend,
and defers the suspension until we send a ProcessReadyToSuspend back.
Delay ProcessReadyToSuspend until all layers in our process have been marked volatile.
We'll keep trying every 20ms until they're all volatile. For safety, the UI process will eventually
stop waiting for us, but the volatility change is usually applied successfully within the first
or second timer callback.
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@170316
268f45cc-cd09-0410-ab3c-
d52691b4dbfc
+2014-06-23 Timothy Horton <timothy_horton@apple.com>
+
+ [iOS][wk2] Ensure that layers are marked volatile before allowing the process to suspend
+ https://bugs.webkit.org/show_bug.cgi?id=134004
+ <rdar://problem/17186342>
+
+ Reviewed by Simon Fraser.
+
+ WebKit tries to make layers volatile when unparented, but sometimes isn't given
+ a chance to do so before the process gets suspended, so we end up with lots of
+ non-volatile surfaces that should really be volatile.
+
+ * Shared/mac/RemoteLayerBackingStoreCollection.h:
+ * Shared/mac/RemoteLayerBackingStoreCollection.mm:
+ (WebKit::RemoteLayerBackingStoreCollection::markBackingStoreVolatileImmediately):
+ (WebKit::RemoteLayerBackingStoreCollection::markAllBackingStoreVolatileImmediatelyIfPossible):
+ Add markAllBackingStoreVolatileImmediatelyIfPossible, which tries to mark *all*
+ buffers of *all* backing store, (live and unreachable), (front, back, and secondary),
+ volatile right away. It returns false if any buffer isn't marked volatile (because it was in-use).
+
+ * UIProcess/ios/ProcessThrottler.h:
+ * UIProcess/ios/ProcessThrottler.mm:
+ (WebKit::ProcessThrottler::updateAssertion):
+ (WebKit::ProcessThrottler::processReadyToSuspend):
+ (WebKit::ProcessThrottler::didCancelProcessSuspension):
+ * UIProcess/ios/WebProcessProxyIOS.mm:
+ (WebKit::WebProcessProxy::sendCancelProcessWillSuspend):
+ (WebKit::WebProcessProxy::didCancelProcessSuspension):
+ * UIProcess/WebProcessProxy.h:
+ * UIProcess/WebProcessProxy.messages.in:
+ * WebProcess/WebProcess.h:
+ * WebProcess/WebProcess.messages.in:
+ If the UI process is waiting for the Web process to confirm that it can suspend
+ and something happens (the view is reparented) that cancels the suspension, inform
+ the Web process that this happened, so that it can cancel any cleanup that might still be taking place.
+
+ * UIProcess/WebPageProxy.cpp:
+ (WebKit::WebPageProxy::viewStateDidChange):
+ If a view goes in-window, dispatch the view state change immediately without delay,
+ to minimize the latency between coming in-window and being ready to go.
+
+ * WebProcess/WebPage/DrawingArea.h:
+ (WebKit::DrawingArea::markLayersVolatileImmediatelyIfPossible):
+ * WebProcess/WebPage/mac/RemoteLayerTreeDrawingArea.h:
+ * WebProcess/WebPage/mac/RemoteLayerTreeDrawingArea.mm:
+ (WebKit::RemoteLayerTreeDrawingArea::setRootCompositingLayer):
+ Schedule a flush when we change the root layer; otherwise, we can end up
+ detaching the root layer but changing nothing else, and never committing that change.
+
+ (WebKit::RemoteLayerTreeDrawingArea::markLayersVolatileImmediatelyIfPossible):
+
+ * WebProcess/WebProcess.cpp:
+ (WebKit::WebProcess::WebProcess):
+ (WebKit::WebProcess::processWillSuspend):
+ (WebKit::WebProcess::cancelProcessWillSuspend):
+ (WebKit::WebProcess::markAllLayersVolatileIfPossible):
+ (WebKit::WebProcess::processSuspensionCleanupTimerFired):
+ When the UI process is going to suspend the process, it sends us ProcessWillSuspend,
+ and defers the suspension until we send a ProcessReadyToSuspend back.
+ Delay ProcessReadyToSuspend until all layers in our process have been marked volatile.
+ We'll keep trying every 20ms until they're all volatile. For safety, the UI process will eventually
+ stop waiting for us, but the volatility change is usually applied successfully within the first
+ or second timer callback.
+
2014-06-23 Oliver Hunt <oliver@apple.com>
Ensure that we always use symlink free paths when specifying cache directories
void didFlushLayers();
void volatilityTimerFired(WebCore::Timer<RemoteLayerBackingStoreCollection>&);
+ bool markAllBackingStoreVolatileImmediatelyIfPossible();
void scheduleVolatilityTimer();
private:
- bool markBackingStoreVolatileImmediately(RemoteLayerBackingStore&);
+ enum VolatilityMarkingFlag {
+ MarkBuffersIgnoringReachability = 1 << 0
+ };
+ typedef unsigned VolatilityMarkingFlags;
+ bool markBackingStoreVolatileImmediately(RemoteLayerBackingStore&, VolatilityMarkingFlags volatilityMarkingFlags = 0);
bool markBackingStoreVolatile(RemoteLayerBackingStore&, std::chrono::steady_clock::time_point now);
HashSet<RemoteLayerBackingStore*> m_liveBackingStore;
m_unparentedBackingStore.remove(backingStoreIter);
}
-bool RemoteLayerBackingStoreCollection::markBackingStoreVolatileImmediately(RemoteLayerBackingStore& backingStore)
+bool RemoteLayerBackingStoreCollection::markBackingStoreVolatileImmediately(RemoteLayerBackingStore& backingStore, VolatilityMarkingFlags volatilityMarkingFlags)
{
ASSERT(!m_inLayerFlush);
bool successfullyMadeBackingStoreVolatile = true;
if (!backingStore.setBufferVolatility(RemoteLayerBackingStore::BufferType::Back, true))
successfullyMadeBackingStoreVolatile = false;
- if (!m_reachableBackingStoreInLatestFlush.contains(&backingStore)) {
+ if (!m_reachableBackingStoreInLatestFlush.contains(&backingStore) || (volatilityMarkingFlags & MarkBuffersIgnoringReachability)) {
if (!backingStore.setBufferVolatility(RemoteLayerBackingStore::BufferType::Front, true))
successfullyMadeBackingStoreVolatile = false;
}
markBackingStoreVolatileImmediately(backingStore);
}
+bool RemoteLayerBackingStoreCollection::markAllBackingStoreVolatileImmediatelyIfPossible()
+{
+ bool successfullyMadeBackingStoreVolatile = true;
+
+ for (const auto& backingStore : m_liveBackingStore)
+ successfullyMadeBackingStoreVolatile &= markBackingStoreVolatileImmediately(*backingStore, MarkBuffersIgnoringReachability);
+
+ for (const auto& backingStore : m_unparentedBackingStore)
+ successfullyMadeBackingStoreVolatile &= markBackingStoreVolatileImmediately(*backingStore, MarkBuffersIgnoringReachability);
+
+ return successfullyMadeBackingStoreVolatile;
+}
+
void RemoteLayerBackingStoreCollection::volatilityTimerFired(WebCore::Timer<RemoteLayerBackingStoreCollection>&)
{
bool successfullyMadeBackingStoreVolatile = true;
m_viewStateChangeWantsReply = (wantsReply == WantsReplyOrNot::DoesWantReply || m_viewStateChangeWantsReply == WantsReplyOrNot::DoesWantReply) ? WantsReplyOrNot::DoesWantReply : WantsReplyOrNot::DoesNotWantReply;
#if PLATFORM(COCOA)
+ bool isNewlyInWindow = !(m_viewState & ViewState::IsInWindow) && (mayHaveChanged & ViewState::IsInWindow) && m_pageClient.isViewInWindow();
+ if (isNewlyInWindow) {
+ dispatchViewStateChange();
+ return;
+ }
m_viewStateChangeDispatcher->schedule();
#else
dispatchViewStateChange();
#if PLATFORM(IOS)
void sendProcessWillSuspend();
void processReadyToSuspend();
+ void sendCancelProcessWillSuspend();
+ void didCancelProcessSuspension();
ProcessThrottler& throttler() { return *m_throttler; }
#endif
#endif
#if PLATFORM(IOS)
ProcessReadyToSuspend()
+ DidCancelProcessSuspension()
#endif
}
void didConnnectToProcess(pid_t);
void processReadyToSuspend();
+ void didCancelProcessSuspension();
private:
friend class ForegroundActivityToken;
WebCore::Timer<ProcessThrottler> m_suspendTimer;
unsigned m_foregroundCount;
unsigned m_backgroundCount;
- unsigned m_suspendMessageCount;
+ int m_suspendMessageCount;
};
}
m_process->sendProcessWillSuspend();
m_suspendTimer.startOneShot(processSuspensionTimeout);
m_assertion->setState(AssertionState::Background);
- } else
- updateAssertionNow();
+ return;
+ }
+
+ // If we're currently waiting for the Web process to do suspension cleanup, but no longer need to be suspended, tell the Web process to cancel the cleanup.
+ if (m_suspendTimer.isActive() && (m_foregroundCount || m_backgroundCount))
+ m_process->sendCancelProcessWillSuspend();
+
+ updateAssertionNow();
}
void ProcessThrottler::didConnnectToProcess(pid_t pid)
{
if (!--m_suspendMessageCount)
updateAssertionNow();
+ ASSERT(m_suspendMessageCount >= 0);
+}
+
+void ProcessThrottler::didCancelProcessSuspension()
+{
+ if (!--m_suspendMessageCount)
+ updateAssertionNow();
+ ASSERT(m_suspendMessageCount >= 0);
}
}
if (canSendMessage())
send(Messages::WebProcess::ProcessWillSuspend(), 0);
}
+
+void WebProcessProxy::sendCancelProcessWillSuspend()
+{
+ if (canSendMessage())
+ send(Messages::WebProcess::CancelProcessWillSuspend(), 0);
+}
void WebProcessProxy::processReadyToSuspend()
{
m_throttler->processReadyToSuspend();
}
+
+void WebProcessProxy::didCancelProcessSuspension()
+{
+ m_throttler->didCancelProcessSuspension();
+}
} // namespace WebKit
virtual void viewStateDidChange(WebCore::ViewState::Flags) { }
virtual void setLayerHostingMode(LayerHostingMode) { }
+ virtual bool markLayersVolatileImmediatelyIfPossible() { return true; }
+
protected:
DrawingArea(DrawingAreaType, WebPage&);
uint64_t takeNextTransactionID() { return ++m_currentTransactionID; }
+ virtual bool markLayersVolatileImmediatelyIfPossible() override;
+
class BackingStoreFlusher : public ThreadSafeRefCounted<BackingStoreFlusher> {
public:
static PassRefPtr<BackingStoreFlusher> create(IPC::Connection*, std::unique_ptr<IPC::MessageEncoder>, Vector<RetainPtr<CGContextRef>>);
void RemoteLayerTreeDrawingArea::setRootCompositingLayer(GraphicsLayer* rootLayer)
{
- Vector<GraphicsLayer *> children;
+ Vector<GraphicsLayer*> children;
if (rootLayer) {
children.append(rootLayer);
children.append(m_webPage.pageOverlayController().viewOverlayRootLayer());
}
m_rootLayer->setChildren(children);
+
+ scheduleCompositingLayerFlush();
}
void RemoteLayerTreeDrawingArea::updateGeometry(const IntSize& viewSize, const IntSize& layerPosition)
m_webPage.pageOverlayController().didChangeDocumentSize();
}
+bool RemoteLayerTreeDrawingArea::markLayersVolatileImmediatelyIfPossible()
+{
+ return m_remoteLayerTreeContext->backingStoreCollection().markAllBackingStoreVolatileImmediatelyIfPossible();
+}
+
PassRefPtr<RemoteLayerTreeDrawingArea::BackingStoreFlusher> RemoteLayerTreeDrawingArea::BackingStoreFlusher::create(IPC::Connection* connection, std::unique_ptr<IPC::MessageEncoder> encoder, Vector<RetainPtr<CGContextRef>> contextsToFlush)
{
return adoptRef(new RemoteLayerTreeDrawingArea::BackingStoreFlusher(connection, std::move(encoder), std::move(contextsToFlush)));
#include "APIFrameHandle.h"
#include "AuthenticationManager.h"
+#include "DrawingArea.h"
#include "EventDispatcher.h"
#include "InjectedBundle.h"
#include "InjectedBundleUserMessageCoders.h"
: m_eventDispatcher(EventDispatcher::create())
#if PLATFORM(IOS)
, m_viewUpdateDispatcher(ViewUpdateDispatcher::create())
+ , m_processSuspensionCleanupTimer(this, &WebProcess::processSuspensionCleanupTimerFired)
#endif // PLATFORM(IOS)
, m_inDidClose(false)
, m_hasSetCacheModel(false)
void WebProcess::processWillSuspend()
{
memoryPressureHandler().releaseMemory(true);
- parentProcessConnection()->send(Messages::WebProcessProxy::ProcessReadyToSuspend(), 0);
+
+ if (!markAllLayersVolatileIfPossible())
+ m_processSuspensionCleanupTimer.startRepeating(std::chrono::milliseconds(20));
+ else
+ parentProcessConnection()->send(Messages::WebProcessProxy::ProcessReadyToSuspend(), 0);
+}
+
+void WebProcess::cancelProcessWillSuspend()
+{
+ // If we've already finished cleaning up and sent ProcessReadyToSuspend, we
+ // shouldn't send DidCancelProcessSuspension; the UI process strictly expects one or the other.
+ if (!m_processSuspensionCleanupTimer.isActive())
+ return;
+
+ m_processSuspensionCleanupTimer.stop();
+ parentProcessConnection()->send(Messages::WebProcessProxy::DidCancelProcessSuspension(), 0);
+}
+
+bool WebProcess::markAllLayersVolatileIfPossible()
+{
+ bool successfullyMarkedAllLayersVolatile = true;
+ for (auto& page : m_pageMap.values()) {
+ if (auto drawingArea = page->drawingArea())
+ successfullyMarkedAllLayersVolatile &= drawingArea->markLayersVolatileImmediatelyIfPossible();
+ }
+
+ return successfullyMarkedAllLayersVolatile;
+}
+
+void WebProcess::processSuspensionCleanupTimerFired(Timer<WebProcess>* timer)
+{
+ if (markAllLayersVolatileIfPossible()) {
+ parentProcessConnection()->send(Messages::WebProcessProxy::ProcessReadyToSuspend(), 0);
+ timer->stop();
+ }
}
-
#endif // PLATFORM(IOS)
void WebProcess::pageDidEnterWindow(uint64_t pageID)
#include "CacheModel.h"
#include "ChildProcess.h"
#include "DownloadManager.h"
+#include "DrawingArea.h"
#include "PluginProcessConnectionManager.h"
#include "ResourceCachesToClear.h"
#include "SandboxExtension.h"
#if PLATFORM(IOS)
void resetAllGeolocationPermissions();
void processWillSuspend();
+ void cancelProcessWillSuspend();
+ bool markAllLayersVolatileIfPossible();
+ void processSuspensionCleanupTimerFired(WebCore::Timer<WebProcess>*);
#endif // PLATFORM(IOS)
RefPtr<API::Object> apiObjectByConvertingFromHandles(API::Object*);
RefPtr<EventDispatcher> m_eventDispatcher;
#if PLATFORM(IOS)
RefPtr<ViewUpdateDispatcher> m_viewUpdateDispatcher;
+ WebCore::Timer<WebProcess> m_processSuspensionCleanupTimer;
#endif
bool m_inDidClose;
#if PLATFORM(IOS)
ProcessWillSuspend()
+ CancelProcessWillSuspend()
#endif
}