Bug 943586: Add autocompletion suggestions for members of arrays; r=msucan
authorSami Jaktholm <sjakthol@outlook.com>
Fri, 07 Mar 2014 13:32:41 +0200
changeset 172484 67ccb76be87c898f9191418d6c772a0f1985384f
parent 172483 4886d716a8d98630066f8f39e765c8a977ed8784
child 172485 834c3308130ab81a4f635ca691056c786be421a3
push id26363
push userryanvm@gmail.com
push dateFri, 07 Mar 2014 20:25:45 +0000
treeherdermozilla-central@0d70e6efa22c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmsucan
bugs943586
milestone30.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 943586: Add autocompletion suggestions for members of arrays; r=msucan
toolkit/devtools/webconsole/test/unit/test_js_property_provider.js
toolkit/devtools/webconsole/test/unit/xpcshell.ini
toolkit/devtools/webconsole/utils.js
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/webconsole/test/unit/test_js_property_provider.js
@@ -0,0 +1,71 @@
+/* -*- js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+
+"use strict";
+const { devtools } = Components.utils.import("resource://gre/modules/devtools/Loader.jsm", {});
+let JSPropertyProvider = devtools.require("devtools/toolkit/webconsole/utils").JSPropertyProvider;
+
+Components.utils.import("resource://gre/modules/jsdebugger.jsm");
+addDebuggerToGlobal(this);
+
+function run_test() {
+  const testArray = 'var testArray = [\
+    {propA: "A"},\
+    {\
+      propB: "B", \
+      propC: [\
+        {propD: "D"}\
+      ]\
+    },\
+    [\
+      {propE: "E"}\
+    ]\
+  ];'
+
+  const testObject = 'var testObject = {"propA": [{"propB": "B"}]}';
+
+  let sandbox = Components.utils.Sandbox("http://example.com");
+  let dbg = new Debugger;
+  let dbgObject = dbg.addDebuggee(sandbox);
+  Components.utils.evalInSandbox(testArray, sandbox);
+  Components.utils.evalInSandbox(testObject, sandbox);
+
+  let results = JSPropertyProvider(dbgObject, null, "testArray[0].");
+  do_print("Test that suggestions are given for 'foo[n]' where n is an integer.");
+  test_has_result(results, "propA");
+
+  do_print("Test that suggestions are given for multidimensional arrays.");
+  results = JSPropertyProvider(dbgObject, null, "testArray[2][0].");
+  test_has_result(results, "propE");
+
+  do_print("Test that suggestions are not given for index that's out of bounds.");
+  results = JSPropertyProvider(dbgObject, null, "testArray[10].");
+  do_check_null(results);
+
+  do_print("Test that no suggestions are given if an index is not numerical somewhere in the chain.");
+  results = JSPropertyProvider(dbgObject, null, "testArray[0]['propC'][0].");
+  do_check_null(results);
+
+  results = JSPropertyProvider(dbgObject, null, "testObject['propA'][0].");
+  do_check_null(results);
+
+  results = JSPropertyProvider(dbgObject, null, "testArray[0]['propC'].");
+  do_check_null(results);
+
+  results = JSPropertyProvider(dbgObject, null, "testArray[][1].");
+  do_check_null(results);
+}
+
+/**
+ * A helper that ensures (required) results were found.
+ * @param Object aResults
+ *        The results returned by JSPropertyProvider.
+ * @param String aRequiredSuggestion
+ *        A suggestion that must be found from the results.
+ */
+function test_has_result(aResults, aRequiredSuggestion) {
+  do_check_neq(aResults, null);
+  do_check_true(aResults.matches.length > 0);
+  do_check_true(aResults.matches.indexOf(aRequiredSuggestion) !== -1);
+}
--- a/toolkit/devtools/webconsole/test/unit/xpcshell.ini
+++ b/toolkit/devtools/webconsole/test/unit/xpcshell.ini
@@ -1,6 +1,7 @@
 [DEFAULT]
 head =
 tail =
 support-files =
 
-[test_network_helper.js]
\ No newline at end of file
+[test_js_property_provider.js]
+[test_network_helper.js]
--- a/toolkit/devtools/webconsole/utils.js
+++ b/toolkit/devtools/webconsole/utils.js
@@ -833,32 +833,83 @@ function JSPropertyProvider(aDbgObject, 
   // We get the rest of the properties recursively starting from the Debugger.Object
   // that wraps the first property
   for (let prop of properties) {
     prop = prop.trim();
     if (!prop) {
       return null;
     }
 
-    obj = DevToolsUtils.getProperty(obj, prop);
+    if (/\[\d+\]$/.test(prop)) {
+      // The property to autocomplete is a member of array. For example
+      // list[i][j]..[n]. Traverse the array to get the actual element.
+      obj = getArrayMemberProperty(obj, prop);
+    }
+    else {
+      obj = DevToolsUtils.getProperty(obj, prop);
+    }
 
     if (!isObjectUsable(obj)) {
       return null;
     }
   }
 
   // If the final property is a primitive
   if (typeof obj != "object") {
     return getMatchedProps(obj, matchProp);
   }
 
   return getMatchedPropsInDbgObject(obj, matchProp);
 }
 
 /**
+ * Get the array member of aObj for the given aProp. For example, given
+ * aProp='list[0][1]' the element at [0][1] of aObj.list is returned.
+ *
+ * @param object aObj
+ *        The object to operate on.
+ * @param string aProp
+ *        The property to return.
+ * @return null or Object
+ *         Returns null if the property couldn't be located. Otherwise the array
+ *         member identified by aProp.
+ */
+function getArrayMemberProperty(aObj, aProp)
+{
+  // First get the array.
+  let obj = aObj;
+  let propWithoutIndices = aProp.substr(0, aProp.indexOf("["));
+  obj = DevToolsUtils.getProperty(obj, propWithoutIndices);
+  if (!isObjectUsable(obj)) {
+    return null;
+  }
+
+  // Then traverse the list of indices to get the actual element.
+  let result;
+  let arrayIndicesRegex = /\[[^\]]*\]/g;
+  while ((result = arrayIndicesRegex.exec(aProp)) !== null) {
+    let indexWithBrackets = result[0];
+    let indexAsText = indexWithBrackets.substr(1, indexWithBrackets.length - 2);
+    let index = parseInt(indexAsText);
+
+    if (isNaN(index)) {
+      return null;
+    }
+
+    obj = DevToolsUtils.getProperty(obj, index);
+
+    if (!isObjectUsable(obj)) {
+      return null;
+    }
+  }
+
+  return obj;
+}
+
+/**
  * Check if the given Debugger.Object can be used for autocomplete.
  *
  * @param Debugger.Object aObject
  *        The Debugger.Object to check.
  * @return boolean
  *         True if further inspection into the object is possible, or false
  *         otherwise.
  */