fe8472bf2ee8d463bd280e5cb4b4d5076075eb5d
[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 "WebReplayInputs.h"
48 #include <replay/EmptyInputCursor.h>
49 #include <wtf/text/CString.h>
50
51 namespace WebCore {
52
53 ReplayController::ReplayController(Page& page)
54     : m_page(page)
55     , m_loadedSegment(nullptr)
56     , m_loadedSession(ReplaySession::create())
57     , m_emptyCursor(EmptyInputCursor::create())
58     , m_activeCursor(nullptr)
59     , m_targetPosition(ReplayPosition(0, 0))
60     , m_currentPosition(ReplayPosition(0, 0))
61     , m_segmentState(SegmentState::Unloaded)
62     , m_sessionState(SessionState::Inactive)
63     , m_dispatchSpeed(DispatchSpeed::FastForward)
64 {
65 }
66
67 void ReplayController::switchSession(PassRefPtr<ReplaySession> session)
68 {
69     ASSERT(m_segmentState == SegmentState::Unloaded);
70     ASSERT(m_sessionState == SessionState::Inactive);
71
72     m_loadedSession = session;
73     m_currentPosition = ReplayPosition(0, 0);
74
75     LOG(WebReplay, "%-20sSwitching sessions from %p to %p.\n", "ReplayController", m_loadedSession.get(), session.get());
76     InspectorInstrumentation::sessionLoaded(&m_page, m_loadedSession);
77 }
78
79 void ReplayController::createSegment()
80 {
81     ASSERT(m_sessionState == SessionState::Capturing);
82     ASSERT(m_segmentState == SegmentState::Unloaded);
83
84     m_segmentState = SegmentState::Appending;
85
86     // Create a new segment but don't associate it with the current session
87     // until we stop appending to it. This preserves the invariant that
88     // segments associated with a replay session have immutable data.
89     m_loadedSegment = ReplaySessionSegment::create();
90
91     LOG(WebReplay, "%-20s Created segment: %p.\n", "ReplayController", m_loadedSegment.get());
92     InspectorInstrumentation::segmentCreated(&m_page, m_loadedSegment);
93
94     m_activeCursor = m_loadedSegment->createCapturingCursor(m_page);
95     m_activeCursor->appendInput<BeginSegmentSentinel>();
96
97     std::unique_ptr<InitialNavigation> navigationInput = InitialNavigation::createFromPage(m_page);
98     // Dispatching this input schedules navigation of the main frame, causing a refresh.
99     navigationInput->dispatch(*this);
100     m_activeCursor->storeInput(std::move(navigationInput));
101 }
102
103 void ReplayController::completeSegment()
104 {
105     ASSERT(m_sessionState == SessionState::Capturing);
106     ASSERT(m_segmentState == SegmentState::Appending);
107
108     m_activeCursor->appendInput<EndSegmentSentinel>();
109
110     // Hold on to a reference so unloading the segment doesn't deallocate it.
111     RefPtr<ReplaySessionSegment> segment = m_loadedSegment;
112     m_segmentState = SegmentState::Loaded;
113     bool shouldSuppressNotifications = true;
114     unloadSegment(shouldSuppressNotifications);
115
116     LOG(WebReplay, "%-20s Completed segment: %p.\n", "ReplayController", segment.get());
117     InspectorInstrumentation::segmentCompleted(&m_page, segment);
118
119     m_loadedSession->appendSegment(segment);
120     InspectorInstrumentation::sessionModified(&m_page, m_loadedSession);
121 }
122
123 void ReplayController::loadSegmentAtIndex(size_t segmentIndex)
124 {
125     ASSERT(segmentIndex < m_loadedSession->size());
126     RefPtr<ReplaySessionSegment> segment = m_loadedSession->at(segmentIndex);
127
128     ASSERT(m_sessionState == SessionState::Replaying);
129     ASSERT(m_segmentState == SegmentState::Unloaded);
130     ASSERT(segment);
131     ASSERT(!m_loadedSegment);
132
133     m_loadedSegment = segment;
134     m_segmentState = SegmentState::Loaded;
135
136     m_currentPosition.segmentOffset = segmentIndex;
137     m_currentPosition.inputOffset = 0;
138
139     m_activeCursor = m_loadedSegment->createReplayingCursor(m_page, this);
140
141     LOG(WebReplay, "%-20sLoading segment: %p.\n", "ReplayController", segment.get());
142     InspectorInstrumentation::segmentLoaded(&m_page, segment);
143 }
144
145 void ReplayController::unloadSegment(bool suppressNotifications)
146 {
147     ASSERT(m_sessionState != SessionState::Inactive);
148     ASSERT(m_segmentState == SegmentState::Loaded);
149
150     m_segmentState = SegmentState::Unloaded;
151
152     LOG(WebReplay, "%-20s Clearing input cursors for page: %p\n", "ReplayController", &m_page);
153
154     m_activeCursor = nullptr;
155     RefPtr<ReplaySessionSegment> unloadedSegment = m_loadedSegment.release();
156     for (Frame* frame = &m_page.mainFrame(); frame; frame = frame->tree().traverseNext()) {
157         frame->script().globalObject(mainThreadNormalWorld())->setInputCursor(m_emptyCursor);
158         frame->document()->setInputCursor(m_emptyCursor);
159     }
160
161     // When we stop capturing, don't send out segment unloaded events since we
162     // didn't send out the corresponding segmentLoaded event at the start of capture.
163     if (!suppressNotifications) {
164         LOG(WebReplay, "%-20sUnloading segment: %p.\n", "ReplayController", unloadedSegment.get());
165         InspectorInstrumentation::segmentUnloaded(&m_page);
166     }
167 }
168
169 void ReplayController::startCapturing()
170 {
171     ASSERT(m_sessionState == SessionState::Inactive);
172     ASSERT(m_segmentState == SegmentState::Unloaded);
173
174     m_sessionState = SessionState::Capturing;
175
176     LOG(WebReplay, "%-20s Starting capture.\n", "ReplayController");
177     InspectorInstrumentation::captureStarted(&m_page);
178
179     m_currentPosition = ReplayPosition(0, 0);
180     createSegment();
181 }
182
183 void ReplayController::stopCapturing()
184 {
185     ASSERT(m_sessionState == SessionState::Capturing);
186     ASSERT(m_segmentState == SegmentState::Appending);
187
188     completeSegment();
189
190     m_sessionState = SessionState::Inactive;
191
192     LOG(WebReplay, "%-20s Stopping capture.\n", "ReplayController");
193     InspectorInstrumentation::captureStopped(&m_page);
194 }
195
196 void ReplayController::startPlayback()
197 {
198     ASSERT(m_sessionState == SessionState::Replaying);
199     ASSERT(m_segmentState == SegmentState::Loaded);
200
201     m_segmentState = SegmentState::Dispatching;
202
203     LOG(WebReplay, "%-20s Starting playback to position (segment: %d, input: %d).\n", "ReplayController", m_targetPosition.segmentOffset, m_targetPosition.inputOffset);
204     InspectorInstrumentation::playbackStarted(&m_page);
205
206     dispatcher().setDispatchSpeed(m_dispatchSpeed);
207     dispatcher().run();
208 }
209
210 void ReplayController::pausePlayback()
211 {
212     ASSERT(m_sessionState == SessionState::Replaying);
213     ASSERT(m_segmentState == SegmentState::Dispatching);
214
215     if (dispatcher().isRunning())
216         dispatcher().pause();
217
218     m_segmentState = SegmentState::Loaded;
219
220     LOG(WebReplay, "%-20s Pausing playback at position (segment: %d, input: %d).\n", "ReplayController", m_currentPosition.segmentOffset, m_currentPosition.inputOffset);
221     InspectorInstrumentation::playbackPaused(&m_page, m_currentPosition);
222 }
223
224 void ReplayController::cancelPlayback()
225 {
226     ASSERT(m_sessionState == SessionState::Replaying);
227     ASSERT(m_segmentState != SegmentState::Appending);
228
229     if (m_segmentState == SegmentState::Unloaded)
230         return;
231
232     if (m_segmentState == SegmentState::Dispatching)
233         pausePlayback();
234
235     ASSERT(m_segmentState == SegmentState::Loaded);
236     unloadSegment();
237     m_sessionState = SessionState::Inactive;
238     InspectorInstrumentation::playbackFinished(&m_page);
239 }
240
241 void ReplayController::replayToPosition(const ReplayPosition& position, DispatchSpeed speed)
242 {
243     ASSERT(m_sessionState != SessionState::Capturing);
244     ASSERT(m_segmentState == SegmentState::Loaded || m_segmentState == SegmentState::Unloaded);
245     ASSERT(position.segmentOffset < m_loadedSession->size());
246
247     m_dispatchSpeed = speed;
248
249     if (m_sessionState != SessionState::Replaying)
250         m_sessionState = SessionState::Replaying;
251
252     if (m_segmentState == SegmentState::Unloaded)
253         loadSegmentAtIndex(position.segmentOffset);
254     else if (position.segmentOffset != m_currentPosition.segmentOffset || m_currentPosition.inputOffset > position.inputOffset) {
255         // If the desired segment is not loaded or we have gone past the desired input
256         // offset, then unload the current segment and load the appropriate segment.
257         unloadSegment();
258         loadSegmentAtIndex(position.segmentOffset);
259     }
260
261     ASSERT(m_currentPosition.segmentOffset == position.segmentOffset);
262     ASSERT(m_loadedSession->at(position.segmentOffset) == m_loadedSegment);
263
264     m_targetPosition = position;
265     startPlayback();
266 }
267
268 void ReplayController::frameNavigated(DocumentLoader* loader)
269 {
270     ASSERT(m_sessionState != SessionState::Inactive);
271
272     // The initial capturing segment is created prior to main frame navigation.
273     // Otherwise, the prior capturing segment was completed when the frame detached,
274     // and it is now time to create a new segment.
275     if (m_sessionState == SessionState::Capturing && m_segmentState == SegmentState::Unloaded) {
276         m_currentPosition = ReplayPosition(m_currentPosition.segmentOffset + 1, 0);
277         createSegment();
278     }
279
280     // During playback, the next segment is loaded when the final input is dispatched,
281     // so nothing needs to be done here.
282
283     // We store the input cursor in both Document and JSDOMWindow, so that
284     // replay state is accessible from JavaScriptCore and script-free layout code.
285     loader->frame()->document()->setInputCursor(m_activeCursor.get());
286     loader->frame()->script().globalObject(mainThreadNormalWorld())->setInputCursor(m_activeCursor.get());
287 }
288
289 void ReplayController::frameDetached(Frame* frame)
290 {
291     ASSERT(m_sessionState != SessionState::Inactive);
292     ASSERT(frame);
293
294     if (!frame->document())
295         return;
296
297     // If the frame's cursor isn't capturing or replaying, we should do nothing.
298     // This is the case for the "outbound" frame when starting capture, or when
299     // we clear the input cursor to finish or prematurely unload a segment.
300     if (frame->document()->inputCursor().isCapturing()) {
301         ASSERT(m_segmentState == SegmentState::Appending);
302         completeSegment();
303     }
304
305     // During playback, the segments are unloaded and loaded when the final
306     // input has been dispatched. So, nothing needs to be done here.
307 }
308
309 PassRefPtr<ReplaySession> ReplayController::loadedSession() const
310 {
311     return m_loadedSession;
312 }
313
314 PassRefPtr<ReplaySessionSegment> ReplayController::loadedSegment() const
315 {
316     return m_loadedSegment;
317 }
318
319 InputCursor& ReplayController::activeInputCursor() const
320 {
321     return m_activeCursor ? *m_activeCursor : *m_emptyCursor;
322 }
323
324 EventLoopInputDispatcher& ReplayController::dispatcher() const
325 {
326     ASSERT(m_sessionState == SessionState::Replaying);
327     ASSERT(m_segmentState == SegmentState::Dispatching);
328     ASSERT(m_activeCursor);
329     ASSERT(m_activeCursor->isReplaying());
330
331     return static_cast<ReplayingInputCursor&>(*m_activeCursor).dispatcher();
332 }
333
334 void ReplayController::willDispatchInput(const EventLoopInputBase&)
335 {
336     ASSERT(m_sessionState == SessionState::Replaying);
337     ASSERT(m_segmentState == SegmentState::Dispatching);
338
339     m_currentPosition.inputOffset++;
340     if (m_currentPosition == m_targetPosition)
341         pausePlayback();
342 }
343
344 void ReplayController::didDispatchInput(const EventLoopInputBase&)
345 {
346     ASSERT(m_sessionState == SessionState::Replaying);
347     ASSERT(m_segmentState == SegmentState::Dispatching);
348
349     InspectorInstrumentation::playbackHitPosition(&m_page, m_currentPosition);
350 }
351
352 void ReplayController::didDispatchFinalInput()
353 {
354     ASSERT(m_segmentState == SegmentState::Dispatching);
355
356     // No more segments left to replay; stop.
357     if (m_currentPosition.segmentOffset + 1 == m_loadedSession->size()) {
358         // Normally the position is adjusted when loading the next segment.
359         m_currentPosition.segmentOffset++;
360         m_currentPosition.inputOffset = 0;
361
362         cancelPlayback();
363         return;
364     }
365
366     unloadSegment();
367     loadSegmentAtIndex(m_currentPosition.segmentOffset + 1);
368     startPlayback();
369 }
370
371 } // namespace WebCore
372
373 #endif // ENABLE(WEB_REPLAY)