Bug 1311279, when the mouse is released, check if it should be retargetted at the select element in the content process, so that a click event is received, r=mconley

This commit is contained in:
Neil Deakin 2017-01-19 10:03:56 -05:00
Родитель 03860c9589
Коммит 839f1bb76c
3 изменённых файлов: 55 добавлений и 19 удалений

Просмотреть файл

@ -11,7 +11,8 @@ const XHTML_DTD = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www
const PAGECONTENT =
"<html xmlns='http://www.w3.org/1999/xhtml'>" +
"<body onload='gChangeEvents = 0;gInputEvents = 0; document.body.firstChild.focus()'><select oninput='gInputEvents++' onchange='gChangeEvents++'>" +
"<body onload='gChangeEvents = 0;gInputEvents = 0; gClickEvents = 0; document.body.firstChild.focus()'>" +
"<select oninput='gInputEvents++' onchange='gChangeEvents++' onclick='if (event.target == this) gClickEvents++'>" +
" <optgroup label='First Group'>" +
" <option value='One'>One</option>" +
" <option value='Two'>Two</option>" +
@ -127,6 +128,12 @@ function getChangeEvents() {
});
}
function getClickEvents() {
return ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
return content.wrappedJSObject.gClickEvents;
});
}
function* doSelectTests(contentType, dtd) {
const pageUrl = "data:" + contentType + "," + escape(dtd + "\n" + PAGECONTENT);
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
@ -169,6 +176,7 @@ function* doSelectTests(contentType, dtd) {
is((yield getInputEvents()), 0, "Before closed - number of input events");
is((yield getChangeEvents()), 0, "Before closed - number of change events");
is((yield getClickEvents()), 0, "Before closed - number of click events");
EventUtils.synthesizeKey("a", { accelKey: true });
yield ContentTask.spawn(gBrowser.selectedBrowser, { isWindows }, function(args) {
@ -189,26 +197,31 @@ function* doSelectTests(contentType, dtd) {
is(menulist.selectedIndex, 3, "Item 3 still selected");
is((yield getInputEvents()), 1, "After closed - number of input events");
is((yield getChangeEvents()), 1, "After closed - number of change events");
is((yield getClickEvents()), 0, "After closed - number of click events");
// Opening and closing the popup without changing the value should not fire a change event.
yield openSelectPopup(selectPopup, "click");
yield hideSelectPopup(selectPopup, "escape");
is((yield getInputEvents()), 1, "Open and close with no change - number of input events");
is((yield getChangeEvents()), 1, "Open and close with no change - number of change events");
is((yield getClickEvents()), 1, "Open and close with no change - number of click events");
EventUtils.synthesizeKey("VK_TAB", { });
EventUtils.synthesizeKey("VK_TAB", { shiftKey: true });
is((yield getInputEvents()), 1, "Tab away from select with no change - number of input events");
is((yield getChangeEvents()), 1, "Tab away from select with no change - number of change events");
is((yield getClickEvents()), 1, "Tab away from select with no change - number of click events");
yield openSelectPopup(selectPopup, "click");
EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" });
yield hideSelectPopup(selectPopup, "escape");
is((yield getInputEvents()), isWindows ? 2 : 1, "Open and close with change - number of input events");
is((yield getChangeEvents()), isWindows ? 2 : 1, "Open and close with change - number of change events");
is((yield getClickEvents()), 2, "Open and close with change - number of click events");
EventUtils.synthesizeKey("VK_TAB", { });
EventUtils.synthesizeKey("VK_TAB", { shiftKey: true });
is((yield getInputEvents()), isWindows ? 2 : 1, "Tab away from select with change - number of input events");
is((yield getChangeEvents()), isWindows ? 2 : 1, "Tab away from select with change - number of change events");
is((yield getClickEvents()), 2, "Tab away from select with change - number of click events");
is(selectPopup.lastChild.previousSibling.label, "Seven", "Spaces collapsed");
is(selectPopup.lastChild.label, "\xA0\xA0Eight\xA0\xA0", "Non-breaking spaces not collapsed");

Просмотреть файл

@ -113,6 +113,15 @@ this.SelectContentHelper.prototype = {
});
},
dispatchMouseEvent(win, target, eventName) {
let mouseEvent = new win.MouseEvent(eventName, {
view: win,
bubbles: true,
cancelable: true,
});
target.dispatchEvent(mouseEvent);
},
receiveMessage(message) {
switch (message.name) {
case "Forms:SelectDropDownItem":
@ -131,15 +140,8 @@ this.SelectContentHelper.prototype = {
// to select an element in the dropdown, we only fire input and
// change events.
if (!this.closedWithEnter) {
const MOUSE_EVENTS = ["mousedown", "mouseup"];
for (let eventName of MOUSE_EVENTS) {
let mouseEvent = new win.MouseEvent(eventName, {
view: win,
bubbles: true,
cancelable: true,
});
selectedOption.dispatchEvent(mouseEvent);
}
this.dispatchMouseEvent(win, selectedOption, "mousedown");
this.dispatchMouseEvent(win, selectedOption, "mouseup");
DOMUtils.removeContentState(this.element, kStateActive);
}
@ -154,12 +156,7 @@ this.SelectContentHelper.prototype = {
this.element.dispatchEvent(changeEvent);
if (!this.closedWithEnter) {
let mouseEvent = new win.MouseEvent("click", {
view: win,
bubbles: true,
cancelable: true,
});
selectedOption.dispatchEvent(mouseEvent);
this.dispatchMouseEvent(win, selectedOption, "click");
}
}
@ -175,9 +172,15 @@ this.SelectContentHelper.prototype = {
break;
case "Forms:MouseUp":
let win = this.element.ownerDocument.defaultView;
if (message.data.onAnchor) {
this.dispatchMouseEvent(win, this.element, "mouseup");
}
DOMUtils.removeContentState(this.element, kStateActive);
if (message.data.onAnchor) {
this.dispatchMouseEvent(win, this.element, "click");
}
break;
}
},

Просмотреть файл

@ -25,6 +25,7 @@ var currentBrowser = null;
var currentMenulist = null;
var currentZoom = 1;
var closedWithEnter = false;
var selectRect;
this.SelectParentHelper = {
draggedOverPopup: false,
@ -42,6 +43,7 @@ this.SelectParentHelper = {
menulist.hidden = false;
currentBrowser = browser;
closedWithEnter = false;
selectRect = rect;
this._registerListeners(browser, menulist.menupopup);
let win = browser.ownerDocument.defaultView;
@ -97,7 +99,15 @@ this.SelectParentHelper = {
case "mouseup":
this.clearScrollTimer();
currentMenulist.menupopup.removeEventListener("mousemove", this);
currentBrowser.messageManager.sendAsyncMessage("Forms:MouseUp", {});
function inRect(rect, x, y) {
return x >= rect.left && x <= rect.left + rect.width && y >= rect.top && y <= rect.top + rect.height;
}
let x = event.screenX, y = event.screenY;
let onAnchor = !inRect(currentMenulist.menupopup.getOuterScreenRect(), x, y) &&
inRect(selectRect, x, y) && currentMenulist.menupopup.state == "open";
currentBrowser.messageManager.sendAsyncMessage("Forms:MouseUp", { onAnchor });
break;
case "mouseover":
@ -110,15 +120,25 @@ this.SelectParentHelper = {
case "mousemove":
let menupopup = currentMenulist.menupopup;
let popupRect = menupopup.getOuterScreenRect();
this.clearScrollTimer();
// If the user released the mouse before the popup opens, we will
// still be capturing, so check that the button is still pressed. If
// not, release the capture and do nothing else. This also handles if
// the dropdown was opened via the keyboard.
if (!(event.buttons & 1)) {
currentMenulist.menupopup.removeEventListener("mousemove", this);
menupopup.releaseCapture();
return;
}
// If dragging outside the top or bottom edge of the popup, but within
// the popup area horizontally, scroll the list in that direction. The
// draggedOverPopup flag is used to ensure that scrolling does not start
// until the mouse has moved over the popup first, preventing scrolling
// while over the dropdown button.
let popupRect = menupopup.getOuterScreenRect();
if (event.screenX >= popupRect.left && event.screenX <= popupRect.right) {
if (!this.draggedOverPopup) {
if (event.screenY > popupRect.top && event.screenY < popupRect.bottom) {