iOS: Crash in InteractiveUpdateHandler set by ViewGestureController::beginSwipeGesture
[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 namespace WebKit {
41 using namespace WebCore;
42
43 static const Seconds swipeSnapshotRemovalWatchdogAfterFirstVisuallyNonEmptyLayoutDuration { 3_s };
44 static const Seconds swipeSnapshotRemovalActiveLoadMonitoringInterval { 250_ms };
45
46 #if PLATFORM(MAC)
47 static const Seconds swipeSnapshotRemovalWatchdogDuration = 5_s;
48 #else
49 static const Seconds swipeSnapshotRemovalWatchdogDuration = 3_s;
50 #endif
51
52 static HashMap<uint64_t, ViewGestureController*>& viewGestureControllersForAllPages()
53 {
54     // The key in this map is the associated page ID.
55     static NeverDestroyed<HashMap<uint64_t, ViewGestureController*>> viewGestureControllers;
56     return viewGestureControllers.get();
57 }
58
59 ViewGestureController::ViewGestureController(WebPageProxy& webPageProxy)
60     : m_webPageProxy(webPageProxy)
61     , m_swipeActiveLoadMonitoringTimer(RunLoop::main(), this, &ViewGestureController::checkForActiveLoads)
62 #if PLATFORM(MAC)
63     , m_pendingSwipeTracker(webPageProxy, *this)
64 #endif
65 {
66     connectToProcess();
67
68     viewGestureControllersForAllPages().add(webPageProxy.pageID(), this);
69 }
70
71 ViewGestureController::~ViewGestureController()
72 {
73     platformTeardown();
74
75     viewGestureControllersForAllPages().remove(m_webPageProxy.pageID());
76
77     disconnectFromProcess();
78 }
79
80 void ViewGestureController::disconnectFromProcess()
81 {
82     if (!m_isConnectedToProcess)
83         return;
84
85     m_webPageProxy.process().removeMessageReceiver(Messages::ViewGestureController::messageReceiverName(), m_webPageProxy.pageID());
86     m_isConnectedToProcess = false;
87 }
88
89 void ViewGestureController::connectToProcess()
90 {
91     if (m_isConnectedToProcess)
92         return;
93
94     m_webPageProxy.process().addMessageReceiver(Messages::ViewGestureController::messageReceiverName(), m_webPageProxy.pageID(), *this);
95     m_isConnectedToProcess = true;
96 }
97
98 ViewGestureController* ViewGestureController::controllerForGesture(uint64_t pageID, ViewGestureController::GestureID gestureID)
99 {
100     auto gestureControllerIter = viewGestureControllersForAllPages().find(pageID);
101     if (gestureControllerIter == viewGestureControllersForAllPages().end())
102         return nullptr;
103     if (gestureControllerIter->value->m_currentGestureID != gestureID)
104         return nullptr;
105     return gestureControllerIter->value;
106 }
107
108 ViewGestureController::GestureID ViewGestureController::takeNextGestureID()
109 {
110     static GestureID nextGestureID;
111     return ++nextGestureID;
112 }
113
114 void ViewGestureController::willBeginGesture(ViewGestureType type)
115 {
116     m_activeGestureType = type;
117     m_currentGestureID = takeNextGestureID();
118 }
119
120 void ViewGestureController::didEndGesture()
121 {
122     m_activeGestureType = ViewGestureType::None;
123     m_currentGestureID = 0;
124 }
125     
126 void ViewGestureController::setAlternateBackForwardListSourcePage(WebPageProxy* page)
127 {
128     m_alternateBackForwardListSourcePage = makeWeakPtr(page);
129 }
130     
131 bool ViewGestureController::canSwipeInDirection(SwipeDirection direction) const
132 {
133     if (!m_swipeGestureEnabled)
134         return false;
135
136 #if ENABLE(FULLSCREEN_API)
137     if (m_webPageProxy.fullScreenManager() && m_webPageProxy.fullScreenManager()->isFullScreen())
138         return false;
139 #endif
140
141     RefPtr<WebPageProxy> alternateBackForwardListSourcePage = m_alternateBackForwardListSourcePage.get();
142     auto& backForwardList = alternateBackForwardListSourcePage ? alternateBackForwardListSourcePage->backForwardList() : m_webPageProxy.backForwardList();
143     if (direction == SwipeDirection::Back)
144         return !!backForwardList.backItem();
145     return !!backForwardList.forwardItem();
146 }
147
148 void ViewGestureController::didStartProvisionalOrSameDocumentLoadForMainFrame()
149 {
150     m_snapshotRemovalTracker.resume();
151 #if PLATFORM(MAC)
152     requestRenderTreeSizeNotificationIfNeeded();
153 #endif
154
155     if (auto loadCallback = WTFMove(m_loadCallback))
156         loadCallback();
157 }
158
159 void ViewGestureController::didStartProvisionalLoadForMainFrame()
160 {
161     didStartProvisionalOrSameDocumentLoadForMainFrame();
162 }
163
164 void ViewGestureController::didFirstVisuallyNonEmptyLayoutForMainFrame()
165 {
166     if (!m_snapshotRemovalTracker.eventOccurred(SnapshotRemovalTracker::VisuallyNonEmptyLayout))
167         return;
168
169     m_snapshotRemovalTracker.cancelOutstandingEvent(SnapshotRemovalTracker::MainFrameLoad);
170     m_snapshotRemovalTracker.cancelOutstandingEvent(SnapshotRemovalTracker::SubresourceLoads);
171     m_snapshotRemovalTracker.startWatchdog(swipeSnapshotRemovalWatchdogAfterFirstVisuallyNonEmptyLayoutDuration);
172 }
173
174 void ViewGestureController::didRepaintAfterNavigation()
175 {
176     m_snapshotRemovalTracker.eventOccurred(SnapshotRemovalTracker::RepaintAfterNavigation);
177 }
178
179 void ViewGestureController::didHitRenderTreeSizeThreshold()
180 {
181     m_snapshotRemovalTracker.eventOccurred(SnapshotRemovalTracker::RenderTreeSizeThreshold);
182 }
183
184 void ViewGestureController::didRestoreScrollPosition()
185 {
186     m_snapshotRemovalTracker.eventOccurred(SnapshotRemovalTracker::ScrollPositionRestoration);
187 }
188
189 void ViewGestureController::didReachMainFrameLoadTerminalState()
190 {
191     if (m_snapshotRemovalTracker.isPaused() && m_snapshotRemovalTracker.hasRemovalCallback()) {
192         removeSwipeSnapshot();
193         return;
194     }
195
196     if (!m_snapshotRemovalTracker.eventOccurred(SnapshotRemovalTracker::MainFrameLoad))
197         return;
198
199     // Coming back from the page cache will result in getting a load event, but no first visually non-empty layout.
200     // WebCore considers a loaded document enough to be considered visually non-empty, so that's good
201     // enough for us too.
202     m_snapshotRemovalTracker.cancelOutstandingEvent(SnapshotRemovalTracker::VisuallyNonEmptyLayout);
203
204     checkForActiveLoads();
205 }
206
207 void ViewGestureController::didSameDocumentNavigationForMainFrame(SameDocumentNavigationType type)
208 {
209     didStartProvisionalOrSameDocumentLoadForMainFrame();
210
211     bool cancelledOutstandingEvent = false;
212
213     // Same-document navigations don't have a main frame load or first visually non-empty layout.
214     cancelledOutstandingEvent |= m_snapshotRemovalTracker.cancelOutstandingEvent(SnapshotRemovalTracker::MainFrameLoad);
215     cancelledOutstandingEvent |= m_snapshotRemovalTracker.cancelOutstandingEvent(SnapshotRemovalTracker::VisuallyNonEmptyLayout);
216
217     if (!cancelledOutstandingEvent)
218         return;
219
220     if (type != SameDocumentNavigationSessionStateReplace && type != SameDocumentNavigationSessionStatePop)
221         return;
222
223     checkForActiveLoads();
224 }
225
226 void ViewGestureController::checkForActiveLoads()
227 {
228     if (m_webPageProxy.pageLoadState().isLoading()) {
229         if (!m_swipeActiveLoadMonitoringTimer.isActive())
230             m_swipeActiveLoadMonitoringTimer.startRepeating(swipeSnapshotRemovalActiveLoadMonitoringInterval);
231         return;
232     }
233
234     m_swipeActiveLoadMonitoringTimer.stop();
235     m_snapshotRemovalTracker.eventOccurred(SnapshotRemovalTracker::SubresourceLoads);
236 }
237
238 ViewGestureController::SnapshotRemovalTracker::SnapshotRemovalTracker()
239     : m_watchdogTimer(RunLoop::main(), this, &SnapshotRemovalTracker::watchdogTimerFired)
240 {
241 }
242
243 String ViewGestureController::SnapshotRemovalTracker::eventsDescription(Events event)
244 {
245     StringBuilder description;
246
247     if (event & ViewGestureController::SnapshotRemovalTracker::VisuallyNonEmptyLayout)
248         description.append("VisuallyNonEmptyLayout ");
249
250     if (event & ViewGestureController::SnapshotRemovalTracker::RenderTreeSizeThreshold)
251         description.append("RenderTreeSizeThreshold ");
252
253     if (event & ViewGestureController::SnapshotRemovalTracker::RepaintAfterNavigation)
254         description.append("RepaintAfterNavigation ");
255
256     if (event & ViewGestureController::SnapshotRemovalTracker::MainFrameLoad)
257         description.append("MainFrameLoad ");
258
259     if (event & ViewGestureController::SnapshotRemovalTracker::SubresourceLoads)
260         description.append("SubresourceLoads ");
261
262     if (event & ViewGestureController::SnapshotRemovalTracker::ScrollPositionRestoration)
263         description.append("ScrollPositionRestoration ");
264     
265     return description.toString();
266 }
267
268
269 void ViewGestureController::SnapshotRemovalTracker::log(const String& log) const
270 {
271     auto sinceStart = MonotonicTime::now() - m_startTime;
272     RELEASE_LOG(ViewGestures, "Swipe Snapshot Removal (%0.2f ms) - %{public}s", sinceStart.milliseconds(), log.utf8().data());
273 }
274     
275 void ViewGestureController::SnapshotRemovalTracker::resume()
276 {
277     if (isPaused() && m_outstandingEvents)
278         log("resume");
279     m_paused = false;
280 }
281
282 void ViewGestureController::SnapshotRemovalTracker::start(Events desiredEvents, WTF::Function<void()>&& removalCallback)
283 {
284     m_outstandingEvents = desiredEvents;
285     m_removalCallback = WTFMove(removalCallback);
286     m_startTime = MonotonicTime::now();
287
288     log("start");
289
290     startWatchdog(swipeSnapshotRemovalWatchdogDuration);
291     
292     // Initially start out paused; we'll resume when the load is committed.
293     // This avoids processing callbacks from earlier loads.
294     pause();
295 }
296
297 void ViewGestureController::SnapshotRemovalTracker::reset()
298 {
299     if (m_outstandingEvents)
300         log("reset; had outstanding events: " + eventsDescription(m_outstandingEvents));
301     m_outstandingEvents = 0;
302     m_watchdogTimer.stop();
303     m_removalCallback = nullptr;
304 }
305
306 bool ViewGestureController::SnapshotRemovalTracker::stopWaitingForEvent(Events event, const String& logReason)
307 {
308     ASSERT(hasOneBitSet(event));
309
310     if (!(m_outstandingEvents & event))
311         return false;
312
313     if (isPaused()) {
314         log("is paused; ignoring event: " + eventsDescription(event));
315         return false;
316     }
317
318     log(logReason + eventsDescription(event));
319
320     m_outstandingEvents &= ~event;
321
322     fireRemovalCallbackIfPossible();
323     return true;
324 }
325
326 bool ViewGestureController::SnapshotRemovalTracker::eventOccurred(Events event)
327 {
328     return stopWaitingForEvent(event, "outstanding event occurred: ");
329 }
330
331 bool ViewGestureController::SnapshotRemovalTracker::cancelOutstandingEvent(Events event)
332 {
333     return stopWaitingForEvent(event, "wait for event cancelled: ");
334 }
335
336 bool ViewGestureController::SnapshotRemovalTracker::hasOutstandingEvent(Event event)
337 {
338     return m_outstandingEvents & event;
339 }
340
341 void ViewGestureController::SnapshotRemovalTracker::fireRemovalCallbackIfPossible()
342 {
343     if (m_outstandingEvents) {
344         log("deferring removal; had outstanding events: " + eventsDescription(m_outstandingEvents));
345         return;
346     }
347
348     fireRemovalCallbackImmediately();
349 }
350
351 void ViewGestureController::SnapshotRemovalTracker::fireRemovalCallbackImmediately()
352 {
353     m_watchdogTimer.stop();
354
355     auto removalCallback = WTFMove(m_removalCallback);
356     if (removalCallback) {
357         log("removing snapshot");
358         reset();
359         removalCallback();
360     }
361 }
362
363 void ViewGestureController::SnapshotRemovalTracker::watchdogTimerFired()
364 {
365     log("watchdog timer fired");
366     fireRemovalCallbackImmediately();
367 }
368
369 void ViewGestureController::SnapshotRemovalTracker::startWatchdog(Seconds duration)
370 {
371     log(String::format("(re)started watchdog timer for %.1f seconds", duration.seconds()));
372     m_watchdogTimer.startOneShot(duration);
373 }
374
375 } // namespace WebKit