Skip to content

Commit 5d3639f

Browse files
committed
Don't blur when clicking a non-focusable child of the active element.
The active element will retain the focus, so no events should be fired. Fixes some, but not all, instances of #2312.
1 parent 006a6c4 commit 5d3639f

File tree

5 files changed

+85
-6
lines changed

5 files changed

+85
-6
lines changed

common/src/web/javascriptPage.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,16 @@ <h1>Type Stuff</h1>
239239
<span id="hideMe" onclick="this.style.display = 'none';">Click to hide me.</span>
240240
</div>
241241

242+
<div id="hideOnBlur" tabindex="0"
243+
style="width: 100px; height: 100px; border: 1px solid red"
244+
onblur="appendMessage('blur'); this.style.display = 'none';">
245+
<div id="hideOnBlurChild"
246+
style="width: 30px; height: 30px; border: 1px solid blue">
247+
x
248+
</div>
249+
Focusable. Will hide when focus is lost.
250+
</div>
251+
242252
<div style="margin-top: 10px;">
243253
Click actions delayed by 3000ms:
244254
<div id="clickToShow" onclick="delayedShowHide(3000, true);"

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,26 @@ public void testSendingKeysToAFocusedElementShouldNotBlurThatElement() {
335335
assertEventNotFired("blur");
336336
}
337337

338+
@JavascriptEnabled
339+
@Ignore(value = {SAFARI, HTMLUNIT})
340+
@Test
341+
public void testClickingAnUnfocusableChildShouldNotBlurTheParent() {
342+
assumeFalse(isOldIe(driver));
343+
driver.get(pages.javascriptPage);
344+
// Click on parent, giving it the focus.
345+
WebElement parent = driver.findElement(By.id("hideOnBlur"));
346+
parent.click();
347+
assertEventNotFired("blur");
348+
// Click on child. It is not focusable, so focus should stay on the parent.
349+
driver.findElement(By.id("hideOnBlurChild")).click();
350+
assertTrue("#hideOnBlur should still be displayed after click",
351+
parent.isDisplayed());
352+
assertEventNotFired("blur");
353+
// Click elsewhere, and let the element disappear.
354+
driver.findElement(By.id("result")).click();
355+
assertEventFired("blur");
356+
}
357+
338358
@JavascriptEnabled
339359
@Test
340360
public void testSubmittingFormFromFormElementShouldFireOnSubmitForThatForm() {

javascript/atoms/device.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -452,8 +452,14 @@ bot.Device.prototype.clickElement = function(coord, button, opt_force,
452452
* @protected
453453
*/
454454
bot.Device.prototype.focusOnElement = function() {
455-
// Focusing on an <option> always focuses on the parent <select>.
456-
var elementToFocus = this.select_ || this.element_;
455+
var elementToFocus = goog.dom.getAncestor(
456+
this.element_,
457+
function (node) {
458+
return !!node && bot.dom.isElement(node) &&
459+
bot.dom.isFocusable(/** @type {!Element} */ (node));
460+
},
461+
true /* Return this.element_ if it is focusable. */);
462+
elementToFocus = elementToFocus || this.element_;
457463

458464
var activeElement = bot.dom.getActiveElement(elementToFocus);
459465
if (elementToFocus == activeElement) {

javascript/atoms/dom.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,15 +173,19 @@ bot.dom.FOCUSABLE_FORM_FIELDS_ = [
173173

174174
/**
175175
* Returns whether a node is a focusable element. An element may receive focus
176-
* if it is a form field or has a positive tabindex.
176+
* if it is a form field, has a non-negative tabindex, or is editable.
177177
* @param {!Element} element The node to test.
178178
* @return {boolean} Whether the node is focusable.
179179
*/
180180
bot.dom.isFocusable = function(element) {
181-
return goog.array.some(bot.dom.FOCUSABLE_FORM_FIELDS_, function(tagName) {
181+
return goog.array.some(bot.dom.FOCUSABLE_FORM_FIELDS_, tagNameMatches) ||
182+
(bot.dom.getAttribute(element, 'tabindex') != null &&
183+
Number(bot.dom.getProperty(element, 'tabIndex')) >= 0) ||
184+
bot.dom.isEditable(element);
185+
186+
function tagNameMatches(tagName) {
182187
return element.tagName.toUpperCase() == tagName;
183-
}) || (bot.dom.getAttribute(element, 'tabindex') != null &&
184-
Number(bot.dom.getProperty(element, 'tabIndex')) >= 0);
188+
}
185189
};
186190

187191

javascript/atoms/test/focus_test.html

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,36 @@
141141
});
142142
}
143143

144+
function testShouldRetainFocusIfFirstFocusableAncestorIsAlreadyActive() {
145+
if (!bot.test.isWindowFocused()) {
146+
return;
147+
}
148+
149+
var parent = document.getElementById('hideOnBlur');
150+
var child = document.getElementById('hideOnBlurChild');
151+
recordBlurAndFocus(parent);
152+
153+
expect(eventType.FOCUS, parent);
154+
bot.action.focusOnElement(parent);
155+
return timeout().then(function() {
156+
assertHasExpectedEventOrder();
157+
assertElementIsActiveElement(parent);
158+
159+
bot.action.focusOnElement(child);
160+
// No additional events expected. The parent should retain focus.
161+
return timeout();
162+
}).then(function() {
163+
assertHasExpectedEventOrder();
164+
assertElementIsActiveElement(parent);
165+
166+
bot.action.focusOnElement(text1);
167+
expect(eventType.BLUR, parent);
168+
return timeout();
169+
}).then(function() {
170+
assertHasExpectedEventOrder();
171+
});
172+
}
173+
144174
function testFocusOnHiddenElementThrows() {
145175
var element = document.getElementById('invisibleText');
146176
assertThrows(goog.partial(bot.action.focusOnElement, element));
@@ -217,6 +247,15 @@
217247
<div><strong>Disabled textbox</strong></div>
218248
<input id="disabledText" type="text" value="I'm disabled" disabled/>
219249
</div>
250+
<div id="hideOnBlur" tabindex="0"
251+
style="width: 100px; height: 100px; border: 1px solid red"
252+
onblur="this.style.display = 'none';">
253+
<div id="hideOnBlurChild"
254+
style="width: 30px; height: 30px; border: 1px solid blue">
255+
x
256+
</div>
257+
Focusable. Will hide when focus is lost.
258+
</div>
220259
<script type="text/javascript">
221260
function log(msg) {
222261
goog.dom.$('testLog').appendChild(

0 commit comments

Comments
 (0)