Bug 1311279, scroll the select popup when click+drag is used, r=mconley

This commit is contained in:
Neil Deakin 2016-11-17 09:56:43 -05:00
Родитель c8d9aa19d1
Коммит 03860c9589
4 изменённых файлов: 126 добавлений и 21 удалений

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

@ -79,12 +79,18 @@ const PAGECONTENT_TRANSLATED =
"</iframe>" +
"</div></body></html>";
function openSelectPopup(selectPopup, withMouse, selector = "select", win = window) {
function openSelectPopup(selectPopup, mode = "key", selector = "select", win = window) {
let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
if (withMouse) {
return Promise.all([popupShownPromise,
BrowserTestUtils.synthesizeMouseAtCenter(selector, { }, win.gBrowser.selectedBrowser)]);
if (mode == "click" || mode == "mousedown") {
let mousePromise;
if (mode == "click") {
mousePromise = BrowserTestUtils.synthesizeMouseAtCenter(selector, { }, win.gBrowser.selectedBrowser);
} else {
mousePromise = BrowserTestUtils.synthesizeMouse(selector, 5, 5, { type: "mousedown" }, win.gBrowser.selectedBrowser);
}
return Promise.all([popupShownPromise, mousePromise]);
}
EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true, code: "ArrowDown" }, win);
@ -185,7 +191,7 @@ function* doSelectTests(contentType, dtd) {
is((yield getChangeEvents()), 1, "After closed - number of change events");
// Opening and closing the popup without changing the value should not fire a change event.
yield openSelectPopup(selectPopup, true);
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");
@ -194,7 +200,7 @@ function* doSelectTests(contentType, dtd) {
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");
yield openSelectPopup(selectPopup, true);
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");
@ -236,7 +242,7 @@ add_task(function*() {
let selectPopup = menulist.menupopup;
// First, try it when a different <select> element than the one that is open is removed
yield openSelectPopup(selectPopup, true, "#one");
yield openSelectPopup(selectPopup, "click", "#one");
yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
content.document.body.removeChild(content.document.getElementById("two"));
@ -250,7 +256,7 @@ add_task(function*() {
yield hideSelectPopup(selectPopup);
// Next, try it when the same <select> element than the one that is open is removed
yield openSelectPopup(selectPopup, true, "#three");
yield openSelectPopup(selectPopup, "click", "#three");
let popupHiddenPromise = BrowserTestUtils.waitForEvent(selectPopup, "popuphidden");
yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
@ -261,7 +267,7 @@ add_task(function*() {
ok(true, "Popup hidden when select is removed");
// Finally, try it when the tab is closed while the select popup is open.
yield openSelectPopup(selectPopup, true, "#one");
yield openSelectPopup(selectPopup, "click", "#one");
popupHiddenPromise = BrowserTestUtils.waitForEvent(selectPopup, "popuphidden");
yield BrowserTestUtils.removeTab(tab);
@ -279,7 +285,7 @@ add_task(function*() {
let selectPopup = menulist.menupopup;
// First, get the position of the select popup when no translations have been applied.
yield openSelectPopup(selectPopup, false);
yield openSelectPopup(selectPopup);
let rect = selectPopup.getBoundingClientRect();
let expectedX = rect.left;
@ -320,7 +326,7 @@ add_task(function*() {
});
});
yield openSelectPopup(selectPopup, false);
yield openSelectPopup(selectPopup);
expectedX += step[2];
expectedY += step[3];
@ -391,7 +397,7 @@ add_task(function* test_event_order() {
for (let mode of ["enter", "click"]) {
let expected = mode == "enter" ? expectedEnter : expectedClick;
yield openSelectPopup(selectPopup, true, mode == "enter" ? "#one" : "#two");
yield openSelectPopup(selectPopup, "click", mode == "enter" ? "#one" : "#two");
let eventsPromise = ContentTask.spawn(browser, [mode, expected], function*([contentMode, contentExpected]) {
return new Promise((resolve) => {
@ -443,6 +449,38 @@ function* performLargePopupTests(win) {
let selectPopup = win.document.getElementById("ContentSelectDropdown").menupopup;
let browserRect = browser.getBoundingClientRect();
// Check if a drag-select works and scrolls the list.
yield openSelectPopup(selectPopup, "mousedown", "select", win);
let scrollPos = selectPopup.scrollBox.scrollTop;
let popupRect = selectPopup.getBoundingClientRect();
// First, check that scrolling does not occur when the mouse is moved over the
// anchor button but not the popup yet.
EventUtils.synthesizeMouseAtPoint(popupRect.left + 5, popupRect.top - 10, { type: "mousemove" }, win);
is(selectPopup.scrollBox.scrollTop, scrollPos, "scroll position after mousemove over button");
EventUtils.synthesizeMouseAtPoint(popupRect.left + 20, popupRect.top + 10, { type: "mousemove" }, win);
// Dragging above the popup scrolls it up.
EventUtils.synthesizeMouseAtPoint(popupRect.left + 20, popupRect.top - 20, { type: "mousemove" }, win);
ok(selectPopup.scrollBox.scrollTop < scrollPos - 5, "scroll position at drag up");
// Dragging below the popup scrolls it down.
scrollPos = selectPopup.scrollBox.scrollTop;
EventUtils.synthesizeMouseAtPoint(popupRect.left + 20, popupRect.bottom + 20, { type: "mousemove" }, win);
ok(selectPopup.scrollBox.scrollTop > scrollPos + 5, "scroll position at drag down");
// Releasing the mouse button and moving the mouse does not change the scroll position.
scrollPos = selectPopup.scrollBox.scrollTop;
EventUtils.synthesizeMouseAtPoint(popupRect.left + 20, popupRect.bottom + 25, { type: "mouseup" }, win);
is(selectPopup.scrollBox.scrollTop, scrollPos, "scroll position at mouseup");
EventUtils.synthesizeMouseAtPoint(popupRect.left + 20, popupRect.bottom + 20, { type: "mousemove" }, win);
is(selectPopup.scrollBox.scrollTop, scrollPos, "scroll position at mouseup again");
yield hideSelectPopup(selectPopup, "escape", win);
let positions = [
"margin-top: 300px;",
"position: fixed; bottom: 100px;",
@ -450,8 +488,8 @@ function* performLargePopupTests(win) {
];
let position;
while (true) {
yield openSelectPopup(selectPopup, false, "select", win);
while (positions.length) {
yield openSelectPopup(selectPopup, "key", "select", win);
let rect = selectPopup.getBoundingClientRect();
ok(rect.top >= browserRect.top, "Popup top position in within browser area");
@ -481,14 +519,11 @@ function* performLargePopupTests(win) {
yield hideSelectPopup(selectPopup, "enter", win);
position = positions.shift();
if (!position) {
break;
}
let contentPainted = BrowserTestUtils.contentPainted(browser);
yield ContentTask.spawn(browser, position, function*(contentPosition) {
let select = content.document.getElementById("one");
select.setAttribute("style", contentPosition);
select.setAttribute("style", contentPosition || "");
select.getBoundingClientRect();
});
yield contentPainted;
@ -622,7 +657,7 @@ add_task(function* test_mousemove_correcttarget() {
// The popup should be closed when fullscreen mode is entered or exited.
for (let steps = 0; steps < 2; steps++) {
yield openSelectPopup(selectPopup, true);
yield openSelectPopup(selectPopup, "click");
let popupHiddenPromise = BrowserTestUtils.waitForEvent(selectPopup, "popuphidden");
let sizeModeChanged = BrowserTestUtils.waitForEvent(window, "sizemodechange");
BrowserFullScreen();

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

@ -246,6 +246,12 @@
</xul:arrowscrollbox>
</content>
<implementation>
<field name="scrollBox" readonly="true">
document.getAnonymousElementByAttribute(this, "class", "popup-internal-box");
</field>
</implementation>
<handlers>
<handler event="popupshowing" phase="target">
<![CDATA[
@ -633,9 +639,9 @@
<binding id="popup-scrollbars" extends="chrome://global/content/bindings/popup.xml#popup">
<content>
<xul:hbox class="popup-internal-box" flex="1" orient="vertical" style="overflow: auto;">
<xul:scrollbox class="popup-internal-box" flex="1" orient="vertical" style="overflow: auto;">
<children/>
</xul:hbox>
</xul:scrollbox>
</content>
</binding>

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

@ -21,6 +21,15 @@
<children/>
</xul:box>
</content>
<implementation>
<method name="scrollByIndex">
<parameter name="index"/>
<body>
this.boxObject.scrollByIndex(index);
</body>
</method>
</implementation>
</binding>
<binding id="arrowscrollbox" extends="chrome://global/content/bindings/scrollbox.xml#scrollbox-base">

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

@ -15,6 +15,9 @@ const {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
// Maximum number of rows to display in the select dropdown.
const MAX_ROWS = 20;
// Interval between autoscrolls
const AUTOSCROLL_INTERVAL = 25;
// Minimum elements required to show select search
const SEARCH_MINIMUM_ELEMENTS = 40;
@ -24,6 +27,9 @@ var currentZoom = 1;
var closedWithEnter = false;
this.SelectParentHelper = {
draggedOverPopup: false,
scrollTimer: 0,
populate(menulist, items, selectedIndex, zoom) {
// Clear the current contents of the popup
menulist.menupopup.textContent = "";
@ -65,6 +71,11 @@ this.SelectParentHelper = {
constraintRect.width, constraintRect.height);
menupopup.setConstraintRect(constraintRect);
menupopup.openPopupAtScreenRect(AppConstants.platform == "macosx" ? "selection" : "after_start", rect.left, rect.top, rect.width, rect.height, false, false);
// Set up for dragging
menupopup.setCaptureAlways();
this.draggedOverPopup = false;
menupopup.addEventListener("mousemove", this);
},
hide(menulist, browser) {
@ -73,9 +84,19 @@ this.SelectParentHelper = {
}
},
clearScrollTimer() {
if (this.scrollTimer) {
let win = currentBrowser.ownerDocument.defaultView;
win.clearInterval(this.scrollTimer);
this.scrollTimer = 0;
}
},
handleEvent(event) {
switch (event.type) {
case "mouseup":
this.clearScrollTimer();
currentMenulist.menupopup.removeEventListener("mousemove", this);
currentBrowser.messageManager.sendAsyncMessage("Forms:MouseUp", {});
break;
@ -87,6 +108,38 @@ this.SelectParentHelper = {
currentBrowser.messageManager.sendAsyncMessage("Forms:MouseOut", {});
break;
case "mousemove":
let menupopup = currentMenulist.menupopup;
let popupRect = menupopup.getOuterScreenRect();
this.clearScrollTimer();
// 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.
if (event.screenX >= popupRect.left && event.screenX <= popupRect.right) {
if (!this.draggedOverPopup) {
if (event.screenY > popupRect.top && event.screenY < popupRect.bottom) {
this.draggedOverPopup = true;
}
}
if (this.draggedOverPopup &&
(event.screenY <= popupRect.top || event.screenY >= popupRect.bottom)) {
let scrollAmount = event.screenY <= popupRect.top ? -1 : 1;
menupopup.scrollBox.scrollByIndex(scrollAmount);
let win = currentBrowser.ownerDocument.defaultView;
this.scrollTimer = win.setInterval(function() {
menupopup.scrollBox.scrollByIndex(scrollAmount);
}, AUTOSCROLL_INTERVAL);
}
}
break;
case "keydown":
if (event.keyCode == event.DOM_VK_RETURN) {
closedWithEnter = true;
@ -112,6 +165,8 @@ this.SelectParentHelper = {
currentBrowser.messageManager.sendAsyncMessage("Forms:DismissedDropDown", {});
let popup = event.target;
this._unregisterListeners(currentBrowser, popup);
this.clearScrollTimer();
popup.releaseCapture();
popup.parentNode.hidden = true;
currentBrowser = null;
currentMenulist = null;