Bug 1304501 - Properly disable trimUrl on autofill. r=adw
MozReview-Commit-ID: IxCOWkqFYV0
--- a/browser/base/content/test/urlbar/browser_urlbarAutoFillTrimURLs.js
+++ b/browser/base/content/test/urlbar/browser_urlbarAutoFillTrimURLs.js
@@ -1,81 +1,49 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
// This test ensures that autoFilled values are not trimmed, unless the user
// selects from the autocomplete popup.
-function test() {
- waitForExplicitFinish();
-
+add_task(function* setup() {
const PREF_TRIMURL = "browser.urlbar.trimURLs";
const PREF_AUTOFILL = "browser.urlbar.autoFill";
- registerCleanupFunction(function () {
+ registerCleanupFunction(function* () {
Services.prefs.clearUserPref(PREF_TRIMURL);
Services.prefs.clearUserPref(PREF_AUTOFILL);
+ yield PlacesTestUtils.clearHistory();
gURLBar.handleRevert();
});
Services.prefs.setBoolPref(PREF_TRIMURL, true);
Services.prefs.setBoolPref(PREF_AUTOFILL, true);
// Adding a tab would hit switch-to-tab, so it's safer to just add a visit.
- let callback = {
- handleError: function () {},
- handleResult: function () {},
- handleCompletion: continue_test
- };
- let history = Cc["@mozilla.org/browser/history;1"]
- .getService(Ci.mozIAsyncHistory);
- history.updatePlaces({ uri: NetUtil.newURI("http://www.autofilltrimurl.com/whatever")
- , visits: [ { transitionType: Ci.nsINavHistoryService.TRANSITION_TYPED
- , visitDate: Date.now() * 1000
- } ]
- }, callback);
+ yield PlacesTestUtils.addVisits({
+ uri: "http://www.autofilltrimurl.com/whatever",
+ transition: Ci.nsINavHistoryService.TRANSITION_TYPED,
+ });
+});
+
+function* promiseSearch(searchtext) {
+ gURLBar.focus();
+ gURLBar.inputField.value = searchtext.substr(0, searchtext.length -1);
+ EventUtils.synthesizeKey(searchtext.substr(-1, 1), {});
+ yield promiseSearchComplete();
}
-function continue_test() {
- function test_autoFill(aTyped, aExpected, aCallback) {
- info(`Testing with input: ${aTyped}`);
- gURLBar.inputField.value = aTyped.substr(0, aTyped.length - 1);
- gURLBar.focus();
- gURLBar.selectionStart = aTyped.length - 1;
- gURLBar.selectionEnd = aTyped.length - 1;
+add_task(function* () {
+ yield promiseSearch("http://");
+ is(gURLBar.inputField.value, "http://", "Autofilled value is as expected");
+});
- EventUtils.synthesizeKey(aTyped.substr(-1), {});
- waitForSearchComplete(function () {
- info(`Got value: ${gURLBar.textValue}`);
- is(gURLBar.textValue, aExpected, "Autofilled value is as expected");
- aCallback();
- });
- }
+add_task(function* () {
+ yield promiseSearch("http://au");
+ is(gURLBar.inputField.value, "http://autofilltrimurl.com/", "Autofilled value is as expected");
+});
- test_autoFill("http://", "http://", function () {
- test_autoFill("http://au", "http://autofilltrimurl.com/", function () {
- test_autoFill("http://www.autofilltrimurl.com", "http://www.autofilltrimurl.com/", function () {
- // Now ensure selecting from the popup correctly trims.
- is(gURLBar.controller.matchCount, 2, "Found the expected number of matches");
- EventUtils.synthesizeKey("VK_DOWN", {});
- is(gURLBar.textValue, "www.autofilltrimurl.com/whatever", "trim was applied correctly");
- gURLBar.closePopup();
- PlacesTestUtils.clearHistory().then(finish);
- });
- });
- });
-}
+add_task(function* () {
+ yield promiseSearch("http://www.autofilltrimurl.com");
+ is(gURLBar.inputField.value, "http://www.autofilltrimurl.com/", "Autofilled value is as expected");
-var gOnSearchComplete = null;
-function waitForSearchComplete(aCallback) {
- info("Waiting for onSearchComplete");
- if (!gOnSearchComplete) {
- gOnSearchComplete = gURLBar.onSearchComplete;
- registerCleanupFunction(() => {
- gURLBar.onSearchComplete = gOnSearchComplete;
- });
- }
- gURLBar.onSearchComplete = function () {
- ok(gURLBar.popupOpen, "The autocomplete popup is correctly open");
- gOnSearchComplete.apply(gURLBar);
- aCallback();
- }
-}
+ // Now ensure selecting from the popup correctly trims.
+ is(gURLBar.controller.matchCount, 2, "Found the expected number of matches");
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ is(gURLBar.inputField.value, "www.autofilltrimurl.com/whatever", "trim was applied correctly");
+});
--- a/browser/base/content/test/urlbar/browser_urlbarDecode.js
+++ b/browser/base/content/test/urlbar/browser_urlbarDecode.js
@@ -20,19 +20,21 @@ add_task(function* injectJSON() {
yield checkInput(inputStr);
}
gURLBar.value = "";
gURLBar.handleRevert();
gURLBar.blur();
});
add_task(function losslessDecode() {
- let url = "http://example.com/\u30a2\u30a4\u30a6\u30a8\u30aa";
+ let urlNoScheme = "example.com/\u30a2\u30a4\u30a6\u30a8\u30aa";
+ let url = "http://" + urlNoScheme;
gURLBar.textValue = url;
- Assert.equal(gURLBar.inputField.value, url,
+ // Since this is directly setting textValue, it is expected to be trimmed.
+ Assert.equal(gURLBar.inputField.value, urlNoScheme,
"The string displayed in the textbox should not be escaped");
gURLBar.value = "";
gURLBar.handleRevert();
gURLBar.blur();
});
add_task(function* actionURILosslessDecode() {
let urlNoScheme = "example.com/\u30a2\u30a4\u30a6\u30a8\u30aa";
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -142,17 +142,17 @@ file, You can obtain one at http://mozil
<!--
onBeforeValueGet is called by the base-binding's .value getter.
It can return an object with a "value" property, to override the
return value of the getter.
-->
<method name="onBeforeValueGet">
<body><![CDATA[
- return {value: this._value};
+ return { value: this._value };
]]></body>
</method>
<!--
onBeforeValueSet is called by the base-binding's .value setter.
It should return the value that the setter should use.
-->
<method name="onBeforeValueSet">
@@ -914,63 +914,51 @@ file, You can obtain one at http://mozil
case "underflow":
this._contentIsCropped = false;
this._hideURLTooltip();
break;
}
]]></body>
</method>
- <property name="textValue">
- <getter><![CDATA[
- return this.inputField.value;
- ]]></getter>
- <setter>
- <![CDATA[
+ <!--
+ onBeforeTextValueSet is called by the base-binding's .textValue getter.
+ It should return the value that the getter should use.
+ -->
+ <method name="onBeforeTextValueGet">
+ <body><![CDATA[
+ return { value: this.inputField.value };
+ ]]></body>
+ </method>
+
+ <!--
+ onBeforeTextValueSet is called by the base-binding's .textValue setter.
+ It should return the value that the setter should use.
+ -->
+ <method name="onBeforeTextValueSet">
+ <parameter name="aValue"/>
+ <body><![CDATA[
+ let val = aValue;
let uri;
try {
uri = makeURI(val);
} catch (ex) {}
if (uri) {
// Do not touch moz-action URIs at all. They depend on being
// properly encoded and decoded and will break if decoded
// unexpectedly.
if (!this._parseActionUrl(val)) {
val = losslessDecodeURI(uri);
}
}
- // Trim popup selected values, but never trim results coming from
- // autofill.
- let styles = new Set(
- this.popup.selectedIndex == -1 ? [] :
- this.mController.getStyleAt(this.popup.selectedIndex).split(/\s+/)
- );
- if (this.popup.selectedIndex == -1 ||
- this.mController
- .getStyleAt(this.popup.selectedIndex)
- .split(/\s+/).indexOf("autofill") >= 0) {
- this._disableTrim = true;
- }
- this.value = val;
- this._disableTrim = false;
-
- // Completing a result should simulate the user typing the result, so
- // fire an input event.
- let evt = document.createEvent("UIEvents");
- evt.initUIEvent("input", true, false, window, 0);
- this.mIgnoreInput = true;
- this.dispatchEvent(evt);
- this.mIgnoreInput = false;
-
- return this.value;
- ]]>
- </setter>
- </property>
+ return val;
+ ]]></body>
+ </method>
<method name="_parseActionUrl">
<parameter name="aUrl"/>
<body><![CDATA[
const MOZ_ACTION_REGEX = /^moz-action:([^,]+),(.*)$/;
if (!MOZ_ACTION_REGEX.test(aUrl))
return null;
--- a/toolkit/components/autocomplete/nsAutoCompleteController.cpp
+++ b/toolkit/components/autocomplete/nsAutoCompleteController.cpp
@@ -18,16 +18,30 @@
#include "nsITreeColumns.h"
#include "nsIObserverService.h"
#include "nsIDOMKeyEvent.h"
#include "mozilla/Services.h"
#include "mozilla/ModuleUtils.h"
static const char *kAutoCompleteSearchCID = "@mozilla.org/autocomplete/search;1?name=";
+namespace {
+
+void
+SetTextValue(nsIAutoCompleteInput* aInput,
+ const nsString& aValue,
+ uint16_t aReason) {
+ nsresult rv = aInput->SetTextValueWithReason(aValue, aReason);
+ if (NS_FAILED(rv)) {
+ aInput->SetTextValue(aValue);
+ }
+}
+
+} // anon namespace
+
NS_IMPL_CYCLE_COLLECTION_CLASS(nsAutoCompleteController)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsAutoCompleteController)
tmp->SetInput(nullptr);
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsAutoCompleteController)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInput)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSearches)
@@ -478,26 +492,31 @@ nsAutoCompleteController::HandleKeyNavig
// If the result is the previously autofilled string, then restore
// the search string and selection that existed when the result was
// autofilled. Else, fill the result and move the caret to the end.
int32_t start;
if (value.Equals(mPlaceholderCompletionString,
nsCaseInsensitiveStringComparator())) {
start = mSearchString.Length();
value = mPlaceholderCompletionString;
+ SetTextValue(input, value,
+ nsIAutoCompleteInput::TEXTVALUE_REASON_COMPLETEDEFAULT);
} else {
start = value.Length();
+ SetTextValue(input, value,
+ nsIAutoCompleteInput::TEXTVALUE_REASON_COMPLETESELECTED);
}
- input->SetTextValue(value);
+
input->SelectTextRange(start, value.Length());
}
mCompletedSelectionIndex = selectedIndex;
} else {
// Nothing is selected, so fill in the last typed value
- input->SetTextValue(mSearchString);
+ SetTextValue(input, mSearchString,
+ nsIAutoCompleteInput::TEXTVALUE_REASON_REVERT);
input->SelectTextRange(mSearchString.Length(), mSearchString.Length());
mCompletedSelectionIndex = -1;
}
}
} else {
#ifdef XP_MACOSX
// on Mac, only show the popup if the caret is at the start or end of
// the input and there is no selection, so that the default defined key
@@ -582,17 +601,18 @@ nsAutoCompleteController::HandleKeyNavig
int32_t selectedIndex;
popup->GetSelectedIndex(&selectedIndex);
bool shouldComplete;
input->GetCompleteDefaultIndex(&shouldComplete);
if (selectedIndex >= 0) {
// The pop-up is open and has a selection, take its value
nsAutoString value;
if (NS_SUCCEEDED(GetResultValueAt(selectedIndex, false, value))) {
- input->SetTextValue(value);
+ SetTextValue(input, value,
+ nsIAutoCompleteInput::TEXTVALUE_REASON_COMPLETESELECTED);
input->SelectTextRange(value.Length(), value.Length());
}
}
else if (shouldComplete) {
// We usually try to preserve the casing of what user has typed, but
// if he wants to autocomplete, we will replace the value with the
// actual autocomplete result. Note that the autocomplete input can also
// be showing e.g. "bar >> foo bar" if the search matched "bar", a
@@ -607,17 +627,18 @@ nsAutoCompleteController::HandleKeyNavig
int32_t pos = inputValue.Find(" >> ");
if (pos > 0) {
inputValue.Right(suggestedValue, inputValue.Length() - pos - 4);
} else {
suggestedValue = inputValue;
}
if (value.Equals(suggestedValue, nsCaseInsensitiveStringComparator())) {
- input->SetTextValue(value);
+ SetTextValue(input, value,
+ nsIAutoCompleteInput::TEXTVALUE_REASON_COMPLETEDEFAULT);
input->SelectTextRange(value.Length(), value.Length());
}
}
}
// Close the pop-up even if nothing was selected
ClearSearchTimer();
ClosePopup();
@@ -1522,17 +1543,17 @@ nsAutoCompleteController::EnterMatch(boo
}
nsCOMPtr<nsIObserverService> obsSvc =
mozilla::services::GetObserverService();
NS_ENSURE_STATE(obsSvc);
obsSvc->NotifyObservers(input, "autocomplete-will-enter-text", nullptr);
if (!value.IsEmpty()) {
- input->SetTextValue(value);
+ SetTextValue(input, value, nsIAutoCompleteInput::TEXTVALUE_REASON_ENTERMATCH);
input->SelectTextRange(value.Length(), value.Length());
mSearchString = value;
}
obsSvc->NotifyObservers(input, "autocomplete-did-enter-text", nullptr);
ClosePopup();
bool cancel;
@@ -1562,17 +1583,17 @@ nsAutoCompleteController::RevertTextValu
NS_ENSURE_STATE(obsSvc);
obsSvc->NotifyObservers(input, "autocomplete-will-revert-text", nullptr);
nsAutoString inputValue;
input->GetTextValue(inputValue);
// Don't change the value if it is the same to prevent sending useless events.
// NOTE: how can |RevertTextValue| be called with inputValue != oldValue?
if (!oldValue.Equals(inputValue)) {
- input->SetTextValue(oldValue);
+ SetTextValue(input, oldValue, nsIAutoCompleteInput::TEXTVALUE_REASON_REVERT);
}
obsSvc->NotifyObservers(input, "autocomplete-did-revert-text", nullptr);
}
return NS_OK;
}
@@ -1888,17 +1909,18 @@ nsAutoCompleteController::CompleteValue(
if (aValue.IsEmpty() ||
StringBeginsWith(aValue, mSearchString,
nsCaseInsensitiveStringComparator())) {
// aValue is empty (we were asked to clear mInput), or mSearchString
// matches the beginning of aValue. In either case we can simply
// autocomplete to aValue.
mPlaceholderCompletionString = aValue;
- input->SetTextValue(aValue);
+ SetTextValue(input, aValue,
+ nsIAutoCompleteInput::TEXTVALUE_REASON_COMPLETEDEFAULT);
} else {
nsresult rv;
nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString scheme;
if (NS_SUCCEEDED(ios->ExtractScheme(NS_ConvertUTF16toUTF8(aValue), scheme))) {
// Trying to autocomplete a URI from somewhere other than the beginning.
// Only succeed if the missing portion is "http://"; otherwise do not
@@ -1910,24 +1932,26 @@ nsAutoCompleteController::CompleteValue(
!scheme.LowerCaseEqualsLiteral("http") ||
!Substring(aValue, findIndex, mSearchStringLength).Equals(
mSearchString, nsCaseInsensitiveStringComparator())) {
return NS_OK;
}
mPlaceholderCompletionString = mSearchString +
Substring(aValue, mSearchStringLength + findIndex, endSelect);
- input->SetTextValue(mPlaceholderCompletionString);
+ SetTextValue(input, mPlaceholderCompletionString,
+ nsIAutoCompleteInput::TEXTVALUE_REASON_COMPLETEDEFAULT);
endSelect -= findIndex; // We're skipping this many characters of aValue.
} else {
// Autocompleting something other than a URI from the middle.
// Use the format "searchstring >> full string" to indicate to the user
// what we are going to replace their search string with.
- input->SetTextValue(mSearchString + NS_LITERAL_STRING(" >> ") + aValue);
+ SetTextValue(input, mSearchString + NS_LITERAL_STRING(" >> ") + aValue,
+ nsIAutoCompleteInput::TEXTVALUE_REASON_COMPLETEDEFAULT);
endSelect = mSearchString.Length() + 4 + aValue.Length();
// Reset the last search completion.
mPlaceholderCompletionString.Truncate();
}
}
--- a/toolkit/components/autocomplete/nsIAutoCompleteInput.idl
+++ b/toolkit/components/autocomplete/nsIAutoCompleteInput.idl
@@ -4,61 +4,61 @@
#include "nsISupports.idl"
#include "nsIAutoCompleteController.idl"
interface nsIAutoCompletePopup;
[scriptable, uuid(B068E70F-F82C-4C12-AD87-82E271C5C180)]
interface nsIAutoCompleteInput : nsISupports
-{
+{
/*
* The result view that will be used to display results
*/
readonly attribute nsIAutoCompletePopup popup;
-
+
/*
* The controller.
*/
readonly attribute nsIAutoCompleteController controller;
- /*
+ /*
* Indicates if the popup is currently open
*/
attribute boolean popupOpen;
/*
* Option to disable autocomplete functionality
- */
+ */
attribute boolean disableAutoComplete;
-
- /*
+
+ /*
* If a search result has its defaultIndex set, this will optionally
* try to complete the text in the textbox to the entire text of the
* result at the default index as the user types
*/
attribute boolean completeDefaultIndex;
/*
* complete text in the textbox as the user selects from the dropdown
* options if set to true
*/
attribute boolean completeSelectedIndex;
- /*
+ /*
* Option for completing to the default result whenever the user hits
* enter or the textbox loses focus
*/
attribute boolean forceComplete;
-
+
/*
* Option to open the popup only after a certain number of results are available
*/
attribute unsigned long minResultsForPopup;
-
+
/*
* The maximum number of rows to show in the autocomplete popup.
*/
attribute unsigned long maxRows;
/*
* Option to show a second column in the popup which contains
* the comment for each autocomplete result
@@ -66,42 +66,61 @@ interface nsIAutoCompleteInput : nsISupp
attribute boolean showCommentColumn;
/*
* Option to show a third column in the popup which contains
* an additional image for each autocomplete result
*/
attribute boolean showImageColumn;
- /*
+ /*
* Number of milliseconds after a keystroke before a search begins
*/
attribute unsigned long timeout;
/*
* An extra parameter to configure searches with.
*/
attribute AString searchParam;
/*
* The number of autocomplete session to search
*/
readonly attribute unsigned long searchCount;
-
+
/*
* Get the name of one of the autocomplete search session objects
*/
ACString getSearchAt(in unsigned long index);
/*
- * The value of text in the autocomplete textbox
+ * The value of text in the autocomplete textbox.
+ *
+ * @note when setting a new value, the controller always first tries to use
+ * setTextboxValueWithReason, and only if that throws (unimplemented),
+ * fallbacks to the textValue's setter. If a reason is not provided,
+ * the implementation should assume TEXTVALUE_REASON_UNKNOWN, but it
+ * should only happen in testing code.
*/
attribute AString textValue;
/*
+ * Set the value of text in the autocomplete textbox, providing a reason to
+ * the autocomplete view.
+ */
+ const unsigned short TEXTVALUE_REASON_UNKNOWN = 0;
+ const unsigned short TEXTVALUE_REASON_COMPLETEDEFAULT = 1;
+ const unsigned short TEXTVALUE_REASON_COMPLETESELECTED = 2;
+ const unsigned short TEXTVALUE_REASON_REVERT = 3;
+ const unsigned short TEXTVALUE_REASON_ENTERMATCH = 4;
+
+ void setTextValueWithReason(in AString aValue,
+ in unsigned short aReason);
+
+ /*
* Report the starting index of the cursor in the textbox
*/
readonly attribute long selectionStart;
/*
* Report the ending index of the cursor in the textbox
*/
readonly attribute long selectionEnd;
--- a/toolkit/components/satchel/nsFormFillController.cpp
+++ b/toolkit/components/satchel/nsFormFillController.cpp
@@ -517,16 +517,23 @@ nsFormFillController::SetTextValue(const
mSuppressOnInput = true;
editable->SetUserInput(aTextValue);
mSuppressOnInput = false;
}
return NS_OK;
}
NS_IMETHODIMP
+nsFormFillController::SetTextValueWithReason(const nsAString & aTextValue,
+ uint16_t aReason)
+{
+ return SetTextValue(aTextValue);
+}
+
+NS_IMETHODIMP
nsFormFillController::GetSelectionStart(int32_t *aSelectionStart)
{
if (mFocusedInput)
mFocusedInput->GetSelectionStart(aSelectionStart);
return NS_OK;
}
NS_IMETHODIMP
--- a/toolkit/content/widgets/autocomplete.xml
+++ b/toolkit/content/widgets/autocomplete.xml
@@ -183,33 +183,53 @@
<method name="getSearchAt">
<parameter name="aIndex"/>
<body><![CDATA[
this.initSearchNames();
return this.mSearchNames[aIndex];
]]></body>
</method>
- <property name="textValue"
- onget="return this.value;">
+ <method name="setTextValueWithReason">
+ <parameter name="aValue"/>
+ <parameter name="aReason"/>
+ <body><![CDATA[
+ if (aReason == Components.interfaces.nsIAutoCompleteInput
+ .TEXTVALUE_REASON_COMPLETEDEFAULT) {
+ this._disableTrim = true;
+ }
+ this.textValue = aValue;
+ this._disableTrim = false;
+ ]]></body>
+ </method>
+
+ <property name="textValue">
+ <getter><![CDATA[
+ if (typeof this.onBeforeTextValueGet == "function") {
+ let result = this.onBeforeTextValueGet();
+ if (result) {
+ return result.value;
+ }
+ }
+ return this.value;
+ ]]></getter>
<setter><![CDATA[
- // Completing a result should simulate the user typing the result,
- // so fire an input event.
- // Trim popup selected values, but never trim results coming from
- // autofill.
- if (this.popup.selectedIndex == -1)
- this._disableTrim = true;
+ if (typeof this.onBeforeTextValueSet == "function")
+ val = this.onBeforeTextValueSet(val);
+
this.value = val;
- this._disableTrim = false;
- var evt = document.createEvent("UIEvents");
+ // Completing a result should simulate the user typing the result, so
+ // fire an input event.
+ let evt = document.createEvent("UIEvents");
evt.initUIEvent("input", true, false, window, 0);
this.mIgnoreInput = true;
this.dispatchEvent(evt);
this.mIgnoreInput = false;
+
return this.value;
]]></setter>
</property>
<method name="selectTextRange">
<parameter name="aStartIndex"/>
<parameter name="aEndIndex"/>
<body><![CDATA[