Bug 912031 - Use Asynchronous FormHistory.jsm in Data Manager. r=IanN a=IanN
authorFrank-Rainer Grahl <frgrahl@gmx.net>
Sun, 16 Apr 2017 21:08:48 +0200
changeset 24464 a393a71df238
parent 24463 cd96715bc14a
child 24465 509cbae906af
push id2058
push userfrgrahl@gmx.net
push dateSun, 16 Apr 2017 19:09:33 +0000
treeherdercomm-aurora@509cbae906af [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersIanN, IanN
bugs912031
Bug 912031 - Use Asynchronous FormHistory.jsm in Data Manager. r=IanN a=IanN
suite/common/dataman/dataman.js
--- a/suite/common/dataman/dataman.js
+++ b/suite/common/dataman/dataman.js
@@ -1,25 +1,26 @@
 /* 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/. */
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://services-common/async.js");
 // Load DownloadUtils module for convertByteUnits
 Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
 
 // locally loaded services
 var gLocSvc = {};
 XPCOMUtils.defineLazyServiceGetter(gLocSvc, "date",
                                    "@mozilla.org/intl/scriptabledateformat;1",
                                    "nsIScriptableDateFormat");
-XPCOMUtils.defineLazyServiceGetter(gLocSvc, "fhist",
-                                   "@mozilla.org/satchel/form-history;1",
-                                   "nsIFormHistory2");
+XPCOMUtils.defineLazyModuleGetter(gLocSvc, "FormHistory",
+                                  "resource://gre/modules/FormHistory.jsm",
+                                  "FormHistory");
 XPCOMUtils.defineLazyServiceGetter(gLocSvc, "url",
                                    "@mozilla.org/network/url-parser;1?auth=maybe",
                                    "nsIURLParser");
 XPCOMUtils.defineLazyServiceGetter(gLocSvc, "clipboard",
                                    "@mozilla.org/widget/clipboardhelper;1",
                                    "nsIClipboardHelper");
 XPCOMUtils.defineLazyServiceGetter(gLocSvc, "idn",
                                    "@mozilla.org/network/idn-service;1",
@@ -2806,36 +2807,47 @@ var gFormdata = {
 
   shutdown: function formdata_shutdown() {
     gDataman.debugMsg("Shutting down form data panel");
     this.tree.view.selection.clearSelection();
     this.tree.view = null;
     this.displayedFormdata = [];
   },
 
+  _promiseLoadFormHistory: function formdata_promiseLoadFormHistory() {
+    return new Promise(resolve => {
+      let callbacks = {
+        handleResult(result) {
+          gFormdata.formdata.push({fieldname: result.fieldname,
+                                   value: result.value,
+                                   timesUsed: result.timesUsed,
+                                   firstUsed: gFormdata._getTimeString(result.firstUsed),
+                                   firstUsedSortValue: result.firstUsed,
+                                   lastUsed: gFormdata._getTimeString(result.lastUsed),
+                                   lastUsedSortValue: result.lastUsed,
+                                   guid: result.guid});
+        },
+        handleError(aError) {
+          Components.utils.reportError(aError);
+        },
+        handleCompletion(aReason) {
+          // This needs to stay in or Async.promiseSpinningly will fail.
+          resolve();
+        }
+      };
+      gLocSvc.FormHistory.search(["fieldname", "value", "timesUsed", "firstUsed", "lastUsed", "guid"],
+                                 {},
+                                 callbacks);
+   });
+  },
+
   loadList: function formdata_loadList() {
     this.formdata = [];
-    try {
-      let sql = "SELECT fieldname, value, timesUsed, firstUsed, lastUsed, guid FROM moz_formhistory";
-      var statement = gLocSvc.fhist.DBConnection.createStatement(sql);
-      while (statement.executeStep()) {
-        this.formdata.push({fieldname: statement.row["fieldname"],
-                            value: statement.row["value"],
-                            timesUsed: statement.row["timesUsed"],
-                            firstUsed: this._getTimeString(statement.row["firstUsed"]),
-                            firstUsedSortValue: statement.row["firstUsed"],
-                            lastUsed: this._getTimeString(statement.row["lastUsed"]),
-                            lastUsedSortValue: statement.row["lastUsed"],
-                            guid: statement.row["guid"]}
-                          );
-      }
-    }
-    finally {
-      statement.reset();
-    }
+    // Use Async.promiseSpinningly to Sync the call.
+    Async.promiseSpinningly(this._promiseLoadFormHistory());
   },
 
   _getTimeString: function formdata__getTimeString(aTimestamp) {
     if (aTimestamp) {
       let date = new Date(aTimestamp / 1000);
 
       // If a date has an extreme value, the dateservice can't cope with it
       // properly, so we'll just return a blank string.
@@ -2954,17 +2966,22 @@ var gFormdata = {
 
     this.tree.view.selection.clearSelection();
     // Loop backwards so later indexes in the list don't change.
     for (let i = selections.length - 1; i >= 0; i--) {
       let delFData = this.displayedFormdata[selections[i]];
       this.formdata.splice(this.formdata.indexOf(this.displayedFormdata[selections[i]]), 1);
       this.displayedFormdata.splice(selections[i], 1);
       this.tree.treeBoxObject.rowCountChanged(selections[i], -1);
-      gLocSvc.fhist.removeEntry(delFData.fieldname, delFData.value);
+      let changes = [{op: "remove",
+                      fieldname: delFData.fieldname,
+                      value: delFData.value}];
+      // Async call but we don't care about the completion just now and remove the entry from the panel.
+      // If the call fails the entry will just reappear the next time the form panel is opened.
+      gLocSvc.FormHistory.update(changes);
     }
     // Select the entry after the first deleted one or the last of all entries.
     if (selections.length && this.displayedFormdata.length)
       this.tree.view.selection.toggleSelect(selections[0] < this.displayedFormdata.length ?
                                             selections[0] :
                                             this.displayedFormdata.length - 1);
   },
 
@@ -2989,117 +3006,154 @@ var gFormdata = {
 
   updateContext: function formdata_updateContext() {
     document.getElementById("fdata-context-remove").disabled =
       this.removeButton.disabled;
     document.getElementById("fdata-context-selectall").disabled =
       this.tree.view.selection.count >= this.tree.view.rowCount;
   },
 
+  /**
+   * _promiseReadFormHistory
+   *
+   * Retrieves the formddata from the data for the given guid.
+   *
+   * @param aGuid guid for which form data should be returned.
+   * @return Promise<null if no row is found with the specified guid,
+   *         or an object containing the row full content values>
+   */
+  _promiseReadFormHistory: function formdata_promiseReadFormHistory(aGuid) {
+
+    return new Promise((resolve, reject) => {
+      var entry = null;
+      let callbacks = {
+        handleResult(result) {
+          // There can be only one entry for a given guid.
+          // If there are more we will not behead it but instead 
+          // only keep the last returned result.
+          entry = result;
+        },
+        handleError(aError) {
+          Components.utils.reportError(aError);
+          reject(error);
+        },
+        handleCompletion(aReason) {
+          resolve(entry);
+        }
+      };
+
+      gLocSvc.FormHistory.search(["fieldname", "value", "timesUsed", "firstUsed", "lastUsed", "guid"],
+                                 {guid :aGuid},
+                                 callbacks);
+   });
+  },
+
+  // Updates the form data panel when receiving a notification.
+  //
+  // The notification type is passed in aData.
+  //
+  // The following types are supported:
+  //   formhistory-add  formhistory-update  formhistory-remove
+  //   formhistory-expireoldentries
+  //
+  // The following types will be ignored:
+  //   formhistory-shutdown  formhistory-beforeexpireoldentries
   reactToChange: function formdata_reactToChange(aSubject, aData) {
-    // aData: addEntry, modifyEntry, removeEntry, removeAllEntries,
-    // removeEntriesForName, removeEntriesByTimeframe, expireOldEntries,
-    // before-removeEntry, before-removeAllEntries, before-removeEntriesForName,
-    // before-removeEntriesByTimeframe, before-expireOldEntries
 
     // Ignore changes when no form data pane is loaded
-    // or if we caught a before-* notification.
-    if (!this.displayedFormdata.length || /^before-/.test(aData))
+    // or if we caught an unsupported notification.
+    if (!this.displayedFormdata.length ||
+        aData == "formhistory-shutdown" ||
+        aData == "formhistory-beforeexpireoldentries") {
       return;
-
-    if (aData == "removeAllEntries" || aData == "removeEntriesForName" ||
-        aData == "removeEntriesByTimeframe" || aData == "expireOldEntries") {
+    }
+
+    if (aData == "formhistory-expireoldentries") {
       // Go for re-parsing the whole thing.
       this.tree.treeBoxObject.beginUpdateBatch();
       this.tree.view.selection.clearSelection();
       this.displayedFormdata = [];
       this.tree.treeBoxObject.endUpdateBatch();
 
       this.loadList();
       this.search("");
       return;
     }
 
-    // Usual notifications for addEntry, modifyEntry, removeEntry - do "surgical" updates.
-    let subjectData = []; // Those notifications all have: name, value, guid.
-    if (aSubject instanceof Components.interfaces.nsIArray) {
-      let enumerator = aSubject.enumerate();
-      while (enumerator.hasMoreElements()) {
-        let nextElem = enumerator.getNext();
-        if (nextElem instanceof Components.interfaces.nsISupportsString ||
-            nextElem instanceof Components.interfaces.nsISupportsPRInt64) {
-          subjectData.push(nextElem.data);
-        }
-      }
-    }
-    else {
+    if (aData != "formhistory-add" && aData != "formhistory-change" &&
+        aData != "formhistory-remove") {
       Components.utils.reportError("Observed an unrecognized formdata change of type " + aData);
       return;
     }
 
-    let entryData = null;
-    if (aData == "addEntry" || aData == "modifyEntry") {
-      try {
-        let sql = "SELECT fieldname, value, timesUsed, firstUsed, lastUsed, guid FROM moz_formhistory WHERE guid = :guid";
-        var statement = gLocSvc.fhist.DBConnection.createStatement(sql);
-        statement.params.guid = subjectData[2];
-        while (statement.executeStep()) {
-          entryData = {fieldname: statement.row["fieldname"],
-                       value: statement.row["value"],
-                       timesUsed: statement.row["timesUsed"],
-                       firstUsed: this._getTimeString(statement.row["firstUsed"]),
-                       firstUsedSortValue: statement.row["firstUsed"],
-                       lastUsed: this._getTimeString(statement.row["lastUsed"]),
-                       lastUsedSortValue: statement.row["lastUsed"],
-                       guid: statement.row["guid"]};
+    var cGuid = null;
+
+    if (aSubject instanceof Components.interfaces.nsISupportsString) {
+      cGuid = aSubject.toString();
+    }
+
+    if (!cGuid) {
+      // See bug 1346850. Remove has a problem and always sends a null guid.
+      // We just let the panel stay the same which might cause minor problems
+      // because there is no longer a notification when removing all entries.
+      if (aData != "formhistory-remove") {
+        Components.utils.reportError("FormHistory guid is null for " + aData);
+      }
+      return;
+    }
+
+    var entryData = null;
+
+    if (aData == "formhistory-add" || aData == "formhistory-change") {
+      // Use Async.promiseSpinningly to Sync the call.
+      Async.promiseSpinningly(this._promiseReadFormHistory(cGuid).then(entry => {
+        if (entry) {
+          entryData = entry;
         }
-      }
-      finally {
-        statement.reset();
-      }
+        return;
+      }));
 
       if (!entryData) {
         Components.utils.reportError("Could not find added/modifed formdata entry");
         return;
       }
     }
 
-    if (aData == "addEntry") {
+    if (aData == "formhistory-add") {
       this.formdata.push(entryData);
-
       this.displayedFormdata.push(this.formdata[this.formdata.length - 1]);
       this.tree.treeBoxObject.rowCountChanged(this.formdata.length - 1, 1);
       this.search("");
     }
     else {
       let idx = -1, disp_idx = -1;
       for (let i = 0; i < this.displayedFormdata.length; i++) {
         let fdata = this.displayedFormdata[i];
-        if (fdata && fdata.guid == subjectData[2]) {
+        if (fdata && fdata.guid == cGuid) {
           idx = this.formdata.indexOf(this.displayedFormdata[i]);
           disp_idx = i;
           break;
         }
       }
       if (idx >= 0) {
-        if (aData == "modifyEntry") {
+        if (aData == "formhistory-change") {
           this.formdata[idx] = entryData;
           this.tree.treeBoxObject.invalidateRow(disp_idx);
         }
-        else if (aData == "removeEntry") {
+        else if (aData == "formhistory-remove") {
           this.formdata.splice(idx, 1);
           this.displayedFormdata.splice(disp_idx, 1);
           this.tree.treeBoxObject.rowCountChanged(disp_idx, -1);
         }
       }
     }
   },
 
   forget: function formdata_forget() {
-    gLocSvc.fhist.removeAllEntries();
+    gLocSvc.FormHistory.update({ op: "remove" });
   },
 
   // nsITreeView
   __proto__: gBaseTreeView,
   get rowCount() {
     return this.displayedFormdata.length;
   },
   getCellText: function(aRow, aColumn) {