Bug 1492842 - Implement UrlbarInput up/down key event handling to navigate within the UrlbarView. r=Standard8

Differential Revision: https://phabricator.services.mozilla.com/D13794

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Dão Gottwald 2018-12-06 21:33:30 +00:00
Родитель f4794a2bfb
Коммит e54f0d9a0f
3 изменённых файлов: 137 добавлений и 25 удалений

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

@ -107,6 +107,26 @@ class UrlbarController {
this._listeners = new Set();
}
/**
* Hooks up the controller with an input.
*
* @param {UrlbarInput} input
* The UrlbarInput instance associated with this controller.
*/
setInput(input) {
this.input = input;
}
/**
* Hooks up the controller with a view.
*
* @param {UrlbarView} view
* The UrlbarView instance associated with this controller.
*/
setView(view) {
this.view = view;
}
/**
* Takes a query context and starts the query based on the user input.
*
@ -173,6 +193,55 @@ class UrlbarController {
// TODO: implementation needed (bug 1496685)
}
/**
* Receives keyboard events from the input and handles those that should
* navigate within the view or pick the currently selected item.
*
* @param {KeyboardEvent} event
* The DOM KeyboardEvent.
*/
handleKeyNavigation(event) {
// Handle readline/emacs-style navigation bindings on Mac.
if (AppConstants.platform == "macosx" &&
this.view.isOpen &&
event.ctrlKey &&
(event.key == "n" || event.key == "p")) {
this.view.selectNextItem({ reverse: event.key == "p" });
event.preventDefault();
return;
}
switch (event.keyCode) {
case KeyEvent.DOM_VK_RETURN:
if (AppConstants.platform == "macosx" &&
event.metaKey) {
// Prevent beep on Mac.
event.preventDefault();
}
// TODO: We may have an autoFill entry, so we should use that instead.
// TODO: We should have an input bufferrer so that we can use search results
// if appropriate.
this.input.handleCommand(event);
return;
case KeyEvent.DOM_VK_TAB:
this.view.selectNextItem({ reverse: event.shiftKey });
event.preventDefault();
break;
case KeyEvent.DOM_VK_DOWN:
if (!event.ctrlKey && !event.altKey) {
this.view.selectNextItem();
event.preventDefault();
}
break;
case KeyEvent.DOM_VK_UP:
if (!event.ctrlKey && !event.altKey) {
this.view.selectNextItem({ reverse: true });
event.preventDefault();
}
break;
}
}
/**
* Internal function to notify listeners of results.
*

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

@ -50,6 +50,7 @@ class UrlbarInput {
this.controller = options.controller || new UrlbarController({
browserWindow: this.window,
});
this.controller.setInput(this);
this.view = new UrlbarView(this);
this.valueIsTyped = false;
this.userInitiatedFocus = false;
@ -104,7 +105,7 @@ class UrlbarInput {
this.inputField.addEventListener("underflow", this);
this.inputField.addEventListener("scrollend", this);
this.inputField.addEventListener("select", this);
this.inputField.addEventListener("keyup", this);
this.inputField.addEventListener("keydown", this);
this.inputField.controllers.insertControllerAt(0, new CopyCutController(this));
}
@ -180,7 +181,7 @@ class UrlbarInput {
* Handles an event which would cause a url or text to be opened.
* XXX the name is currently handleCommand which is compatible with
* urlbarBindings. However, it is no longer called automatically by autocomplete,
* See _on_keyup.
* See _on_keydown.
*
* @param {Event} event The event triggering the open.
* @param {string} [openWhere] Where we expect the result to be opened.
@ -255,12 +256,12 @@ class UrlbarInput {
}
/**
* Called by the view when a result is selected.
* Called by the view when a result is picked.
*
* @param {Event} event The event that selected the result.
* @param {UrlbarMatch} result The result that was selected.
* @param {Event} event The event that picked the result.
* @param {UrlbarMatch} result The result that was picked.
*/
resultSelected(event, result) {
pickResult(event, result) {
this.setValueFromResult(result);
// TODO: Work out how we get the user selection behavior, probably via passing
@ -712,13 +713,8 @@ class UrlbarInput {
this.controller.tabContextChanged();
}
_on_keyup(event) {
// TODO: We may have an autoFill entry, so we should use that instead.
// TODO: We should have an input bufferrer so that we can use search results
// if appropriate.
if (event.key == "Enter") {
this.handleCommand(event);
}
_on_keydown(event) {
this.controller.handleKeyNavigation(event);
}
}

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

@ -16,24 +16,27 @@ XPCOMUtils.defineLazyModuleGetters(this, {
*/
class UrlbarView {
/**
* @param {UrlbarInput} urlbar
* @param {UrlbarInput} input
* The UrlbarInput instance belonging to this UrlbarView instance.
*/
constructor(urlbar) {
this.urlbar = urlbar;
this.panel = urlbar.panel;
this.controller = urlbar.controller;
this.document = urlbar.panel.ownerDocument;
constructor(input) {
this.input = input;
this.panel = input.panel;
this.controller = input.controller;
this.document = this.panel.ownerDocument;
this.window = this.document.defaultView;
this._mainContainer = this.panel.querySelector(".urlbarView-body-inner");
this._rows = this.panel.querySelector(".urlbarView-results");
this._rows.addEventListener("click", this);
// For the horizontal fade-out effect, set the overflow attribute on result
// rows when they overflow.
this._rows.addEventListener("overflow", this);
this._rows.addEventListener("underflow", this);
this.controller.setView(this);
this.controller.addQueryListener(this);
}
@ -43,6 +46,50 @@ class UrlbarView {
new this.window.SearchOneOffs(this.panel.querySelector(".search-one-offs")));
}
/**
* @returns {boolean}
* Whether the panel is open.
*/
get isOpen() {
return this.panel.state == "open" || this.panel.state == "showing";
}
/**
* Selects the next or previous view item. An item could be an autocomplete
* result or a one-off search button.
*
* @param {boolean} options.reverse
* Set to true to select the previous item. By default the next item
* will be selected.
*/
selectNextItem({reverse = false} = {}) {
if (!this.isOpen) {
this.open();
return;
}
// TODO: handle one-off search buttons
let row;
if (reverse) {
row = this._selected.previousElementSibling ||
this._rows.lastElementChild;
} else {
row = this._selected.nextElementSibling ||
this._rows.firstElementChild;
}
this._selected.toggleAttribute("selected", false);
this._selected = row;
row.toggleAttribute("selected", true);
let resultIndex = row.getAttribute("resultIndex");
let result = this._queryContext.results[resultIndex];
if (result) {
this.input.setValueFromResult(result);
}
}
/**
* Opens the autocomplete results popup.
*/
@ -55,9 +102,10 @@ class UrlbarView {
// We'll need to set them up properly.
this.oneOffSearchButtons;
this.panel.openPopup(this.urlbar.textbox.closest("toolbar"), "after_end", 0, -1);
this.panel.openPopup(this.input.textbox.closest("toolbar"), "after_end", 0, -1);
this._rows.firstElementChild.toggleAttribute("selected", true);
this._selected = this._rows.firstElementChild;
this._selected.toggleAttribute("selected", true);
}
/**
@ -111,12 +159,12 @@ class UrlbarView {
// Subtract two pixels for left and right borders on the panel.
this._mainContainer.style.maxWidth = (width - 2) + "px";
// Keep the popup items' site icons aligned with the urlbar's identity
// Keep the popup items' site icons aligned with the input's identity
// icon if it's not too far from the edge of the window. We define
// "too far" as "more than 30% of the window's width AND more than
// 250px".
let boundToCheck = this.window.RTL_UI ? "right" : "left";
let inputRect = this._getBoundsWithoutFlushing(this.urlbar.textbox);
let inputRect = this._getBoundsWithoutFlushing(this.input.textbox);
let startOffset = Math.abs(inputRect[boundToCheck] - documentRect[boundToCheck]);
let alignSiteIcons = startOffset / width <= 0.3 || startOffset <= 250;
if (alignSiteIcons) {
@ -148,7 +196,6 @@ class UrlbarView {
let result = this._queryContext.results[resultIndex];
let item = this._createElement("div");
item.className = "urlbarView-row";
item.addEventListener("click", this);
item.setAttribute("resultIndex", resultIndex);
if (result.type == UrlbarUtils.MATCH_TYPE.TAB_SWITCH) {
item.setAttribute("action", "switch-to-tab");
@ -208,7 +255,7 @@ class UrlbarView {
let resultIndex = row.getAttribute("resultIndex");
let result = this._queryContext.results[resultIndex];
if (result) {
this.urlbar.resultSelected(event, result);
this.input.pickResult(event, result);
}
this.close();
}