зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1692526
- Allow results to include a help/info button. r=dao
This supports a new `helpUrl` payload property on all results. It causes results in the view to have a help button that can be selected and picked independently of the main part of the result. When picked, the help button loads the `helpUrl`. It looks and acts the same as the help buttons we already have for tip results. The help button should be flush with the trailing edge of the result row, and it should be selectable independently from the main part of the result. To achieve that without disrupting things too much, I create the button inside of `.urlbarView-row` but outside of `.urlbarView-row-inner`. The "main" part of the row is `.urlbarView-row-inner`. I made `.urlbarView-row` have `display: flex` so the the inner part can have `flex: 1` so it can fill up the entire row except for the help button. This also reworks view selection a little so that for each row, we look for selectable elements in the row instead of assuming that the row itself is selectable. That also lets us remove a couple of special cases for tip and dynamic results. Differential Revision: https://phabricator.services.mozilla.com/D105095
This commit is contained in:
Родитель
6e0f534177
Коммит
f7cda9a0be
|
@ -206,7 +206,7 @@ add_task(async function tip_onResultPicked_helpButton_url_mouse() {
|
|||
waitForFocus,
|
||||
value: "test",
|
||||
});
|
||||
let helpButton = gURLBar.querySelector(".urlbarView-tip-help");
|
||||
let helpButton = gURLBar.querySelector(".urlbarView-help");
|
||||
Assert.ok(helpButton);
|
||||
let loadedPromise = BrowserTestUtils.browserLoaded(
|
||||
gBrowser.selectedBrowser
|
||||
|
|
|
@ -888,7 +888,7 @@ class TelemetryEvent {
|
|||
// Element handlers go here.
|
||||
if (
|
||||
row.result.type == UrlbarUtils.RESULT_TYPE.TIP &&
|
||||
element.classList.contains("urlbarView-tip-help")
|
||||
element.classList.contains("urlbarView-help")
|
||||
) {
|
||||
return "tiphelp";
|
||||
}
|
||||
|
|
|
@ -694,8 +694,13 @@ class UrlbarInput {
|
|||
return;
|
||||
}
|
||||
|
||||
let urlOverride;
|
||||
if (element?.classList.contains("urlbarView-help")) {
|
||||
urlOverride = result.payload.helpUrl;
|
||||
}
|
||||
|
||||
let originalUntrimmedValue = this.untrimmedValue;
|
||||
let isCanonized = this.setValueFromResult(result, event);
|
||||
let isCanonized = this.setValueFromResult({ result, event, urlOverride });
|
||||
let where = this._whereToOpen(event);
|
||||
let openParams = {
|
||||
allowInheritPrincipal: false,
|
||||
|
@ -719,7 +724,9 @@ class UrlbarInput {
|
|||
return;
|
||||
}
|
||||
|
||||
let { url, postData } = UrlbarUtils.getUrlFromResult(result);
|
||||
let { url, postData } = urlOverride
|
||||
? { url: urlOverride, postData: null }
|
||||
: UrlbarUtils.getUrlFromResult(result);
|
||||
openParams.postData = postData;
|
||||
|
||||
switch (result.type) {
|
||||
|
@ -850,7 +857,7 @@ class UrlbarInput {
|
|||
}
|
||||
case UrlbarUtils.RESULT_TYPE.TIP: {
|
||||
let scalarName;
|
||||
if (element.classList.contains("urlbarView-tip-help")) {
|
||||
if (element.classList.contains("urlbarView-help")) {
|
||||
url = result.payload.helpUrl;
|
||||
if (!url) {
|
||||
Cu.reportError("helpUrl not specified");
|
||||
|
@ -986,10 +993,13 @@ class UrlbarInput {
|
|||
* The result that was selected or picked, null if no result was selected.
|
||||
* @param {Event} [event]
|
||||
* The event that picked the result.
|
||||
* @param {string} [urlOverride]
|
||||
* Normally the URL is taken from `result.payload.url`, but if `urlOverride`
|
||||
* is specified, it's used instead.
|
||||
* @returns {boolean}
|
||||
* Whether the value has been canonized
|
||||
*/
|
||||
setValueFromResult(result = null, event = null) {
|
||||
setValueFromResult({ result = null, event = null, urlOverride = null } = {}) {
|
||||
// Usually this is set by a previous input event, but in certain cases, like
|
||||
// when opening Top Sites on a loaded page, it wouldn't happen. To avoid
|
||||
// confusing the user, we always enforce it when a result changes our value.
|
||||
|
@ -1074,21 +1084,25 @@ class UrlbarInput {
|
|||
// it would end up executing a search instead of visiting it.
|
||||
let allowTrim = true;
|
||||
if (
|
||||
result.type == UrlbarUtils.RESULT_TYPE.URL &&
|
||||
UrlbarPrefs.get("trimURLs") &&
|
||||
result.payload.url.startsWith(BrowserUIUtils.trimURLProtocol)
|
||||
(urlOverride || result.type == UrlbarUtils.RESULT_TYPE.URL) &&
|
||||
UrlbarPrefs.get("trimURLs")
|
||||
) {
|
||||
let fixupInfo = this._getURIFixupInfo(
|
||||
BrowserUIUtils.trimURL(result.payload.url)
|
||||
);
|
||||
if (fixupInfo?.keywordAsSent) {
|
||||
allowTrim = false;
|
||||
let url = urlOverride || result.payload.url;
|
||||
if (url.startsWith(BrowserUIUtils.trimURLProtocol)) {
|
||||
let fixupInfo = this._getURIFixupInfo(BrowserUIUtils.trimURL(url));
|
||||
if (fixupInfo?.keywordAsSent) {
|
||||
allowTrim = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!result.autofill) {
|
||||
setValueAndRestoreActionType(this._getValueFromResult(result), allowTrim);
|
||||
setValueAndRestoreActionType(
|
||||
this._getValueFromResult(result, urlOverride),
|
||||
allowTrim
|
||||
);
|
||||
}
|
||||
|
||||
this.setResultForCurrentValue(result);
|
||||
return false;
|
||||
}
|
||||
|
@ -1140,7 +1154,7 @@ class UrlbarInput {
|
|||
return;
|
||||
}
|
||||
|
||||
this.setValueFromResult(result);
|
||||
this.setValueFromResult({ result });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1878,7 +1892,7 @@ class UrlbarInput {
|
|||
return val;
|
||||
}
|
||||
|
||||
_getValueFromResult(result) {
|
||||
_getValueFromResult(result, urlOverride = null) {
|
||||
switch (result.type) {
|
||||
case UrlbarUtils.RESULT_TYPE.KEYWORD:
|
||||
return result.payload.input;
|
||||
|
@ -1895,7 +1909,7 @@ class UrlbarInput {
|
|||
}
|
||||
|
||||
try {
|
||||
let uri = Services.io.newURI(result.payload.url);
|
||||
let uri = Services.io.newURI(urlOverride || result.payload.url);
|
||||
if (uri) {
|
||||
return losslessDecodeURI(uri);
|
||||
}
|
||||
|
|
|
@ -1177,6 +1177,12 @@ UrlbarUtils.RESULT_PAYLOAD_SCHEMA = {
|
|||
displayUrl: {
|
||||
type: "string",
|
||||
},
|
||||
helpL10nId: {
|
||||
type: "string",
|
||||
},
|
||||
helpUrl: {
|
||||
type: "string",
|
||||
},
|
||||
icon: {
|
||||
type: "string",
|
||||
},
|
||||
|
|
|
@ -301,16 +301,12 @@ class UrlbarView {
|
|||
* null if there is no such element.
|
||||
*/
|
||||
getClosestSelectableElement(element) {
|
||||
let row = element.closest(".urlbarView-row");
|
||||
if (!row) {
|
||||
return null;
|
||||
}
|
||||
let closest = row;
|
||||
if (
|
||||
row.result.type == UrlbarUtils.RESULT_TYPE.TIP ||
|
||||
row.result.type == UrlbarUtils.RESULT_TYPE.DYNAMIC
|
||||
) {
|
||||
closest = element.closest(SELECTABLE_ELEMENT_SELECTOR);
|
||||
let closest = element.closest(SELECTABLE_ELEMENT_SELECTOR);
|
||||
if (!closest) {
|
||||
let row = element.closest(".urlbarView-row");
|
||||
if (row && !row.querySelector(SELECTABLE_ELEMENT_SELECTOR)) {
|
||||
closest = row;
|
||||
}
|
||||
}
|
||||
return this._isElementVisible(closest) ? closest : null;
|
||||
}
|
||||
|
@ -635,9 +631,9 @@ class UrlbarView {
|
|||
|
||||
if (!this.selectedElement && !this.oneOffSearchButtons.selectedButton) {
|
||||
if (firstResult.heuristic) {
|
||||
// Select the heuristic result. The heuristic may not be the first result
|
||||
// added, which is why we do this check here when each result is added and
|
||||
// not above.
|
||||
// Select the heuristic result. The heuristic may not be the first
|
||||
// result added, which is why we do this check here when each result is
|
||||
// added and not above.
|
||||
this._selectElement(this._getFirstSelectableElement(), {
|
||||
updateInput: false,
|
||||
setAccessibleFocus: this.controller._userSelectionBehavior == "arrow",
|
||||
|
@ -991,7 +987,7 @@ class UrlbarView {
|
|||
return item;
|
||||
}
|
||||
|
||||
_createRowContent(item) {
|
||||
_createRowContent(item, result) {
|
||||
// The url is the only element that can wrap, thus all the other elements
|
||||
// are child of noWrap.
|
||||
let noWrap = this._createElement("span");
|
||||
|
@ -1049,6 +1045,22 @@ class UrlbarView {
|
|||
url.className = "urlbarView-url";
|
||||
item._content.appendChild(url);
|
||||
item._elements.set("url", url);
|
||||
|
||||
// Usually we create all child elements for the row regardless of whether
|
||||
// the specific result will use them, but we don't expect the vast majority
|
||||
// of results to have help URLs, so as an optimization, only create the help
|
||||
// button if the result will use it.
|
||||
if (result.payload.helpUrl) {
|
||||
let helpButton = this._createElement("span");
|
||||
helpButton.className = "urlbarView-help";
|
||||
helpButton.setAttribute("role", "button");
|
||||
if (result.payload.helpL10nId) {
|
||||
helpButton.setAttribute("data-l10n-id", result.payload.helpL10nId);
|
||||
}
|
||||
item.appendChild(helpButton);
|
||||
item._elements.set("helpButton", helpButton);
|
||||
item._content.setAttribute("selectable", "true");
|
||||
}
|
||||
}
|
||||
|
||||
_createRowContentForTip(item) {
|
||||
|
@ -1079,7 +1091,7 @@ class UrlbarView {
|
|||
item._elements.set("tipButton", tipButton);
|
||||
|
||||
let helpIcon = this._createElement("span");
|
||||
helpIcon.className = "urlbarView-tip-help";
|
||||
helpIcon.className = "urlbarView-help";
|
||||
helpIcon.setAttribute("role", "button");
|
||||
helpIcon.setAttribute("data-l10n-id", "urlbar-tip-help-icon");
|
||||
item._elements.set("helpButton", helpIcon);
|
||||
|
@ -1150,13 +1162,14 @@ class UrlbarView {
|
|||
(result.type == UrlbarUtils.RESULT_TYPE.DYNAMIC) ||
|
||||
(oldResultType == UrlbarUtils.RESULT_TYPE.DYNAMIC &&
|
||||
result.type == UrlbarUtils.RESULT_TYPE.DYNAMIC &&
|
||||
oldResult.dynamicType != result.dynamicType);
|
||||
oldResult.dynamicType != result.dynamicType) ||
|
||||
!!result.payload.helpUrl != item._elements.has("helpButton");
|
||||
|
||||
if (needsNewContent) {
|
||||
if (item._content) {
|
||||
item._content.remove();
|
||||
item._elements.clear();
|
||||
while (item.lastChild) {
|
||||
item.lastChild.remove();
|
||||
}
|
||||
item._elements.clear();
|
||||
item._content = this._createElement("span");
|
||||
item._content.className = "urlbarView-row-inner";
|
||||
item.appendChild(item._content);
|
||||
|
@ -1166,7 +1179,7 @@ class UrlbarView {
|
|||
} else if (item.result.type == UrlbarUtils.RESULT_TYPE.DYNAMIC) {
|
||||
this._createRowContentForDynamicType(item, result);
|
||||
} else {
|
||||
this._createRowContent(item);
|
||||
this._createRowContent(item, result);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1389,6 +1402,12 @@ class UrlbarView {
|
|||
} else {
|
||||
title.removeAttribute("dir");
|
||||
}
|
||||
|
||||
if (item._elements.has("helpButton")) {
|
||||
item.setAttribute("has-help", "true");
|
||||
} else {
|
||||
item.removeAttribute("has-help");
|
||||
}
|
||||
}
|
||||
|
||||
_iconForResult(result, iconUrlOverride = null) {
|
||||
|
@ -1593,28 +1612,36 @@ class UrlbarView {
|
|||
}
|
||||
}
|
||||
|
||||
_selectElement(item, { updateInput = true, setAccessibleFocus = true } = {}) {
|
||||
_selectElement(
|
||||
element,
|
||||
{ updateInput = true, setAccessibleFocus = true } = {}
|
||||
) {
|
||||
if (this._selectedElement) {
|
||||
this._selectedElement.toggleAttribute("selected", false);
|
||||
this._selectedElement.removeAttribute("aria-selected");
|
||||
}
|
||||
if (item) {
|
||||
item.toggleAttribute("selected", true);
|
||||
item.setAttribute("aria-selected", "true");
|
||||
if (element) {
|
||||
element.toggleAttribute("selected", true);
|
||||
element.setAttribute("aria-selected", "true");
|
||||
}
|
||||
this._setAccessibleFocus(setAccessibleFocus && item);
|
||||
this._selectedElement = item;
|
||||
this._setAccessibleFocus(setAccessibleFocus && element);
|
||||
this._selectedElement = element;
|
||||
|
||||
let result = item?.closest(".urlbarView-row")?.result;
|
||||
let result = element?.closest(".urlbarView-row")?.result;
|
||||
if (updateInput) {
|
||||
this.input.setValueFromResult(result);
|
||||
this.input.setValueFromResult({
|
||||
result,
|
||||
urlOverride: element?.classList?.contains("urlbarView-help")
|
||||
? result.payload.helpUrl
|
||||
: null,
|
||||
});
|
||||
} else {
|
||||
this.input.setResultForCurrentValue(result);
|
||||
}
|
||||
|
||||
let provider = UrlbarProvidersManager.getProvider(result?.providerName);
|
||||
if (provider) {
|
||||
provider.tryMethod("onSelection", result, item);
|
||||
provider.tryMethod("onSelection", result, element);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1651,7 +1678,15 @@ class UrlbarView {
|
|||
* The last selectable element in the view.
|
||||
*/
|
||||
_getLastSelectableElement() {
|
||||
let element = this._rows.lastElementChild;
|
||||
let row = this._rows.lastElementChild;
|
||||
if (!row) {
|
||||
return null;
|
||||
}
|
||||
let selectables = row.querySelectorAll(SELECTABLE_ELEMENT_SELECTOR);
|
||||
let element = selectables.length
|
||||
? selectables[selectables.length - 1]
|
||||
: row;
|
||||
|
||||
if (element && !this._isSelectableElement(element)) {
|
||||
element = this._getPreviousSelectableElement(element);
|
||||
}
|
||||
|
@ -1674,20 +1709,13 @@ class UrlbarView {
|
|||
return null;
|
||||
}
|
||||
|
||||
let next;
|
||||
if (
|
||||
row.result.type == UrlbarUtils.RESULT_TYPE.TIP ||
|
||||
row.result.type == UrlbarUtils.RESULT_TYPE.DYNAMIC
|
||||
) {
|
||||
let selectables = [...row.querySelectorAll(SELECTABLE_ELEMENT_SELECTOR)];
|
||||
let next = row.nextElementSibling;
|
||||
let selectables = [...row.querySelectorAll(SELECTABLE_ELEMENT_SELECTOR)];
|
||||
if (selectables.length) {
|
||||
let index = selectables.indexOf(element);
|
||||
if (index == selectables.length - 1) {
|
||||
next = row.nextElementSibling;
|
||||
} else {
|
||||
if (index < selectables.length - 1) {
|
||||
next = selectables[index + 1];
|
||||
}
|
||||
} else {
|
||||
next = row.nextElementSibling;
|
||||
}
|
||||
|
||||
if (next && !this._isSelectableElement(next)) {
|
||||
|
@ -1713,22 +1741,15 @@ class UrlbarView {
|
|||
return null;
|
||||
}
|
||||
|
||||
let previous;
|
||||
if (
|
||||
row.result.type == UrlbarUtils.RESULT_TYPE.TIP ||
|
||||
row.result.type == UrlbarUtils.RESULT_TYPE.DYNAMIC
|
||||
) {
|
||||
let selectables = [...row.querySelectorAll(SELECTABLE_ELEMENT_SELECTOR)];
|
||||
let previous = row.previousElementSibling;
|
||||
let selectables = [...row.querySelectorAll(SELECTABLE_ELEMENT_SELECTOR)];
|
||||
if (selectables.length) {
|
||||
let index = selectables.indexOf(element);
|
||||
if (index == 0 || !selectables.length) {
|
||||
previous = row.previousElementSibling;
|
||||
} else if (index < 0) {
|
||||
if (index < 0) {
|
||||
previous = selectables[selectables.length - 1];
|
||||
} else {
|
||||
} else if (index > 0) {
|
||||
previous = selectables[index - 1];
|
||||
}
|
||||
} else {
|
||||
previous = row.previousElementSibling;
|
||||
}
|
||||
|
||||
if (previous && !this._isSelectableElement(previous)) {
|
||||
|
|
|
@ -72,7 +72,7 @@ add_task(async function tipIsSecondResult() {
|
|||
EventUtils.synthesizeKey("KEY_ArrowDown");
|
||||
Assert.ok(
|
||||
UrlbarTestUtils.getSelectedElement(window).classList.contains(
|
||||
"urlbarView-tip-help"
|
||||
"urlbarView-help"
|
||||
),
|
||||
"The selected element should be the tip help button."
|
||||
);
|
||||
|
@ -112,7 +112,7 @@ add_task(async function tipIsSecondResult() {
|
|||
EventUtils.synthesizeKey("KEY_ArrowUp");
|
||||
Assert.ok(
|
||||
UrlbarTestUtils.getSelectedElement(window).classList.contains(
|
||||
"urlbarView-tip-help"
|
||||
"urlbarView-help"
|
||||
),
|
||||
"The selected element should be the tip help button."
|
||||
);
|
||||
|
@ -173,7 +173,7 @@ add_task(async function tipIsOnlyResult() {
|
|||
EventUtils.synthesizeKey("KEY_ArrowDown");
|
||||
Assert.ok(
|
||||
UrlbarTestUtils.getSelectedElement(window).classList.contains(
|
||||
"urlbarView-tip-help"
|
||||
"urlbarView-help"
|
||||
),
|
||||
"The selected element should be the tip help button."
|
||||
);
|
||||
|
@ -193,7 +193,7 @@ add_task(async function tipIsOnlyResult() {
|
|||
EventUtils.synthesizeKey("KEY_ArrowUp");
|
||||
Assert.ok(
|
||||
UrlbarTestUtils.getSelectedElement(window).classList.contains(
|
||||
"urlbarView-tip-help"
|
||||
"urlbarView-help"
|
||||
),
|
||||
"The selected element should be the tip help button."
|
||||
);
|
||||
|
|
|
@ -71,6 +71,7 @@ support-files =
|
|||
[browser_focusedCmdK.js]
|
||||
[browser_handleCommand_fallback.js]
|
||||
[browser_hashChangeProxyState.js]
|
||||
[browser_helpUrl.js]
|
||||
[browser_heuristicNotAddedFirst.js]
|
||||
[browser_ime_composition.js]
|
||||
[browser_inputHistory.js]
|
||||
|
|
|
@ -35,7 +35,7 @@ add_task(function losslessDecode() {
|
|||
UrlbarUtils.RESULT_SOURCE.TABS,
|
||||
{ url }
|
||||
);
|
||||
gURLBar.setValueFromResult(result);
|
||||
gURLBar.setValueFromResult({ result });
|
||||
// Since this is directly setting textValue, it is expected to be trimmed.
|
||||
Assert.equal(
|
||||
gURLBar.inputField.value,
|
||||
|
|
|
@ -0,0 +1,343 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Tests the help/info button that appears for results whose payloads have a
|
||||
// `helpUrl` property.
|
||||
|
||||
"use strict";
|
||||
|
||||
const MAX_RESULTS = UrlbarPrefs.get("maxRichResults");
|
||||
const RESULT_URL = "http://example.com/test";
|
||||
const RESULT_HELP_URL = "http://example.com/help";
|
||||
|
||||
add_task(async function init() {
|
||||
// Add enough results to fill up the view.
|
||||
await PlacesUtils.history.clear();
|
||||
for (let i = 0; i < MAX_RESULTS; i++) {
|
||||
await PlacesTestUtils.addVisits("http://example.com/" + i);
|
||||
}
|
||||
registerCleanupFunction(async () => {
|
||||
await PlacesUtils.history.clear();
|
||||
});
|
||||
});
|
||||
|
||||
// Arrows up and down through a result with a help button. The result is the
|
||||
// second result and has other results after it.
|
||||
add_task(async function keyboardSelection_secondResult() {
|
||||
let provider = registerTestProvider(1);
|
||||
await UrlbarTestUtils.promiseAutocompleteResultPopup({
|
||||
value: "example",
|
||||
window,
|
||||
});
|
||||
|
||||
// Sanity-check initial state.
|
||||
Assert.equal(
|
||||
UrlbarTestUtils.getResultCount(window),
|
||||
MAX_RESULTS,
|
||||
"There should be MAX_RESULTS results in the view"
|
||||
);
|
||||
Assert.equal(
|
||||
UrlbarTestUtils.getSelectedElementIndex(window),
|
||||
0,
|
||||
"The heuristic result should be selected"
|
||||
);
|
||||
await assertIsTestResult(1);
|
||||
|
||||
// Arrow down to the main part of the result.
|
||||
EventUtils.synthesizeKey("KEY_ArrowDown");
|
||||
assertMainPartSelected(1);
|
||||
|
||||
// Arrow down to the help button.
|
||||
EventUtils.synthesizeKey("KEY_ArrowDown");
|
||||
assertHelpButtonSelected(2);
|
||||
|
||||
// Arrow down to the next (third) result.
|
||||
EventUtils.synthesizeKey("KEY_ArrowDown");
|
||||
assertOtherResultSelected(3, "next result");
|
||||
|
||||
// Arrow up to the help button.
|
||||
EventUtils.synthesizeKey("KEY_ArrowUp");
|
||||
assertHelpButtonSelected(2);
|
||||
|
||||
// Arrow up to the main part of the result.
|
||||
EventUtils.synthesizeKey("KEY_ArrowUp");
|
||||
assertMainPartSelected(1);
|
||||
|
||||
// Arrow up to the previous (first) result.
|
||||
EventUtils.synthesizeKey("KEY_ArrowUp");
|
||||
assertOtherResultSelected(0, "previous result");
|
||||
|
||||
await UrlbarTestUtils.promisePopupClose(window);
|
||||
UrlbarProvidersManager.unregisterProvider(provider);
|
||||
|
||||
gURLBar.view.close();
|
||||
UrlbarProvidersManager.unregisterProvider(provider);
|
||||
});
|
||||
|
||||
// Arrows up and down through a result with a help button. The result is the
|
||||
// last result.
|
||||
add_task(async function keyboardSelection_lastResult() {
|
||||
let provider = registerTestProvider(MAX_RESULTS - 1);
|
||||
await UrlbarTestUtils.promiseAutocompleteResultPopup({
|
||||
value: "example",
|
||||
window,
|
||||
});
|
||||
|
||||
// Sanity-check initial state.
|
||||
Assert.equal(
|
||||
UrlbarTestUtils.getResultCount(window),
|
||||
MAX_RESULTS,
|
||||
"There should be MAX_RESULTS results in the view"
|
||||
);
|
||||
Assert.equal(
|
||||
UrlbarTestUtils.getSelectedElementIndex(window),
|
||||
0,
|
||||
"The heuristic result should be selected"
|
||||
);
|
||||
await assertIsTestResult(MAX_RESULTS - 1);
|
||||
|
||||
// Arrow down to the main part of the result.
|
||||
EventUtils.synthesizeKey("KEY_ArrowDown", { repeat: MAX_RESULTS - 1 });
|
||||
assertMainPartSelected(MAX_RESULTS - 1);
|
||||
|
||||
// Arrow down to the help button.
|
||||
EventUtils.synthesizeKey("KEY_ArrowDown");
|
||||
assertHelpButtonSelected(MAX_RESULTS);
|
||||
|
||||
// Arrow down to the first one-off. If this test is running alone, the
|
||||
// one-offs will rebuild themselves when the view is opened above, and they
|
||||
// may not be visible yet. Wait for the first one to become visible before
|
||||
// trying to select it.
|
||||
await TestUtils.waitForCondition(() => {
|
||||
return (
|
||||
gURLBar.view.oneOffSearchButtons.buttons.firstElementChild &&
|
||||
BrowserTestUtils.is_visible(
|
||||
gURLBar.view.oneOffSearchButtons.buttons.firstElementChild
|
||||
)
|
||||
);
|
||||
}, "Waiting for first one-off to become visible.");
|
||||
|
||||
EventUtils.synthesizeKey("KEY_ArrowDown");
|
||||
await TestUtils.waitForCondition(() => {
|
||||
return gURLBar.view.oneOffSearchButtons.selectedButton;
|
||||
}, "Waiting for one-off to become selected.");
|
||||
Assert.equal(
|
||||
UrlbarTestUtils.getSelectedElementIndex(window),
|
||||
-1,
|
||||
"No results should be selected."
|
||||
);
|
||||
|
||||
// Arrow up to the help button.
|
||||
EventUtils.synthesizeKey("KEY_ArrowUp");
|
||||
assertHelpButtonSelected(MAX_RESULTS);
|
||||
|
||||
// Arrow up to the main part of the result.
|
||||
EventUtils.synthesizeKey("KEY_ArrowUp");
|
||||
assertMainPartSelected(MAX_RESULTS - 1);
|
||||
|
||||
// Arrow up to the previous result.
|
||||
EventUtils.synthesizeKey("KEY_ArrowUp");
|
||||
assertOtherResultSelected(MAX_RESULTS - 2, "previous result");
|
||||
|
||||
await UrlbarTestUtils.promisePopupClose(window);
|
||||
UrlbarProvidersManager.unregisterProvider(provider);
|
||||
});
|
||||
|
||||
// Picks the main part of the test result -- the non-help-button part -- with
|
||||
// the keyboard.
|
||||
add_task(async function pick_mainPart_keyboard() {
|
||||
await doPickTest({ pickHelpButton: false, useKeyboard: true });
|
||||
});
|
||||
|
||||
// Picks the help button with the keyboard.
|
||||
add_task(async function pick_helpButton_keyboard() {
|
||||
await doPickTest({ pickHelpButton: true, useKeyboard: true });
|
||||
});
|
||||
|
||||
// Picks the main part of the test result -- the non-help-button part -- with
|
||||
// the mouse.
|
||||
add_task(async function pick_mainPart_mouse() {
|
||||
await doPickTest({ pickHelpButton: false, useKeyboard: false });
|
||||
});
|
||||
|
||||
// Picks the help button with the mouse.
|
||||
add_task(async function pick_helpButton_mouse() {
|
||||
await doPickTest({ pickHelpButton: true, useKeyboard: false });
|
||||
});
|
||||
|
||||
async function doPickTest({ pickHelpButton, useKeyboard }) {
|
||||
await BrowserTestUtils.withNewTab("about:blank", async () => {
|
||||
let index = 1;
|
||||
let provider = registerTestProvider(index);
|
||||
await UrlbarTestUtils.promiseAutocompleteResultPopup({
|
||||
value: "example",
|
||||
window,
|
||||
});
|
||||
|
||||
// Sanity-check initial state.
|
||||
Assert.equal(
|
||||
UrlbarTestUtils.getSelectedElementIndex(window),
|
||||
0,
|
||||
"The heuristic result should be selected"
|
||||
);
|
||||
await assertIsTestResult(1);
|
||||
|
||||
let clickTarget;
|
||||
if (useKeyboard) {
|
||||
// Arrow down to the result.
|
||||
if (pickHelpButton) {
|
||||
EventUtils.synthesizeKey("KEY_ArrowDown", { repeat: index + 1 });
|
||||
assertHelpButtonSelected(index + 1);
|
||||
} else {
|
||||
EventUtils.synthesizeKey("KEY_ArrowDown", { repeat: index });
|
||||
assertMainPartSelected(index);
|
||||
}
|
||||
} else {
|
||||
// Get the click target.
|
||||
let result = await UrlbarTestUtils.getDetailsOfResultAt(window, index);
|
||||
clickTarget = pickHelpButton
|
||||
? result.element.row._elements.get("helpButton")
|
||||
: result.element.row._content;
|
||||
Assert.ok(
|
||||
clickTarget,
|
||||
"Click target found, pickHelpButton=" + pickHelpButton
|
||||
);
|
||||
}
|
||||
|
||||
// Pick the result. The appropriate URL should load.
|
||||
await Promise.all([
|
||||
BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser),
|
||||
UrlbarTestUtils.promisePopupClose(window, () => {
|
||||
if (useKeyboard) {
|
||||
EventUtils.synthesizeKey("KEY_Enter");
|
||||
} else {
|
||||
EventUtils.synthesizeMouseAtCenter(clickTarget, {});
|
||||
}
|
||||
}),
|
||||
]);
|
||||
Assert.equal(
|
||||
gBrowser.selectedBrowser.currentURI.spec,
|
||||
pickHelpButton ? RESULT_HELP_URL : RESULT_URL,
|
||||
"Expected URL should have loaded"
|
||||
);
|
||||
|
||||
UrlbarProvidersManager.unregisterProvider(provider);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a provider that creates a result with a help button.
|
||||
*
|
||||
* @param {number} suggestedIndex
|
||||
* The result's suggestedIndex.
|
||||
* @returns {UrlbarProvider}
|
||||
* The new provider.
|
||||
*/
|
||||
function registerTestProvider(suggestedIndex) {
|
||||
let results = [
|
||||
Object.assign(
|
||||
new UrlbarResult(
|
||||
UrlbarUtils.RESULT_TYPE.URL,
|
||||
UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
|
||||
{
|
||||
url: RESULT_URL,
|
||||
helpUrl: RESULT_HELP_URL,
|
||||
}
|
||||
),
|
||||
{ suggestedIndex }
|
||||
),
|
||||
];
|
||||
let provider = new UrlbarTestUtils.TestProvider({ results });
|
||||
UrlbarProvidersManager.registerProvider(provider);
|
||||
return provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the result at the given index is our test result with a help
|
||||
* button.
|
||||
*
|
||||
* @param {number} index
|
||||
* The expected index of the test result.
|
||||
*/
|
||||
async function assertIsTestResult(index) {
|
||||
let result = await UrlbarTestUtils.getDetailsOfResultAt(window, index);
|
||||
Assert.equal(
|
||||
result.type,
|
||||
UrlbarUtils.RESULT_TYPE.URL,
|
||||
"The second result should be a URL"
|
||||
);
|
||||
Assert.equal(
|
||||
result.url,
|
||||
RESULT_URL,
|
||||
"The result's URL should be the expected URL"
|
||||
);
|
||||
Assert.ok(
|
||||
result.element.row._elements.get("helpButton"),
|
||||
"The result should have a help button"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a particular element is selected.
|
||||
*
|
||||
* @param {number} expectedSelectedElementIndex
|
||||
* The expected selected element index.
|
||||
* @param {string} expectedClassName
|
||||
* A class name of the expected selected element.
|
||||
* @param {string} msg
|
||||
* A string to include in the assertion message.
|
||||
*/
|
||||
function assertSelection(expectedSelectedElementIndex, expectedClassName, msg) {
|
||||
Assert.equal(
|
||||
UrlbarTestUtils.getSelectedElementIndex(window),
|
||||
expectedSelectedElementIndex,
|
||||
"Expected selected element index: " + msg
|
||||
);
|
||||
Assert.ok(
|
||||
UrlbarTestUtils.getSelectedElement(window).classList.contains(
|
||||
expectedClassName
|
||||
),
|
||||
"Expected selected element: " + msg
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the main part of our test resut -- the non-help-button part --
|
||||
* is selected.
|
||||
*
|
||||
* @param {number} expectedSelectedElementIndex
|
||||
* The expected selected element index.
|
||||
*/
|
||||
function assertMainPartSelected(expectedSelectedElementIndex) {
|
||||
assertSelection(
|
||||
expectedSelectedElementIndex,
|
||||
"urlbarView-row-inner",
|
||||
"main part of test result"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the help button part of our test resut is selected.
|
||||
*
|
||||
* @param {number} expectedSelectedElementIndex
|
||||
* The expected selected element index.
|
||||
*/
|
||||
function assertHelpButtonSelected(expectedSelectedElementIndex) {
|
||||
assertSelection(
|
||||
expectedSelectedElementIndex,
|
||||
"urlbarView-help",
|
||||
"help button"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a result other than our test result is selected.
|
||||
*
|
||||
* @param {number} expectedSelectedElementIndex
|
||||
* The expected selected element index.
|
||||
* @param {string} msg
|
||||
* A string to include in the assertion message.
|
||||
*/
|
||||
function assertOtherResultSelected(expectedSelectedElementIndex, msg) {
|
||||
assertSelection(expectedSelectedElementIndex, "urlbarView-row", msg);
|
||||
}
|
|
@ -88,12 +88,19 @@
|
|||
}
|
||||
|
||||
.urlbarView-row {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
fill: currentColor;
|
||||
fill-opacity: .6;
|
||||
padding-block: 3px;
|
||||
}
|
||||
|
||||
.urlbarView-row-inner {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
overflow: hidden;
|
||||
border-radius: 2px;
|
||||
padding-inline-start: @urlbarViewItemInlinePadding@;
|
||||
padding-block: 6px;
|
||||
|
@ -107,8 +114,6 @@
|
|||
padding-block-start: 18px;
|
||||
/* Compensating for the 16px bottom margin on the tip elements. */
|
||||
padding-block-end: calc(18px - 16px);
|
||||
/* Compensating for the 4px focus ring on tip buttons. */
|
||||
padding-inline-end: calc(@urlbarViewItemInlinePadding@ + 4px);
|
||||
}
|
||||
|
||||
.urlbarView-row-inner,
|
||||
|
@ -119,6 +124,16 @@
|
|||
justify-content: start;
|
||||
}
|
||||
|
||||
.urlbarView-no-wrap {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.urlbarView-url {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
.urlbarView[actionoverride] .urlbarView-row[has-url] > .urlbarView-row-inner > .urlbarView-no-wrap,
|
||||
.urlbarView-row[has-url]:not([type$=tab]) > .urlbarView-row-inner > .urlbarView-no-wrap,
|
||||
.urlbarView-row[has-url]:is([type=remotetab], [sponsored]):is(:hover, [selected]) > .urlbarView-row-inner > .urlbarView-no-wrap {
|
||||
|
@ -186,11 +201,13 @@
|
|||
direction: ltr !important;
|
||||
}
|
||||
|
||||
.urlbarView-row:not([type=tip], [type=dynamic]):hover > .urlbarView-row-inner {
|
||||
.urlbarView-row:not([type=tip], [type=dynamic], [has-help]):hover > .urlbarView-row-inner,
|
||||
.urlbarView-row[has-help] > .urlbarView-row-inner:hover {
|
||||
background: var(--arrowpanel-dimmed);
|
||||
}
|
||||
|
||||
.urlbarView-row:not([type=tip], [type=dynamic])[selected] > .urlbarView-row-inner {
|
||||
.urlbarView-row:not([type=tip], [type=dynamic])[selected] > .urlbarView-row-inner,
|
||||
.urlbarView-row-inner[selected] {
|
||||
background: var(--autocomplete-popup-highlight-background);
|
||||
color: var(--autocomplete-popup-highlight-color);
|
||||
fill-opacity: 1;
|
||||
|
@ -290,6 +307,36 @@
|
|||
stroke: #38383d; /* Grey-70 */
|
||||
}
|
||||
|
||||
/* Help button */
|
||||
|
||||
/* Help buttons are a standard Photon ghost buttons. */
|
||||
.urlbarView-help {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
flex-basis: 16px;
|
||||
min-width: 16px;
|
||||
background-image: url("chrome://global/skin/icons/help.svg");
|
||||
background-size: 16px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
padding-inline: 8px;
|
||||
margin-inline-start: 8px;
|
||||
margin-inline-end: 4px;
|
||||
-moz-context-properties: fill, fill-opacity;
|
||||
}
|
||||
|
||||
.urlbarView-help[selected] {
|
||||
box-shadow: 0 0 0 1px #0a84ff inset, 0 0 0 1px #0a84ff, 0 0 0 4px rgba(10, 132, 255, 0.3);
|
||||
}
|
||||
|
||||
.urlbarView-help:hover {
|
||||
background-color: var(--urlbarView-button-background-hover);
|
||||
}
|
||||
|
||||
.urlbarView-help:hover:active {
|
||||
background-color: var(--urlbarView-button-background-active);
|
||||
}
|
||||
|
||||
/* Tip rows */
|
||||
|
||||
.urlbarView-row[type=tip]:not(:last-child) {
|
||||
|
@ -315,34 +362,13 @@
|
|||
they all remain vertically aligned. */
|
||||
.urlbarView-row[type=tip] > .urlbarView-row-inner > .urlbarView-favicon,
|
||||
.urlbarView-row[type=tip] > .urlbarView-row-inner > .urlbarView-title,
|
||||
.urlbarView-tip-button,
|
||||
.urlbarView-tip-help {
|
||||
.urlbarView-row[type=tip] > .urlbarView-row-inner > .urlbarView-help,
|
||||
.urlbarView-tip-button {
|
||||
margin-block-end: 16px;
|
||||
}
|
||||
|
||||
/* The help icon is a standard Photon ghost button. */
|
||||
.urlbarView-tip-help {
|
||||
min-width: 16px;
|
||||
.urlbarView-row[type=tip] > .urlbarView-row-inner > .urlbarView-help {
|
||||
height: 32px;
|
||||
background-image: url("chrome://global/skin/icons/help.svg");
|
||||
background-size: 16px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
padding-inline: 8px;
|
||||
margin-inline-start: 8px;
|
||||
-moz-context-properties: fill, fill-opacity;
|
||||
}
|
||||
|
||||
.urlbarView-tip-help[selected] {
|
||||
box-shadow: 0 0 0 1px #0a84ff inset, 0 0 0 1px #0a84ff, 0 0 0 4px rgba(10, 132, 255, 0.3);
|
||||
}
|
||||
|
||||
.urlbarView-tip-help:hover {
|
||||
background-color: var(--urlbarView-button-background-hover);
|
||||
}
|
||||
|
||||
.urlbarView-tip-help:hover:active {
|
||||
background-color: var(--urlbarView-button-background-active);
|
||||
}
|
||||
|
||||
/* The tip button is a Photon default button when unfocused and a
|
||||
|
@ -485,7 +511,9 @@
|
|||
}
|
||||
|
||||
.urlbarView-row[selected] > .urlbarView-row-inner > .urlbarView-no-wrap >.urlbarView-action,
|
||||
.urlbarView-row[selected] > .urlbarView-row-inner > .urlbarView-url {
|
||||
.urlbarView-row[selected] > .urlbarView-row-inner > .urlbarView-url,
|
||||
.urlbarView-row-inner[selected] > .urlbarView-no-wrap >.urlbarView-action,
|
||||
.urlbarView-row-inner[selected] > .urlbarView-url {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче