Bug 1316266 - Handle grip `safeGetterValues` property in Grip Reps; r=Honza draft
authorNicolas Chevobbe <chevobbe.nicolas@gmail.com>
Wed, 09 Nov 2016 13:13:45 +0100
changeset 436756 f001a57ab8785dd12dde6afce24cd2c899d60f10
parent 436738 336759fad4621dfcd0a3293840edbed67018accd
child 536424 a0089ad7ba4e4982e1e9f6d70d63d2b71eea70a6
push id35177
push userchevobbe.nicolas@gmail.com
push dateWed, 09 Nov 2016 18:17:42 +0000
reviewersHonza
bugs1316266
milestone52.0a1
Bug 1316266 - Handle grip `safeGetterValues` property in Grip Reps; r=Honza Some object has properties in the safeGetterValues property, like ArrayBuffer for example. We now evaluate this property too when building the Rep. A function was created to retrieve the property value since in safeGetterValues, the value is a wrapper object with a getterValue property holding the actual value. We add some tests to make sure we handle those grips as expected. MozReview-Commit-ID: JqnTXnnAZpd
devtools/client/shared/components/reps/grip.js
devtools/client/shared/components/test/mochitest/test_reps_grip.html
--- a/devtools/client/shared/components/reps/grip.js
+++ b/devtools/client/shared/components/reps/grip.js
@@ -64,32 +64,40 @@ define(function (require, exports, modul
       let isInterestingProp = this.props.isInterestingProp || ((type, value) => {
         return (
           type == "boolean" ||
           type == "number" ||
           (type == "string" && value.length != 0)
         );
       });
 
-      let ownProperties = object.preview ? object.preview.ownProperties : {};
+      let properties = object.preview
+        ? object.preview.ownProperties
+        : {};
       let propertiesLength = object.preview && object.preview.ownPropertiesLength
         ? object.preview.ownPropertiesLength
         : object.ownPropertyLength;
-      let indexes = this.getPropIndexes(ownProperties, max, isInterestingProp);
+
+      if (object.preview && object.preview.safeGetterValues) {
+        properties = Object.assign({}, properties, object.preview.safeGetterValues);
+        propertiesLength += Object.keys(object.preview.safeGetterValues).length;
+      }
+
+      let indexes = this.getPropIndexes(properties, max, isInterestingProp);
       if (indexes.length < max && indexes.length < propertiesLength) {
         // There are not enough props yet. Then add uninteresting props to display them.
         indexes = indexes.concat(
-          this.getPropIndexes(ownProperties, max - indexes.length, (t, value, name) => {
+          this.getPropIndexes(properties, max - indexes.length, (t, value, name) => {
             return !isInterestingProp(t, value, name);
           })
         );
       }
 
-      const truncate = Object.keys(ownProperties).length > max;
-      let props = this.getProps(ownProperties, indexes, truncate);
+      const truncate = Object.keys(properties).length > max;
+      let props = this.getProps(properties, indexes, truncate);
       if (truncate) {
         // There are some undisplayed props. Then display "more...".
         let objectLink = this.props.objectLink || span;
 
         props.push(Caption({
           object: objectLink({
             object: object
           }, `${object.ownPropertyLength - max} moreā€¦`)
@@ -97,82 +105,98 @@ define(function (require, exports, modul
       }
 
       return props;
     },
 
     /**
      * Get props ordered by index.
      *
-     * @param {Object} ownProperties Props object.
+     * @param {Object} properties Props object.
      * @param {Array} indexes Indexes of props.
      * @param {Boolean} truncate true if the grip will be truncated.
      * @return {Array} Props.
      */
-    getProps: function (ownProperties, indexes, truncate) {
+    getProps: function (properties, indexes, truncate) {
       let props = [];
 
       // Make indexes ordered by ascending.
       indexes.sort(function (a, b) {
         return a - b;
       });
 
       indexes.forEach((i) => {
-        let name = Object.keys(ownProperties)[i];
-        let prop = ownProperties[name];
-        let value = prop.value !== undefined ? prop.value : prop;
+        let name = Object.keys(properties)[i];
+        let value = this.getPropValue(properties[name]);
+
         props.push(PropRep(Object.assign({}, this.props, {
           mode: "tiny",
           name: name,
           object: value,
           equal: ": ",
           delim: i !== indexes.length - 1 || truncate ? ", " : "",
           defaultRep: Grip
         })));
       });
 
       return props;
     },
 
     /**
      * Get the indexes of props in the object.
      *
-     * @param {Object} ownProperties Props object.
+     * @param {Object} properties Props object.
      * @param {Number} max The maximum length of indexes array.
      * @param {Function} filter Filter the props you want.
      * @return {Array} Indexes of interesting props in the object.
      */
-    getPropIndexes: function (ownProperties, max, filter) {
+    getPropIndexes: function (properties, max, filter) {
       let indexes = [];
 
       try {
         let i = 0;
-        for (let name in ownProperties) {
+        for (let name in properties) {
           if (indexes.length >= max) {
             return indexes;
           }
 
-          let prop = ownProperties[name];
-          let value = prop.value !== undefined ? prop.value : prop;
-
           // Type is specified in grip's "class" field and for primitive
           // values use typeof.
+          let value = this.getPropValue(properties[name]);
           let type = (value.class || typeof value);
           type = type.toLowerCase();
 
           if (filter(type, value, name)) {
             indexes.push(i);
           }
           i++;
         }
       } catch (err) {
         console.error(err);
       }
+      return indexes;
+    },
 
-      return indexes;
+    /**
+     * Get the actual value of a property.
+     *
+     * @param {Object} property
+     * @return {Object} Value of the property.
+     */
+    getPropValue: function (property) {
+      let value = property;
+      if (typeof property === "object") {
+        let keys = Object.keys(property);
+        if (keys.includes("value")) {
+          value = property.value;
+        } else if (keys.includes("getterValue")) {
+          value = property.getterValue;
+        }
+      }
+      return value;
     },
 
     render: function () {
       let object = this.props.object;
       let props = this.safePropIterator(object,
         (this.props.mode == "long") ? 100 : 3);
 
       let objectLink = this.props.objectLink || span;
--- a/devtools/client/shared/components/test/mochitest/test_reps_grip.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_grip.html
@@ -23,16 +23,18 @@ window.onload = Task.async(function* () 
   const componentUnderTest = Grip;
 
   try {
     yield testBasic();
     yield testBooleanObject();
     yield testNumberObject();
     yield testStringObject();
     yield testProxy();
+    yield testArrayBuffer();
+    yield testSharedArrayBuffer();
 
     // Test property iterator
     yield testMaxProps();
     yield testMoreThanMaxProps();
     yield testUninterestingProps();
     yield testNonEnumerableProps();
 
     // Test that properties are rendered as expected by PropRep
@@ -212,16 +214,84 @@ window.onload = Task.async(function* () 
         mode: "long",
         expectedOutput: defaultOutput,
       }
     ];
 
     testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
   }
 
+  function testArrayBuffer() {
+    // Test object: `new ArrayBuffer(10)`
+    const testName = "testArrayBuffer";
+
+    // Test that correct rep is chosen
+    const gripStub = getGripStub(testName);
+    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
+    is(renderedRep.type, Grip.rep, `Rep correctly selects ${Grip.rep.displayName}`);
+
+    // Test rendering
+    const defaultOutput = `ArrayBuffer { byteLength: 10 }`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "tiny",
+        expectedOutput: `ArrayBuffer`,
+      },
+      {
+        mode: "short",
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "long",
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
+  }
+
+  function testSharedArrayBuffer() {
+    // Test object: `new SharedArrayBuffer(5)`
+    const testName = "testSharedArrayBuffer";
+
+    // Test that correct rep is chosen
+    const gripStub = getGripStub(testName);
+    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
+    is(renderedRep.type, Grip.rep, `Rep correctly selects ${Grip.rep.displayName}`);
+
+    // Test rendering
+    const defaultOutput = `SharedArrayBuffer { byteLength: 5 }`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "tiny",
+        expectedOutput: `SharedArrayBuffer`,
+      },
+      {
+        mode: "short",
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "long",
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
+  }
+
   function testMaxProps() {
     // Test object: `{a: "a", b: "b", c: "c"}`;
     const testName = "testMaxProps";
 
     const defaultOutput = `Object { a: "a", b: "b", c: "c" }`;
 
     const modeTests = [
       {
@@ -757,15 +827,61 @@ window.onload = Task.async(function* () 
                     "length": 3
                   }
                 }
               }
             },
             "ownPropertiesLength": 2
           }
         };
+      case "testArrayBuffer":
+        return {
+          "type": "object",
+          "actor": "server1.conn1.child1/obj170",
+          "class": "ArrayBuffer",
+          "extensible": true,
+          "frozen": false,
+          "sealed": false,
+          "ownPropertyLength": 0,
+          "preview": {
+            "kind": "Object",
+            "ownProperties": {},
+            "ownPropertiesLength": 0,
+            "safeGetterValues": {
+              "byteLength": {
+                "getterValue": 10,
+                "getterPrototypeLevel": 1,
+                "enumerable": false,
+                "writable": true
+              }
+            }
+          }
+        };
+      case "testSharedArrayBuffer":
+        return {
+          "type": "object",
+          "actor": "server1.conn1.child1/obj171",
+          "class": "SharedArrayBuffer",
+          "extensible": true,
+          "frozen": false,
+          "sealed": false,
+          "ownPropertyLength": 0,
+          "preview": {
+            "kind": "Object",
+            "ownProperties": {},
+            "ownPropertiesLength": 0,
+            "safeGetterValues": {
+              "byteLength": {
+                "getterValue": 5,
+                "getterPrototypeLevel": 1,
+                "enumerable": false,
+                "writable": true
+              }
+            }
+          }
+        };
     }
   }
 });
 </script>
 </pre>
 </body>
 </html>