Bug 1301794 - Allow sparse pseudo-arrays in console. r=fitzgen
☠☠ backed out by 8b57e28ff342 ☠ ☠
authorOriol <oriol-bugzilla@hotmail.com>
Fri, 09 Sep 2016 14:59:00 +0200
changeset 354756 4cba7ff88539a9b47628cd86d0eb86199a7d0077
parent 354755 cd2f33114ac415889028969fa92ea2e803a1d2be
child 354757 8b57e28ff3423249a926dd86b43b0b3b9330784e
push id6570
push userraliiev@mozilla.com
push dateMon, 14 Nov 2016 12:26:13 +0000
treeherdermozilla-beta@f455459b2ae5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfitzgen
bugs1301794
milestone51.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1301794 - Allow sparse pseudo-arrays in console. r=fitzgen
devtools/client/webconsole/test/browser_webconsole_output_06.js
devtools/server/actors/object.js
--- a/devtools/client/webconsole/test/browser_webconsole_output_06.js
+++ b/devtools/client/webconsole/test/browser_webconsole_output_06.js
@@ -126,20 +126,20 @@ var inputTests = [
     printOutput: "[object Object]",
     inspectable: true,
     variablesViewLabel: "Object[2]",
   },
 
   // 14
   {
     input: '({0: "a", 42: "b"})',
-    output: 'Object { 0: "a", 42: "b" }',
+    output: '[ "a", <9 empty slots>, 33 more\u2026 ]',
     printOutput: "[object Object]",
     inspectable: true,
-    variablesViewLabel: "Object",
+    variablesViewLabel: "Object[43]",
   },
 
   // 15
   {
     input: '({0: "a", 1: "b", 2: "c", 3: "d", 4: "e", 5: "f", 6: "g", ' +
            '7: "h", 8: "i", 9: "j", 10: "k", 11: "l"})',
     output: 'Object [ "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", ' +
             "2 more\u2026 ]",
@@ -184,20 +184,20 @@ var inputTests = [
     printOutput: "[object Object]",
     inspectable: true,
     variablesViewLabel: "Object[0]",
   },
 
   // 20
   {
     input: '({length: 1})',
-    output: 'Object { length: 1 }',
+    output: '[ <1 empty slot> ]',
     printOutput: "[object Object]",
     inspectable: true,
-    variablesViewLabel: "Object",
+    variablesViewLabel: "Object[1]",
   },
 
   // 21
   {
     input: '({0: "a", 1: "b", length: 1})',
     output: 'Object { 1: "b", length: 1, 1 more\u2026 }',
     printOutput: "[object Object]",
     inspectable: true,
@@ -211,38 +211,38 @@ var inputTests = [
     printOutput: "[object Object]",
     inspectable: true,
     variablesViewLabel: "Object[2]",
   },
 
   // 23
   {
     input: '({0: "a", 1: "b", length: 3})',
-    output: 'Object { length: 3, 2 more\u2026 }',
+    output: '[ "a", "b", <1 empty slot> ]',
     printOutput: "[object Object]",
     inspectable: true,
-    variablesViewLabel: "Object",
+    variablesViewLabel: "Object[3]",
   },
 
   // 24
   {
     input: '({0: "a", 2: "b", length: 2})',
     output: 'Object { 2: "b", length: 2, 1 more\u2026 }',
     printOutput: "[object Object]",
     inspectable: true,
     variablesViewLabel: "Object",
   },
 
   // 25
   {
     input: '({0: "a", 2: "b", length: 3})',
-    output: 'Object { length: 3, 2 more\u2026 }',
+    output: '[ "a", <1 empty slot>, "b" ]',
     printOutput: "[object Object]",
     inspectable: true,
-    variablesViewLabel: "Object",
+    variablesViewLabel: "Object[3]",
   },
 
   // 26
   {
     input: '({0: "a", b: "b", length: 1})',
     output: 'Object { b: "b", length: 1, 1 more\u2026 }',
     printOutput: "[object Object]",
     inspectable: true,
@@ -252,16 +252,25 @@ var inputTests = [
   // 27
   {
     input: '({0: "a", b: "b", length: 2})',
     output: 'Object { b: "b", length: 2, 1 more\u2026 }',
     printOutput: "[object Object]",
     inspectable: true,
     variablesViewLabel: "Object",
   },
+
+  // 28
+  {
+    input: '({42: "a"})',
+    output: 'Object { 42: "a" }',
+    printOutput: "[object Object]",
+    inspectable: true,
+    variablesViewLabel: "Object",
+  },
 ];
 
 function test() {
   requestLongerTimeout(2);
   Task.spawn(function* () {
     let {tab} = yield loadTab(TEST_URI);
     let hud = yield openConsole(tab);
     return checkOutputForInputs(hud, inputTests);
--- a/devtools/server/actors/object.js
+++ b/devtools/server/actors/object.js
@@ -1788,65 +1788,76 @@ DebuggerServer.ObjectActorPreviewers.Obj
       lineNumber: hooks.createValueGrip(rawObj.lineNumber),
       columnNumber: hooks.createValueGrip(rawObj.columnNumber),
     };
 
     return true;
   },
 
   function PseudoArray({obj, hooks}, grip, rawObj) {
-    let length = 0;
+    let length;
 
     let keys = obj.getOwnPropertyNames();
     if (keys.length == 0) {
       return false;
     }
 
-    // Pseudo-arrays should only have array indices and, optionally, a "length" property.
-    // Since array indices are sorted first, check if the last property is "length".
-    if(keys[keys.length-1] === "length") {
-      keys.pop();
-      // The value of "length" should equal the number of other properties. If eventually
-      // we allow sparse pseudo-arrays, we should check whether it's a Uint32 instead.
-      if(rawObj.length !== keys.length) {
-        return false;
-      }
+    // If no item is going to be displayed in preview, better display as sparse object.
+    // The first key should contain the smallest integer index (if any).
+    if(keys[0] >= OBJECT_PREVIEW_MAX_ITEMS) {
+      return false;
     }
 
-    // Ensure that the keys are consecutive integers starting at "0". If eventually we
-    // allow sparse pseudo-arrays, we should check that they are array indices, that is:
-    // `(key >>> 0) + '' === key && key !== "4294967295"`.
-    // Checking the last property first allows us to avoid useless iterations when
-    // there is any property which is not an array index.
-    if(keys.length && keys[keys.length-1] !== keys.length - 1 + '') {
+    // Pseudo-arrays should only have array indices and, optionally, a "length" property.
+    // Since integer indices are sorted first, check if the last property is "length".
+    if(keys[keys.length-1] === "length") {
+      keys.pop();
+      length = DevToolsUtils.getProperty(obj, "length");
+    } else {
+      // Otherwise, let length be the (presumably) greatest array index plus 1.
+      length = +keys[keys.length-1] + 1;
+    }
+    // Check if length is a valid array length, i.e. is a Uint32 number.
+    if(typeof length !== "number" || length >>> 0 !== length) {
       return false;
     }
-    for (let key of keys) {
-      if (key !== (length++) + '') {
+
+    // Ensure all keys are increasing array indices smaller than length. The order is not
+    // guaranteed for exotic objects but, in most cases, big array indices and properties
+    // which are not integer indices should be at the end. Then, iterating backwards
+    // allows us to return earlier when the object is not completely a pseudo-array.
+    let prev = length;
+    for(let i = keys.length - 1; i >= 0; --i) {
+      let key = keys[i];
+      let numKey = key >>> 0; // ToUint32(key)
+      if (numKey + '' !== key || numKey >= prev) {
         return false;
       }
+      prev = numKey;
     }
 
     grip.preview = {
       kind: "ArrayLike",
       length: length,
     };
 
     // Avoid recursive object grips.
     if (hooks.getGripDepth() > 1) {
       return true;
     }
 
     let items = grip.preview.items = [];
+    let numItems = Math.min(OBJECT_PREVIEW_MAX_ITEMS, length);
 
-    let i = 0;
-    for (let key of keys) {
-      if (rawObj.hasOwnProperty(key) && i++ < OBJECT_PREVIEW_MAX_ITEMS) {
-        let value = makeDebuggeeValueIfNeeded(obj, rawObj[key]);
-        items.push(hooks.createValueGrip(value));
+    for (let i = 0; i < numItems; ++i) {
+      let desc = obj.getOwnPropertyDescriptor(i);
+      if (desc && 'value' in desc) {
+        items.push(hooks.createValueGrip(desc.value));
+      } else {
+        items.push(null);
       }
     }
 
     return true;
   },
 
   GenericObject,
 ];