diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/replay | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebCore/replay')
27 files changed, 3782 insertions, 0 deletions
diff --git a/Source/WebCore/replay/AllReplayInputs.h b/Source/WebCore/replay/AllReplayInputs.h new file mode 100644 index 000000000..7b52be6be --- /dev/null +++ b/Source/WebCore/replay/AllReplayInputs.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2013 University of Washington. All rights reserved. + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// This file is the include equivalent for WEB_REPLAY_INPUT_NAMES_FOR_EACH. +// Note that there is not an exact correspondence between the two, since +// Some input types reside in the same file. + +#pragma once + +#if ENABLE(WEB_REPLAY) + +#include "MemoizedDOMResult.h" +#include "WebReplayInputs.h" +#include <JavaScriptCore/JSReplayInputs.h> + +#define IMPORT_FROM_JSC_NAMESPACE(name) \ +using JSC::name; \ + +JS_REPLAY_INPUT_NAMES_FOR_EACH(IMPORT_FROM_JSC_NAMESPACE) + +#endif // ENABLE(WEB_REPLAY) diff --git a/Source/WebCore/replay/CapturingInputCursor.cpp b/Source/WebCore/replay/CapturingInputCursor.cpp new file mode 100644 index 000000000..99649bfe2 --- /dev/null +++ b/Source/WebCore/replay/CapturingInputCursor.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2013 University of Washington. All rights reserved. + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "CapturingInputCursor.h" + +#if ENABLE(WEB_REPLAY) + +#include "EventLoopInput.h" +#include "Logging.h" +#include "ReplaySessionSegment.h" +#include "SegmentedInputStorage.h" +#include <wtf/CurrentTime.h> + +namespace WebCore { + +CapturingInputCursor::CapturingInputCursor(RefPtr<ReplaySessionSegment>&& segment) + : m_segment(WTFMove(segment)) +{ + LOG(WebReplay, "%-30sCreated capture cursor=%p.\n", "[ReplayController]", this); +} + +CapturingInputCursor::~CapturingInputCursor() +{ + LOG(WebReplay, "%-30sDestroyed capture cursor=%p.\n", "[ReplayController]", this); +} + +Ref<CapturingInputCursor> CapturingInputCursor::create(RefPtr<ReplaySessionSegment>&& segment) +{ + return adoptRef(*new CapturingInputCursor(WTFMove(segment))); +} + +void CapturingInputCursor::storeInput(std::unique_ptr<NondeterministicInputBase> input) +{ + ASSERT_ARG(input, input); + + if (input->queue() == InputQueue::EventLoopInput) { + // FIXME: rewrite this (and related dispatch code) to use std::chrono. + double now = monotonicallyIncreasingTime(); + m_segment->eventLoopTimings().append(now); + } + + m_segment->storage().store(WTFMove(input)); +} + +NondeterministicInputBase* CapturingInputCursor::loadInput(InputQueue, const String&) +{ + // Can't load inputs from capturing cursor. + ASSERT_NOT_REACHED(); + return nullptr; +} + +NondeterministicInputBase* CapturingInputCursor::uncheckedLoadInput(InputQueue) +{ + // Can't load inputs from capturing cursor. + ASSERT_NOT_REACHED(); + return nullptr; +} + +}; // namespace WebCore + +#endif // ENABLE(WEB_REPLAY) diff --git a/Source/WebCore/replay/CapturingInputCursor.h b/Source/WebCore/replay/CapturingInputCursor.h new file mode 100644 index 000000000..01ce7b56e --- /dev/null +++ b/Source/WebCore/replay/CapturingInputCursor.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2013 University of Washington. All rights reserved. + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if ENABLE(WEB_REPLAY) + +#include <replay/InputCursor.h> +#include <wtf/Noncopyable.h> +#include <wtf/RefPtr.h> + +namespace WebCore { + +class EventLoopInputExtent; +class Page; +class ReplaySessionSegment; + +class CapturingInputCursor final : public InputCursor { + WTF_MAKE_NONCOPYABLE(CapturingInputCursor); +public: + static Ref<CapturingInputCursor> create(RefPtr<ReplaySessionSegment>&&); + virtual ~CapturingInputCursor(); + + bool isCapturing() const override { return true; } + bool isReplaying() const override { return false; } + +protected: + NondeterministicInputBase* loadInput(InputQueue, const String& type) override; + +private: + CapturingInputCursor(RefPtr<ReplaySessionSegment>&&); + + NondeterministicInputBase* uncheckedLoadInput(InputQueue) override; + void storeInput(std::unique_ptr<NondeterministicInputBase>) override; + + RefPtr<ReplaySessionSegment> m_segment; +}; + +} // namespace WebCore + +#endif // ENABLE(WEB_REPLAY) diff --git a/Source/WebCore/replay/EventLoopInput.cpp b/Source/WebCore/replay/EventLoopInput.cpp new file mode 100644 index 000000000..2599a0aee --- /dev/null +++ b/Source/WebCore/replay/EventLoopInput.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2014 University of Washington. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "EventLoopInput.h" + +#if ENABLE(WEB_REPLAY) + +#include <replay/InputCursor.h> + +namespace WebCore { + +EventLoopInputExtent::EventLoopInputExtent(JSC::InputCursor& cursor) + : EventLoopInputExtent(&cursor) { } + +EventLoopInputExtent::EventLoopInputExtent(JSC::InputCursor* cursor) + : m_cursor(cursor) +{ + if (m_cursor) + m_cursor->setWithinEventLoopInputExtent(true); +} + +EventLoopInputExtent::~EventLoopInputExtent() +{ + if (m_cursor) + m_cursor->setWithinEventLoopInputExtent(false); +} + +}; // namespace WebCore + +#endif // ENABLE(WEB_REPLAY) diff --git a/Source/WebCore/replay/EventLoopInput.h b/Source/WebCore/replay/EventLoopInput.h new file mode 100644 index 000000000..53a978e77 --- /dev/null +++ b/Source/WebCore/replay/EventLoopInput.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2011-2013 University of Washington. All rights reserved. + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if ENABLE(WEB_REPLAY) + +#include <replay/NondeterministicInput.h> +#include <wtf/CurrentTime.h> + +namespace JSC { +class InputCursor; +}; + +namespace WebCore { + +class ReplayController; + +// This is an RAII helper used during capturing which sets a flag on the input cursor +// to track the dynamic extent of a captured event loop input. This extent approximates +// the interval in which EventLoopInputDispatcher::dispatching() is true. +class EventLoopInputExtent { + WTF_MAKE_NONCOPYABLE(EventLoopInputExtent); +public: + EventLoopInputExtent(JSC::InputCursor&); + EventLoopInputExtent(JSC::InputCursor*); + ~EventLoopInputExtent(); +private: + JSC::InputCursor* m_cursor; +}; + +class EventLoopInputBase : public NondeterministicInputBase { +public: + virtual ~EventLoopInputBase() { } + InputQueue queue() const final { return InputQueue::EventLoopInput; } + + virtual void dispatch(ReplayController&) = 0; +}; + +template <typename InputType> +class EventLoopInput : public EventLoopInputBase { +public: + const String& type() const final + { + return InputTraits<InputType>::type(); + } +}; + +} // namespace WebCore + +#endif // ENABLE(WEB_REPLAY) diff --git a/Source/WebCore/replay/EventLoopInputDispatcher.cpp b/Source/WebCore/replay/EventLoopInputDispatcher.cpp new file mode 100644 index 000000000..510293fc1 --- /dev/null +++ b/Source/WebCore/replay/EventLoopInputDispatcher.cpp @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2011-2013 University of Washington. All rights reserved. + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "EventLoopInputDispatcher.h" + +#if ENABLE(WEB_REPLAY) + +#include "Page.h" +#include "ReplayingInputCursor.h" +#include "WebReplayInputs.h" +#include <wtf/SetForScope.h> + +#if !LOG_DISABLED +#include "Logging.h" +#include "SerializationMethods.h" +#include <replay/EncodedValue.h> +#include <wtf/text/CString.h> +#endif + +namespace WebCore { + +EventLoopInputDispatcher::EventLoopInputDispatcher(Page& page, ReplayingInputCursor& cursor, EventLoopInputDispatcherClient* client) + : m_page(page) + , m_client(client) + , m_cursor(cursor) + , m_timer(*this, &EventLoopInputDispatcher::timerFired) + , m_speed(DispatchSpeed::FastForward) +{ + m_currentWork.input = nullptr; + m_currentWork.timestamp = 0.0; +} + +void EventLoopInputDispatcher::run() +{ + ASSERT(!m_running); + m_running = true; + + LOG(WebReplay, "%-20s Starting dispatch of event loop inputs for page: %p\n", "ReplayEvents", &m_page); + dispatchInputSoon(); +} + +void EventLoopInputDispatcher::pause() +{ + ASSERT(!m_dispatching); + ASSERT(m_running); + m_running = false; + + LOG(WebReplay, "%-20s Pausing dispatch of event loop inputs for page: %p\n", "ReplayEvents", &m_page); + if (m_timer.isActive()) + m_timer.stop(); +} + +void EventLoopInputDispatcher::timerFired() +{ + dispatchInput(); +} + +void EventLoopInputDispatcher::dispatchInputSoon() +{ + ASSERT(m_running); + + // We may already have an input if replay was paused just before dispatching. + if (!m_currentWork.input) + m_currentWork = m_cursor.loadEventLoopInput(); + + if (m_timer.isActive()) + m_timer.stop(); + + double waitInterval = 0; + + if (m_speed == DispatchSpeed::RealTime) { + // The goal is to reproduce the dispatch delay between inputs as it was + // was observed during the recording. So, we need to compute how much time + // to wait such that the elapsed time plus the wait time will equal the + // observed delay between the previous and current input. + + if (!m_previousInputTimestamp) + m_previousInputTimestamp = m_currentWork.timestamp; + + double targetInterval = m_currentWork.timestamp - m_previousInputTimestamp; + double elapsed = monotonicallyIncreasingTime() - m_previousDispatchStartTime; + waitInterval = targetInterval - elapsed; + } + + // A negative wait time means that dispatch took longer on replay than on + // capture. In this case, proceed without waiting at all. + if (waitInterval < 0) + waitInterval = 0; + + if (waitInterval > 1000.0) { + LOG_ERROR("%-20s Tried to wait for over 1000 seconds before dispatching next event loop input; this is probably a bug.", "ReplayEvents"); + waitInterval = 0; + } + + LOG(WebReplay, "%-20s (WAIT: %.3f ms)", "ReplayEvents", waitInterval * 1000.0); + m_timer.startOneShot(waitInterval); +} + +void EventLoopInputDispatcher::dispatchInput() +{ + ASSERT(m_currentWork.input); + ASSERT(!m_dispatching); + + if (m_speed == DispatchSpeed::RealTime) { + m_previousDispatchStartTime = monotonicallyIncreasingTime(); + m_previousInputTimestamp = m_currentWork.timestamp; + } + +#if !LOG_DISABLED + EncodedValue encodedInput = EncodingTraits<NondeterministicInputBase>::encodeValue(*m_currentWork.input); + String jsonString = encodedInput.asObject()->toJSONString(); + + LOG(WebReplay, "%-20s ----------------------------------------------", "ReplayEvents"); + LOG(WebReplay, "%-20s >DISPATCH: %s %s\n", "ReplayEvents", m_currentWork.input->type().utf8().data(), jsonString.utf8().data()); +#endif + + m_client->willDispatchInput(*m_currentWork.input); + // Client could stop replay in the previous callback, so check again. + if (!m_running) + return; + + { + SetForScope<bool> change(m_dispatching, true); + m_currentWork.input->dispatch(m_page.replayController()); + } + + EventLoopInputBase* dispatchedInput = m_currentWork.input; + m_currentWork.input = nullptr; + + // Notify clients that the event was dispatched. + m_client->didDispatchInput(*dispatchedInput); + if (dispatchedInput->type() == InputTraits<EndSegmentSentinel>::type()) { + m_running = false; + m_dispatching = false; + m_client->didDispatchFinalInput(); + return; + } + + // Clients could stop replay during event dispatch, or from any callback above. + if (!m_running) + return; + + dispatchInputSoon(); +} + +} // namespace WebCore + +#endif // ENABLE(WEB_REPLAY) diff --git a/Source/WebCore/replay/EventLoopInputDispatcher.h b/Source/WebCore/replay/EventLoopInputDispatcher.h new file mode 100644 index 000000000..2067e78d4 --- /dev/null +++ b/Source/WebCore/replay/EventLoopInputDispatcher.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2013 University of Washington. All rights reserved. + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if ENABLE(WEB_REPLAY) + +#include "EventLoopInput.h" +#include "ReplayingInputCursor.h" +#include "Timer.h" +#include <wtf/Noncopyable.h> +#include <wtf/text/WTFString.h> + +namespace WebCore { + +class Page; + +enum class DispatchSpeed { + RealTime, + FastForward, +}; + +class EventLoopInputDispatcherClient { +public: + EventLoopInputDispatcherClient() { } + virtual ~EventLoopInputDispatcherClient() { } + + virtual void willDispatchInput(const EventLoopInputBase&) =0; + virtual void didDispatchInput(const EventLoopInputBase&) =0; + virtual void didDispatchFinalInput() =0; +}; + +class EventLoopInputDispatcher { + WTF_MAKE_NONCOPYABLE(EventLoopInputDispatcher); +public: + EventLoopInputDispatcher(Page&, ReplayingInputCursor&, EventLoopInputDispatcherClient*); + + void run(); + void pause(); + + void setDispatchSpeed(DispatchSpeed speed) { m_speed = speed; } + DispatchSpeed dispatchSpeed() const { return m_speed; } + + bool isRunning() const { return m_running; } + bool isDispatching() const { return m_dispatching; } +private: + void dispatchInputSoon(); + void dispatchInput(); + void timerFired(); + + Page& m_page; + EventLoopInputDispatcherClient* m_client; + ReplayingInputCursor& m_cursor; + Timer m_timer; + + // This data is valid when an event loop input is presently dispatching. + EventLoopInputData m_currentWork; + // Whether the dispatcher is currently calling out to an inputs' dispatch() method. + bool m_dispatching {false}; + // Whether the dispatcher is waiting to dispatch or actively dispatching inputs. + bool m_running {false}; + + DispatchSpeed m_speed; + // The time at which the last input dispatch() method was called. + double m_previousDispatchStartTime {0.0}; + // The timestamp specified by the last dispatched input. + double m_previousInputTimestamp {0.0}; +}; + +} // namespace WebCore + +#endif // ENABLE(WEB_REPLAY) diff --git a/Source/WebCore/replay/FunctorInputCursor.h b/Source/WebCore/replay/FunctorInputCursor.h new file mode 100644 index 000000000..6f82f1fb6 --- /dev/null +++ b/Source/WebCore/replay/FunctorInputCursor.h @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2013 University of Washington. All rights reserved. + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if ENABLE(WEB_REPLAY) + +#include "ReplaySessionSegment.h" +#include "SegmentedInputStorage.h" +#include <replay/InputCursor.h> +#include <replay/NondeterministicInput.h> +#include <wtf/Assertions.h> +#include <wtf/Noncopyable.h> + +namespace WebCore { + +class FunctorInputCursor final : public InputCursor { + WTF_MAKE_NONCOPYABLE(FunctorInputCursor); +public: + static Ref<FunctorInputCursor> create(RefPtr<ReplaySessionSegment>&& segment) + { + return adoptRef(*new FunctorInputCursor(WTFMove(segment))); + } + + // InputCursor + bool isCapturing() const override { return false; } + bool isReplaying() const override { return false; } + + void storeInput(std::unique_ptr<NondeterministicInputBase>) override; + NondeterministicInputBase* uncheckedLoadInput(InputQueue) override; + + template<typename Functor> + typename Functor::ReturnType forEachInputInQueue(InputQueue, Functor&); +protected: + NondeterministicInputBase* loadInput(InputQueue, const String&) override; +private: + FunctorInputCursor(RefPtr<ReplaySessionSegment>&&); + + RefPtr<ReplaySessionSegment> m_segment; +}; + +template<typename Functor> inline +typename Functor::ReturnType FunctorInputCursor::forEachInputInQueue(InputQueue queue, Functor& functor) +{ + for (size_t i = 0; i < m_segment->storage().queueSize(queue); i++) + functor(i, m_segment->storage().queue(queue).at(i).get()); + + return functor.returnValue(); +} + +inline FunctorInputCursor::FunctorInputCursor(RefPtr<ReplaySessionSegment>&& segment) + : m_segment(WTFMove(segment)) +{ +} + +inline void FunctorInputCursor::storeInput(std::unique_ptr<NondeterministicInputBase>) +{ + ASSERT_NOT_REACHED(); +} + +inline NondeterministicInputBase* FunctorInputCursor::loadInput(InputQueue, const String&) +{ + ASSERT_NOT_REACHED(); + return nullptr; +} + +inline NondeterministicInputBase* FunctorInputCursor::uncheckedLoadInput(InputQueue) +{ + ASSERT_NOT_REACHED(); + return nullptr; +} + +} // namespace WebCore + +#endif // ENABLE(WEB_REPLAY) diff --git a/Source/WebCore/replay/MemoizedDOMResult.cpp b/Source/WebCore/replay/MemoizedDOMResult.cpp new file mode 100644 index 000000000..fdef95d08 --- /dev/null +++ b/Source/WebCore/replay/MemoizedDOMResult.cpp @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "MemoizedDOMResult.h" + +#if ENABLE(WEB_REPLAY) + +#include "SerializationMethods.h" +#include "WebReplayInputs.h" +#include <wtf/NeverDestroyed.h> + +namespace WebCore { + +const String& MemoizedDOMResultBase::type() const +{ + return InputTraits<MemoizedDOMResultBase>::type(); +} + +std::unique_ptr<MemoizedDOMResultBase> MemoizedDOMResultBase::createFromEncodedResult(const String& attribute, EncodedCType ctype, EncodedValue encodedValue, ExceptionCode exceptionCode) +{ + switch (ctype) { +#define CREATE_DECODE_SWITCH_CASE(name, type) \ + case CTypeTraits<type>::encodedType: { \ + CTypeTraits<type>::CType result; \ + if (!EncodingTraits<type>::decodeValue(encodedValue, result)) \ + return nullptr; \ + return std::make_unique<MemoizedDOMResult<type>>(attribute, result, exceptionCode); \ + } \ +\ + +FOR_EACH_MEMOIZED_CTYPE(CREATE_DECODE_SWITCH_CASE) +#undef CREATE_DECODE_SWITCH_CASE + } + + RELEASE_ASSERT_NOT_REACHED(); + return nullptr; +} + +} // namespace WebCore + +namespace JSC { + +using WebCore::EncodedCType; +using WebCore::ExceptionCode; +using WebCore::MemoizedDOMResult; +using WebCore::SerializedScriptValue; + +const String& InputTraits<MemoizedDOMResultBase>::type() +{ + static NeverDestroyed<const String> type(ASCIILiteral("MemoizedDOMResult")); + return type; +} + +void InputTraits<MemoizedDOMResultBase>::encode(EncodedValue& encodedValue, const MemoizedDOMResultBase& input) +{ + encodedValue.put<String>(ASCIILiteral("attribute"), input.attribute()); + encodedValue.put<EncodedCType>(ASCIILiteral("ctype"), input.ctype()); + encodedValue.put<EncodedValue>(ASCIILiteral("result"), input.encodedResult()); + if (input.exceptionCode()) + encodedValue.put<ExceptionCode>(ASCIILiteral("exceptionCode"), input.exceptionCode()); +} + +bool InputTraits<MemoizedDOMResultBase>::decode(EncodedValue& encodedValue, std::unique_ptr<MemoizedDOMResultBase>& input) +{ + String attribute; + if (!encodedValue.get<String>(ASCIILiteral("attribute"), attribute)) + return false; + + EncodedCType ctype; + if (!encodedValue.get<EncodedCType>(ASCIILiteral("ctype"), ctype)) + return false; + + EncodedValue encodedResult; + if (!encodedValue.get<EncodedValue>(ASCIILiteral("result"), encodedResult)) + return false; + + ExceptionCode exceptionCode = 0; + encodedValue.get<ExceptionCode>(ASCIILiteral("exceptionCode"), exceptionCode); + + std::unique_ptr<MemoizedDOMResultBase> decodedInput = MemoizedDOMResultBase::createFromEncodedResult(attribute, ctype, encodedResult, exceptionCode); + if (!decodedInput) + return false; + input = WTFMove(decodedInput); + return true; +} + +} // namespace JSC + +#endif // ENABLE(WEB_REPLAY) diff --git a/Source/WebCore/replay/MemoizedDOMResult.h b/Source/WebCore/replay/MemoizedDOMResult.h new file mode 100644 index 000000000..b00b15b5a --- /dev/null +++ b/Source/WebCore/replay/MemoizedDOMResult.h @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2012 University of Washington. All rights reserved. + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if ENABLE(WEB_REPLAY) + +#include <replay/EncodedValue.h> +#include <replay/NondeterministicInput.h> +#include <wtf/TypeCasts.h> + +namespace WebCore { + +class SerializedScriptValue; + +using ExceptionCode = int; + +// Add new memoized ctypes here. The first argument is the enum value, +// which cannot conflict with built-in primitive types. The second is +// the actual C type that is used to specialize CTypeTraits. New enum +// values should also be added to the definition in WebInputs.json. +#define FOR_EACH_MEMOIZED_CTYPE(macro) \ + macro(Boolean, bool) \ + macro(Int, int) \ + macro(String, String) \ + macro(Unsigned, unsigned) \ + \ +// end of FOR_EACH_MEMOIZED_CTYPE + +// We encode this enum so that we can recover MemoizedType when decoding the input +// and then call the correct specialized MemoizedDOMResult<T> constructor. +enum class EncodedCType { +#define CREATE_ENUM_VALUE(name, type) name, + +FOR_EACH_MEMOIZED_CTYPE(CREATE_ENUM_VALUE) +#undef CREATE_ENUM_VALUE +}; + +class MemoizedDOMResultBase : public NondeterministicInputBase { +public: + MemoizedDOMResultBase(const String& attribute, EncodedCType ctype, ExceptionCode exceptionCode = 0) + : m_attribute(attribute) + , m_ctype(ctype) + , m_exceptionCode(exceptionCode) { } + + virtual ~MemoizedDOMResultBase() { } + + static std::unique_ptr<MemoizedDOMResultBase> createFromEncodedResult(const String& attribute, EncodedCType, EncodedValue, ExceptionCode); + + template<typename T> + bool convertTo(T& decodedValue); + + virtual EncodedValue encodedResult() const = 0; + InputQueue queue() const final { return InputQueue::ScriptMemoizedData; } + const String& type() const final; + + const String& attribute() const { return m_attribute; } + EncodedCType ctype() const { return m_ctype; } + ExceptionCode exceptionCode() const { return m_exceptionCode; } +private: + String m_attribute; + EncodedCType m_ctype; + ExceptionCode m_exceptionCode; +}; + +template<typename T> +struct CTypeTraits { + static bool decode(EncodedValue& encodedValue, T& decodedValue) + { + return EncodingTraits<T>::decodeValue(encodedValue, decodedValue); + } +}; + +#define CREATE_CTYPE_TRAITS(_name, _type) \ +template<> \ +struct CTypeTraits<_type> { \ + typedef _type CType; \ + static const EncodedCType encodedType = EncodedCType::_name; \ +}; \ + +FOR_EACH_MEMOIZED_CTYPE(CREATE_CTYPE_TRAITS) +#undef CREATE_CTYPE_TRAITS + +template<typename MemoizedType> +class MemoizedDOMResult final : public MemoizedDOMResultBase { +public: + MemoizedDOMResult(const String& attribute, typename CTypeTraits<MemoizedType>::CType result, ExceptionCode exceptionCode) + : MemoizedDOMResultBase(attribute, CTypeTraits<MemoizedType>::encodedType, exceptionCode) + , m_result(result) { } + virtual ~MemoizedDOMResult() { } + + EncodedValue encodedResult() const override + { + return EncodingTraits<MemoizedType>::encodeValue(m_result); + } + + typename CTypeTraits<MemoizedType>::CType result() const { return m_result; } +private: + typename CTypeTraits<MemoizedType>::CType m_result; +}; + +// This is used by clients of the memoized DOM result to get out the memoized +// value without performing a cast to MemoizedDOMResult<T> and calling result(). +template<typename T> +bool MemoizedDOMResultBase::convertTo(T& convertedValue) +{ + // Type tag doesn't match; fail to decode the value. + if (m_ctype != CTypeTraits<T>::encodedType) + return false; + + MemoizedDOMResult<T>& castedResult = static_cast<MemoizedDOMResult<T>&>(*this); + convertedValue = castedResult.result(); + return true; +} + +} // namespace WebCore + +using WebCore::MemoizedDOMResultBase; + +namespace JSC { + +template<> +struct InputTraits<MemoizedDOMResultBase> { + static InputQueue queue() { return InputQueue::ScriptMemoizedData; } + static const String& type(); + + static void encode(EncodedValue&, const MemoizedDOMResultBase& input); + static bool decode(EncodedValue&, std::unique_ptr<MemoizedDOMResultBase>& input); +}; + +} // namespace JSC + +SPECIALIZE_TYPE_TRAITS_BEGIN(WebCore::MemoizedDOMResultBase) +static bool isType(const NondeterministicInputBase& input) { return input.type() == InputTraits<WebCore::MemoizedDOMResultBase>::type(); } +SPECIALIZE_TYPE_TRAITS_END() + +#endif // ENABLE(WEB_REPLAY) diff --git a/Source/WebCore/replay/ReplayController.cpp b/Source/WebCore/replay/ReplayController.cpp new file mode 100644 index 000000000..bf358dc6a --- /dev/null +++ b/Source/WebCore/replay/ReplayController.cpp @@ -0,0 +1,544 @@ +/* + * Copyright (C) 2011-2013 University of Washington. All rights reserved. + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "ReplayController.h" + +#if ENABLE(WEB_REPLAY) + +#include "AllReplayInputs.h" +#include "CapturingInputCursor.h" +#include "DOMWindow.h" +#include "DocumentLoader.h" +#include "Frame.h" +#include "FrameTree.h" +#include "InspectorInstrumentation.h" +#include "Location.h" +#include "Logging.h" +#include "MainFrame.h" +#include "Page.h" +#include "ReplaySession.h" +#include "ReplaySessionSegment.h" +#include "ReplayingInputCursor.h" +#include "ScriptController.h" +#include "SerializationMethods.h" +#include "Settings.h" +#include "UserInputBridge.h" +#include "WebReplayInputs.h" +#include <replay/EmptyInputCursor.h> +#include <wtf/text/CString.h> + +#if ENABLE(ASYNC_SCROLLING) +#include "ScrollingCoordinator.h" +#endif + +namespace WebCore { + +#if !LOG_DISABLED +static void logDispatchedDOMEvent(const Event& event, bool eventIsUnrelated) +{ + EventTarget* target = event.target(); + if (!target) + return; + + // A DOM event is unrelated if it is being dispatched to a document that is neither capturing nor replaying. + if (Node* node = target->toNode()) { + LOG(WebReplay, "%-20s --->%s DOM event: type=%s, target=%u/node[%p] %s\n", "ReplayEvents", + (eventIsUnrelated) ? "Unrelated" : "Dispatching", + event.type().string().utf8().data(), + frameIndexFromDocument((node->isConnected()) ? &node->document() : node->ownerDocument()), + node, + node->nodeName().utf8().data()); + } else if (DOMWindow* window = target->toDOMWindow()) { + LOG(WebReplay, "%-20s --->%s DOM event: type=%s, target=%u/window[%p] %s\n", "ReplayEvents", + (eventIsUnrelated) ? "Unrelated" : "Dispatching", + event.type().string().utf8().data(), + frameIndexFromDocument(window->document()), + window, + window->location()->href().utf8().data()); + } +} + +static const char* sessionStateToString(SessionState state) +{ + switch (state) { + case SessionState::Capturing: + return "Capturing"; + case SessionState::Inactive: + return "Inactive"; + case SessionState::Replaying: + return "Replaying"; + } +} + +static const char* segmentStateToString(SegmentState state) +{ + switch (state) { + case SegmentState::Appending: + return "Appending"; + case SegmentState::Unloaded: + return "Unloaded"; + case SegmentState::Loaded: + return "Loaded"; + case SegmentState::Dispatching: + return "Dispatching"; + } +} + +#endif // !LOG_DISABLED + +ReplayController::ReplayController(Page& page) + : m_page(page) + , m_loadedSession(ReplaySession::create()) + , m_emptyCursor(EmptyInputCursor::create()) + , m_targetPosition(ReplayPosition(0, 0)) + , m_currentPosition(ReplayPosition(0, 0)) + , m_segmentState(SegmentState::Unloaded) + , m_sessionState(SessionState::Inactive) + , m_dispatchSpeed(DispatchSpeed::FastForward) +{ +} + +void ReplayController::setForceDeterministicSettings(bool shouldForceDeterministicBehavior) +{ + ASSERT_ARG(shouldForceDeterministicBehavior, shouldForceDeterministicBehavior ^ (m_sessionState == SessionState::Inactive)); + + if (shouldForceDeterministicBehavior) { + m_savedSettings.usesPageCache = m_page.settings().usesPageCache(); + + m_page.settings().setUsesPageCache(false); + } else { + m_page.settings().setUsesPageCache(m_savedSettings.usesPageCache); + } + +#if ENABLE(ASYNC_SCROLLING) + if (ScrollingCoordinator* scrollingCoordinator = m_page.scrollingCoordinator()) + scrollingCoordinator->replaySessionStateDidChange(); +#endif +} + +void ReplayController::setSessionState(SessionState state) +{ + ASSERT_ARG(state, state != m_sessionState); + + LOG(WebReplay, "%-20s SessionState transition: %10s --> %10s.\n", "ReplayController", sessionStateToString(m_sessionState), sessionStateToString(state)); + + switch (m_sessionState) { + case SessionState::Capturing: + ASSERT(state == SessionState::Inactive); + + m_sessionState = state; + m_page.userInputBridge().setState(UserInputBridge::State::Open); + break; + + case SessionState::Inactive: + m_sessionState = state; + m_page.userInputBridge().setState(state == SessionState::Capturing ? UserInputBridge::State::Capturing : UserInputBridge::State::Replaying); + break; + + case SessionState::Replaying: + ASSERT(state == SessionState::Inactive); + + m_sessionState = state; + m_page.userInputBridge().setState(UserInputBridge::State::Open); + break; + } +} + +void ReplayController::setSegmentState(SegmentState state) +{ + ASSERT_ARG(state, state != m_segmentState); + + LOG(WebReplay, "%-20s SegmentState transition: %10s --> %10s.\n", "ReplayController", segmentStateToString(m_segmentState), segmentStateToString(state)); + + switch (m_segmentState) { + case SegmentState::Appending: + ASSERT(state == SegmentState::Unloaded); + break; + + case SegmentState::Unloaded: + ASSERT(state == SegmentState::Appending || state == SegmentState::Loaded); + break; + + case SegmentState::Loaded: + ASSERT(state == SegmentState::Unloaded || state == SegmentState::Dispatching); + break; + + case SegmentState::Dispatching: + ASSERT(state == SegmentState::Loaded); + break; + } + + m_segmentState = state; +} + +void ReplayController::switchSession(RefPtr<ReplaySession>&& session) +{ + ASSERT_ARG(session, session); + ASSERT(m_segmentState == SegmentState::Unloaded); + ASSERT(m_sessionState == SessionState::Inactive); + + m_loadedSession = session; + m_currentPosition = ReplayPosition(0, 0); + + LOG(WebReplay, "%-20sSwitching sessions from %p to %p.\n", "ReplayController", m_loadedSession.get(), session.get()); + InspectorInstrumentation::sessionLoaded(m_page, m_loadedSession.copyRef()); +} + +void ReplayController::createSegment() +{ + ASSERT(m_sessionState == SessionState::Capturing); + ASSERT(m_segmentState == SegmentState::Unloaded); + + setSegmentState(SegmentState::Appending); + + // Create a new segment but don't associate it with the current session + // until we stop appending to it. This preserves the invariant that + // segments associated with a replay session have immutable data. + m_loadedSegment = ReplaySessionSegment::create(); + + LOG(WebReplay, "%-20s Created segment: %p.\n", "ReplayController", m_loadedSegment.get()); + InspectorInstrumentation::segmentCreated(m_page, m_loadedSegment.copyRef()); + + m_activeCursor = CapturingInputCursor::create(m_loadedSegment.copyRef()); + m_activeCursor->appendInput<BeginSegmentSentinel>(); + + std::unique_ptr<InitialNavigation> navigationInput = InitialNavigation::createFromPage(m_page); + // Dispatching this input schedules navigation of the main frame, causing a refresh. + navigationInput->dispatch(*this); + m_activeCursor->storeInput(WTFMove(navigationInput)); +} + +void ReplayController::completeSegment() +{ + ASSERT(m_sessionState == SessionState::Capturing); + ASSERT(m_segmentState == SegmentState::Appending); + + m_activeCursor->appendInput<EndSegmentSentinel>(); + + // Hold on to a reference so unloading the segment doesn't deallocate it. + RefPtr<ReplaySessionSegment> segment = m_loadedSegment; + bool shouldSuppressNotifications = true; + unloadSegment(shouldSuppressNotifications); + + LOG(WebReplay, "%-20s Completed segment: %p.\n", "ReplayController", segment.get()); + InspectorInstrumentation::segmentCompleted(m_page, segment.copyRef()); + + m_loadedSession->appendSegment(segment.copyRef()); + InspectorInstrumentation::sessionModified(m_page, m_loadedSession.copyRef()); +} + +void ReplayController::loadSegmentAtIndex(size_t segmentIndex) +{ + ASSERT_ARG(segmentIndex, segmentIndex >= 0 && segmentIndex < m_loadedSession->size()); + RefPtr<ReplaySessionSegment> segment = m_loadedSession->at(segmentIndex); + + ASSERT(m_sessionState == SessionState::Replaying); + ASSERT(m_segmentState == SegmentState::Unloaded); + ASSERT(segment); + ASSERT(!m_loadedSegment); + + m_loadedSegment = segment; + setSegmentState(SegmentState::Loaded); + + m_currentPosition.segmentOffset = segmentIndex; + m_currentPosition.inputOffset = 0; + + m_activeCursor = ReplayingInputCursor::create(m_loadedSegment.copyRef(), m_page, this); + + LOG(WebReplay, "%-20sLoading segment: %p.\n", "ReplayController", segment.get()); + InspectorInstrumentation::segmentLoaded(m_page, segment.copyRef()); +} + +void ReplayController::unloadSegment(bool suppressNotifications) +{ + ASSERT(m_sessionState != SessionState::Inactive); + ASSERT(m_segmentState == SegmentState::Loaded || m_segmentState == SegmentState::Appending); + + setSegmentState(SegmentState::Unloaded); + + LOG(WebReplay, "%-20s Clearing input cursors for page: %p\n", "ReplayController", &m_page); + + m_activeCursor = nullptr; + auto unloadedSegment = WTFMove(m_loadedSegment); + for (Frame* frame = &m_page.mainFrame(); frame; frame = frame->tree().traverseNext()) { + frame->script().globalObject(mainThreadNormalWorld())->setInputCursor(m_emptyCursor.copyRef()); + frame->document()->setInputCursor(m_emptyCursor.copyRef()); + } + + // When we stop capturing, don't send out segment unloaded events since we + // didn't send out the corresponding segmentLoaded event at the start of capture. + if (!suppressNotifications) { + LOG(WebReplay, "%-20sUnloading segment: %p.\n", "ReplayController", unloadedSegment.get()); + InspectorInstrumentation::segmentUnloaded(m_page); + } +} + +void ReplayController::startCapturing() +{ + ASSERT(m_sessionState == SessionState::Inactive); + ASSERT(m_segmentState == SegmentState::Unloaded); + + setSessionState(SessionState::Capturing); + setForceDeterministicSettings(true); + + LOG(WebReplay, "%-20s Starting capture.\n", "ReplayController"); + InspectorInstrumentation::captureStarted(m_page); + + m_currentPosition = ReplayPosition(0, 0); + + createSegment(); +} + +void ReplayController::stopCapturing() +{ + ASSERT(m_sessionState == SessionState::Capturing); + ASSERT(m_segmentState == SegmentState::Appending); + + completeSegment(); + + setSessionState(SessionState::Inactive); + setForceDeterministicSettings(false); + + LOG(WebReplay, "%-20s Stopping capture.\n", "ReplayController"); + InspectorInstrumentation::captureStopped(m_page); +} + +void ReplayController::startPlayback() +{ + ASSERT(m_sessionState == SessionState::Replaying); + ASSERT(m_segmentState == SegmentState::Loaded); + + setSegmentState(SegmentState::Dispatching); + + LOG(WebReplay, "%-20s Starting playback to position (segment: %d, input: %d).\n", "ReplayController", m_targetPosition.segmentOffset, m_targetPosition.inputOffset); + InspectorInstrumentation::playbackStarted(m_page); + + dispatcher().setDispatchSpeed(m_dispatchSpeed); + dispatcher().run(); +} + +void ReplayController::pausePlayback() +{ + ASSERT(m_sessionState == SessionState::Replaying); + ASSERT(m_segmentState == SegmentState::Dispatching); + + if (dispatcher().isRunning()) + dispatcher().pause(); + + setSegmentState(SegmentState::Loaded); + + LOG(WebReplay, "%-20s Pausing playback at position (segment: %d, input: %d).\n", "ReplayController", m_currentPosition.segmentOffset, m_currentPosition.inputOffset); + InspectorInstrumentation::playbackPaused(m_page, m_currentPosition); +} + +void ReplayController::cancelPlayback() +{ + ASSERT(m_sessionState == SessionState::Replaying); + ASSERT(m_segmentState != SegmentState::Appending); + + if (m_segmentState == SegmentState::Unloaded) + return; + + if (m_segmentState == SegmentState::Dispatching) + pausePlayback(); + + ASSERT(m_segmentState == SegmentState::Loaded); + unloadSegment(); + m_sessionState = SessionState::Inactive; + setForceDeterministicSettings(false); + InspectorInstrumentation::playbackFinished(m_page); +} + +void ReplayController::replayToPosition(const ReplayPosition& position, DispatchSpeed speed) +{ + ASSERT(m_sessionState != SessionState::Capturing); + ASSERT(m_segmentState == SegmentState::Loaded || m_segmentState == SegmentState::Unloaded); + ASSERT(position.segmentOffset < m_loadedSession->size()); + + m_dispatchSpeed = speed; + + if (m_sessionState != SessionState::Replaying) { + setSessionState(SessionState::Replaying); + setForceDeterministicSettings(true); + } + + if (m_segmentState == SegmentState::Unloaded) + loadSegmentAtIndex(position.segmentOffset); + else if (position.segmentOffset != m_currentPosition.segmentOffset || m_currentPosition.inputOffset > position.inputOffset) { + // If the desired segment is not loaded or we have gone past the desired input + // offset, then unload the current segment and load the appropriate segment. + unloadSegment(); + loadSegmentAtIndex(position.segmentOffset); + } + + ASSERT(m_currentPosition.segmentOffset == position.segmentOffset); + ASSERT(m_loadedSession->at(position.segmentOffset) == m_loadedSegment); + + m_targetPosition = position; + startPlayback(); +} + +void ReplayController::frameNavigated(Frame& frame) +{ + ASSERT(m_sessionState != SessionState::Inactive); + + // The initial capturing segment is created prior to main frame navigation. + // Otherwise, the prior capturing segment was completed when the frame detached, + // and it is now time to create a new segment. + if (m_sessionState == SessionState::Capturing && m_segmentState == SegmentState::Unloaded) { + m_currentPosition = ReplayPosition(m_currentPosition.segmentOffset + 1, 0); + createSegment(); + } + + // During playback, the next segment is loaded when the final input is dispatched, + // so nothing needs to be done here. + + // We store the input cursor in both Document and JSDOMWindow, so that + // replay state is accessible from JavaScriptCore and script-free layout code. + frame.document()->setInputCursor(*m_activeCursor); + frame.script().globalObject(mainThreadNormalWorld())->setInputCursor(*m_activeCursor); +} + +void ReplayController::frameDetached(Frame& frame) +{ + ASSERT(m_sessionState != SessionState::Inactive); + + if (!frame.document()) + return; + + // If the frame's cursor isn't capturing or replaying, we should do nothing. + // This is the case for the "outbound" frame when starting capture, or when + // we clear the input cursor to finish or prematurely unload a segment. + if (frame.document()->inputCursor().isCapturing()) { + ASSERT(m_segmentState == SegmentState::Appending); + completeSegment(); + } + + // During playback, the segments are unloaded and loaded when the final + // input has been dispatched. So, nothing needs to be done here. +} + +void ReplayController::willDispatchEvent(const Event& event, Frame* frame) +{ + EventTarget* target = event.target(); + if (!target && !frame) + return; + + Document* document = frame ? frame->document() : nullptr; + // Fetch the document from the event target, because the target could be detached. + if (Node* node = target->toNode()) + document = node->isConnected() ? &node->document() : node->ownerDocument(); + else if (DOMWindow* window = target->toDOMWindow()) + document = window->document(); + + ASSERT(document); + InputCursor& cursor = document->inputCursor(); + +#if !LOG_DISABLED + bool eventIsUnrelated = !cursor.isCapturing() && !cursor.isReplaying(); + logDispatchedDOMEvent(event, eventIsUnrelated); +#else + UNUSED_PARAM(cursor); +#endif + +#if ENABLE_AGGRESSIVE_DETERMINISM_CHECKS + // To ensure deterministic JS execution, all DOM events must be dispatched deterministically. + // If these assertions fail, then this DOM event is being dispatched by a nondeterministic EventLoop + // cycle, and may cause program execution to diverge if any JS code runs because of the DOM event. + if (cursor.isCapturing() || cursor.isReplaying()) + ASSERT(cursor.withinEventLoopInputExtent()); + else if (cursor.isReplaying()) + ASSERT(dispatcher().isDispatching()); +#endif +} + +RefPtr<ReplaySession> ReplayController::loadedSession() const +{ + return m_loadedSession.copyRef(); +} + +RefPtr<ReplaySessionSegment> ReplayController::loadedSegment() const +{ + return m_loadedSegment.copyRef(); +} + +InputCursor& ReplayController::activeInputCursor() +{ + return m_activeCursor ? *m_activeCursor : m_emptyCursor.get(); +} + +EventLoopInputDispatcher& ReplayController::dispatcher() const +{ + ASSERT(m_sessionState == SessionState::Replaying); + ASSERT(m_segmentState == SegmentState::Dispatching); + ASSERT(m_activeCursor && m_activeCursor->isReplaying()); + + return static_cast<ReplayingInputCursor&>(*m_activeCursor).dispatcher(); +} + +void ReplayController::willDispatchInput(const EventLoopInputBase&) +{ + ASSERT(m_sessionState == SessionState::Replaying); + ASSERT(m_segmentState == SegmentState::Dispatching); + + m_currentPosition.inputOffset++; + + InspectorInstrumentation::playbackHitPosition(m_page, m_currentPosition); + + if (m_currentPosition == m_targetPosition) + pausePlayback(); +} + +void ReplayController::didDispatchInput(const EventLoopInputBase&) +{ + ASSERT(m_sessionState == SessionState::Replaying); + ASSERT(m_segmentState == SegmentState::Dispatching); +} + +void ReplayController::didDispatchFinalInput() +{ + ASSERT(m_segmentState == SegmentState::Dispatching); + + // No more segments left to replay; stop. + if (m_currentPosition.segmentOffset + 1 == m_loadedSession->size()) { + // Normally the position is adjusted when loading the next segment. + m_currentPosition.segmentOffset++; + m_currentPosition.inputOffset = 0; + + cancelPlayback(); + return; + } + + unloadSegment(); + loadSegmentAtIndex(m_currentPosition.segmentOffset + 1); + startPlayback(); +} + +} // namespace WebCore + +#endif // ENABLE(WEB_REPLAY) diff --git a/Source/WebCore/replay/ReplayController.h b/Source/WebCore/replay/ReplayController.h new file mode 100644 index 000000000..f4eff4649 --- /dev/null +++ b/Source/WebCore/replay/ReplayController.h @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2011-2013 University of Washington. All rights reserved. + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if ENABLE(WEB_REPLAY) + +#include "EventLoopInputDispatcher.h" +#include <wtf/Noncopyable.h> + +// Determinism assertions are guarded by this macro. When a user-facing error reporting and +// recovery mechanism is implemented, this guard can be removed. <https://webkit.org/b/131279> +#define ENABLE_AGGRESSIVE_DETERMINISM_CHECKS 0 + +namespace JSC { +class InputCursor; +} + +namespace WebCore { + +class DOMWindow; +class Document; +class DocumentLoader; +class Element; +class Event; +class EventLoopInputBase; +class Frame; +class Node; +class Page; +class ReplaySession; +class ReplaySessionSegment; + +// Each state may transition to the state immediately above or below it. +// SessionState transitions are only allowed when SegmentState is Unloaded. +enum class SessionState { + Capturing, + // Neither capturing or replaying. m_currentPosition is not valid in this state. + Inactive, + Replaying, +}; + +// Each state may transition to the state immediately above or below it. +enum class SegmentState { + // Inputs can be appended into an unassociated session segment. + // We can stop capturing, which reverts to the Unloaded state. + Appending, + // No session segment is loaded. + // We can start capturing, or load a segment (and then replay it). + Unloaded, + // A session segment is loaded. + // We can unload the segment, or begin playback from m_currentPosition. + Loaded, + // The controller is actively dispatching event loop inputs. + // We can pause or cancel playback, which reverts to the Loaded state. + Dispatching, +}; + +struct ReplayPosition { + ReplayPosition(unsigned segmentOffset, unsigned inputOffset) + : segmentOffset(segmentOffset) + , inputOffset(inputOffset) + { + } + + // By convention, this position represents the end of the last segment of the session. + ReplayPosition() + : segmentOffset(0) + , inputOffset(0) + { + } + + bool operator<(const ReplayPosition& other) + { + return segmentOffset <= other.segmentOffset && inputOffset < other.inputOffset; + } + + bool operator==(const ReplayPosition& other) + { + return segmentOffset == other.segmentOffset && inputOffset == other.inputOffset; + } + + unsigned segmentOffset; + unsigned inputOffset; +}; + +class ReplayController final : public EventLoopInputDispatcherClient { + WTF_MAKE_FAST_ALLOCATED; + WTF_MAKE_NONCOPYABLE(ReplayController); +public: + ReplayController(Page&); + + void startCapturing(); + void stopCapturing(); + + // Start or resume playback with default speed and target replay position. + void startPlayback(); + void pausePlayback(); + void cancelPlayback(); + + void replayToPosition(const ReplayPosition&, DispatchSpeed = DispatchSpeed::FastForward); + void replayToCompletion(DispatchSpeed speed = DispatchSpeed::FastForward) + { + replayToPosition(ReplayPosition(), speed); + } + + void switchSession(RefPtr<ReplaySession>&&); + + // InspectorReplayAgent notifications. + void frameNavigated(Frame&); + void frameDetached(Frame&); + void willDispatchEvent(const Event&, Frame*); + + Page& page() const { return m_page; } + + SessionState sessionState() const { return m_sessionState; } + SegmentState segmentState() const { return m_segmentState; } + + RefPtr<ReplaySession> loadedSession() const; + RefPtr<ReplaySessionSegment> loadedSegment() const; + + JSC::InputCursor& activeInputCursor(); + ReplayPosition currentPosition() const { return m_currentPosition; } + +private: + // EventLoopInputDispatcherClient API + void willDispatchInput(const EventLoopInputBase&) override; + void didDispatchInput(const EventLoopInputBase&) override; + void didDispatchFinalInput() override; + + void createSegment(); + void completeSegment(); + + void loadSegmentAtIndex(size_t); + void unloadSegment(bool suppressNotifications = false); + + EventLoopInputDispatcher& dispatcher() const; + + void setSessionState(SessionState); + void setSegmentState(SegmentState); + void setForceDeterministicSettings(bool); + + struct SavedSettings { + bool usesPageCache; + + SavedSettings() + : usesPageCache(false) + { } + }; + + Page& m_page; + + RefPtr<ReplaySessionSegment> m_loadedSegment; + RefPtr<ReplaySession> m_loadedSession; + Ref<JSC::InputCursor> m_emptyCursor; + // The active cursor is set to nullptr when invalid. + RefPtr<JSC::InputCursor> m_activeCursor; + + // This position is valid when SessionState == Replaying. + ReplayPosition m_targetPosition; + // This position is valid when SessionState != Inactive. + ReplayPosition m_currentPosition; + SegmentState m_segmentState; + // This tracks state across multiple segments. When navigating the main frame, + // there is a small interval during segment switching when no segment is loaded. + SessionState m_sessionState; + + DispatchSpeed m_dispatchSpeed; + SavedSettings m_savedSettings; +}; + +} // namespace WebCore + +#endif // ENABLE(WEB_REPLAY) diff --git a/Source/WebCore/replay/ReplayInputCreationMethods.cpp b/Source/WebCore/replay/ReplayInputCreationMethods.cpp new file mode 100644 index 000000000..f0065c7c3 --- /dev/null +++ b/Source/WebCore/replay/ReplayInputCreationMethods.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2011-2013 University of Washington. + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#if ENABLE(WEB_REPLAY) + +#include "Document.h" +#include "MainFrame.h" +#include "Page.h" +#include "SecurityOrigin.h" +#include "SerializationMethods.h" +#include "WebReplayInputs.h" + +namespace WebCore { + +std::unique_ptr<InitialNavigation> InitialNavigation::createFromPage(const Page& page) +{ + const MainFrame& mainFrame = page.mainFrame(); + ASSERT(mainFrame.document()); + + // Make sure that this is in sync with ReplayController::beginCapturing(). + RefPtr<SecurityOrigin> originCopy = mainFrame.document()->securityOrigin().isolatedCopy(); + URL url = mainFrame.document()->url(); + String referrer = mainFrame.loader().referrer(); + return std::make_unique<InitialNavigation>(WTFMove(originCopy), url, referrer); +} + +} // namespace WebCore + +#endif // ENABLE(WEB_REPLAY) diff --git a/Source/WebCore/replay/ReplayInputDispatchMethods.cpp b/Source/WebCore/replay/ReplayInputDispatchMethods.cpp new file mode 100644 index 000000000..044264ee5 --- /dev/null +++ b/Source/WebCore/replay/ReplayInputDispatchMethods.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2011-2013 University of Washington. + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#if ENABLE(WEB_REPLAY) + +#include "AllReplayInputs.h" +#include "MainFrame.h" +#include "NavigationScheduler.h" +#include "Page.h" +#include "ReplayController.h" +#include "URL.h" +#include "UserInputBridge.h" + +namespace WebCore { + +// Sentinel inputs. +void BeginSegmentSentinel::dispatch(ReplayController&) +{ +} + +void EndSegmentSentinel::dispatch(ReplayController&) +{ +} + +// Navigation inputs. +void InitialNavigation::dispatch(ReplayController& controller) +{ + auto& frame = controller.page().mainFrame(); + ASSERT(frame.document()); + frame.navigationScheduler().scheduleLocationChange(*frame.document(), *m_securityOrigin, m_url, m_referrer); +} + +void HandleKeyPress::dispatch(ReplayController& controller) +{ + controller.page().userInputBridge().handleKeyEvent(platformEvent(), InputSource::Synthetic); +} + +// User interaction inputs. +void HandleMouseMove::dispatch(ReplayController& controller) +{ + if (m_scrollbarTargeted) + controller.page().userInputBridge().handleMouseMoveOnScrollbarEvent(platformEvent(), InputSource::Synthetic); + else + controller.page().userInputBridge().handleMouseMoveEvent(platformEvent(), InputSource::Synthetic); +} + +void HandleMousePress::dispatch(ReplayController& controller) +{ + controller.page().userInputBridge().handleMousePressEvent(platformEvent(), InputSource::Synthetic); +} + +void HandleMouseRelease::dispatch(ReplayController& controller) +{ + controller.page().userInputBridge().handleMouseReleaseEvent(platformEvent(), InputSource::Synthetic); +} + +void HandleWheelEvent::dispatch(ReplayController& controller) +{ + controller.page().userInputBridge().handleWheelEvent(platformEvent(), InputSource::Synthetic); +} + +void LogicalScrollPage::dispatch(ReplayController& controller) +{ + controller.page().userInputBridge().logicalScrollRecursively(direction(), granularity(), InputSource::Synthetic); +} + +void ScrollPage::dispatch(ReplayController& controller) +{ + controller.page().userInputBridge().scrollRecursively(direction(), granularity(), InputSource::Synthetic); +} + +} // namespace WebCore + +#endif // ENABLE(WEB_REPLAY) diff --git a/Source/WebCore/replay/ReplaySession.cpp b/Source/WebCore/replay/ReplaySession.cpp new file mode 100644 index 000000000..93da28c1c --- /dev/null +++ b/Source/WebCore/replay/ReplaySession.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2013 University of Washington. All rights reserved. + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "ReplaySession.h" + +#if ENABLE(WEB_REPLAY) + +#include "ReplaySessionSegment.h" +#include <wtf/CurrentTime.h> + +namespace WebCore { + +static unsigned s_nextIdentifier = 1; + +Ref<ReplaySession> ReplaySession::create() +{ + return adoptRef(*new ReplaySession()); +} + +ReplaySession::ReplaySession() + : m_identifier(s_nextIdentifier++) + , m_timestamp(currentTimeMS()) +{ +} + +ReplaySession::~ReplaySession() +{ +} + +RefPtr<ReplaySessionSegment> ReplaySession::at(size_t position) const +{ + ASSERT_ARG(position, position >= 0 && position < m_segments.size()); + + return m_segments.at(position).copyRef(); +} + +void ReplaySession::appendSegment(RefPtr<ReplaySessionSegment>&& segment) +{ + ASSERT_ARG(segment, segment); + // For now, only support one segment. + ASSERT(!m_segments.size()); + + // Since replay locations are specified with segment IDs, we can only + // have one instance of a segment in the session. + size_t offset = m_segments.find(segment.copyRef()); + ASSERT_UNUSED(offset, offset == notFound); + + m_segments.append(WTFMove(segment)); +} + +void ReplaySession::insertSegment(size_t position, RefPtr<ReplaySessionSegment>&& segment) +{ + ASSERT_ARG(segment, segment); + ASSERT_ARG(position, position >= 0 && position < m_segments.size()); + + m_segments.insert(position, WTFMove(segment)); +} + +void ReplaySession::removeSegment(size_t position) +{ + ASSERT_ARG(position, position >= 0 && position < m_segments.size()); + + m_segments.remove(position); +} + +}; // namespace WebCore + +#endif // ENABLE(WEB_REPLAY) diff --git a/Source/WebCore/replay/ReplaySession.h b/Source/WebCore/replay/ReplaySession.h new file mode 100644 index 000000000..2db80102d --- /dev/null +++ b/Source/WebCore/replay/ReplaySession.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2013 University of Washington. All rights reserved. + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if ENABLE(WEB_REPLAY) + +#include <wtf/Noncopyable.h> +#include <wtf/RefCounted.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class ReplaySessionSegment; + +typedef Vector<RefPtr<ReplaySessionSegment>>::const_iterator SegmentIterator; + +class ReplaySession : public RefCounted<ReplaySession> { + WTF_MAKE_NONCOPYABLE(ReplaySession); +public: + static Ref<ReplaySession> create(); + ~ReplaySession(); + + double timestamp() const { return m_timestamp; } + unsigned identifier() const { return m_identifier; } + + size_t size() const { return m_segments.size(); } + RefPtr<ReplaySessionSegment> at(size_t position) const; + + SegmentIterator begin() const { return m_segments.begin(); } + SegmentIterator end() const { return m_segments.end(); } + + void appendSegment(RefPtr<ReplaySessionSegment>&&); + void insertSegment(size_t position, RefPtr<ReplaySessionSegment>&&); + void removeSegment(size_t position); + +private: + ReplaySession(); + + Vector<RefPtr<ReplaySessionSegment>> m_segments; + unsigned m_identifier; + double m_timestamp; +}; + +} // namespace WebCore + +#endif // ENABLE(WEB_REPLAY) diff --git a/Source/WebCore/replay/ReplaySessionSegment.cpp b/Source/WebCore/replay/ReplaySessionSegment.cpp new file mode 100644 index 000000000..f18ddf41f --- /dev/null +++ b/Source/WebCore/replay/ReplaySessionSegment.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2013 University of Washington. All rights reserved. + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "ReplaySessionSegment.h" + +#if ENABLE(WEB_REPLAY) + +#include "CapturingInputCursor.h" +#include "FunctorInputCursor.h" +#include "ReplayingInputCursor.h" +#include "SegmentedInputStorage.h" +#include <wtf/CurrentTime.h> + +namespace WebCore { + +static unsigned s_nextSegmentIdentifier = 1; + +Ref<ReplaySessionSegment> ReplaySessionSegment::create() +{ + return adoptRef(*new ReplaySessionSegment); +} + +ReplaySessionSegment::ReplaySessionSegment() + : m_storage(std::make_unique<SegmentedInputStorage>()) + , m_identifier(s_nextSegmentIdentifier++) + , m_timestamp(currentTimeMS()) +{ +} + +ReplaySessionSegment::~ReplaySessionSegment() +{ +} + +} // namespace WebCore + +#endif // ENABLE(WEB_REPLAY) diff --git a/Source/WebCore/replay/ReplaySessionSegment.h b/Source/WebCore/replay/ReplaySessionSegment.h new file mode 100644 index 000000000..d562b53c0 --- /dev/null +++ b/Source/WebCore/replay/ReplaySessionSegment.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2013 University of Washington. All rights reserved. + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if ENABLE(WEB_REPLAY) + +#include <wtf/Forward.h> +#include <wtf/RefCounted.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class CapturingInputCursor; +class EventLoopInputDispatcherClient; +class FunctorInputCursor; +class Page; +class ReplayingInputCursor; +class SegmentedInputStorage; + +class ReplaySessionSegment : public RefCounted<ReplaySessionSegment> { +friend class CapturingInputCursor; +friend class FunctorInputCursor; +friend class ReplayingInputCursor; +public: + static Ref<ReplaySessionSegment> create(); + ~ReplaySessionSegment(); + + unsigned identifier() const { return m_identifier; } + double timestamp() const { return m_timestamp; } +protected: + SegmentedInputStorage& storage() { return *m_storage; } + Vector<double, 0>& eventLoopTimings() { return m_eventLoopTimings; } + +private: + ReplaySessionSegment(); + + std::unique_ptr<SegmentedInputStorage> m_storage; + Vector<double, 0> m_eventLoopTimings; + + unsigned m_identifier; + bool m_canCapture {true}; + double m_timestamp; +}; + +} // namespace WebCore + +#endif // ENABLE(WEB_REPLAY) diff --git a/Source/WebCore/replay/ReplayingInputCursor.cpp b/Source/WebCore/replay/ReplayingInputCursor.cpp new file mode 100644 index 000000000..0e26a2a4b --- /dev/null +++ b/Source/WebCore/replay/ReplayingInputCursor.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2013 University of Washington. All rights reserved. + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "ReplayingInputCursor.h" + +#if ENABLE(WEB_REPLAY) + +#include "EventLoopInputDispatcher.h" +#include "ReplaySessionSegment.h" +#include "SegmentedInputStorage.h" +#include "SerializationMethods.h" +#include "WebReplayInputs.h" +#include <wtf/text/CString.h> + +namespace WebCore { + +ReplayingInputCursor::ReplayingInputCursor(RefPtr<ReplaySessionSegment>&& segment, Page& page, EventLoopInputDispatcherClient* client) + : m_segment(WTFMove(segment)) + , m_dispatcher(std::make_unique<EventLoopInputDispatcher>(page, *this, client)) +{ + for (size_t i = 0; i < static_cast<size_t>(InputQueue::Count); i++) + m_positions.append(0); +} + +ReplayingInputCursor::~ReplayingInputCursor() +{ +} + +Ref<ReplayingInputCursor> ReplayingInputCursor::create(RefPtr<ReplaySessionSegment>&& segment, Page& page, EventLoopInputDispatcherClient* client) +{ + return adoptRef(*new ReplayingInputCursor(WTFMove(segment), page, client)); +} + +void ReplayingInputCursor::storeInput(std::unique_ptr<NondeterministicInputBase>) +{ + // Cannot store inputs from a replaying input cursor. + ASSERT_NOT_REACHED(); +} + +NondeterministicInputBase* ReplayingInputCursor::loadInput(InputQueue queue, const String& type) +{ + NondeterministicInputBase* input = uncheckedLoadInput(queue); + + if (input->type() != type) { + LOG_ERROR("%-25s ERROR: Expected replay input of type %s, but got type %s\n", "[ReplayingInputCursor]", type.ascii().data(), input->type().ascii().data()); + return nullptr; + } + + return input; +} + +NondeterministicInputBase* ReplayingInputCursor::uncheckedLoadInput(InputQueue queue) +{ + if (m_positions[static_cast<size_t>(queue)] >= m_segment->storage().queueSize(queue)) { + String queueString = EncodingTraits<InputQueue>::encodeValue(queue).convertTo<String>(); + LOG_ERROR("%-30s ERROR No more inputs remain for determinism queue %s, but one was requested.", "[ReplayingInputCursor]", queueString.ascii().data()); + return nullptr; + } + + return m_segment->storage().load(queue, m_positions[static_cast<size_t>(queue)]++); +} + +EventLoopInputData ReplayingInputCursor::loadEventLoopInput() +{ + ASSERT(m_segment); + + size_t offset = m_positions.at(static_cast<size_t>(InputQueue::EventLoopInput)); + ASSERT(offset < m_segment->eventLoopTimings().size()); + + EventLoopInputData data; + data.timestamp = m_segment->eventLoopTimings().at(offset); + data.input = static_cast<EventLoopInputBase*>(uncheckedLoadInput(InputQueue::EventLoopInput)); + return data; +} + +}; // namespace WebCore + +#endif // ENABLE(WEB_REPLAY) diff --git a/Source/WebCore/replay/ReplayingInputCursor.h b/Source/WebCore/replay/ReplayingInputCursor.h new file mode 100644 index 000000000..77c3b9420 --- /dev/null +++ b/Source/WebCore/replay/ReplayingInputCursor.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2013 University of Washington. All rights reserved. + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if ENABLE(WEB_REPLAY) + +#include <replay/InputCursor.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class EventLoopInputBase; +class EventLoopInputDispatcher; +class EventLoopInputDispatcherClient; +class Page; +class ReplaySessionSegment; + +struct EventLoopInputData { + EventLoopInputBase* input; + double timestamp; +}; + +class ReplayingInputCursor final : public InputCursor { + WTF_MAKE_NONCOPYABLE(ReplayingInputCursor); +public: + static Ref<ReplayingInputCursor> create(RefPtr<ReplaySessionSegment>&&, Page&, EventLoopInputDispatcherClient*); + virtual ~ReplayingInputCursor(); + + bool isCapturing() const override { return false; } + bool isReplaying() const override { return true; } + + EventLoopInputDispatcher& dispatcher() const { return *m_dispatcher; } + + EventLoopInputData loadEventLoopInput(); +protected: + NondeterministicInputBase* loadInput(InputQueue, const String& type) override; +private: + ReplayingInputCursor(RefPtr<ReplaySessionSegment>&&, Page&, EventLoopInputDispatcherClient*); + + void storeInput(std::unique_ptr<NondeterministicInputBase>) override; + NondeterministicInputBase* uncheckedLoadInput(InputQueue) override; + + RefPtr<ReplaySessionSegment> m_segment; + std::unique_ptr<EventLoopInputDispatcher> m_dispatcher; + Vector<size_t> m_positions; +}; + +} // namespace WebCore + +#endif // ENABLE(WEB_REPLAY) diff --git a/Source/WebCore/replay/SegmentedInputStorage.cpp b/Source/WebCore/replay/SegmentedInputStorage.cpp new file mode 100644 index 000000000..e61b069e5 --- /dev/null +++ b/Source/WebCore/replay/SegmentedInputStorage.cpp @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2013 University of Washington. All rights reserved. + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "SegmentedInputStorage.h" + +#if ENABLE(WEB_REPLAY) + +#if !LOG_DISABLED +#include "Logging.h" +#include "SerializationMethods.h" +#include <replay/EncodedValue.h> +#include <wtf/text/CString.h> +#include <wtf/text/WTFString.h> +#endif + +namespace WebCore { + +#if !LOG_DISABLED +// These are used to make the log spew from LOG(WebReplay, ...) more readable. +static const char* queueTypeToLogPrefix(InputQueue inputQueue, bool isLoad) +{ + if (isLoad) { + switch (inputQueue) { + case InputQueue::EventLoopInput: return "(DSPTCH-LOAD)"; + case InputQueue::LoaderMemoizedData: return "<LDMEMO-LOAD"; + case InputQueue::ScriptMemoizedData: return "<---<---<---JSMEMO-LOAD"; + case InputQueue::Count: return "ERROR!"; + } + } else { + switch (inputQueue) { + case InputQueue::EventLoopInput: return ">DSPTCH-STORE"; + case InputQueue::LoaderMemoizedData: return "<LDMEMO-STORE"; + case InputQueue::ScriptMemoizedData: return "<---<---<---JSMEMO-STORE"; + case InputQueue::Count: return "ERROR!"; + } + } +} + +static String jsonStringForInput(const NondeterministicInputBase& input) +{ + EncodedValue encodedValue = EncodingTraits<NondeterministicInputBase>::encodeValue(input); + return encodedValue.asObject()->toJSONString(); +} +#endif // !LOG_DISABLED + +static size_t offsetForInputQueue(InputQueue inputQueue) +{ + return static_cast<size_t>(inputQueue); +} + +SegmentedInputStorage::SegmentedInputStorage() +{ + for (size_t i = 0; i < offsetForInputQueue(InputQueue::Count); i++) + m_queues.append(new QueuedInputs); +} + +SegmentedInputStorage::~SegmentedInputStorage() +{ + for (size_t i = 0; i < offsetForInputQueue(InputQueue::Count); i++) + delete m_queues.at(i); +} + +NondeterministicInputBase* SegmentedInputStorage::load(InputQueue inputQueue, size_t offset) +{ + ASSERT(offset < queueSize(inputQueue)); + + NondeterministicInputBase* input = queue(inputQueue).at(offset).get(); + ASSERT(input); + + LOG(WebReplay, "%-20s %s: %s %s\n", "ReplayEvents", queueTypeToLogPrefix(inputQueue, true), input->type().utf8().data(), jsonStringForInput(*input).utf8().data()); + + return input; +} + +void SegmentedInputStorage::store(std::unique_ptr<NondeterministicInputBase> input) +{ + ASSERT_ARG(input, input); + ASSERT_ARG(input, input->queue() < InputQueue::Count); + + LOG(WebReplay, "%-14s#%-5u %s: %s %s\n", "ReplayEvents", m_inputCount++, queueTypeToLogPrefix(input->queue(), false), input->type().utf8().data(), jsonStringForInput(*input).utf8().data()); + + m_queues.at(offsetForInputQueue(input->queue()))->append(WTFMove(input)); +} + +size_t SegmentedInputStorage::queueSize(InputQueue inputQueue) const +{ + return queue(inputQueue).size(); +} + +const SegmentedInputStorage::QueuedInputs& SegmentedInputStorage::queue(InputQueue queue) const +{ + ASSERT_ARG(queue, queue < InputQueue::Count); + return *m_queues.at(offsetForInputQueue(queue)); +} + +} // namespace WebCore + +#endif // ENABLE(WEB_REPLAY) diff --git a/Source/WebCore/replay/SegmentedInputStorage.h b/Source/WebCore/replay/SegmentedInputStorage.h new file mode 100644 index 000000000..5294562f0 --- /dev/null +++ b/Source/WebCore/replay/SegmentedInputStorage.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2013 University of Washington. All rights reserved. + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if ENABLE(WEB_REPLAY) + +#include <replay/NondeterministicInput.h> +#include <wtf/Noncopyable.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class SegmentedInputStorage { + WTF_MAKE_NONCOPYABLE(SegmentedInputStorage); + friend class FunctorInputCursor; +public: + SegmentedInputStorage(); + ~SegmentedInputStorage(); + + NondeterministicInputBase* load(InputQueue, size_t); + void store(std::unique_ptr<NondeterministicInputBase>); + size_t queueSize(InputQueue) const; + +private: + typedef Vector<std::unique_ptr<NondeterministicInputBase>> QueuedInputs; + const QueuedInputs& queue(InputQueue) const; + + Vector<QueuedInputs*, 3> m_queues; + unsigned m_inputCount {0}; +}; + +} // namespace WebCore + +#endif // ENABLE(WEB_REPLAY) diff --git a/Source/WebCore/replay/SerializationMethods.cpp b/Source/WebCore/replay/SerializationMethods.cpp new file mode 100644 index 000000000..ca017fbd8 --- /dev/null +++ b/Source/WebCore/replay/SerializationMethods.cpp @@ -0,0 +1,538 @@ +/* + * Copyright (C) 2012 University of Washington. All rights reserved. + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "SerializationMethods.h" + +#if ENABLE(WEB_REPLAY) + +#include "AllReplayInputs.h" +#include "Document.h" +#include "Frame.h" +#include "FrameTree.h" +#include "MainFrame.h" +#include "Page.h" +#include "PlatformKeyboardEvent.h" +#include "PlatformMouseEvent.h" +#include "PlatformWheelEvent.h" +#include "PluginData.h" +#include "SecurityOrigin.h" +#include "URL.h" +#include <wtf/text/Base64.h> + +using WebCore::IntPoint; +using WebCore::MimeClassInfo; +using WebCore::MouseButton; +using WebCore::PlatformEvent; +using WebCore::PlatformKeyboardEvent; +using WebCore::PlatformMouseEvent; +using WebCore::PlatformWheelEvent; +using WebCore::PlatformWheelEventGranularity; +using WebCore::PluginData; +using WebCore::PluginLoadClientPolicy; +using WebCore::PluginInfo; +using WebCore::SecurityOrigin; +using WebCore::URL; + +#if PLATFORM(COCOA) +using WebCore::KeypressCommand; +using WebCore::PlatformWheelEventPhase; +#endif + +#define IMPORT_FROM_WEBCORE_NAMESPACE(name) \ +using WebCore::name; \ + +WEB_REPLAY_INPUT_NAMES_FOR_EACH(IMPORT_FROM_WEBCORE_NAMESPACE) +#undef IMPORT_FROM_WEBCORE_NAMESPACE + +namespace WebCore { + +uint32_t frameIndexFromDocument(const Document* document) +{ + ASSERT(document); + ASSERT(document->frame()); + return frameIndexFromFrame(document->frame()); +} + +uint32_t frameIndexFromFrame(const Frame* targetFrame) +{ + ASSERT(targetFrame); + + uint32_t currentIndex = 0; + for (const Frame* frame = &targetFrame->tree().top(); frame; ++currentIndex, frame = frame->tree().traverseNext()) { + if (frame == targetFrame) + return currentIndex; + } + + ASSERT_NOT_REACHED(); + return 0; +} + +Document* documentFromFrameIndex(Page* page, uint32_t frameIndex) +{ + Frame* frame = frameFromFrameIndex(page, frameIndex); + return frame ? frame->document() : nullptr; +} + +Frame* frameFromFrameIndex(Page* page, uint32_t frameIndex) +{ + ASSERT(page); + ASSERT(frameIndex >= 0); + + Frame* frame = &page->mainFrame(); + uint32_t currentIndex = 0; + for (; currentIndex < frameIndex && frame; ++currentIndex, frame = frame->tree().traverseNext()) { } + + return frame; +} + +} // namespace WebCore + +#define ENCODE_TYPE_WITH_KEY(_encodedValue, _type, _key, _value) \ + _encodedValue.put<_type>(ASCIILiteral(#_key), _value) + +#define ENCODE_OPTIONAL_TYPE_WITH_KEY(_encodedValue, _type, _key, _value, condition) \ + if (condition) \ + ENCODE_TYPE_WITH_KEY(_encodedValue, _type, _key, _value) + +#define DECODE_TYPE_WITH_KEY_TO_LVALUE(_encodedValue, _type, _key, _lvalue) \ + if (!_encodedValue.get<_type>(ASCIILiteral(#_key), _lvalue)) \ + return false + +#define DECODE_TYPE_WITH_KEY(_encodedValue, _type, _key) \ + EncodingTraits<_type>::DecodedType _key; \ + DECODE_TYPE_WITH_KEY_TO_LVALUE(_encodedValue, _type, _key, _key) + +#define DECODE_OPTIONAL_TYPE_WITH_KEY_TO_LVALUE(_encodedValue, _type, _key, _lvalue) \ + bool _key ## WasDecoded = _encodedValue.get<_type>(ASCIILiteral(#_key), _lvalue) + +#define DECODE_OPTIONAL_TYPE_WITH_KEY(_encodedValue, _type, _key) \ + EncodingTraits<_type>::DecodedType _key; \ + DECODE_OPTIONAL_TYPE_WITH_KEY_TO_LVALUE(_encodedValue, _type, _key, _key) + +namespace JSC { + +template<> +EncodedValue EncodingTraits<MimeClassInfo>::encodeValue(const MimeClassInfo& input) +{ + EncodedValue encodedData = EncodedValue::createObject(); + + ENCODE_TYPE_WITH_KEY(encodedData, String, type, input.type); + ENCODE_TYPE_WITH_KEY(encodedData, String, desc, input.desc); + ENCODE_TYPE_WITH_KEY(encodedData, Vector<String>, extensions, input.extensions); + + return encodedData; +} + +template<> +bool EncodingTraits<MimeClassInfo>::decodeValue(EncodedValue& encodedData, MimeClassInfo& input) +{ + MimeClassInfo info; + + DECODE_TYPE_WITH_KEY_TO_LVALUE(encodedData, String, type, info.type); + DECODE_TYPE_WITH_KEY_TO_LVALUE(encodedData, String, desc, info.desc); + DECODE_TYPE_WITH_KEY_TO_LVALUE(encodedData, Vector<String>, extensions, info.extensions); + + input = info; + return true; +} + +EncodedValue EncodingTraits<NondeterministicInputBase>::encodeValue(const NondeterministicInputBase& input) +{ + EncodedValue encodedValue = EncodedValue::createObject(); + ENCODE_TYPE_WITH_KEY(encodedValue, String, type, input.type()); + +#define ENCODE_IF_TYPE_TAG_MATCHES(name) \ + if (is<name>(input)) { \ + InputTraits<name>::encode(encodedValue, downcast<name>(input)); \ + return encodedValue; \ + } \ + + JS_REPLAY_INPUT_NAMES_FOR_EACH(ENCODE_IF_TYPE_TAG_MATCHES) + WEB_REPLAY_INPUT_NAMES_FOR_EACH(ENCODE_IF_TYPE_TAG_MATCHES) +#undef ENCODE_IF_TYPE_TAG_MATCHES + + // The macro won't work here because of the class template argument. + if (is<MemoizedDOMResultBase>(input)) { + InputTraits<MemoizedDOMResultBase>::encode(encodedValue, downcast<MemoizedDOMResultBase>(input)); + return encodedValue; + } + + ASSERT_NOT_REACHED(); + return EncodedValue(); +} + +bool EncodingTraits<NondeterministicInputBase>::decodeValue(EncodedValue& encodedValue, std::unique_ptr<NondeterministicInputBase>& input) +{ + DECODE_TYPE_WITH_KEY(encodedValue, String, type); + +#define DECODE_IF_TYPE_TAG_MATCHES(name) \ + if (type == InputTraits<name>::type()) { \ + std::unique_ptr<name> decodedInput; \ + if (!InputTraits<name>::decode(encodedValue, decodedInput)) \ + return false; \ + \ + input = WTFMove(decodedInput); \ + return true; \ + } \ + + JS_REPLAY_INPUT_NAMES_FOR_EACH(DECODE_IF_TYPE_TAG_MATCHES) + WEB_REPLAY_INPUT_NAMES_FOR_EACH(DECODE_IF_TYPE_TAG_MATCHES) +#undef DECODE_IF_TYPE_TAG_MATCHES + + if (type == InputTraits<MemoizedDOMResultBase>::type()) { + std::unique_ptr<MemoizedDOMResultBase> decodedInput; + if (!InputTraits<MemoizedDOMResultBase>::decode(encodedValue, decodedInput)) + return false; + + input = WTFMove(decodedInput); + return true; + } + + return false; +} + +#if USE(APPKIT) +EncodedValue EncodingTraits<KeypressCommand>::encodeValue(const KeypressCommand& command) +{ + EncodedValue encodedValue = EncodedValue::createObject(); + + ENCODE_TYPE_WITH_KEY(encodedValue, String, commandName, command.commandName); + ENCODE_OPTIONAL_TYPE_WITH_KEY(encodedValue, String, text, command.text, !command.text.isEmpty()); + + return encodedValue; +} + +bool EncodingTraits<KeypressCommand>::decodeValue(EncodedValue& encodedValue, KeypressCommand& decodedValue) +{ + DECODE_TYPE_WITH_KEY(encodedValue, String, commandName); + DECODE_OPTIONAL_TYPE_WITH_KEY(encodedValue, String, text); + + decodedValue = textWasDecoded ? KeypressCommand(commandName, text) : KeypressCommand(commandName); + return true; +} + +class PlatformKeyboardEventAppKit : public WebCore::PlatformKeyboardEvent { +public: + PlatformKeyboardEventAppKit(const PlatformKeyboardEvent& event, bool handledByInputMethod, Vector<KeypressCommand>& commands) + : PlatformKeyboardEvent(event) + { + m_handledByInputMethod = handledByInputMethod; + m_commands = commands; + } +}; +#endif // USE(APPKIT) + +EncodedValue EncodingTraits<PlatformKeyboardEvent>::encodeValue(const PlatformKeyboardEvent& input) +{ + EncodedValue encodedValue = EncodedValue::createObject(); + + ENCODE_TYPE_WITH_KEY(encodedValue, double, timestamp, input.timestamp()); + ENCODE_TYPE_WITH_KEY(encodedValue, PlatformEvent::Type, type, input.type()); + ENCODE_TYPE_WITH_KEY(encodedValue, PlatformEvent::Modifier, modifiers, input.modifiers()); + ENCODE_TYPE_WITH_KEY(encodedValue, String, text, input.text()); + ENCODE_TYPE_WITH_KEY(encodedValue, String, unmodifiedText, input.unmodifiedText()); +#if ENABLE(KEYBOARD_KEY_ATTRIBUTE) + ENCODE_TYPE_WITH_KEY(encodedValue, String, key, input.key()); +#endif +#if ENABLE(KEYBOARD_CODE_ATTRIBUTE) + ENCODE_TYPE_WITH_KEY(encodedValue, String, code, input.code()); +#endif + ENCODE_TYPE_WITH_KEY(encodedValue, String, keyIdentifier, input.keyIdentifier()); + ENCODE_TYPE_WITH_KEY(encodedValue, int, windowsVirtualKeyCode, input.windowsVirtualKeyCode()); + ENCODE_TYPE_WITH_KEY(encodedValue, bool, autoRepeat, input.isAutoRepeat()); + ENCODE_TYPE_WITH_KEY(encodedValue, bool, keypad, input.isKeypad()); + ENCODE_TYPE_WITH_KEY(encodedValue, bool, systemKey, input.isSystemKey()); +#if USE(APPKIT) + ENCODE_TYPE_WITH_KEY(encodedValue, bool, handledByInputMethod, input.handledByInputMethod()); + ENCODE_TYPE_WITH_KEY(encodedValue, Vector<KeypressCommand>, commands, input.commands()); +#endif + return encodedValue; +} + +bool EncodingTraits<PlatformKeyboardEvent>::decodeValue(EncodedValue& encodedValue, std::unique_ptr<PlatformKeyboardEvent>& input) +{ + DECODE_TYPE_WITH_KEY(encodedValue, double, timestamp); + DECODE_TYPE_WITH_KEY(encodedValue, PlatformEvent::Type, type); + DECODE_TYPE_WITH_KEY(encodedValue, PlatformEvent::Modifier, modifiers); + DECODE_TYPE_WITH_KEY(encodedValue, String, text); + DECODE_TYPE_WITH_KEY(encodedValue, String, unmodifiedText); +#if ENABLE(KEYBOARD_KEY_ATTRIBUTE) + DECODE_TYPE_WITH_KEY(encodedValue, String, key); +#endif +#if ENABLE(KEYBOARD_CODE_ATTRIBUTE) + DECODE_TYPE_WITH_KEY(encodedValue, String, code); +#endif + DECODE_TYPE_WITH_KEY(encodedValue, String, keyIdentifier); + DECODE_TYPE_WITH_KEY(encodedValue, int, windowsVirtualKeyCode); + DECODE_TYPE_WITH_KEY(encodedValue, bool, autoRepeat); + DECODE_TYPE_WITH_KEY(encodedValue, bool, keypad); + DECODE_TYPE_WITH_KEY(encodedValue, bool, systemKey); +#if USE(APPKIT) + DECODE_TYPE_WITH_KEY(encodedValue, bool, handledByInputMethod); + DECODE_TYPE_WITH_KEY(encodedValue, Vector<KeypressCommand>, commands); +#endif + + PlatformKeyboardEvent platformEvent = PlatformKeyboardEvent(type, text, unmodifiedText, +#if ENABLE(KEYBOARD_KEY_ATTRIBUTE) + key, +#endif +#if ENABLE(KEYBOARD_CODE_ATTRIBUTE) + code, +#endif + keyIdentifier, WTF::safeCast<int>(windowsVirtualKeyCode), autoRepeat, keypad, systemKey, modifiers, timestamp); + +#if USE(APPKIT) + input = std::make_unique<PlatformKeyboardEventAppKit>(platformEvent, handledByInputMethod, commands); +#else + input = std::make_unique<PlatformKeyboardEvent>(platformEvent); +#endif + return true; +} + +EncodedValue EncodingTraits<PlatformMouseEvent>::encodeValue(const PlatformMouseEvent& input) +{ + EncodedValue encodedValue = EncodedValue::createObject(); + + ENCODE_TYPE_WITH_KEY(encodedValue, int, positionX, input.position().x()); + ENCODE_TYPE_WITH_KEY(encodedValue, int, positionY, input.position().y()); + ENCODE_TYPE_WITH_KEY(encodedValue, int, globalPositionX, input.globalPosition().x()); + ENCODE_TYPE_WITH_KEY(encodedValue, int, globalPositionY, input.globalPosition().y()); + ENCODE_TYPE_WITH_KEY(encodedValue, MouseButton, button, input.button()); + ENCODE_TYPE_WITH_KEY(encodedValue, PlatformEvent::Type, type, input.type()); + ENCODE_TYPE_WITH_KEY(encodedValue, int, clickCount, input.clickCount()); + ENCODE_TYPE_WITH_KEY(encodedValue, bool, shiftKey, input.shiftKey()); + ENCODE_TYPE_WITH_KEY(encodedValue, bool, ctrlKey, input.ctrlKey()); + ENCODE_TYPE_WITH_KEY(encodedValue, bool, altKey, input.altKey()); + ENCODE_TYPE_WITH_KEY(encodedValue, bool, metaKey, input.metaKey()); + ENCODE_TYPE_WITH_KEY(encodedValue, int, timestamp, input.timestamp()); + ENCODE_TYPE_WITH_KEY(encodedValue, double, force, input.force()); + + return encodedValue; +} + +bool EncodingTraits<PlatformMouseEvent>::decodeValue(EncodedValue& encodedValue, std::unique_ptr<PlatformMouseEvent>& input) +{ + DECODE_TYPE_WITH_KEY(encodedValue, int, positionX); + DECODE_TYPE_WITH_KEY(encodedValue, int, positionY); + DECODE_TYPE_WITH_KEY(encodedValue, int, globalPositionX); + DECODE_TYPE_WITH_KEY(encodedValue, int, globalPositionY); + DECODE_TYPE_WITH_KEY(encodedValue, MouseButton, button); + DECODE_TYPE_WITH_KEY(encodedValue, PlatformEvent::Type, type); + DECODE_TYPE_WITH_KEY(encodedValue, int, clickCount); + DECODE_TYPE_WITH_KEY(encodedValue, bool, shiftKey); + DECODE_TYPE_WITH_KEY(encodedValue, bool, ctrlKey); + DECODE_TYPE_WITH_KEY(encodedValue, bool, altKey); + DECODE_TYPE_WITH_KEY(encodedValue, bool, metaKey); + DECODE_TYPE_WITH_KEY(encodedValue, int, timestamp); + DECODE_TYPE_WITH_KEY(encodedValue, double, force); + + input = std::make_unique<PlatformMouseEvent>(IntPoint(positionX, positionY), + IntPoint(globalPositionX, globalPositionY), + button, type, clickCount, + shiftKey, ctrlKey, altKey, metaKey, timestamp, force, WebCore::NoTap); + return true; +} + +#if PLATFORM(COCOA) +struct PlatformWheelEventCocoaArguments { + bool directionInvertedFromDevice; + bool hasPreciseScrollingDeltas; + PlatformWheelEventPhase phase; + PlatformWheelEventPhase momentumPhase; + int scrollCount; + float unacceleratedScrollingDeltaX; + float unacceleratedScrollingDeltaY; +}; + +class PlatformWheelEventCocoa : public PlatformWheelEvent { +public: + PlatformWheelEventCocoa(PlatformWheelEvent& event, PlatformWheelEventCocoaArguments& arguments) + : PlatformWheelEvent(event) + { + m_directionInvertedFromDevice = arguments.directionInvertedFromDevice; + m_hasPreciseScrollingDeltas = arguments.hasPreciseScrollingDeltas; + m_phase = arguments.phase; + m_momentumPhase = arguments.momentumPhase; + m_scrollCount = arguments.scrollCount; + m_unacceleratedScrollingDeltaX = arguments.unacceleratedScrollingDeltaX; + m_unacceleratedScrollingDeltaY = arguments.unacceleratedScrollingDeltaY; + } +}; +#endif // PLATFORM(COCOA) + +EncodedValue EncodingTraits<PlatformWheelEvent>::encodeValue(const PlatformWheelEvent& input) +{ + EncodedValue encodedData = EncodedValue::createObject(); + + ENCODE_TYPE_WITH_KEY(encodedData, int, positionX, input.position().x()); + ENCODE_TYPE_WITH_KEY(encodedData, int, positionY, input.position().y()); + ENCODE_TYPE_WITH_KEY(encodedData, int, globalPositionX, input.globalPosition().x()); + ENCODE_TYPE_WITH_KEY(encodedData, int, globalPositionY, input.globalPosition().y()); + ENCODE_TYPE_WITH_KEY(encodedData, bool, shiftKey, input.shiftKey()); + ENCODE_TYPE_WITH_KEY(encodedData, bool, ctrlKey, input.ctrlKey()); + ENCODE_TYPE_WITH_KEY(encodedData, bool, altKey, input.altKey()); + ENCODE_TYPE_WITH_KEY(encodedData, bool, metaKey, input.metaKey()); + ENCODE_TYPE_WITH_KEY(encodedData, float, deltaX, input.deltaX()); + ENCODE_TYPE_WITH_KEY(encodedData, float, deltaY, input.deltaY()); + ENCODE_TYPE_WITH_KEY(encodedData, float, wheelTicksX, input.wheelTicksX()); + ENCODE_TYPE_WITH_KEY(encodedData, float, wheelTicksY, input.wheelTicksY()); + ENCODE_TYPE_WITH_KEY(encodedData, PlatformWheelEventGranularity, granularity, static_cast<PlatformWheelEventGranularity>(input.granularity())); + +#if PLATFORM(COCOA) + ENCODE_TYPE_WITH_KEY(encodedData, bool, directionInvertedFromDevice, input.directionInvertedFromDevice()); + ENCODE_TYPE_WITH_KEY(encodedData, bool, hasPreciseScrollingDeltas, input.hasPreciseScrollingDeltas()); + ENCODE_TYPE_WITH_KEY(encodedData, PlatformWheelEventPhase, phase, static_cast<PlatformWheelEventPhase>(input.phase())); + ENCODE_TYPE_WITH_KEY(encodedData, PlatformWheelEventPhase, momentumPhase, static_cast<PlatformWheelEventPhase>(input.momentumPhase())); + ENCODE_TYPE_WITH_KEY(encodedData, int, scrollCount, input.scrollCount()); + ENCODE_TYPE_WITH_KEY(encodedData, float, unacceleratedScrollingDeltaX, input.unacceleratedScrollingDeltaX()); + ENCODE_TYPE_WITH_KEY(encodedData, float, unacceleratedScrollingDeltaY, input.unacceleratedScrollingDeltaY()); +#endif + + return encodedData; +} + +bool EncodingTraits<PlatformWheelEvent>::decodeValue(EncodedValue& encodedData, std::unique_ptr<PlatformWheelEvent>& input) +{ + DECODE_TYPE_WITH_KEY(encodedData, int, positionX); + DECODE_TYPE_WITH_KEY(encodedData, int, positionY); + DECODE_TYPE_WITH_KEY(encodedData, int, globalPositionX); + DECODE_TYPE_WITH_KEY(encodedData, int, globalPositionY); + DECODE_TYPE_WITH_KEY(encodedData, bool, shiftKey); + DECODE_TYPE_WITH_KEY(encodedData, bool, ctrlKey); + DECODE_TYPE_WITH_KEY(encodedData, bool, altKey); + DECODE_TYPE_WITH_KEY(encodedData, bool, metaKey); + DECODE_TYPE_WITH_KEY(encodedData, float, deltaX); + DECODE_TYPE_WITH_KEY(encodedData, float, deltaY); + DECODE_TYPE_WITH_KEY(encodedData, float, wheelTicksX); + DECODE_TYPE_WITH_KEY(encodedData, float, wheelTicksY); + DECODE_TYPE_WITH_KEY(encodedData, PlatformWheelEventGranularity, granularity); + +#if PLATFORM(COCOA) + PlatformWheelEventCocoaArguments arguments; + DECODE_TYPE_WITH_KEY_TO_LVALUE(encodedData, bool, directionInvertedFromDevice, arguments.directionInvertedFromDevice); + DECODE_TYPE_WITH_KEY_TO_LVALUE(encodedData, bool, hasPreciseScrollingDeltas, arguments.hasPreciseScrollingDeltas); + DECODE_TYPE_WITH_KEY_TO_LVALUE(encodedData, PlatformWheelEventPhase, phase, arguments.phase); + DECODE_TYPE_WITH_KEY_TO_LVALUE(encodedData, PlatformWheelEventPhase, momentumPhase, arguments.momentumPhase); + DECODE_TYPE_WITH_KEY_TO_LVALUE(encodedData, int, scrollCount, arguments.scrollCount); + DECODE_TYPE_WITH_KEY_TO_LVALUE(encodedData, float, unacceleratedScrollingDeltaX, arguments.unacceleratedScrollingDeltaX); + DECODE_TYPE_WITH_KEY_TO_LVALUE(encodedData, float, unacceleratedScrollingDeltaY, arguments.unacceleratedScrollingDeltaY); +#endif + + PlatformWheelEvent event(IntPoint(positionX, positionY), IntPoint(globalPositionX, globalPositionY), + deltaX, deltaY, wheelTicksX, wheelTicksY, granularity, shiftKey, ctrlKey, altKey, metaKey); + +#if PLATFORM(COCOA) + input = std::make_unique<PlatformWheelEventCocoa>(event, arguments); +#else + input = std::make_unique<PlatformWheelEvent>(event); +#endif + return true; +} + +EncodedValue EncodingTraits<PluginData>::encodeValue(RefPtr<PluginData> input) +{ + // FIXME: This needs to work in terms of web-visible plug-ins. + EncodedValue encodedData = EncodedValue::createObject(); + + ENCODE_TYPE_WITH_KEY(encodedData, Vector<PluginInfo>, plugins, input->plugins()); + + return encodedData; +} + +bool EncodingTraits<PluginData>::decodeValue(EncodedValue& encodedData, RefPtr<PluginData>&) +{ + // FIXME: This needs to work in terms of web-visible plug-ins. + DECODE_TYPE_WITH_KEY(encodedData, Vector<PluginInfo>, plugins); + + return true; +} + +template<> +EncodedValue EncodingTraits<PluginInfo>::encodeValue(const PluginInfo& input) +{ + EncodedValue encodedData = EncodedValue::createObject(); + + ENCODE_TYPE_WITH_KEY(encodedData, String, name, input.name); + ENCODE_TYPE_WITH_KEY(encodedData, String, file, input.file); + ENCODE_TYPE_WITH_KEY(encodedData, String, desc, input.desc); + ENCODE_TYPE_WITH_KEY(encodedData, Vector<MimeClassInfo>, mimes, input.mimes); + ENCODE_TYPE_WITH_KEY(encodedData, bool, isApplicationPlugin, input.isApplicationPlugin); + ENCODE_TYPE_WITH_KEY(encodedData, PluginLoadClientPolicy, clientLoadPolicy, static_cast<PluginLoadClientPolicy>(input.clientLoadPolicy)); +#if PLATFORM(MAC) + ENCODE_TYPE_WITH_KEY(encodedData, String, bundleIdentifier, input.bundleIdentifier); + ENCODE_TYPE_WITH_KEY(encodedData, String, versionString, input.versionString); +#endif + + return encodedData; +} + +template<> +bool EncodingTraits<PluginInfo>::decodeValue(EncodedValue& encodedData, PluginInfo& input) +{ + PluginInfo info; + + DECODE_TYPE_WITH_KEY_TO_LVALUE(encodedData, String, name, info.name); + DECODE_TYPE_WITH_KEY_TO_LVALUE(encodedData, String, file, info.file); + DECODE_TYPE_WITH_KEY_TO_LVALUE(encodedData, String, desc, info.desc); + DECODE_TYPE_WITH_KEY_TO_LVALUE(encodedData, Vector<MimeClassInfo>, mimes, info.mimes); + DECODE_TYPE_WITH_KEY_TO_LVALUE(encodedData, bool, isApplicationPlugin, info.isApplicationPlugin); + DECODE_TYPE_WITH_KEY_TO_LVALUE(encodedData, PluginLoadClientPolicy, clientLoadPolicy, info.clientLoadPolicy); +#if PLATFORM(MAC) + DECODE_TYPE_WITH_KEY_TO_LVALUE(encodedData, String, bundleIdentifier, input.bundleIdentifier); + DECODE_TYPE_WITH_KEY_TO_LVALUE(encodedData, String, versionString, input.versionString); +#endif + + input = info; + return true; +} + +EncodedValue EncodingTraits<SecurityOrigin>::encodeValue(RefPtr<SecurityOrigin> input) +{ + return EncodedValue::createString(input->toString()); +} + +bool EncodingTraits<SecurityOrigin>::decodeValue(EncodedValue& encodedValue, RefPtr<SecurityOrigin>& input) +{ + input = SecurityOrigin::createFromString(encodedValue.convertTo<String>()); + return true; +} + +EncodedValue EncodingTraits<URL>::encodeValue(const URL& input) +{ + return EncodedValue::createString(input.string()); +} + +bool EncodingTraits<URL>::decodeValue(EncodedValue& encodedValue, URL& input) +{ + input = URL(WebCore::ParsedURLString, encodedValue.convertTo<String>()); + return true; +} + +} // namespace JSC + +#endif // ENABLE(WEB_REPLAY) diff --git a/Source/WebCore/replay/SerializationMethods.h b/Source/WebCore/replay/SerializationMethods.h new file mode 100644 index 000000000..7d1b67812 --- /dev/null +++ b/Source/WebCore/replay/SerializationMethods.h @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2013 University of Washington. All rights reserved. + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if ENABLE(WEB_REPLAY) + +#include <replay/EncodedValue.h> +#include <replay/NondeterministicInput.h> + +namespace WebCore { + +class Document; +class Frame; +class Page; +class PlatformKeyboardEvent; +class PlatformMouseEvent; +class PlatformWheelEvent; +class PluginData; +class SecurityOrigin; +class URL; + +#if USE(APPKIT) +struct KeypressCommand; +#endif + +uint32_t frameIndexFromDocument(const Document*); +uint32_t frameIndexFromFrame(const Frame*); +Document* documentFromFrameIndex(Page*, uint32_t frameIndex); +Frame* frameFromFrameIndex(Page*, uint32_t frameIndex); + +} // namespace WebCore + +// Template specializations must be defined in the same namespace as the template declaration. +namespace JSC { + +#if USE(APPKIT) +template<> struct EncodingTraits<WebCore::KeypressCommand> { + typedef WebCore::KeypressCommand DecodedType; + + static EncodedValue encodeValue(const WebCore::KeypressCommand& value); + static bool decodeValue(EncodedValue&, WebCore::KeypressCommand& value); +}; +#endif // USE(APPKIT) + +template<> struct EncodingTraits<NondeterministicInputBase> { + typedef NondeterministicInputBase DecodedType; + + static EncodedValue encodeValue(const NondeterministicInputBase& value); + static bool decodeValue(EncodedValue&, std::unique_ptr<NondeterministicInputBase>& value); +}; + +template<> struct EncodingTraits<WebCore::PlatformKeyboardEvent> { + typedef WebCore::PlatformKeyboardEvent DecodedType; + + static EncodedValue encodeValue(const WebCore::PlatformKeyboardEvent& value); + static bool decodeValue(EncodedValue&, std::unique_ptr<WebCore::PlatformKeyboardEvent>& value); +}; + +template<> struct EncodingTraits<WebCore::PlatformMouseEvent> { + typedef WebCore::PlatformMouseEvent DecodedType; + + static EncodedValue encodeValue(const WebCore::PlatformMouseEvent& value); + static bool decodeValue(EncodedValue&, std::unique_ptr<WebCore::PlatformMouseEvent>& value); +}; + +template<> struct EncodingTraits<WebCore::PlatformWheelEvent> { + typedef WebCore::PlatformWheelEvent DecodedType; + + static EncodedValue encodeValue(const WebCore::PlatformWheelEvent& value); + static bool decodeValue(EncodedValue&, std::unique_ptr<WebCore::PlatformWheelEvent>& value); +}; + +template<> struct EncodingTraits<WebCore::PluginData> { + typedef RefPtr<WebCore::PluginData> DecodedType; + + static EncodedValue encodeValue(RefPtr<WebCore::PluginData> value); + static bool decodeValue(EncodedValue&, RefPtr<WebCore::PluginData>& value); +}; + +template<> struct EncodingTraits<WebCore::URL> { + typedef WebCore::URL DecodedType; + + static EncodedValue encodeValue(const WebCore::URL& value); + static bool decodeValue(EncodedValue&, WebCore::URL& value); +}; + +template<> struct EncodingTraits<WebCore::SecurityOrigin> { + typedef RefPtr<WebCore::SecurityOrigin> DecodedType; + + static EncodedValue encodeValue(RefPtr<WebCore::SecurityOrigin> value); + static bool decodeValue(EncodedValue&, RefPtr<WebCore::SecurityOrigin>& value); +}; + +} // namespace JSC + +#endif // ENABLE(WEB_REPLAY) diff --git a/Source/WebCore/replay/UserInputBridge.cpp b/Source/WebCore/replay/UserInputBridge.cpp new file mode 100644 index 000000000..233bcc475 --- /dev/null +++ b/Source/WebCore/replay/UserInputBridge.cpp @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2013 University of Washington. All rights reserved. + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "UserInputBridge.h" + +#include "EventHandler.h" +#include "FocusController.h" +#include "Frame.h" +#include "FrameLoadRequest.h" +#include "MainFrame.h" +#include "Page.h" +#include "PlatformKeyboardEvent.h" +#include "PlatformMouseEvent.h" +#include "PlatformWheelEvent.h" + +#if ENABLE(WEB_REPLAY) +#include "ReplayController.h" +#include "SerializationMethods.h" +#include "WebReplayInputs.h" +#include <replay/InputCursor.h> +#endif + +#define EARLY_RETURN_IF_SHOULD_IGNORE_INPUT \ + do { \ + if (inputSource == InputSource::User && m_state == UserInputBridge::State::Replaying) \ + return true; \ + } while (false) + +namespace WebCore { + +UserInputBridge::UserInputBridge(Page& page) + : m_page(page) +#if ENABLE(WEB_REPLAY) + , m_state(UserInputBridge::State::Open) +#endif +{ +} + +#if ENABLE(WEB_REPLAY) +InputCursor& UserInputBridge::activeCursor() const +{ + return m_page.replayController().activeInputCursor(); +} +#endif + +#if ENABLE(CONTEXT_MENUS) +bool UserInputBridge::handleContextMenuEvent(const PlatformMouseEvent& mouseEvent, const Frame* frame, InputSource) +{ + return frame->eventHandler().sendContextMenuEvent(mouseEvent); +} +#endif + +bool UserInputBridge::handleMousePressEvent(const PlatformMouseEvent& mouseEvent, InputSource inputSource) +{ +#if ENABLE(WEB_REPLAY) + EARLY_RETURN_IF_SHOULD_IGNORE_INPUT; + + InputCursor& cursor = activeCursor(); + if (cursor.isCapturing()) { + std::unique_ptr<PlatformMouseEvent> ownedEvent = std::make_unique<PlatformMouseEvent>(mouseEvent); + cursor.appendInput<HandleMousePress>(WTFMove(ownedEvent)); + } + EventLoopInputExtent extent(cursor); +#else + UNUSED_PARAM(inputSource); +#endif + + return m_page.mainFrame().eventHandler().handleMousePressEvent(mouseEvent); +} + +bool UserInputBridge::handleMouseReleaseEvent(const PlatformMouseEvent& mouseEvent, InputSource inputSource) +{ +#if ENABLE(WEB_REPLAY) + EARLY_RETURN_IF_SHOULD_IGNORE_INPUT; + + InputCursor& cursor = activeCursor(); + if (cursor.isCapturing()) { + std::unique_ptr<PlatformMouseEvent> ownedEvent = std::make_unique<PlatformMouseEvent>(mouseEvent); + cursor.appendInput<HandleMouseRelease>(WTFMove(ownedEvent)); + } + EventLoopInputExtent extent(cursor); +#else + UNUSED_PARAM(inputSource); +#endif + + return m_page.mainFrame().eventHandler().handleMouseReleaseEvent(mouseEvent); +} + +bool UserInputBridge::handleMouseMoveEvent(const PlatformMouseEvent& mouseEvent, InputSource inputSource) +{ +#if ENABLE(WEB_REPLAY) + EARLY_RETURN_IF_SHOULD_IGNORE_INPUT; + + InputCursor& cursor = activeCursor(); + if (cursor.isCapturing()) { + std::unique_ptr<PlatformMouseEvent> ownedEvent = std::make_unique<PlatformMouseEvent>(mouseEvent); + cursor.appendInput<HandleMouseMove>(WTFMove(ownedEvent), false); + } + EventLoopInputExtent extent(cursor); +#else + UNUSED_PARAM(inputSource); +#endif + + return m_page.mainFrame().eventHandler().mouseMoved(mouseEvent); +} + +bool UserInputBridge::handleMouseMoveOnScrollbarEvent(const PlatformMouseEvent& mouseEvent, InputSource inputSource) +{ +#if ENABLE(WEB_REPLAY) + EARLY_RETURN_IF_SHOULD_IGNORE_INPUT; + + InputCursor& cursor = activeCursor(); + if (cursor.isCapturing()) { + std::unique_ptr<PlatformMouseEvent> ownedEvent = std::make_unique<PlatformMouseEvent>(mouseEvent); + cursor.appendInput<HandleMouseMove>(WTFMove(ownedEvent), true); + } + EventLoopInputExtent extent(cursor); +#else + UNUSED_PARAM(inputSource); +#endif + + return m_page.mainFrame().eventHandler().passMouseMovedEventToScrollbars(mouseEvent); +} + +bool UserInputBridge::handleMouseForceEvent(const PlatformMouseEvent& mouseEvent, InputSource) +{ + return m_page.mainFrame().eventHandler().handleMouseForceEvent(mouseEvent); +} + +bool UserInputBridge::handleKeyEvent(const PlatformKeyboardEvent& keyEvent, InputSource inputSource) +{ +#if ENABLE(WEB_REPLAY) + EARLY_RETURN_IF_SHOULD_IGNORE_INPUT; + + InputCursor& cursor = activeCursor(); + if (cursor.isCapturing()) { + std::unique_ptr<PlatformKeyboardEvent> ownedEvent = std::make_unique<PlatformKeyboardEvent>(keyEvent); + cursor.appendInput<HandleKeyPress>(WTFMove(ownedEvent)); + } + EventLoopInputExtent extent(cursor); +#else + UNUSED_PARAM(inputSource); +#endif + + return m_page.focusController().focusedOrMainFrame().eventHandler().keyEvent(keyEvent); +} + +bool UserInputBridge::handleAccessKeyEvent(const PlatformKeyboardEvent& keyEvent, InputSource) +{ + return m_page.focusController().focusedOrMainFrame().eventHandler().handleAccessKey(keyEvent); +} + +bool UserInputBridge::handleWheelEvent(const PlatformWheelEvent& wheelEvent, InputSource inputSource) +{ +#if ENABLE(WEB_REPLAY) + EARLY_RETURN_IF_SHOULD_IGNORE_INPUT; + + InputCursor& cursor = activeCursor(); + if (cursor.isCapturing()) { + std::unique_ptr<PlatformWheelEvent> ownedEvent = std::make_unique<PlatformWheelEvent>(wheelEvent); + cursor.appendInput<HandleWheelEvent>(WTFMove(ownedEvent)); + } + EventLoopInputExtent extent(cursor); +#else + UNUSED_PARAM(inputSource); +#endif + + return m_page.mainFrame().eventHandler().handleWheelEvent(wheelEvent); +} + +void UserInputBridge::focusSetActive(bool active, InputSource) +{ + m_page.focusController().setActive(active); +} + +void UserInputBridge::focusSetFocused(bool focused, InputSource) +{ + m_page.focusController().setFocused(focused); +} + +bool UserInputBridge::scrollRecursively(ScrollDirection direction, ScrollGranularity granularity, InputSource inputSource) +{ +#if ENABLE(WEB_REPLAY) + EARLY_RETURN_IF_SHOULD_IGNORE_INPUT; + + InputCursor& cursor = activeCursor(); + if (cursor.isCapturing()) + cursor.appendInput<ScrollPage>(direction, granularity); + + EventLoopInputExtent extent(cursor); +#else + UNUSED_PARAM(inputSource); +#endif + + return m_page.focusController().focusedOrMainFrame().eventHandler().scrollRecursively(direction, granularity, nullptr); +} + +bool UserInputBridge::logicalScrollRecursively(ScrollLogicalDirection direction, ScrollGranularity granularity, InputSource inputSource) +{ +#if ENABLE(WEB_REPLAY) + EARLY_RETURN_IF_SHOULD_IGNORE_INPUT; + + InputCursor& cursor = activeCursor(); + if (cursor.isCapturing()) + cursor.appendInput<LogicalScrollPage>(direction, granularity); + + EventLoopInputExtent extent(cursor); +#else + UNUSED_PARAM(inputSource); +#endif + + return m_page.focusController().focusedOrMainFrame().eventHandler().logicalScrollRecursively(direction, granularity, nullptr); +} + +void UserInputBridge::loadRequest(const FrameLoadRequest& request, InputSource) +{ + m_page.mainFrame().loader().load(request); +} + +void UserInputBridge::reloadFrame(Frame* frame, bool endToEndReload, bool contentBlockersEnabled, InputSource) +{ + frame->loader().reload(endToEndReload, contentBlockersEnabled); +} + +void UserInputBridge::stopLoadingFrame(Frame* frame, InputSource) +{ + frame->loader().stopForUserCancel(); +} + +bool UserInputBridge::tryClosePage(InputSource) +{ + return m_page.mainFrame().loader().shouldClose(); +} + +} // namespace WebCore diff --git a/Source/WebCore/replay/UserInputBridge.h b/Source/WebCore/replay/UserInputBridge.h new file mode 100644 index 000000000..9746211d7 --- /dev/null +++ b/Source/WebCore/replay/UserInputBridge.h @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2012, 2013 University of Washington. All rights reserved. + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "ScrollTypes.h" +#include <wtf/Noncopyable.h> + +namespace JSC { +class InputCursor; +} + +namespace WebCore { + +struct FrameLoadRequest; + +class Frame; +class Page; +class PlatformKeyboardEvent; +class PlatformMouseEvent; +class PlatformWheelEvent; + +// Real user inputs come from WebKit or WebKit2. +// Synthetic inputs come from within WebCore (i.e., from web replay or fake mouse moves). +enum class InputSource { + User, + Synthetic +}; + +class UserInputBridge { + WTF_MAKE_NONCOPYABLE(UserInputBridge); +public: + UserInputBridge(Page&); + +#if ENABLE(WEB_REPLAY) + enum class State { + Capturing, + Open, + Replaying, + }; + + void setState(State bridgeState) { m_state = bridgeState; } + State state() const { return m_state; } + + JSC::InputCursor& activeCursor() const; +#endif + + // User input APIs. +#if ENABLE(CONTEXT_MENUS) + WEBCORE_EXPORT bool handleContextMenuEvent(const PlatformMouseEvent&, const Frame*, InputSource source = InputSource::User); +#endif + WEBCORE_EXPORT bool handleMousePressEvent(const PlatformMouseEvent&, InputSource source = InputSource::User); + WEBCORE_EXPORT bool handleMouseReleaseEvent(const PlatformMouseEvent&, InputSource source = InputSource::User); + WEBCORE_EXPORT bool handleMouseMoveEvent(const PlatformMouseEvent&, InputSource source = InputSource::User); + WEBCORE_EXPORT bool handleMouseMoveOnScrollbarEvent(const PlatformMouseEvent&, InputSource source = InputSource::User); + WEBCORE_EXPORT bool handleMouseForceEvent(const PlatformMouseEvent&, InputSource = InputSource::User); + WEBCORE_EXPORT bool handleWheelEvent(const PlatformWheelEvent&, InputSource source = InputSource::User); + WEBCORE_EXPORT bool handleKeyEvent(const PlatformKeyboardEvent&, InputSource source = InputSource::User); + WEBCORE_EXPORT bool handleAccessKeyEvent(const PlatformKeyboardEvent&, InputSource source = InputSource::User); + void focusSetActive(bool active, InputSource source = InputSource::User); + void focusSetFocused(bool focused, InputSource source = InputSource::User); + WEBCORE_EXPORT bool scrollRecursively(ScrollDirection, ScrollGranularity, InputSource source = InputSource::User); + bool logicalScrollRecursively(ScrollLogicalDirection, ScrollGranularity, InputSource source = InputSource::User); + + // Navigation APIs. + WEBCORE_EXPORT void loadRequest(const FrameLoadRequest&, InputSource source = InputSource::User); + WEBCORE_EXPORT void reloadFrame(Frame*, bool endToEndReload, bool contentBlockersEnabled, InputSource = InputSource::User); + WEBCORE_EXPORT void stopLoadingFrame(Frame*, InputSource source = InputSource::User); + WEBCORE_EXPORT bool tryClosePage(InputSource source = InputSource::User); + +private: + Page& m_page; +#if ENABLE(WEB_REPLAY) + State m_state; +#endif +}; + +} // namespace WebCore diff --git a/Source/WebCore/replay/WebInputs.json b/Source/WebCore/replay/WebInputs.json new file mode 100644 index 000000000..b7e5bcd83 --- /dev/null +++ b/Source/WebCore/replay/WebInputs.json @@ -0,0 +1,244 @@ +{ + "types": { + "WebCore": [ + { + "name": "EncodedCType", "mode": "SCALAR", "storage": "uint8_t", + "flags": ["ENUM_CLASS"], + "values": [ + "Boolean", + "Int", + "String", + "Unsigned" + ], + "header": "replay/MemoizedDOMResult.h" + }, + { + "name": "Modifier", "mode": "SCALAR", + "enclosing_class": "PlatformEvent", + "flags": ["OPTION_SET"], + "values": ["AltKey", "CtrlKey", "MetaKey", "ShiftKey"], + "header": "platform/PlatformEvent.h" + }, + { + "name": "MouseButton", "mode": "SCALAR", "storage": "int8_t", + "flags": ["ENUM"], + "values": ["NoButton", "LeftButton", "MiddleButton", "RightButton"], + "header": "platform/PlatformMouseEvent.h" + }, + { + "name": "Page", "mode": "OWNED", + "header": "page/Page.h" + }, + { + "name": "PlatformKeyboardEvent", "mode": "OWNED", + "header": "platform/PlatformKeyboardEvent.h" + }, + { + "name": "PlatformMouseEvent", "mode": "OWNED", + "header": "platform/PlatformMouseEvent.h" + }, + { + "name": "PlatformWheelEvent", "mode": "OWNED", + "header": "platform/PlatformWheelEvent.h" + }, + { + "name": "PlatformWheelEventGranularity", "mode": "SCALAR", "storage": "uint8_t", + "flags": ["ENUM"], + "values": ["ScrollByPageWheelEvent", "ScrollByPixelWheelEvent"], + "header": "platform/PlatformWheelEvent.h" + }, + { + "name": "PlatformWheelEventPhase", "mode": "SCALAR", "storage": "uint8_t", + "flags": ["ENUM"], + "guard": "PLATFORM(COCOA)", + "values": [ + "PlatformWheelEventPhaseNone", + "PlatformWheelEventPhaseBegan", + "PlatformWheelEventPhaseStationary", + "PlatformWheelEventPhaseChanged", + "PlatformWheelEventPhaseEnded", + "PlatformWheelEventPhaseCancelled", + "PlatformWheelEventPhaseMayBegin" + ], + "header": "platform/PlatformWheelEvent.h" + }, + { + "name": "PluginData", "mode": "SHARED", + "header": "plugins/PluginData.h" + }, + { + "name": "PluginLoadClientPolicy", "mode": "SCALAR", "storage": "uint8_t", + "flags": ["ENUM"], + "values": ["PluginLoadClientPolicyUndefined", "PluginLoadClientPolicyBlock", "PluginLoadClientPolicyAsk", "PluginLoadClientPolicyAllow", "PluginLoadClientPolicyAllowAlways"], + "header": "plugins/PluginData.h" + }, + + { + "name": "ScrollDirection", "mode": "SCALAR", "storage": "uint8_t", + "flags": ["ENUM"], + "values": ["ScrollUp", "ScrollDown", "ScrollLeft", "ScrollRight"], + "header": "platform/ScrollTypes.h" + }, + { + "name": "ScrollGranularity", "mode": "SCALAR", "storage": "uint8_t", + "flags": ["ENUM"], + "values": [ + "ScrollByLine", + "ScrollByPage", + "ScrollByDocument", + "ScrollByPixel", + "ScrollByPrecisePixel" + ], + "header": "platform/ScrollTypes.h" + }, + { + "name": "ScrollLogicalDirection", "mode": "SCALAR", "storage": "uint8_t", + "flags": ["ENUM"], + "values": [ + "ScrollBlockDirectionBackward", + "ScrollBlockDirectionForward", + "ScrollInlineDirectionBackward", + "ScrollInlineDirectionForward" + ], + "header": "platform/ScrollTypes.h" + }, + { + "name": "SecurityOrigin", "mode": "SHARED", + "header": "page/SecurityOrigin.h" + }, + { + "name": "Type", "mode": "SCALAR", "storage": "uint8_t", + "enclosing_class": "PlatformEvent", + "flags": ["ENUM"], + "values": [ + "NoType", + "KeyDown", + "KeyUp", + "RawKeyDown", + "Char", + "MouseMoved", + "MousePressed", + "MouseReleased", + "MouseScroll", + "Wheel" + ], + "guarded_values": { + "ENABLE(TOUCH_EVENTS)": [ + "TouchStart", + "TouchMove", + "TouchEnd", + "TouchCancel" + ] + }, + "header": "platform/PlatformEvent.h" + }, + { + "name": "URL", "mode": "HEAVY_SCALAR", + "header": "platform/URL.h" + } + ] + }, + + "inputs": { + "WebCore": [ + { + "name": "BeginSegmentSentinel", + "description": "A sentinel input to signal the start of a segment.", + "queue": "EVENT_LOOP", + "members": [ ] + }, + { + "name": "DocumentLastModifiedDate", + "description": "A fallback value used for the document's last modified date if the Last-Modified header can't be found or used.", + "queue": "SCRIPT_MEMOIZED", + "members": [ + { "name": "fallbackValue", "type": "double" } + ] + }, + { + "name": "EndSegmentSentinel", + "description": "A sentinel input to signal the end of a segment.", + "queue": "EVENT_LOOP", + "members": [ ] + }, + { + "name": "HandleMouseMove", + "description": "The embedder signalled a mouse move event.", + "queue": "EVENT_LOOP", + "members": [ + { "name": "platformEvent", "type": "PlatformMouseEvent" }, + { "name": "scrollbarTargeted", "type": "bool" } + ] + }, + { + "name": "HandleMousePress", + "description": "The embedder signalled a mouse press event.", + "queue": "EVENT_LOOP", + "members": [ + { "name": "platformEvent", "type": "PlatformMouseEvent" } + ] + }, + { + "name": "HandleMouseRelease", + "description": "The embedder signalled a mouse release event.", + "queue": "EVENT_LOOP", + "members": [ + { "name": "platformEvent", "type": "PlatformMouseEvent" } + ] + }, + { + "name": "HandleKeyPress", + "description": "The embedder signalled a key press event.", + "queue": "EVENT_LOOP", + "members": [ + { "name": "platformEvent", "type": "PlatformKeyboardEvent" } + ] + }, + { + "name": "HandleWheelEvent", + "description": "The embedder signalled a mouse wheel event.", + "queue": "EVENT_LOOP", + "members": [ + { "name": "platformEvent", "type": "PlatformWheelEvent" } + ] + }, + { + "name": "InitialNavigation", + "description": "Initiate the initial main frame navigation.", + "queue": "EVENT_LOOP", + "flags": ["HIDDEN", "CREATE_FROM_PAGE"], + "members": [ + { "name": "securityOrigin", "type": "SecurityOrigin" }, + { "name": "url", "type": "URL" }, + { "name": "referrer", "type": "String" } + ] + }, + { + "name": "FetchPluginData", + "description": "Plugin data was requested through DOMPluginArray or DOMMimeTypeArray.", + "queue": "SCRIPT_MEMOIZED", + "members": [ + { "name": "pluginData", "type": "PluginData" } + ] + }, + { + "name": "LogicalScrollPage", + "description": "The embedder signalled a logical scroll event.", + "queue": "EVENT_LOOP", + "members": [ + { "name": "direction", "type": "ScrollLogicalDirection" }, + { "name": "granularity", "type": "ScrollGranularity" } + ] + }, + { + "name": "ScrollPage", + "description": "The embedder signalled a scroll event.", + "queue": "EVENT_LOOP", + "members": [ + { "name": "direction", "type": "ScrollDirection" }, + { "name": "granularity", "type": "ScrollGranularity" } + ] + } + ] + } +} |