Web Replay: capture and replay mouse events
[WebKit-https.git] / Source / WebCore / replay / ReplayController.cpp
1 /*
2  * Copyright (C) 2011-2013 University of Washington. All rights reserved.
3  * Copyright (C) 2014 Apple Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1.  Redistributions of source code must retain the above copyright
10  *     notice, this list of conditions and the following disclaimer.
11  * 2.  Redistributions in binary form must reproduce the above copyright
12  *     notice, this list of conditions and the following disclaimer in the
13  *     documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
16  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
18  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19  * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #include "config.h"
29 #include "ReplayController.h"
30
31 #if ENABLE(WEB_REPLAY)
32
33 #include "AllReplayInputs.h"
34 #include "CapturingInputCursor.h"
35 #include "DOMWindow.h"
36 #include "DocumentLoader.h"
37 #include "Frame.h"
38 #include "FrameTree.h"
39 #include "InspectorInstrumentation.h"
40 #include "Logging.h"
41 #include "MainFrame.h"
42 #include "Page.h"
43 #include "ReplaySession.h"
44 #include "ReplaySessionSegment.h"
45 #include "ReplayingInputCursor.h"
46 #include "ScriptController.h"
47 #include "UserInputBridge.h"
48 #include "WebReplayInputs.h"
49 #include <replay/EmptyInputCursor.h>
50 #include <wtf/text/CString.h>
51
52 namespace WebCore {
53
54 ReplayController::ReplayController(Page& page)
55     : m_page(page)
56     , m_loadedSegment(nullptr)
57     , m_loadedSession(ReplaySession::create())
58     , m_emptyCursor(EmptyInputCursor::create())
59     , m_activeCursor(nullptr)
60     , m_targetPosition(ReplayPosition(0, 0))
61     , m_currentPosition(ReplayPosition(0, 0))
62     , m_segmentState(SegmentState::Unloaded)
63     , m_sessionState(SessionState::Inactive)
64     , m_dispatchSpeed(DispatchSpeed::FastForward)
65 {
66 }
67
68 void ReplayController::setSessionState(SessionState state)
69 {
70     ASSERT(state != m_sessionState);
71
72     switch (m_sessionState) {
73     case SessionState::Capturing:
74         ASSERT(state == SessionState::Inactive);
75
76         m_sessionState = state;
77         m_page.userInputBridge().setState(UserInputBridge::State::Capturing);
78         break;
79
80     case SessionState::Inactive:
81         m_sessionState = state;
82         m_page.userInputBridge().setState(UserInputBridge::State::Open);
83         break;
84
85     case SessionState::Replaying:
86         ASSERT(state == SessionState::Inactive);
87
88         m_sessionState = state;
89         m_page.userInputBridge().setState(UserInputBridge::State::Replaying);
90         break;
91     }
92 }
93
94 void ReplayController::switchSession(PassRefPtr<ReplaySession> session)
95 {
96     ASSERT(m_segmentState == SegmentState::Unloaded);
97     ASSERT(m_sessionState == SessionState::Inactive);
98
99     m_loadedSession = session;
100     m_currentPosition = ReplayPosition(0, 0);
101
102     LOG(WebReplay, "%-20sSwitching sessions from %p to %p.\n", "ReplayController", m_loadedSession.get(), session.get());
103     InspectorInstrumentation::sessionLoaded(&m_page, m_loadedSession);
104 }
105
106 void ReplayController::createSegment()
107 {
108     ASSERT(m_sessionState == SessionState::Capturing);
109     ASSERT(m_segmentState == SegmentState::Unloaded);
110
111     m_segmentState = SegmentState::Appending;
112
113     // Create a new segment but don't associate it with the current session
114     // until we stop appending to it. This preserves the invariant that
115     // segments associated with a replay session have immutable data.
116     m_loadedSegment = ReplaySessionSegment::create();
117
118     LOG(WebReplay, "%-20s Created segment: %p.\n", "ReplayController", m_loadedSegment.get());
119     InspectorInstrumentation::segmentCreated(&m_page, m_loadedSegment);
120
121     m_activeCursor = m_loadedSegment->createCapturingCursor(m_page);
122     m_activeCursor->appendInput<BeginSegmentSentinel>();
123
124     std::unique_ptr<InitialNavigation> navigationInput = InitialNavigation::createFromPage(m_page);
125     // Dispatching this input schedules navigation of the main frame, causing a refresh.
126     navigationInput->dispatch(*this);
127     m_activeCursor->storeInput(std::move(navigationInput));
128 }
129
130 void ReplayController::completeSegment()
131 {
132     ASSERT(m_sessionState == SessionState::Capturing);
133     ASSERT(m_segmentState == SegmentState::Appending);
134
135     m_activeCursor->appendInput<EndSegmentSentinel>();
136
137     // Hold on to a reference so unloading the segment doesn't deallocate it.
138     RefPtr<ReplaySessionSegment> segment = m_loadedSegment;
139     m_segmentState = SegmentState::Loaded;
140     bool shouldSuppressNotifications = true;
141     unloadSegment(shouldSuppressNotifications);
142
143     LOG(WebReplay, "%-20s Completed segment: %p.\n", "ReplayController", segment.get());
144     InspectorInstrumentation::segmentCompleted(&m_page, segment);
145
146     m_loadedSession->appendSegment(segment);
147     InspectorInstrumentation::sessionModified(&m_page, m_loadedSession);
148 }
149
150 void ReplayController::loadSegmentAtIndex(size_t segmentIndex)
151 {
152     ASSERT(segmentIndex < m_loadedSession->size());
153     RefPtr<ReplaySessionSegment> segment = m_loadedSession->at(segmentIndex);
154
155     ASSERT(m_sessionState == SessionState::Replaying);
156     ASSERT(m_segmentState == SegmentState::Unloaded);
157     ASSERT(segment);
158     ASSERT(!m_loadedSegment);
159
160     m_loadedSegment = segment;
161     m_segmentState = SegmentState::Loaded;
162
163     m_currentPosition.segmentOffset = segmentIndex;
164     m_currentPosition.inputOffset = 0;
165
166     m_activeCursor = m_loadedSegment->createReplayingCursor(m_page, this);
167
168     LOG(WebReplay, "%-20sLoading segment: %p.\n", "ReplayController", segment.get());
169     InspectorInstrumentation::segmentLoaded(&m_page, segment);
170 }
171
172 void ReplayController::unloadSegment(bool suppressNotifications)
173 {
174     ASSERT(m_sessionState != SessionState::Inactive);
175     ASSERT(m_segmentState == SegmentState::Loaded);
176
177     m_segmentState = SegmentState::Unloaded;
178
179     LOG(WebReplay, "%-20s Clearing input cursors for page: %p\n", "ReplayController", &m_page);
180
181     m_activeCursor = nullptr;
182     RefPtr<ReplaySessionSegment> unloadedSegment = m_loadedSegment.release();
183     for (Frame* frame = &m_page.mainFrame(); frame; frame = frame->tree().traverseNext()) {
184         frame->script().globalObject(mainThreadNormalWorld())->setInputCursor(m_emptyCursor);
185         frame->document()->setInputCursor(m_emptyCursor);
186     }
187
188     // When we stop capturing, don't send out segment unloaded events since we
189     // didn't send out the corresponding segmentLoaded event at the start of capture.
190     if (!suppressNotifications) {
191         LOG(WebReplay, "%-20sUnloading segment: %p.\n", "ReplayController", unloadedSegment.get());
192         InspectorInstrumentation::segmentUnloaded(&m_page);
193     }
194 }
195
196 void ReplayController::startCapturing()
197 {
198     ASSERT(m_sessionState == SessionState::Inactive);
199     ASSERT(m_segmentState == SegmentState::Unloaded);
200
201     setSessionState(SessionState::Capturing);
202
203     LOG(WebReplay, "%-20s Starting capture.\n", "ReplayController");
204     InspectorInstrumentation::captureStarted(&m_page);
205
206     m_currentPosition = ReplayPosition(0, 0);
207
208     createSegment();
209 }
210
211 void ReplayController::stopCapturing()
212 {
213     ASSERT(m_sessionState == SessionState::Capturing);
214     ASSERT(m_segmentState == SegmentState::Appending);
215
216     completeSegment();
217
218     setSessionState(SessionState::Inactive);
219
220     LOG(WebReplay, "%-20s Stopping capture.\n", "ReplayController");
221     InspectorInstrumentation::captureStopped(&m_page);
222 }
223
224 void ReplayController::startPlayback()
225 {
226     ASSERT(m_sessionState == SessionState::Replaying);
227     ASSERT(m_segmentState == SegmentState::Loaded);
228
229     m_segmentState = SegmentState::Dispatching;
230
231     LOG(WebReplay, "%-20s Starting playback to position (segment: %d, input: %d).\n", "ReplayController", m_targetPosition.segmentOffset, m_targetPosition.inputOffset);
232     InspectorInstrumentation::playbackStarted(&m_page);
233
234     dispatcher().setDispatchSpeed(m_dispatchSpeed);
235     dispatcher().run();
236 }
237
238 void ReplayController::pausePlayback()
239 {
240     ASSERT(m_sessionState == SessionState::Replaying);
241     ASSERT(m_segmentState == SegmentState::Dispatching);
242
243     if (dispatcher().isRunning())
244         dispatcher().pause();
245
246     m_segmentState = SegmentState::Loaded;
247
248     LOG(WebReplay, "%-20s Pausing playback at position (segment: %d, input: %d).\n", "ReplayController", m_currentPosition.segmentOffset, m_currentPosition.inputOffset);
249     InspectorInstrumentation::playbackPaused(&m_page, m_currentPosition);
250 }
251
252 void ReplayController::cancelPlayback()
253 {
254     ASSERT(m_sessionState == SessionState::Replaying);
255     ASSERT(m_segmentState != SegmentState::Appending);
256
257     if (m_segmentState == SegmentState::Unloaded)
258         return;
259
260     if (m_segmentState == SegmentState::Dispatching)
261         pausePlayback();
262
263     ASSERT(m_segmentState == SegmentState::Loaded);
264     unloadSegment();
265     m_sessionState = SessionState::Inactive;
266     InspectorInstrumentation::playbackFinished(&m_page);
267 }
268
269 void ReplayController::replayToPosition(const ReplayPosition& position, DispatchSpeed speed)
270 {
271     ASSERT(m_sessionState != SessionState::Capturing);
272     ASSERT(m_segmentState == SegmentState::Loaded || m_segmentState == SegmentState::Unloaded);
273     ASSERT(position.segmentOffset < m_loadedSession->size());
274
275     m_dispatchSpeed = speed;
276
277     if (m_sessionState != SessionState::Replaying)
278         setSessionState(SessionState::Replaying);
279
280     if (m_segmentState == SegmentState::Unloaded)
281         loadSegmentAtIndex(position.segmentOffset);
282     else if (position.segmentOffset != m_currentPosition.segmentOffset || m_currentPosition.inputOffset > position.inputOffset) {
283         // If the desired segment is not loaded or we have gone past the desired input
284         // offset, then unload the current segment and load the appropriate segment.
285         unloadSegment();
286         loadSegmentAtIndex(position.segmentOffset);
287     }
288
289     ASSERT(m_currentPosition.segmentOffset == position.segmentOffset);
290     ASSERT(m_loadedSession->at(position.segmentOffset) == m_loadedSegment);
291
292     m_targetPosition = position;
293     startPlayback();
294 }
295
296 void ReplayController::frameNavigated(DocumentLoader* loader)
297 {
298     ASSERT(m_sessionState != SessionState::Inactive);
299
300     // The initial capturing segment is created prior to main frame navigation.
301     // Otherwise, the prior capturing segment was completed when the frame detached,
302     // and it is now time to create a new segment.
303     if (m_sessionState == SessionState::Capturing && m_segmentState == SegmentState::Unloaded) {
304         m_currentPosition = ReplayPosition(m_currentPosition.segmentOffset + 1, 0);
305         createSegment();
306     }
307
308     // During playback, the next segment is loaded when the final input is dispatched,
309     // so nothing needs to be done here.
310
311     // We store the input cursor in both Document and JSDOMWindow, so that
312     // replay state is accessible from JavaScriptCore and script-free layout code.
313     loader->frame()->document()->setInputCursor(m_activeCursor.get());
314     loader->frame()->script().globalObject(mainThreadNormalWorld())->setInputCursor(m_activeCursor.get());
315 }
316
317 void ReplayController::frameDetached(Frame* frame)
318 {
319     ASSERT(m_sessionState != SessionState::Inactive);
320     ASSERT(frame);
321
322     if (!frame->document())
323         return;
324
325     // If the frame's cursor isn't capturing or replaying, we should do nothing.
326     // This is the case for the "outbound" frame when starting capture, or when
327     // we clear the input cursor to finish or prematurely unload a segment.
328     if (frame->document()->inputCursor().isCapturing()) {
329         ASSERT(m_segmentState == SegmentState::Appending);
330         completeSegment();
331     }
332
333     // During playback, the segments are unloaded and loaded when the final
334     // input has been dispatched. So, nothing needs to be done here.
335 }
336
337 PassRefPtr<ReplaySession> ReplayController::loadedSession() const
338 {
339     return m_loadedSession;
340 }
341
342 PassRefPtr<ReplaySessionSegment> ReplayController::loadedSegment() const
343 {
344     return m_loadedSegment;
345 }
346
347 InputCursor& ReplayController::activeInputCursor() const
348 {
349     return m_activeCursor ? *m_activeCursor : *m_emptyCursor;
350 }
351
352 EventLoopInputDispatcher& ReplayController::dispatcher() const
353 {
354     ASSERT(m_sessionState == SessionState::Replaying);
355     ASSERT(m_segmentState == SegmentState::Dispatching);
356     ASSERT(m_activeCursor);
357     ASSERT(m_activeCursor->isReplaying());
358
359     return static_cast<ReplayingInputCursor&>(*m_activeCursor).dispatcher();
360 }
361
362 void ReplayController::willDispatchInput(const EventLoopInputBase&)
363 {
364     ASSERT(m_sessionState == SessionState::Replaying);
365     ASSERT(m_segmentState == SegmentState::Dispatching);
366
367     m_currentPosition.inputOffset++;
368     if (m_currentPosition == m_targetPosition)
369         pausePlayback();
370 }
371
372 void ReplayController::didDispatchInput(const EventLoopInputBase&)
373 {
374     ASSERT(m_sessionState == SessionState::Replaying);
375     ASSERT(m_segmentState == SegmentState::Dispatching);
376
377     InspectorInstrumentation::playbackHitPosition(&m_page, m_currentPosition);
378 }
379
380 void ReplayController::didDispatchFinalInput()
381 {
382     ASSERT(m_segmentState == SegmentState::Dispatching);
383
384     // No more segments left to replay; stop.
385     if (m_currentPosition.segmentOffset + 1 == m_loadedSession->size()) {
386         // Normally the position is adjusted when loading the next segment.
387         m_currentPosition.segmentOffset++;
388         m_currentPosition.inputOffset = 0;
389
390         cancelPlayback();
391         return;
392     }
393
394     unloadSegment();
395     loadSegmentAtIndex(m_currentPosition.segmentOffset + 1);
396     startPlayback();
397 }
398
399 } // namespace WebCore
400
401 #endif // ENABLE(WEB_REPLAY)