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
50namespace WebKit {
51using namespace WebCore;
52
53static const Seconds swipeSnapshotRemovalWatchdogAfterFirstVisuallyNonEmptyLayoutDuration { 3_s };
54static const Seconds swipeSnapshotRemovalActiveLoadMonitoringInterval { 250_ms };
55
56#if !PLATFORM(IOS_FAMILY)
57static const Seconds swipeSnapshotRemovalWatchdogDuration = 5_s;
58#else
59static const Seconds swipeSnapshotRemovalWatchdogDuration = 3_s;
60#endif
61
62#if !PLATFORM(IOS_FAMILY)
63static const float minimumHorizontalSwipeDistance = 15;
64static const float minimumScrollEventRatioForSwipe = 0.5;
65
66static const float swipeSnapshotRemovalRenderTreeSizeTargetFraction = 0.5;
67#endif
68
69static HashMap<PageIdentifier, ViewGestureController*>& viewGestureControllersForAllPages()
70{
71 // The key in this map is the associated page ID.
72 static NeverDestroyed<HashMap<PageIdentifier, ViewGestureController*>> viewGestureControllers;
73 return viewGestureControllers.get();
74}
75
76ViewGestureController::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
92ViewGestureController::~ViewGestureController()
93{
94 platformTeardown();
95
96 viewGestureControllersForAllPages().remove(m_webPageProxy.pageID());
97
98 disconnectFromProcess();
99}
100
101void 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
110void 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
119ViewGestureController* ViewGestureController::controllerForGesture(PageIdentifier 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
129ViewGestureController::GestureID ViewGestureController::takeNextGestureID()
130{
131 static GestureID nextGestureID;
132 return ++nextGestureID;
133}
134
135void ViewGestureController::willBeginGesture(ViewGestureType type)
136{
137 m_activeGestureType = type;
138 m_currentGestureID = takeNextGestureID();
139}
140
141void ViewGestureController::didEndGesture()
142{
143 m_activeGestureType = ViewGestureType::None;
144 m_currentGestureID = 0;
145}
146
147void ViewGestureController::setAlternateBackForwardListSourcePage(WebPageProxy* page)
148{
149 m_alternateBackForwardListSourcePage = makeWeakPtr(page);
150}
151
152bool 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
169void 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
180void ViewGestureController::didStartProvisionalLoadForMainFrame()
181{
182 didStartProvisionalOrSameDocumentLoadForMainFrame();
183}
184
185void 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
195void ViewGestureController::didRepaintAfterNavigation()
196{
197 m_snapshotRemovalTracker.eventOccurred(SnapshotRemovalTracker::RepaintAfterNavigation);
198}
199
200void ViewGestureController::didHitRenderTreeSizeThreshold()
201{
202 m_snapshotRemovalTracker.eventOccurred(SnapshotRemovalTracker::RenderTreeSizeThreshold);
203}
204
205void ViewGestureController::didRestoreScrollPosition()
206{
207 m_snapshotRemovalTracker.eventOccurred(SnapshotRemovalTracker::ScrollPositionRestoration);
208}
209
210void 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
228void 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
247void 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
259ViewGestureController::SnapshotRemovalTracker::SnapshotRemovalTracker()
260 : m_watchdogTimer(RunLoop::main(), this, &SnapshotRemovalTracker::watchdogTimerFired)
261{
262}
263
264String 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
290void 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
295void ViewGestureController::SnapshotRemovalTracker::resume()
296{
297 if (isPaused() && m_outstandingEvents)
298 log("resume");
299 m_paused = false;
300}
301
302void 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
317void 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
326bool 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
346bool ViewGestureController::SnapshotRemovalTracker::eventOccurred(Events event)
347{
348 return stopWaitingForEvent(event, "outstanding event occurred: ");
349}
350
351bool ViewGestureController::SnapshotRemovalTracker::cancelOutstandingEvent(Events event)
352{
353 return stopWaitingForEvent(event, "wait for event cancelled: ");
354}
355
356bool ViewGestureController::SnapshotRemovalTracker::hasOutstandingEvent(Event event)
357{
358 return m_outstandingEvents & event;
359}
360
361void 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
371void 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
383void ViewGestureController::SnapshotRemovalTracker::watchdogTimerFired()
384{
385 log("watchdog timer fired");
386 fireRemovalCallbackImmediately();
387}
388
389void 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)
396static bool deltaShouldCancelSwipe(FloatSize delta)
397{
398 return std::abs(delta.height()) >= std::abs(delta.width()) * minimumScrollEventRatioForSwipe;
399}
400
401ViewGestureController::PendingSwipeTracker::PendingSwipeTracker(WebPageProxy& webPageProxy, ViewGestureController& viewGestureController)
402 : m_viewGestureController(viewGestureController)
403 , m_webPageProxy(webPageProxy)
404{
405}
406
407bool 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
432bool 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
455void 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
466bool 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
495void 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
504void 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
519bool 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
526bool 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
540void 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 auto 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
558void ViewGestureController::willEndSwipeGesture(WebBackForwardListItem& targetItem, bool cancelled)
559{
560 m_webPageProxy.navigationGestureWillEnd(!cancelled, targetItem);
561}
562
563void 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
612void 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
625