Web Replay: capture and replay mouse events
authorbburg@apple.com <bburg@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 20 Mar 2014 22:14:41 +0000 (22:14 +0000)
committerbburg@apple.com <bburg@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 20 Mar 2014 22:14:41 +0000 (22:14 +0000)
https://bugs.webkit.org/show_bug.cgi?id=129395

Reviewed by Joseph Pecoraro.

.:

Create a manual test for capture/replay of mouse events.
Copy over the crypto-md5.js library from SunSpider.

* ManualTests/inspector/replay-mouse-events.html: Added.
* ManualTests/inspector/resources/crypto-md5.js: Added.

Source/WebCore:

Add support for capturing and replaying mouse inputs that come from WebKit2.
Hook up the UserInputBridge to session state changes in the ReplayController so
that the bridge knows when to capture or deny mouse inputs.

Test: ManualTests/inspector/replay-mouse-events.html

* platform/PlatformEvent.h: Give explicit storage types to Modifiers and Type enums
so they can be forward-declared.
* platform/PlatformMouseEvent.h: Give an explicit storage type to enum MouseButton.
Add operator== and operator!= for MouseButton to work around an MSVC bug.

* replay/ReplayController.cpp: Perform session state changes in a helper function, and
at the same time change the state of the page's user input bridge.
(WebCore::ReplayController::setSessionState):
(WebCore::ReplayController::startCapturing):
(WebCore::ReplayController::stopCapturing):
(WebCore::ReplayController::startPlayback):
(WebCore::ReplayController::cancelPlayback):

* replay/ReplayInputDispatchMethods.cpp: Add dispatch methods for new inputs.
(WebCore::HandleMouseMove::dispatch):
(WebCore::HandleMousePress::dispatch):
(WebCore::HandleMouseRelease::dispatch):

* replay/SerializationMethods.cpp: Add helper macros so that encode/decode methods look
symmetric with one data member per line. This helps reduce unintentional inconsistencies.
(JSC::EncodingTraits<PlatformMouseEvent>::encodeValue): Added.
(JSC::EncodingTraits<PlatformMouseEvent>::decodeValue): Added.
* replay/SerializationMethods.h:

* replay/UserInputBridge.cpp: Fill in the bridge method implementations for mouse
events, adding helpers and macros as necessary to reduce boilerplate.
(WebCore::UserInputBridge::activeCursor): Added.
(WebCore::UserInputBridge::handleMousePressEvent):
(WebCore::UserInputBridge::handleMouseReleaseEvent):
(WebCore::UserInputBridge::handleMouseMoveEvent):
(WebCore::UserInputBridge::handleMouseMoveOnScrollbarEvent):

* replay/UserInputBridge.h: Add a bridge state enum along with getters and setters.
The enum value controls whether the bridge should capture commands, deny non-synthetic
commands (from the user), or allow anything to pass (the default).
(WebCore::UserInputBridge::setState): Added.
(WebCore::UserInputBridge::state): Added.

* replay/WebInputs.json: Add inputs HandleMouseMove, HandleMousePress, HandleMouseRelease.
Add enum definitions for PlatformEvent::Type, PlatformEvent::Modifiers, and PlatformMouseEvent::MouseButton.
Alphabetize the existing data type definitions.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@166006 268f45cc-cd09-0410-ab3c-d52691b4dbfc

15 files changed:
ChangeLog
ManualTests/inspector/replay-mouse-events.html [new file with mode: 0644]
ManualTests/inspector/resources/crypto-md5.js [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/platform/PlatformEvent.h
Source/WebCore/platform/PlatformMouseEvent.h
Source/WebCore/platform/win/PlatformMouseEventWin.cpp
Source/WebCore/replay/ReplayController.cpp
Source/WebCore/replay/ReplayController.h
Source/WebCore/replay/ReplayInputDispatchMethods.cpp
Source/WebCore/replay/SerializationMethods.cpp
Source/WebCore/replay/SerializationMethods.h
Source/WebCore/replay/UserInputBridge.cpp
Source/WebCore/replay/UserInputBridge.h
Source/WebCore/replay/WebInputs.json

index 6985a2d02defb2ffdc8cf3f7512fa35568461b2f..501e34e9bd150f7939a664eae72f3c5e7b9b7c78 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,16 @@
+2014-03-20  Brian Burg  <bburg@apple.com>
+
+        Web Replay: capture and replay mouse events
+        https://bugs.webkit.org/show_bug.cgi?id=129395
+
+        Reviewed by Joseph Pecoraro.
+
+        Create a manual test for capture/replay of mouse events.
+        Copy over the crypto-md5.js library from SunSpider.
+
+        * ManualTests/inspector/replay-mouse-events.html: Added.
+        * ManualTests/inspector/resources/crypto-md5.js: Added.
+
 2014-03-20  Zan Dobersek  <zdobersek@igalia.com>
 
         [GTK][CMake] Add support for building with Clang
diff --git a/ManualTests/inspector/replay-mouse-events.html b/ManualTests/inspector/replay-mouse-events.html
new file mode 100644 (file)
index 0000000..3f1d8ec
--- /dev/null
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+        "http://www.w3.org/TR/html4/strict.dtd">
+<html lang="en">
+<head>
+<script src="./resources/crypto-md5.js"></script>
+<script type="text/javascript" language="javascript" charset="utf-8">
+
+    function glyphForType(type) {
+        switch (type) {
+        case "mousedown": return "D";
+        case "mousemove": return "M";
+        case "mouseup": return "U";
+        case "mouseover": return "I";
+        case "mouseout": return "O";
+        case "click": return "C";
+        case "dblclick": return "2";
+        }
+    }
+
+    document.onmousedown = handleEvent;
+    document.onmousemove = handleEvent;
+    document.onmouseup = handleEvent;
+    document.onmouseover = handleEvent;
+    document.onmouseout = handleEvent;
+    document.onclick = handleEvent;
+    document.ondblclick = handleEvent;
+    
+    window.dumpedEvents = [];
+    
+    function handleEvent(event) {
+        var properties = ["type", "eventPhase", "bubbles", "cancelable", "screenX", "screenY", "clientX", "clientY", "ctrlKey", "shiftKey", "altKey", "metaKey", "button"];
+        obj = {};
+        for (var key of properties)
+            obj[key] = event[key];
+    
+        dumpedEvents.push(obj);
+
+        var block = createBlock(hex_md5(JSON.stringify(obj)));
+        block.textContent = glyphForType(event.type);
+        var blocksContainer = document.getElementById("blocks");
+        blocksContainer.appendChild(block);
+        
+        var hashLabel = document.getElementById("hash");
+        hash.textContent = hex_md5(JSON.stringify(dumpedEvents));
+    }
+    
+    function createBlock(hash) {
+        var color = "#" + hash.substr(0,6);
+        var block = document.createElement("span");
+        block.style.backgroundColor = color;
+        return block;
+    }
+    
+    function stateHash() {
+        return hex_md5(JSON.stringify(dumpedEvents));
+    }
+    
+</script>
+
+<style type="text/css">
+body {
+    max-width: 800px;
+}
+#blocks {
+    display: -webkit-flex;
+    width: 600px;
+    -webkit-flex-flow: row wrap;
+}
+    
+#blocks > span {
+    width: 20px;
+    height: 20px;
+    border-radius: 10px;
+    font-size: 18px;
+    font-weight: bold;
+    font-family: sans-serif;
+    color: #fff;
+    text-align: center;
+}
+</style>
+</head>
+<body>
+<p>This page is a manual test for capture and replay of mouse-related events.</p>
+<p>Below, a block is created for each mouse event, where the color is derived from a hash of the mouse event. At the bottom is a cumulative hash of all mouse event data.</p>
+<hr/>
+<p>
+To test the replay functionality, open the Web Inspector, start capturing, and then move the mouse around this test page. After some time, stop capturing and then replay. The replayed execution should produce the same sequence of blocks. More importantly, the cumulative hash value should be the same at the end of capturing and at the end of any subsequent replays.</p>
+</p>
+<hr/>
+<div id="hash"></div>
+<div id="blocks"></div>
+</body>
+</html>
diff --git a/ManualTests/inspector/resources/crypto-md5.js b/ManualTests/inspector/resources/crypto-md5.js
new file mode 100644 (file)
index 0000000..46d2aab
--- /dev/null
@@ -0,0 +1,256 @@
+/*
+ * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
+ * Digest Algorithm, as defined in RFC 1321.
+ * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
+ * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
+ * Distributed under the BSD License
+ * See http://pajhome.org.uk/crypt/md5 for more info.
+ */
+
+/*
+ * Configurable variables. You may need to tweak these to be compatible with
+ * the server-side, but the defaults work in most cases.
+ */
+var hexcase = 0;  /* hex output format. 0 - lowercase; 1 - uppercase        */
+var b64pad  = ""; /* base-64 pad character. "=" for strict RFC compliance   */
+var chrsz   = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      */
+
+/*
+ * These are the functions you'll usually want to call
+ * They take string arguments and return either hex or base-64 encoded strings
+ */
+function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));}
+function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));}
+function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));}
+function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); }
+function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); }
+function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); }
+
+/*
+ * Perform a simple self-test to see if the VM is working
+ */
+function md5_vm_test()
+{
+  return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";
+}
+
+/*
+ * Calculate the MD5 of an array of little-endian words, and a bit length
+ */
+function core_md5(x, len)
+{
+  /* append padding */
+  x[len >> 5] |= 0x80 << ((len) % 32);
+  x[(((len + 64) >>> 9) << 4) + 14] = len;
+
+  var a =  1732584193;
+  var b = -271733879;
+  var c = -1732584194;
+  var d =  271733878;
+
+  for(var i = 0; i < x.length; i += 16)
+  {
+    var olda = a;
+    var oldb = b;
+    var oldc = c;
+    var oldd = d;
+
+    a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
+    d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
+    c = md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);
+    b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
+    a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
+    d = md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);
+    c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
+    b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
+    a = md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
+    d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
+    c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
+    b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
+    a = md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);
+    d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
+    c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
+    b = md5_ff(b, c, d, a, x[i+15], 22,  1236535329);
+
+    a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
+    d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
+    c = md5_gg(c, d, a, b, x[i+11], 14,  643717713);
+    b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
+    a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
+    d = md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);
+    c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
+    b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
+    a = md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
+    d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
+    c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
+    b = md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);
+    a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
+    d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
+    c = md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);
+    b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
+
+    a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
+    d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
+    c = md5_hh(c, d, a, b, x[i+11], 16,  1839030562);
+    b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
+    a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
+    d = md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);
+    c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
+    b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
+    a = md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);
+    d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
+    c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
+    b = md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);
+    a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
+    d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
+    c = md5_hh(c, d, a, b, x[i+15], 16,  530742520);
+    b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
+
+    a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
+    d = md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);
+    c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
+    b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
+    a = md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);
+    d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
+    c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
+    b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
+    a = md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
+    d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
+    c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
+    b = md5_ii(b, c, d, a, x[i+13], 21,  1309151649);
+    a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
+    d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
+    c = md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);
+    b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
+
+    a = safe_add(a, olda);
+    b = safe_add(b, oldb);
+    c = safe_add(c, oldc);
+    d = safe_add(d, oldd);
+  }
+  return Array(a, b, c, d);
+
+}
+
+/*
+ * These functions implement the four basic operations the algorithm uses.
+ */
+function md5_cmn(q, a, b, x, s, t)
+{
+  return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
+}
+function md5_ff(a, b, c, d, x, s, t)
+{
+  return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
+}
+function md5_gg(a, b, c, d, x, s, t)
+{
+  return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
+}
+function md5_hh(a, b, c, d, x, s, t)
+{
+  return md5_cmn(b ^ c ^ d, a, b, x, s, t);
+}
+function md5_ii(a, b, c, d, x, s, t)
+{
+  return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
+}
+
+/*
+ * Calculate the HMAC-MD5, of a key and some data
+ */
+function core_hmac_md5(key, data)
+{
+  var bkey = str2binl(key);
+  if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz);
+
+  var ipad = Array(16), opad = Array(16);
+  for(var i = 0; i < 16; i++)
+  {
+    ipad[i] = bkey[i] ^ 0x36363636;
+    opad[i] = bkey[i] ^ 0x5C5C5C5C;
+  }
+
+  var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
+  return core_md5(opad.concat(hash), 512 + 128);
+}
+
+/*
+ * Add integers, wrapping at 2^32. This uses 16-bit operations internally
+ * to work around bugs in some JS interpreters.
+ */
+function safe_add(x, y)
+{
+  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
+  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+  return (msw << 16) | (lsw & 0xFFFF);
+}
+
+/*
+ * Bitwise rotate a 32-bit number to the left.
+ */
+function bit_rol(num, cnt)
+{
+  return (num << cnt) | (num >>> (32 - cnt));
+}
+
+/*
+ * Convert a string to an array of little-endian words
+ * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
+ */
+function str2binl(str)
+{
+  var bin = Array();
+  var mask = (1 << chrsz) - 1;
+  for(var i = 0; i < str.length * chrsz; i += chrsz)
+    bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
+  return bin;
+}
+
+/*
+ * Convert an array of little-endian words to a string
+ */
+function binl2str(bin)
+{
+  var str = "";
+  var mask = (1 << chrsz) - 1;
+  for(var i = 0; i < bin.length * 32; i += chrsz)
+    str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
+  return str;
+}
+
+/*
+ * Convert an array of little-endian words to a hex string.
+ */
+function binl2hex(binarray)
+{
+  var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
+  var str = "";
+  for(var i = 0; i < binarray.length * 4; i++)
+  {
+    str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
+           hex_tab.charAt((binarray[i>>2] >> ((i%4)*8  )) & 0xF);
+  }
+  return str;
+}
+
+/*
+ * Convert an array of little-endian words to a base-64 string
+ */
+function binl2b64(binarray)
+{
+  var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+  var str = "";
+  for(var i = 0; i < binarray.length * 4; i += 3)
+  {
+    var triplet = (((binarray[i   >> 2] >> 8 * ( i   %4)) & 0xFF) << 16)
+                | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 )
+                |  ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
+    for(var j = 0; j < 4; j++)
+    {
+      if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
+      else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
+    }
+  }
+  return str;
+}
index 05bae80215bcaef04c7412f7dd59ec237db23c28..906f95619355aa14b9401c9bb9b557cac8f9acda 100644 (file)
@@ -1,3 +1,58 @@
+2014-03-20  Brian Burg  <bburg@apple.com>
+
+        Web Replay: capture and replay mouse events
+        https://bugs.webkit.org/show_bug.cgi?id=129395
+
+        Reviewed by Joseph Pecoraro.
+
+        Add support for capturing and replaying mouse inputs that come from WebKit2.
+        Hook up the UserInputBridge to session state changes in the ReplayController so
+        that the bridge knows when to capture or deny mouse inputs.
+
+        Test: ManualTests/inspector/replay-mouse-events.html
+
+        * platform/PlatformEvent.h: Give explicit storage types to Modifiers and Type enums
+        so they can be forward-declared.
+        * platform/PlatformMouseEvent.h: Give an explicit storage type to enum MouseButton.
+        Add operator== and operator!= for MouseButton to work around an MSVC bug.
+
+        * replay/ReplayController.cpp: Perform session state changes in a helper function, and
+        at the same time change the state of the page's user input bridge.
+        (WebCore::ReplayController::setSessionState):
+        (WebCore::ReplayController::startCapturing):
+        (WebCore::ReplayController::stopCapturing):
+        (WebCore::ReplayController::startPlayback):
+        (WebCore::ReplayController::cancelPlayback):
+
+        * replay/ReplayInputDispatchMethods.cpp: Add dispatch methods for new inputs.
+        (WebCore::HandleMouseMove::dispatch):
+        (WebCore::HandleMousePress::dispatch):
+        (WebCore::HandleMouseRelease::dispatch):
+
+        * replay/SerializationMethods.cpp: Add helper macros so that encode/decode methods look
+        symmetric with one data member per line. This helps reduce unintentional inconsistencies.
+        (JSC::EncodingTraits<PlatformMouseEvent>::encodeValue): Added.
+        (JSC::EncodingTraits<PlatformMouseEvent>::decodeValue): Added.
+        * replay/SerializationMethods.h:
+
+        * replay/UserInputBridge.cpp: Fill in the bridge method implementations for mouse
+        events, adding helpers and macros as necessary to reduce boilerplate.
+        (WebCore::UserInputBridge::activeCursor): Added.
+        (WebCore::UserInputBridge::handleMousePressEvent):
+        (WebCore::UserInputBridge::handleMouseReleaseEvent):
+        (WebCore::UserInputBridge::handleMouseMoveEvent):
+        (WebCore::UserInputBridge::handleMouseMoveOnScrollbarEvent):
+
+        * replay/UserInputBridge.h: Add a bridge state enum along with getters and setters.
+        The enum value controls whether the bridge should capture commands, deny non-synthetic
+        commands (from the user), or allow anything to pass (the default).
+        (WebCore::UserInputBridge::setState): Added.
+        (WebCore::UserInputBridge::state): Added.
+
+        * replay/WebInputs.json: Add inputs HandleMouseMove, HandleMousePress, HandleMouseRelease.
+        Add enum definitions for PlatformEvent::Type, PlatformEvent::Modifiers, and PlatformMouseEvent::MouseButton.
+        Alphabetize the existing data type definitions.
+
 2014-03-20  Tim Horton  <timothy_horton@apple.com>
 
         Add WebCore::IOSurface wrapper
index 533a924d12cf403713648510c76f10eaffab1a93..bffdb8256b5bf1e0b9608b0243a2fe155ca6999d 100644 (file)
@@ -30,7 +30,7 @@ namespace WebCore {
 
 class PlatformEvent {
 public:
-    enum Type {
+    enum Type : uint8_t {
         NoType = 0,
 
         // PlatformKeyboardEvent
@@ -57,7 +57,7 @@ public:
 #endif
     };
 
-    enum Modifiers {
+    enum Modifiers : uint8_t {
         AltKey      = 1 << 0,
         CtrlKey     = 1 << 1,
         MetaKey     = 1 << 2,
index bde273faa8708cf12759036db1dacc1ecf28ba65..4da63190fe0360a75f176027a1d18b8966d8382b 100644 (file)
@@ -44,7 +44,7 @@ typedef struct _Evas_Event_Mouse_Move Evas_Event_Mouse_Move;
 namespace WebCore {
     
     // These button numbers match the ones used in the DOM API, 0 through 2, except for NoButton which isn't specified.
-    enum MouseButton { NoButton = -1, LeftButton, MiddleButton, RightButton };
+    enum MouseButton : int8_t { NoButton = -1, LeftButton, MiddleButton, RightButton };
 
     class PlatformMouseEvent : public PlatformEvent {
     public:
@@ -128,6 +128,13 @@ namespace WebCore {
 #endif
     };
 
+#if PLATFORM(WIN)
+    // These methods are necessary to work around the fact that MSVC will not find a most-specific
+    // operator== to use after implicitly converting MouseButton to an unsigned short.
+    bool operator==(unsigned short a, MouseButton b);
+    bool operator!=(unsigned short a, MouseButton b);
+#endif
+
 } // namespace WebCore
 
 #endif // PlatformMouseEvent_h
index e6ac14950dd438885b068dd55300f9f50ff285d0..143bbaa7326103e30c85c85eecfed32d649df913 100644 (file)
@@ -121,4 +121,14 @@ PlatformMouseEvent::PlatformMouseEvent(HWND hWnd, UINT message, WPARAM wParam, L
     }
 }
 
+bool operator==(unsigned short a, MouseButton b)
+{
+    return a == static_cast<unsigned short>(b);
+}
+
+bool operator!=(unsigned short a, MouseButton b)
+{
+    return a != static_cast<unsigned short>(b);
+}
+
 } // namespace WebCore
index fe8472bf2ee8d463bd280e5cb4b4d5076075eb5d..056ef0779d1a0b5b8a18bba82940417489b560b7 100644 (file)
@@ -44,6 +44,7 @@
 #include "ReplaySessionSegment.h"
 #include "ReplayingInputCursor.h"
 #include "ScriptController.h"
+#include "UserInputBridge.h"
 #include "WebReplayInputs.h"
 #include <replay/EmptyInputCursor.h>
 #include <wtf/text/CString.h>
@@ -64,6 +65,32 @@ ReplayController::ReplayController(Page& page)
 {
 }
 
+void ReplayController::setSessionState(SessionState state)
+{
+    ASSERT(state != m_sessionState);
+
+    switch (m_sessionState) {
+    case SessionState::Capturing:
+        ASSERT(state == SessionState::Inactive);
+
+        m_sessionState = state;
+        m_page.userInputBridge().setState(UserInputBridge::State::Capturing);
+        break;
+
+    case SessionState::Inactive:
+        m_sessionState = state;
+        m_page.userInputBridge().setState(UserInputBridge::State::Open);
+        break;
+
+    case SessionState::Replaying:
+        ASSERT(state == SessionState::Inactive);
+
+        m_sessionState = state;
+        m_page.userInputBridge().setState(UserInputBridge::State::Replaying);
+        break;
+    }
+}
+
 void ReplayController::switchSession(PassRefPtr<ReplaySession> session)
 {
     ASSERT(m_segmentState == SegmentState::Unloaded);
@@ -171,12 +198,13 @@ void ReplayController::startCapturing()
     ASSERT(m_sessionState == SessionState::Inactive);
     ASSERT(m_segmentState == SegmentState::Unloaded);
 
-    m_sessionState = SessionState::Capturing;
+    setSessionState(SessionState::Capturing);
 
     LOG(WebReplay, "%-20s Starting capture.\n", "ReplayController");
     InspectorInstrumentation::captureStarted(&m_page);
 
     m_currentPosition = ReplayPosition(0, 0);
+
     createSegment();
 }
 
@@ -187,7 +215,7 @@ void ReplayController::stopCapturing()
 
     completeSegment();
 
-    m_sessionState = SessionState::Inactive;
+    setSessionState(SessionState::Inactive);
 
     LOG(WebReplay, "%-20s Stopping capture.\n", "ReplayController");
     InspectorInstrumentation::captureStopped(&m_page);
@@ -247,7 +275,7 @@ void ReplayController::replayToPosition(const ReplayPosition& position, Dispatch
     m_dispatchSpeed = speed;
 
     if (m_sessionState != SessionState::Replaying)
-        m_sessionState = SessionState::Replaying;
+        setSessionState(SessionState::Replaying);
 
     if (m_segmentState == SegmentState::Unloaded)
         loadSegmentAtIndex(position.segmentOffset);
index af733d982f9d4a68fb0df1c72ffa478148c14aaf..ca8cbd6203ccc30d0c6b3e2ca48bd08d6d1f760e 100644 (file)
@@ -150,6 +150,8 @@ private:
 
     EventLoopInputDispatcher& dispatcher() const;
 
+    void setSessionState(SessionState);
+
     Page& m_page;
 
     RefPtr<ReplaySessionSegment> m_loadedSegment;
index 14f76cec2265feffb28f37269b38d021a476a39e..385522519d21c8508e0d91250167378ca4c94999 100644 (file)
@@ -34,6 +34,7 @@
 #include "Page.h"
 #include "ReplayController.h"
 #include "URL.h"
+#include "UserInputBridge.h"
 
 namespace WebCore {
 
@@ -52,6 +53,25 @@ void InitialNavigation::dispatch(ReplayController& controller)
     controller.page().mainFrame().navigationScheduler().scheduleLocationChange(m_securityOrigin.get(), m_url, m_referrer, true, true);
 }
 
+// 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);
+}
+
 } // namespace WebCore
 
 #endif // ENABLE(WEB_REPLAY)
index 1f1504302ed949a0ee7fe6e28de14fe40fc6080d..e8609846b7f2f43b54e43d7278e4932cb157a1f9 100644 (file)
 #if ENABLE(WEB_REPLAY)
 
 #include "AllReplayInputs.h"
+#include "PlatformMouseEvent.h"
 #include "ReplayInputTypes.h"
 #include "SecurityOrigin.h"
 #include "URL.h"
 
+using WebCore::IntPoint;
+using WebCore::MouseButton;
+using WebCore::PlatformEvent;
+using WebCore::PlatformMouseEvent;
 using WebCore::SecurityOrigin;
 using WebCore::URL;
 using WebCore::inputTypes;
@@ -45,6 +50,14 @@ using WebCore::name; \
 WEB_REPLAY_INPUT_NAMES_FOR_EACH(IMPORT_FROM_WEBCORE_NAMESPACE)
 #undef IMPORT_FROM_WEBCORE_NAMESPACE
 
+#define ENCODE_SCALAR_TYPE_WITH_KEY(_encodedValue, _type, _key, _value) \
+    _encodedValue.put<_type>(ASCIILiteral(#_key), _value)
+
+#define DECODE_SCALAR_TYPE_WITH_KEY(_encodedValue, _type, _key) \
+    _type _key; \
+    if (!_encodedValue.get<_type>(ASCIILiteral(#_key), _key)) \
+        return false
+
 namespace JSC {
 
 EncodedValue EncodingTraits<NondeterministicInputBase>::encodeValue(const NondeterministicInputBase& input)
@@ -105,6 +118,48 @@ bool EncodingTraits<NondeterministicInputBase>::decodeValue(EncodedValue& encode
     return false;
 }
 
+EncodedValue EncodingTraits<PlatformMouseEvent>::encodeValue(const PlatformMouseEvent& input)
+{
+    EncodedValue encodedValue = EncodedValue::createObject();
+
+    ENCODE_SCALAR_TYPE_WITH_KEY(encodedValue, int, positionX, input.position().x());
+    ENCODE_SCALAR_TYPE_WITH_KEY(encodedValue, int, positionY, input.position().y());
+    ENCODE_SCALAR_TYPE_WITH_KEY(encodedValue, int, globalPositionX, input.globalPosition().x());
+    ENCODE_SCALAR_TYPE_WITH_KEY(encodedValue, int, globalPositionY, input.globalPosition().y());
+    ENCODE_SCALAR_TYPE_WITH_KEY(encodedValue, MouseButton, button, input.button());
+    ENCODE_SCALAR_TYPE_WITH_KEY(encodedValue, PlatformEvent::Type, type, input.type());
+    ENCODE_SCALAR_TYPE_WITH_KEY(encodedValue, int, clickCount, input.clickCount());
+    ENCODE_SCALAR_TYPE_WITH_KEY(encodedValue, bool, shiftKey, input.shiftKey());
+    ENCODE_SCALAR_TYPE_WITH_KEY(encodedValue, bool, ctrlKey, input.ctrlKey());
+    ENCODE_SCALAR_TYPE_WITH_KEY(encodedValue, bool, altKey, input.altKey());
+    ENCODE_SCALAR_TYPE_WITH_KEY(encodedValue, bool, metaKey, input.metaKey());
+    ENCODE_SCALAR_TYPE_WITH_KEY(encodedValue, int, timestamp, input.timestamp());
+
+    return encodedValue;
+}
+
+bool EncodingTraits<PlatformMouseEvent>::decodeValue(EncodedValue& encodedValue, std::unique_ptr<PlatformMouseEvent>& input)
+{
+    DECODE_SCALAR_TYPE_WITH_KEY(encodedValue, int, positionX);
+    DECODE_SCALAR_TYPE_WITH_KEY(encodedValue, int, positionY);
+    DECODE_SCALAR_TYPE_WITH_KEY(encodedValue, int, globalPositionX);
+    DECODE_SCALAR_TYPE_WITH_KEY(encodedValue, int, globalPositionY);
+    DECODE_SCALAR_TYPE_WITH_KEY(encodedValue, MouseButton, button);
+    DECODE_SCALAR_TYPE_WITH_KEY(encodedValue, PlatformEvent::Type, type);
+    DECODE_SCALAR_TYPE_WITH_KEY(encodedValue, int, clickCount);
+    DECODE_SCALAR_TYPE_WITH_KEY(encodedValue, bool, shiftKey);
+    DECODE_SCALAR_TYPE_WITH_KEY(encodedValue, bool, ctrlKey);
+    DECODE_SCALAR_TYPE_WITH_KEY(encodedValue, bool, altKey);
+    DECODE_SCALAR_TYPE_WITH_KEY(encodedValue, bool, metaKey);
+    DECODE_SCALAR_TYPE_WITH_KEY(encodedValue, int, timestamp);
+
+    input = std::make_unique<PlatformMouseEvent>(IntPoint(positionX, positionY),
+        IntPoint(globalPositionX, globalPositionY),
+        button, type, clickCount,
+        shiftKey, ctrlKey, altKey, metaKey, timestamp);
+    return true;
+}
+
 EncodedValue EncodingTraits<SecurityOrigin>::encodeValue(RefPtr<SecurityOrigin> input)
 {
     return EncodedValue::createString(input->toString());
index 8e112b820dfbd36d76dcec15314cb77886cc7439..839c45c0035fbac73d794e2fcc8e4d9cf29a4368 100644 (file)
@@ -37,6 +37,7 @@ namespace WebCore {
 class Document;
 class Frame;
 class Page;
+class PlatformMouseEvent;
 class SecurityOrigin;
 class URL;
 } // namespace WebCore
@@ -51,6 +52,13 @@ template<> struct EncodingTraits<NondeterministicInputBase> {
     static bool decodeValue(EncodedValue&, std::unique_ptr<NondeterministicInputBase>& 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::URL> {
     typedef WebCore::URL DecodedType;
 
index ab02421a140f325af59b3ae5a40537817144107a..30a0e6c3d441d23bc66ef370f3471ab75e330dbf 100644 (file)
 #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)
@@ -45,6 +58,13 @@ UserInputBridge::UserInputBridge(Page& page)
 {
 }
 
+#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)
 {
@@ -52,23 +72,67 @@ bool UserInputBridge::handleContextMenuEvent(const PlatformMouseEvent& mouseEven
 }
 #endif
 
-bool UserInputBridge::handleMousePressEvent(const PlatformMouseEvent& mouseEvent, InputSource)
+bool UserInputBridge::handleMousePressEvent(const PlatformMouseEvent& mouseEvent, InputSource inputSource)
 {
+#if ENABLE(WEB_REPLAY)
+    EARLY_RETURN_IF_SHOULD_IGNORE_INPUT;
+
+    if (activeCursor().isCapturing()) {
+        std::unique_ptr<PlatformMouseEvent> ownedEvent = std::make_unique<PlatformMouseEvent>(mouseEvent);
+        activeCursor().appendInput<HandleMousePress>(std::move(ownedEvent));
+    }
+#else
+    UNUSED_PARAM(inputSource);
+#endif
+
     return m_page.mainFrame().eventHandler().handleMousePressEvent(mouseEvent);
 }
 
-bool UserInputBridge::handleMouseReleaseEvent(const PlatformMouseEvent& mouseEvent, InputSource)
+bool UserInputBridge::handleMouseReleaseEvent(const PlatformMouseEvent& mouseEvent, InputSource inputSource)
 {
+#if ENABLE(WEB_REPLAY)
+    EARLY_RETURN_IF_SHOULD_IGNORE_INPUT;
+
+    if (activeCursor().isCapturing()) {
+        std::unique_ptr<PlatformMouseEvent> ownedEvent = std::make_unique<PlatformMouseEvent>(mouseEvent);
+        activeCursor().appendInput<HandleMouseRelease>(std::move(ownedEvent));
+    }
+#else
+    UNUSED_PARAM(inputSource);
+#endif
+
     return m_page.mainFrame().eventHandler().handleMouseReleaseEvent(mouseEvent);
 }
 
-bool UserInputBridge::handleMouseMoveEvent(const PlatformMouseEvent& mouseEvent, InputSource)
+bool UserInputBridge::handleMouseMoveEvent(const PlatformMouseEvent& mouseEvent, InputSource inputSource)
 {
+#if ENABLE(WEB_REPLAY)
+    EARLY_RETURN_IF_SHOULD_IGNORE_INPUT;
+
+    if (activeCursor().isCapturing()) {
+        std::unique_ptr<PlatformMouseEvent> ownedEvent = std::make_unique<PlatformMouseEvent>(mouseEvent);
+        activeCursor().appendInput<HandleMouseMove>(std::move(ownedEvent), false);
+    }
+#else
+    UNUSED_PARAM(inputSource);
+#endif
+
     return m_page.mainFrame().eventHandler().mouseMoved(mouseEvent);
 }
 
-bool UserInputBridge::handleMouseMoveOnScrollbarEvent(const PlatformMouseEvent& mouseEvent, InputSource)
+bool UserInputBridge::handleMouseMoveOnScrollbarEvent(const PlatformMouseEvent& mouseEvent, InputSource inputSource)
 {
+#if ENABLE(WEB_REPLAY)
+    EARLY_RETURN_IF_SHOULD_IGNORE_INPUT;
+
+    if (activeCursor().isCapturing()) {
+        std::unique_ptr<PlatformMouseEvent> ownedEvent = std::make_unique<PlatformMouseEvent>(mouseEvent);
+        activeCursor().appendInput<HandleMouseMove>(std::move(ownedEvent), true);
+    }
+#else
+    UNUSED_PARAM(inputSource);
+#endif
+
     return m_page.mainFrame().eventHandler().passMouseMovedEventToScrollbars(mouseEvent);
 }
 
index b1160b227067c94556854354f3ff92f4082c1dd4..d9c7650482036eb9e901c704f079c3f2ceb537ce 100644 (file)
 #include "ScrollTypes.h"
 #include <wtf/Noncopyable.h>
 
+namespace JSC {
+class InputCursor;
+}
+
 namespace WebCore {
 
 struct FrameLoadRequest;
@@ -53,6 +57,19 @@ class 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)
     bool handleContextMenuEvent(const PlatformMouseEvent&, const Frame*, InputSource source = InputSource::User);
@@ -77,6 +94,9 @@ public:
 
 private:
     Page& m_page;
+#if ENABLE(WEB_REPLAY)
+    State m_state;
+#endif
 };
 
 } // namespace WebCore
index 28f0d7b8c8bed230f5b95f6a9130d7fc6ca77e49..6c32283eff07e0b05966944ca86f6a8fca26c838 100644 (file)
         ],
 
         "WebCore": [
-            {
-                "name": "URL", "mode": "HEAVY_SCALAR",
-                "header": "platform/URL.h"
-            },
             {
                 "name": "EncodedCType", "mode": "SCALAR", "storage": "uint8_t",
                 "flags": ["ENUM_CLASS"],
                 "header": "replay/MemoizedDOMResult.h"
             },
             {
-                "name": "SecurityOrigin", "mode": "SHARED",
-                "header": "page/SecurityOrigin.h"
+                "name": "Modifiers", "mode": "SCALAR", "storage": "uint8_t",
+                "enclosing_class": "PlatformEvent",
+                "flags": ["ENUM"],
+                "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": "PlatformMouseEvent", "mode": "OWNED",
+                "header": "platform/PlatformMouseEvent.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"
             }
         ]
     },
             "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": "InitialNavigation",
             "description": "Initiate the initial main frame navigation.",