Bug 1470887 - Preserve exactly autofilled values in the urlbar, and don't call losslessDecodeURI on them. r=mak
authorDrew Willcoxon <adw@mozilla.com>
Fri, 20 Jul 2018 17:08:29 +0000
changeset 427509 22005ac8d9b286975b3d207862571a70b67c62b7
parent 427508 e56a701fc960ad035011da615ec13907d4f693a1
child 427510 4d268f4ed8a3c66e4c5d4f1717dd4e03bb010c4c
push id66668
push userdwillcoxon@mozilla.com
push dateFri, 20 Jul 2018 17:11:00 +0000
treeherderautoland@22005ac8d9b2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmak
bugs1470887
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1470887 - Preserve exactly autofilled values in the urlbar, and don't call losslessDecodeURI on them. r=mak Differential Revision: https://phabricator.services.mozilla.com/D2256
browser/base/content/test/urlbar/browser.ini
browser/base/content/test/urlbar/browser_urlbarAutofillPreserveCase.js
toolkit/components/places/tests/unifiedcomplete/test_autofill_origins.js
toolkit/content/widgets/autocomplete.xml
--- a/browser/base/content/test/urlbar/browser.ini
+++ b/browser/base/content/test/urlbar/browser.ini
@@ -70,16 +70,17 @@ support-files =
 skip-if = os == 'linux' # Bug 1104755
 [browser_urlbarAddonIframe.js]
 support-files =
   Panel.jsm
   urlbarAddonIframe.html
   urlbarAddonIframe.js
   urlbarAddonIframeContentScript.js
 [browser_urlbarAboutHomeLoading.js]
+[browser_urlbarAutofillPreserveCase.js]
 [browser_urlbarAutoFillTrimURLs.js]
 [browser_urlbarCopying.js]
 subsuite = clipboard
 support-files =
   authenticate.sjs
 [browser_urlbarDecode.js]
 [browser_urlbarDelete.js]
 [browser_urlbarEnter.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarAutofillPreserveCase.js
@@ -0,0 +1,74 @@
+// This test makes sure that when the user starts typing origins and URLs, the
+// case of the user's search string is preserved inside the origin part of the
+// autofilled string.
+
+"use strict";
+
+add_task(async function init() {
+  await cleanUp();
+});
+
+add_task(async function origin() {
+  await PlacesTestUtils.addVisits([{
+    uri: "http://example.com/",
+  }]);
+  await promiseAutocompleteResultPopup("ExA");
+  await waitForAutocompleteResultAt(0);
+  Assert.equal(gURLBar.value, "ExAmple.com/");
+  await cleanUp();
+});
+
+add_task(async function originPort() {
+  await PlacesTestUtils.addVisits([{
+    uri: "http://example.com:8888/",
+  }]);
+  await promiseAutocompleteResultPopup("ExA");
+  await waitForAutocompleteResultAt(0);
+  Assert.equal(gURLBar.value, "ExAmple.com:8888/");
+  await cleanUp();
+});
+
+add_task(async function originScheme() {
+  await PlacesTestUtils.addVisits([{
+    uri: "http://example.com/",
+  }]);
+  await promiseAutocompleteResultPopup("http://ExA");
+  await waitForAutocompleteResultAt(0);
+  Assert.equal(gURLBar.value, "http://ExAmple.com/");
+  await cleanUp();
+});
+
+add_task(async function originPortScheme() {
+  await PlacesTestUtils.addVisits([{
+    uri: "http://example.com:8888/",
+  }]);
+  await promiseAutocompleteResultPopup("http://ExA");
+  await waitForAutocompleteResultAt(0);
+  Assert.equal(gURLBar.value, "http://ExAmple.com:8888/");
+  await cleanUp();
+});
+
+add_task(async function url() {
+  await PlacesTestUtils.addVisits([{
+    uri: "http://example.com/foo",
+  }]);
+  await promiseAutocompleteResultPopup("ExAmple.com/f");
+  Assert.equal(gURLBar.value, "ExAmple.com/foo");
+  await cleanUp();
+});
+
+add_task(async function urlPort() {
+  await PlacesTestUtils.addVisits([{
+    uri: "http://example.com:8888/foo",
+  }]);
+  await promiseAutocompleteResultPopup("ExAmple.com:8888/f");
+  Assert.equal(gURLBar.value, "ExAmple.com:8888/foo");
+  await cleanUp();
+});
+
+async function cleanUp() {
+  EventUtils.synthesizeKey("KEY_Escape");
+  await promisePopupHidden(gURLBar.popup);
+  await PlacesUtils.bookmarks.eraseEverything();
+  await PlacesUtils.history.clear();
+}
--- a/toolkit/components/places/tests/unifiedcomplete/test_autofill_origins.js
+++ b/toolkit/components/places/tests/unifiedcomplete/test_autofill_origins.js
@@ -76,16 +76,55 @@ add_task(async function portPartial() {
       value: "example.com:8888/",
       comment: "example.com:8888",
       style: ["autofill", "heuristic"],
     }],
   });
   await cleanup();
 });
 
+// "EXaM" should match http://example.com/ and the case of the search string
+// should be preserved in the autofilled value.
+add_task(async function preserveCase() {
+  await PlacesTestUtils.addVisits([{
+    uri: "http://example.com/",
+  }]);
+  await check_autocomplete({
+    search: "EXaM",
+    autofilled: "EXaMple.com/",
+    completed: "http://example.com/",
+    matches: [{
+      value: "example.com/",
+      comment: "example.com",
+      style: ["autofill", "heuristic"],
+    }],
+  });
+  await cleanup();
+});
+
+// "EXaM" should match http://example.com:8888/, the port should be completed,
+// and the case of the search string should be preserved in the autofilled
+// value.
+add_task(async function preserveCasePort() {
+  await PlacesTestUtils.addVisits([{
+    uri: "http://example.com:8888/",
+  }]);
+  await check_autocomplete({
+    search: "EXaM",
+    autofilled: "EXaMple.com:8888/",
+    completed: "http://example.com:8888/",
+    matches: [{
+      value: "example.com:8888/",
+      comment: "example.com:8888",
+      style: ["autofill", "heuristic"],
+    }],
+  });
+  await cleanup();
+});
+
 // "example.com:89" should *not* match http://example.com:8888/.
 add_task(async function portNoMatch1() {
   await PlacesTestUtils.addVisits([{
     uri: "http://example.com:8888/",
   }]);
   await check_autocomplete({
     search: "example.com:89",
     matches: [],
--- a/toolkit/content/widgets/autocomplete.xml
+++ b/toolkit/content/widgets/autocomplete.xml
@@ -168,36 +168,38 @@
       </method>
 
       <method name="setTextValueWithReason">
         <parameter name="aValue"/>
         <parameter name="aReason"/>
         <body><![CDATA[
           if (aReason == Ci.nsIAutoCompleteInput
                            .TEXTVALUE_REASON_COMPLETEDEFAULT) {
-            this._disableTrim = true;
+            this._textValueSetByCompleteDefault = true;
           }
           this.textValue = aValue;
-          this._disableTrim = false;
+          this._textValueSetByCompleteDefault = 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[
-          if (typeof this.onBeforeTextValueSet == "function")
+          if (typeof this.onBeforeTextValueSet == "function" &&
+              !this._textValueSetByCompleteDefault) {
             val = this.onBeforeTextValueSet(val);
+          }
 
           this.value = val;
 
           // 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;
@@ -278,33 +280,34 @@
           else
             this.closePopup();
         ]]></setter>
       </property>
 
       <!-- =================== PUBLIC MEMBERS =================== -->
 
       <field name="valueIsTyped">false</field>
-      <field name="_disableTrim">false</field>
+      <field name="_textValueSetByCompleteDefault">false</field>
       <property name="value">
         <getter><![CDATA[
           if (typeof this.onBeforeValueGet == "function") {
             var result = this.onBeforeValueGet();
             if (result)
               return result.value;
           }
           return this.inputField.value;
         ]]></getter>
         <setter><![CDATA[
           this.mIgnoreInput = true;
 
           if (typeof this.onBeforeValueSet == "function")
             val = this.onBeforeValueSet(val);
 
-          if (typeof this.trimValue == "function" && !this._disableTrim)
+          if (typeof this.trimValue == "function" &&
+              !this._textValueSetByCompleteDefault)
             val = this.trimValue(val);
 
           this.valueIsTyped = false;
           this.inputField.value = val;
 
           if (typeof this.formatValue == "function")
             this.formatValue();