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 id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewerspast
bugs943496
milestone28.0a1
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
 ///////////////////////////////////////////////////////////////////////////////