Unreviewed, rolling out r234489.
[WebKit-https.git] / Source / WebKit / UIProcess / Cocoa / 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 #import "config.h"
27 #import "ViewGestureController.h"
28
29 #import "Logging.h"
30 #import "RemoteLayerTreeDrawingAreaProxy.h"
31 #import "ViewGestureControllerMessages.h"
32 #import "WebBackForwardList.h"
33 #import "WebFullScreenManagerProxy.h"
34 #import "WebPageProxy.h"
35 #import "WebProcessProxy.h"
36 #import <wtf/MathExtras.h>
37 #import <wtf/NeverDestroyed.h>
38 #import <wtf/text/StringBuilder.h>
39
40 using namespace WebCore;
41
42 namespace WebKit {
43
44 static const Seconds swipeSnapshotRemovalWatchdogAfterFirstVisuallyNonEmptyLayoutDuration { 3_s };
45 static const Seconds swipeSnapshotRemovalActiveLoadMonitoringInterval { 250_ms };
46
47 #if PLATFORM(MAC)
48 static const Seconds swipeSnapshotRemovalWatchdogDuration = 5_s;
49 #else
50 static const Seconds swipeSnapshotRemovalWatchdogDuration = 3_s;
51 #endif
52
53 static HashMap<uint64_t, ViewGestureController*>& viewGestureControllersForAllPages()
54 {
55     // The key in this map is the associated page ID.
56     static NeverDestroyed<HashMap<uint64_t, ViewGestureController*>> viewGestureControllers;
57     return viewGestureControllers.get();
58 }
59
60 ViewGestureController::ViewGestureController(WebPageProxy& webPageProxy)
61     : m_webPageProxy(webPageProxy)
62     , m_swipeActiveLoadMonitoringTimer(RunLoop::main(), this, &ViewGestureController::checkForActiveLoads)
63 #if PLATFORM(MAC)
64     , m_pendingSwipeTracker(webPageProxy, *this)
65 #endif
66 {
67     m_webPageProxy.process().addMessageReceiver(Messages::ViewGestureController::messageReceiverName(), m_webPageProxy.pageID(), *this);
68
69     viewGestureControllersForAllPages().add(webPageProxy.pageID(), this);
70 }
71
72 ViewGestureController::~ViewGestureController()
73 {
74     platformTeardown();
75
76     viewGestureControllersForAllPages().remove(m_webPageProxy.pageID());
77
78     m_webPageProxy.process().removeMessageReceiver(Messages::ViewGestureController::messageReceiverName(), m_webPageProxy.pageID());
79 }
80
81 ViewGestureController* ViewGestureController::controllerForGesture(uint64_t pageID, ViewGestureController::GestureID gestureID)
82 {
83     auto gestureControllerIter = viewGestureControllersForAllPages().find(pageID);
84     if (gestureControllerIter == viewGestureControllersForAllPages().end())
85         return nullptr;
86     if (gestureControllerIter->value->m_currentGestureID != gestureID)
87         return nullptr;
88     return gestureControllerIter->value;
89 }
90
91 ViewGestureController::GestureID ViewGestureController::takeNextGestureID()
92 {
93     static GestureID nextGestureID;
94     return ++nextGestureID;
95 }
96
97 void ViewGestureController::willBeginGesture(ViewGestureType type)
98 {
99     m_activeGestureType = type;
100     m_currentGestureID = takeNextGestureID();
101 }
102
103 void ViewGestureController::didEndGesture()
104 {
105     m_activeGestureType = ViewGestureType::None;
106     m_currentGestureID = 0;
107 }
108     
109 void ViewGestureController::setAlternateBackForwardListSourcePage(WebPageProxy* page)
110 {
111     m_alternateBackForwardListSourcePage = makeWeakPtr(page);
112 }
113     
114 bool ViewGestureController::canSwipeInDirection(SwipeDirection direction) const
115 {
116 #if ENABLE(FULLSCREEN_API)
117     if (m_webPageProxy.fullScreenManager() && m_webPageProxy.fullScreenManager()->isFullScreen())
118         return false;
119 #endif
120     RefPtr<WebPageProxy> alternateBackForwardListSourcePage = m_alternateBackForwardListSourcePage.get();
121     auto& backForwardList = alternateBackForwardListSourcePage ? alternateBackForwardListSourcePage->backForwardList() : m_webPageProxy.backForwardList();
122     if (direction == SwipeDirection::Back)
123         return !!backForwardList.backItem();
124     return !!backForwardList.forwardItem();
125 }
126
127 void ViewGestureController::didStartProvisionalLoadForMainFrame()
128 {
129     if (auto provisionalOrSameDocumentLoadCallback = WTFMove(m_provisionalOrSameDocumentLoadCallback))
130         provisionalOrSameDocumentLoadCallback();
131 }
132
133
134 void ViewGestureController::didFirstVisuallyNonEmptyLayoutForMainFrame()
135 {
136     if (!m_snapshotRemovalTracker.eventOccurred(SnapshotRemovalTracker::VisuallyNonEmptyLayout))
137         return;
138
139     m_snapshotRemovalTracker.cancelOutstandingEvent(SnapshotRemovalTracker::MainFrameLoad);
140     m_snapshotRemovalTracker.cancelOutstandingEvent(SnapshotRemovalTracker::SubresourceLoads);
141     m_snapshotRemovalTracker.startWatchdog(swipeSnapshotRemovalWatchdogAfterFirstVisuallyNonEmptyLayoutDuration);
142 }
143
144 void ViewGestureController::didRepaintAfterNavigation()
145 {
146     m_snapshotRemovalTracker.eventOccurred(SnapshotRemovalTracker::RepaintAfterNavigation);
147 }
148
149 void ViewGestureController::didHitRenderTreeSizeThreshold()
150 {
151     m_snapshotRemovalTracker.eventOccurred(SnapshotRemovalTracker::RenderTreeSizeThreshold);
152 }
153
154 void ViewGestureController::didRestoreScrollPosition()
155 {
156     m_snapshotRemovalTracker.eventOccurred(SnapshotRemovalTracker::ScrollPositionRestoration);
157 }
158
159 void ViewGestureController::didReachMainFrameLoadTerminalState()
160 {
161     if (m_provisionalOrSameDocumentLoadCallback) {
162         m_provisionalOrSameDocumentLoadCallback = nullptr;
163         removeSwipeSnapshot();
164         return;
165     }
166
167     if (!m_snapshotRemovalTracker.eventOccurred(SnapshotRemovalTracker::MainFrameLoad))
168         return;
169
170     // Coming back from the page cache will result in getting a load event, but no first visually non-empty layout.
171     // WebCore considers a loaded document enough to be considered visually non-empty, so that's good
172     // enough for us too.
173     m_snapshotRemovalTracker.cancelOutstandingEvent(SnapshotRemovalTracker::VisuallyNonEmptyLayout);
174
175     // With Web-process scrolling, we check if the scroll position restoration succeeded by comparing the
176     // requested and actual scroll position. It's possible that we will never succeed in restoring
177     // the exact scroll position we wanted, in the case of a dynamic page, but we know that by
178     // main frame load time that we've gotten as close as we're going to get, so stop waiting.
179     // We don't want to do this with UI-side scrolling because scroll position restoration is baked into the transaction.
180     // FIXME: It seems fairly dirty to type-check the DrawingArea like this.
181     if (auto drawingArea = m_webPageProxy.drawingArea()) {
182         if (is<RemoteLayerTreeDrawingAreaProxy>(drawingArea))
183             m_snapshotRemovalTracker.cancelOutstandingEvent(SnapshotRemovalTracker::ScrollPositionRestoration);
184     }
185
186     checkForActiveLoads();
187 }
188
189 void ViewGestureController::didSameDocumentNavigationForMainFrame(SameDocumentNavigationType type)
190 {
191
192     if (auto provisionalOrSameDocumentLoadCallback = WTFMove(m_provisionalOrSameDocumentLoadCallback))
193         provisionalOrSameDocumentLoadCallback();
194
195     bool cancelledOutstandingEvent = false;
196
197     // Same-document navigations don't have a main frame load or first visually non-empty layout.
198     cancelledOutstandingEvent |= m_snapshotRemovalTracker.cancelOutstandingEvent(SnapshotRemovalTracker::MainFrameLoad);
199     cancelledOutstandingEvent |= m_snapshotRemovalTracker.cancelOutstandingEvent(SnapshotRemovalTracker::VisuallyNonEmptyLayout);
200
201     if (!cancelledOutstandingEvent)
202         return;
203
204     if (type != SameDocumentNavigationSessionStateReplace && type != SameDocumentNavigationSessionStatePop)
205         return;
206
207     checkForActiveLoads();
208 }
209
210 void ViewGestureController::checkForActiveLoads()
211 {
212     if (m_webPageProxy.pageLoadState().isLoading()) {
213         if (!m_swipeActiveLoadMonitoringTimer.isActive())
214             m_swipeActiveLoadMonitoringTimer.startRepeating(swipeSnapshotRemovalActiveLoadMonitoringInterval);
215         return;
216     }
217
218     m_swipeActiveLoadMonitoringTimer.stop();
219     m_snapshotRemovalTracker.eventOccurred(SnapshotRemovalTracker::SubresourceLoads);
220 }
221
222 ViewGestureController::SnapshotRemovalTracker::SnapshotRemovalTracker()
223     : m_watchdogTimer(RunLoop::main(), this, &SnapshotRemovalTracker::watchdogTimerFired)
224 {
225 }
226
227 String ViewGestureController::SnapshotRemovalTracker::eventsDescription(Events event)
228 {
229     StringBuilder description;
230
231     if (event & ViewGestureController::SnapshotRemovalTracker::VisuallyNonEmptyLayout)
232         description.append("VisuallyNonEmptyLayout ");
233
234     if (event & ViewGestureController::SnapshotRemovalTracker::RenderTreeSizeThreshold)
235         description.append("RenderTreeSizeThreshold ");
236
237     if (event & ViewGestureController::SnapshotRemovalTracker::RepaintAfterNavigation)
238         description.append("RepaintAfterNavigation ");
239
240     if (event & ViewGestureController::SnapshotRemovalTracker::MainFrameLoad)
241         description.append("MainFrameLoad ");
242
243     if (event & ViewGestureController::SnapshotRemovalTracker::SubresourceLoads)
244         description.append("SubresourceLoads ");
245
246     if (event & ViewGestureController::SnapshotRemovalTracker::ScrollPositionRestoration)
247         description.append("ScrollPositionRestoration ");
248     
249     return description.toString();
250 }
251
252
253 void ViewGestureController::SnapshotRemovalTracker::log(const String& log) const
254 {
255 #if !LOG_DISABLED
256     auto sinceStart = MonotonicTime::now() - m_startTime;
257 #endif
258     LOG(ViewGestures, "Swipe Snapshot Removal (%0.2f ms) - %s", sinceStart.milliseconds(), log.utf8().data());
259 }
260
261 void ViewGestureController::SnapshotRemovalTracker::start(Events desiredEvents, WTF::Function<void()>&& removalCallback)
262 {
263     m_outstandingEvents = desiredEvents;
264     m_removalCallback = WTFMove(removalCallback);
265     m_startTime = MonotonicTime::now();
266
267     log("start");
268
269     startWatchdog(swipeSnapshotRemovalWatchdogDuration);
270 }
271
272 void ViewGestureController::SnapshotRemovalTracker::reset()
273 {
274     if (m_outstandingEvents)
275         log("reset; had outstanding events: " + eventsDescription(m_outstandingEvents));
276     m_outstandingEvents = 0;
277     m_watchdogTimer.stop();
278     m_removalCallback = nullptr;
279 }
280
281 bool ViewGestureController::SnapshotRemovalTracker::stopWaitingForEvent(Events event, const String& logReason)
282 {
283     ASSERT(hasOneBitSet(event));
284
285     if (!(m_outstandingEvents & event))
286         return false;
287
288     log(logReason + eventsDescription(event));
289
290     m_outstandingEvents &= ~event;
291
292     fireRemovalCallbackIfPossible();
293     return true;
294 }
295
296 bool ViewGestureController::SnapshotRemovalTracker::eventOccurred(Events event)
297 {
298     return stopWaitingForEvent(event, "outstanding event occurred: ");
299 }
300
301 bool ViewGestureController::SnapshotRemovalTracker::cancelOutstandingEvent(Events event)
302 {
303     return stopWaitingForEvent(event, "wait for event cancelled: ");
304 }
305
306 void ViewGestureController::SnapshotRemovalTracker::fireRemovalCallbackIfPossible()
307 {
308     if (m_outstandingEvents) {
309         log("deferring removal; had outstanding events: " + eventsDescription(m_outstandingEvents));
310         return;
311     }
312
313     fireRemovalCallbackImmediately();
314 }
315
316 void ViewGestureController::SnapshotRemovalTracker::fireRemovalCallbackImmediately()
317 {
318     m_watchdogTimer.stop();
319
320     auto removalCallback = WTFMove(m_removalCallback);
321     if (removalCallback) {
322         log("removing snapshot");
323         reset();
324         removalCallback();
325     }
326 }
327
328 void ViewGestureController::SnapshotRemovalTracker::watchdogTimerFired()
329 {
330     log("watchdog timer fired");
331     fireRemovalCallbackImmediately();
332 }
333
334 void ViewGestureController::SnapshotRemovalTracker::startWatchdog(Seconds duration)
335 {
336     log(String::format("(re)started watchdog timer for %.1f seconds", duration.seconds()));
337     m_watchdogTimer.startOneShot(duration);
338 }
339
340 } // namespace WebKit