Bug 1470887 - Preserve exactly autofilled values in the urlbar, and don't call losslessDecodeURI on them. r=mak a=lizzard
authorDrew Willcoxon <adw@mozilla.com>
Fri, 20 Jul 2018 17:08:29 +0000
changeset 480597 1a33a2a195220441b64e900d815d7d1786e641ca
parent 480596 d8d3fca29016566df5d798d0709f51a4036f40c7
child 480598 4af69471cde4c4731175c26bac3edb7358c0a74b
push id1757
push userffxbld-merge
push dateFri, 24 Aug 2018 17:02:43 +0000
treeherdermozilla-release@736023aebdb1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmak, lizzard
bugs1470887
milestone62.0
Bug 1470887 - Preserve exactly autofilled values in the urlbar, and don't call losslessDecodeURI on them. r=mak a=lizzard 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
@@ -69,16 +69,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
@@ -173,36 +173,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;
@@ -283,33 +285,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();