Commit 76cab852 authored by dbeam's avatar dbeam Committed by Commit bot

history: fix more focus oddness.

Various things (cr.ui.MenuButtons, checkboxes) call .preventDefault()
while handling 'mousedown' events which messes with focus. Account for this.

R=arv@chromium.org
BUG=424475

Review URL: https://codereview.chromium.org/685783003

Cr-Commit-Position: refs/heads/master@{#302699}
parent 3dadf61a
...@@ -212,7 +212,7 @@ Visit.prototype.getResultDOM = function(propertyBag) { ...@@ -212,7 +212,7 @@ Visit.prototype.getResultDOM = function(propertyBag) {
if (!isMobileVersion()) { if (!isMobileVersion()) {
// Clicking anywhere in the entryBox will check/uncheck the checkbox. // Clicking anywhere in the entryBox will check/uncheck the checkbox.
entryBox.setAttribute('for', checkbox.id); entryBox.setAttribute('for', checkbox.id);
entryBox.addEventListener('mousedown', entryBoxMousedown); entryBox.addEventListener('mousedown', this.handleMousedown_.bind(this));
entryBox.addEventListener('click', entryBoxClick); entryBox.addEventListener('click', entryBoxClick);
entryBox.addEventListener('keydown', this.handleKeydown_.bind(this)); entryBox.addEventListener('keydown', this.handleKeydown_.bind(this));
} }
...@@ -498,6 +498,20 @@ Visit.prototype.handleKeydown_ = function(e) { ...@@ -498,6 +498,20 @@ Visit.prototype.handleKeydown_ = function(e) {
this.removeEntryFromHistory_(e); this.removeEntryFromHistory_(e);
}; };
/**
* @param {Event} event A mousedown event.
* @private
*/
Visit.prototype.handleMousedown_ = function(event) {
// Prevent text selection when shift-clicking to select multiple entries.
if (event.shiftKey) {
event.preventDefault();
if (this.model_.getView().isInFocusGrid(event.target))
event.target.focus();
}
};
/** /**
* Removes a history entry on click or keydown and finds a new entry to focus. * Removes a history entry on click or keydown and finds a new entry to focus.
* @param {Event} e A click or keydown event. * @param {Event} e A click or keydown event.
...@@ -897,6 +911,28 @@ HistoryFocusObserver.prototype = { ...@@ -897,6 +911,28 @@ HistoryFocusObserver.prototype = {
}, },
}; };
///////////////////////////////////////////////////////////////////////////////
// HistoryFocusGrid:
/**
* @param {Node=} opt_boundary
* @param {cr.ui.FocusRow.Observer=} opt_observer
* @constructor
* @extends {cr.ui.FocusGrid}
*/
function HistoryFocusGrid(opt_boundary, opt_observer) {
cr.ui.FocusGrid.apply(this, arguments);
}
HistoryFocusGrid.prototype = {
__proto__: cr.ui.FocusGrid.prototype,
/** @override */
onMousedown: function(row, e) {
return !!findAncestorByClass(e.target, 'menu-button');
},
};
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// HistoryView: // HistoryView:
...@@ -911,8 +947,8 @@ function HistoryView(model) { ...@@ -911,8 +947,8 @@ function HistoryView(model) {
this.editButtonTd_ = $('edit-button'); this.editButtonTd_ = $('edit-button');
this.editingControlsDiv_ = $('editing-controls'); this.editingControlsDiv_ = $('editing-controls');
this.resultDiv_ = $('results-display'); this.resultDiv_ = $('results-display');
this.focusGrid_ = new cr.ui.FocusGrid(this.resultDiv_, this.focusGrid_ = new HistoryFocusGrid(this.resultDiv_,
new HistoryFocusObserver); new HistoryFocusObserver);
this.pageDiv_ = $('results-pagination'); this.pageDiv_ = $('results-pagination');
this.model_ = model; this.model_ = model;
this.pageIndex_ = 0; this.pageIndex_ = 0;
...@@ -1254,6 +1290,14 @@ HistoryView.prototype.positionNotificationBar = function() { ...@@ -1254,6 +1290,14 @@ HistoryView.prototype.positionNotificationBar = function() {
} }
}; };
/**
* @param {Element} el An element to look for.
* @return {boolean} Whether |el| is in |this.focusGrid_|.
*/
HistoryView.prototype.isInFocusGrid = function(el) {
return !!this.focusGrid_.getPositionForTarget(el);
};
// HistoryView, private: ------------------------------------------------------ // HistoryView, private: ------------------------------------------------------
/** /**
...@@ -2163,12 +2207,6 @@ function updateParentCheckbox(checkbox) { ...@@ -2163,12 +2207,6 @@ function updateParentCheckbox(checkbox) {
groupCheckbox.checked = false; groupCheckbox.checked = false;
} }
function entryBoxMousedown(event) {
// Prevent text selection when shift-clicking to select multiple entries.
if (event.shiftKey)
event.preventDefault();
}
/** /**
* Handle click event for entryBoxes. * Handle click event for entryBoxes.
* @param {!Event} event A click event. * @param {!Event} event A click event.
......
...@@ -909,6 +909,59 @@ TEST_F('HistoryWebUIRealBackendTest', 'showConfirmDialogAndRemove', function() { ...@@ -909,6 +909,59 @@ TEST_F('HistoryWebUIRealBackendTest', 'showConfirmDialogAndRemove', function() {
assertFalse($('alertOverlay').classList.contains('showing')); assertFalse($('alertOverlay').classList.contains('showing'));
}); });
TEST_F('HistoryWebUIRealBackendTest', 'menuButtonActivatesOneRow', function() {
var entries = document.querySelectorAll('.entry');
assertEquals(3, entries.length);
assertTrue(entries[0].classList.contains('active'));
assertTrue($('action-menu').hidden);
// Show the menu via mousedown on the menu button.
var menuButton = entries[2].querySelector('.menu-button');
menuButton.dispatchEvent(new MouseEvent('mousedown'));
expectFalse($('action-menu').hidden);
// Check that the 'active' item hasn't changed.
expectTrue(entries[0].classList.contains('active'));
expectFalse(entries[2].classList.contains('active'));
testDone();
});
TEST_F('HistoryWebUIRealBackendTest', 'shiftClickActivatesOneRow', function() {
var entries = document.querySelectorAll('.entry');
assertEquals(3, entries.length);
assertTrue(entries[0].classList.contains('active'));
entries[0].visit.checkBox.focus();
assertEquals(entries[0].visit.checkBox, document.activeElement);
entries[0].visit.checkBox.click();
assertTrue(entries[0].visit.checkBox.checked);
var entryBox = entries[2].querySelector('.entry-box');
entryBox.dispatchEvent(new MouseEvent('click', {shiftKey: true}));
assertTrue(entries[1].visit.checkBox.checked);
// Focus shouldn't have changed, but the checkbox should toggle.
expectEquals(entries[0].visit.checkBox, document.activeElement);
expectTrue(entries[0].classList.contains('active'));
expectFalse(entries[2].classList.contains('active'));
var shiftDown = new MouseEvent('mousedown', {shiftKey: true, bubbles: true});
entries[2].visit.checkBox.dispatchEvent(shiftDown);
expectEquals(entries[2].visit.checkBox, document.activeElement);
// 'focusin' events aren't dispatched while tests are run in batch (e.g.
// --test-launcher-jobs=2). Simulate this. TODO(dbeam): fix instead.
cr.dispatchSimpleEvent(document.activeElement, 'focusin', true, true);
expectFalse(entries[0].classList.contains('active'));
expectTrue(entries[2].classList.contains('active'));
testDone();
});
/** /**
* Fixture for History WebUI testing when deletions are prohibited. * Fixture for History WebUI testing when deletions are prohibited.
* @extends {HistoryWebUIRealBackendTest} * @extends {HistoryWebUIRealBackendTest}
......
...@@ -80,7 +80,7 @@ cr.define('cr.ui', function() { ...@@ -80,7 +80,7 @@ cr.define('cr.ui', function() {
row = this.rows.length - 1; row = this.rows.length - 1;
if (!this.rows[row]) if (!this.rows[row])
return; return false;
var colIndex = keyRow.items.indexOf(e.target); var colIndex = keyRow.items.indexOf(e.target);
var col = Math.min(colIndex, this.rows[row].items.length - 1); var col = Math.min(colIndex, this.rows[row].items.length - 1);
...@@ -88,6 +88,12 @@ cr.define('cr.ui', function() { ...@@ -88,6 +88,12 @@ cr.define('cr.ui', function() {
this.rows[row].focusIndex(col); this.rows[row].focusIndex(col);
e.preventDefault(); e.preventDefault();
return true;
},
/** @override */
onMousedown: function(row, e) {
return false;
}, },
/** /**
......
...@@ -56,7 +56,7 @@ cr.define('cr.ui', function() { ...@@ -56,7 +56,7 @@ cr.define('cr.ui', function() {
if (item != document.activeElement) if (item != document.activeElement)
item.tabIndex = -1; item.tabIndex = -1;
this.eventTracker_.add(item, 'click', this.onClick_.bind(this)); this.eventTracker_.add(item, 'mousedown', this.onMousedown_.bind(this));
}, this); }, this);
/** /**
...@@ -75,9 +75,17 @@ cr.define('cr.ui', function() { ...@@ -75,9 +75,17 @@ cr.define('cr.ui', function() {
* Called when a key is pressed while an item in |this.items| is focused. If * Called when a key is pressed while an item in |this.items| is focused. If
* |e|'s default is prevented, further processing is skipped. * |e|'s default is prevented, further processing is skipped.
* @param {cr.ui.FocusRow} row The row that detected a keydown. * @param {cr.ui.FocusRow} row The row that detected a keydown.
* @param {Event} e The keydown event. * @param {Event} e
* @return {boolean} Whether the event was handled.
*/ */
onKeydown: assertNotReached, onKeydown: assertNotReached,
/**
* @param {cr.ui.FocusRow} row The row that detected the mouse going down.
* @param {Event} e
* @return {boolean} Whether the event was handled.
*/
onMousedown: assertNotReached,
}; };
/** @interface */ /** @interface */
...@@ -157,10 +165,7 @@ cr.define('cr.ui', function() { ...@@ -157,10 +165,7 @@ cr.define('cr.ui', function() {
if (item < 0) if (item < 0)
return; return;
if (this.delegate_) if (this.delegate_ && this.delegate_.onKeydown(this, e))
this.delegate_.onKeydown(this, e);
if (e.defaultPrevented)
return; return;
var index = -1; var index = -1;
...@@ -185,7 +190,10 @@ cr.define('cr.ui', function() { ...@@ -185,7 +190,10 @@ cr.define('cr.ui', function() {
* @param {Event} e A click event. * @param {Event} e A click event.
* @private * @private
*/ */
onClick_: function(e) { onMousedown_: function(e) {
if (this.delegate_ && this.delegate_.onMousedown(this, e))
return;
if (!e.button) if (!e.button)
this.activeIndex = this.items.indexOf(e.currentTarget); this.activeIndex = this.items.indexOf(e.currentTarget);
}, },
......
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