Bug 943496 - Autocomplete should execute native getters; r=past
authorMihai Sucan <mihai.sucan@gmail.com>
Tue, 03 Dec 2013 15:32:41 +0200
changeset 158493 e7f5c3b76b290155396f40ec1fe182da7b317323
parent 158492 0dfe710c2d6428131175d1f574f97174b07ce5f9
child 158494 921670f4c865d2189f17aa609bf048ddc19b92a0
push id25748
push userryanvm@gmail.com
push dateTue, 03 Dec 2013 21:42:03 +0000
treeherdermozilla-central@03a55dd19083 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspast
bugs943496
milestone28.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 943496 - Autocomplete should execute native getters; r=past
browser/devtools/webconsole/test/browser_webconsole_autocomplete_in_debugger_stackframe.js
browser/devtools/webconsole/test/browser_webconsole_bug_651501_document_body_autocomplete.js
toolkit/devtools/DevToolsUtils.js
toolkit/devtools/DevToolsUtils.jsm
toolkit/devtools/server/actors/script.js
toolkit/devtools/webconsole/utils.js
--- a/browser/devtools/webconsole/test/browser_webconsole_autocomplete_in_debugger_stackframe.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_autocomplete_in_debugger_stackframe.js
@@ -29,23 +29,41 @@ function testNext() {
   });
 }
 
 function testCompletion(hud) {
   let jsterm = hud.jsterm;
   let input = jsterm.inputNode;
   let popup = jsterm.autocompletePopup;
 
+  // Test that document.title gives string methods. Native getters must execute.
+  input.value = "document.title.";
+  input.setSelectionRange(input.value.length, input.value.length);
+  jsterm.complete(jsterm.COMPLETE_HINT_ONLY, testNext);
+  yield undefined;
+
+  let newItems = popup.getItems();
+  ok(newItems.length > 0, "'document.title.' gave a list of suggestions");
+  ok(newItems.some(function(item) {
+       return item.label == "substr";
+     }), "autocomplete results do contain substr");
+  ok(newItems.some(function(item) {
+       return item.label == "toLowerCase";
+     }), "autocomplete results do contain toLowerCase");
+  ok(newItems.some(function(item) {
+       return item.label == "strike";
+     }), "autocomplete results do contain strike");
+
   // Test if 'f' gives 'foo1' but not 'foo2' or 'foo3'
   input.value = "f";
   input.setSelectionRange(1, 1);
   jsterm.complete(jsterm.COMPLETE_HINT_ONLY, testNext);
   yield undefined;
 
-  let newItems = popup.getItems();
+  newItems = popup.getItems();
   ok(newItems.length > 0, "'f' gave a list of suggestions");
   ok(!newItems.every(function(item) {
        return item.label != "foo1";
      }), "autocomplete results do contain foo1");
   ok(!newItems.every(function(item) {
        return item.label != "foo1Obj";
      }), "autocomplete results do contain foo1Obj");
   ok(newItems.every(function(item) {
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_651501_document_body_autocomplete.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_651501_document_body_autocomplete.js
@@ -24,21 +24,24 @@ function consoleOpened(aHud) {
 
   ok(!popup.isOpen, "popup is not open");
 
   popup._panel.addEventListener("popupshown", function onShown() {
     popup._panel.removeEventListener("popupshown", onShown, false);
 
     ok(popup.isOpen, "popup is open");
 
-    // expected properties:
-    // __defineGetter__  __defineSetter__ __lookupGetter__ __lookupSetter__
-    // constructor hasOwnProperty isPrototypeOf propertyIsEnumerable
-    // toLocaleString toSource toString unwatch valueOf watch.
-    ok(popup.itemCount >= 14, "popup.itemCount is correct");
+    is(popup.itemCount, jsterm._autocompleteCache.length,
+       "popup.itemCount is correct");
+    isnot(jsterm._autocompleteCache.indexOf("addEventListener"), -1,
+          "addEventListener is in the list of suggestions");
+    isnot(jsterm._autocompleteCache.indexOf("bgColor"), -1,
+          "bgColor is in the list of suggestions");
+    isnot(jsterm._autocompleteCache.indexOf("ATTRIBUTE_NODE"), -1,
+          "ATTRIBUTE_NODE is in the list of suggestions");
 
     popup._panel.addEventListener("popuphidden", autocompletePopupHidden, false);
 
     EventUtils.synthesizeKey("VK_ESCAPE", {});
   }, false);
 
   jsterm.setInputValue("document.body");
   EventUtils.synthesizeKey(".", {});
--- a/toolkit/devtools/DevToolsUtils.js
+++ b/toolkit/devtools/DevToolsUtils.js
@@ -161,8 +161,53 @@ function defineLazyPrototypeGetter(aObje
         writable: true,
         value: value
       });
 
       return value;
     }
   });
 }
+
+/**
+ * Safely get the property value from a Debugger.Object for a given key. Walks
+ * the prototype chain until the property is found.
+ *
+ * @param Debugger.Object aObject
+ *        The Debugger.Object to get the value from.
+ * @param String aKey
+ *        The key to look for.
+ * @return Any
+ */
+this.getProperty = function getProperty(aObj, aKey) {
+  let root = aObj;
+  try {
+    do {
+      const desc = aObj.getOwnPropertyDescriptor(aKey);
+      if (desc) {
+        if ("value" in desc) {
+          return desc.value;
+        }
+        // Call the getter if it's safe.
+        return hasSafeGetter(desc) ? desc.get.call(root).return : undefined;
+      }
+      aObj = aObj.proto;
+    } while (aObj);
+  } catch (e) {
+    // If anything goes wrong report the error and return undefined.
+    reportException("getProperty", e);
+  }
+  return undefined;
+};
+
+/**
+ * Determines if a descriptor has a getter which doesn't call into JavaScript.
+ *
+ * @param Object aDesc
+ *        The descriptor to check for a safe getter.
+ * @return Boolean
+ *         Whether a safe getter was found.
+ */
+this.hasSafeGetter = function hasSafeGetter(aDesc) {
+  let fn = aDesc.get;
+  return fn && fn.callable && fn.class == "Function" && fn.script === undefined;
+};
+
--- a/toolkit/devtools/DevToolsUtils.jsm
+++ b/toolkit/devtools/DevToolsUtils.jsm
@@ -19,10 +19,12 @@ Components.classes["@mozilla.org/moz/jss
   .loadSubScript("resource://gre/modules/devtools/DevToolsUtils.js", this);
 
 this.DevToolsUtils = {
   safeErrorString: safeErrorString,
   reportException: reportException,
   makeInfallible: makeInfallible,
   yieldingEach: yieldingEach,
   reportingDisabled: false , // Used by tests.
-  defineLazyPrototypeGetter: defineLazyPrototypeGetter
+  defineLazyPrototypeGetter: defineLazyPrototypeGetter,
+  getProperty: getProperty,
+  hasSafeGetter: hasSafeGetter,
 };
--- a/toolkit/devtools/server/actors/script.js
+++ b/toolkit/devtools/server/actors/script.js
@@ -2656,59 +2656,16 @@ SourceActor.prototype.requestTypes = {
  *         Whether the value is non-primitive.
  */
 function isObject(aValue) {
   const type = typeof aValue;
   return type == "object" ? aValue !== null : type == "function";
 }
 
 /**
- * Determines if a descriptor has a getter which doesn't call into JavaScript.
- *
- * @param Object aDesc
- *        The descriptor to check for a safe getter.
- * @return Boolean
- *         Whether a safe getter was found.
- */
-function hasSafeGetter(aDesc) {
-  let fn = aDesc.get;
-  return fn && fn.callable && fn.class == "Function" && fn.script === undefined;
-}
-
-/**
- * Safely get the property value from a Debugger.Object for a given key. Walks
- * the prototype chain until the property is found.
- *
- * @param Debugger.Object aObject
- *        The Debugger.Object to get the value from.
- * @param String aKey
- *        The key to look for.
- * @return Any
- */
-function getProperty(aObj, aKey) {
-  try {
-    do {
-      const desc = aObj.getOwnPropertyDescriptor(aKey);
-      if (desc) {
-        if ("value" in desc) {
-          return desc.value
-        }
-        // Call the getter if it's safe.
-        return hasSafeGetter(desc) ? desc.get.call(aObj) : undefined;
-      }
-      aObj = aObj.proto;
-    } while (aObj);
-  } catch (e) {
-    // If anything goes wrong report the error and return undefined.
-    DevToolsUtils.reportException("getProperty", e);
-  }
-  return undefined;
-}
-
-/**
  * Create a function that can safely stringify Debugger.Objects of a given
  * builtin type.
  *
  * @param Function aCtor
  *        The builtin class constructor.
  * @return Function
  *         The stringifier for the class.
  */
@@ -2720,24 +2677,24 @@ function createBuiltinStringifier(aCtor)
  * Stringify a Debugger.Object-wrapped Error instance.
  *
  * @param Debugger.Object aObj
  *        The object to stringify.
  * @return String
  *         The stringification of the object.
  */
 function errorStringify(aObj) {
-  let name = getProperty(aObj, "name");
+  let name = DevToolsUtils.getProperty(aObj, "name");
   if (name === "" || name === undefined) {
     name = aObj.class;
   } else if (isObject(name)) {
     name = stringify(name);
   }
 
-  let message = getProperty(aObj, "message");
+  let message = DevToolsUtils.getProperty(aObj, "message");
   if (isObject(message)) {
     message = stringify(message);
   }
 
   if (message === "" || message === undefined) {
     return name;
   }
   return name + ": " + message;
@@ -2785,17 +2742,17 @@ let stringifiers = {
     if (topLevel) {
       seen = new Set();
     } else if (seen.has(obj)) {
       return "";
     }
 
     seen.add(obj);
 
-    const len = getProperty(obj, "length");
+    const len = DevToolsUtils.getProperty(obj, "length");
     let string = "";
 
     // The following check is only required because the debuggee could possibly
     // be a Proxy and return any value. For normal objects, array.length is
     // always a non-negative integer.
     if (typeof len == "number" && len > 0) {
       for (let i = 0; i < len; i++) {
         const desc = obj.getOwnPropertyDescriptor(i);
@@ -2814,20 +2771,20 @@ let stringifiers = {
 
     if (topLevel) {
       seen = null;
     }
 
     return string;
   },
   DOMException: obj => {
-    const message = getProperty(obj, "message") || "<no message>";
-    const result = (+getProperty(obj, "result")).toString(16);
-    const code = getProperty(obj, "code");
-    const name = getProperty(obj, "name") || "<unknown>";
+    const message = DevToolsUtils.getProperty(obj, "message") || "<no message>";
+    const result = (+DevToolsUtils.getProperty(obj, "result")).toString(16);
+    const code = DevToolsUtils.getProperty(obj, "code");
+    const name = DevToolsUtils.getProperty(obj, "name") || "<unknown>";
 
     return '[Exception... "' + message + '" ' +
            'code: "' + code +'" ' +
            'nsresult: "0x' + result + ' (' + name + ')"]';
   }
 };
 
 /**
@@ -3097,17 +3054,17 @@ ObjectActor.prototype = {
       } catch (e) {
         // Calling getOwnPropertyDescriptor on wrapped native prototypes is not
         // allowed (bug 560072).
       }
       if (!desc || desc.value !== undefined || !("get" in desc)) {
         continue;
       }
 
-      if (hasSafeGetter(desc)) {
+      if (DevToolsUtils.hasSafeGetter(desc)) {
         getters.add(name);
       }
     }
 
     aObject._safeGetters = getters;
     return getters;
   },
 
--- a/toolkit/devtools/webconsole/utils.js
+++ b/toolkit/devtools/webconsole/utils.js
@@ -19,16 +19,17 @@ loader.lazyServiceGetter(this, "gActivit
                          "@mozilla.org/network/http-activity-distributor;1",
                          "nsIHttpActivityDistributor");
 
 // TODO: Bug 842672 - toolkit/ imports modules from browser/.
 // Note that these are only used in JSTermHelpers, see $0 and pprint().
 loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
 loader.lazyImporter(this, "devtools", "resource://gre/modules/devtools/Loader.jsm");
 loader.lazyImporter(this, "VariablesView", "resource:///modules/devtools/VariablesView.jsm");
+loader.lazyImporter(this, "DevToolsUtils", "resource://gre/modules/devtools/DevToolsUtils.jsm");
 
 // Match the function name from the result of toString() or toSource().
 //
 // Examples:
 // (function foobar(a, b) { ...
 // function foobar2(a) { ...
 // function() { ...
 const REGEX_MATCH_FUNCTION_NAME = /^\(?function\s+([^(\s]+)\s*\(/;
@@ -789,299 +790,259 @@ function JSPropertyProvider(aDbgObject, 
 
   let completionPart = inputValue.substring(beginning.startPos);
 
   // Don't complete on just an empty string.
   if (completionPart.trim() == "") {
     return null;
   }
 
-  let matches = null;
-  let matchProp = "";
-
   let lastDot = completionPart.lastIndexOf(".");
   if (lastDot > 0 &&
       (completionPart[0] == "'" || completionPart[0] == '"') &&
       completionPart[lastDot - 1] == completionPart[0]) {
     // We are completing a string literal.
-    let obj = String.prototype;
-    matchProp = completionPart.slice(lastDot + 1);
-    let matches = Object.keys(getMatchedProps(obj, {matchProp:matchProp}));
-
-    return {
-      matchProp: matchProp,
-      matches: matches,
-    };
+    let matchProp = completionPart.slice(lastDot + 1);
+    return getMatchedProps(String.prototype, matchProp);
   }
-  else {
-    // We are completing a variable / a property lookup.
-    let properties = completionPart.split(".");
-    if (properties.length > 1) {
-      matchProp = properties.pop().trimLeft();
-      let obj;
 
-      //The first property must be found in the environment or the Debugger.Object 
-      //depending of whether the debugger is paused or not
-      let prop = properties[0];
-      if (anEnvironment) {
-        obj = getVariableInEnvironment(anEnvironment, prop);
-      }
-      else {
-        obj = getPropertyInDebuggerObject(aDbgObject, prop);
-      }
-      if (obj == null) {
-        return null;
-      }
-
-      //We get the rest of the properties recursively starting from the Debugger.Object
-      // that wraps the first property
-      for (let i = 1; i < properties.length; i++) {
-        let prop = properties[i].trim();
-        if (!prop) {
-          return null;
-        }
+  // We are completing a variable / a property lookup.
+  let properties = completionPart.split(".");
+  let matchProp = properties.pop().trimLeft();
+  let obj = aDbgObject;
 
-        obj = getPropertyInDebuggerObject(obj, prop);
+  // The first property must be found in the environment if the debugger is
+  // paused.
+  if (anEnvironment) {
+    if (properties.length == 0) {
+      return getMatchedPropsInEnvironment(anEnvironment, matchProp);
+    }
+    obj = getVariableInEnvironment(anEnvironment, properties.shift());
+  }
 
-        // If obj is undefined or null (which is what "== null" does),
-        // then there is no chance to run completion on it. Exit here.
-        if (obj == null) {
-          return null;
-        }
-      }
-
-      // If the final property is a primitive
-      if (typeof obj != 'object' || obj === null) {
-        matchProp = completionPart.slice(lastDot + 1);
-        let matches = Object.keys(getMatchedProps(obj, {matchProp:matchProp}));
+  if (!isObjectUsable(obj)) {
+    return null;
+  }
 
-        return {
-          matchProp: matchProp,
-          matches: matches,
-        };
-      }
-      return getMatchedPropsInDbgObject(obj, matchProp);
+  // 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;
     }
-    else {
-      matchProp = properties[0].trimLeft();
-      if (anEnvironment) {
-        return getMatchedPropsInEnvironment(anEnvironment, matchProp);
-      }
-      else {
-        if (typeof aDbgObject != 'object' || aDbgObject === null) {
-          matchProp = completionPart.slice(lastDot + 1);
-          let matches = Object.keys(getMatchedProps(aDbgObject, {matchProp:matchProp}));
+
+    obj = DevToolsUtils.getProperty(obj, prop);
 
-          return {
-            matchProp: matchProp,
-            matches: matches,
-          };
-        }
-        return getMatchedPropsInDbgObject(aDbgObject, matchProp);
-      }
+    if (!isObjectUsable(obj)) {
+      return null;
     }
   }
+
+  // If the final property is a primitive
+  if (typeof obj != "object") {
+    return getMatchedProps(obj, matchProp);
+  }
+
+  return getMatchedPropsInDbgObject(obj, matchProp);
+}
+
+/**
+ * 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.
+ */
+function isObjectUsable(aObject)
+{
+  if (aObject == null) {
+    return false;
+  }
+
+  if (typeof aObject == "object" && aObject.class == "DeadObject") {
+    return false;
+  }
+
+  return true;
 }
 
 /**
- * Returns the value of aProp in anEnvironment as a debuggee value, by recursively checking the environment chain
- *
- * @param object anEnvironment
- *        A Debugger.Environment to look the aProp into.
- * @param string aProp
- *        The property that is looked up.
- * @returns null or object
- *        A Debugger.Object if aProp exists in the environment chain, null otherwise.
+ * @see getExactMatch_impl()
  */
-function getVariableInEnvironment(anEnvironment, aProp)
+function getVariableInEnvironment(anEnvironment, aName)
+{
+  return getExactMatch_impl(anEnvironment, aName, DebuggerEnvironmentSupport);
+}
+
+/**
+ * @see getMatchedProps_impl()
+ */
+function getMatchedPropsInEnvironment(anEnvironment, aMatch)
 {
-  for (let env = anEnvironment; env; env = env.parent) {
-    try {
-      let obj = env.getVariable(aProp);
-      if (obj) {
-        return obj;
-      }
-    }
-    catch (ex) {
-      return null;
-    }
+  return getMatchedProps_impl(anEnvironment, aMatch, DebuggerEnvironmentSupport);
+}
+
+/**
+ * @see getMatchedProps_impl()
+ */
+function getMatchedPropsInDbgObject(aDbgObject, aMatch)
+{
+  return getMatchedProps_impl(aDbgObject, aMatch, DebuggerObjectSupport);
+}
+
+/**
+ * @see getMatchedProps_impl()
+ */
+function getMatchedProps(aObj, aMatch)
+{
+  if (typeof aObj != "object") {
+    aObj = aObj.constructor.prototype;
   }
-  return null;
+  return getMatchedProps_impl(aObj, aMatch, JSObjectSupport);
 }
 
 /**
- * Returns the value of aProp in aDbgObject as a debuggee value, by recursively checking the prototype chain
+ * Get all properties in the given object (and its parent prototype chain) that
+ * match a given prefix.
  *
- * @param object aDbgObject
- *        A Debugger.Object to look the aProp into.
- * @param string aProp
- *        The property that is looked up.
- * @returns null or object
- *        A Debugger.Object if aProp exists in the prototype chain, null otherwise.
- */
-function getPropertyInDebuggerObject(aDbgObject, aProp)
-{
-  let dbgObject = aDbgObject;
-  while (dbgObject) {
-    try {
-      let desc = dbgObject.getOwnPropertyDescriptor(aProp)
-      if (desc) {
-        let obj = desc.value;
-        if (obj)
-          return obj;
-        obj = desc.get;
-        if (obj)
-          return obj;
-      }
-      dbgObject = dbgObject.proto;
-    }
-    catch (ex) {
-      return null;
-    }
-  }
-  return null;
-}
-
-/**
- * Get all properties on the given Debugger.Environment (and its parent chain) that match a given prefix.
- *
- * @param Debugger.Environment anEnvironment
- *        Debugger.Environment whose properties we want to filter.
- *
- * @param string matchProp Filter for properties that match this one.
- *
+ * @param mixed aObj
+ *        Object whose properties we want to filter.
+ * @param string aMatch
+ *        Filter for properties that match this string.
  * @return object
  *         Object that contains the matchProp and the list of names.
  */
-function getMatchedPropsInEnvironment(anEnvironment, matchProp)
+function getMatchedProps_impl(aObj, aMatch, {chainIterator, getProperties})
 {
-  let names = Object.create(null);
-  let c = MAX_COMPLETIONS;
-  for (let env = anEnvironment; env; env = env.parent) {
-    let ownNames = env.names();
-    for (let i = 0; i < ownNames.length; i++) {
-      if (ownNames[i].indexOf(matchProp) != 0 ||
-        ownNames[i] in names) {
+  let matches = new Set();
+
+  // We need to go up the prototype chain.
+  let iter = chainIterator(aObj);
+  for (let obj of iter) {
+    let props = getProperties(obj);
+    for (let prop of props) {
+      if (prop.indexOf(aMatch) != 0) {
         continue;
       }
-      c--;
-      if (c < 0) {
-        return {
-          matchProp: matchProp,
-          matches: Object.keys(names)
-        };
+
+      // If it is an array index, we can't take it.
+      // This uses a trick: converting a string to a number yields NaN if
+      // the operation failed, and NaN is not equal to itself.
+      if (+prop != +prop) {
+        matches.add(prop);
       }
-      names[ownNames[i]] = true;
+
+      if (matches.size > MAX_COMPLETIONS) {
+        break;
+      }
+    }
+
+    if (matches.size > MAX_COMPLETIONS) {
+      break;
     }
   }
+
   return {
-    matchProp: matchProp,
-    matches: Object.keys(names)
+    matchProp: aMatch,
+    matches: [...matches],
   };
 }
 
 /**
- * Get all properties on the given Debugger.Object (and the prototype chain of the wrapped value) that match a given prefix.
- *
- * @param Debugger.Object aDbgObject
- *        Debugger.Object whose properties we want to filter.
+ * Returns a property value based on its name from the given object, by
+ * recursively checking the object's prototype.
  *
- * @param string matchProp Filter for properties that match this one.
- *
- * @return object
- *         Object that contains the matchProp and the list of names.
+ * @param object aObj
+ *        An object to look the property into.
+ * @param string aName
+ *        The property that is looked up.
+ * @returns object|undefined
+ *        A Debugger.Object if the property exists in the object's prototype
+ *        chain, undefined otherwise.
  */
-function getMatchedPropsInDbgObject(aDbgObject, matchProp)
+function getExactMatch_impl(aObj, aName, {chainIterator, getProperty})
 {
-  let names = Object.create(null);
-  let c = MAX_COMPLETIONS;
-  for (let dbg = aDbgObject; dbg; dbg = dbg.proto) {
-    let raw = dbg.unsafeDereference();
-    if (Cu.isDeadWrapper(raw)) {
-      return null;
-    }
-    let ownNames = dbg.getOwnPropertyNames();
-    for (let i = 0; i < ownNames.length; i++) {
-      if (ownNames[i].indexOf(matchProp) != 0 ||
-        ownNames[i] in names) {
-        continue;
-      }
-      c--;
-      if (c < 0) {
-        return {
-          matchProp: matchProp,
-          matches: Object.keys(names)
-        };
-      }
-      names[ownNames[i]] = true;
+  // We need to go up the prototype chain.
+  let iter = chainIterator(aObj);
+  for (let obj of iter) {
+    let prop = getProperty(obj, aName, aObj);
+    if (prop) {
+      return prop.value;
     }
   }
-  return {
-    matchProp: matchProp,
-    matches: Object.keys(names)
-  };
+  return undefined;
 }
 
-/**
- * Get all accessible properties on this JS value.
- * Filter those properties by name.
- * Take only a certain number of those.
- *
- * @param mixed aObj
- *        JS value whose properties we want to collect.
- *
- * @param object aOptions
- *        Options that the algorithm takes.
- *        - matchProp (string): Filter for properties that match this one.
- *          Defaults to the empty string (which always matches).
- *
- * @return object
- *         Object whose keys are all accessible properties on the object.
- */
-function getMatchedProps(aObj, aOptions = {matchProp: ""})
-{
-  // Argument defaults.
-  aOptions.matchProp = aOptions.matchProp || "";
+
+let JSObjectSupport = {
+  chainIterator: function(aObj)
+  {
+    while (aObj) {
+      yield aObj;
+      aObj = Object.getPrototypeOf(aObj);
+    }
+  },
+
+  getProperties: function(aObj)
+  {
+    return Object.getOwnPropertyNames(aObj);
+  },
+
+  getProperty: function()
+  {
+    // getProperty is unsafe with raw JS objects.
+    throw "Unimplemented!";
+  },
+};
+
+let DebuggerObjectSupport = {
+  chainIterator: function(aObj)
+  {
+    while (aObj) {
+      yield aObj;
+      aObj = aObj.proto;
+    }
+  },
 
-  if (aObj == null) { return {}; }
-  try {
-    Object.getPrototypeOf(aObj);
-  } catch(e) {
-    aObj = aObj.constructor.prototype;
-  }
-  let c = MAX_COMPLETIONS;
-  let names = Object.create(null);   // Using an Object to avoid duplicates.
+  getProperties: function(aObj)
+  {
+    return aObj.getOwnPropertyNames();
+  },
+
+  getProperty: function(aObj, aName, aRootObj)
+  {
+    // This is left unimplemented in favor to DevToolsUtils.getProperty().
+    throw "Unimplemented!";
+  },
+};
 
-  // We need to go up the prototype chain.
-  let ownNames = null;
-  while (aObj !== null) {
-    ownNames = Object.getOwnPropertyNames(aObj);
-    for (let i = 0; i < ownNames.length; i++) {
-      // Filtering happens here.
-      // If we already have it in, no need to append it.
-      if (ownNames[i].indexOf(aOptions.matchProp) != 0 ||
-          ownNames[i] in names) {
-        continue;
-      }
-      c--;
-      if (c < 0) {
-        return names;
-      }
-      // If it is an array index, we can't take it.
-      // This uses a trick: converting a string to a number yields NaN if
-      // the operation failed, and NaN is not equal to itself.
-      if (+ownNames[i] != +ownNames[i]) {
-        names[ownNames[i]] = true;
-      }
+let DebuggerEnvironmentSupport = {
+  chainIterator: function(aObj)
+  {
+    while (aObj) {
+      yield aObj;
+      aObj = aObj.parent;
     }
-    aObj = Object.getPrototypeOf(aObj);
-  }
+  },
+
+  getProperties: function(aObj)
+  {
+    return aObj.names();
+  },
 
-  return names;
-}
+  getProperty: function(aObj, aName)
+  {
+    // TODO: we should use getVariableDescriptor() here - bug 725815.
+    let result = aObj.getVariable(aName);
+    return result === undefined ? null : { value: result };
+  },
+};
 
 
 exports.JSPropertyProvider = JSPropertyProvider;
 })(WebConsoleUtils);
 
 ///////////////////////////////////////////////////////////////////////////////
 // The page errors listener
 ///////////////////////////////////////////////////////////////////////////////