Bug 1252074 - test_form_autocomplete.html and test_form_autocomplete_with_list.html should pass on e10s. r=paolo draft
authorMarco Bonardo <mbonardo@mozilla.com>
Wed, 02 Mar 2016 14:34:38 +0100
changeset 338598 8aa05cc7c358394e8667ad8149cbc4987cd4e412
parent 338086 c8d1f83532a7fe4289a473e3528c04b8b2f85a64
child 515820 40bebf6d40c39ef3a6af175b6675551d7c069afa
push id12538
push usermak77@bonardo.net
push dateWed, 09 Mar 2016 14:33:50 +0000
reviewerspaolo
bugs1252074
milestone47.0a1
Bug 1252074 - test_form_autocomplete.html and test_form_autocomplete_with_list.html should pass on e10s. r=paolo MozReview-Commit-ID: CISKztr4p4N
toolkit/components/satchel/AutoCompleteE10S.jsm
toolkit/components/satchel/nsFormAutoComplete.js
toolkit/components/satchel/nsFormFillController.cpp
toolkit/components/satchel/nsIFormAutoComplete.idl
toolkit/components/satchel/test/mochitest.ini
toolkit/components/satchel/test/parent_utils.js
toolkit/components/satchel/test/test_form_autocomplete.html
toolkit/components/satchel/test/test_form_autocomplete_with_list.html
--- a/toolkit/components/satchel/AutoCompleteE10S.jsm
+++ b/toolkit/components/satchel/AutoCompleteE10S.jsm
@@ -81,16 +81,17 @@ var AutoCompleteE10SView = {
 this.AutoCompleteE10S = {
   init: function() {
     let messageManager = Cc["@mozilla.org/globalmessagemanager;1"].
                          getService(Ci.nsIMessageListenerManager);
     messageManager.addMessageListener("FormAutoComplete:SelectBy", this);
     messageManager.addMessageListener("FormAutoComplete:GetSelectedIndex", this);
     messageManager.addMessageListener("FormAutoComplete:MaybeOpenPopup", this);
     messageManager.addMessageListener("FormAutoComplete:ClosePopup", this);
+    messageManager.addMessageListener("FormAutoComplete:Disconnect", this);
     messageManager.addMessageListener("FormAutoComplete:RemoveEntry", this);
   },
 
   _initPopup: function(browserWindow, rect, direction) {
     this._popupCache = { browserWindow, rect, direction };
 
     this.browser = browserWindow.gBrowser.selectedBrowser;
     this.popup = this.browser.autoCompletePopup;
@@ -185,20 +186,27 @@ this.AutoCompleteE10S = {
                                    Ci.nsIAutoCompleteResult.RESULT_SUCCESS,
                                    0, "", message.data.datalistResult.values,
                                    message.data.datalistResult.labels,
                                    [], null);
     } else {
       message.data.datalistResult = null;
     }
 
+    let previousResult = null;
+    let previousSearchString = message.data.previousSearchString;
+    let searchString = message.data.untrimmedSearchString.toLowerCase();
+    if (previousSearchString && previousSearchString.length > 1 &&
+        searchString.includes(previousSearchString)) {
+      previousResult = this._resultCache;
+    }
     formAutoComplete.autoCompleteSearchAsync(message.data.inputName,
                                              message.data.untrimmedSearchString,
                                              message.data.mockField,
-                                             null,
+                                             previousResult,
                                              message.data.datalistResult,
                                              { onSearchCompletion:
                                                this.onSearchComplete.bind(this) });
   },
 
   // The second half of search, this fills in the popup and returns the
   // results to the child.
   onSearchComplete: function(results) {
@@ -234,16 +242,24 @@ this.AutoCompleteE10S = {
                                     this._popupCache.rect,
                                     this._resultCache);
         }
         break;
 
       case "FormAutoComplete:ClosePopup":
         this.popup.closePopup();
         break;
+
+      case "FormAutoComplete:Disconnect":
+        // The controller stopped controlling the current input, so clear
+        // any cached data.  This is necessary cause otherwise we'd clear data
+        // only when starting a new search, but the next input could not support
+        // autocomplete and it would end up inheriting the existing data.
+        AutoCompleteE10SView.clearResults();
+        break;
     }
   },
 
   handleEnter: function(aIsPopupSelection) {
     this.browser.messageManager.sendAsyncMessage(
       "FormAutoComplete:HandleEnter",
       { selectedIndex: this.popup.selectedIndex,
         isPopupSelection: aIsPopupSelection }
--- a/toolkit/components/satchel/nsFormAutoComplete.js
+++ b/toolkit/components/satchel/nsFormAutoComplete.js
@@ -497,16 +497,17 @@ FormAutoCompleteChild.prototype = {
       let mm = topLevelDocshell.QueryInterface(Ci.nsIInterfaceRequestor)
                                .getInterface(Ci.nsIContentFrameMessageManager);
 
       mm.sendAsyncMessage("FormHistory:AutoCompleteSearchAsync", {
         inputName: aInputName,
         untrimmedSearchString: aUntrimmedSearchString,
         mockField: mockField,
         datalistResult: datalistResult,
+        previousSearchString: aPreviousResult && aPreviousResult.searchString.trim().toLowerCase(),
         left: rect.left,
         top: rect.top,
         width: rect.width,
         height: rect.height,
         direction: direction,
       });
 
       let search = this._pendingSearch = {};
@@ -519,32 +520,43 @@ FormAutoCompleteChild.prototype = {
           return;
         }
         this._pendingSearch = null;
 
         let result = new FormAutoCompleteResult(
           null,
           Array.from(message.data.results, res => ({ text: res })),
           null,
-          null,
+          aUntrimmedSearchString,
           mm
         );
         if (aListener) {
           aListener.onSearchCompletion(result);
         }
       }
 
       mm.addMessageListener("FormAutoComplete:AutoCompleteSearchAsyncResult", searchFinished);
       this.log("autoCompleteSearchAsync message was sent");
     },
 
     stopAutoCompleteSearch : function () {
       this.log("stopAutoCompleteSearch");
       this._pendingSearch = null;
     },
+
+    stopControllingInput(aField) {
+      let window = aField.ownerDocument.defaultView;
+      let topLevelDocshell = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                                   .getInterface(Ci.nsIDocShell)
+                                   .sameTypeRootTreeItem
+                                   .QueryInterface(Ci.nsIDocShell);
+      let mm = topLevelDocshell.QueryInterface(Ci.nsIInterfaceRequestor)
+                               .getInterface(Ci.nsIContentFrameMessageManager);
+      mm.sendAsyncMessage("FormAutoComplete:Disconnect");
+    }
 }; // end of FormAutoCompleteChild implementation
 
 // nsIAutoCompleteResult implementation
 function FormAutoCompleteResult(formHistory,
                                 entries,
                                 fieldName,
                                 searchString,
                                 messageManager) {
@@ -571,17 +583,17 @@ FormAutoCompleteResult.prototype = {
 
     // Allow autoCompleteSearch to get at the JS object so it can
     // modify some readonly properties for internal use.
     get wrappedJSObject() {
         return this;
     },
 
     // Interfaces from idl...
-    searchString : null,
+    searchString : "",
     errorDescription : "",
     get defaultIndex() {
         if (this.entries.length == 0)
             return -1;
         else
             return 0;
     },
     get searchResult() {
--- a/toolkit/components/satchel/nsFormFillController.cpp
+++ b/toolkit/components/satchel/nsFormFillController.cpp
@@ -1186,16 +1186,24 @@ nsFormFillController::StopControllingInp
     nsCOMPtr<nsIAutoCompleteInput> input;
     mController->GetInput(getter_AddRefs(input));
     if (input == this)
       mController->SetInput(nullptr);
   }
 
   if (mFocusedInputNode) {
     MaybeRemoveMutationObserver(mFocusedInputNode);
+
+    nsresult rv;
+    nsCOMPtr <nsIFormAutoComplete> formAutoComplete =
+      do_GetService("@mozilla.org/satchel/form-autocomplete;1", &rv);
+    if (formAutoComplete) {
+      formAutoComplete->StopControllingInput(mFocusedInput);
+    }
+
     mFocusedInputNode = nullptr;
     mFocusedInput = nullptr;
   }
   mFocusedPopup = nullptr;
 }
 
 nsIDocShell *
 nsFormFillController::GetDocShellForInput(nsIDOMHTMLInputElement *aInput)
--- a/toolkit/components/satchel/nsIFormAutoComplete.idl
+++ b/toolkit/components/satchel/nsIFormAutoComplete.idl
@@ -21,16 +21,21 @@ interface nsIFormAutoComplete: nsISuppor
                                  in nsIAutoCompleteResult aDatalistResult,
                                  in nsIFormAutoCompleteObserver aListener);
 
     /**
      * If a search is in progress, stop it. Otherwise, do nothing. This is used
      * to cancel an existing search, for example, in preparation for a new search.
      */
     void stopAutoCompleteSearch();
+
+    /**
+     * Since the controller is disconnecting, any related data must be cleared.
+     */
+    void stopControllingInput(in nsIDOMHTMLInputElement aField);
 };
 
 [scriptable, function, uuid(604419ab-55a0-4831-9eca-1b9e67cc4751)]
 interface nsIFormAutoCompleteObserver : nsISupports
 {
   /*
    * Called when a search is complete and the results are ready even if the
    * result set is empty. If the search is cancelled or a new search is
--- a/toolkit/components/satchel/test/mochitest.ini
+++ b/toolkit/components/satchel/test/mochitest.ini
@@ -7,15 +7,13 @@ support-files =
   parent_utils.js
 
 [test_bug_511615.html]
 skip-if = e10s # bug 1162338 (needs refactoring to talk to the autocomplete popup)
 [test_bug_787624.html]
 skip-if = e10s # bug 1162338 (needs refactoring to talk to the autocomplete popup)
 [test_datalist_with_caching.html]
 [test_form_autocomplete.html]
-skip-if = e10s # bug 1162329 or bug 1162338
 [test_form_autocomplete_with_list.html]
-skip-if = e10s # bug 1162329 (autocomplete impl. in e10s isn't complete)
 [test_form_submission.html]
 [test_form_submission_cap.html]
 [test_form_submission_cap2.html]
 [test_popup_enter_event.html]
--- a/toolkit/components/satchel/test/parent_utils.js
+++ b/toolkit/components/satchel/test/parent_utils.js
@@ -67,17 +67,17 @@ var ParentUtils = {
     FormHistory.count(obj, listener);
   },
 
   checkRowCount(expectedCount, expectedFirstValue = null) {
     ContentTaskUtils.waitForCondition(() => {
       return gAutocompletePopup.tree.view.rowCount === expectedCount &&
         (!expectedFirstValue ||
           expectedCount <= 1 ||
-          gAutocompletePopup.tree.view.getValueAt(0, gAutocompletePopup.tree.columns[0]) ===
+          gAutocompletePopup.tree.view.getCellText(0, gAutocompletePopup.tree.columns[0]) ===
           expectedFirstValue);
     }).then(() => {
       let results = this.getMenuEntries();
       sendAsyncMessage("gotMenuChange", { results });
     });
   },
 
   observe(subject, topic, data) {
--- a/toolkit/components/satchel/test/test_form_autocomplete.html
+++ b/toolkit/components/satchel/test/test_form_autocomplete.html
@@ -646,25 +646,27 @@ function runTest() {
         waitForMenuChange(2);
         break;
 
     case 205:
         checkMenuEntries(["az", "aaz"], testNum);
         input.focus();
         doKey("left");
         expectPopup();
-        sendChar("a");
+        // Check case-insensitivity.
+        sendChar("A");
         break;
 
     case 206:
         checkMenuEntries(["aaz"], testNum);
         addEntry("field3", "aazq");
         break;
 
     case 207:
+        // check that results were cached
         input.focus();
         doKey("right");
         sendChar("q");
         waitForMenuChange(0);
         break;
 
     case 208:
         // check that results were cached
@@ -966,17 +968,17 @@ function runTest() {
         checkForm("");
         restoreForm();
         doKey("down");
         waitForMenuChange(0);
         break;
 
     case 601:
         checkMenuEntries([], testNum);
-        SpecialPowers.removeAutoCompletePopupEventListener(window, "popupshown", popupShownListener);
+        input.blur();
         SimpleTest.finish();
         return;
 
     default:
         ok(false, "Unexpected invocation of test #" + testNum);
         SimpleTest.finish();
         return;
   }
--- a/toolkit/components/satchel/test/test_form_autocomplete_with_list.html
+++ b/toolkit/components/satchel/test/test_form_autocomplete_with_list.html
@@ -345,78 +345,75 @@ function runTest() {
 
     case 201:
       // Adding an attribute after the first one while on the first then going
       // down and push enter. Value should be the on from the new suggestion.
       doKey("down");
       datalist = document.getElementById('suggest');
       var added = new Option("Foo");
       datalist.insertBefore(added, datalist.children[1]);
-
-      SimpleTest.executeSoon(function() {
-        doKey("down");
-        doKey("down");
-        doKey("return");
-        checkForm("Foo");
-
-        // Remove the element.
-        datalist.removeChild(added);
-        expectPopup();
-        restoreForm();
-        doKey("down");
-      });
+      waitForMenuChange(4);
       break;
 
     case 202:
+      doKey("down");
+      doKey("down");
+      doKey("return");
+      checkForm("Foo");
+
+      // Remove the element.
+      datalist = document.getElementById('suggest');
+      datalist.removeChild(datalist.children[1]);
+      waitForMenuChange(0);
+      break;
+
+    case 203:
       // Change the first element value attribute.
-      doKey("down");
+      restoreForm();
       datalist = document.getElementById('suggest');
       prevValue = datalist.children[0].value;
       datalist.children[0].value = "foo";
       expectPopup();
       break;
 
-    case 203:
+    case 204:
       doKey("down");
       doKey("return");
       checkForm("foo");
 
       datalist = document.getElementById('suggest');
       datalist.children[0].value = prevValue;
-      expectPopup();
-      restoreForm();
-      doKey("down");
+      waitForMenuChange(0);
       break;
 
-    case 204:
+    case 205:
       // Change the textContent to update the value attribute.
-      doKey("down");
+      restoreForm();
       datalist = document.getElementById('suggest');
       prevValue = datalist.children[0].getAttribute('value');
       datalist.children[0].removeAttribute('value');
       datalist.children[0].textContent = "foobar";
       expectPopup();
       break;
 
-    case 205:
+    case 206:
       doKey("down");
       doKey("return");
       checkForm("foobar");
 
       datalist = document.getElementById('suggest');
       datalist.children[0].setAttribute('value', prevValue);
       testNum = 299;
-      expectPopup();
-      restoreForm();
-      doKey("down");
+      waitForMenuChange(0);
       break;
 
     // Tests for filtering (or not).
     case 300:
       // Filters with first letter of the word.
+      restoreForm();
       synthesizeKey("f", {});
       expectPopup();
       break;
 
     case 301:
       doKey("down");
       doKey("return");
       checkForm("final");
@@ -464,16 +461,17 @@ function runTest() {
     case 400:
       // Check that the input event is fired.
       input.addEventListener("input", function(event) {
         input.removeEventListener("input", arguments.callee, false);
         ok(true, "oninput should have been received");
         ok(event.bubbles, "input event should bubble");
         ok(event.cancelable, "input event should be cancelable");
         checkForm("Google");
+        input.blur();
         SimpleTest.finish();
       }, false);
 
       doKey("down");
       checkForm("");
       doKey("return");
       break;