Commit 687313c5 authored by Lan Wei's avatar Lan Wei Committed by Commit Bot

Add action_sequence in testdriver

After we expose test_driver.Actions API to web users, we add their
implementation in our testdriver file, and fix the wpt tests of
Actions API.

Bug: 893480
Change-Id: Ib02c0223208eeb2cc30c2ca35b98d5fc938baa2c
Reviewed-on: https://chromium-review.googlesource.com/c/1289119
Commit-Queue: Lan Wei <lanwei@chromium.org>
Reviewed-by: default avatarNavid Zolghadr <nzolghadr@chromium.org>
Reviewed-by: default avatarDave Tapuska <dtapuska@chromium.org>
Cr-Commit-Position: refs/heads/master@{#606882}
parent acb2df40
...@@ -29,12 +29,12 @@ SyntheticPointerActionParams::PointerActionType ToSyntheticPointerActionType( ...@@ -29,12 +29,12 @@ SyntheticPointerActionParams::PointerActionType ToSyntheticPointerActionType(
} }
SyntheticGestureParams::GestureSourceType ToSyntheticGestureSourceType( SyntheticGestureParams::GestureSourceType ToSyntheticGestureSourceType(
std::string source_type) { std::string pointer_type) {
if (source_type == "touch") if (pointer_type == "touch")
return SyntheticGestureParams::TOUCH_INPUT; return SyntheticGestureParams::TOUCH_INPUT;
else if (source_type == "mouse") else if (pointer_type == "mouse")
return SyntheticGestureParams::MOUSE_INPUT; return SyntheticGestureParams::MOUSE_INPUT;
else if (source_type == "pen") else if (pointer_type == "pen")
return SyntheticGestureParams::PEN_INPUT; return SyntheticGestureParams::PEN_INPUT;
else else
return SyntheticGestureParams::DEFAULT_INPUT; return SyntheticGestureParams::DEFAULT_INPUT;
...@@ -85,7 +85,7 @@ bool ActionsParser::ParsePointerActionSequence() { ...@@ -85,7 +85,7 @@ bool ActionsParser::ParsePointerActionSequence() {
} }
gesture_params_.gesture_source_type = gesture_params_.gesture_source_type =
ToSyntheticGestureSourceType(source_type_); ToSyntheticGestureSourceType(pointer_type_);
// Group a list of actions from all pointers into a // Group a list of actions from all pointers into a
// SyntheticPointerActionListParams object, which is a list of actions, which // SyntheticPointerActionListParams object, which is a list of actions, which
// will be dispatched together. // will be dispatched together.
...@@ -103,34 +103,120 @@ bool ActionsParser::ParsePointerActionSequence() { ...@@ -103,34 +103,120 @@ bool ActionsParser::ParsePointerActionSequence() {
} }
bool ActionsParser::ParsePointerActions(const base::DictionaryValue& pointer) { bool ActionsParser::ParsePointerActions(const base::DictionaryValue& pointer) {
int pointer_id = -1;
// If the json format of each pointer has "type" element, it is from the new
// Action API, otherwise it is from gpuBenchmarking.pointerActionSequence
// API. We have to keep both formats for now, but later on once we switch to
// the new Action API in all tests, we will remove the old format.
if (pointer.HasKey("type")) {
std::string source_type; std::string source_type;
if (!pointer.GetString("source", &source_type)) { if (!pointer.GetString("type", &source_type)) {
error_message_ = std::string("source type is missing or not a string"); error_message_ =
std::string("action sequence type is missing or not a string");
return false;
} else if (source_type == "") {
error_message_ = std::string("action sequence type cannot be empty");
return false; return false;
} else if (source_type != "touch" && source_type != "mouse" && } else if (source_type != "pointer") {
source_type != "pen") { error_message_ =
error_message_ = std::string("source type is an unsupported input source"); std::string("we only support action sequence type of pointer");
return false; return false;
} }
if (source_type_.empty()) { if (source_type_.empty())
source_type_ = source_type; source_type_ = source_type;
}
if (source_type_ != source_type) { if (source_type_ != source_type) {
error_message_ = std::string(
"currently multiple action sequence type are not supported");
return false;
}
if (source_type_ == "pointer" && !pointer.HasKey("parameters")) {
error_message_ =
std::string("action sequence parameters is missing for pointer type");
return false;
}
const base::DictionaryValue* parameters;
std::string pointer_type;
if (!pointer.GetDictionary("parameters", &parameters)) {
error_message_ =
std::string("action sequence parameters is not a dictionary");
return false;
}
if (!parameters->GetString("pointerType", &pointer_type)) {
error_message_ = std::string(
"action sequence pointer type is missing or not a string");
return false;
} else if (pointer_type != "touch" && pointer_type != "mouse" &&
pointer_type != "pen") {
error_message_ = std::string(
"action sequence pointer type is an unsupported input type");
return false;
}
if (pointer_type_.empty()) {
pointer_type_ = pointer_type;
}
if (pointer_type_ != pointer_type) {
error_message_ = std::string(
"currently multiple action sequence pointer type are not supported");
return false;
}
if (pointer_type != "touch" && action_index_ > 0) {
error_message_ = std::string(
"for input type of mouse and pen, we only support one device");
return false;
}
std::string pointer_name;
if (!pointer.GetString("id", &pointer_name)) {
error_message_ = std::string("pointer name is missing or not a string");
return false;
}
if (pointer_name_set_.find(pointer_name) != pointer_name_set_.end()) {
error_message_ = std::string("pointer name already exists");
return false;
}
pointer_name_set_.insert(pointer_name);
pointer_id_set_.insert(action_index_);
pointer_id = action_index_;
} else {
std::string pointer_type;
if (!pointer.GetString("source", &pointer_type)) {
error_message_ = std::string("source type is missing or not a string");
return false;
} else if (pointer_type != "touch" && pointer_type != "mouse" &&
pointer_type != "pen") {
error_message_ =
std::string("source type is an unsupported input source");
return false;
}
if (pointer_type_.empty()) {
pointer_type_ = pointer_type;
}
if (pointer_type_ != pointer_type) {
error_message_ = error_message_ =
std::string("currently multiple input sources are not not supported"); std::string("currently multiple input sources are not not supported");
return false; return false;
} }
if (source_type != "touch" && action_index_ > 0) { if (pointer_type != "touch" && action_index_ > 0) {
error_message_ = std::string( error_message_ = std::string(
"for input source type of mouse and pen, we only support one device in " "for input source type of mouse and pen, we only support one device "
"in "
"one sequence"); "one sequence");
return false; return false;
} }
int pointer_id = -1;
if (pointer.HasKey("id")) { if (pointer.HasKey("id")) {
if (!pointer.GetInteger("id", &pointer_id)) { if (!pointer.GetInteger("id", &pointer_id)) {
error_message_ = std::string("pointer id is not an integer"); error_message_ = std::string("pointer id is not an integer");
...@@ -161,6 +247,7 @@ bool ActionsParser::ParsePointerActions(const base::DictionaryValue& pointer) { ...@@ -161,6 +247,7 @@ bool ActionsParser::ParsePointerActions(const base::DictionaryValue& pointer) {
return false; return false;
} }
} }
}
const base::ListValue* actions; const base::ListValue* actions;
if (!pointer.GetList("actions", &actions)) { if (!pointer.GetList("actions", &actions)) {
...@@ -202,13 +289,21 @@ bool ActionsParser::ParseAction( ...@@ -202,13 +289,21 @@ bool ActionsParser::ParseAction(
int pointer_id) { int pointer_id) {
SyntheticPointerActionParams::PointerActionType pointer_action_type = SyntheticPointerActionParams::PointerActionType pointer_action_type =
SyntheticPointerActionParams::PointerActionType::NOT_INITIALIZED; SyntheticPointerActionParams::PointerActionType::NOT_INITIALIZED;
std::string name; std::string type;
if (!action.GetString("name", &name)) { if (action.HasKey("type")) {
if (!action.GetString("type", &type)) {
error_message_ = base::StringPrintf( error_message_ = base::StringPrintf(
"actions[%d].actions.name is missing or not a string", action_index_); "actions[%d].actions.name is missing or not a string", action_index_);
return false; return false;
} }
pointer_action_type = ToSyntheticPointerActionType(name); } else {
if (!action.GetString("name", &type)) {
error_message_ = base::StringPrintf(
"actions[%d].actions.name is missing or not a string", action_index_);
return false;
}
}
pointer_action_type = ToSyntheticPointerActionType(type);
if (pointer_action_type == if (pointer_action_type ==
SyntheticPointerActionParams::PointerActionType::NOT_INITIALIZED) { SyntheticPointerActionParams::PointerActionType::NOT_INITIALIZED) {
error_message_ = base::StringPrintf( error_message_ = base::StringPrintf(
......
...@@ -43,11 +43,13 @@ class CONTENT_EXPORT ActionsParser { ...@@ -43,11 +43,13 @@ class CONTENT_EXPORT ActionsParser {
pointer_actions_list_; pointer_actions_list_;
size_t longest_action_sequence_; size_t longest_action_sequence_;
std::string source_type_; std::string source_type_;
std::string pointer_type_;
std::string error_message_; std::string error_message_;
base::Value* pointer_actions_value_; base::Value* pointer_actions_value_;
int action_index_; int action_index_;
std::set<int> pointer_id_set_; std::set<int> pointer_id_set_;
std::set<std::string> pointer_name_set_;
DISALLOW_COPY_AND_ASSIGN(ActionsParser); DISALLOW_COPY_AND_ASSIGN(ActionsParser);
}; };
......
...@@ -2885,10 +2885,8 @@ crbug.com/893490 [ Mac Win ] external/wpt/css/css-text/white-space/control-chars ...@@ -2885,10 +2885,8 @@ crbug.com/893490 [ Mac Win ] external/wpt/css/css-text/white-space/control-chars
crbug.com/893490 [ Mac ] external/wpt/css/css-text/white-space/control-chars-09F.html [ Failure ] crbug.com/893490 [ Mac ] external/wpt/css/css-text/white-space/control-chars-09F.html [ Failure ]
# needs implementation of test_driver_internal.action_sequence # needs implementation of test_driver_internal.action_sequence
crbug.com/893480 external/wpt/infrastructure/testdriver/actions/elementPosition.html [ Timeout ]
crbug.com/893480 external/wpt/infrastructure/testdriver/actions/elementTiming.html [ Timeout ] crbug.com/893480 external/wpt/infrastructure/testdriver/actions/elementTiming.html [ Timeout ]
crbug.com/893480 external/wpt/infrastructure/testdriver/actions/eventOrder.html [ Timeout ] crbug.com/893480 external/wpt/infrastructure/testdriver/actions/multiDevice.html [ Failure Timeout ]
crbug.com/893480 external/wpt/infrastructure/testdriver/actions/multiDevice.html [ Failure ]
# ====== New tests from wpt-importer added here ====== # ====== New tests from wpt-importer added here ======
crbug.com/626703 external/wpt/css/css-multicol/multicol-span-all-restyle-003.html [ Failure ] crbug.com/626703 external/wpt/css/css-multicol/multicol-span-all-restyle-003.html [ Failure ]
......
...@@ -34,7 +34,7 @@ async_test(t => { ...@@ -34,7 +34,7 @@ async_test(t => {
let div = document.getElementById("test"); let div = document.getElementById("test");
let actions = new test_driver.Actions() let actions = new test_driver.Actions()
.pointerMove(0, 0, {origin: test}) .pointerMove(0, 0, {origin: test})
.pointerDown() .pointerDown(0, 0, {origin: test})
.pointerUp() .pointerUp()
.send() .send()
.then(t.step_func_done(() => assert_array_equals(events, [50, 25]))) .then(t.step_func_done(() => assert_array_equals(events, [50, 25])))
......
...@@ -17,19 +17,17 @@ let events = []; ...@@ -17,19 +17,17 @@ let events = [];
async_test(t => { async_test(t => {
Array.prototype.forEach.call(document.getElementsByTagName("button"), Array.prototype.forEach.call(document.getElementsByTagName("button"),
(x) => x.addEventListener("mousedown", () => {events.push(x.id)})); (x) => x.addEventListener("pointerdown", () => {events.push(x.id)}));
let button_a = document.getElementById("a"); let button_a = document.getElementById("a");
let button_b = document.getElementById("b"); let button_b = document.getElementById("b");
let actions = new test_driver.Actions() let actions = new test_driver.Actions()
.addPointer("pointer1") .addPointer("pointer1", "touch")
.addPointer("pointer2") .addPointer("pointer2", "touch")
.pointerMove(0, 0, {origin: button_a, sourceName: "pointer1"}) .pointerDown(0, 0, {origin: button_a, sourceName: "pointer1"})
.pointerMove(0, 0, {origin: button_b, sourceName: "pointer2"}) .pointerDown(0, 0, {origin: button_b, sourceName: "pointer2"})
.pointerDown({sourceName: "pointer2"})
.pointerDown({sourceName: "pointer1"})
.pointerUp({sourceName: "pointer2"})
.pointerUp({sourceName: "pointer1"}) .pointerUp({sourceName: "pointer1"})
.pointerUp({sourceName: "pointer2"})
.send() .send()
.then(t.step_func_done(() => assert_array_equals(events, ["a", "b"]))) .then(t.step_func_done(() => assert_array_equals(events, ["a", "b"])))
.catch(e => t.step_func(() => assert_unreached("Actions sequence failed " + e))); .catch(e => t.step_func(() => assert_unreached("Actions sequence failed " + e)));
......
...@@ -7,6 +7,9 @@ ...@@ -7,6 +7,9 @@
<link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
<script src="/resources/testharness.js"></script> <script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script> <script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-actions.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<!-- Additional helper script for common checks across event types --> <!-- Additional helper script for common checks across event types -->
<script type="text/javascript" src="pointerevent_support.js"></script> <script type="text/javascript" src="pointerevent_support.js"></script>
</head> </head>
...@@ -32,6 +35,9 @@ ...@@ -32,6 +35,9 @@
eventTested = true; eventTested = true;
} }
}); });
// Inject the inputs to run this test.
new test_driver.Actions().pointerMove(0, 0, {origin: target0}).send();
} }
</script> </script>
<h1>Pointer Events pointermove Tests</h1> <h1>Pointer Events pointermove Tests</h1>
......
...@@ -22,6 +22,14 @@ ...@@ -22,6 +22,14 @@
} }
Actions.prototype = { Actions.prototype = {
ButtonType: {
LEFT: 0,
MIDDLE: 1,
RIGHT: 2,
BACK: 3,
FORWARD: 4,
},
/** /**
* Generate the action sequence suitable for passing to * Generate the action sequence suitable for passing to
* test_driver.action_sequence * test_driver.action_sequence
...@@ -98,7 +106,7 @@ ...@@ -98,7 +106,7 @@
* @returns {Actions} * @returns {Actions}
*/ */
addKeyboard: function(name, set=true) { addKeyboard: function(name, set=true) {
this.createSource("key", name, true); this.createSource("key", name);
if (set) { if (set) {
this.setKeyboard(name); this.setKeyboard(name);
} }
...@@ -125,7 +133,7 @@ ...@@ -125,7 +133,7 @@
* @returns {Actions} * @returns {Actions}
*/ */
addPointer: function(name, pointerType="mouse", set=true) { addPointer: function(name, pointerType="mouse", set=true) {
this.createSource("pointer", name, true, {pointerType: pointerType}); this.createSource("pointer", name, {pointerType: pointerType});
if (set) { if (set) {
this.setPointer(name); this.setPointer(name);
} }
...@@ -225,9 +233,9 @@ ...@@ -225,9 +233,9 @@
* pointer source * pointer source
* @returns {Actions} * @returns {Actions}
*/ */
pointerDown: function({button=0, sourceName=null}={}) { pointerDown: function(x, y, {origin="viewport", button=this.ButtonType.LEFT, sourceName=null}={}) {
let source = this.getSource("pointer", sourceName); let source = this.getSource("pointer", sourceName);
source.pointerDown(this, button); source.pointerDown(this, button, x, y, origin);
return this; return this;
}, },
...@@ -239,7 +247,7 @@ ...@@ -239,7 +247,7 @@
* source * source
* @returns {Actions} * @returns {Actions}
*/ */
pointerUp: function({button=0, sourceName=null}={}) { pointerUp: function({button=this.ButtonType.LEFT, sourceName=null}={}) {
let source = this.getSource("pointer", sourceName); let source = this.getSource("pointer", sourceName);
source.pointerUp(this, button); source.pointerUp(this, button);
return this; return this;
...@@ -359,12 +367,12 @@ ...@@ -359,12 +367,12 @@
return data; return data;
}, },
pointerDown: function(actions, button) { pointerDown: function(actions, button, x, y, origin) {
let tick = actions.tickIdx; let tick = actions.tickIdx;
if (this.actions.has(tick)) { if (this.actions.has(tick)) {
tick = actions.addTick().tickIdx; tick = actions.addTick().tickIdx;
} }
this.actions.set(tick, {type: "pointerDown", button}); this.actions.set(tick, {type: "pointerDown", button, x, y, origin});
}, },
pointerUp: function(actions, button) { pointerUp: function(actions, button) {
......
...@@ -192,7 +192,7 @@ ...@@ -192,7 +192,7 @@
* @returns {Promise} fufiled after the actions are performed, or rejected in * @returns {Promise} fufiled after the actions are performed, or rejected in
* the cases the WebDriver command errors * the cases the WebDriver command errors
*/ */
action_sequence(actions) { action_sequence: function(actions) {
return window.test_driver_internal.action_sequence(actions); return window.test_driver_internal.action_sequence(actions);
} }
}; };
...@@ -233,7 +233,7 @@ ...@@ -233,7 +233,7 @@
/** /**
* Send a sequence of pointer actions * Send a sequence of pointer actions
* *
* @returns {Promise} fufilled after actions are sent, rejected if any actions * @returns {Promise} fulfilled after actions are sent, rejected if any actions
* fail * fail
*/ */
action_sequence: function(actions) { action_sequence: function(actions) {
......
importAutomationScript('/pointerevents/pointerevent_common_input.js');
function inject_input() {
return mouseMoveIntoTarget('#target0');
}
...@@ -5,6 +5,46 @@ ...@@ -5,6 +5,46 @@
// that will be added in the future. // that will be added in the future.
const leftButton = 0; const leftButton = 0;
function getInViewCenterPoint(rect) {
var left = Math.max(0, rect.left);
var right = Math.min(window.innerWidth, rect.right);
var top = Math.max(0, rect.top);
var bottom = Math.min(window.innerHeight, rect.bottom);
var x = 0.5 * (left + right);
var y = 0.5 * (top + bottom);
return [x, y];
}
function getPointerInteractablePaintTree(element) {
if (!window.document.contains(element)) {
return [];
}
var rectangles = element.getClientRects();
if (rectangles.length === 0) {
return [];
}
var centerPoint = getInViewCenterPoint(rectangles[0]);
if ("elementsFromPoint" in document) {
return document.elementsFromPoint(centerPoint[0], centerPoint[1]);
} else if ("msElementsFromPoint" in document) {
var rv = document.msElementsFromPoint(centerPoint[0], centerPoint[1]);
return Array.prototype.slice.call(rv ? rv : []);
} else {
throw new Error("document.elementsFromPoint unsupported");
}
}
function inView(element) {
var pointerInteractablePaintTree = getPointerInteractablePaintTree(element);
return pointerInteractablePaintTree.indexOf(element) !== -1;
}
window.test_driver_internal.click = function(element, coords) { window.test_driver_internal.click = function(element, coords) {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
if (window.chrome && chrome.gpuBenchmarking) { if (window.chrome && chrome.gpuBenchmarking) {
...@@ -35,4 +75,59 @@ ...@@ -35,4 +75,59 @@
}); });
}; };
window.test_driver_internal.action_sequence = function(actions) {
if (window.top !== window) {
return Promise.reject(new Error("can only send keys in top-level window"));
}
var didScrollIntoView = false;
for (let i = 0; i < actions.length; i++) {
for (let j = 0; j < actions[i].actions.length; j++) {
if ('origin' in actions[i].actions[j]) {
if (actions[i].actions[j].origin == "viewport")
continue;
if (actions[i].actions[j].origin == "pointer")
return Promise.reject(new Error("pointer origin is not supported right now"));
let element = actions[i].actions[j].origin;
if (!window.document.contains(element)) {
return Promise.reject(new Error("element in different document or shadow tree"));
}
if (!inView(element)) {
if (didScrollIntoView)
return Promise.reject(new Error("already scrolled into view, the element is not found"));
element.scrollIntoView({behavior: "instant",
block: "end",
inline: "nearest"});
didScrollIntoView = true;
}
var pointerInteractablePaintTree = getPointerInteractablePaintTree(element);
if (pointerInteractablePaintTree.length === 0 ||
!element.contains(pointerInteractablePaintTree[0])) {
return Promise.reject(new Error("element click intercepted error"));
}
var rect = element.getClientRects()[0];
var centerPoint = getInViewCenterPoint(rect);
var x = centerPoint[0];
var y = centerPoint[1];
actions[i].actions[j].x += x;
actions[i].actions[j].y += y;
}
}
}
return new Promise(function(resolve, reject) {
if (window.chrome && chrome.gpuBenchmarking) {
chrome.gpuBenchmarking.pointerActionSequence(actions, resolve);
} else {
reject(new Error("GPU benchmarking is not enabled."));
}
});
};
})(); })();
...@@ -171,6 +171,29 @@ ...@@ -171,6 +171,29 @@
*/ */
freeze: function() { freeze: function() {
return window.test_driver_internal.freeze(); return window.test_driver_internal.freeze();
},
/**
* Send a sequence of actions
*
* This function sends a sequence of actions to the top level window
* to perform. It is modeled after the behaviour of {@link
* https://w3c.github.io/webdriver/#actions|WebDriver Actions Command}
*
* @param {Array} actions - an array of actions. The format is the same as the actions
property of the WebDriver command {@link
https://w3c.github.io/webdriver/#perform-actions|Perform
Actions} command. Each element is an object representing an
input source and each input source itself has an actions
property detailing the behaviour of that source at each timestep
(or tick). Authors are not expected to construct the actions
sequence by hand, but to use the builder api provided in
testdriver-actions.js
* @returns {Promise} fufiled after the actions are performed, or rejected in
* the cases the WebDriver command errors
*/
action_sequence: function(actions) {
return window.test_driver_internal.action_sequence(actions);
} }
}; };
...@@ -205,6 +228,16 @@ ...@@ -205,6 +228,16 @@
*/ */
freeze: function() { freeze: function() {
return Promise.reject(new Error("unimplemented")); return Promise.reject(new Error("unimplemented"));
},
/**
* Send a sequence of pointer actions
*
* @returns {Promise} fulfilled after actions are sent, rejected if any actions
* fail
*/
action_sequence: function(actions) {
return Promise.reject(new Error("unimplemented"));
} }
}; };
})(); })();
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment