Bug 1264582 - Table headers are not removed when selecting an empty storage draft
authorFischer.json <fischer.json@gmail.com>
Fri, 01 Jul 2016 17:42:07 +0800
changeset 392302 dd48d022a896c0f6ea57a68bcfca413c79266de4
parent 392299 7c669d5d63efceb12696cd65cfa72c296013dafb
child 526314 c04b5068cdd976873d60f171eea9e543497a1806
push id24004
push userbmo:fliu@mozilla.com
push dateMon, 25 Jul 2016 11:20:53 +0000
bugs1264582
milestone50.0a1
Bug 1264582 - Table headers are not removed when selecting an empty storage MozReview-Commit-ID: Hcfw7dyrDpV
devtools/client/storage/ui.js
devtools/server/actors/storage.js
devtools/shared/specs/storage.js
--- a/devtools/client/storage/ui.js
+++ b/devtools/client/storage/ui.js
@@ -219,22 +219,25 @@ StorageUI.prototype = {
   },
 
   getCurrentActor: function () {
     let type = this.table.datatype;
 
     return this.storageTypes[type];
   },
 
-  makeFieldsEditable: function* () {
-    let actor = this.getCurrentActor();
-
-    if (typeof actor.getEditableFields !== "undefined") {
-      let fields = yield actor.getEditableFields();
-      this.table.makeFieldsEditable(fields);
+  /**
+   *  Make column fields editable
+   *
+   *  @param {Array} editableFields
+   *         An array of keys of columns to be made editable
+   */
+  makeFieldsEditable: function* (editableFields) {
+    if (editableFields && editableFields.length > 0) {
+      this.table.makeFieldsEditable(editableFields);
     } else if (this.table._editableFieldsEngine) {
       this.table._editableFieldsEngine.destroy();
     }
   },
 
   editItem: function (eventType, data) {
     let actor = this.getCurrentActor();
 
@@ -480,21 +483,35 @@ StorageUI.prototype = {
     if (reason !== REASON.NEXT_50_ITEMS &&
         reason !== REASON.UPDATE &&
         reason !== REASON.NEW_ROW &&
         reason !== REASON.POPULATE) {
       throw new Error("Invalid reason specified");
     }
 
     try {
+      if (reason === REASON.POPULATE) {
+        let subType = null;
+        // The indexedDB type could have sub-type data to fetch.
+        // If having names specified, then it means
+        // we are fetching details of specific database or of object store.
+        if (type == "indexedDB" && names) {
+          let [ dbName, objectStoreName ] = JSON.parse(names[0]);
+          if (dbName) {
+            subType = "database";
+          }
+          if (objectStoreName) {
+            subType = "object store";
+          }
+        }
+        yield this.resetColumns(type, host, subType);
+      }
+
       let {data} = yield storageType.getStoreObjects(host, names, fetchOpts);
       if (data.length) {
-        if (reason === REASON.POPULATE) {
-          yield this.resetColumns(data[0], type, host);
-        }
         this.populateTable(data, reason);
       }
       this.emit("store-objects-updated");
     } catch (ex) {
       console.error(ex);
     }
   }),
 
@@ -725,46 +742,56 @@ StorageUI.prototype = {
     }
     this.fetchStorageObjects(type, host, names, REASON.POPULATE);
     this.itemOffset = 0;
   },
 
   /**
    * Resets the column headers in the storage table with the pased object `data`
    *
-   * @param {object} data
-   *        The object from which key and values will be used for naming the
-   *        headers of the columns
    * @param {string} type
    *        The type of storage corresponding to the after-reset columns in the
    *        table.
    * @param {string} host
    *        The host name corresponding to the table after reset.
+   *
+   * @param {string} [subType]
+   *        The sub type under the given type.
    */
-  resetColumns: function* (data, type, host) {
+  resetColumns: function* (type, host, subtype) {
+    this.table.host = host;
+    this.table.datatype = type;
+
+    let uniqueKey = null;
     let columns = {};
-    let uniqueKey = null;
-    for (let key in data) {
+    let editableFields = [];
+    let fields = yield this.getCurrentActor().getFields(subtype);
+
+    fields.forEach(f => {
       if (!uniqueKey) {
-        this.table.uniqueId = uniqueKey = key;
+        this.table.uniqueId = uniqueKey = f.name;
       }
-      columns[key] = key;
+
+      if (f.editable) {
+        editableFields.push(f.name);
+      }
+
+      columns[f.name] = f.name;
       try {
-        columns[key] = L10N.getStr("table.headers." + type + "." + key);
+        columns[f.name] = L10N.getStr("table.headers." + type + "." + f.name);
       } catch (e) {
         console.error("Unable to localize table header type:" + type +
-                      " key:" + key);
+                      " key:" + f.name);
       }
-    }
+    });
+
     this.table.setColumns(columns, null, HIDDEN_COLUMNS);
-    this.table.datatype = type;
-    this.table.host = host;
     this.hideSidebar();
 
-    yield this.makeFieldsEditable();
+    yield this.makeFieldsEditable(editableFields);
   },
 
   /**
    * Populates or updates the rows in the storage table.
    *
    * @param {array[object]} data
    *        Array of objects to be populated in the storage table
    * @param {Constant} reason
--- a/devtools/server/actors/storage.js
+++ b/devtools/server/actors/storage.js
@@ -76,16 +76,20 @@ var StorageActors = {};
  *               topic.
  *   - getNamesForHost : Given a host, get list of all known store names.
  *   - getValuesForHost : Given a host (and optianally a name) get all known
  *                        store objects.
  *   - toStoreObject : Given a store object, convert it to the required format
  *                     so that it can be transferred over wire.
  *   - populateStoresForHost : Given a host, populate the map of all store
  *                             objects for it
+ *   - getFields: Given a subType(optional), get an array of objects containing
+ *                column field info. The info includes,
+ *                "name" is name of colume key.
+ *                "editable" is 1 means editable field; 0 means uneditable.
  *
  * @param {string} typeName
  *        The typeName of the actor.
  * @param {string} observationTopic
  *        The topic which this actor listens to via Notification Observers.
  */
 StorageActors.defaults = function (typeName, observationTopic) {
   return {
@@ -543,31 +547,27 @@ StorageActors.createActor({
           }
           this.storageActor.update("cleared", "cookies", data);
         }
         break;
     }
     return null;
   },
 
-  /**
-   * This method marks the table as editable.
-   *
-   * @return {Array}
-   *         An array of column header ids.
-   */
-  getEditableFields: Task.async(function* () {
+  getFields: Task.async(function* () {
     return [
-      "name",
-      "path",
-      "host",
-      "expires",
-      "value",
-      "isSecure",
-      "isHttpOnly"
+      { name: "name", editable: 1},
+      { name: "path", editable: 1},
+      { name: "host", editable: 1},
+      { name: "expires", editable: 1},
+      { name: "lastAccessed", editable: 0},
+      { name: "value", editable: 1},
+      { name: "isDomain", editable: 0},
+      { name: "isSecure", editable: 1},
+      { name: "isHttpOnly", editable: 1}
     ];
   }),
 
   /**
    * Pass the editItem command from the content to the chrome process.
    *
    * @param {Object} data
    *        See editCookie() for format details.
@@ -1007,26 +1007,20 @@ function getObjectForLocalOrSessionStora
 
     populateStoresForHosts() {
       this.hostVsStores = new Map();
       for (let window of this.windows) {
         this.populateStoresForHost(this.getHostName(window.location), window);
       }
     },
 
-    /**
-     * This method marks the fields as editable.
-     *
-     * @return {Array}
-     *         An array of field ids.
-     */
-    getEditableFields: Task.async(function* () {
+    getFields: Task.async(function* () {
       return [
-        "name",
-        "value"
+        { name: "name", editable: 1},
+        { name: "value", editable: 1}
       ];
     }),
 
     /**
      * Edit localStorage or sessionStorage fields.
      *
      * @param {Object} data
      *        See editCookie() for format details.
@@ -1194,16 +1188,23 @@ StorageActors.createActor({
 
   processEntry: Task.async(function* (request, response) {
     return {
       url: String(request.url),
       status: String(response.statusText),
     };
   }),
 
+  getFields: Task.async(function* () {
+    return [
+      { name: "url", editable: 0 },
+      { name: "status", editable: 0 }
+    ];
+  }),
+
   getHostName(location) {
     if (!location.host) {
       return location.href;
     }
     return location.protocol + "//" + location.host;
   },
 
   populateStoresForHost: Task.async(function* (host) {
@@ -1648,16 +1649,45 @@ StorageActors.createActor({
       sendAsyncMessage("storage:storage-indexedDB-request-parent", {
         method: methodName,
         args: args
       });
 
       return deferred.promise;
     }
   },
+
+  getFields: Task.async(function* (subType) {
+    switch (subType) {
+      // Detail of database
+      case "database":
+        return [
+          { name: "objectStore", editable: 0 },
+          { name: "keyPath", editable: 0 },
+          { name: "autoIncrement", editable: 0 },
+          { name: "indexes", editable: 0 },
+        ];
+
+      // Detail of object store
+      case "object store":
+        return [
+          { name: "name", editable: 0 },
+          { name: "value", editable: 0 }
+        ];
+
+      // Detail of indexedDB for one origin
+      default:
+        return [
+          { name: "db", editable: 0 },
+          { name: "origin", editable: 0 },
+          { name: "version", editable: 0 },
+          { name: "objectStores", editable: 0 },
+        ];
+    }
+  })
 });
 
 var indexedDBHelpers = {
   backToChild(...args) {
     let mm = Cc["@mozilla.org/globalmessagemanager;1"]
                .getService(Ci.nsIMessageListenerManager);
 
     mm.broadcastAsyncMessage("storage:storage-indexedDB-request-child", {
--- a/devtools/shared/specs/storage.js
+++ b/devtools/shared/specs/storage.js
@@ -13,16 +13,24 @@ function createStorageSpec(options) {
   let methods = {
     getStoreObjects: {
       request: {
         host: Arg(0),
         names: Arg(1, "nullable:array:string"),
         options: Arg(2, "nullable:json")
       },
       response: RetVal(options.storeObjectType)
+    },
+    getFields: {
+      request: {
+        subType: Arg(0, "nullable:string")
+      },
+      response: {
+        value: RetVal("json")
+      }
     }
   };
 
   // extra methods specific for storage type
   Object.assign(methods, options.methods);
 
   childSpecs[options.typeName] = protocol.generateActorSpec({
     typeName: options.typeName,