Bug 1421323 - Ctrl+click should synthesize a contextmenu event; r?ato
Except on Windows.
MozReview-Commit-ID: LZ3CMyrVsDu
--- a/testing/marionette/action.js
+++ b/testing/marionette/action.js
@@ -1,16 +1,18 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/* eslint no-dupe-keys:off */
"use strict";
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
ChromeUtils.import("chrome://marionette/content/assert.js");
const {element} = ChromeUtils.import("chrome://marionette/content/element.js", {});
const {
InvalidArgumentError,
MoveTargetOutOfBoundsError,
UnsupportedOperationError,
} = ChromeUtils.import("chrome://marionette/content/error.js", {});
ChromeUtils.import("chrome://marionette/content/event.js");
@@ -1211,26 +1213,32 @@ function dispatchPointerDown(a, inputSta
// Append a copy of |a| with pointerUp subtype
let copy = Object.assign({}, a, {subtype: action.PointerUp});
action.inputsToCancel.push(copy);
switch (inputState.subtype) {
case action.PointerType.Mouse:
let mouseEvent = new action.Mouse("mousedown", a.button);
mouseEvent.update(inputState);
- if (event.DoubleClickTracker.isClicked()) {
+ if (mouseEvent.ctrlKey) {
+ if (Services.appinfo.OS !== "WINNT") {
+ mouseEvent.button = 2;
+ event.DoubleClickTracker.resetClick();
+ }
+ } else if (event.DoubleClickTracker.isClicked()) {
mouseEvent = Object.assign({},
mouseEvent, {clickCount: 2});
}
event.synthesizeMouseAtPoint(
inputState.x,
inputState.y,
mouseEvent,
window);
- if (event.MouseButton.isSecondary(a.button)) {
+ if (event.MouseButton.isSecondary(a.button) ||
+ mouseEvent.ctrlKey && Services.appinfo.OS !== "WINNT") {
let contextMenuEvent = Object.assign({},
mouseEvent, {type: "contextmenu"});
event.synthesizeMouseAtPoint(
inputState.x,
inputState.y,
contextMenuEvent,
window);
}
--- a/testing/marionette/event.js
+++ b/testing/marionette/event.js
@@ -3,16 +3,18 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** Provides functionality for creating and sending DOM events. */
this.event = {};
"use strict";
/* global content, is */
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
ChromeUtils.import("chrome://marionette/content/element.js");
const dblclickTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
// Max interval between two clicks that should result in a dblclick (in ms)
const DBLCLICK_INTERVAL = 640;
this.EXPORTED_SYMBOLS = ["event"];
@@ -207,17 +209,17 @@ event.parseModifiers_ = function(modifie
}
if (modifiers.altKey) {
mval |= Ci.nsIDOMWindowUtils.MODIFIER_ALT;
}
if (modifiers.metaKey) {
mval |= Ci.nsIDOMWindowUtils.MODIFIER_META;
}
if (modifiers.accelKey) {
- if (navigator.platform.includes("Mac")) {
+ if (Services.appinfo.OS === "Darwin") {
mval |= Ci.nsIDOMWindowUtils.MODIFIER_META;
} else {
mval |= Ci.nsIDOMWindowUtils.MODIFIER_CONTROL;
}
}
return mval;
};
@@ -645,17 +647,17 @@ function emulateToActivateModifiers_(TIP
{key: "Alt", attr: "altKey"},
{key: "AltGraph", attr: "altGraphKey"},
{key: "Control", attr: "ctrlKey"},
{key: "Fn", attr: "fnKey"},
{key: "Meta", attr: "metaKey"},
{key: "OS", attr: "osKey"},
{key: "Shift", attr: "shiftKey"},
{key: "Symbol", attr: "symbolKey"},
- {key: isMac_(win) ? "Meta" : "Control", attr: "accelKey"},
+ {key: Services.appinfo.OS === "Darwin" ? "Meta" : "Control", attr: "accelKey"},
],
lockable: [
{key: "CapsLock", attr: "capsLockKey"},
{key: "FnLock", attr: "fnLockKey"},
{key: "NumLock", attr: "numLockKey"},
{key: "ScrollLock", attr: "scrollLockKey"},
{key: "SymbolLock", attr: "symbolLockKey"},
],
@@ -715,25 +717,16 @@ function emulateToInactivateModifiers_(T
let event = new KeyboardEvent("", {key: modifiers.lockable[j].key});
TIP.keydown(event,
TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
TIP.keyup(event,
TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
}
}
-function isMac_(win = window) {
- if (win) {
- try {
- return win.navigator.platform.indexOf("Mac") > -1;
- } catch (ex) {}
- }
- return navigator.platform.indexOf("Mac") > -1;
-}
-
/* eslint-disable */
function guessKeyNameFromKeyCode_(aKeyCode, win = window) {
let KeyboardEvent = getKeyboardEvent_(win);
switch (aKeyCode) {
case KeyboardEvent.DOM_VK_CANCEL:
return "Cancel";
case KeyboardEvent.DOM_VK_HELP:
return "Help";
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -404119,16 +404119,22 @@
]
],
"webdriver/tests/accept_alert/accept.py": [
[
"/webdriver/tests/accept_alert/accept.py",
{}
]
],
+ "webdriver/tests/actions/control_click.py": [
+ [
+ "/webdriver/tests/actions/control_click.py",
+ {}
+ ]
+ ],
"webdriver/tests/actions/key.py": [
[
"/webdriver/tests/actions/key.py",
{}
]
],
"webdriver/tests/actions/key_shortcuts.py": [
[
@@ -548367,17 +548373,17 @@
"7cad13aa87e8a0d95dc9e35eb7d1dec7930939fb",
"reftest"
],
"css/mediaqueries/mq-calc-005.html": [
"75334bbcf4ef412b9976c59e1efe2177ad62b465",
"reftest"
],
"css/mediaqueries/mq-case-insensitive-001.html": [
- "5d94915b19757b3ee5ac49fb2fd119c82317ddf3",
+ "33087ab04eea830e3fcf10820fbfd167709bd18a",
"reftest"
],
"css/mediaqueries/mq-invalid-media-type-001.html": [
"4b11afa3270c95b0a2736f114627b6f02346805a",
"reftest"
],
"css/mediaqueries/mq-invalid-media-type-002.html": [
"42760d383b11e870f663e11624c5de8d7dfaa1ec",
@@ -618450,26 +618456,30 @@
"webdriver/tests/actions/__init__.py": [
"da39a3ee5e6b4b0d3255bfef95601890afd80709",
"support"
],
"webdriver/tests/actions/conftest.py": [
"f366a25d9d5e35a2897413a265398bc7a54f7c44",
"support"
],
+ "webdriver/tests/actions/control_click.py": [
+ "341a8fbfe64f5231a91999768de0b44bba121122",
+ "wdspec"
+ ],
"webdriver/tests/actions/key.py": [
"0b70d98a4558427666abbe9629a6cf42d69e2597",
"wdspec"
],
"webdriver/tests/actions/key_shortcuts.py": [
"dbe27dd0b1625169fc8cc2055f8fb49d5a4a78d2",
"wdspec"
],
"webdriver/tests/actions/modifier_click.py": [
- "f0ed71cbb6550ef496096541531f3b4fa3f4c6e2",
+ "5cb44f87588a75a5375b552bce713e7144fd02df",
"wdspec"
],
"webdriver/tests/actions/mouse.py": [
"fb5072172d8524788147bde310d612aae5bee1b1",
"wdspec"
],
"webdriver/tests/actions/mouse_dblclick.py": [
"dd73b09a15b438aaf77cbccdf40678a24bbabd39",
@@ -618655,17 +618665,17 @@
"21bae43b3a6e966b8698b7c439b29a68029adc58",
"wdspec"
],
"webdriver/tests/execute_script/json_serialize_windowproxy.py": [
"20db10d82ed2b28a22674fcdc37cac0323d33c95",
"wdspec"
],
"webdriver/tests/execute_script/user_prompts.py": [
- "d87cc66a9d1e0003a3973b86e541b62999e9975f",
+ "4bd4e9089185505d8add4d5ebe9806498da42999",
"wdspec"
],
"webdriver/tests/find_element/__init__.py": [
"da39a3ee5e6b4b0d3255bfef95601890afd80709",
"support"
],
"webdriver/tests/find_element/find.py": [
"9af0db4de0d09cbf68fa43bb40145cffc7b95635",
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/webdriver/tests/actions/control_click.py
@@ -0,0 +1,77 @@
+import pytest
+
+from tests.actions.support.refine import filter_dict, get_events
+from tests.actions.support.keys import Keys
+
+
+@pytest.mark.parametrize("modifier, prop", [
+ (Keys.CONTROL, "ctrlKey"),
+ (Keys.R_CONTROL, "ctrlKey"),
+])
+def test_control_click(session, test_actions_page, key_chain, mouse_chain, modifier, prop):
+ os = session.capabilities["platformName"]
+ key_chain \
+ .pause(0) \
+ .key_down(modifier) \
+ .pause(200) \
+ .key_up(modifier)
+ outer = session.find.css("#outer", all=False)
+ mouse_chain.click(element=outer)
+ session.actions.perform([key_chain.dict, mouse_chain.dict])
+ if os == "windows_nt":
+ expected = [
+ {"type": "mousemove"},
+ {"type": "mousedown"},
+ {"type": "mouseup"},
+ {"type": "click"},
+ ]
+ else:
+ expected = [
+ {"type": "mousemove"},
+ {"type": "mousedown"},
+ {"type": "contextmenu"},
+ {"type": "mouseup"},
+ ]
+ defaults = {
+ "altKey": False,
+ "metaKey": False,
+ "shiftKey": False,
+ "ctrlKey": False
+ }
+ for e in expected:
+ e.update(defaults)
+ if e["type"] != "mousemove":
+ e[prop] = True
+ filtered_events = [filter_dict(e, expected[0]) for e in get_events(session)]
+ assert expected == filtered_events
+
+
+def test_release_control_click(session, key_reporter, key_chain, mouse_chain):
+ # The context menu stays visible during subsequent tests so let's not
+ # display it in the first place.
+ session.execute_script("""
+ var keyReporter = document.getElementById("keys");
+ document.addEventListener("contextmenu", function(e) {
+ e.preventDefault();
+ });
+ """)
+ key_chain \
+ .pause(0) \
+ .key_down(Keys.CONTROL)
+ mouse_chain \
+ .pointer_move(0, 0, origin=key_reporter) \
+ .pointer_down()
+ session.actions.perform([key_chain.dict, mouse_chain.dict])
+ session.execute_script("""
+ var keyReporter = document.getElementById("keys");
+ keyReporter.addEventListener("mousedown", recordPointerEvent);
+ keyReporter.addEventListener("mouseup", recordPointerEvent);
+ resetEvents();
+ """)
+ session.actions.release()
+ expected = [
+ {"type": "mouseup"},
+ {"type": "keyup"},
+ ]
+ events = [filter_dict(e, expected[0]) for e in get_events(session)]
+ assert events == expected
--- a/testing/web-platform/tests/webdriver/tests/actions/modifier_click.py
+++ b/testing/web-platform/tests/webdriver/tests/actions/modifier_click.py
@@ -38,46 +38,47 @@ def test_modifier_click(session, test_ac
if e["type"] != "mousemove":
e[prop] = True
filtered_events = [filter_dict(e, expected[0]) for e in get_events(session)]
assert expected == filtered_events
def test_many_modifiers_click(session, test_actions_page, key_chain, mouse_chain):
outer = session.find.css("#outer", all=False)
+ dblclick_timeout = 800
key_chain \
.pause(0) \
- .key_down(Keys.CONTROL) \
+ .key_down(Keys.ALT) \
.key_down(Keys.SHIFT) \
- .pause(0) \
- .key_up(Keys.CONTROL) \
+ .pause(dblclick_timeout) \
+ .key_up(Keys.ALT) \
.key_up(Keys.SHIFT)
mouse_chain \
.pointer_move(0, 0, origin=outer) \
.pause(0) \
.pointer_down() \
.pointer_up() \
.pause(0) \
.pause(0) \
.pointer_down()
session.actions.perform([key_chain.dict, mouse_chain.dict])
expected = [
{"type": "mousemove"},
- # shift and ctrl presses
+ # shift and alt pressed
{"type": "mousedown"},
{"type": "mouseup"},
{"type": "click"},
# no modifiers pressed
{"type": "mousedown"},
]
defaults = {
"altKey": False,
"metaKey": False,
"shiftKey": False,
"ctrlKey": False
}
for e in expected:
e.update(defaults)
for e in expected[1:4]:
e["shiftKey"] = True
- e["ctrlKey"] = True
+ e["altKey"] = True
events = [filter_dict(e, expected[0]) for e in get_events(session)]
assert events == expected