Delay WebProcess launch until a load is triggered in a Web view
[WebKit-https.git] / Source / WebKit / UIProcess / ViewGestureController.cpp
1 /*
2  * Copyright (C) 2013-2015 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "ViewGestureController.h"
28
29 #include "APINavigation.h"
30 #include "Logging.h"
31 #include "ViewGestureControllerMessages.h"
32 #include "WebBackForwardList.h"
33 #include "WebFullScreenManagerProxy.h"
34 #include "WebPageProxy.h"
35 #include "WebProcessProxy.h"
36 #include <wtf/MathExtras.h>
37 #include <wtf/NeverDestroyed.h>
38 #include <wtf/text/StringBuilder.h>
39 #include <wtf/text/StringConcatenateNumbers.h>
40
41 #if PLATFORM(COCOA)
42 #include "RemoteLayerTreeDrawingAreaProxy.h"
43 #endif
44
45 #if !PLATFORM(IOS_FAMILY)
46 #include "ProvisionalPageProxy.h"
47 #include "ViewGestureGeometryCollectorMessages.h"
48 #endif
49
50 namespace WebKit {
51 using namespace WebCore;
52
53 static const Seconds swipeSnapshotRemovalWatchdogAfterFirstVisuallyNonEmptyLayoutDuration { 3_s };
54 static const Seconds swipeSnapshotRemovalActiveLoadMonitoringInterval { 250_ms };
55
56 #if !PLATFORM(IOS_FAMILY)
57 static const Seconds swipeSnapshotRemovalWatchdogDuration = 5_s;
58 #else
59 static const Seconds swipeSnapshotRemovalWatchdogDuration = 3_s;
60 #endif
61
62 #if !PLATFORM(IOS_FAMILY)
63 static const float minimumHorizontalSwipeDistance = 15;
64 static const float minimumScrollEventRatioForSwipe = 0.5;
65
66 static const float swipeSnapshotRemovalRenderTreeSizeTargetFraction = 0.5;
67 #endif
68
69 static HashMap<uint64_t, ViewGestureController*>& viewGestureControllersForAllPages()
70 {
71     // The key in this map is the associated page ID.
72     static NeverDestroyed<HashMap<uint64_t, ViewGestureController*>> viewGestureControllers;
73     return viewGestureControllers.get();
74 }
75
76 ViewGestureController::ViewGestureController(WebPageProxy& webPageProxy)
77     : m_webPageProxy(webPageProxy)
78     , m_swipeActiveLoadMonitoringTimer(RunLoop::main(), this, &ViewGestureController::checkForActiveLoads)
79 #if !PLATFORM(IOS_FAMILY)
80     , m_pendingSwipeTracker(webPageProxy, *this)
81 #endif
82 #if PLATFORM(GTK)
83     , m_swipeProgressTracker(webPageProxy, *this)
84 #endif
85 {
86     if (webPageProxy.hasRunningProcess())
87         connectToProcess();
88
89     viewGestureControllersForAllPages().add(webPageProxy.pageID(), this);
90 }
91
92 ViewGestureController::~ViewGestureController()
93 {
94     platformTeardown();
95
96     viewGestureControllersForAllPages().remove(m_webPageProxy.pageID());
97
98     disconnectFromProcess();
99 }
100
101 void ViewGestureController::disconnectFromProcess()
102 {
103     if (!m_isConnectedToProcess)
104         return;
105
106     m_webPageProxy.process().removeMessageReceiver(Messages::ViewGestureController::messageReceiverName(), m_webPageProxy.pageID());
107     m_isConnectedToProcess = false;
108 }
109
110 void ViewGestureController::connectToProcess()
111 {
112     if (m_isConnectedToProcess)
113         return;
114
115     m_webPageProxy.process().addMessageReceiver(Messages::ViewGestureController::messageReceiverName(), m_webPageProxy.pageID(), *this);
116     m_isConnectedToProcess = true;
117 }
118
119 ViewGestureController* ViewGestureController::controllerForGesture(uint64_t pageID, ViewGestureController::GestureID gestureID)
120 {
121     auto gestureControllerIter = viewGestureControllersForAllPages().find(pageID);
122     if (gestureControllerIter == viewGestureControllersForAllPages().end())
123         return nullptr;
124     if (gestureControllerIter->value->m_currentGestureID != gestureID)
125         return nullptr;
126     return gestureControllerIter->value;
127 }
128
129 ViewGestureController::GestureID ViewGestureController::takeNextGestureID()
130 {
131     static GestureID nextGestureID;
132     return ++nextGestureID;
133 }
134
135 void ViewGestureController::willBeginGesture(ViewGestureType type)
136 {
137     m_activeGestureType = type;
138     m_currentGestureID = takeNextGestureID();
139 }
140
141 void ViewGestureController::didEndGesture()
142 {
143     m_activeGestureType = ViewGestureType::None;
144     m_currentGestureID = 0;
145 }
146
147 void ViewGestureController::setAlternateBackForwardListSourcePage(WebPageProxy* page)
148 {
149     m_alternateBackForwardListSourcePage = makeWeakPtr(page);
150 }
151
152 bool ViewGestureController::canSwipeInDirection(SwipeDirection direction) const
153 {
154     if (!m_swipeGestureEnabled)
155         return false;
156
157 #if ENABLE(FULLSCREEN_API)
158     if (m_webPageProxy.fullScreenManager() && m_webPageProxy.fullScreenManager()->isFullScreen())
159         return false;
160 #endif
161
162     RefPtr<WebPageProxy> alternateBackForwardListSourcePage = m_alternateBackForwardListSourcePage.get();
163     auto& backForwardList = alternateBackForwardListSourcePage ? alternateBackForwardListSourcePage->backForwardList() : m_webPageProxy.backForwardList();
164     if (direction == SwipeDirection::Back)
165         return !!backForwardList.backItem();
166     return !!backForwardList.forwardItem();
167 }
168
169 void ViewGestureController::didStartProvisionalOrSameDocumentLoadForMainFrame()
170 {
171     m_snapshotRemovalTracker.resume();
172 #if !PLATFORM(IOS_FAMILY)
173     requestRenderTreeSizeNotificationIfNeeded();
174 #endif
175
176     if (auto loadCallback = std::exchange(m_loadCallback, nullptr))
177         loadCallback();
178 }
179
180 void ViewGestureController::didStartProvisionalLoadForMainFrame()
181 {
182     didStartProvisionalOrSameDocumentLoadForMainFrame();
183 }
184
185 void ViewGestureController::didFirstVisuallyNonEmptyLayoutForMainFrame()
186 {
187     if (!m_snapshotRemovalTracker.eventOccurred(SnapshotRemovalTracker::VisuallyNonEmptyLayout))
188         return;
189
190     m_snapshotRemovalTracker.cancelOutstandingEvent(SnapshotRemovalTracker::MainFrameLoad);
191     m_snapshotRemovalTracker.cancelOutstandingEvent(SnapshotRemovalTracker::SubresourceLoads);
192     m_snapshotRemovalTracker.startWatchdog(swipeSnapshotRemovalWatchdogAfterFirstVisuallyNonEmptyLayoutDuration);
193 }
194
195 void ViewGestureController::didRepaintAfterNavigation()
196 {
197     m_snapshotRemovalTracker.eventOccurred(SnapshotRemovalTracker::RepaintAfterNavigation);
198 }
199
200 void ViewGestureController::didHitRenderTreeSizeThreshold()
201 {
202     m_snapshotRemovalTracker.eventOccurred(SnapshotRemovalTracker::RenderTreeSizeThreshold);
203 }
204
205 void ViewGestureController::didRestoreScrollPosition()
206 {
207     m_snapshotRemovalTracker.eventOccurred(SnapshotRemovalTracker::ScrollPositionRestoration);
208 }
209
210 void ViewGestureController::didReachMainFrameLoadTerminalState()
211 {
212     if (m_snapshotRemovalTracker.isPaused() && m_snapshotRemovalTracker.hasRemovalCallback()) {
213         removeSwipeSnapshot();
214         return;
215     }
216
217     if (!m_snapshotRemovalTracker.eventOccurred(SnapshotRemovalTracker::MainFrameLoad))
218         return;
219
220     // Coming back from the page cache will result in getting a load event, but no first visually non-empty layout.
221     // WebCore considers a loaded document enough to be considered visually non-empty, so that's good
222     // enough for us too.
223     m_snapshotRemovalTracker.cancelOutstandingEvent(SnapshotRemovalTracker::VisuallyNonEmptyLayout);
224
225     checkForActiveLoads();
226 }
227
228 void ViewGestureController::didSameDocumentNavigationForMainFrame(SameDocumentNavigationType type)
229 {
230     didStartProvisionalOrSameDocumentLoadForMainFrame();
231
232     bool cancelledOutstandingEvent = false;
233
234     // Same-document navigations don't have a main frame load or first visually non-empty layout.
235     cancelledOutstandingEvent |= m_snapshotRemovalTracker.cancelOutstandingEvent(SnapshotRemovalTracker::MainFrameLoad);
236     cancelledOutstandingEvent |= m_snapshotRemovalTracker.cancelOutstandingEvent(SnapshotRemovalTracker::VisuallyNonEmptyLayout);
237
238     if (!cancelledOutstandingEvent)
239         return;
240
241     if (type != SameDocumentNavigationSessionStateReplace && type != SameDocumentNavigationSessionStatePop)
242         return;
243
244     checkForActiveLoads();
245 }
246
247 void ViewGestureController::checkForActiveLoads()
248 {
249     if (m_webPageProxy.pageLoadState().isLoading()) {
250         if (!m_swipeActiveLoadMonitoringTimer.isActive())
251             m_swipeActiveLoadMonitoringTimer.startRepeating(swipeSnapshotRemovalActiveLoadMonitoringInterval);
252         return;
253     }
254
255     m_swipeActiveLoadMonitoringTimer.stop();
256     m_snapshotRemovalTracker.eventOccurred(SnapshotRemovalTracker::SubresourceLoads);
257 }
258
259 ViewGestureController::SnapshotRemovalTracker::SnapshotRemovalTracker()
260     : m_watchdogTimer(RunLoop::main(), this, &SnapshotRemovalTracker::watchdogTimerFired)
261 {
262 }
263
264 String ViewGestureController::SnapshotRemovalTracker::eventsDescription(Events event)
265 {
266     StringBuilder description;
267
268     if (event & ViewGestureController::SnapshotRemovalTracker::VisuallyNonEmptyLayout)
269         description.append("VisuallyNonEmptyLayout ");
270
271     if (event & ViewGestureController::SnapshotRemovalTracker::RenderTreeSizeThreshold)
272         description.append("RenderTreeSizeThreshold ");
273
274     if (event & ViewGestureController::SnapshotRemovalTracker::RepaintAfterNavigation)
275         description.append("RepaintAfterNavigation ");
276
277     if (event & ViewGestureController::SnapshotRemovalTracker::MainFrameLoad)
278         description.append("MainFrameLoad ");
279
280     if (event & ViewGestureController::SnapshotRemovalTracker::SubresourceLoads)
281         description.append("SubresourceLoads ");
282
283     if (event & ViewGestureController::SnapshotRemovalTracker::ScrollPositionRestoration)
284         description.append("ScrollPositionRestoration ");
285
286     return description.toString();
287 }
288
289
290 void ViewGestureController::SnapshotRemovalTracker::log(const String& log) const
291 {
292     RELEASE_LOG(ViewGestures, "Swipe Snapshot Removal (%0.2f ms) - %s", (MonotonicTime::now() - m_startTime).milliseconds(), log.utf8().data());
293 }
294
295 void ViewGestureController::SnapshotRemovalTracker::resume()
296 {
297     if (isPaused() && m_outstandingEvents)
298         log("resume");
299     m_paused = false;
300 }
301
302 void ViewGestureController::SnapshotRemovalTracker::start(Events desiredEvents, WTF::Function<void()>&& removalCallback)
303 {
304     m_outstandingEvents = desiredEvents;
305     m_removalCallback = WTFMove(removalCallback);
306     m_startTime = MonotonicTime::now();
307
308     log("start");
309
310     startWatchdog(swipeSnapshotRemovalWatchdogDuration);
311
312     // Initially start out paused; we'll resume when the load is committed.
313     // This avoids processing callbacks from earlier loads.
314     pause();
315 }
316
317 void ViewGestureController::SnapshotRemovalTracker::reset()
318 {
319     if (m_outstandingEvents)
320         log("reset; had outstanding events: " + eventsDescription(m_outstandingEvents));
321     m_outstandingEvents = 0;
322     m_watchdogTimer.stop();
323     m_removalCallback = nullptr;
324 }
325
326 bool ViewGestureController::SnapshotRemovalTracker::stopWaitingForEvent(Events event, const String& logReason)
327 {
328     ASSERT(hasOneBitSet(event));
329
330     if (!(m_outstandingEvents & event))
331         return false;
332
333     if (isPaused()) {
334         log("is paused; ignoring event: " + eventsDescription(event));
335         return false;
336     }
337
338     log(logReason + eventsDescription(event));
339
340     m_outstandingEvents &= ~event;
341
342     fireRemovalCallbackIfPossible();
343     return true;
344 }
345
346 bool ViewGestureController::SnapshotRemovalTracker::eventOccurred(Events event)
347 {
348     return stopWaitingForEvent(event, "outstanding event occurred: ");
349 }
350
351 bool ViewGestureController::SnapshotRemovalTracker::cancelOutstandingEvent(Events event)
352 {
353     return stopWaitingForEvent(event, "wait for event cancelled: ");
354 }
355
356 bool ViewGestureController::SnapshotRemovalTracker::hasOutstandingEvent(Event event)
357 {
358     return m_outstandingEvents & event;
359 }
360
361 void ViewGestureController::SnapshotRemovalTracker::fireRemovalCallbackIfPossible()
362 {
363     if (m_outstandingEvents) {
364         log("deferring removal; had outstanding events: " + eventsDescription(m_outstandingEvents));
365         return;
366     }
367
368     fireRemovalCallbackImmediately();
369 }
370
371 void ViewGestureController::SnapshotRemovalTracker::fireRemovalCallbackImmediately()
372 {
373     m_watchdogTimer.stop();
374
375     auto removalCallback = WTFMove(m_removalCallback);
376     if (removalCallback) {
377         log("removing snapshot");
378         reset();
379         removalCallback();
380     }
381 }
382
383 void ViewGestureController::SnapshotRemovalTracker::watchdogTimerFired()
384 {
385     log("watchdog timer fired");
386     fireRemovalCallbackImmediately();
387 }
388
389 void ViewGestureController::SnapshotRemovalTracker::startWatchdog(Seconds duration)
390 {
391     log(makeString("(re)started watchdog timer for ", FormattedNumber::fixedWidth(duration.seconds(), 1), " seconds"));
392     m_watchdogTimer.startOneShot(duration);
393 }
394
395 #if !PLATFORM(IOS_FAMILY)
396 static bool deltaShouldCancelSwipe(FloatSize delta)
397 {
398     return std::abs(delta.height()) >= std::abs(delta.width()) * minimumScrollEventRatioForSwipe;
399 }
400
401 ViewGestureController::PendingSwipeTracker::PendingSwipeTracker(WebPageProxy& webPageProxy, ViewGestureController& viewGestureController)
402     : m_viewGestureController(viewGestureController)
403     , m_webPageProxy(webPageProxy)
404 {
405 }
406
407 bool ViewGestureController::PendingSwipeTracker::scrollEventCanBecomeSwipe(PlatformScrollEvent event, ViewGestureController::SwipeDirection& potentialSwipeDirection)
408 {
409     if (!scrollEventCanStartSwipe(event) || !scrollEventCanInfluenceSwipe(event))
410         return false;
411
412     FloatSize size = scrollEventGetScrollingDeltas(event);
413
414     if (deltaShouldCancelSwipe(size))
415         return false;
416
417     bool isPinnedToLeft = m_shouldIgnorePinnedState || m_webPageProxy.isPinnedToLeftSide();
418     bool isPinnedToRight = m_shouldIgnorePinnedState || m_webPageProxy.isPinnedToRightSide();
419
420     bool tryingToSwipeBack = size.width() > 0 && isPinnedToLeft;
421     bool tryingToSwipeForward = size.width() < 0 && isPinnedToRight;
422     if (m_webPageProxy.userInterfaceLayoutDirection() != WebCore::UserInterfaceLayoutDirection::LTR)
423         std::swap(tryingToSwipeBack, tryingToSwipeForward);
424
425     if (!tryingToSwipeBack && !tryingToSwipeForward)
426         return false;
427
428     potentialSwipeDirection = tryingToSwipeBack ? SwipeDirection::Back : SwipeDirection::Forward;
429     return m_viewGestureController.canSwipeInDirection(potentialSwipeDirection);
430 }
431
432 bool ViewGestureController::PendingSwipeTracker::handleEvent(PlatformScrollEvent event)
433 {
434     if (scrollEventCanEndSwipe(event)) {
435         reset("gesture ended");
436         return false;
437     }
438
439     if (m_state == State::None) {
440         if (!scrollEventCanBecomeSwipe(event, m_direction))
441             return false;
442
443         if (!m_shouldIgnorePinnedState && m_webPageProxy.willHandleHorizontalScrollEvents()) {
444             m_state = State::WaitingForWebCore;
445             LOG(ViewGestures, "Swipe Start Hysteresis - waiting for WebCore to handle event");
446         }
447     }
448
449     if (m_state == State::WaitingForWebCore)
450         return false;
451
452     return tryToStartSwipe(event);
453 }
454
455 void ViewGestureController::PendingSwipeTracker::eventWasNotHandledByWebCore(PlatformScrollEvent event)
456 {
457     if (m_state != State::WaitingForWebCore)
458         return;
459
460     LOG(ViewGestures, "Swipe Start Hysteresis - WebCore didn't handle event");
461     m_state = State::None;
462     m_cumulativeDelta = FloatSize();
463     tryToStartSwipe(event);
464 }
465
466 bool ViewGestureController::PendingSwipeTracker::tryToStartSwipe(PlatformScrollEvent event)
467 {
468     ASSERT(m_state != State::WaitingForWebCore);
469
470     if (m_state == State::None) {
471         SwipeDirection direction;
472         if (!scrollEventCanBecomeSwipe(event, direction))
473             return false;
474     }
475
476     if (!scrollEventCanInfluenceSwipe(event))
477         return false;
478
479     m_cumulativeDelta += scrollEventGetScrollingDeltas(event);
480     LOG(ViewGestures, "Swipe Start Hysteresis - consumed event, cumulative delta (%0.2f, %0.2f)", m_cumulativeDelta.width(), m_cumulativeDelta.height());
481
482     if (deltaShouldCancelSwipe(m_cumulativeDelta)) {
483         reset("cumulative delta became too vertical");
484         return false;
485     }
486
487     if (std::abs(m_cumulativeDelta.width()) >= minimumHorizontalSwipeDistance)
488         m_viewGestureController.startSwipeGesture(event, m_direction);
489     else
490         m_state = State::InsufficientMagnitude;
491
492     return true;
493 }
494
495 void ViewGestureController::PendingSwipeTracker::reset(const char* resetReasonForLogging)
496 {
497     if (m_state != State::None)
498         LOG(ViewGestures, "Swipe Start Hysteresis - reset; %s", resetReasonForLogging);
499
500     m_state = State::None;
501     m_cumulativeDelta = FloatSize();
502 }
503
504 void ViewGestureController::startSwipeGesture(PlatformScrollEvent event, SwipeDirection direction)
505 {
506     ASSERT(m_activeGestureType == ViewGestureType::None);
507
508     m_pendingSwipeTracker.reset("starting to track swipe");
509
510     m_webPageProxy.recordAutomaticNavigationSnapshot();
511
512     RefPtr<WebBackForwardListItem> targetItem = (direction == SwipeDirection::Back) ? m_webPageProxy.backForwardList().backItem() : m_webPageProxy.backForwardList().forwardItem();
513     if (!targetItem)
514         return;
515
516     trackSwipeGesture(event, direction, targetItem);
517 }
518
519 bool ViewGestureController::isPhysicallySwipingLeft(SwipeDirection direction) const
520 {
521     bool isLTR = m_webPageProxy.userInterfaceLayoutDirection() == WebCore::UserInterfaceLayoutDirection::LTR;
522     bool isSwipingForward = direction == SwipeDirection::Forward;
523     return isLTR != isSwipingForward;
524 }
525
526 bool ViewGestureController::shouldUseSnapshotForSize(ViewSnapshot& snapshot, FloatSize swipeLayerSize, float topContentInset)
527 {
528     float deviceScaleFactor = m_webPageProxy.deviceScaleFactor();
529     if (snapshot.deviceScaleFactor() != deviceScaleFactor)
530         return false;
531
532     FloatSize unobscuredSwipeLayerSizeInDeviceCoordinates = swipeLayerSize - FloatSize(0, topContentInset);
533     unobscuredSwipeLayerSizeInDeviceCoordinates.scale(deviceScaleFactor);
534     if (snapshot.size() != unobscuredSwipeLayerSizeInDeviceCoordinates)
535         return false;
536
537     return true;
538 }
539
540 void ViewGestureController::forceRepaintIfNeeded()
541 {
542     if (m_activeGestureType != ViewGestureType::Swipe)
543         return;
544
545     if (m_hasOutstandingRepaintRequest)
546         return;
547
548     m_hasOutstandingRepaintRequest = true;
549
550     uint64_t pageID = m_webPageProxy.pageID();
551     GestureID gestureID = m_currentGestureID;
552     m_webPageProxy.forceRepaint(VoidCallback::create([pageID, gestureID] (CallbackBase::Error error) {
553         if (auto gestureController = controllerForGesture(pageID, gestureID))
554             gestureController->removeSwipeSnapshot();
555     }));
556 }
557
558 void ViewGestureController::willEndSwipeGesture(WebBackForwardListItem& targetItem, bool cancelled)
559 {
560     m_webPageProxy.navigationGestureWillEnd(!cancelled, targetItem);
561 }
562
563 void ViewGestureController::endSwipeGesture(WebBackForwardListItem* targetItem, bool cancelled)
564 {
565     ASSERT(m_activeGestureType == ViewGestureType::Swipe);
566     ASSERT(targetItem);
567
568 #if PLATFORM(MAC)
569     m_swipeCancellationTracker = nullptr;
570 #endif
571
572     if (cancelled) {
573         removeSwipeSnapshot();
574         m_webPageProxy.navigationGestureDidEnd(false, *targetItem);
575         return;
576     }
577
578     uint64_t renderTreeSize = 0;
579     if (ViewSnapshot* snapshot = targetItem->snapshot())
580         renderTreeSize = snapshot->renderTreeSize();
581     auto renderTreeSizeThreshold = renderTreeSize * swipeSnapshotRemovalRenderTreeSizeTargetFraction;
582
583     m_webPageProxy.navigationGestureDidEnd(true, *targetItem);
584     m_webPageProxy.goToBackForwardItem(*targetItem);
585
586     auto* currentItem = m_webPageProxy.backForwardList().currentItem();
587     // The main frame will not be navigated so hide the snapshot right away.
588     if (currentItem && currentItem->itemIsClone(*targetItem)) {
589         removeSwipeSnapshot();
590         return;
591     }
592
593     SnapshotRemovalTracker::Events desiredEvents = SnapshotRemovalTracker::VisuallyNonEmptyLayout
594         | SnapshotRemovalTracker::MainFrameLoad
595         | SnapshotRemovalTracker::SubresourceLoads
596         | SnapshotRemovalTracker::ScrollPositionRestoration;
597
598     if (renderTreeSizeThreshold) {
599         desiredEvents |= SnapshotRemovalTracker::RenderTreeSizeThreshold;
600         m_snapshotRemovalTracker.setRenderTreeSizeThreshold(renderTreeSizeThreshold);
601     }
602
603     m_snapshotRemovalTracker.start(desiredEvents, [this] { this->forceRepaintIfNeeded(); });
604
605     // FIXME: Like on iOS, we should ensure that even if one of the timeouts fires,
606     // we never show the old page content, instead showing the snapshot background color.
607
608     if (ViewSnapshot* snapshot = targetItem->snapshot())
609         m_backgroundColorForCurrentSnapshot = snapshot->backgroundColor();
610 }
611
612 void ViewGestureController::requestRenderTreeSizeNotificationIfNeeded()
613 {
614     if (!m_snapshotRemovalTracker.hasOutstandingEvent(SnapshotRemovalTracker::RenderTreeSizeThreshold))
615         return;
616
617     auto& process = m_webPageProxy.provisionalPageProxy() ? m_webPageProxy.provisionalPageProxy()->process() : m_webPageProxy.process();
618     auto threshold = m_snapshotRemovalTracker.renderTreeSizeThreshold();
619
620     process.send(Messages::ViewGestureGeometryCollector::SetRenderTreeSizeNotificationThreshold(threshold), m_webPageProxy.pageID());
621 }
622 #endif
623
624 } // namespace WebKit