Skip to content

Commit 0eec81d

Browse files
committed
Firefox: implementing mouse up/down actions via nsIDOMWindowUtils. This makes mouse actions "more native". In particular, if there are overlapping element at the click point, the driver will click the topmost element.
1 parent 4f446af commit 0eec81d

File tree

7 files changed

+187
-16
lines changed

7 files changed

+187
-16
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2+
<html xmlns="http://www.w3.org/1999/xhtml">
3+
<head>
4+
<title>An element that disappears on click</title>
5+
<style>
6+
#under {
7+
position: absolute;
8+
top: 20;
9+
left: 20;
10+
width: 100px;
11+
height: 100px;
12+
background-color: white;
13+
}
14+
15+
#over {
16+
position: absolute;
17+
top: 20;
18+
left: 20;
19+
width: 100px;
20+
height: 100px;
21+
background-color: red;
22+
opacity: 0.5;
23+
}
24+
25+
#log {
26+
position: absolute;
27+
top: 120px;
28+
}
29+
</style>
30+
</head>
31+
<body id="body">
32+
<div id="under"><p id="contents">Hello</p></div>
33+
<div id="over"></div>
34+
<div id="log">
35+
<p>Log:<p>
36+
</div>
37+
<script>
38+
var byId = document.getElementById.bind(document);
39+
40+
var log = byId("log");
41+
42+
function handler(ev) {
43+
log.innerHTML += "<p></p>";
44+
log.lastElementChild.textContent = ev.type + " in " + ev.target.id + " (handled by " + ev.currentTarget.id + ")\n";
45+
}
46+
47+
var under = byId("under");
48+
var over = byId("over");
49+
var body = document.body;
50+
51+
var types = ["click", "mousedown", "mouseup"];
52+
for (var i = 0, type; (type = types[i]); ++i) {
53+
under.addEventListener(type, handler);
54+
over.addEventListener(type, handler);
55+
body.addEventListener(type, handler);
56+
}
57+
over.addEventListener("mousedown", function () {
58+
over.style.display = "none";
59+
})
60+
</script>
61+
</body>
62+
</html>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2+
<html xmlns="http://www.w3.org/1999/xhtml">
3+
<head>
4+
<title>An element that disappears on click</title>
5+
<style>
6+
#under {
7+
position: absolute;
8+
top: 20;
9+
left: 20;
10+
width: 100px;
11+
height: 100px;
12+
background-color: white;
13+
}
14+
15+
#over {
16+
position: absolute;
17+
top: 20;
18+
left: 20;
19+
width: 100px;
20+
height: 100px;
21+
background-color: red;
22+
opacity: 0.5;
23+
}
24+
25+
#log {
26+
position: absolute;
27+
top: 120px;
28+
}
29+
</style>
30+
</head>
31+
<body id="body">
32+
<div id="under"><p id="contents">Hello</p></div>
33+
<div id="over"></div>
34+
<div id="log">
35+
<p>Log:<p>
36+
</div>
37+
<script>
38+
var byId = document.getElementById.bind(document);
39+
40+
var log = byId("log");
41+
42+
function handler(ev) {
43+
log.innerHTML += "<p></p>";
44+
log.lastElementChild.textContent = ev.type + " in " + ev.target.id + " (handled by " + ev.currentTarget.id + ")\n";
45+
}
46+
47+
var under = byId("under");
48+
var over = byId("over");
49+
var body = document.body;
50+
51+
var types = ["click", "mousedown", "mouseup"];
52+
for (var i = 0, type; (type = types[i]); ++i) {
53+
under.addEventListener(type, handler);
54+
over.addEventListener(type, handler);
55+
body.addEventListener(type, handler);
56+
}
57+
</script>
58+
</body>
59+
</html>

java/client/test/org/openqa/selenium/CorrectEventFiringTest.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
import static org.hamcrest.Matchers.equalTo;
2121
import static org.hamcrest.Matchers.not;
22+
import static org.hamcrest.Matchers.startsWith;
23+
import static org.junit.Assert.assertEquals;
2224
import static org.junit.Assert.assertFalse;
2325
import static org.junit.Assert.assertNotNull;
2426
import static org.junit.Assert.assertThat;
@@ -29,6 +31,7 @@
2931
import static org.openqa.selenium.WaitingConditions.elementTextToEqual;
3032
import static org.openqa.selenium.WaitingConditions.elementValueToEqual;
3133
import static org.openqa.selenium.support.ui.ExpectedConditions.visibilityOfElementLocated;
34+
import static org.openqa.selenium.testing.Ignore.Driver.FIREFOX;
3235
import static org.openqa.selenium.testing.Ignore.Driver.HTMLUNIT;
3336
import static org.openqa.selenium.testing.Ignore.Driver.IE;
3437
import static org.openqa.selenium.testing.Ignore.Driver.MARIONETTE;
@@ -410,6 +413,36 @@ public void testClickEventsShouldBubble() {
410413
assertTrue("Event didn't bubble up", eventBubbled);
411414
}
412415

416+
@JavascriptEnabled
417+
@Ignore(value = {MARIONETTE, SAFARI, HTMLUNIT})
418+
@Test
419+
public void testClickOverlappingElements() {
420+
driver.get(appServer.whereIs("click_tests/overlapping_elements.html"));
421+
driver.findElement(By.id("under")).click();
422+
assertEquals(driver.findElement(By.id("log")).getText(),
423+
"Log:\n"
424+
+ "mousedown in over (handled by over)\n"
425+
+ "mousedown in over (handled by body)\n"
426+
+ "mouseup in over (handled by over)\n"
427+
+ "mouseup in over (handled by body)\n"
428+
+ "click in over (handled by over)\n"
429+
+ "click in over (handled by body)");
430+
}
431+
432+
@JavascriptEnabled
433+
@Ignore(value = {FIREFOX, MARIONETTE, SAFARI, HTMLUNIT})
434+
@Test
435+
public void testClickAnElementThatDisappear() {
436+
driver.get(appServer.whereIs("click_tests/disappearing_element.html"));
437+
driver.findElement(By.id("over")).click();
438+
assertThat(driver.findElement(By.id("log")).getText(),
439+
startsWith("Log:\n"
440+
+ "mousedown in over (handled by over)\n"
441+
+ "mousedown in over (handled by body)\n"
442+
+ "mouseup in under (handled by under)\n"
443+
+ "mouseup in under (handled by body)"));
444+
}
445+
413446
private void clickOnElementWhichRecordsEvents() {
414447
driver.findElement(By.id("plainButton")).click();
415448
}

java/client/test/org/openqa/selenium/interactions/BasicMouseInterfaceTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ public void testMousePositionIsNotPreservedInActionsChain() {
259259
}
260260
}
261261

262-
@Ignore(value = {IE, REMOTE},
262+
@Ignore(value = {FIREFOX, IE, REMOTE},
263263
reason = "Behaviour not finalized yet regarding linked images.")
264264
@NotYetImplemented(HTMLUNIT)
265265
@Test

javascript/firefox-driver/js/syntheticMouse.js

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -193,19 +193,14 @@ SyntheticMouse.prototype.click = function(target) {
193193
if (parent && parent.tagName.toLowerCase() == 'select' && !parent.multiple) {
194194
bot.action.click(parent, undefined /* coords */);
195195
}
196-
}
197196

198-
goog.log.info(SyntheticMouse.LOG_,
199-
'About to do a bot.action.click on ' + element);
200-
var keyboardState = new bot.Device.ModifiersState();
201-
if (this.modifierKeys !== undefined) {
202-
keyboardState.setPressed(bot.Device.Modifier.SHIFT, this.modifierKeys.isShiftPressed());
203-
keyboardState.setPressed(bot.Device.Modifier.CONTROL, this.modifierKeys.isControlPressed());
204-
keyboardState.setPressed(bot.Device.Modifier.ALT, this.modifierKeys.isAltPressed());
205-
keyboardState.setPressed(bot.Device.Modifier.META, this.modifierKeys.isMetaPressed());
206-
}
197+
goog.log.info(SyntheticMouse.LOG_, 'About to do a bot.action.click on ' + element);
198+
bot.action.click(element, this.lastMousePosition, new bot.Mouse(null, this.modifierKeys));
207199

208-
bot.action.click(element, this.lastMousePosition, new bot.Mouse(null, keyboardState));
200+
} else {
201+
goog.log.info(SyntheticMouse.LOG_, 'About to do a bot.action.click on ' + element);
202+
bot.action.click(element, this.lastMousePosition, this.getMouse_());
203+
}
209204

210205
if (bot.dom.isEditable(element) && element.value !== undefined) {
211206
goog.dom.selection.setCursorPosition(

javascript/firefox-driver/js/utils.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,30 @@ Utils.getLocationOnceScrolledIntoView = function(element, opt_elementScrollBehav
649649
};
650650

651651

652+
Utils.getClickablePoint = function(element) {
653+
element = element.wrappedJSObject ? element.wrappedJSObject : element;
654+
var rect = bot.dom.getClientRect(element);
655+
656+
if (element.getClientRects().length > 1) {
657+
for (var i = 0; i < element.getClientRects().length; i++) {
658+
var candidate = element.getClientRects()[i];
659+
if (candidate.width != 0 && candidate.height != 0) {
660+
return {
661+
x: (candidate.left - rect.left + Math.floor(candidate.width / 2)),
662+
y: (candidate.top - rect.top + Math.floor(candidate.height / 2))
663+
};
664+
}
665+
}
666+
}
667+
668+
// Fallback to the main rect
669+
return {
670+
x: (rect.width ? Math.floor(rect.width / 2) : 0),
671+
y: (rect.height ? Math.floor(rect.height / 2) : 0)
672+
};
673+
};
674+
675+
652676
Utils.unwrapParameters = function(wrappedParameters, doc) {
653677
switch (typeof wrappedParameters) {
654678
case 'number':

javascript/firefox-driver/js/wrappedElement.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,12 @@ WebElement.clickElement = function(respond, parameters) {
6666

6767
var unwrapped = fxdriver.moz.unwrapFor4(element);
6868

69-
var location = Utils.getLocation(unwrapped, unwrapped.tagName == 'A');
70-
var elementHalfWidth = (location.width ? Math.floor(location.width / 2) : 0);
71-
var elementHalfHeight = (location.height ? Math.floor(location.height / 2) : 0);
69+
var offset = Utils.getClickablePoint(unwrapped);
7270

7371
Utils.installWindowCloseListener(respond);
7472
Utils.installClickListener(respond, WebLoadingListener);
7573

76-
var res = this.mouse.move(element, elementHalfWidth, elementHalfHeight);
74+
var res = this.mouse.move(element, offset.x, offset.y);
7775
if (res.status != bot.ErrorCode.SUCCESS) {
7876
respond.status = res.status;
7977
respond.value = res.message;

0 commit comments

Comments
 (0)