Web Replay: don't encode/decode primitive types that lack explicit sizes
[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 "Location.h"
41 #include "Logging.h"
42 #include "MainFrame.h"
43 #include "Page.h"
44 #include "ReplaySession.h"
45 #include "ReplaySessionSegment.h"
46 #include "ReplayingInputCursor.h"
47 #include "ScriptController.h"
48 #include "SerializationMethods.h"
49 #include "Settings.h"
50 #include "UserInputBridge.h"
51 #include "WebReplayInputs.h"
52 #include <replay/EmptyInputCursor.h>
53 #include <wtf/text/CString.h>
54
55 #if ENABLE(ASYNC_SCROLLING)
56 #include "ScrollingCoordinator.h"
57 #endif
58
59 namespace WebCore {
60
61 static void logDispatchedDOMEvent(const Event& event, bool eventIsUnrelated)
62 {
63 #if !LOG_DISABLED
64     EventTarget* target = event.target();
65     if (!target)
66         return;
67
68     // A DOM event is unrelated if it is being dispatched to a document that is neither capturing nor replaying.
69     if (Node* node = target->toNode()) {
70         LOG(WebReplay, "%-20s --->%s DOM event: type=%s, target=%u/node[%p] %s\n", "ReplayEvents",
71             (eventIsUnrelated) ? "Unrelated" : "Dispatching",
72             event.type().string().utf8().data(),
73             frameIndexFromDocument((node->inDocument()) ? &node->document() : node->ownerDocument()),
74             node,
75             node->nodeName().utf8().data());
76     } else if (DOMWindow* window = target->toDOMWindow()) {
77         LOG(WebReplay, "%-20s --->%s DOM event: type=%s, target=%u/window[%p] %s\n", "ReplayEvents",
78             (eventIsUnrelated) ? "Unrelated" : "Dispatching",
79             event.type().string().utf8().data(),
80             frameIndexFromDocument(window->document()),
81             window,
82             window->location()->href().utf8().data());
83     }
84 #else
85     UNUSED_PARAM(event);
86     UNUSED_PARAM(eventIsUnrelated);
87 #endif
88 }
89
90 ReplayController::ReplayController(Page& page)
91     : m_page(page)
92     , m_loadedSegment(nullptr)
93     , m_loadedSession(ReplaySession::create())
94     , m_emptyCursor(EmptyInputCursor::create())
95     , m_activeCursor(nullptr)
96     , m_targetPosition(ReplayPosition(0, 0))
97     , m_currentPosition(ReplayPosition(0, 0))
98     , m_segmentState(SegmentState::Unloaded)
99     , m_sessionState(SessionState::Inactive)
100     , m_dispatchSpeed(DispatchSpeed::FastForward)
101 {
102 }
103
104 void ReplayController::setForceDeterministicSettings(bool shouldForceDeterministicBehavior)
105 {
106     ASSERT(shouldForceDeterministicBehavior ^ (m_sessionState == SessionState::Inactive));
107
108     if (shouldForceDeterministicBehavior) {
109         m_savedSettings.usesPageCache = m_page.settings().usesPageCache();
110
111         m_page.settings().setUsesPageCache(false);
112     } else {
113         m_page.settings().setUsesPageCache(m_savedSettings.usesPageCache);
114     }
115
116 #if ENABLE(ASYNC_SCROLLING)
117     if (ScrollingCoordinator* scrollingCoordinator = m_page.scrollingCoordinator())
118         scrollingCoordinator->replaySessionStateDidChange();
119 #endif
120 }
121
122 void ReplayController::setSessionState(SessionState state)
123 {
124     ASSERT(state != m_sessionState);
125
126     switch (m_sessionState) {
127     case SessionState::Capturing:
128         ASSERT(state == SessionState::Inactive);
129
130         m_sessionState = state;
131         m_page.userInputBridge().setState(UserInputBridge::State::Open);
132         break;
133
134     case SessionState::Inactive:
135         m_sessionState = state;
136         m_page.userInputBridge().setState(state == SessionState::Capturing ? UserInputBridge::State::Capturing : UserInputBridge::State::Replaying);
137         break;
138
139     case SessionState::Replaying:
140         ASSERT(state == SessionState::Inactive);
141
142         m_sessionState = state;
143         m_page.userInputBridge().setState(UserInputBridge::State::Open);
144         break;
145     }
146 }
147
148 void ReplayController::switchSession(PassRefPtr<ReplaySession> session)
149 {
150     ASSERT(m_segmentState == SegmentState::Unloaded);
151     ASSERT(m_sessionState == SessionState::Inactive);
152
153     m_loadedSession = session;
154     m_currentPosition = ReplayPosition(0, 0);
155
156     LOG(WebReplay, "%-20sSwitching sessions from %p to %p.\n", "ReplayController", m_loadedSession.get(), session.get());
157     InspectorInstrumentation::sessionLoaded(&m_page, m_loadedSession);
158 }
159
160 void ReplayController::createSegment()
161 {
162     ASSERT(m_sessionState == SessionState::Capturing);
163     ASSERT(m_segmentState == SegmentState::Unloaded);
164
165     m_segmentState = SegmentState::Appending;
166
167     // Create a new segment but don't associate it with the current session
168     // until we stop appending to it. This preserves the invariant that
169     // segments associated with a replay session have immutable data.
170     m_loadedSegment = ReplaySessionSegment::create();
171
172     LOG(WebReplay, "%-20s Created segment: %p.\n", "ReplayController", m_loadedSegment.get());
173     InspectorInstrumentation::segmentCreated(&m_page, m_loadedSegment);
174
175     m_activeCursor = m_loadedSegment->createCapturingCursor(m_page);
176     m_activeCursor->appendInput<BeginSegmentSentinel>();
177
178     std::unique_ptr<InitialNavigation> navigationInput = InitialNavigation::createFromPage(m_page);
179     // Dispatching this input schedules navigation of the main frame, causing a refresh.
180     navigationInput->dispatch(*this);
181     m_activeCursor->storeInput(WTF::move(navigationInput));
182 }
183
184 void ReplayController::completeSegment()
185 {
186     ASSERT(m_sessionState == SessionState::Capturing);
187     ASSERT(m_segmentState == SegmentState::Appending);
188
189     m_activeCursor->appendInput<EndSegmentSentinel>();
190
191     // Hold on to a reference so unloading the segment doesn't deallocate it.
192     RefPtr<ReplaySessionSegment> segment = m_loadedSegment;
193     m_segmentState = SegmentState::Loaded;
194     bool shouldSuppressNotifications = true;
195     unloadSegment(shouldSuppressNotifications);
196
197     LOG(WebReplay, "%-20s Completed segment: %p.\n", "ReplayController", segment.get());
198     InspectorInstrumentation::segmentCompleted(&m_page, segment);
199
200     m_loadedSession->appendSegment(segment);
201     InspectorInstrumentation::sessionModified(&m_page, m_loadedSession);
202 }
203
204 void ReplayController::loadSegmentAtIndex(size_t segmentIndex)
205 {
206     ASSERT(segmentIndex < m_loadedSession->size());
207     RefPtr<ReplaySessionSegment> segment = m_loadedSession->at(segmentIndex);
208
209     ASSERT(m_sessionState == SessionState::Replaying);
210     ASSERT(m_segmentState == SegmentState::Unloaded);
211     ASSERT(segment);
212     ASSERT(!m_loadedSegment);
213
214     m_loadedSegment = segment;
215     m_segmentState = SegmentState::Loaded;
216
217     m_currentPosition.segmentOffset = segmentIndex;
218     m_currentPosition.inputOffset = 0;
219
220     m_activeCursor = m_loadedSegment->createReplayingCursor(m_page, this);
221
222     LOG(WebReplay, "%-20sLoading segment: %p.\n", "ReplayController", segment.get());
223     InspectorInstrumentation::segmentLoaded(&m_page, segment);
224 }
225
226 void ReplayController::unloadSegment(bool suppressNotifications)
227 {
228     ASSERT(m_sessionState != SessionState::Inactive);
229     ASSERT(m_segmentState == SegmentState::Loaded);
230
231     m_segmentState = SegmentState::Unloaded;
232
233     LOG(WebReplay, "%-20s Clearing input cursors for page: %p\n", "ReplayController", &m_page);
234
235     m_activeCursor = nullptr;
236     RefPtr<ReplaySessionSegment> unloadedSegment = m_loadedSegment.release();
237     for (Frame* frame = &m_page.mainFrame(); frame; frame = frame->tree().traverseNext()) {
238         frame->script().globalObject(mainThreadNormalWorld())->setInputCursor(m_emptyCursor);
239         frame->document()->setInputCursor(m_emptyCursor);
240     }
241
242     // When we stop capturing, don't send out segment unloaded events since we
243     // didn't send out the corresponding segmentLoaded event at the start of capture.
244     if (!suppressNotifications) {
245         LOG(WebReplay, "%-20sUnloading segment: %p.\n", "ReplayController", unloadedSegment.get());
246         InspectorInstrumentation::segmentUnloaded(&m_page);
247     }
248 }
249
250 void ReplayController::startCapturing()
251 {
252     ASSERT(m_sessionState == SessionState::Inactive);
253     ASSERT(m_segmentState == SegmentState::Unloaded);
254
255     setSessionState(SessionState::Capturing);
256     setForceDeterministicSettings(true);
257
258     LOG(WebReplay, "%-20s Starting capture.\n", "ReplayController");
259     InspectorInstrumentation::captureStarted(&m_page);
260
261     m_currentPosition = ReplayPosition(0, 0);
262
263     createSegment();
264 }
265
266 void ReplayController::stopCapturing()
267 {
268     ASSERT(m_sessionState == SessionState::Capturing);
269     ASSERT(m_segmentState == SegmentState::Appending);
270
271     completeSegment();
272
273     setSessionState(SessionState::Inactive);
274     setForceDeterministicSettings(false);
275
276     LOG(WebReplay, "%-20s Stopping capture.\n", "ReplayController");
277     InspectorInstrumentation::captureStopped(&m_page);
278 }
279
280 void ReplayController::startPlayback()
281 {
282     ASSERT(m_sessionState == SessionState::Replaying);
283     ASSERT(m_segmentState == SegmentState::Loaded);
284
285     m_segmentState = SegmentState::Dispatching;
286
287     LOG(WebReplay, "%-20s Starting playback to position (segment: %d, input: %d).\n", "ReplayController", m_targetPosition.segmentOffset, m_targetPosition.inputOffset);
288     InspectorInstrumentation::playbackStarted(&m_page);
289
290     dispatcher().setDispatchSpeed(m_dispatchSpeed);
291     dispatcher().run();
292 }
293
294 void ReplayController::pausePlayback()
295 {
296     ASSERT(m_sessionState == SessionState::Replaying);
297     ASSERT(m_segmentState == SegmentState::Dispatching);
298
299     if (dispatcher().isRunning())
300         dispatcher().pause();
301
302     m_segmentState = SegmentState::Loaded;
303
304     LOG(WebReplay, "%-20s Pausing playback at position (segment: %d, input: %d).\n", "ReplayController", m_currentPosition.segmentOffset, m_currentPosition.inputOffset);
305     InspectorInstrumentation::playbackPaused(&m_page, m_currentPosition);
306 }
307
308 void ReplayController::cancelPlayback()
309 {
310     ASSERT(m_sessionState == SessionState::Replaying);
311     ASSERT(m_segmentState != SegmentState::Appending);
312
313     if (m_segmentState == SegmentState::Unloaded)
314         return;
315
316     if (m_segmentState == SegmentState::Dispatching)
317         pausePlayback();
318
319     ASSERT(m_segmentState == SegmentState::Loaded);
320     unloadSegment();
321     m_sessionState = SessionState::Inactive;
322     setForceDeterministicSettings(false);
323     InspectorInstrumentation::playbackFinished(&m_page);
324 }
325
326 void ReplayController::replayToPosition(const ReplayPosition& position, DispatchSpeed speed)
327 {
328     ASSERT(m_sessionState != SessionState::Capturing);
329     ASSERT(m_segmentState == SegmentState::Loaded || m_segmentState == SegmentState::Unloaded);
330     ASSERT(position.segmentOffset < m_loadedSession->size());
331
332     m_dispatchSpeed = speed;
333
334     if (m_sessionState != SessionState::Replaying) {
335         setSessionState(SessionState::Replaying);
336         setForceDeterministicSettings(true);
337     }
338
339     if (m_segmentState == SegmentState::Unloaded)
340         loadSegmentAtIndex(position.segmentOffset);
341     else if (position.segmentOffset != m_currentPosition.segmentOffset || m_currentPosition.inputOffset > position.inputOffset) {
342         // If the desired segment is not loaded or we have gone past the desired input
343         // offset, then unload the current segment and load the appropriate segment.
344         unloadSegment();
345         loadSegmentAtIndex(position.segmentOffset);
346     }
347
348     ASSERT(m_currentPosition.segmentOffset == position.segmentOffset);
349     ASSERT(m_loadedSession->at(position.segmentOffset) == m_loadedSegment);
350
351     m_targetPosition = position;
352     startPlayback();
353 }
354
355 void ReplayController::frameNavigated(DocumentLoader* loader)
356 {
357     ASSERT(m_sessionState != SessionState::Inactive);
358
359     // The initial capturing segment is created prior to main frame navigation.
360     // Otherwise, the prior capturing segment was completed when the frame detached,
361     // and it is now time to create a new segment.
362     if (m_sessionState == SessionState::Capturing && m_segmentState == SegmentState::Unloaded) {
363         m_currentPosition = ReplayPosition(m_currentPosition.segmentOffset + 1, 0);
364         createSegment();
365     }
366
367     // During playback, the next segment is loaded when the final input is dispatched,
368     // so nothing needs to be done here.
369
370     // We store the input cursor in both Document and JSDOMWindow, so that
371     // replay state is accessible from JavaScriptCore and script-free layout code.
372     loader->frame()->document()->setInputCursor(m_activeCursor.get());
373     loader->frame()->script().globalObject(mainThreadNormalWorld())->setInputCursor(m_activeCursor.get());
374 }
375
376 void ReplayController::frameDetached(Frame* frame)
377 {
378     ASSERT(m_sessionState != SessionState::Inactive);
379     ASSERT(frame);
380
381     if (!frame->document())
382         return;
383
384     // If the frame's cursor isn't capturing or replaying, we should do nothing.
385     // This is the case for the "outbound" frame when starting capture, or when
386     // we clear the input cursor to finish or prematurely unload a segment.
387     if (frame->document()->inputCursor().isCapturing()) {
388         ASSERT(m_segmentState == SegmentState::Appending);
389         completeSegment();
390     }
391
392     // During playback, the segments are unloaded and loaded when the final
393     // input has been dispatched. So, nothing needs to be done here.
394 }
395
396 void ReplayController::willDispatchEvent(const Event& event, Frame* frame)
397 {
398     EventTarget* target = event.target();
399     if (!target && !frame)
400         return;
401
402     Document* document = frame ? frame->document() : nullptr;
403     // Fetch the document from the event target, because the target could be detached.
404     if (Node* node = target->toNode())
405         document = node->inDocument() ? &node->document() : node->ownerDocument();
406     else if (DOMWindow* window = target->toDOMWindow())
407         document = window->document();
408
409     ASSERT(document);
410
411     InputCursor& cursor = document->inputCursor();
412     bool eventIsUnrelated = !cursor.isCapturing() && !cursor.isReplaying();
413     logDispatchedDOMEvent(event, eventIsUnrelated);
414
415 #if ENABLE_AGGRESSIVE_DETERMINISM_CHECKS
416     // To ensure deterministic JS execution, all DOM events must be dispatched deterministically.
417     // If these assertions fail, then this DOM event is being dispatched by a nondeterministic EventLoop
418     // cycle, and may cause program execution to diverge if any JS code runs because of the DOM event.
419     if (cursor.isCapturing())
420         ASSERT(static_cast<CapturingInputCursor&>(cursor).withinEventLoopInputExtent());
421     else if (cursor.isReplaying())
422         ASSERT(dispatcher().isDispatching());
423 #endif
424 }
425
426 PassRefPtr<ReplaySession> ReplayController::loadedSession() const
427 {
428     return m_loadedSession;
429 }
430
431 PassRefPtr<ReplaySessionSegment> ReplayController::loadedSegment() const
432 {
433     return m_loadedSegment;
434 }
435
436 InputCursor& ReplayController::activeInputCursor() const
437 {
438     return m_activeCursor ? *m_activeCursor : *m_emptyCursor;
439 }
440
441 EventLoopInputDispatcher& ReplayController::dispatcher() const
442 {
443     ASSERT(m_sessionState == SessionState::Replaying);
444     ASSERT(m_segmentState == SegmentState::Dispatching);
445     ASSERT(m_activeCursor);
446     ASSERT(m_activeCursor->isReplaying());
447
448     return static_cast<ReplayingInputCursor&>(*m_activeCursor).dispatcher();
449 }
450
451 void ReplayController::willDispatchInput(const EventLoopInputBase&)
452 {
453     ASSERT(m_sessionState == SessionState::Replaying);
454     ASSERT(m_segmentState == SegmentState::Dispatching);
455
456     m_currentPosition.inputOffset++;
457     if (m_currentPosition == m_targetPosition)
458         pausePlayback();
459 }
460
461 void ReplayController::didDispatchInput(const EventLoopInputBase&)
462 {
463     ASSERT(m_sessionState == SessionState::Replaying);
464     ASSERT(m_segmentState == SegmentState::Dispatching);
465
466     InspectorInstrumentation::playbackHitPosition(&m_page, m_currentPosition);
467 }
468
469 void ReplayController::didDispatchFinalInput()
470 {
471     ASSERT(m_segmentState == SegmentState::Dispatching);
472
473     // No more segments left to replay; stop.
474     if (m_currentPosition.segmentOffset + 1 == m_loadedSession->size()) {
475         // Normally the position is adjusted when loading the next segment.
476         m_currentPosition.segmentOffset++;
477         m_currentPosition.inputOffset = 0;
478
479         cancelPlayback();
480         return;
481     }
482
483     unloadSegment();
484     loadSegmentAtIndex(m_currentPosition.segmentOffset + 1);
485     startPlayback();
486 }
487
488 } // namespace WebCore
489
490 #endif // ENABLE(WEB_REPLAY)