Bug 1524718 - Replace context.autofillValue with result.autofill, and autofill results when they're selected. r=mak

We should replace the context.autofillValue property with a result.autofill property. When the view selects results, it already notifies the input about it by calling input.setValueFromResult(). So we can modify setValueFromResult to check for the presence of result.autofill and thereby get autofill "for free".

result.autofill is an object: { value, selectionStart, selectionEnd }

This is going to help me implement bug 1521702.

One potentially cool thing about doing autofill this way is that any result can now trigger autofill, not only the heuristic result, and do it easily. Of course the user isn't typing when they select a non-heuristic result, so it's probably not fair to call that "autofill", but the result can trigger the selection aspect of autofill. As one example, that might be interesting for search suggestions: Type "foo", key down to the "foobar" suggestion, and the "bar" substring is automatically selected.

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Drew Willcoxon 2019-02-07 00:30:04 +00:00
parent e095de11d8
commit ee7630f333
8 changed files with 72 additions and 118 deletions

View File

@ -136,8 +136,8 @@ class UrlbarController {
}
if (queryContext.lastResultCount == 0) {
if (queryContext.autofillValue) {
this.input.autofill(queryContext.autofillValue);
if (queryContext.results.length && queryContext.results[0].autofill) {
this.input.setValueFromResult(queryContext.results[0]);
}
// The first time we receive results try to connect to the heuristic
// result.
@ -273,7 +273,7 @@ class UrlbarController {
switch (reason) {
case "resultsadded": {
// We should connect to an heuristic result, if it exists.
if (resultIndex == 0 && (context.preselected || context.autofillValue)) {
if ((resultIndex == 0 && context.preselected) || result.autofill) {
if (result.type == UrlbarUtils.RESULT_TYPE.SEARCH) {
// Speculative connect only if search suggestions are enabled.
if (UrlbarPrefs.get("suggest.searches") &&
@ -281,7 +281,7 @@ class UrlbarController {
let engine = Services.search.defaultEngine;
UrlbarUtils.setupSpeculativeConnection(engine, this.browserWindow);
}
} else if (context.autofillValue) {
} else if (result.autofill) {
UrlbarUtils.setupSpeculativeConnection(url, this.browserWindow);
}
}

View File

@ -368,29 +368,14 @@ class UrlbarInput {
* @param {UrlbarResult} result The result that was selected.
*/
setValueFromResult(result) {
let val;
switch (result.type) {
case UrlbarUtils.RESULT_TYPE.SEARCH:
val = result.payload.suggestion || result.payload.query;
break;
case UrlbarUtils.RESULT_TYPE.OMNIBOX:
val = result.payload.content;
break;
default: {
val = result.payload.url;
let uri;
try {
uri = Services.io.newURI(val);
} catch (ex) {}
if (uri) {
val = this.window.losslessDecodeURI(uri);
}
break;
}
if (result.autofill) {
this._setValueFromResultAutofill(result);
} else {
this.value = this._valueFromResultPayload(result);
}
this.value = val;
// Also update userTypedValue. See bug 287996.
this.window.gBrowser.userTypedValue = val;
this.window.gBrowser.userTypedValue = this.value;
// The value setter clobbers the actiontype attribute, so update this after that.
switch (result.type) {
@ -483,27 +468,6 @@ class UrlbarInput {
this.textbox.classList.remove("hidden-focus");
}
/**
* Autofills the given value into the input. That is, sets the input's value
* to the given value and selects the portion of the new value that comes
* after the current value. The given value should therefore start with the
* input's current value. If it doesn't, then this method doesn't do
* anything.
*
* @param {string} value
* The value to autofill.
*/
autofill(value) {
if (!value.toLocaleLowerCase()
.startsWith(this.textValue.toLocaleLowerCase())) {
return;
}
let len = this.textValue.length;
this.value = this.textValue + value.substring(len);
this.selectionStart = len;
this.selectionEnd = value.length;
}
// Getters and Setters below.
get focused() {
@ -548,6 +512,30 @@ class UrlbarInput {
// Private methods below.
_setValueFromResultAutofill(result) {
this.value = result.autofill.value;
this.selectionStart = result.autofill.selectionStart;
this.selectionEnd = result.autofill.selectionEnd;
}
_valueFromResultPayload(result) {
switch (result.type) {
case UrlbarUtils.RESULT_TYPE.SEARCH:
return result.payload.suggestion || result.payload.query;
case UrlbarUtils.RESULT_TYPE.OMNIBOX:
return result.payload.content;
}
try {
let uri = Services.io.newURI(result.payload.url);
if (uri) {
return this.window.losslessDecodeURI(uri);
}
} catch (ex) {}
return "";
}
_updateTextOverflow() {
if (!this._overflowing) {
this.removeAttribute("textoverflow");

View File

@ -197,10 +197,19 @@ function convertResultToMatches(context, result, urls) {
if (!match) {
continue;
}
// Manage autofillValue and preselected properties for the first match.
// Manage autofill and preselected properties for the first match.
if (i == 0) {
if (style.includes("autofill") && result.defaultIndex == 0) {
context.autofillValue = result.getValueAt(i);
let autofillValue = result.getValueAt(i);
if (autofillValue.toLocaleLowerCase()
.startsWith(context.searchString.toLocaleLowerCase())) {
match.autofill = {
value: context.searchString +
autofillValue.substring(context.searchString.length),
selectionStart: context.searchString.length,
selectionEnd: autofillValue.length,
};
}
}
if (style.includes("heuristic")) {
context.preselected = true;

View File

@ -337,7 +337,7 @@ class UrlbarAbstraction {
let context = await this.urlbar.lastQueryContextPromise;
details.url = (UrlbarUtils.getUrlFromResult(context.results[index])).url;
details.type = context.results[index].type;
details.autofill = index == 0 && context.autofillValue;
details.autofill = index == 0 && context.results[index].autofill;
details.image = element.getElementsByClassName("urlbarView-favicon")[0].src;
details.title = context.results[index].title;
let actions = element.getElementsByClassName("urlbarView-action");

View File

@ -2,34 +2,24 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests the UrlbarInput.autofill() method. It needs to be tested in the actual
* browser because UrlbarInput relies on the trimURL() function defined in the
* browser window's global scope, and this test hits the path that calls it.
* Tests the autofill functionality of UrlbarInput.
*/
"use strict";
add_task(async function test() {
let tests = [
// [initial value, autofill value, expected selected substring in new value]
["foo", "foobar", "bar"],
["FOO", "foobar", "bar"],
["fOo", "foobar", "bar"],
["foo", "quuxbar", ""],
["FOO", "quuxbar", ""],
["fOo", "quuxbar", ""],
];
gURLBar.focus();
for (let [initial, autofill, expectedSelected] of tests) {
gURLBar.value = initial;
gURLBar.autofill(autofill);
let expectedValue = initial + expectedSelected;
Assert.equal(gURLBar.value, expectedValue,
"The input value should be correct");
Assert.equal(gURLBar.selectionStart, initial.length,
"The start of the selection should be correct");
Assert.equal(gURLBar.selectionEnd, expectedValue.length,
"The end of the selection should be correct");
}
gURLBar.setValueFromResult({
autofill: {
value: "foobar",
selectionStart: "foo".length,
selectionEnd: "foobar".length,
},
type: UrlbarUtils.RESULT_TYPE.URL,
});
Assert.equal(gURLBar.value, "foobar",
"The input value should be correct");
Assert.equal(gURLBar.selectionStart, "foo".length,
"The start of the selection should be correct");
Assert.equal(gURLBar.selectionEnd, "foobar".length,
"The end of the selection should be correct");
});

View File

@ -27,6 +27,11 @@ async function checkURLBarValueStays(browser) {
}
add_task(async function() {
// Disable autofill so that when checkURLBarValueStays types "a", it's not
// autofilled to addons.mozilla.org (or anything else).
await SpecialPowers.pushPrefEnv({ set: [
["browser.urlbar.autoFill", false],
]});
await BrowserTestUtils.withNewTab({
gBrowser,
url: TEST_URL,

View File

@ -205,45 +205,3 @@ add_task(function test_receiveResults() {
sandbox.resetHistory();
});
add_task(async function test_autofillValue() {
// Ensure the controller doesn't have any previous queries.
delete controller._lastQueryContext;
// Stub the controller's input so we can tell whether input.autofill() is
// called.
let input = {
autofill: sandbox.stub(),
};
controller.input = input;
const context = createContext();
controller.startQuery(context);
// Set autofillValue and call receiveResults().
context.autofillValue = "test";
context.results = [
new UrlbarResult(UrlbarUtils.RESULT_TYPE.TAB_SWITCH,
UrlbarUtils.RESULT_SOURCE.TABS,
{ url: "http://example.com/1" }),
];
controller.receiveResults(context);
Assert.equal(input.autofill.callCount, 1,
"Should have called input.autofill() one time");
Assert.deepEqual(input.autofill.args[0], ["test"],
"Should have called input.autofill() with context.autofillValue");
// Call receiveResults() again with more results.
context.results.push(
new UrlbarResult(UrlbarUtils.RESULT_TYPE.TAB_SWITCH,
UrlbarUtils.RESULT_SOURCE.TABS,
{ url: "http://example.com/2" }),
);
controller.receiveResults(context);
Assert.equal(input.autofill.callCount, 1,
"Should not have called input.autofill() again");
sandbox.resetHistory();
});

View File

@ -75,8 +75,6 @@ It is augmented as it progresses through the system, with various information:
// RESULT_SOURCE.*, that can be returned by the model.
// Properties added by the Model.
autofillValue; // {string} the text value that should be autofilled in the
// input, if any.
preselected; // {boolean} whether the first result should be preselected.
results; // {array} list of UrlbarResult objects.
tokens; // {array} tokens extracted from the searchString, each token is an
@ -375,6 +373,12 @@ properties, supported by all of the results.
title: {string} A title that may be used as a label for this result.
icon: {string} Url of an icon for this result.
payload: {object} Object containing properties for the specific RESULT_TYPE.
autofill: {object} An object describing the text that should be
autofilled in the input when the result is selected, if any.
autofill.value: {string} The autofill value.
autofill.selectionStart: {integer} The first index in the autofill
selection.
autofill.selectionEnd: {integer} The last index in the autofill selection.
}
The following RESULT_TYPEs are supported: